/* This file is part of GNU Taler (C) 2022 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import { Amounts, TalerError, TranslatedString } from "@gnu-taler/taler-util"; import { useLocalNotification, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { mutate } from "swr"; import { Attention } from "@gnu-taler/web-util/browser"; import { ErrorLoading } from "@gnu-taler/web-util/browser"; import { Loading } from "@gnu-taler/web-util/browser"; import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser"; import { useBankCoreApiContext } from "../../context/config.js"; import { useAccountDetails } from "../../hooks/access.js"; import { useBackendState } from "../../hooks/backend.js"; import { useEstimator } from "../../hooks/circuit.js"; import { TanChannel, undefinedIfEmpty, withRuntimeErrorHandling } from "../../utils.js"; import { LoginForm } from "../LoginForm.js"; import { InputAmount } from "../PaytoWireTransferForm.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; import { ShowLocalNotification } from "@gnu-taler/web-util/browser"; interface Props { account: string; onComplete: (id: string) => void; onCancel: () => void; } type FormType = { isDebit: boolean; amount: string; subject: string; channel: TanChannel; }; type ErrorFrom = { [P in keyof T]+?: string; }; export function CreateCashout({ account: accountName, onComplete, onCancel, }: Props): VNode { const { i18n } = useTranslationContext(); const resultAccount = useAccountDetails(accountName); const { estimateByCredit: calculateFromCredit, estimateByDebit: calculateFromDebit, } = useEstimator(); const { state } = useBackendState() const creds = state.status !== "loggedIn" ? undefined : state const { api, config } = useBankCoreApiContext() const [form, setForm] = useState>({ isDebit: true }); const [notification, notify, handleError] = useLocalNotification() if (!config.have_cashout) { return The bank configuration does not support cashout operations. } if (!config.fiat_currency) { return The bank configuration support cashout operations but there is no fiat currency. } if (!resultAccount) { return } if (resultAccount instanceof TalerError) { return } if (resultAccount.type === "fail") { switch (resultAccount.case) { case "unauthorized": return case "not-found": return default: assertUnreachable(resultAccount) } } // if (resultRatios.type === "fail") { // switch (resultRatios.case) { // case "not-supported": return
cashout operations are not supported
// default: assertUnreachable(resultRatios.case) // } // } // const ratio = resultRatios.body const account = { balance: Amounts.parseOrThrow(resultAccount.body.balance.amount), balanceIsDebit: resultAccount.body.balance.credit_debit_indicator == "debit", debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold) } const zero = Amounts.zeroOfCurrency(account.balance.currency); const limit = account.balanceIsDebit ? Amounts.sub(account.debitThreshold, account.balance).amount : Amounts.add(account.balance, account.debitThreshold).amount; const zeroCalc = { debit: zero, credit: zero, beforeFee: zero }; const [calc, setCalc] = useState(zeroCalc); const sellRate = config.conversion_info?.sell_at_ratio; const sellFee = !config.conversion_info?.sell_out_fee ? zero : Amounts.parseOrThrow( `${account.balance.currency}:${config.conversion_info.sell_out_fee}`, ); if (sellRate === undefined || sellRate < 0) return
error rate
; const safeSellRate = sellRate const amount = Amounts.parseOrThrow( `${!form.isDebit ? config.fiat_currency.name : account.balance.currency}:${!form.amount ? "0" : form.amount }`, ); useEffect(() => { async function doAsync() { await handleError(async () => { const resp = await (form.isDebit ? calculateFromDebit(amount, sellFee, safeSellRate) : calculateFromCredit(amount, sellFee, safeSellRate)); setCalc(resp) }) } doAsync() }, [form.amount, form.isDebit]); const balanceAfter = Amounts.sub(account.balance, calc.debit).amount; function updateForm(newForm: typeof form): void { setForm(newForm); } const errors = undefinedIfEmpty>({ amount: !form.amount ? i18n.str`required` : !amount ? i18n.str`could not be parsed` : Amounts.cmp(limit, calc.debit) === -1 ? i18n.str`balance is not enough` : Amounts.cmp(calc.beforeFee, sellFee) === -1 ? i18n.str`the total amount to transfer does not cover the fees` : Amounts.isZero(calc.credit) ? i18n.str`the total transfer at destination will be zero` : undefined, channel: !form.channel ? i18n.str`required` : undefined, }); return (

New cashout

{ form.subject = e.currentTarget.value; updateForm(structuredClone(form)); }} />
{ form.amount = v; updateForm(structuredClone(form)); }} error={errors?.amount} />
{" "} {Amounts.isZero(sellFee) ? undefined : (
)}
{ e.preventDefault(); form.channel = TanChannel.EMAIL; updateForm(structuredClone(form)); }} /> { e.preventDefault(); form.channel = TanChannel.SMS; updateForm(structuredClone(form)); }} /> { e.preventDefault(); form.channel = TanChannel.FILE; updateForm(structuredClone(form)); }} />

); }