/* 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 { AmountJson, Amounts, HttpStatusCode, TranslatedString, } from "@gnu-taler/taler-util"; import { HttpResponsePaginated, RequestError, useTranslationContext, } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Cashouts } from "../components/Cashouts/index.js"; import { useBackendContext } from "../context/backend.js"; import { ErrorMessage, usePageContext } from "../context/pageState.js"; import { useAccountDetails } from "../hooks/access.js"; import { useCashoutDetails, useCircuitAccountAPI, useRatiosAndFeeConfig, } from "../hooks/circuit.js"; import { TanChannel, undefinedIfEmpty } from "../utils.js"; import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js"; import { ErrorBanner } from "./BankFrame.js"; import { LoginForm } from "./LoginForm.js"; import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js"; interface Props { onClose: () => void; onRegister: () => void; onLoadNotOk: (error: HttpResponsePaginated) => VNode; } export function BusinessAccount({ onClose, onLoadNotOk, onRegister, }: Props): VNode { const { i18n } = useTranslationContext(); const { pageStateSetter } = usePageContext(); const backend = useBackendContext(); const [updatePassword, setUpdatePassword] = useState(false); const [newCashout, setNewcashout] = useState(false); const [showCashoutDetails, setShowCashoutDetails] = useState< string | undefined >(); function showInfoMessage(info: TranslatedString): void { pageStateSetter((prev) => ({ ...prev, info, })); } if (backend.state.status === "loggedOut") { return ; } if (newCashout) { return ( { setNewcashout(false); }} onComplete={(id) => { setNewcashout(false); setShowCashoutDetails(id); }} /> ); } if (showCashoutDetails) { return ( { setShowCashoutDetails(undefined); }} /> ); } if (updatePassword) { return ( { showInfoMessage(i18n.str`Password changed`); setUpdatePassword(false); }} onClear={() => { setUpdatePassword(false); }} /> ); } return (
{ showInfoMessage(i18n.str`Account updated`); }} onChangePassword={() => { setUpdatePassword(true); }} onClear={onClose} />

{i18n.str`Latest cashouts`}

{ setShowCashoutDetails(id); }} />

{ e.preventDefault(); setNewcashout(true); }} />
); } interface PropsCashout { account: string; onComplete: (id: string) => void; onCancel: () => void; onLoadNotOk: (error: HttpResponsePaginated) => VNode; } type FormType = { isDebit: boolean; amount: string; subject: string; channel: TanChannel; }; type ErrorFrom = { [P in keyof T]+?: string; }; function CreateCashout({ account, onComplete, onCancel, onLoadNotOk, }: PropsCashout): VNode { const { i18n } = useTranslationContext(); const ratiosResult = useRatiosAndFeeConfig(); const result = useAccountDetails(account); const [error, saveError] = useState(); const [form, setForm] = useState>({}); const { createCashout } = useCircuitAccountAPI(); if (!result.ok) return onLoadNotOk(result); if (!ratiosResult.ok) return onLoadNotOk(ratiosResult); const config = ratiosResult.data; const maybeBalance = Amounts.parse(result.data.balance.amount); if (!maybeBalance) return
error
; const balance = maybeBalance; const zero = Amounts.zeroOfCurrency(balance.currency); const sellRate = config.ratios_and_fees.sell_at_ratio; const sellFee = !config.ratios_and_fees.sell_out_fee ? zero : Amounts.fromFloat(config.ratios_and_fees.sell_out_fee, balance.currency); const fiatCurrency = config.ratios_and_fees.fiat_currency; if (!sellRate || sellRate < 0) return
error rate
; function truncate(a: AmountJson): AmountJson { const str = Amounts.stringify(a); const idx = str.indexOf("."); if (idx === -1) return a; const truncated = str.substring(0, idx + 3); console.log(str, truncated); return Amounts.parseOrThrow(truncated); } const amount = Amounts.parse(`${balance.currency}:${form.amount}`); const amount_debit = !amount ? zero : form.isDebit ? amount : truncate(Amounts.divide(Amounts.add(amount, sellFee).amount, sellRate)); const credit_before_fee = !amount ? zero : form.isDebit ? truncate(Amounts.divide(amount, 1 / sellRate)) : Amounts.add(amount, sellFee).amount; const __amount_credit = Amounts.sub(credit_before_fee, sellFee).amount; const amount_credit = Amounts.parseOrThrow( `${fiatCurrency}:${Amounts.stringifyValue(__amount_credit)}`, ); const balanceAfter = Amounts.sub(balance, amount_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(balance, amount_debit) === -1 ? i18n.str`balance is not enough` : Amounts.cmp(credit_before_fee, sellFee) === -1 ? i18n.str`amount is not enough` : Amounts.isZero(amount_credit) ? i18n.str`amount is not enough` : undefined, channel: !form.channel ? i18n.str`required` : undefined, }); return (
{error && ( saveError(undefined)} /> )}

New cashout

{ form.subject = e.currentTarget.value; updateForm(structuredClone(form)); }} />
  { form.amount = e.currentTarget.value; updateForm(structuredClone(form)); }} />  
 
 
 
{" "} {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)); }} />

); } interface ShowCashoutProps { id: string; onCancel: () => void; onLoadNotOk: (error: HttpResponsePaginated) => VNode; } export function ShowCashoutDetails({ id, onCancel, onLoadNotOk, }: ShowCashoutProps): VNode { const { i18n } = useTranslationContext(); const result = useCashoutDetails(id); const { abortCashout, confirmCashout } = useCircuitAccountAPI(); const [code, setCode] = useState(undefined); const [error, saveError] = useState(); if (!result.ok) return onLoadNotOk(result); const errors = undefinedIfEmpty({ code: !code ? i18n.str`required` : undefined, }); const isPending = String(result.data.status).toUpperCase() === "PENDING"; return (

Cashout details {id}

{error && ( saveError(undefined)} /> )}
{isPending ? (
{ setCode(e.currentTarget.value); }} />
) : undefined}

{isPending ? (
 
) : (
)}
); }