gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/05: working #8882


From: gnunet
Subject: [taler-wallet-core] 02/05: working #8882
Date: Fri, 07 Jun 2024 15:44:13 +0200

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

sebasjm pushed a commit to branch master
in repository wallet-core.

commit 60a4640a35be584ee004de5e362f21ed03fa239e
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu May 30 13:16:15 2024 -0300

    working #8882
---
 .../test-timetravel-autorefresh.ts                 |  10 +-
 .../test-withdrawal-bank-integrated.ts             |  21 +-
 .../src/integrationtests/test-withdrawal-fees.ts   |  10 +-
 packages/taler-util/src/transactions-types.ts      |   2 +-
 packages/taler-util/src/wallet-types.ts            |   4 +-
 packages/taler-wallet-core/src/balance.ts          |  12 +
 packages/taler-wallet-core/src/db.ts               |   4 +-
 packages/taler-wallet-core/src/transactions.ts     |  51 +++-
 packages/taler-wallet-core/src/wallet.ts           |   5 +-
 packages/taler-wallet-core/src/withdraw.ts         | 301 ++++++++++++---------
 .../src/components/HistoryItem.tsx                 |   4 +-
 .../src/cta/Withdraw/state.ts                      |  33 +--
 .../src/cta/Withdraw/test.ts                       |   5 +-
 13 files changed, 277 insertions(+), 185 deletions(-)

diff --git 
a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts 
b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
index f3e3802e5..046bd5aed 100644
--- a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
+++ b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
@@ -24,6 +24,7 @@ import {
   NotificationType,
   PreparePayResultType,
   TalerCorebankApiClient,
+  j2s,
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { makeNoFeeCoinConfig } from "../harness/denomStructures.js";
@@ -149,12 +150,15 @@ export async function runTimetravelAutorefreshTest(t: 
GlobalTestState) {
   });
   t.logStep("wait");
   await wres.withdrawalFinishedCond;
-
   const exchangeUpdated1Cond = walletClient.waitForNotificationCond(
     (x) =>
-      x.type === NotificationType.ExchangeStateTransition &&
-      x.exchangeBaseUrl === exchange.baseUrl,
+      {
+        t.logStep(`EXCHANGE UPDATE, ${j2s(x)}`)
+        return x.type === NotificationType.ExchangeStateTransition &&
+        x.exchangeBaseUrl === exchange.baseUrl
+      }
   );
+
   t.logStep("waiting tx");
   await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
   {
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 a13095883..3ec2a3bcd 100644
--- 
a/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts
+++ 
b/packages/taler-harness/src/integrationtests/test-withdrawal-bank-integrated.ts
@@ -46,7 +46,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     "TESTKUDOS:10",
   );
 
-  // Hand it to the wallet
+  t.logStep("Hand it to the wallet")
 
   const r1 = await walletClient.client.call(
     WalletApiOperation.GetWithdrawalDetailsForUri,
@@ -55,7 +55,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     },
   );
 
-  // Withdraw
+  t.logStep("Withdraw")
 
   const r2 = await walletClient.client.call(
     WalletApiOperation.AcceptBankIntegratedWithdrawal,
@@ -65,6 +65,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     },
   );
 
+  t.logStep("wait confirmed")
   const withdrawalBankConfirmedCond = walletClient.waitForNotificationCond(
     (x) => {
       return (
@@ -76,6 +77,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     },
   );
 
+  t.logStep("wait finished")
   const withdrawalFinishedCond = walletClient.waitForNotificationCond((x) => {
     return (
       x.type === NotificationType.TransactionStateTransition &&
@@ -84,6 +86,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     );
   });
 
+  t.logStep("wait withdraw coins")
   const withdrawalReserveReadyCond = walletClient.waitForNotificationCond(
     (x) => {
       return (
@@ -95,7 +98,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     },
   );
 
-  // Do it twice to check idempotency
+  t.logStep("Do it twice to check idempotency")
   const r3 = await walletClient.client.call(
     WalletApiOperation.AcceptBankIntegratedWithdrawal,
     {
@@ -104,9 +107,10 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     },
   );
 
+  t.logStep("stop wirewatch")
   await exchange.stopWirewatch();
 
-  // Check status before withdrawal is confirmed by bank.
+  t.logStep("Check status before withdrawal is confirmed by bank.")
   {
     const txn = await walletClient.client.call(
       WalletApiOperation.GetTransactions,
@@ -122,7 +126,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     t.assertTrue(tx0.withdrawalDetails.reserveIsReady === false);
   }
 
-  // Confirm it
+  t.logStep("Confirm it")
 
   await bankClient.confirmWithdrawalOperation(user.username, {
     withdrawalOperationId: wop.withdrawal_id,
@@ -132,6 +136,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
 
   // Check status after withdrawal is confirmed by bank,
   // but before funds are wired to the exchange.
+  t.logStep("Check status after withdrawal")
   {
     const txn = await walletClient.client.call(
       WalletApiOperation.GetTransactions,
@@ -147,11 +152,13 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
     t.assertTrue(tx0.withdrawalDetails.reserveIsReady === false);
   }
 
+  t.logStep("start wirewatch")
   await exchange.startWirewatch();
 
+  t.logStep("wait reserve")
   await withdrawalReserveReadyCond;
 
-  // Check status after funds were wired.
+  t.logStep("Check status after funds were wired.")
   {
     const txn = await walletClient.client.call(
       WalletApiOperation.GetTransactions,
@@ -169,7 +176,7 @@ export async function runWithdrawalBankIntegratedTest(t: 
GlobalTestState) {
 
   await withdrawalFinishedCond;
 
-  // Check balance
+  t.logStep("Check balance")
 
   const balResp = await walletClient.client.call(
     WalletApiOperation.GetBalances,
diff --git 
a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts 
b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
index 8a2268231..0657d2da7 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
@@ -138,7 +138,7 @@ export async function runWithdrawalFeesTest(t: 
GlobalTestState) {
   bankClient.setAuth(user);
   const wop = await bankClient.createWithdrawalOperation(user.username, 
amount);
 
-  // Hand it to the wallet
+  t.logStep("Hand it to the wallet")
 
   const details = await wallet.client.call(
     WalletApiOperation.GetWithdrawalDetailsForUri,
@@ -165,23 +165,25 @@ export async function runWithdrawalFeesTest(t: 
GlobalTestState) {
   t.assertAmountEquals(amountDetails.amountEffective, "TESTKUDOS:5");
   t.assertAmountEquals(amountDetails.amountRaw, "TESTKUDOS:7.5");
 
+  t.logStep("Complete all pending operations")
+
   await wallet.runPending();
 
-  // Withdraw (AKA select)
+  t.logStep("Withdraw (AKA select)")
 
   await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
     exchangeBaseUrl: exchange.baseUrl,
     talerWithdrawUri: wop.taler_withdraw_uri,
   });
 
-  // Confirm it
+  t.logStep("Confirm it")
 
   await bankClient.confirmWithdrawalOperation(user.username, {
     withdrawalOperationId: wop.withdrawal_id,
   });
   await wallet.runUntilDone();
 
-  // Check balance
+  t.logStep("Check balance")
 
   const balResp = await wallet.client.call(WalletApiOperation.GetBalances, {});
   console.log(j2s(balResp));
diff --git a/packages/taler-util/src/transactions-types.ts 
b/packages/taler-util/src/transactions-types.ts
index cee3de9fa..db2133944 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -324,7 +324,7 @@ export interface TransactionWithdrawal extends 
TransactionCommon {
   /**
    * Exchange of the withdrawal.
    */
-  exchangeBaseUrl: string;
+  exchangeBaseUrl: string | undefined;
 
   /**
    * Amount that got subtracted from the reserve balance.
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index a7cd65fa2..66b1e9769 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1850,18 +1850,16 @@ export interface GetWithdrawalDetailsForAmountRequest {
 
 export interface PrepareBankIntegratedWithdrawalRequest {
   talerWithdrawUri: string;
-  selectedExchange?: string;
 }
 
 export const codecForPrepareBankIntegratedWithdrawalRequest =
   (): Codec<PrepareBankIntegratedWithdrawalRequest> =>
     buildCodecForObject<PrepareBankIntegratedWithdrawalRequest>()
       .property("talerWithdrawUri", codecForString())
-      .property("selectedExchange", codecOptional(codecForString()))
       .build("PrepareBankIntegratedWithdrawalRequest");
 
 export interface PrepareBankIntegratedWithdrawalResponse {
-  transactionId?: string;
+  transactionId: TransactionIdStr;
   info: WithdrawUriInfoResponse;
 }
 
diff --git a/packages/taler-wallet-core/src/balance.ts 
b/packages/taler-wallet-core/src/balance.ts
index 76e604324..4f06e3756 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -379,6 +379,10 @@ export async function getBalancesInsideTransaction(
             wg.denomsSel !== undefined,
             "wg in kyc state should have been initialized",
           );
+          checkDbInvariant(
+            wg.exchangeBaseUrl !== undefined,
+            "wg in kyc state should have been initialized",
+          );
           const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
           await balanceStore.setFlagIncomingKyc(currency, wg.exchangeBaseUrl);
           break;
@@ -389,6 +393,10 @@ export async function getBalancesInsideTransaction(
             wg.denomsSel !== undefined,
             "wg in aml state should have been initialized",
           );
+          checkDbInvariant(
+            wg.exchangeBaseUrl !== undefined,
+            "wg in kyc state should have been initialized",
+          );
           const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
           await balanceStore.setFlagIncomingAml(currency, wg.exchangeBaseUrl);
           break;
@@ -408,6 +416,10 @@ export async function getBalancesInsideTransaction(
             wg.denomsSel !== undefined,
             "wg in confirmed state should have been initialized",
           );
+          checkDbInvariant(
+            wg.exchangeBaseUrl !== undefined,
+            "wg in kyc state should have been initialized",
+          );
           const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
           await balanceStore.setFlagIncomingConfirmation(
             currency,
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index ad9b4f1cb..4ec83a783 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -395,6 +395,8 @@ export interface ReserveBankInfo {
   timestampBankConfirmed: DbPreciseTimestamp | undefined;
 
   wireTypes: string[] | undefined;
+
+  currency: string | undefined;
 }
 
 /**
@@ -1531,7 +1533,7 @@ export interface WithdrawalGroupRecord {
    * The exchange base URL that we're withdrawing from.
    * (Redundantly stored, as the reserve record also has this info.)
    */
-  exchangeBaseUrl: string;
+  exchangeBaseUrl?: string;
 
   /**
    * When was the withdrawal operation started started?
diff --git a/packages/taler-wallet-core/src/transactions.ts 
b/packages/taler-wallet-core/src/transactions.ts
index 72aff319a..bcf3fcaf6 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -243,11 +243,14 @@ export async function getTransactionById(
           const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
           const ort = await tx.operationRetries.get(opId);
 
-          const exchangeDetails = await getExchangeWireDetailsInTx(
-            tx,
-            withdrawalGroupRecord.exchangeBaseUrl,
-          );
-          if (!exchangeDetails) throw Error("not exchange details");
+          const exchangeDetails =
+            withdrawalGroupRecord.exchangeBaseUrl === undefined
+              ? undefined
+              : await getExchangeWireDetailsInTx(
+                  tx,
+                  withdrawalGroupRecord.exchangeBaseUrl,
+                );
+          // if (!exchangeDetails) throw Error("not exchange details");
 
           if (
             withdrawalGroupRecord.wgInfo.withdrawalType ===
@@ -259,7 +262,10 @@ export async function getTransactionById(
               ort,
             );
           }
-
+          checkDbInvariant(
+            exchangeDetails !== undefined,
+            "manual withdrawal without exchange",
+          );
           return buildTransactionForManualWithdraw(
             withdrawalGroupRecord,
             exchangeDetails,
@@ -404,7 +410,10 @@ export async function getTransactionById(
           const debit = await tx.peerPushDebit.get(parsedTx.pursePub);
           if (!debit) throw Error("not found");
           const ct = await tx.contractTerms.get(debit.contractTermsHash);
-          checkDbInvariant(!!ct, `no contract terms for p2p push 
${parsedTx.pursePub}`);
+          checkDbInvariant(
+            !!ct,
+            `no contract terms for p2p push ${parsedTx.pursePub}`,
+          );
           return buildTransactionForPushPaymentDebit(
             debit,
             ct.contractTermsRaw,
@@ -428,7 +437,10 @@ export async function getTransactionById(
           const pushInc = await tx.peerPushCredit.get(peerPushCreditId);
           if (!pushInc) throw Error("not found");
           const ct = await tx.contractTerms.get(pushInc.contractTermsHash);
-          checkDbInvariant(!!ct, `no contract terms for p2p push 
${peerPushCreditId}`);
+          checkDbInvariant(
+            !!ct,
+            `no contract terms for p2p push ${peerPushCreditId}`,
+          );
 
           let wg: WithdrawalGroupRecord | undefined = undefined;
           let wgOrt: OperationRetryRecord | undefined = undefined;
@@ -593,6 +605,7 @@ function buildTransactionForPeerPullCredit(
     const txState = computePeerPullCreditTransactionState(pullCredit);
     checkDbInvariant(wsr.instructedAmount !== undefined, "wg uninitialized");
     checkDbInvariant(wsr.denomsSel !== undefined, "wg uninitialized");
+    checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg uninitialized");
     return {
       type: TransactionType.PeerPullCredit,
       txState,
@@ -667,6 +680,7 @@ function buildTransactionForPeerPushCredit(
     }
     checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
     checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+    checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
 
     const txState = computePeerPushCreditTransactionState(pushInc);
     return {
@@ -719,15 +733,17 @@ function buildTransactionForPeerPushCredit(
 
 function buildTransactionForBankIntegratedWithdraw(
   wg: WithdrawalGroupRecord,
-  exchangeDetails: ExchangeWireDetails,
+  exchangeDetails: ExchangeWireDetails | undefined,
   ort?: OperationRetryRecord,
 ): TransactionWithdrawal {
   if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
     throw Error("");
   }
+  checkDbInvariant(wg.wgInfo.bankInfo.currency !== undefined, "wg 
uninitialized");
   const txState = computeWithdrawalTransactionStatus(wg);
+  
   const zero = Amounts.stringify(
-    Amounts.zeroOfCurrency(exchangeDetails.currency),
+    Amounts.zeroOfCurrency(wg.wgInfo.bankInfo.currency),
   );
   return {
     type: TransactionType.Withdrawal,
@@ -784,6 +800,7 @@ function buildTransactionForManualWithdraw(
 
   checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized");
   checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized");
+  checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized");
   const exchangePaytoUris = augmentPaytoUrisForWithdrawal(
     plainPaytoUris,
     wg.reservePub,
@@ -1034,8 +1051,14 @@ function buildTransactionForPurchase(
   }));
 
   const timestamp = purchaseRecord.timestampAccept;
-  checkDbInvariant(!!timestamp, `purchase ${purchaseRecord.orderId} without 
accepted time`);
-  checkDbInvariant(!!purchaseRecord.payInfo, `purchase 
${purchaseRecord.orderId} without payinfo`);
+  checkDbInvariant(
+    !!timestamp,
+    `purchase ${purchaseRecord.orderId} without accepted time`,
+  );
+  checkDbInvariant(
+    !!purchaseRecord.payInfo,
+    `purchase ${purchaseRecord.orderId} without payinfo`,
+  );
 
   const txState = computePayMerchantTransactionState(purchaseRecord);
   return {
@@ -1089,6 +1112,10 @@ export async function getWithdrawalTransactionByUri(
       if (!withdrawalGroupRecord) {
         return undefined;
       }
+      if (withdrawalGroupRecord.exchangeBaseUrl === undefined) {
+        // prepared and unconfirmed withdrawals are hidden
+        return undefined;
+      }
 
       const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord);
       const ort = await tx.operationRetries.get(opId);
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index d98106d1f..f1d53b7d5 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -1010,10 +1010,7 @@ async function dispatchRequestInternal(
     case WalletApiOperation.PrepareBankIntegratedWithdrawal: {
       const req =
         codecForPrepareBankIntegratedWithdrawalRequest().decode(payload);
-      return prepareBankIntegratedWithdrawal(wex, {
-        talerWithdrawUri: req.talerWithdrawUri,
-        selectedExchange: req.selectedExchange,
-      });
+      return prepareBankIntegratedWithdrawal(wex, req);
     }
     case WalletApiOperation.GetExchangeTos: {
       const req = codecForGetExchangeTosRequest().decode(payload);
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index 24e2861e1..0e7ed144c 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -344,9 +344,11 @@ export class WithdrawTransactionContext implements 
TransactionContext {
       "exchanges" as const,
       "exchangeDetails" as const,
     ];
-    let stores = opts.extraStores
+    const stores = opts.extraStores
       ? [...baseStores, ...opts.extraStores]
       : baseStores;
+
+    let errorThrown: Error | undefined;
     const transitionInfo = await this.wex.db.runReadWriteTx(
       { storeNames: stores },
       async (tx) => {
@@ -359,7 +361,17 @@ export class WithdrawTransactionContext implements 
TransactionContext {
             major: TransactionMajorState.None,
           };
         }
-        const res = await f(wgRec, tx);
+        let res: TransitionResult<WithdrawalGroupRecord> | undefined;
+        try {
+          res = await f(wgRec, tx);
+        } catch (error) {
+          if (error instanceof Error) {
+            errorThrown = error;
+          }
+          return undefined;
+        }
+
+        // const res = await f(wgRec, tx);
         switch (res.type) {
           case TransitionResultType.Transition: {
             await tx.withdrawalGroups.put(res.rec);
@@ -384,6 +396,9 @@ export class WithdrawTransactionContext implements 
TransactionContext {
         }
       },
     );
+    if (errorThrown) {
+      throw errorThrown;
+    }
     notifyTransition(this.wex, this.transactionId, transitionInfo);
     return transitionInfo;
   }
@@ -929,6 +944,10 @@ async function processPlanchetGenerate(
     withdrawalGroup.denomsSel !== undefined,
     "can't process uninitialized exchange",
   );
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
   const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
   let planchet = await wex.db.runReadOnlyTx(
     { storeNames: ["planchets"] },
@@ -1133,6 +1152,10 @@ async function processPlanchetExchangeBatchRequest(
   logger.info(
     `processing planchet exchange batch request 
${withdrawalGroup.withdrawalGroupId}, start=${args.coinStartIndex}, 
len=${args.batchSize}`,
   );
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
   const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
 
   const batchReq: ExchangeBatchWithdrawRequest = { planchets: [] };
@@ -1268,6 +1291,10 @@ async function processPlanchetVerifyAndStoreCoin(
   resp: ExchangeWithdrawResponse,
 ): Promise<void> {
   const withdrawalGroup = wgContext.wgRecord;
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
   const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
 
   logger.trace(`checking and storing planchet idx=${coinIdx}`);
@@ -1516,6 +1543,10 @@ async function processQueryReserve(
   if (withdrawalGroup.status !== WithdrawalGroupStatus.PendingQueryingStatus) {
     return TaskRunResult.backoff();
   }
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
   checkDbInvariant(
     withdrawalGroup.denomsSel !== undefined,
     "can't process uninitialized exchange",
@@ -1751,6 +1782,10 @@ async function redenominateWithdrawal(
       if (!wg) {
         return;
       }
+      checkDbInvariant(
+        wg.exchangeBaseUrl !== undefined,
+        "can't get funding uri from uninitialized wg",
+      );
       checkDbInvariant(
         wg.denomsSel !== undefined,
         "can't process uninitialized exchange",
@@ -1894,6 +1929,10 @@ async function processWithdrawalGroupPendingReady(
     withdrawalGroup.denomsSel !== undefined,
     "can't process uninitialized exchange",
   );
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
   const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
   await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl);
 
@@ -2320,6 +2359,10 @@ export async function getFundingPaytoUris(
 ): Promise<string[]> {
   const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
   checkDbInvariant(!!withdrawalGroup, `no withdrawal for 
${withdrawalGroupId}`);
+  checkDbInvariant(
+    withdrawalGroup.exchangeBaseUrl !== undefined,
+    "can't get funding uri from uninitialized wg",
+  );
   checkDbInvariant(
     withdrawalGroup.instructedAmount !== undefined,
     "can't get funding uri from uninitialized wg",
@@ -2675,7 +2718,7 @@ export async function 
internalPrepareCreateWithdrawalGroup(
   args: {
     reserveStatus: WithdrawalGroupStatus;
     amount?: AmountJson;
-    exchangeBaseUrl: string;
+    exchangeBaseUrl: string | undefined;
     forcedWithdrawalGroupId?: string;
     forcedDenomSel?: ForcedDenomSel;
     reserveKeyPair?: EddsaKeypair;
@@ -2716,7 +2759,7 @@ export async function 
internalPrepareCreateWithdrawalGroup(
   let initialDenomSel: DenomSelectionState | undefined;
   const denomSelUid = encodeCrock(getRandomBytes(16));
 
-  if (amount !== undefined) {
+  if (amount !== undefined && exchangeBaseUrl !== undefined) {
     initialDenomSel = await getInitialDenomsSelection(
       wex,
       exchangeBaseUrl,
@@ -2747,7 +2790,9 @@ export async function 
internalPrepareCreateWithdrawalGroup(
     wgInfo: args.wgInfo,
   };
 
-  await fetchFreshExchange(wex, exchangeBaseUrl);
+  if (exchangeBaseUrl !== undefined) {
+    await fetchFreshExchange(wex, exchangeBaseUrl);
+  }
 
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
@@ -2757,12 +2802,13 @@ export async function 
internalPrepareCreateWithdrawalGroup(
   return {
     withdrawalGroup,
     transactionId,
-    creationInfo: !amount
-      ? undefined
-      : {
-          amount,
-          canonExchange: exchangeBaseUrl,
-        },
+    creationInfo:
+      !amount || !exchangeBaseUrl
+        ? undefined
+        : {
+            amount,
+            canonExchange: exchangeBaseUrl,
+          },
   };
 }
 
@@ -2792,8 +2838,8 @@ export async function 
internalPerformCreateWithdrawalGroup(
   if (existingWg) {
     return {
       withdrawalGroup: existingWg,
-      exchangeNotif: undefined,
       transitionInfo: undefined,
+      exchangeNotif: undefined,
     };
   }
   await tx.withdrawalGroups.add(withdrawalGroup);
@@ -2809,7 +2855,21 @@ export async function 
internalPerformCreateWithdrawalGroup(
       exchangeNotif: undefined,
     };
   }
-  const exchange = await tx.exchanges.get(prep.creationInfo.canonExchange);
+  return internalPerformExchangeWasUsed(
+    wex,
+    tx,
+    prep.creationInfo.canonExchange,
+    withdrawalGroup,
+  );
+}
+
+export async function internalPerformExchangeWasUsed(
+  wex: WalletExecutionContext,
+  tx: WalletDbReadWriteTransaction<["exchanges"]>,
+  canonExchange: string,
+  withdrawalGroup: WithdrawalGroupRecord,
+): Promise<PerformCreateWithdrawalGroupResult> {
+  const exchange = await tx.exchanges.get(canonExchange);
   if (exchange) {
     exchange.lastWithdrawal = 
timestampPreciseToDb(TalerPreciseTimestamp.now());
     await tx.exchanges.put(exchange);
@@ -2825,11 +2885,7 @@ export async function 
internalPerformCreateWithdrawalGroup(
     newTxState,
   };
 
-  const exchangeUsedRes = await markExchangeUsed(
-    wex,
-    tx,
-    prep.creationInfo.canonExchange,
-  );
+  const exchangeUsedRes = await markExchangeUsed(wex, tx, canonExchange);
 
   const ctx = new WithdrawTransactionContext(
     wex,
@@ -2857,7 +2913,7 @@ export async function internalCreateWithdrawalGroup(
   wex: WalletExecutionContext,
   args: {
     reserveStatus: WithdrawalGroupStatus;
-    exchangeBaseUrl: string;
+    exchangeBaseUrl: string | undefined;
     amount?: AmountJson;
     forcedWithdrawalGroupId?: string;
     forcedDenomSel?: ForcedDenomSel;
@@ -2903,7 +2959,6 @@ export async function prepareBankIntegratedWithdrawal(
   wex: WalletExecutionContext,
   req: {
     talerWithdrawUri: string;
-    selectedExchange?: string;
   },
 ): Promise<PrepareBankIntegratedWithdrawalResponse> {
   const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
@@ -2932,12 +2987,6 @@ export async function prepareBankIntegratedWithdrawal(
 
   const info = await getWithdrawalDetailsForUri(wex, req.talerWithdrawUri);
 
-  const exchangeBaseUrl =
-    req.selectedExchange ?? withdrawInfo.suggestedExchange;
-  if (!exchangeBaseUrl) {
-    return { info };
-  }
-
   /**
    * Withdrawal group without exchange and amount
    * this is an special case when the user haven't yet
@@ -2946,7 +2995,7 @@ export async function prepareBankIntegratedWithdrawal(
    * same URI
    */
   const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
-    exchangeBaseUrl,
+    exchangeBaseUrl: undefined,
     wgInfo: {
       withdrawalType: WithdrawalRecordType.BankIntegrated,
       bankInfo: {
@@ -2955,6 +3004,7 @@ export async function prepareBankIntegratedWithdrawal(
         timestampBankConfirmed: undefined,
         timestampReserveInfoPosted: undefined,
         wireTypes: withdrawInfo.wireTypes,
+        currency: withdrawInfo.currency,
       },
     },
     reserveStatus: WithdrawalGroupStatus.DialogProposed,
@@ -2977,6 +3027,9 @@ export async function confirmWithdrawal(
   req: ConfirmWithdrawalRequest,
 ): Promise<void> {
   const parsedTx = parseTransactionIdentifier(req.transactionId);
+  const selectedExchange = req.exchangeBaseUrl;
+  const instructedAmount = Amounts.parseOrThrow(req.amount);
+
   if (parsedTx?.tag !== TransactionType.Withdrawal) {
     throw Error("invalid withdrawal transaction ID");
   }
@@ -2998,7 +3051,6 @@ export async function confirmWithdrawal(
     throw Error("not a bank integrated withdrawal");
   }
 
-  const selectedExchange = req.exchangeBaseUrl;
   const exchange = await fetchFreshExchange(wex, selectedExchange);
 
   const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri;
@@ -3006,30 +3058,36 @@ export async function confirmWithdrawal(
 
   /**
    * The only reason this could be undefined is because it is an old wallet
-   * database before adding the wireType field was added
+   * database before adding the prepareWithdrawal feature
    */
-  let wtypes: string[];
-  if (withdrawalGroup.wgInfo.bankInfo.wireTypes === undefined) {
+  let bankWireTypes: string[];
+  let bankCurrency: string;
+  if (
+    withdrawalGroup.wgInfo.bankInfo.wireTypes === undefined ||
+    withdrawalGroup.wgInfo.bankInfo.currency === undefined
+  ) {
     const withdrawInfo = await getBankWithdrawalInfo(
       wex.http,
       talerWithdrawUri,
     );
-    wtypes = withdrawInfo.wireTypes;
+    bankWireTypes = withdrawInfo.wireTypes;
+    bankCurrency = withdrawInfo.currency;
   } else {
-    wtypes = withdrawalGroup.wgInfo.bankInfo.wireTypes;
+    bankWireTypes = withdrawalGroup.wgInfo.bankInfo.wireTypes;
+    bankCurrency = withdrawalGroup.wgInfo.bankInfo.currency;
   }
 
   const exchangePaytoUri = await getExchangePaytoUri(
     wex,
     selectedExchange,
-    wtypes,
+    bankWireTypes,
   );
 
   const withdrawalAccountList = await fetchWithdrawalAccountInfo(
     wex,
     {
       exchange,
-      instructedAmount: Amounts.parseOrThrow(req.amount),
+      instructedAmount,
     },
     wex.cancellationToken,
   );
@@ -3040,23 +3098,34 @@ export async function confirmWithdrawal(
   );
   const initalDenoms = await getInitialDenomsSelection(
     wex,
-    req.exchangeBaseUrl,
-    Amounts.parseOrThrow(req.amount),
+    exchange.exchangeBaseUrl,
+    instructedAmount,
     req.forcedDenomSel,
   );
 
+  let pending = false;
   await ctx.transition({}, async (rec) => {
     if (!rec) {
       return TransitionResult.stay();
     }
     switch (rec.status) {
+      case WithdrawalGroupStatus.PendingWaitConfirmBank: {
+        pending = true;
+        return TransitionResult.stay();
+      }
+      case WithdrawalGroupStatus.AbortedOtherWallet: {
+        throw TalerError.fromDetail(
+          TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
+          {},
+        );
+      }
       case WithdrawalGroupStatus.DialogProposed: {
-        rec.exchangeBaseUrl = req.exchangeBaseUrl;
+        rec.exchangeBaseUrl = exchange.exchangeBaseUrl;
         rec.instructedAmount = req.amount;
+        rec.restrictAge = req.restrictAge;
         rec.denomsSel = initalDenoms;
         rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost;
         rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue;
-        rec.restrictAge = req.restrictAge;
 
         rec.wgInfo = {
           withdrawalType: WithdrawalRecordType.BankIntegrated,
@@ -3067,19 +3136,50 @@ export async function confirmWithdrawal(
             confirmUrl: confirmUrl,
             timestampBankConfirmed: undefined,
             timestampReserveInfoPosted: undefined,
-            wireTypes: wtypes,
+            wireTypes: bankWireTypes,
+            currency: bankCurrency,
           },
         };
-
+        pending = true;
         rec.status = WithdrawalGroupStatus.PendingRegisteringBank;
         return TransitionResult.transition(rec);
       }
-      default:
-        throw Error("unable to confirm withdrawal in current state");
+      default: {
+        throw Error(
+          `unable to confirm withdrawal in current state: ${rec.status}`,
+        );
+      }
     }
   });
 
   await wex.taskScheduler.resetTaskRetries(ctx.taskId);
+
+  wex.ws.notify({
+    type: NotificationType.BalanceChange,
+    hintTransactionId: ctx.transactionId,
+  });
+
+  const res = await wex.db.runReadWriteTx(
+    {
+      storeNames: ["exchanges"],
+    },
+    async (tx) => {
+      const r = await internalPerformExchangeWasUsed(
+        wex,
+        tx,
+        exchange.exchangeBaseUrl,
+        withdrawalGroup,
+      );
+      return r;
+    },
+  );
+  if (res.exchangeNotif) {
+    wex.ws.notify(res.exchangeNotif);
+  }
+
+  if (pending) {
+    await waitWithdrawalRegistered(wex, ctx);
+  }
 }
 
 /**
@@ -3104,112 +3204,57 @@ export async function acceptWithdrawalFromUri(
 ): Promise<AcceptWithdrawalResponse> {
   const selectedExchange = req.selectedExchange;
   logger.info(
-    `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected 
exchange ${selectedExchange}`,
-  );
-  const existingWithdrawalGroup = await wex.db.runReadOnlyTx(
-    { storeNames: ["withdrawalGroups"] },
-    async (tx) => {
-      return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
-        req.talerWithdrawUri,
-      );
-    },
+    `preparing withdrawal via ${req.talerWithdrawUri}, canonicalized selected 
exchange ${selectedExchange}`,
   );
 
-  if (existingWithdrawalGroup) {
-    let url: string | undefined;
-    if (
-      existingWithdrawalGroup.wgInfo.withdrawalType ===
-      WithdrawalRecordType.BankIntegrated
-    ) {
-      url = existingWithdrawalGroup.wgInfo.bankInfo.confirmUrl;
-    }
-    return {
-      reservePub: existingWithdrawalGroup.reservePub,
-      confirmTransferUrl: url,
-      transactionId: constructTransactionIdentifier({
-        tag: TransactionType.Withdrawal,
-        withdrawalGroupId: existingWithdrawalGroup.withdrawalGroupId,
-      }),
-    };
-  }
-
-  const exchange = await fetchFreshExchange(wex, selectedExchange);
-  const withdrawInfo = await getBankWithdrawalInfo(
-    wex.http,
-    req.talerWithdrawUri,
-  );
-  const exchangePaytoUri = await getExchangePaytoUri(
-    wex,
-    selectedExchange,
-    withdrawInfo.wireTypes,
-  );
+  const p = await prepareBankIntegratedWithdrawal(wex, {
+    talerWithdrawUri: req.talerWithdrawUri,
+  });
 
-  let amount: AmountJson;
-  if (withdrawInfo.amount == null) {
+  let amount: AmountString;
+  if (p.info.amount == null) {
     if (req.amount == null) {
       throw Error(
         "amount required, as withdrawal operation has flexible amount",
       );
     }
-    amount = Amounts.parseOrThrow(req.amount);
+    amount = req.amount as AmountString;
   } else {
-    if (
-      req.amount != null &&
-      Amounts.cmp(req.amount, withdrawInfo.amount) != 0
-    ) {
+    if (req.amount != null && Amounts.cmp(req.amount, p.info.amount) != 0) {
       throw Error(
         "mismatched amount, amount is fixed by bank but client provided 
different amount",
       );
     }
-    amount = withdrawInfo.amount;
+    amount = p.info.amount;
   }
 
-  const withdrawalAccountList = await fetchWithdrawalAccountInfo(
-    wex,
-    {
-      exchange,
-      instructedAmount: amount,
-    },
-    CancellationToken.CONTINUE,
-  );
-
-  const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
-    amount,
-    exchangeBaseUrl: req.selectedExchange,
-    wgInfo: {
-      withdrawalType: WithdrawalRecordType.BankIntegrated,
-      exchangeCreditAccounts: withdrawalAccountList,
-      bankInfo: {
-        exchangePaytoUri,
-        talerWithdrawUri: req.talerWithdrawUri,
-        confirmUrl: withdrawInfo.confirmTransferUrl,
-        timestampBankConfirmed: undefined,
-        timestampReserveInfoPosted: undefined,
-        wireTypes: withdrawInfo.wireTypes,
-      },
-    },
+  logger.info(`confirming withdrawal with tx ${p.transactionId}`);
+  await confirmWithdrawal(wex, {
+    amount: Amounts.stringify(amount),
+    exchangeBaseUrl: selectedExchange,
+    transactionId: p.transactionId,
     restrictAge: req.restrictAge,
     forcedDenomSel: req.forcedDenomSel,
-    reserveStatus: WithdrawalGroupStatus.PendingRegisteringBank,
-  });
-
-  const withdrawalGroupId = withdrawalGroup.withdrawalGroupId;
-
-  const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId);
-
-  wex.ws.notify({
-    type: NotificationType.BalanceChange,
-    hintTransactionId: ctx.transactionId,
   });
 
-  wex.taskScheduler.startShepherdTask(ctx.taskId);
+  const newWithdrawralGroup = await wex.db.runReadOnlyTx(
+    { storeNames: ["withdrawalGroups"] },
+    async (tx) => {
+      return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get(
+        req.talerWithdrawUri,
+      );
+    },
+  );
 
-  await waitWithdrawalRegistered(wex, ctx);
+  checkDbInvariant(
+    newWithdrawralGroup !== undefined,
+    "withdrawal don't exist after confirm",
+  );
 
   return {
-    reservePub: withdrawalGroup.reservePub,
-    confirmTransferUrl: withdrawInfo.confirmTransferUrl,
-    transactionId: ctx.transactionId,
+    reservePub: newWithdrawralGroup.reservePub,
+    confirmTransferUrl: p.info.confirmTransferUrl,
+    transactionId: p.transactionId,
   };
 }
 
@@ -3434,7 +3479,7 @@ export async function createManualWithdrawal(
   );
 
   const withdrawalGroup = await internalCreateWithdrawalGroup(wex, {
-    amount: Amounts.jsonifyAmount(req.amount),
+    amount: amount,
     wgInfo: {
       withdrawalType: WithdrawalRecordType.BankManual,
       exchangeCreditAccounts: withdrawalAccountsList,
diff --git a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx 
b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
index 9be9326b2..8e48a2e9f 100644
--- a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
+++ b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
@@ -26,7 +26,7 @@ import {
   DenomLossEventType,
   parsePaytoUri,
 } from "@gnu-taler/taler-util";
-import { h, VNode } from "preact";
+import { Fragment, h, VNode } from "preact";
 import { useTranslationContext } from "@gnu-taler/web-util/browser";
 import { Avatar } from "../mui/Avatar.js";
 import { Pages } from "../NavigationBar.js";
@@ -49,6 +49,8 @@ export function HistoryItem(props: { tx: Transaction }): 
VNode {
    */
   switch (tx.type) {
     case TransactionType.Withdrawal:
+      //withdrawal that has not been confirmed are hidden
+      if (!tx.exchangeBaseUrl) return <Fragment />
       return (
         <Layout
           id={tx.transactionId}
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts 
b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
index 90ad65d6e..da3b1eeb2 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/state.ts
@@ -213,10 +213,7 @@ export function useComponentStateFromURI({
 
     const uriInfo = await api.wallet.call(
       WalletApiOperation.PrepareBankIntegratedWithdrawal,
-      {
-        talerWithdrawUri,
-        selectedExchange: updatedExchangeByUser,
-      },
+      { talerWithdrawUri },
     );
     const {
       amount,
@@ -225,12 +222,12 @@ export function useComponentStateFromURI({
       confirmTransferUrl,
       status,
     } = uriInfo.info;
-    const txInfo =
-      uriInfo.transactionId === undefined
-        ? undefined
-        : await api.wallet.call(WalletApiOperation.GetTransactionById, {
-            transactionId: uriInfo.transactionId,
-          });
+    const txInfo = await api.wallet.call(
+      WalletApiOperation.GetTransactionById,
+      {
+        transactionId: uriInfo.transactionId,
+      },
+    );
     return {
       talerWithdrawUri,
       status,
@@ -292,9 +289,6 @@ export function useComponentStateFromURI({
     transactionId: string;
     confirmTransferUrl: string | undefined;
   }> {
-    if (!txId) {
-      throw Error("can't confirm transaction");
-    }
     const res = await api.wallet.call(WalletApiOperation.ConfirmWithdrawal, {
       exchangeBaseUrl: exchange,
       amount,
@@ -370,15 +364,16 @@ function exchangeSelectionState(
       onExchangeUpdated(current);
     }
   }, [current]);
-  
-  const safeAmount = !infoAmount ? Amounts.zeroOfCurrency(currency) : 
infoAmount
-  const [choosenAmount, setChoosenAmount] = useState(safeAmount)
+
+  const safeAmount = !infoAmount
+    ? Amounts.zeroOfCurrency(currency)
+    : infoAmount;
+  const [choosenAmount, setChoosenAmount] = useState(safeAmount);
 
   if (selectedExchange.status !== "ready") {
     return selectedExchange;
   }
 
-
   return useCallback(():
     | State.Success
     | State.LoadingUriError
@@ -520,8 +515,8 @@ function exchangeSelectionState(
       amount: {
         value: choosenAmount,
         onInput: pushAlertOnError(async (v) => {
-          setChoosenAmount(v)
-        })
+          setChoosenAmount(v);
+        }),
       },
       talerWithdrawUri,
       ageRestriction,
diff --git a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts 
b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
index 785fab996..5bbf5f6c8 100644
--- a/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
+++ b/packages/taler-wallet-webextension/src/cta/Withdraw/test.ts
@@ -26,6 +26,7 @@ import {
   ExchangeListItem,
   ExchangeTosStatus,
   ScopeType,
+  TransactionIdStr,
 } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { expect } from "chai";
@@ -111,7 +112,7 @@ describe("Withdraw CTA states", () => {
       WalletApiOperation.PrepareBankIntegratedWithdrawal,
       undefined,
       {
-        transactionId: "123",
+        transactionId: "123" as TransactionIdStr,
         info: {
           status: "pending",
           operationId: "123",
@@ -153,7 +154,7 @@ describe("Withdraw CTA states", () => {
       WalletApiOperation.PrepareBankIntegratedWithdrawal,
       undefined,
       {
-        transactionId: "123",
+        transactionId: "123" as TransactionIdStr,
         info: {
           status: "pending",
           operationId: "123",

-- 
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]