gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-typescript-core] branch master updated: fix #9444, test for bank


From: gnunet
Subject: [taler-typescript-core] branch master updated: fix #9444, test for bank integration API
Date: Thu, 16 Jan 2025 13:15:38 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository taler-typescript-core.

The following commit(s) were added to refs/heads/master by this push:
     new 32693c20d fix #9444, test for bank integration API
32693c20d is described below

commit 32693c20db860b7543b55595593f51666560da53
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu Jan 16 13:15:24 2025 +0100

    fix #9444, test for bank integration API
---
 packages/taler-harness/src/harness/environments.ts |  69 ++-------
 packages/taler-harness/src/harness/harness.ts      |  21 ++-
 .../integrationtests/test-account-restrictions.ts  |   9 ++
 .../src/integrationtests/test-bank-wop.ts          |  62 ++++++++
 .../src/integrationtests/test-known-accounts.ts    |  12 +-
 .../test-kyc-threshold-withdrawal.ts               |   8 +
 .../test-kyc-withdrawal-verboten.ts                |  16 ++
 .../taler-harness/src/integrationtests/test-kyc.ts |  13 +-
 .../src/integrationtests/test-multiexchange.ts     |   1 -
 .../src/integrationtests/test-pay-paid.ts          |   9 +-
 .../src/integrationtests/test-payment-abort.ts     |   9 +-
 .../src/integrationtests/test-payment-transient.ts |   9 +-
 .../src/integrationtests/test-peer-pull-large.ts   |  16 +-
 .../src/integrationtests/test-peer-push-abort.ts   |   6 +-
 .../src/integrationtests/test-peer-push-large.ts   |  18 ++-
 .../src/integrationtests/test-repurchase.ts        |  11 +-
 .../src/integrationtests/test-simple-payment.ts    |  14 +-
 .../src/integrationtests/test-stored-backups.ts    |  12 +-
 .../test-wallet-balance-notifications.ts           |  12 +-
 .../test-wallet-blocked-pay-merchant.ts            |   9 +-
 .../test-wallet-blocked-pay-peer-pull.ts           |   9 +-
 .../test-wallet-blocked-pay-peer-push.ts           |  11 +-
 .../test-wallet-exchange-update.ts                 |  10 +-
 .../integrationtests/test-wallet-notifications.ts  |  13 +-
 .../integrationtests/test-wallet-refresh-errors.ts |  14 +-
 .../integrationtests/test-wallet-transactions.ts   |   9 +-
 .../test-withdrawal-bank-integrated.ts             |   8 +
 .../integrationtests/test-withdrawal-conflict.ts   | 161 +++++++++++++++++++++
 .../integrationtests/test-withdrawal-external.ts   |   8 +
 .../src/integrationtests/test-withdrawal-fees.ts   |  26 +++-
 .../src/integrationtests/test-withdrawal-flex.ts   |  27 +++-
 .../integrationtests/test-withdrawal-handover.ts   |   4 +-
 .../src/integrationtests/testrunner.ts             |   4 +
 packages/taler-util/src/types-taler-wallet.ts      |   1 -
 packages/taler-wallet-core/src/testing.ts          |   7 +
 packages/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
index 3ab521653..17af401b4 100644
--- 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";
@@ -797,66 +798,6 @@ export interface WithdrawViaBankResult {
   transactionId: string;
 }
 
-/**
- * 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.
@@ -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
index 483f5e402..0aacf6280 100644
--- 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
index d7eaea914..34b4d77eb 100644
--- 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
new file mode 100644
index 000000000..a07ed9920
--- /dev/null
+++ 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
index 8fe58f4de..dc460a664 100644
--- 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
index c6f8c329b..00a3171dc 100644
--- 
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
index 97bfeab0d..df71c5f08 100644
--- 
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
index 41e05b148..bc0b1b82d 100644
--- 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
index c03948f62..0d53cbf25 100644
--- 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
index a3ebd63e7..878480c4f 100644
--- 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
index 7b655a147..4302b2610 100644
--- 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
index b9f1b5f6d..2601c498a 100644
--- 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
index faaebfa3a..4d07cb988 100644
--- 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,
@@ -30,17 +31,17 @@ import {
 } from "@gnu-taler/taler-util";
 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
index 54ec2fad8..456c53eea 100644
--- 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
index 1ca042fa7..c10b049b1 100644
--- 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
index 585e4433e..3f80bbf33 100644
--- 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
index ab7ec5859..242b9df5b 100644
--- 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
index ed3dbf177..f812076c3 100644
--- 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
index bdeaf3e68..6c273fa68 100644
--- 
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
index 47fb34eac..893e3e7dd 100644
--- 
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
index 282c119a2..d1f84bb89 100644
--- 
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
index 2637e76e5..695eb8c2d 100644
--- 
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
index 2cbf5a533..904b27eb4 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts
@@ -26,20 +26,18 @@ import {
 } from "@gnu-taler/taler-util";
 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
index d7002ba20..650024491 100644
--- 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
index a773e10eb..f4b78c2d7 100644
--- 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
index 49fcce92d..4c05d95ef 100644
--- 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
index c2f73fb9c..7976869cd 100644
--- 
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
new file mode 100644
index 000000000..576890801
--- /dev/null
+++ 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
index 596b9f7a2..97e78679a 100644
--- 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
index 6fa15cc95..3eb3fd903 100644
--- 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
index 9263d6a75..8d7d770a0 100644
--- 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
index 16c564eab..7402705b0 100644
--- 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
index 7becdb0c6..eea077935 100644
--- 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
index 55611e00f..5291f304f 100644
--- 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
index ecbe1e6df..5917c28e5 100644
--- 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
index d20f76fe7..d469fa4b1 100644
--- 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,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]