From 689d58a5c79e98fec733c3cda31146620d8bde1d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 10 Jun 2024 15:25:51 -0300 Subject: fix #8886 --- packages/bank-ui/src/hooks/preferences.ts | 2 +- packages/bank-ui/src/pages/OperationState/state.ts | 14 ++- .../bank-ui/src/pages/PaytoWireTransferForm.tsx | 73 +++++++++++- packages/bank-ui/src/pages/WalletWithdrawForm.tsx | 13 ++- packages/bank-ui/src/settings.json | 2 +- .../taler-util/src/http-client/bank-integration.ts | 2 +- packages/taler-util/src/http-client/types.ts | 64 ++++++++--- packages/taler-util/src/wallet-types.ts | 13 ++- packages/taler-wallet-core/src/dbless.ts | 2 +- packages/taler-wallet-core/src/withdraw.ts | 69 ++++++++--- .../src/cta/Withdraw/index.ts | 3 + .../src/cta/Withdraw/state.ts | 128 ++++++++++++++------- .../src/cta/Withdraw/test.ts | 24 +++- .../src/cta/Withdraw/views.tsx | 7 +- packages/taler-wallet-webextension/src/wxApi.ts | 2 +- 15 files changed, 320 insertions(+), 98 deletions(-) (limited to 'packages') diff --git a/packages/bank-ui/src/hooks/preferences.ts b/packages/bank-ui/src/hooks/preferences.ts index bb3dcb153..9c60456c7 100644 --- a/packages/bank-ui/src/hooks/preferences.ts +++ b/packages/bank-ui/src/hooks/preferences.ts @@ -104,7 +104,7 @@ export function getLabelForPreferences( case "showInstallWallet": return i18n.str`Show install wallet first`; case "fastWithdrawal": - return i18n.str`Use fast withdrawal form`; + return i18n.str`Set the withdrawal amount in the wallet`; case "showDebugInfo": return i18n.str`Show debug info`; } diff --git a/packages/bank-ui/src/pages/OperationState/state.ts b/packages/bank-ui/src/pages/OperationState/state.ts index 19c097d18..32d4fea7a 100644 --- a/packages/bank-ui/src/pages/OperationState/state.ts +++ b/packages/bank-ui/src/pages/OperationState/state.ts @@ -18,6 +18,7 @@ import { Amounts, HttpStatusCode, TalerCoreBankErrorsByMethod, + TalerCorebankApi, TalerError, assertUnreachable, parsePaytoUri, @@ -58,9 +59,16 @@ export function useComponentState({ // FIXME: if amount is not enough use balance const parsedAmount = Amounts.parseOrThrow(`${currency}:${amount}`); if (!creds) return; - const resp = await bank.createWithdrawal(creds, { - amount: Amounts.stringify(parsedAmount), - }); + const params: TalerCorebankApi.BankAccountCreateWithdrawalRequest = + settings.fastWithdrawal + ? { + suggested_amount: Amounts.stringify(parsedAmount), + } + : { + amount: Amounts.stringify(parsedAmount), + }; + + const resp = await bank.createWithdrawal(creds, params); if (resp.type === "fail") { setFailure(resp); return; diff --git a/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx b/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx index 3bf891504..bb408e497 100644 --- a/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx +++ b/packages/bank-ui/src/pages/PaytoWireTransferForm.tsx @@ -111,6 +111,17 @@ export function PaytoWireTransferForm({ ? ("x-taler-bank" as const) : ("iban" as const); + const wireFee = + config.wire_transfer_fees === undefined + ? Amounts.zeroOfCurrency(config.currency) + // Amounts.parseOrThrow("YEIN:2.5") + : Amounts.parseOrThrow(config.wire_transfer_fees); + + const amountAfterFee = + !parsedAmount || Amounts.cmp(parsedAmount, wireFee) < 1 + ? undefined + : Amounts.sub(parsedAmount, wireFee).amount; + const errorsWire = undefinedIfEmpty({ account: !account ? i18n.str`Required` @@ -124,7 +135,7 @@ export function PaytoWireTransferForm({ ? i18n.str`Required` : !parsedAmount ? i18n.str`Not valid` - : validateAmount(parsedAmount, limit, i18n), + : validateAmount(parsedAmount, limit, wireFee, i18n), }); const parsed = !rawPaytoInput ? undefined : parsePaytoUri(rawPaytoInput); @@ -134,7 +145,7 @@ export function PaytoWireTransferForm({ ? i18n.str`Required` : !parsed ? i18n.str`Does not follow the pattern` - : validateRawPayto(parsed, limit, url.host, i18n, paytoType), + : validateRawPayto(parsed, limit, wireFee, url.host, i18n, paytoType), }); async function doSend() { @@ -479,9 +490,9 @@ export function PaytoWireTransferForm({ e.preventDefault(); }} > -
+
{!isRawPayto ? ( -
+
{(() => { switch (paytoType) { case "x-taler-bank": { @@ -623,6 +634,53 @@ export function PaytoWireTransferForm({
)}
+
+
+
+
+ {Amounts.isZero(wireFee) ? undefined : ( + +
+
+ + Fee + +
+
+ +
+
+
+ )}{" "} + {!parsedAmount || !amountAfterFee ? undefined : ( + +
+
+ + + Recipient will receive + + +
+
+ +
+
+
+ )} +
+
+
+
{routeCancel ? ( { - const resp = await api.createWithdrawal(creds, { - amount: Amounts.stringify(parsedAmount), - }); + const params: TalerCorebankApi.BankAccountCreateWithdrawalRequest = + settings.fastWithdrawal + ? { + suggested_amount: Amounts.stringify(parsedAmount), + } + : { + amount: Amounts.stringify(parsedAmount), + }; + const resp = await api.createWithdrawal(creds, params); if (resp.type === "ok") { const uri = parseWithdrawUri(resp.body.taler_withdraw_uri); if (!uri) { diff --git a/packages/bank-ui/src/settings.json b/packages/bank-ui/src/settings.json index b1df52568..df5fe75ce 100644 --- a/packages/bank-ui/src/settings.json +++ b/packages/bank-ui/src/settings.json @@ -1,5 +1,5 @@ { - "backendBaseURL": "http://bank-fir.taler.test:1180/", + "backendBaseURL": "http://bank.taler.test:1180/", "simplePasswordForRandomAccounts": true, "allowRandomAccountCreation": true, "bankName": "Taler DEVELOPMENT Bank", diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts index 08dab58e2..e07b6c5fa 100644 --- a/packages/taler-util/src/http-client/bank-integration.ts +++ b/packages/taler-util/src/http-client/bank-integration.ts @@ -50,7 +50,7 @@ export type TalerBankIntegrationErrorsByMethod< * The API is used by the wallets. */ export class TalerBankIntegrationHttpClient { - public readonly PROTOCOL_VERSION = "2:0:2"; + public readonly PROTOCOL_VERSION = "2:0:0"; httpLib: HttpRequestLibrary; diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 94f2c7518..f05bbc91a 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -1312,6 +1312,7 @@ export const codecForBankWithdrawalOperationStatus = ), ) .property("amount", codecOptional(codecForAmountString())) + .property("suggested_amount", codecOptional(codecForAmountString())) .property("card_fees", codecOptional(codecForAmountString())) .property("sender_wire", codecOptional(codecForPaytoString())) .property("suggested_exchange", codecOptional(codecForString())) @@ -2029,12 +2030,23 @@ export namespace TalerBankIntegrationApi { // confirmed: the transfer has been confirmed and registered by the bank status: WithdrawalOperationStatus; + // Currency used for the withdrawal. + // MUST be present when amount is absent. + // @since v2, may become mandatory in the future. + currency?: string; + // Amount that will be withdrawn with this operation // (raw amount without fee considerations). Only // given once the amount is fixed and cannot be changed. // Optional since **vC2EC**. amount?: AmountString | undefined; + // Suggestion for the amount to be withdrawn with this + // operation. Given if a suggestion was made but the + // user may still change the amount. + // Optional since **vC2EC**. + suggested_amount?: AmountString | undefined; + // Maximum amount that the wallet can choose to withdraw. // Only applicable when the amount is not fixed. // @since **vC2EC**. @@ -2046,17 +2058,13 @@ export namespace TalerBankIntegrationApi { // @since **vC2EC** card_fees?: AmountString | undefined; - // Bank account of the customer that is withdrawing, as a - // payto URI. + // Bank account of the customer that is debiting, as an + // RFC 8905 payto URI. sender_wire?: PaytoString; - // Suggestion for the amount to be withdrawn with this - // operation. Given if a suggestion was made but the - // user may still change the amount. - // Optional since **vC2EC**. - suggested_amount?: AmountString | undefined; - - // Suggestion for an exchange given by the bank. + // Base URL of the suggested exchange. The bank may have + // neither a suggestion nor a requirement for the exchange. + // This value is typically set in the bank's configuration. suggested_exchange?: string; // Base URL of an exchange that must be used. Optional, @@ -2067,7 +2075,8 @@ export namespace TalerBankIntegrationApi { // URL that the user needs to navigate to in order to // complete some final confirmation (e.g. 2FA). - // It may contain withdrawal operation id + // Only applicable when status is selected or pending. + // It may contain the withdrawal operation id. confirm_transfer_url?: string; // Wire transfer types supported by the bank. @@ -2077,13 +2086,15 @@ export namespace TalerBankIntegrationApi { // only non-null if status is selected or confirmed. selected_reserve_pub?: string; - // Exchange account selected by the wallet + // Exchange account selected by the wallet; // only non-null if status is selected or confirmed. + // @since **v1** selected_exchange_account?: string; } export interface BankWithdrawalOperationPostRequest { - // Reserve public key. + // Reserve public key that should become the wire transfer + // subject to fund the withdrawal. reserve_pub: string; // Payto address of the exchange selected for the withdrawal. @@ -2106,7 +2117,7 @@ export namespace TalerBankIntegrationApi { // URL that the user needs to navigate to in order to // complete some final confirmation (e.g. 2FA). // - // Only applicable when status is selected. + // Only applicable when status is selected or pending. // It may contain withdrawal operation id confirm_transfer_url?: string; } @@ -2181,12 +2192,31 @@ export namespace TalerCorebankApi { // Default to 'iban' is missing // @since v4, may become mandatory in the future. wire_type: string; + + // Wire transfer execution fees. + // @since v4, will become mandatory in the next version. + wire_transfer_fees?: AmountString; } export interface BankAccountCreateWithdrawalRequest { - // Amount to withdraw. - amount: AmountString; + // Amount to withdraw. If given, the wallet + // cannot change the amount. + // Optional since **vC2EC**. + amount?: AmountString; + + // Suggested amount to withdraw. The wallet can + // still change the suggestion. + // @since **vC2EC** + suggested_amount?: AmountString; + + // The non-Taler card fees the customer will have + // to pay to the account owner, bank and/or + // payment service provider + // they are using to make this withdrawal. + // @since **vC2EC** + card_fees?: AmountString; } + export interface BankAccountCreateWithdrawalResponse { // ID of the withdrawal, can be used to view/modify the withdrawal operation. withdrawal_id: string; @@ -2529,10 +2559,6 @@ export namespace TalerCorebankApi { export interface CashoutInfo { cashout_id: number; - /** - * @deprecated since 4, use new 2fa - */ - status?: "pending" | "aborted" | "confirmed"; } export interface GlobalCashouts { // Every string represents a cash-out operation ID. diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 66b1e9769..a7aa4f863 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -988,9 +988,12 @@ export interface BankWithdrawDetails { status: WithdrawalOperationStatus; currency: string; amount: AmountJson | undefined; + editableAmount: boolean; + maxAmount: AmountJson | undefined; wireFee: AmountJson | undefined; senderWire?: string; - suggestedExchange?: string; + exchange?: string; + editableExchange: boolean; confirmTransferUrl?: string; wireTypes: string[]; operationId: string; @@ -2364,7 +2367,11 @@ export interface WithdrawUriInfoResponse { confirmTransferUrl?: string; currency: string; amount: AmountString | undefined; + editableAmount: boolean; + maxAmount: AmountString | undefined; + wireFee: AmountString | undefined; defaultExchangeBaseUrl?: string; + editableExchange: boolean; possibleExchanges: ExchangeListItem[]; } @@ -2383,7 +2390,11 @@ export const codecForWithdrawUriInfoResponse = ), ) .property("amount", codecOptional(codecForAmountString())) + .property("maxAmount", codecOptional(codecForAmountString())) + .property("wireFee", codecOptional(codecForAmountString())) .property("currency", codecForString()) + .property("editableAmount", codecForBoolean()) + .property("editableExchange", codecForBoolean()) .property("defaultExchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .property("possibleExchanges", codecForList(codecForExchangeListItem())) .build("WithdrawUriInfoResponse"); diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index d3085ecb4..ec9655e6f 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -123,7 +123,7 @@ export async function topupReserveWithBank(args: TopupReserveWithBankArgs) { ); const bankInfo = await getBankWithdrawalInfo(http, wopi.taler_withdraw_uri); const bankStatusUrl = getBankStatusUrl(wopi.taler_withdraw_uri); - if (!bankInfo.suggestedExchange) { + if (!bankInfo.exchange) { throw Error("no suggested exchange"); } const plainPaytoUris = diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 6aa3b186a..0434aefc2 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -44,6 +44,7 @@ import { Duration, EddsaPrivateKeyString, ExchangeBatchWithdrawRequest, + ExchangeListItem, ExchangeUpdateStatus, ExchangeWireAccount, ExchangeWithdrawBatchResponse, @@ -150,6 +151,7 @@ import { getExchangePaytoUri, getExchangeWireDetailsInTx, listExchanges, + lookupExchangeByUri, markExchangeUsed, } from "./exchanges.js"; import { DbAccess } from "./query.js"; @@ -886,7 +888,7 @@ export async function getBankWithdrawalInfo( TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE, { bankProtocolVersion: config.version, - walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, + walletProtocolVersion: bankApi.PROTOCOL_VERSION, }, "bank integration protocol version not compatible with wallet", ); @@ -901,15 +903,36 @@ export async function getBankWithdrawalInfo( } const { body: status } = resp; + const maxAmount = + status.max_amount === undefined + ? undefined + : Amounts.parseOrThrow(status.max_amount); + let amount: AmountJson | undefined; - if (status.amount) { + let editableAmount = false; + if (status.amount !== undefined) { amount = Amounts.parseOrThrow(status.amount); + } else { + amount = + status.suggested_amount === undefined + ? undefined + : Amounts.parseOrThrow(status.suggested_amount); + editableAmount = true; } + let wireFee: AmountJson | undefined; if (status.card_fees) { wireFee = Amounts.parseOrThrow(status.card_fees); } + let exchange: string | undefined = undefined; + let editableExchange = false; + if (status.required_exchange !== undefined) { + exchange = status.required_exchange; + } else { + exchange = status.suggested_exchange; + editableExchange = true; + } return { operationId: uriResult.withdrawalOperationId, apiBaseUrl: uriResult.bankIntegrationApiBaseUrl, @@ -918,7 +941,10 @@ export async function getBankWithdrawalInfo( wireFee, confirmTransferUrl: status.confirm_transfer_url, senderWire: status.sender_wire, - suggestedExchange: status.suggested_exchange, + exchange, + editableAmount, + editableExchange, + maxAmount, wireTypes: status.wire_types, status: status.status, }; @@ -2328,39 +2354,52 @@ export async function getWithdrawalDetailsForUri( logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`); const info = await getBankWithdrawalInfo(wex.http, talerWithdrawUri); logger.trace(`got bank info`); - if (info.suggestedExchange) { + if (info.exchange) { try { // If the exchange entry doesn't exist yet, // it'll be created as an ephemeral entry. - await fetchFreshExchange(wex, info.suggestedExchange); + await fetchFreshExchange(wex, info.exchange); } catch (e) { // We still continued if it failed, as other exchanges might be available. // We don't want to fail if the bank-suggested exchange is broken/offline. logger.trace( - `querying bank-suggested exchange (${info.suggestedExchange}) failed`, + `querying bank-suggested exchange (${info.exchange}) failed`, ); } } const currency = info.currency; - const listExchangesResp = await listExchanges(wex); - const possibleExchanges = listExchangesResp.exchanges.filter((x) => { - return ( - x.currency === currency && - (x.exchangeUpdateStatus === ExchangeUpdateStatus.Ready || - x.exchangeUpdateStatus === ExchangeUpdateStatus.ReadyUpdate) - ); - }); + let possibleExchanges: ExchangeListItem[]; + if (!info.editableExchange && info.exchange !== undefined) { + const ex: ExchangeListItem = await lookupExchangeByUri(wex, { + exchangeBaseUrl: info.exchange, + }); + possibleExchanges = [ex]; + } else { + const listExchangesResp = await listExchanges(wex); + + possibleExchanges = listExchangesResp.exchanges.filter((x) => { + return ( + x.currency === currency && + (x.exchangeUpdateStatus === ExchangeUpdateStatus.Ready || + x.exchangeUpdateStatus === ExchangeUpdateStatus.ReadyUpdate) + ); + }); + } return { operationId: info.operationId, confirmTransferUrl: info.confirmTransferUrl, status: info.status, currency, + editableAmount: info.editableAmount, + editableExchange: info.editableExchange, + maxAmount: info.maxAmount ? Amounts.stringify(info.maxAmount) : undefined, amount: info.amount ? Amounts.stringify(info.amount) : undefined, - defaultExchangeBaseUrl: info.suggestedExchange, + defaultExchangeBaseUrl: info.exchange, possibleExchanges, + wireFee: info.wireFee ? Amounts.stringify(info.wireFee) : undefined, }; } diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts index 026a879df..af1ef213b 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/index.ts @@ -96,12 +96,15 @@ export namespace State { currentExchange: ExchangeListItem; amount: AmountFieldHandler; + editableAmount: boolean; + bankFee: AmountJson; withdrawalFee: AmountJson; toBeReceived: AmountJson; doWithdrawal: ButtonHandler; doSelectExchange: ButtonHandler; + editableExchange: boolean; chooseCurrencies: string[]; selectedCurrency: string; diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts index da3b1eeb2..f8e27e688 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts @@ -185,10 +185,16 @@ export function useComponentStateFromParams({ cancel, onSuccess, undefined, - chosenAmount, - chosenAmount.currency, - exchangeList, - exchangeByTalerUri, + { + amount: chosenAmount, + currency: chosenAmount.currency, + maxAmount: Amounts.zeroOfCurrency(chosenAmount.currency), + bankFee: Amounts.zeroOfCurrency(chosenAmount.currency), + editableAmount: true, + editableExchange: true, + exchange: exchangeByTalerUri, + exchangeList: exchangeList, + }, setUpdatedExchangeByUser, ); } @@ -215,13 +221,7 @@ export function useComponentStateFromURI({ WalletApiOperation.PrepareBankIntegratedWithdrawal, { talerWithdrawUri }, ); - const { - amount, - defaultExchangeBaseUrl, - possibleExchanges, - confirmTransferUrl, - status, - } = uriInfo.info; + const { status } = uriInfo.info; const txInfo = await api.wallet.call( WalletApiOperation.GetTransactionById, { @@ -232,12 +232,8 @@ export function useComponentStateFromURI({ talerWithdrawUri, status, transactionId: uriInfo.transactionId, - currency: uriInfo.info.currency, + bankWithdrawalInfo: uriInfo.info, txInfo: txInfo, - confirmTransferUrl, - amount: !amount ? undefined : Amounts.parseOrThrow(amount), - thisExchange: defaultExchangeBaseUrl, - exchanges: possibleExchanges, }; }); @@ -277,9 +273,22 @@ export function useComponentStateFromURI({ const uri = uriInfoHook.response.talerWithdrawUri; const txId = uriInfoHook.response.transactionId; - const infoAmount = uriInfoHook.response.amount; - const defaultExchange = uriInfoHook.response.thisExchange; - const exchangeList = uriInfoHook.response.exchanges; + const bwi = uriInfoHook.response.bankWithdrawalInfo; + + const amount = + bwi.amount === undefined + ? Amounts.zeroOfCurrency(bwi.currency) + : Amounts.parseOrThrow(bwi.amount); + + const maxAmount = + bwi.maxAmount === undefined + ? Amounts.zeroOfCurrency(bwi.currency) + : Amounts.parseOrThrow(bwi.maxAmount); + + const bankFee = + bwi.wireFee === undefined + ? Amounts.zeroOfCurrency(bwi.currency) + : Amounts.parseOrThrow(bwi.wireFee); async function doManagedWithdraw( exchange: string, @@ -309,7 +318,7 @@ export function useComponentStateFromURI({ return { status: "already-completed", operationState: uriInfoHook.response.status, - confirmTransferUrl: uriInfoHook.response.confirmTransferUrl, + confirmTransferUrl: bwi.confirmTransferUrl, thisWallet: info.txState.major === TransactionMajorState.Pending, redirectToTx: () => onSuccess(info.transactionId), error: undefined, @@ -322,15 +331,32 @@ export function useComponentStateFromURI({ cancel, onSuccess, uri, - infoAmount, - uriInfoHook.response.currency, - exchangeList, - defaultExchange, + { + amount, + bankFee, + maxAmount, + currency: bwi.currency, + editableAmount: bwi.editableAmount, + editableExchange: bwi.editableExchange, + exchange: bwi.defaultExchangeBaseUrl, + exchangeList: bwi.possibleExchanges, + }, setUpdatedExchangeByUser, ); }, []); } +type WithdrawalInfo = { + currency: string; + amount: AmountJson; + bankFee: AmountJson; + maxAmount: AmountJson; + editableAmount: boolean; + exchange: string | undefined; + editableExchange: boolean; + exchangeList: ExchangeListItem[]; +}; + type ManualOrManagedWithdrawFunction = ( exchange: string, ageRestricted: number | undefined, @@ -342,17 +368,14 @@ function exchangeSelectionState( cancel: () => Promise, onSuccess: (txid: string) => Promise, talerWithdrawUri: string | undefined, - infoAmount: AmountJson | undefined, - currency: string, - exchangeList: ExchangeListItem[], - exchangeSuggestedByTheBank: string | undefined, + wInfo: WithdrawalInfo, onExchangeUpdated: (ex: string) => void, ): RecursiveState { const api = useBackendContext(); const selectedExchange = useSelectedExchange({ - currency: currency, - defaultExchange: exchangeSuggestedByTheBank, - list: exchangeList, + currency: wInfo.currency, + defaultExchange: wInfo.exchange, + list: wInfo.exchangeList, }); const current = @@ -365,9 +388,9 @@ function exchangeSelectionState( } }, [current]); - const safeAmount = !infoAmount - ? Amounts.zeroOfCurrency(currency) - : infoAmount; + const safeAmount = wInfo.amount + ? wInfo.amount + : Amounts.zeroOfCurrency(wInfo.currency); const [choosenAmount, setChoosenAmount] = useState(safeAmount); if (selectedExchange.status !== "ready") { @@ -383,7 +406,9 @@ function exchangeSelectionState( const [ageRestricted, setAgeRestricted] = useState(0); const currentExchange = selectedExchange.selected; - const [selectedCurrency, setSelectedCurrency] = useState(currency); + const [selectedCurrency, setSelectedCurrency] = useState( + wInfo.currency, + ); /** * With the exchange and amount, ask the wallet the information * about the withdrawal @@ -456,6 +481,8 @@ function exchangeSelectionState( ).amount; const toBeReceived = amountHook.response.amount.effective; + const bankFee = wInfo.amount; + const ageRestrictionOptions = amountHook.response.ageRestrictionOptions?.reduce( (p, c) => ({ ...p, [c]: i18n.str`under ${c}` }), @@ -499,13 +526,26 @@ function exchangeSelectionState( amount: Amounts.parseOrThrow(convAccount.transferAmount!), }; + const amountError = Amounts.isZero(choosenAmount) + ? i18n.str`should be greater than zero` + : Amounts.cmp(choosenAmount, wInfo.maxAmount) === -1 + ? i18n.str`choose a lower value` + : undefined; + return { status: "success", error: undefined, - doSelectExchange: selectedExchange.doSelect, + doSelectExchange: { + onClick: wInfo.editableExchange + ? selectedExchange.doSelect.onClick + : undefined, + }, + editableAmount: wInfo.editableAmount, + editableExchange: wInfo.editableExchange, currentExchange, toBeReceived, chooseCurrencies, + bankFee, selectedCurrency, changeCurrency: (s) => { setSelectedCurrency(s); @@ -514,16 +554,20 @@ function exchangeSelectionState( withdrawalFee, amount: { value: choosenAmount, - onInput: pushAlertOnError(async (v) => { - setChoosenAmount(v); - }), + onInput: wInfo.editableAmount + ? pushAlertOnError(async (v) => { + setChoosenAmount(v); + }) + : undefined, + error: amountError, }, talerWithdrawUri, ageRestriction, doWithdrawal: { - onClick: doingWithdraw - ? undefined - : pushAlertOnError(doWithdrawAndCheckError), + onClick: + doingWithdraw && !amountError + ? undefined + : pushAlertOnError(doWithdrawAndCheckError), }, cancel, }; diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts index 5bbf5f6c8..bce5f71e3 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts @@ -119,7 +119,11 @@ describe("Withdraw CTA states", () => { currency: "ARS", amount: "EUR:2" as AmountString, possibleExchanges: [], - } + editableAmount: false, + editableExchange: false, + maxAmount: "ARS:1", + wireFee: "ARS:0", + }, }, ); @@ -162,7 +166,11 @@ describe("Withdraw CTA states", () => { amount: "ARS:2" as AmountString, possibleExchanges: exchanges, defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, - } + editableAmount: false, + editableExchange: false, + maxAmount: "ARS:1", + wireFee: "ARS:0", + }, }, ); handler.addWalletCallResponse( @@ -176,7 +184,7 @@ describe("Withdraw CTA states", () => { scopeInfo: { currency: "ARS", type: ScopeType.Exchange, - url: "http://asd" + url: "http://asd", }, withdrawalAccountsList: [], ageRestrictionOptions: [], @@ -236,6 +244,10 @@ describe("Withdraw CTA states", () => { amount: "ARS:2" as AmountString, possibleExchanges: exchangeWithNewTos, defaultExchangeBaseUrl: exchangeWithNewTos[0].exchangeBaseUrl, + editableAmount: false, + editableExchange: false, + maxAmount: "ARS:1", + wireFee: "ARS:0", }, ); handler.addWalletCallResponse( @@ -248,7 +260,7 @@ describe("Withdraw CTA states", () => { scopeInfo: { currency: "ARS", type: ScopeType.Exchange, - url: "http://asd" + url: "http://asd", }, tosAccepted: false, withdrawalAccountsList: [], @@ -267,6 +279,10 @@ describe("Withdraw CTA states", () => { amount: "ARS:2" as AmountString, possibleExchanges: exchanges, defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl, + editableAmount: false, + editableExchange: false, + maxAmount: "ARS:1", + wireFee: "ARS:0", }, ); diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx index a4917446d..86d7248a4 100644 --- a/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx +++ b/packages/taler-wallet-webextension/src/cta/Withdraw/views.tsx @@ -19,6 +19,7 @@ import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; import { Amount } from "../../components/Amount.js"; import { AmountField } from "../../components/AmountField.js"; +import { EnabledBySettings } from "../../components/EnabledBySettings.js"; import { Part } from "../../components/Part.js"; import { QR } from "../../components/QR.js"; import { SelectList } from "../../components/SelectList.js"; @@ -38,7 +39,6 @@ import { getAmountWithFee, } from "../../wallet/Transaction.js"; import { State } from "./index.js"; -import { EnabledBySettings } from "../../components/EnabledBySettings.js"; export function FinalStateOperation(state: State.AlreadyCompleted): VNode { const { i18n } = useTranslationContext(); @@ -174,6 +174,11 @@ export function SuccessView(state: State.Success): VNode { kind="neutral" big /> + {state.editableAmount ? ( + + + + ) : undefined} {state.chooseCurrencies.length > 0 ? (

diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 4394a982f..47b466fcd 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -55,7 +55,7 @@ import { WalletActivityTrack } from "./wxBackend.js"; const logger = new Logger("wxApi"); -export const WALLET_CORE_SUPPORTED_VERSION = "4:0:0" +export const WALLET_CORE_SUPPORTED_VERSION = "5:0:0" export interface ExtendedPermissionsResponse { newValue: boolean; -- cgit v1.2.3