diff options
-rw-r--r-- | packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts | 15 | ||||
-rw-r--r-- | packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts | 70 | ||||
-rw-r--r-- | packages/taler-harness/src/integrationtests/testrunner.ts | 4 | ||||
-rw-r--r-- | packages/taler-util/src/bank-api-client.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/types.ts | 4 | ||||
-rw-r--r-- | packages/taler-util/src/taler-types.ts | 4 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 31 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 1 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/shepherd.ts | 8 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 5 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/withdraw.ts | 58 |
11 files changed, 166 insertions, 36 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts index 1c65de7d9..8a2268231 100644 --- a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts @@ -90,7 +90,10 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) { await exchange.addBankAccount("1", { accountName: exchangeBankUsername, accountPassword: exchangeBankPassword, - wireGatewayApiBaseUrl: new URL("accounts/exchange/taler-wire-gateway/", bank.baseUrl).href, + wireGatewayApiBaseUrl: new URL( + "accounts/exchange/taler-wire-gateway/", + bank.baseUrl, + ).href, accountPaytoUri: exchangePaytoUri, }); @@ -133,10 +136,7 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) { const user = await bankClient.createRandomBankUser(); bankClient.setAuth(user); - const wop = await bankClient.createWithdrawalOperation( - user.username, - amount, - ); + const wop = await bankClient.createWithdrawalOperation(user.username, amount); // Hand it to the wallet @@ -149,10 +149,13 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) { console.log(j2s(details)); + const myAmount = details.amount; + t.assertTrue(!!myAmount); + const amountDetails = await wallet.client.call( WalletApiOperation.GetWithdrawalDetailsForAmount, { - amount: details.amount, + amount: myAmount, exchangeBaseUrl: details.possibleExchanges[0].exchangeBaseUrl, }, ); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts new file mode 100644 index 000000000..ffc7249b8 --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts @@ -0,0 +1,70 @@ +/* + This file is part of GNU Taler + (C) 2020 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/> + */ + +/** + * Imports. + */ +import { j2s } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; +import { createSimpleTestkudosEnvironmentV3 } from "../harness/helpers.js"; + +/** + * Run test for bank-integrated withdrawal with flexible amount, + * i.e. the amount is chosen by the wallet. + */ +export async function runWithdrawalFlexTest(t: GlobalTestState) { + // Set up test environment + + const { walletClient, bankClient, exchange } = + await createSimpleTestkudosEnvironmentV3(t); + + // Create a withdrawal operation + const user = await bankClient.createRandomBankUser(); + bankClient.setAuth(user); + const wop = await bankClient.createWithdrawalOperation( + user.username, + undefined, + ); + + const r1 = await walletClient.call( + WalletApiOperation.GetWithdrawalDetailsForUri, + { + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); + + console.log(j2s(r1)); + + // Withdraw + + const r2 = await walletClient.call( + WalletApiOperation.AcceptBankIntegratedWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + talerWithdrawUri: wop.taler_withdraw_uri, + amount: "TESTKUDOS:10", + }, + ); + + await bankClient.confirmWithdrawalOperation(user.username, { + withdrawalOperationId: wop.withdrawal_id, + }); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {}); +} + +runWithdrawalFlexTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index eb2ae7fa6..4588310b1 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -113,14 +113,15 @@ import { runWalletRefreshTest } from "./test-wallet-refresh.js"; import { runWalletWirefeesTest } from "./test-wallet-wirefees.js"; import { runWallettestingTest } from "./test-wallettesting.js"; import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank.js"; +import { runWithdrawalAmountTest } from "./test-withdrawal-amount.js"; import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated.js"; import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js"; import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js"; import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js"; +import { runWithdrawalFlexTest } from "./test-withdrawal-flex.js"; import { runWithdrawalHandoverTest } from "./test-withdrawal-handover.js"; import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js"; import { runWithdrawalManualTest } from "./test-withdrawal-manual.js"; -import { runWithdrawalAmountTest } from "./test-withdrawal-amount.js"; /** * Test runner. @@ -232,6 +233,7 @@ const allTests: TestMainFunction[] = [ runPeerPushLargeTest, runWithdrawalHandoverTest, runWithdrawalAmountTest, + runWithdrawalFlexTest, ]; export interface TestRunSpec { diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts index e9f442af6..e1409087f 100644 --- a/packages/taler-util/src/bank-api-client.ts +++ b/packages/taler-util/src/bank-api-client.ts @@ -385,7 +385,7 @@ export class TalerCorebankApiClient { async createWithdrawalOperation( user: string, - amount: string, + amount: string | undefined, ): Promise<WithdrawalOperationInfo> { const url = new URL(`accounts/${user}/withdrawals`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index edddf7d94..9268f6387 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -1311,7 +1311,7 @@ export const codecForBankWithdrawalOperationStatus = codecForConstString("confirmed"), ), ) - .property("amount", codecForAmountString()) + .property("amount", codecOptional(codecForAmountString())) .property("sender_wire", codecOptional(codecForPaytoString())) .property("suggested_exchange", codecOptional(codecForString())) .property("confirm_transfer_url", codecOptional(codecForURL())) @@ -2030,7 +2030,7 @@ export namespace TalerBankIntegrationApi { // Amount that will be withdrawn with this operation // (raw amount without fee considerations). - amount: AmountString; + amount: AmountString | undefined; // Bank account of the customer that is withdrawing, as a // payto URI. diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts index e2536b74a..66f98ea9a 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/taler-types.ts @@ -978,7 +978,7 @@ export class WithdrawOperationStatusResponse { aborted: boolean; - amount: string; + amount: string | undefined; sender_wire?: string; @@ -1557,7 +1557,7 @@ export const codecForWithdrawOperationStatusResponse = .property("selection_done", codecForBoolean()) .property("transfer_done", codecForBoolean()) .property("aborted", codecForBoolean()) - .property("amount", codecForString()) + .property("amount", codecOptional(codecForString())) .property("sender_wire", codecOptional(codecForString())) .property("suggested_exchange", codecOptional(codecForString())) .property("confirm_transfer_url", codecOptional(codecForString())) diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index d472af187..9301a9723 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -663,11 +663,11 @@ export interface CoinDumpJson { withdrawal_reserve_pub: string | undefined; coin_status: CoinStatus; spend_allocation: - | { - id: string; - amount: AmountString; - } - | undefined; + | { + id: string; + amount: AmountString; + } + | undefined; /** * Information about the age restriction */ @@ -801,7 +801,7 @@ export const codecForPreparePayResultPaymentPossible = ) .build("PreparePayResultPaymentPossible"); -export interface BalanceDetails { } +export interface BalanceDetails {} /** * Detailed reason for why the wallet's balance is insufficient. @@ -984,7 +984,8 @@ export interface PreparePayResultAlreadyConfirmed { export interface BankWithdrawDetails { status: WithdrawalOperationStatus; - amount: AmountJson; + currency: string; + amount: AmountJson | undefined; senderWire?: string; suggestedExchange?: string; confirmTransferUrl?: string; @@ -1883,6 +1884,13 @@ export interface AcceptBankIntegratedWithdrawalRequest { talerWithdrawUri: string; exchangeBaseUrl: string; forcedDenomSel?: ForcedDenomSel; + /** + * Amount to withdraw. + * If the bank's withdrawal operation uses a fixed amount, + * this field must either be left undefined or its value must match + * the amount from the withdrawal operation. + */ + amount?: AmountString; restrictAge?: number; } @@ -1892,6 +1900,7 @@ export const codecForAcceptBankIntegratedWithdrawalRequest = .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("talerWithdrawUri", codecForString()) .property("forcedDenomSel", codecForAny()) + .property("amount", codecOptional(codecForAmountString())) .property("restrictAge", codecOptional(codecForNumber())) .build("AcceptBankIntegratedWithdrawalRequest"); @@ -2047,7 +2056,7 @@ export interface CheckPayTemplateRequest { export type CheckPayTemplateReponse = { templateDetails: TalerMerchantApi.WalletTemplateDetails; supportedCurrencies: string[]; -} +}; export const codecForCheckPayTemplateRequest = (): Codec<CheckPayTemplateRequest> => @@ -2352,7 +2361,8 @@ export interface WithdrawUriInfoResponse { operationId: string; status: WithdrawalOperationStatus; confirmTransferUrl?: string; - amount: AmountString; + currency: string; + amount: AmountString | undefined; defaultExchangeBaseUrl?: string; possibleExchanges: ExchangeListItem[]; } @@ -2371,7 +2381,8 @@ export const codecForWithdrawUriInfoResponse = codecForConstString("confirmed"), ), ) - .property("amount", codecForAmountString()) + .property("amount", codecOptional(codecForAmountString())) + .property("currency", codecForString()) .property("defaultExchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .property("possibleExchanges", codecForList(codecForExchangeListItem())) .build("WithdrawUriInfoResponse"); diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 44c241aed..640d94753 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1439,6 +1439,7 @@ export interface WgInfoBankIntegrated { * a Taler-integrated bank. */ bankInfo: ReserveBankInfo; + /** * Info about withdrawal accounts, possibly including currency conversion. */ diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index dbdd7aac5..d662bd7ae 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -382,7 +382,13 @@ export class TaskSchedulerImpl implements TaskScheduler { }); switch (res.type) { case TaskRunResultType.Error: { - logger.trace(`Shepherd for ${taskId} got error result.`); + if (logger.shouldLogTrace()) { + logger.trace( + `Shepherd for ${taskId} got error result: ${j2s( + res.errorDetail, + )}`, + ); + } const retryRecord = await storePendingTaskError( this.ws, taskId, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index c17a2b467..3455d451b 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -724,7 +724,9 @@ async function dispatchRequestInternal( const req = codecForInitRequest().decode(payload); if (logger.shouldLogTrace()) { - const initType = wex.ws.initCalled ? "repeat initialization" : "first initialization"; + const initType = wex.ws.initCalled + ? "repeat initialization" + : "first initialization"; logger.trace(`init request (${initType}): ${j2s(req)}`); } @@ -997,6 +999,7 @@ async function dispatchRequestInternal( talerWithdrawUri: req.talerWithdrawUri, forcedDenomSel: req.forcedDenomSel, restrictAge: req.restrictAge, + amount: req.amount, }); } case WalletApiOperation.ConfirmWithdrawal: { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 4a7c7873c..16289b1ef 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -857,10 +857,16 @@ export async function getBankWithdrawalInfo( } const { body: status } = resp; + let amount: AmountJson | undefined; + if (status.amount) { + amount = Amounts.parseOrThrow(status.amount); + } + return { operationId: uriResult.withdrawalOperationId, apiBaseUrl: uriResult.bankIntegrationApiBaseUrl, - amount: Amounts.parseOrThrow(status.amount), + currency: config.currency, + amount, confirmTransferUrl: status.confirm_transfer_url, senderWire: status.sender_wire, suggestedExchange: status.suggested_exchange, @@ -2262,7 +2268,7 @@ export async function getWithdrawalDetailsForUri( } } - const currency = Amounts.currencyOf(info.amount); + const currency = info.currency; const listExchangesResp = await listExchanges(wex); const possibleExchanges = listExchangesResp.exchanges.filter((x) => { @@ -2277,7 +2283,8 @@ export async function getWithdrawalDetailsForUri( operationId: info.operationId, confirmTransferUrl: info.confirmTransferUrl, status: info.status, - amount: Amounts.stringify(info.amount), + currency, + amount: info.amount ? Amounts.stringify(info.amount) : undefined, defaultExchangeBaseUrl: info.suggestedExchange, possibleExchanges, }; @@ -2379,6 +2386,7 @@ export function getBankAbortUrl(talerWithdrawUri: string): string { async function registerReserveWithBank( wex: WalletExecutionContext, withdrawalGroupId: string, + isFlexibleAmount: boolean, ): Promise<void> { const withdrawalGroup = await wex.db.runReadOnlyTx( { storeNames: ["withdrawalGroups"] }, @@ -2407,7 +2415,11 @@ async function registerReserveWithBank( const reqBody = { reserve_pub: withdrawalGroup.reservePub, selected_exchange: bankInfo.exchangePaytoUri, - }; + } as any; + if (isFlexibleAmount) { + reqBody.amount = withdrawalGroup.instructedAmount; + } + logger.trace(`isFlexibleAmount: ${isFlexibleAmount}`); logger.info(`registering reserve with bank: ${j2s(reqBody)}`); const httpResp = await wex.http.fetch(bankStatusUrl, { method: "POST", @@ -2516,7 +2528,9 @@ async function processBankRegisterReserve( // FIXME: Put confirm transfer URL in the DB! - await registerReserveWithBank(wex, withdrawalGroupId); + const isFlexibleAmount = status.amount == null; + + await registerReserveWithBank(wex, withdrawalGroupId, isFlexibleAmount); return TaskRunResult.progress(); } @@ -2985,7 +2999,7 @@ export async function confirmWithdrawal( const confirmUrl = withdrawalGroup.wgInfo.bankInfo.confirmUrl; /** - * The only reasong this to be undefined is because it is an old wallet + * The only reason this could be undefined is because it is an old wallet * database before adding the wireType field was added */ let wtypes: string[]; @@ -3025,7 +3039,7 @@ export async function confirmWithdrawal( req.forcedDenomSel, ); - ctx.transition({}, async (rec) => { + await ctx.transition({}, async (rec) => { if (!rec) { return TransitionResult.stay(); } @@ -3060,7 +3074,6 @@ export async function confirmWithdrawal( }); await wex.taskScheduler.resetTaskRetries(ctx.taskId); - wex.taskScheduler.startShepherdTask(ctx.taskId); } /** @@ -3080,6 +3093,7 @@ export async function acceptWithdrawalFromUri( selectedExchange: string; forcedDenomSel?: ForcedDenomSel; restrictAge?: number; + amount?: AmountLike; }, ): Promise<AcceptWithdrawalResponse> { const selectedExchange = req.selectedExchange; @@ -3124,17 +3138,37 @@ export async function acceptWithdrawalFromUri( withdrawInfo.wireTypes, ); + let amount: AmountJson; + if (withdrawInfo.amount == null) { + if (req.amount == null) { + throw Error( + "amount required, as withdrawal operation has flexible amount", + ); + } + amount = Amounts.parseOrThrow(req.amount); + } else { + if ( + req.amount != null && + Amounts.cmp(req.amount, withdrawInfo.amount) != 0 + ) { + throw Error( + "mismatched amount, amount is fixed by bank but client provided different amount", + ); + } + amount = withdrawInfo.amount; + } + const withdrawalAccountList = await fetchWithdrawalAccountInfo( wex, { exchange, - instructedAmount: withdrawInfo.amount, + instructedAmount: amount, }, CancellationToken.CONTINUE, ); const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { - amount: withdrawInfo.amount, + amount, exchangeBaseUrl: req.selectedExchange, wgInfo: { withdrawalType: WithdrawalRecordType.BankIntegrated, @@ -3162,10 +3196,10 @@ export async function acceptWithdrawalFromUri( hintTransactionId: ctx.transactionId, }); - await waitWithdrawalRegistered(wex, ctx); - wex.taskScheduler.startShepherdTask(ctx.taskId); + await waitWithdrawalRegistered(wex, ctx); + return { reservePub: withdrawalGroup.reservePub, confirmTransferUrl: withdrawInfo.confirmTransferUrl, |