taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 32693c20db860b7543b55595593f51666560da53
parent 35b6e5931ced488031e6e934659278ebd5c63a94
Author: Florian Dold <florian@dold.me>
Date:   Thu, 16 Jan 2025 13:15:24 +0100

fix #9444, test for bank integration API

Diffstat:
Mpackages/taler-harness/src/harness/environments.ts | 69+++++++++------------------------------------------------------------
Mpackages/taler-harness/src/harness/harness.ts | 21++++++++++++++++++++-
Mpackages/taler-harness/src/integrationtests/test-account-restrictions.ts | 9+++++++++
Apackages/taler-harness/src/integrationtests/test-bank-wop.ts | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/test-known-accounts.ts | 12+++++++-----
Mpackages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts | 8++++++++
Mpackages/taler-harness/src/integrationtests/test-kyc-withdrawal-verboten.ts | 16++++++++++++++++
Mpackages/taler-harness/src/integrationtests/test-kyc.ts | 13++++++++++++-
Mpackages/taler-harness/src/integrationtests/test-multiexchange.ts | 1-
Mpackages/taler-harness/src/integrationtests/test-pay-paid.ts | 9++++++---
Mpackages/taler-harness/src/integrationtests/test-payment-abort.ts | 9++++++---
Mpackages/taler-harness/src/integrationtests/test-payment-transient.ts | 9++++++---
Mpackages/taler-harness/src/integrationtests/test-peer-pull-large.ts | 16+++++++++-------
Mpackages/taler-harness/src/integrationtests/test-peer-push-abort.ts | 6+++---
Mpackages/taler-harness/src/integrationtests/test-peer-push-large.ts | 18++++++++++++------
Mpackages/taler-harness/src/integrationtests/test-repurchase.ts | 11+++++++----
Mpackages/taler-harness/src/integrationtests/test-simple-payment.ts | 14+++++++++-----
Mpackages/taler-harness/src/integrationtests/test-stored-backups.ts | 12+++++++-----
Mpackages/taler-harness/src/integrationtests/test-wallet-balance-notifications.ts | 12++++++++++--
Mpackages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts | 9++++++---
Mpackages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-pull.ts | 9++++++---
Mpackages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-push.ts | 11+++++++----
Mpackages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts | 10++++------
Mpackages/taler-harness/src/integrationtests/test-wallet-notifications.ts | 13+++++++++++--
Mpackages/taler-harness/src/integrationtests/test-wallet-refresh-errors.ts | 14++++++++------
Mpackages/taler-harness/src/integrationtests/test-wallet-transactions.ts | 9++++++---
Mpackages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts | 8++++++++
Apackages/taler-harness/src/integrationtests/test-withdrawal-conflict.ts | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/test-withdrawal-external.ts | 8++++++++
Mpackages/taler-harness/src/integrationtests/test-withdrawal-fees.ts | 26+++++++++++++++++++++-----
Mpackages/taler-harness/src/integrationtests/test-withdrawal-flex.ts | 27+++++++++++++++++++++------
Mpackages/taler-harness/src/integrationtests/test-withdrawal-handover.ts | 4++--
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 4++++
Mpackages/taler-util/src/types-taler-wallet.ts | 1-
Mpackages/taler-wallet-core/src/testing.ts | 7+++++++
Mpackages/taler-wallet-core/src/withdraw.ts | 131++++++++++++++++++++++++++++++++++---------------------------------------------
36 files changed, 554 insertions(+), 225 deletions(-)

diff --git a/packages/taler-harness/src/harness/environments.ts b/packages/taler-harness/src/harness/environments.ts @@ -51,6 +51,7 @@ import { TalerProtocolTimestamp, TransactionIdStr, TransactionMajorState, + TransactionMinorState, WalletNotification, WireGatewayApiClient, } from "@gnu-taler/taler-util"; @@ -799,66 +800,6 @@ export interface WithdrawViaBankResult { /** * Withdraw via a bank with the testing API enabled. - * Uses the new notification-based mechanism to wait for the - * operation to finish. - */ -export async function withdrawViaBankV2( - t: GlobalTestState, - p: { - walletClient: WalletClient; - bank: BankService; - exchange: ExchangeServiceInterface; - amount: AmountString | string; - restrictAge?: number; - }, -): Promise<WithdrawViaBankResult> { - const { walletClient: wallet, bank, exchange, amount } = p; - - const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl); - - const user = await bankClient.createRandomBankUser(); - const wop = await bankClient.createWithdrawalOperation(user.username, amount); - - // Hand it to the wallet - - await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, { - talerWithdrawUri: wop.taler_withdraw_uri, - restrictAge: p.restrictAge, - }); - - // Withdraw (AKA select) - - const acceptRes = await wallet.client.call( - WalletApiOperation.AcceptBankIntegratedWithdrawal, - { - exchangeBaseUrl: exchange.baseUrl, - talerWithdrawUri: wop.taler_withdraw_uri, - restrictAge: p.restrictAge, - }, - ); - - const withdrawalFinishedCond = wallet.waitForNotificationCond( - (x) => - x.type === NotificationType.TransactionStateTransition && - x.newTxState.major === TransactionMajorState.Done && - x.transactionId === acceptRes.transactionId, - ); - - // Confirm it - - await bankClient.confirmWithdrawalOperation(user.username, { - withdrawalOperationId: wop.withdrawal_id, - }); - - return { - accountPaytoUri: user.accountPaytoUri, - withdrawalFinishedCond, - transactionId: acceptRes.transactionId, - }; -} - -/** - * Withdraw via a bank with the testing API enabled. * Uses the new Corebank API. */ export async function withdrawViaBankV3( @@ -910,6 +851,14 @@ export async function withdrawViaBankV3( x.transactionId === acceptRes.transactionId, ); + await wallet.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptRes.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + // Confirm it await bankClient2.confirmWithdrawalOperation(user.username, { diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts @@ -77,8 +77,8 @@ import { } from "@gnu-taler/taler-wallet-core/remote"; import { deepStrictEqual } from "assert"; import { ChildProcess, spawn } from "child_process"; -import * as fs from "node:fs"; import * as http from "http"; +import * as fs from "node:fs"; import * as net from "node:net"; import * as path from "node:path"; import * as readline from "readline"; @@ -803,6 +803,15 @@ export class FakebankService const url = `http://localhost:${this.bankConfig.httpPort}/config`; await pingProc(this.proc, url, "bank"); } + + async stop(): Promise<void> { + const bankProc = this.proc; + if (bankProc) { + bankProc.proc.kill("SIGTERM"); + await bankProc.wait(); + this.proc = undefined; + } + } } /** @@ -979,6 +988,15 @@ export class LibeufinBankService const url = `http://localhost:${this.bankConfig.httpPort}/config`; await pingProc(this.proc, url, "libeufin-bank"); } + + async stop(): Promise<void> { + const bankProc = this.proc; + if (bankProc) { + bankProc.proc.kill("SIGTERM"); + await bankProc.wait(); + this.proc = undefined; + } + } } // Use libeufin bank instead of pybank. @@ -991,6 +1009,7 @@ export interface BankServiceHandle { setSuggestedExchange(exchange: ExchangeService, exchangePayto: string): void; start(): Promise<void>; pingUntilAvailable(): Promise<void>; + stop(): Promise<void>; } export type BankService = BankServiceHandle; diff --git a/packages/taler-harness/src/integrationtests/test-account-restrictions.ts b/packages/taler-harness/src/integrationtests/test-account-restrictions.ts @@ -24,6 +24,7 @@ import { NotificationType, TalerCorebankApiClient, TransactionMajorState, + TransactionMinorState, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { @@ -163,6 +164,14 @@ export async function myWithdrawViaBank( x.transactionId === acceptRes.transactionId, ); + await wallet.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptRes.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + // Confirm it await bankClient2.confirmWithdrawalOperation(user.username, { diff --git a/packages/taler-harness/src/integrationtests/test-bank-wop.ts b/packages/taler-harness/src/integrationtests/test-bank-wop.ts @@ -0,0 +1,62 @@ +/* + 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, + narrowOpSuccessOrThrow, + TalerBankIntegrationHttpClient, + TalerCoreBankHttpClient, +} from "@gnu-taler/taler-util"; +import { + createSimpleTestkudosEnvironmentV3, + registerHarnessBankTestUser, +} from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; + +/** + * Test handing over a withdrawal to another wallet. + */ +export async function runBankWopTest(t: GlobalTestState) { + // Set up test environment + + const { bank } = await createSimpleTestkudosEnvironmentV3(t); + + const bankClientNg = new TalerCoreBankHttpClient(bank.corebankApiBaseUrl); + + const bankUser = await registerHarnessBankTestUser(bankClientNg); + + const withdrawalRes = await bankClientNg.createWithdrawal(bankUser, { + amount: "TESTKUDOS:42", + }); + + narrowOpSuccessOrThrow("", withdrawalRes); + + const biClient = new TalerBankIntegrationHttpClient( + `${bank.corebankApiBaseUrl}/taler-integration/`, + ); + + const wopStatus = await biClient.getWithdrawalOperationById( + withdrawalRes.body.withdrawal_id, + ); + narrowOpSuccessOrThrow("", wopStatus); + + console.log(`${j2s(wopStatus)}`); +} + +runBankWopTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-known-accounts.ts b/packages/taler-harness/src/integrationtests/test-known-accounts.ts @@ -17,11 +17,11 @@ /** * Imports. */ -import { j2s } from "@gnu-taler/taler-util"; +import { j2s, TalerCorebankApiClient, TalerCoreBankHttpClient } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useSharedTestkudosEnvironment, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; import { GlobalTestState } from "../harness/harness.js"; @@ -31,14 +31,16 @@ import { GlobalTestState } from "../harness/harness.js"; export async function runKnownAccountsTest(t: GlobalTestState) { // Set up test environment - const { walletClient, bank, exchange, merchant } = + const { walletClient, bank, exchange } = await useSharedTestkudosEnvironment(t); + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts @@ -111,6 +111,14 @@ export async function runKycThresholdWithdrawalTest(t: GlobalTestState) { const withdrawalTxId = acceptResp.transactionId; + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptResp.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + // Confirm it await bankClient.confirmWithdrawalOperation(user.username, { diff --git a/packages/taler-harness/src/integrationtests/test-kyc-withdrawal-verboten.ts b/packages/taler-harness/src/integrationtests/test-kyc-withdrawal-verboten.ts @@ -113,6 +113,14 @@ export async function runKycWithdrawalVerbotenTest(t: GlobalTestState) { // Confirm it + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptResp.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop.withdrawal_id, }); @@ -207,6 +215,14 @@ export async function runKycWithdrawalVerbotenTest(t: GlobalTestState) { // Confirm it + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptResp2.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop2.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/test-kyc.ts b/packages/taler-harness/src/integrationtests/test-kyc.ts @@ -29,7 +29,10 @@ import { import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import * as http from "node:http"; -import { configureCommonKyc, createKycTestkudosEnvironment } from "../harness/environments.js"; +import { + configureCommonKyc, + createKycTestkudosEnvironment, +} from "../harness/environments.js"; import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; const logger = new Logger("test-kyc.ts"); @@ -245,6 +248,14 @@ export async function runKycTest(t: GlobalTestState) { // Confirm it + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: withdrawalTxId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/test-multiexchange.ts b/packages/taler-harness/src/integrationtests/test-multiexchange.ts @@ -36,7 +36,6 @@ import { import { createWalletDaemonWithClient, makeTestPaymentV2, - withdrawViaBankV2, withdrawViaBankV3, } from "../harness/environments.js"; diff --git a/packages/taler-harness/src/integrationtests/test-pay-paid.ts b/packages/taler-harness/src/integrationtests/test-pay-paid.ts @@ -21,13 +21,14 @@ import { ConfirmPayResultType, MerchantApiClient, PreparePayResultType, + TalerCorebankApiClient, URL, codecForMerchantOrderStatusUnpaid, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { createFaultInjectedMerchantTestkudosEnvironment, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; import { FaultInjectionRequestContext } from "../harness/faultInjection.js"; import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; @@ -48,9 +49,11 @@ export async function runPayPaidTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - const wres = await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + const wres = await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange: faultyExchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-payment-abort.ts b/packages/taler-harness/src/integrationtests/test-payment-abort.ts @@ -21,6 +21,7 @@ import { ConfirmPayResultType, MerchantApiClient, PreparePayResultType, + TalerCorebankApiClient, TalerErrorCode, TalerErrorDetail, URL, @@ -30,7 +31,7 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { createFaultInjectedMerchantTestkudosEnvironment, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; import { FaultInjectionRequestContext } from "../harness/faultInjection.js"; import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; @@ -43,9 +44,11 @@ export async function runPaymentAbortTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - const wres = await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + const wres = await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange: faultyExchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-payment-transient.ts b/packages/taler-harness/src/integrationtests/test-payment-transient.ts @@ -21,6 +21,7 @@ import { ConfirmPayResultType, MerchantApiClient, PreparePayResultType, + TalerCorebankApiClient, TalerErrorCode, TalerErrorDetail, URL, @@ -29,7 +30,7 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { createFaultInjectedMerchantTestkudosEnvironment, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; import { FaultInjectionResponseContext } from "../harness/faultInjection.js"; import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; @@ -50,9 +51,11 @@ export async function runPaymentTransientTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - const wres = await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + const wres = await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange: faultyExchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-peer-pull-large.ts b/packages/taler-harness/src/integrationtests/test-peer-pull-large.ts @@ -23,6 +23,7 @@ import { Duration, j2s, NotificationType, + TalerCorebankApiClient, TransactionMajorState, TransactionMinorState, TransactionType, @@ -31,16 +32,16 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig } from "../harness/denomStructures.js"; import { + createSimpleTestkudosEnvironmentV2, + createWalletDaemonWithClient, + withdrawViaBankV3, +} from "../harness/environments.js"; +import { BankServiceHandle, ExchangeService, GlobalTestState, WalletClient, } from "../harness/harness.js"; -import { - createSimpleTestkudosEnvironmentV2, - createWalletDaemonWithClient, - withdrawViaBankV2, -} from "../harness/environments.js"; const coinCommon = { cipher: "RSA" as const, @@ -109,9 +110,10 @@ async function checkNormalPeerPull( wallet2: WalletClient, ): Promise<void> { t.logStep("starting withdrawal"); - const withdrawRes = await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl); + const withdrawRes = await withdrawViaBankV3(t, { walletClient: wallet2, - bank, + bankClient, exchange, amount: "TESTKUDOS:500", }); diff --git a/packages/taler-harness/src/integrationtests/test-peer-push-abort.ts b/packages/taler-harness/src/integrationtests/test-peer-push-abort.ts @@ -26,7 +26,7 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { createSimpleTestkudosEnvironmentV3, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; import { GlobalTestState } from "../harness/harness.js"; @@ -37,9 +37,9 @@ export async function runPeerPushAbortTest(t: GlobalTestState) { const { bank, bankClient, exchange, walletClient } = await createSimpleTestkudosEnvironmentV3(t); - const wres = await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-peer-push-large.ts b/packages/taler-harness/src/integrationtests/test-peer-push-large.ts @@ -22,6 +22,7 @@ import { AmountString, Duration, NotificationType, + TalerCorebankApiClient, TransactionMajorState, TransactionMinorState, TransactionType, @@ -29,13 +30,13 @@ import { j2s, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; +import { CoinConfig } from "../harness/denomStructures.js"; import { createSimpleTestkudosEnvironmentV2, createWalletDaemonWithClient, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; -import { CoinConfig } from "../harness/denomStructures.js"; +import { GlobalTestState } from "../harness/harness.js"; const coinCommon = { cipher: "RSA" as const, @@ -61,7 +62,10 @@ const coinConfigList: CoinConfig[] = [ * Run a test for a multi-batch peer push payment. */ export async function runPeerPushLargeTest(t: GlobalTestState) { - const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t, coinConfigList); + const { bank, exchange } = await createSimpleTestkudosEnvironmentV2( + t, + coinConfigList, + ); let allW1Notifications: WalletNotification[] = []; let allW2Notifications: WalletNotification[] = []; @@ -81,9 +85,11 @@ export async function runPeerPushLargeTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - const withdrawRes = await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + const withdrawRes = await withdrawViaBankV3(t, { walletClient: w1.walletClient, - bank, + bankClient, exchange, amount: "TESTKUDOS:300", }); diff --git a/packages/taler-harness/src/integrationtests/test-repurchase.ts b/packages/taler-harness/src/integrationtests/test-repurchase.ts @@ -21,14 +21,15 @@ import { ConfirmPayResultType, MerchantApiClient, PreparePayResultType, + TalerCorebankApiClient, TalerMerchantApi, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; import { useSharedTestkudosEnvironment, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; +import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; export async function runRepurchaseTest(t: GlobalTestState) { // Set up test environment @@ -38,9 +39,11 @@ export async function runRepurchaseTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-simple-payment.ts b/packages/taler-harness/src/integrationtests/test-simple-payment.ts @@ -17,14 +17,17 @@ /** * Imports. */ +import { + TalerCorebankApiClient, + TalerMerchantApi, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; import { - withdrawViaBankV2, makeTestPaymentV2, useSharedTestkudosEnvironment, + withdrawViaBankV3, } from "../harness/environments.js"; -import { TalerMerchantApi } from "@gnu-taler/taler-util"; +import { GlobalTestState } from "../harness/harness.js"; /** * Run test for basic, bank-integrated withdrawal and payment. @@ -36,10 +39,11 @@ export async function runSimplePaymentTest(t: GlobalTestState) { await useSharedTestkudosEnvironment(t); // Withdraw digital cash into the wallet. + const bankClient = new TalerCorebankApiClient(bank.baseUrl); - await withdrawViaBankV2(t, { + await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-stored-backups.ts b/packages/taler-harness/src/integrationtests/test-stored-backups.ts @@ -17,14 +17,14 @@ /** * Imports. */ +import { TalerCorebankApiClient, TalerMerchantApi } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; import { - withdrawViaBankV2, makeTestPaymentV2, useSharedTestkudosEnvironment, + withdrawViaBankV3, } from "../harness/environments.js"; -import { TalerMerchantApi } from "@gnu-taler/taler-util"; +import { GlobalTestState } from "../harness/harness.js"; /** * Test stored backup wallet-core API. @@ -37,9 +37,11 @@ export async function runStoredBackupsTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - const wres = await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + const wres = await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-balance-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-balance-notifications.ts @@ -19,12 +19,12 @@ */ import { NotificationType, - TalerCorebankApiClient, TransactionMajorState, + TransactionMinorState, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; /** * Test behavior when an order is deleted while the wallet is paying for it. @@ -78,6 +78,14 @@ export async function runWalletBalanceNotificationsTest(t: GlobalTestState) { // Confirm it + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptRes.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts b/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-merchant.ts @@ -20,6 +20,7 @@ import { MerchantApiClient, PreparePayResultType, + TalerCorebankApiClient, j2s, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -29,7 +30,7 @@ import { createSimpleTestkudosEnvironmentV2, createWalletDaemonWithClient, makeTestPaymentV2, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; const coinCommon = { @@ -68,6 +69,8 @@ export async function runWalletBlockedPayMerchantTest(t: GlobalTestState) { coinConfigList, ); + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + // Withdraw digital cash into the wallet. const { walletClient: w1 } = await createWalletDaemonWithClient(t, { @@ -80,9 +83,9 @@ export async function runWalletBlockedPayMerchantTest(t: GlobalTestState) { }, }); - await withdrawViaBankV2(t, { + await withdrawViaBankV3(t, { walletClient: w1, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-pull.ts b/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-pull.ts @@ -22,6 +22,7 @@ import { AmountString, Duration, NotificationType, + TalerCorebankApiClient, TransactionMajorState, TransactionMinorState, TransactionType, @@ -34,7 +35,7 @@ import { createSimpleTestkudosEnvironmentV2, createWalletDaemonWithClient, makeTestPaymentV2, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; const coinCommon = { @@ -95,9 +96,11 @@ export async function runWalletBlockedPayPeerPullTest(t: GlobalTestState) { }, }); - await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + await withdrawViaBankV3(t, { walletClient: w1, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-push.ts b/packages/taler-harness/src/integrationtests/test-wallet-blocked-pay-peer-push.ts @@ -22,19 +22,20 @@ import { AmountString, Duration, NotificationType, + TalerCorebankApiClient, TransactionMajorState, TransactionMinorState, j2s, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig } from "../harness/denomStructures.js"; -import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV2, createWalletDaemonWithClient, makeTestPaymentV2, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; const coinCommon = { cipher: "RSA" as const, @@ -72,6 +73,8 @@ export async function runWalletBlockedPayPeerPushTest(t: GlobalTestState) { coinConfigList, ); + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + // Withdraw digital cash into the wallet. const { walletClient: w1 } = await createWalletDaemonWithClient(t, { @@ -84,9 +87,9 @@ export async function runWalletBlockedPayPeerPushTest(t: GlobalTestState) { }, }); - await withdrawViaBankV2(t, { + await withdrawViaBankV3(t, { walletClient: w1, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts b/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts @@ -27,19 +27,17 @@ import { import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { defaultCoinConfig } from "../harness/denomStructures.js"; import { + createWalletDaemonWithClient, + withdrawViaBankV3, +} from "../harness/environments.js"; +import { BankService, ExchangeService, - FakebankService, GlobalTestState, HarnessExchangeBankAccount, getTestHarnessPaytoForLabel, setupDb, } from "../harness/harness.js"; -import { - createWalletDaemonWithClient, - withdrawViaBankV2, - withdrawViaBankV3, -} from "../harness/environments.js"; /** * Test how the wallet reacts when an exchange unexpectedly updates diff --git a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts @@ -18,10 +18,11 @@ * Imports. */ import { - TalerCorebankApiClient, Duration, NotificationType, + TalerCorebankApiClient, TransactionMajorState, + TransactionMinorState, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; @@ -32,8 +33,8 @@ import { MerchantService, WalletClient, WalletService, - getTestHarnessPaytoForLabel, generateRandomTestIban, + getTestHarnessPaytoForLabel, setupDb, } from "../harness/harness.js"; @@ -189,6 +190,14 @@ export async function runWalletNotificationsTest(t: GlobalTestState) { // Confirm it + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptRes.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-refresh-errors.ts b/packages/taler-harness/src/integrationtests/test-wallet-refresh-errors.ts @@ -17,12 +17,12 @@ /** * Imports. */ -import { AmountString } from "@gnu-taler/taler-util"; +import { AmountString, TalerCorebankApiClient } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig } from "../harness/denomStructures.js"; import { createSimpleTestkudosEnvironmentV2, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; import { GlobalTestState } from "../harness/harness.js"; @@ -60,9 +60,11 @@ export async function runWalletRefreshErrorsTest(t: GlobalTestState) { const { walletClient, bank, exchange, merchant } = await createSimpleTestkudosEnvironmentV2(t, coinConfigList); - const wres = await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + const wres = await withdrawViaBankV3(t, { amount: "TESTKUDOS:5", - bank, + bankClient, exchange, walletClient, }); @@ -107,9 +109,9 @@ export async function runWalletRefreshErrorsTest(t: GlobalTestState) { await walletClient.call(WalletApiOperation.ClearDb, {}); { - const wres = await withdrawViaBankV2(t, { + const wres = await withdrawViaBankV3(t, { amount: "TESTKUDOS:5", - bank, + bankClient, exchange, walletClient, }); diff --git a/packages/taler-harness/src/integrationtests/test-wallet-transactions.ts b/packages/taler-harness/src/integrationtests/test-wallet-transactions.ts @@ -21,13 +21,14 @@ import { AbsoluteTime, Duration, j2s, + TalerCorebankApiClient, TalerMerchantApi, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { makeTestPaymentV2, useSharedTestkudosEnvironment, - withdrawViaBankV2, + withdrawViaBankV3, } from "../harness/environments.js"; import { GlobalTestState } from "../harness/harness.js"; @@ -47,9 +48,11 @@ export async function runWalletTransactionsTest(t: GlobalTestState) { // Withdraw digital cash into the wallet. - await withdrawViaBankV2(t, { + const bankClient = new TalerCorebankApiClient(bank.baseUrl); + + await withdrawViaBankV3(t, { walletClient, - bank, + bankClient, exchange, amount: "TESTKUDOS:20", }); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts @@ -128,6 +128,14 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) { t.logStep("Confirm it") + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: r3.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-conflict.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-conflict.ts @@ -0,0 +1,161 @@ +/* + 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 { + TalerCorebankApiClient, + TransactionIdStr, + TransactionMajorState, + TransactionMinorState, + j2s, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + createSimpleTestkudosEnvironmentV3, + createWalletDaemonWithClient, +} from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; + +/** + * Test two wallets scanning the same taler:// withdraw QR code. + */ +export async function runWithdrawalConflictTest(t: GlobalTestState) { + // Set up test environment + + const { walletClient, bankClient, bank } = + await createSimpleTestkudosEnvironmentV3(t); + + const w2 = await createWalletDaemonWithClient(t, { + name: "w2", + }); + + // Create a withdrawal operation + + const user = await bankClient.createRandomBankUser(); + const userBankClient = new TalerCorebankApiClient(bankClient.baseUrl); + userBankClient.setAuth(user); + const amount = "TESTKUDOS:10"; + const wop = await userBankClient.createWithdrawalOperation( + user.username, + amount, + ); + + const wMainCheckResp = await walletClient.call( + WalletApiOperation.GetWithdrawalDetailsForUri, + { + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); + + t.assertTrue(!!wMainCheckResp.defaultExchangeBaseUrl); + + const wMainPrepareResp = await walletClient.call( + WalletApiOperation.PrepareBankIntegratedWithdrawal, + { + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); + + console.log(`prepareResp: ${j2s(wMainPrepareResp)}`); + + t.assertTrue(!!wMainPrepareResp.transactionId); + + const txns1 = await walletClient.call(WalletApiOperation.GetTransactions, { + sort: "stable-ascending", + }); + console.log(j2s(txns1)); + + const w2PrepareResp = await w2.walletClient.call( + WalletApiOperation.PrepareBankIntegratedWithdrawal, + { + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); + + t.logStep("stopping bank"); + + // Make sure both wallets go into the state where they need + // to register the reserve info with the bank. + await bank.stop(); + + await walletClient.call(WalletApiOperation.ConfirmWithdrawal, { + transactionId: wMainPrepareResp.transactionId, + amount, + exchangeBaseUrl: wMainCheckResp.defaultExchangeBaseUrl, + }); + + t.assertTrue(!!w2PrepareResp.info.defaultExchangeBaseUrl); + + // Also let the second wallet confirm! + await w2.walletClient.call(WalletApiOperation.ConfirmWithdrawal, { + transactionId: w2PrepareResp.transactionId, + amount, + exchangeBaseUrl: w2PrepareResp.info.defaultExchangeBaseUrl, + }); + + t.logStep("withdrawals-confirmed-by-wallets"); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: wMainPrepareResp.transactionId as TransactionIdStr, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankRegisterReserve, + }, + }); + + await w2.walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: w2PrepareResp.transactionId as TransactionIdStr, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankRegisterReserve, + }, + }); + + await bank.start(); + + // One wallet will succeed, another one will have an aborted transaction. + // Order is non-determinstic. + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: wMainPrepareResp.transactionId as TransactionIdStr, + txState: [ + { + major: TransactionMajorState.Done, + }, + { + major: TransactionMajorState.Aborted, + minor: TransactionMinorState.CompletedByOtherWallet, + }, + ], + }); + + await w2.walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: w2PrepareResp.transactionId as TransactionIdStr, + txState: [ + { + major: TransactionMajorState.Done, + }, + { + major: TransactionMajorState.Aborted, + minor: TransactionMinorState.CompletedByOtherWallet, + }, + ], + }); +} + +runWithdrawalConflictTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts @@ -91,6 +91,14 @@ export async function runWithdrawalExternalTest(t: GlobalTestState) { t.logStep("confirming withdrawal operation"); + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptResp.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(bankUser.username, { withdrawalOperationId: wop.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts @@ -17,7 +17,12 @@ /** * Imports. */ -import { TalerCorebankApiClient, j2s } from "@gnu-taler/taler-util"; +import { + TalerCorebankApiClient, + TransactionMajorState, + TransactionMinorState, + j2s, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig } from "../harness/denomStructures.js"; import { @@ -171,13 +176,24 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) { t.logStep("Withdraw (AKA select)"); - await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { - exchangeBaseUrl: exchange.baseUrl, - talerWithdrawUri: wop.taler_withdraw_uri, - }); + const acceptResp = await wallet.client.call( + WalletApiOperation.AcceptBankIntegratedWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); t.logStep("Confirm it"); + await wallet.client.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptResp.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, + }); + await bankClient.confirmWithdrawalOperation(user.username, { withdrawalOperationId: wop.withdrawal_id, }); diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-flex.ts @@ -17,10 +17,14 @@ /** * Imports. */ -import { j2s } from "@gnu-taler/taler-util"; +import { + j2s, + TransactionMajorState, + TransactionMinorState, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV3 } from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; /** * Run test for bank-integrated withdrawal with flexible amount, @@ -53,10 +57,21 @@ export async function runWithdrawalFlexTest(t: GlobalTestState) { // Withdraw - await walletClient.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { - exchangeBaseUrl: exchange.baseUrl, - talerWithdrawUri: wop.taler_withdraw_uri, - amount: "TESTKUDOS:10", + const acceptResp = await walletClient.call( + WalletApiOperation.AcceptBankIntegratedWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + talerWithdrawUri: wop.taler_withdraw_uri, + amount: "TESTKUDOS:10", + }, + ); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptResp.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }, }); await bankClient.confirmWithdrawalOperation(user.username, { diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-handover.ts @@ -25,11 +25,11 @@ import { j2s, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironmentV3, createWalletDaemonWithClient, } from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; /** * Test handing over a withdrawal to another wallet. @@ -37,7 +37,7 @@ import { export async function runWithdrawalHandoverTest(t: GlobalTestState) { // Set up test environment - const { walletClient, bankClient, exchange } = + const { walletClient, bankClient } = await createSimpleTestkudosEnvironmentV3(t); // Do one normal withdrawal with the new split API diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -34,6 +34,7 @@ import { runAgeRestrictionsMerchantTest } from "./test-age-restrictions-merchant import { runAgeRestrictionsMixedMerchantTest } from "./test-age-restrictions-mixed-merchant.js"; import { runAgeRestrictionsPeerTest } from "./test-age-restrictions-peer.js"; import { runBankApiTest } from "./test-bank-api.js"; +import { runBankWopTest } from "./test-bank-wop.js"; import { runClaimLoopTest } from "./test-claim-loop.js"; import { runClauseSchnorrTest } from "./test-clause-schnorr.js"; import { runCurrencyScopeTest } from "./test-currency-scope.js"; @@ -144,6 +145,7 @@ import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank.js"; import { runWithdrawalAmountTest } from "./test-withdrawal-amount.js"; import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated.js"; import { runWithdrawalCashacceptorTest } from "./test-withdrawal-cashacceptor.js"; +import { runWithdrawalConflictTest } from "./test-withdrawal-conflict.js"; import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js"; import { runWithdrawalExternalTest } from "./test-withdrawal-external.js"; import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js"; @@ -298,6 +300,8 @@ const allTests: TestMainFunction[] = [ runKycAmpFailureTest, runPeerPushAbortTest, runWithdrawalCashacceptorTest, + runWithdrawalConflictTest, + runBankWopTest, ]; export interface TestRunSpec { diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -969,7 +969,6 @@ export interface BankWithdrawDetails { } export interface AcceptWithdrawalResponse { - reservePub: string; confirmTransferUrl?: string; transactionId: TransactionIdStr; } diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts @@ -141,6 +141,13 @@ export async function withdrawTestBalance( isForeignAccount: req.useForeignAccount, }); + // We need to wait until the wallet sent the reserve information + // to the bank, otherwise the confirmation in the bank would fail. + await waitTransactionState(wex, acceptResp.transactionId, { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.BankConfirmTransfer, + }); + await corebankClient.confirmWithdrawalOperation(bankUser.username, { withdrawalOperationId: wresp.withdrawal_id, }); diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -2786,7 +2786,7 @@ async function registerReserveWithBank( wex: WalletExecutionContext, withdrawalGroupId: string, isFlexibleAmount: boolean, -): Promise<void> { +): Promise<TaskRunResult> { const withdrawalGroup = await wex.db.runReadOnlyTx( { storeNames: ["withdrawalGroups"] }, async (tx) => { @@ -2799,7 +2799,7 @@ async function registerReserveWithBank( case WithdrawalGroupStatus.PendingRegisteringBank: break; default: - return; + return TaskRunResult.finished(); } if ( withdrawalGroup.wgInfo.withdrawalType != WithdrawalRecordType.BankIntegrated @@ -2808,7 +2808,9 @@ async function registerReserveWithBank( } const bankInfo = withdrawalGroup.wgInfo.bankInfo; if (!bankInfo) { - return; + throw Error( + "BUG: Tried to register reserve with bank, but bankInfo unavailable", + ); } const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri); const reqBody = { @@ -2826,6 +2828,21 @@ async function registerReserveWithBank( timeout: getReserveRequestTimeout(withdrawalGroup), cancellationToken: wex.cancellationToken, }); + + switch (httpResp.status) { + case HttpStatusCode.Conflict: + await ctx.transition({}, async (rec) => { + switch (rec?.status) { + case WithdrawalGroupStatus.PendingRegisteringBank: { + rec.status = WithdrawalGroupStatus.FailedBankAborted; + return TransitionResult.transition(rec); + } + } + return TransitionResult.stay(); + }); + return TaskRunResult.progress(); + } + const status = await readSuccessResponseJsonOrThrow( httpResp, codecForBankWithdrawalOperationPostResponse(), @@ -2852,6 +2869,8 @@ async function registerReserveWithBank( r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url; return TransitionResult.transition(r); }); + + return TaskRunResult.progress(); } async function transitionBankAborted( @@ -2916,11 +2935,40 @@ async function processBankRegisterReserve( cancellationToken: wex.cancellationToken, }); + if (statusResp.status >= 400 && statusResp.status >= 499) { + let newSt: WithdrawalGroupStatus | undefined; + // FIXME: Consider looking at the exact status code + switch (statusResp.status) { + case HttpStatusCode.NotFound: + newSt = WithdrawalGroupStatus.FailedAbortingBank; + break; + case HttpStatusCode.Conflict: + newSt = WithdrawalGroupStatus.AbortedOtherWallet; + break; + default: + break; + } + if (newSt != null) { + // FIXME: Consider looking at the exact status code + await ctx.transition({}, async (rec) => { + switch (rec?.status) { + case WithdrawalGroupStatus.PendingRegisteringBank: { + rec.status = WithdrawalGroupStatus.FailedBankAborted; + return TransitionResult.transition(rec); + } + } + return TransitionResult.stay(); + }); + return TaskRunResult.progress(); + } + } + const status = await readSuccessResponseJsonOrThrow( statusResp, codecForBankWithdrawalOperationStatus(), ); + // Legacy libeufin-bank behavior if (status.status === "aborted") { return transitionBankAborted(ctx); } @@ -2929,8 +2977,11 @@ async function processBankRegisterReserve( const isFlexibleAmount = status.amount == null; - await registerReserveWithBank(wex, withdrawalGroupId, isFlexibleAmount); - return TaskRunResult.progress(); + return await registerReserveWithBank( + wex, + withdrawalGroupId, + isFlexibleAmount, + ); } async function processReserveBankStatus( @@ -3670,21 +3721,7 @@ export async function confirmWithdrawal( wex.ws.notify(res.exchangeNotif); } - if (pending) { - wex.oc.observe({ - type: ObservabilityEventType.Message, - contents: "waiting for withdrawal operation to be registered with bank", - }); - await waitWithdrawalRegistered(wex, ctx); - wex.oc.observe({ - type: ObservabilityEventType.Message, - contents: - "done waiting for withdrawal operation to be registered with bank", - }); - } - return { - reservePub: withdrawalGroup.reservePub, transactionId: req.transactionId as TransactionIdStr, confirmTransferUrl: withdrawalGroup.wgInfo.bankInfo.confirmUrl, }; @@ -3788,67 +3825,11 @@ export async function acceptBankIntegratedWithdrawal( ); return { - reservePub: newWithdrawralGroup.reservePub, confirmTransferUrl: p.info.confirmTransferUrl, transactionId: p.transactionId, }; } -async function waitWithdrawalRegistered( - wex: WalletExecutionContext, - ctx: WithdrawTransactionContext, -): Promise<void> { - await genericWaitForState(wex, { - async checkState(): Promise<boolean> { - const { withdrawalRec, retryRec } = await wex.db.runReadOnlyTx( - { storeNames: ["withdrawalGroups", "operationRetries"] }, - async (tx) => { - return { - withdrawalRec: await tx.withdrawalGroups.get(ctx.withdrawalGroupId), - retryRec: await tx.operationRetries.get(ctx.taskId), - }; - }, - ); - - if (!withdrawalRec) { - throw Error("withdrawal not found anymore"); - } - - switch (withdrawalRec.status) { - case WithdrawalGroupStatus.FailedBankAborted: - throw TalerError.fromDetail( - TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, - {}, - ); - case WithdrawalGroupStatus.PendingKyc: - case WithdrawalGroupStatus.PendingQueryingStatus: - case WithdrawalGroupStatus.PendingReady: - case WithdrawalGroupStatus.Done: - case WithdrawalGroupStatus.PendingWaitConfirmBank: - return true; - case WithdrawalGroupStatus.PendingRegisteringBank: - break; - default: { - if (retryRec) { - if (retryRec.lastError) { - throw TalerError.fromUncheckedDetail(retryRec.lastError); - } else { - throw Error("withdrawal unexpectedly pending"); - } - } - } - } - return false; - }, - filterNotification(notif) { - return ( - notif.type === NotificationType.TransactionStateTransition && - notif.transactionId === ctx.transactionId - ); - }, - }); -} - async function fetchAccount( wex: WalletExecutionContext, instructedAmount: AmountJson,