2022-09-23 20:18:18 +02:00
|
|
|
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>
|
|
|
|
*/
|
|
|
|
|
2022-10-14 16:40:38 +02:00
|
|
|
import {
|
|
|
|
AmountJson,
|
|
|
|
Amounts,
|
|
|
|
DepositGroupFees,
|
|
|
|
KnownBankAccountsInfo,
|
|
|
|
parsePaytoUri,
|
|
|
|
PaytoUri,
|
|
|
|
stringifyPaytoUri,
|
|
|
|
} from "@gnu-taler/taler-util";
|
2022-09-23 20:18:18 +02:00
|
|
|
import { useState } from "preact/hooks";
|
|
|
|
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
|
|
|
|
import * as wxApi from "../../wxApi.js";
|
|
|
|
import { Props, State } from "./index.js";
|
|
|
|
|
2022-10-14 16:40:38 +02:00
|
|
|
export function useComponentState(
|
|
|
|
{ amount: amountStr, currency: currencyStr, onCancel, onSuccess }: Props,
|
|
|
|
api: typeof wxApi,
|
|
|
|
): State {
|
2022-09-23 20:18:18 +02:00
|
|
|
const parsed = amountStr === undefined ? undefined : Amounts.parse(amountStr);
|
|
|
|
const currency = parsed !== undefined ? parsed.currency : currencyStr;
|
|
|
|
|
|
|
|
const hook = useAsyncAsHook(async () => {
|
|
|
|
const { balances } = await api.getBalance();
|
|
|
|
const { accounts } = await api.listKnownBankAccounts(currency);
|
|
|
|
|
|
|
|
return { accounts, balances };
|
|
|
|
});
|
|
|
|
|
|
|
|
const initialValue =
|
|
|
|
parsed !== undefined ? Amounts.stringifyValue(parsed) : "0";
|
|
|
|
// const [accountIdx, setAccountIdx] = useState<number>(0);
|
|
|
|
const [amount, setAmount] = useState(initialValue);
|
|
|
|
|
|
|
|
const [selectedAccount, setSelectedAccount] = useState<
|
|
|
|
PaytoUri | undefined
|
|
|
|
>();
|
|
|
|
|
|
|
|
const [fee, setFee] = useState<DepositGroupFees | undefined>(undefined);
|
|
|
|
const [addingAccount, setAddingAccount] = useState(false);
|
|
|
|
|
|
|
|
if (!currency) {
|
|
|
|
return {
|
|
|
|
status: "amount-or-currency-error",
|
2022-10-14 16:40:38 +02:00
|
|
|
error: undefined,
|
|
|
|
};
|
2022-09-23 20:18:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!hook) {
|
|
|
|
return {
|
|
|
|
status: "loading",
|
|
|
|
error: undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (hook.hasError) {
|
|
|
|
return {
|
|
|
|
status: "loading-error",
|
|
|
|
error: hook,
|
2022-10-14 16:40:38 +02:00
|
|
|
};
|
2022-09-23 20:18:18 +02:00
|
|
|
}
|
|
|
|
const { accounts, balances } = hook.response;
|
|
|
|
|
|
|
|
const parsedAmount = Amounts.parse(`${currency}:${amount}`);
|
|
|
|
|
|
|
|
if (addingAccount) {
|
|
|
|
return {
|
|
|
|
status: "adding-account",
|
|
|
|
error: undefined,
|
|
|
|
currency,
|
|
|
|
onAccountAdded: (p: string) => {
|
|
|
|
updateAccountFromList(p);
|
|
|
|
setAddingAccount(false);
|
2022-10-14 16:40:38 +02:00
|
|
|
hook.retry();
|
2022-09-23 20:18:18 +02:00
|
|
|
},
|
|
|
|
onCancel: () => {
|
|
|
|
setAddingAccount(false);
|
2022-10-14 16:40:38 +02:00
|
|
|
},
|
|
|
|
};
|
2022-09-23 20:18:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const bs = balances.filter((b) => b.available.startsWith(currency));
|
|
|
|
const balance =
|
|
|
|
bs.length > 0
|
|
|
|
? Amounts.parseOrThrow(bs[0].available)
|
|
|
|
: Amounts.getZero(currency);
|
|
|
|
|
|
|
|
if (Amounts.isZero(balance)) {
|
|
|
|
return {
|
|
|
|
status: "no-enough-balance",
|
|
|
|
error: undefined,
|
|
|
|
currency,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (accounts.length === 0) {
|
|
|
|
return {
|
|
|
|
status: "no-accounts",
|
|
|
|
error: undefined,
|
|
|
|
currency,
|
|
|
|
onAddAccount: {
|
2022-10-14 16:40:38 +02:00
|
|
|
onClick: async () => {
|
|
|
|
setAddingAccount(true);
|
|
|
|
},
|
2022-09-23 20:18:18 +02:00
|
|
|
},
|
2022-10-14 16:40:38 +02:00
|
|
|
};
|
2022-09-23 20:18:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const accountMap = createLabelsForBankAccount(accounts);
|
2022-10-14 16:40:38 +02:00
|
|
|
accountMap[""] = "Select one account...";
|
2022-09-23 20:18:18 +02:00
|
|
|
|
|
|
|
async function updateAccountFromList(accountStr: string): Promise<void> {
|
|
|
|
// const newSelected = !accountMap[accountStr] ? undefined : accountMap[accountStr];
|
|
|
|
// if (!newSelected) return;
|
|
|
|
const uri = !accountStr ? undefined : parsePaytoUri(accountStr);
|
|
|
|
setSelectedAccount(uri);
|
|
|
|
if (uri && parsedAmount) {
|
|
|
|
try {
|
|
|
|
const result = await getFeeForAmount(uri, parsedAmount, api);
|
|
|
|
setFee(result);
|
|
|
|
} catch (e) {
|
|
|
|
setFee(undefined);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updateAmount(numStr: string): Promise<void> {
|
|
|
|
setAmount(numStr);
|
|
|
|
const parsed = Amounts.parse(`${currency}:${numStr}`);
|
|
|
|
if (parsed && selectedAccount) {
|
|
|
|
try {
|
|
|
|
const result = await getFeeForAmount(selectedAccount, parsed, api);
|
|
|
|
setFee(result);
|
|
|
|
} catch (e) {
|
|
|
|
setFee(undefined);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const totalFee =
|
|
|
|
fee !== undefined
|
|
|
|
? Amounts.sum([fee.wire, fee.coin, fee.refresh]).amount
|
|
|
|
: Amounts.getZero(currency);
|
|
|
|
|
2022-10-14 16:40:38 +02:00
|
|
|
const totalToDeposit =
|
|
|
|
parsedAmount && fee !== undefined
|
|
|
|
? Amounts.sub(parsedAmount, totalFee).amount
|
|
|
|
: Amounts.getZero(currency);
|
2022-09-23 20:18:18 +02:00
|
|
|
|
|
|
|
const isDirty = amount !== initialValue;
|
|
|
|
const amountError = !isDirty
|
|
|
|
? undefined
|
|
|
|
: !parsedAmount
|
2022-10-14 16:40:38 +02:00
|
|
|
? "Invalid amount"
|
|
|
|
: Amounts.cmp(balance, parsedAmount) === -1
|
|
|
|
? `Too much, your current balance is ${Amounts.stringifyValue(balance)}`
|
|
|
|
: undefined;
|
2022-09-23 20:18:18 +02:00
|
|
|
|
|
|
|
const unableToDeposit =
|
|
|
|
!parsedAmount ||
|
|
|
|
selectedAccount === undefined ||
|
|
|
|
Amounts.isZero(totalToDeposit) ||
|
|
|
|
fee === undefined ||
|
|
|
|
amountError !== undefined;
|
|
|
|
|
|
|
|
async function doSend(): Promise<void> {
|
|
|
|
if (!selectedAccount || !parsedAmount || !currency) return;
|
|
|
|
|
|
|
|
const account = `payto://${selectedAccount.targetType}/${selectedAccount.targetPath}`;
|
|
|
|
const amount = Amounts.stringify(parsedAmount);
|
|
|
|
await api.createDepositGroup(account, amount);
|
|
|
|
onSuccess(currency);
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
status: "ready",
|
|
|
|
error: undefined,
|
|
|
|
currency,
|
|
|
|
amount: {
|
|
|
|
value: String(amount),
|
|
|
|
onInput: updateAmount,
|
|
|
|
error: amountError,
|
|
|
|
},
|
|
|
|
onAddAccount: {
|
2022-10-14 16:40:38 +02:00
|
|
|
onClick: async () => {
|
|
|
|
setAddingAccount(true);
|
|
|
|
},
|
2022-09-23 20:18:18 +02:00
|
|
|
},
|
|
|
|
account: {
|
|
|
|
list: accountMap,
|
|
|
|
value: !selectedAccount ? "" : stringifyPaytoUri(selectedAccount),
|
|
|
|
onChange: updateAccountFromList,
|
|
|
|
},
|
|
|
|
selectedAccount,
|
|
|
|
cancelHandler: {
|
|
|
|
onClick: async () => {
|
|
|
|
onCancel(currency);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
depositHandler: {
|
|
|
|
onClick: unableToDeposit ? undefined : doSend,
|
|
|
|
},
|
|
|
|
totalFee,
|
|
|
|
totalToDeposit,
|
|
|
|
// currentAccount,
|
|
|
|
// parsedAmount,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getFeeForAmount(
|
|
|
|
p: PaytoUri,
|
|
|
|
a: AmountJson,
|
|
|
|
api: typeof wxApi,
|
|
|
|
): Promise<DepositGroupFees> {
|
|
|
|
const account = `payto://${p.targetType}/${p.targetPath}`;
|
|
|
|
const amount = Amounts.stringify(a);
|
|
|
|
return await api.getFeeForDeposit(account, amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function labelForAccountType(id: string) {
|
|
|
|
switch (id) {
|
2022-10-14 16:40:38 +02:00
|
|
|
case "":
|
|
|
|
return "Choose one";
|
|
|
|
case "x-taler-bank":
|
|
|
|
return "Taler Bank";
|
|
|
|
case "bitcoin":
|
|
|
|
return "Bitcoin";
|
|
|
|
case "iban":
|
|
|
|
return "IBAN";
|
|
|
|
default:
|
|
|
|
return id;
|
2022-09-23 20:18:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createLabelsForBankAccount(
|
|
|
|
knownBankAccounts: Array<KnownBankAccountsInfo>,
|
|
|
|
): { [value: string]: string } {
|
2022-10-14 16:40:38 +02:00
|
|
|
const initialList: Record<string, string> = {};
|
2022-09-23 20:18:18 +02:00
|
|
|
if (!knownBankAccounts.length) return initialList;
|
|
|
|
return knownBankAccounts.reduce((prev, cur, i) => {
|
2022-10-14 16:40:38 +02:00
|
|
|
prev[stringifyPaytoUri(cur.uri)] = cur.alias;
|
2022-09-23 20:18:18 +02:00
|
|
|
return prev;
|
|
|
|
}, initialList);
|
|
|
|
}
|