gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: wallet-core: support bank-int


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: support bank-integrated withdrawal with amount selection by wallet
Date: Thu, 23 May 2024 19:51:02 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new cdcedf655 wallet-core: support bank-integrated withdrawal with amount 
selection by wallet
cdcedf655 is described below

commit cdcedf65595393fc186eb2012d3ae6cd55540f59
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu May 23 19:50:59 2024 +0200

    wallet-core: support bank-integrated withdrawal with amount selection by 
wallet
---
 .../src/integrationtests/test-withdrawal-fees.ts   | 15 +++--
 .../src/integrationtests/test-withdrawal-flex.ts   | 70 ++++++++++++++++++++++
 .../src/integrationtests/testrunner.ts             |  4 +-
 packages/taler-util/src/bank-api-client.ts         |  2 +-
 packages/taler-util/src/http-client/types.ts       |  4 +-
 packages/taler-util/src/taler-types.ts             |  4 +-
 packages/taler-util/src/wallet-types.ts            | 31 ++++++----
 packages/taler-wallet-core/src/db.ts               |  1 +
 packages/taler-wallet-core/src/shepherd.ts         |  8 ++-
 packages/taler-wallet-core/src/wallet.ts           |  5 +-
 packages/taler-wallet-core/src/withdraw.ts         | 58 ++++++++++++++----
 11 files changed, 166 insertions(+), 36 deletions(-)

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

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