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,harness: support


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core,harness: support cash acceptor withdrawals, test
Date: Tue, 07 Jan 2025 19:30:56 +0100

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 15cd09e5c wallet-core,harness: support cash acceptor withdrawals, test
15cd09e5c is described below

commit 15cd09e5ce7293dc5ae370b67426a4b2cc5a219b
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Jan 7 19:30:53 2025 +0100

    wallet-core,harness: support cash acceptor withdrawals, test
---
 packages/taler-harness/src/harness/environments.ts |  54 ++++++-
 .../src/integrationtests/testrunner.ts             |   2 +
 packages/taler-util/src/http-client/bank-core.ts   |  33 +++-
 .../taler-util/src/http-client/bank-integration.ts |  12 +-
 .../taler-util/src/types-taler-bank-integration.ts |  59 +++++---
 packages/taler-util/src/types-taler-common.ts      |  83 -----------
 packages/taler-util/src/types-taler-corebank.ts    |   4 +-
 packages/taler-util/src/types-taler-wallet.ts      |   8 +-
 packages/taler-wallet-core/src/balance.ts          |  26 +++-
 packages/taler-wallet-core/src/withdraw.ts         | 166 +++++++++++++--------
 10 files changed, 265 insertions(+), 182 deletions(-)

diff --git a/packages/taler-harness/src/harness/environments.ts 
b/packages/taler-harness/src/harness/environments.ts
index ed3e34881..155dae463 100644
--- a/packages/taler-harness/src/harness/environments.ts
+++ b/packages/taler-harness/src/harness/environments.ts
@@ -24,6 +24,7 @@
  * Imports
  */
 import {
+  AccessToken,
   AccountProperties,
   AmlDecisionRequest,
   AmlDecisionRequestWithoutSignature,
@@ -33,16 +34,19 @@ import {
   decodeCrock,
   Duration,
   encodeCrock,
+  getRandomBytes,
   HttpStatusCode,
   j2s,
   LegitimizationRuleSet,
   Logger,
   MerchantApiClient,
+  narrowOpSuccessOrThrow,
   NotificationType,
   PartialWalletRunConfig,
   PreparePayResultType,
   signAmlDecision,
   TalerCorebankApiClient,
+  TalerCoreBankHttpClient,
   TalerMerchantApi,
   TalerProtocolTimestamp,
   TransactionIdStr,
@@ -142,6 +146,11 @@ export interface EnvOptions {
 
   accountRestrictions?: HarnessAccountRestriction[];
 
+  /**
+   * Force usage of libeufin for this particular test.
+   */
+  forceLibeufin?: boolean;
+
   additionalExchangeConfig?(e: ExchangeService): void;
   additionalMerchantConfig?(m: MerchantService): void;
   additionalBankConfig?(b: BankService): void;
@@ -463,9 +472,10 @@ export async function createSimpleTestkudosEnvironmentV3(
     httpPort: 8082,
   };
 
-  const bank: BankService = useLibeufinBank
-    ? await LibeufinBankService.create(t, bc)
-    : await FakebankService.create(t, bc);
+  const bank: BankService =
+    useLibeufinBank || opts.forceLibeufin
+      ? await LibeufinBankService.create(t, bc)
+      : await FakebankService.create(t, bc);
 
   const exchange = ExchangeService.create(t, {
     name: "testexchange-1",
@@ -1130,7 +1140,6 @@ export interface KycTestEnv {
   wireGatewayApiClient: WireGatewayApiClient;
 }
 
-
 export async function createKycTestkudosEnvironment(
   t: GlobalTestState,
   opts: KycEnvOptions = {},
@@ -1219,7 +1228,6 @@ export async function createKycTestkudosEnvironment(
   await walletService.start();
   await walletService.pingUntilAvailable();
 
-
   const walletClient = new WalletClient({
     name: "wallet",
     unixPath: walletService.socketPath,
@@ -1293,3 +1301,39 @@ export async function createKycTestkudosEnvironment(
     wireGatewayApiClient,
   };
 }
+
+export interface TestUserResult {
+  username: string;
+  password: string;
+  token: AccessToken;
+}
+
+/**
+ * Register a new bank user with a random name and obtain a
+ * login token.
+ */
+export async function registerHarnessBankTestUser(
+  bankClient: TalerCoreBankHttpClient,
+): Promise<TestUserResult> {
+  const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+  const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase();
+  const createRes = await bankClient.createAccount(undefined, {
+    name: username,
+    username,
+    password,
+  });
+  narrowOpSuccessOrThrow("createAccount", createRes);
+  // It's a test account, so it's safe to log credentials.
+  logger.info(
+    `Created test bank account ${username} with password ${password}`,
+  );
+  const tokRes = await bankClient.createAccessTokenBasic(username, password, {
+    scope: "readwrite",
+  });
+  narrowOpSuccessOrThrow("token", tokRes);
+  return {
+    password,
+    username,
+    token: tokRes.body.access_token,
+  };
+}
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 6e5eb21fa..1a5c32764 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -143,6 +143,7 @@ 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 { runWithdrawalCashacceptorTest } from 
"./test-withdrawal-cashacceptor.js";
 import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js";
 import { runWithdrawalExternalTest } from "./test-withdrawal-external.js";
 import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
@@ -296,6 +297,7 @@ const allTests: TestMainFunction[] = [
   runKycAmpTimeoutTest,
   runKycAmpFailureTest,
   runPeerPushAbortTest,
+  runWithdrawalCashacceptorTest,
 ];
 
 export interface TestRunSpec {
diff --git a/packages/taler-util/src/http-client/bank-core.ts 
b/packages/taler-util/src/http-client/bank-core.ts
index ef17b32e1..208c05ce5 100644
--- a/packages/taler-util/src/http-client/bank-core.ts
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -26,8 +26,10 @@ import {
   PaginationParams,
   TalerError,
   TalerErrorCode,
+  TokenRequest,
   UserAndToken,
   codecForTalerCommonConfigResponse,
+  codecForTokenSuccessResponse,
   opKnownAlternativeFailure,
   opKnownHttpFailure,
   opKnownTalerFailure,
@@ -46,7 +48,7 @@ import {
   opSuccessFromHttp,
   opUnknownFailure,
 } from "../operation.js";
-import { WithdrawalOperationStatus } from "../types-taler-bank-integration.js";
+import { WithdrawalOperationStatusFlag } from 
"../types-taler-bank-integration.js";
 import {
   codecForAccountData,
   codecForBankAccountCreateWithdrawalResponse,
@@ -70,6 +72,7 @@ import {
   CacheEvictor,
   addLongPollingParam,
   addPaginationParams,
+  makeBasicAuthHeader,
   makeBearerTokenAuthHeader,
   nullEvictor,
 } from "./utils.js";
@@ -123,6 +126,32 @@ export class TalerCoreBankHttpClient {
     return compare?.compatible ?? false;
   }
 
+  async createAccessTokenBasic(
+    username: string,
+    password: string,
+    body: TokenRequest,
+  ) {
+    const url = new URL(`accounts/${username}/token`, this.baseUrl);
+    const resp = await this.httpLib.fetch(url.href, {
+      method: "POST",
+      headers: {
+        Authorization: makeBasicAuthHeader(username, password),
+      },
+      body,
+    });
+    switch (resp.status) {
+      case HttpStatusCode.Ok:
+        return opSuccessFromHttp(resp, codecForTokenSuccessResponse());
+      //FIXME: missing in docs
+      case HttpStatusCode.Unauthorized:
+        return opKnownHttpFailure(resp.status, resp);
+      case HttpStatusCode.NotFound:
+        return opKnownHttpFailure(resp.status, resp);
+      default:
+        return opUnknownFailure(resp, await readTalerErrorResponse(resp));
+    }
+  }
+
   /**
    * https://docs.taler.net/core/api-corebank.html#config
    *
@@ -754,7 +783,7 @@ export class TalerCoreBankHttpClient {
   async getWithdrawalById(
     wid: string,
     params?: {
-      old_state?: WithdrawalOperationStatus;
+      old_state?: WithdrawalOperationStatusFlag;
     } & LongPollParams,
   ) {
     const url = new URL(`withdrawals/${wid}`, this.baseUrl);
diff --git a/packages/taler-util/src/http-client/bank-integration.ts 
b/packages/taler-util/src/http-client/bank-integration.ts
index 9bcdac683..17c5337c2 100644
--- a/packages/taler-util/src/http-client/bank-integration.ts
+++ b/packages/taler-util/src/http-client/bank-integration.ts
@@ -21,6 +21,8 @@ import { LibtoolVersion } from "../libtool-version.js";
 import { Logger } from "../logging.js";
 import {
   FailCasesByMethod,
+  OperationFail,
+  OperationOk,
   ResultByMethod,
   opEmptySuccess,
   opKnownHttpFailure,
@@ -31,7 +33,8 @@ import {
 import { TalerErrorCode } from "../taler-error-codes.js";
 import {
   BankWithdrawalOperationPostRequest,
-  WithdrawalOperationStatus,
+  BankWithdrawalOperationStatus,
+  WithdrawalOperationStatusFlag,
   codecForBankWithdrawalOperationPostResponse,
   codecForBankWithdrawalOperationStatus,
 } from "../types-taler-bank-integration.js";
@@ -96,9 +99,12 @@ export class TalerBankIntegrationHttpClient {
   async getWithdrawalOperationById(
     woid: string,
     params?: {
-      old_state?: WithdrawalOperationStatus;
+      old_state?: WithdrawalOperationStatusFlag;
     } & LongPollParams,
-  ) {
+  ): Promise<
+    | OperationOk<BankWithdrawalOperationStatus>
+    | OperationFail<HttpStatusCode.NotFound>
+  > {
     const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl);
     addLongPollingParam(url, params);
     if (params) {
diff --git a/packages/taler-util/src/types-taler-bank-integration.ts 
b/packages/taler-util/src/types-taler-bank-integration.ts
index 517d59f38..6cec0fc9b 100644
--- a/packages/taler-util/src/types-taler-bank-integration.ts
+++ b/packages/taler-util/src/types-taler-bank-integration.ts
@@ -16,12 +16,30 @@
  SPDX-License-Identifier: AGPL-3.0-or-later
  */
 
-import { Codec, buildCodecForObject, codecForConstString, codecForEither, 
codecOptional } from "./codec.js";
-import { codecForAmountString, codecForList, codecForString } from 
"./index.js";
+import {
+  Codec,
+  buildCodecForObject,
+  codecForConstString,
+  codecForEither,
+  codecOptional,
+} from "./codec.js";
+import {
+  codecForAmountString,
+  codecForBoolean,
+  codecForList,
+  codecForString,
+} from "./index.js";
 import { PaytoString, codecForPaytoString } from "./payto.js";
-import { AmountString, CurrencySpecification, codecForCurrencyName, 
codecForCurrencySpecificiation, codecForLibtoolVersion, codecForURLString } 
from "./types-taler-common.js";
-
-export type WithdrawalOperationStatus =
+import {
+  AmountString,
+  CurrencySpecification,
+  codecForCurrencyName,
+  codecForCurrencySpecificiation,
+  codecForLibtoolVersion,
+  codecForURLString,
+} from "./types-taler-common.js";
+
+export type WithdrawalOperationStatusFlag =
   | "pending"
   | "selected"
   | "aborted"
@@ -49,7 +67,7 @@ export interface BankWithdrawalOperationStatus {
   // selected: the operations has been selected and is pending confirmation
   // aborted: the operation has been aborted
   // confirmed: the transfer has been confirmed and registered by the bank
-  status: WithdrawalOperationStatus;
+  status: WithdrawalOperationStatusFlag;
 
   // Currency used for the withdrawal.
   // MUST be present when amount is absent.
@@ -116,6 +134,14 @@ export interface BankWithdrawalOperationStatus {
   // only non-null if status is selected or confirmed.
   // @since **v1**
   selected_exchange_account?: string;
+
+  // If true, tells the wallet not to allow the user to
+  // specify an amount to withdraw and to not provide
+  // any amount when registering with the withdrawal
+  // operation. The amount to withdraw will be set
+  // by the final /withdrawals/$WITHDRAWAL_ID/confirm step.
+  // @since **v5**
+  no_amount_to_wallet?: boolean;
 }
 
 export interface BankWithdrawalOperationPostRequest {
@@ -138,7 +164,7 @@ export interface BankWithdrawalOperationPostResponse {
   // selected: the operations has been selected and is pending confirmation
   // aborted: the operation has been aborted
   // confirmed: the transfer has been confirmed and registered by the bank
-  status: Omit<"pending", WithdrawalOperationStatus>;
+  status: Omit<"pending", WithdrawalOperationStatusFlag>;
 
   // URL that the user needs to navigate to in order to
   // complete some final confirmation (e.g. 2FA).
@@ -148,15 +174,13 @@ export interface BankWithdrawalOperationPostResponse {
   confirm_transfer_url?: string;
 }
 
-
-export const codecForBankVersion =
-  (): Codec<BankVersion> =>
-    buildCodecForObject<BankVersion>()
-      .property("currency", codecForCurrencyName())
-      .property("currency_specification", codecForCurrencySpecificiation())
-      .property("name", codecForConstString("taler-bank-integration"))
-      .property("version", codecForLibtoolVersion())
-      .build("TalerBankIntegrationApi.BankVersion");
+export const codecForBankVersion = (): Codec<BankVersion> =>
+  buildCodecForObject<BankVersion>()
+    .property("currency", codecForCurrencyName())
+    .property("currency_specification", codecForCurrencySpecificiation())
+    .property("name", codecForConstString("taler-bank-integration"))
+    .property("version", codecForLibtoolVersion())
+    .build("TalerBankIntegrationApi.BankVersion");
 
 export const codecForBankWithdrawalOperationStatus =
   (): Codec<BankWithdrawalOperationStatus> =>
@@ -183,6 +207,7 @@ export const codecForBankWithdrawalOperationStatus =
       .property("wire_types", codecForList(codecForString()))
       .property("selected_reserve_pub", codecOptional(codecForString()))
       .property("selected_exchange_account", codecOptional(codecForString()))
+      .property("no_amount_to_wallet", codecOptional(codecForBoolean()))
       .build("TalerBankIntegrationApi.BankWithdrawalOperationStatus");
 
 export const codecForBankWithdrawalOperationPostResponse =
@@ -197,4 +222,4 @@ export const codecForBankWithdrawalOperationPostResponse =
         ),
       )
       .property("confirm_transfer_url", codecOptional(codecForURLString()))
-      .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse");
\ No newline at end of file
+      .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse");
diff --git a/packages/taler-util/src/types-taler-common.ts 
b/packages/taler-util/src/types-taler-common.ts
index 7a5f297d6..174626b02 100644
--- a/packages/taler-util/src/types-taler-common.ts
+++ b/packages/taler-util/src/types-taler-common.ts
@@ -177,61 +177,6 @@ export type ImageDataUrl = string;
  */
 export type Cs25519Point = string;
 
-/**
- * Response from the bank.
- */
-export class WithdrawOperationStatusResponse {
-  status: "selected" | "aborted" | "confirmed" | "pending";
-
-  selection_done: boolean;
-
-  transfer_done: boolean;
-
-  aborted: boolean;
-
-  amount: string | undefined;
-
-  sender_wire?: string;
-
-  suggested_exchange?: string;
-
-  confirm_transfer_url?: string;
-
-  wire_types: string[];
-
-  // Currency used for the withdrawal.
-  // MUST be present when amount is absent.
-  // @since **v2**, may become mandatory in the future.
-  currency?: string;
-
-  // Minimum amount that the wallet can choose to withdraw.
-  // Only applicable when the amount is not fixed.
-  // @since **v4**.
-  min_amount?: AmountString;
-
-  // Maximum amount that the wallet can choose to withdraw.
-  // Only applicable when the amount is not fixed.
-  // @since **v4**.
-  max_amount?: AmountString;
-
-  // The non-Taler card fees the customer will have
-  // to pay to the bank / payment service provider
-  // they are using to make the withdrawal in addition
-  // to the amount.
-  // @since **v4**
-  card_fees?: AmountString;
-
-  // Exchange account selected by the wallet;
-  // only non-null if status is selected or confirmed.
-  // @since **v1**
-  selected_exchange_account?: string;
-
-  // Reserve public key selected by the exchange,
-  // only non-null if status is selected or confirmed.
-  // @since **v1**
-  selected_reserve_pub?: EddsaPublicKey;
-}
-
 export type LitAmountString = `${string}:${number}`;
 
 export type LibtoolVersionString = string;
@@ -265,34 +210,6 @@ export const codecForEddsaSignature = codecForString;
 export const codecForInternationalizedString =
   (): Codec<InternationalizedString> => codecForMap(codecForString());
 
-export const codecForWithdrawOperationStatusResponse =
-  (): Codec<WithdrawOperationStatusResponse> =>
-    buildCodecForObject<WithdrawOperationStatusResponse>()
-      .property(
-        "status",
-        codecForEither(
-          codecForConstString("selected"),
-          codecForConstString("confirmed"),
-          codecForConstString("aborted"),
-          codecForConstString("pending"),
-        ),
-      )
-      .property("selection_done", codecForBoolean())
-      .property("transfer_done", codecForBoolean())
-      .property("aborted", codecForBoolean())
-      .property("amount", codecOptional(codecForString()))
-      .property("sender_wire", codecOptional(codecForString()))
-      .property("suggested_exchange", codecOptional(codecForString()))
-      .property("confirm_transfer_url", codecOptional(codecForString()))
-      .property("wire_types", codecForList(codecForString()))
-      .property("currency", codecOptional(codecForString()))
-      .property("card_fees", codecOptional(codecForAmountString()))
-      .property("min_amount", codecOptional(codecForAmountString()))
-      .property("max_amount", codecOptional(codecForAmountString()))
-      .property("selected_exchange_account", codecOptional(codecForString()))
-      .property("selected_reserve_pub", 
codecOptional(codecForEddsaPublicKey()))
-      .build("WithdrawOperationStatusResponse");
-
 export const codecForCurrencySpecificiation =
   (): Codec<CurrencySpecification> =>
     buildCodecForObject<CurrencySpecification>()
diff --git a/packages/taler-util/src/types-taler-corebank.ts 
b/packages/taler-util/src/types-taler-corebank.ts
index e198e057f..310b0c2e4 100644
--- a/packages/taler-util/src/types-taler-corebank.ts
+++ b/packages/taler-util/src/types-taler-corebank.ts
@@ -36,7 +36,7 @@ import {
 } from "./index.js";
 import { PaytoString, codecForPaytoString } from "./payto.js";
 import { TalerUriString } from "./taleruri.js";
-import { WithdrawalOperationStatus } from "./types-taler-bank-integration.js";
+import { WithdrawalOperationStatusFlag } from 
"./types-taler-bank-integration.js";
 import {
   AmountString,
   CurrencySpecification,
@@ -174,7 +174,7 @@ export interface WithdrawalPublicInfo {
   // selected: the operations has been selected and is pending confirmation
   // aborted: the operation has been aborted
   // confirmed: the transfer has been confirmed and registered by the bank
-  status: WithdrawalOperationStatus;
+  status: WithdrawalOperationStatusFlag;
 
   // Amount that will be withdrawn with this operation
   // (raw amount without fee considerations).
diff --git a/packages/taler-util/src/types-taler-wallet.ts 
b/packages/taler-util/src/types-taler-wallet.ts
index 007b5e9af..0a3d49a49 100644
--- a/packages/taler-util/src/types-taler-wallet.ts
+++ b/packages/taler-util/src/types-taler-wallet.ts
@@ -54,7 +54,7 @@ import {
   InternationalizedString,
   TalerMerchantApi,
   TemplateParams,
-  WithdrawalOperationStatus,
+  WithdrawalOperationStatusFlag,
   canonicalizeBaseUrl,
 } from "./index.js";
 import { PaytoString, codecForPaytoString } from "./payto.js";
@@ -953,7 +953,7 @@ export interface PreparePayResultAlreadyConfirmed {
 }
 
 export interface BankWithdrawDetails {
-  status: WithdrawalOperationStatus;
+  status: WithdrawalOperationStatusFlag;
   currency: string;
   amount: AmountJson | undefined;
   editableAmount: boolean;
@@ -1926,7 +1926,7 @@ export interface PrepareBankIntegratedWithdrawalResponse {
 export interface ConfirmWithdrawalRequest {
   transactionId: string;
   exchangeBaseUrl: string;
-  amount: AmountString;
+  amount: AmountString | undefined;
   forcedDenomSel?: ForcedDenomSel;
   restrictAge?: number;
 }
@@ -2503,7 +2503,7 @@ export interface TxIdResponse {
 
 export interface WithdrawUriInfoResponse {
   operationId: string;
-  status: WithdrawalOperationStatus;
+  status: WithdrawalOperationStatusFlag;
   confirmTransferUrl?: string;
   currency: string;
   amount: AmountString | undefined;
diff --git a/packages/taler-wallet-core/src/balance.ts 
b/packages/taler-wallet-core/src/balance.ts
index ddc0fb804..b4cb02481 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -80,6 +80,7 @@ import {
   RefreshOperationStatus,
   WalletDbReadOnlyTransaction,
   WithdrawalGroupStatus,
+  WithdrawalRecordType,
 } from "./db.js";
 import {
   getExchangeScopeInfo,
@@ -400,15 +401,28 @@ export async function getBalancesInsideTransaction(
           break;
         }
         case WithdrawalGroupStatus.PendingWaitConfirmBank: {
-          checkDbInvariant(
-            wg.denomsSel !== undefined,
-            "wg in confirmed state should have been initialized",
-          );
           checkDbInvariant(
             wg.exchangeBaseUrl !== undefined,
-            "wg in kyc state should have been initialized",
+            "withdrawal group in PendingWaitConfirmBank state should have been 
initialized",
           );
-          const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
+
+          // FIXME: Consider just having the currency as a fixed field in the 
DB
+          // instead of having it in many locations.
+          let currency: string;
+
+          if (wg.denomsSel) {
+            currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue);
+          } else if (
+            wg.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated &&
+            wg.wgInfo.bankInfo.currency
+          ) {
+            currency = wg.wgInfo.bankInfo.currency;
+          } else {
+            logger.warn(
+              "could not determine currency for confirmed withdrawal group",
+            );
+            break;
+          }
           await balanceStore.setFlagIncomingConfirmation(
             currency,
             wg.exchangeBaseUrl,
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index 952de45e4..c2b4ae805 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -95,7 +95,6 @@ import {
   codecForExchangeWithdrawBatchResponse,
   codecForLegitimizationNeededResponse,
   codecForReserveStatus,
-  codecForWithdrawOperationStatusResponse,
   encodeCrock,
   getErrorDetailFromException,
   getRandomBytes,
@@ -430,38 +429,26 @@ export class WithdrawTransactionContext implements 
TransactionContext {
     }
 
     if (
-      !wgRecord.instructedAmount ||
-      !wgRecord.denomsSel ||
+      // !wgRecord.instructedAmount ||
+      // !wgRecord.denomsSel ||
       !wgRecord.exchangeBaseUrl
     ) {
       // withdrawal group is in preparation, nothing to update
       return;
     }
 
-    if (
-      wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated
-    ) {
-    } else if (
-      wgRecord.wgInfo.withdrawalType === WithdrawalRecordType.BankManual
-    ) {
-      checkDbInvariant(
-        wgRecord.instructedAmount !== undefined,
-        "manual withdrawal without amount can't be created",
-      );
-      checkDbInvariant(
-        wgRecord.denomsSel !== undefined,
-        "manual withdrawal without denoms can't be created",
-      );
-    } else {
-      // FIXME: If this is an orphaned withdrawal for a p2p transaction, we
-      // still might want to report the withdrawal.
+    let currency: string | undefined;
+    if (wgRecord.rawWithdrawalAmount) {
+      currency = Amounts.currencyOf(wgRecord.rawWithdrawalAmount);
+    }
+    if (!currency) {
       return;
     }
     await tx.transactionsMeta.put({
       transactionId: ctx.transactionId,
       status: wgRecord.status,
       timestamp: wgRecord.timestampStart,
-      currency: Amounts.currencyOf(wgRecord.instructedAmount),
+      currency,
       exchanges: [wgRecord.exchangeBaseUrl],
     });
 
@@ -1177,7 +1164,7 @@ export async function getBankWithdrawalInfo(
   let editableAmount = false;
   if (status.amount !== undefined) {
     amount = Amounts.parseOrThrow(status.amount);
-  } else {
+  } else if (!status.no_amount_to_wallet) {
     amount =
       status.suggested_amount === undefined
         ? undefined
@@ -2912,10 +2899,10 @@ async function processBankRegisterReserve(
 
   const status = await readSuccessResponseJsonOrThrow(
     statusResp,
-    codecForWithdrawOperationStatusResponse(),
+    codecForBankWithdrawalOperationStatus(),
   );
 
-  if (status.aborted) {
+  if (status.status === "aborted") {
     return transitionBankAborted(ctx);
   }
 
@@ -2973,21 +2960,40 @@ async function processReserveBankStatus(
 
   const status = await readSuccessResponseJsonOrThrow(
     statusResp,
-    codecForWithdrawOperationStatusResponse(),
+    codecForBankWithdrawalOperationStatus(),
   );
 
   if (logger.shouldLogTrace()) {
     logger.trace(`response body: ${j2s(status)}`);
   }
 
-  if (status.aborted) {
+  if (status.status === "aborted") {
     return transitionBankAborted(ctx);
   }
 
-  if (!status.transfer_done) {
+  if (status.status != "confirmed") {
     return TaskRunResult.longpollReturnedPending();
   }
 
+  let denomSel: undefined | DenomSelectionState = undefined;
+
+  if (withdrawalGroup.denomsSel == null) {
+    const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl;
+    if (!exchangeBaseUrl) {
+      throw Error("invalid state");
+    }
+    if (!status.amount) {
+      throw Error("bank did not provide amount");
+    }
+    const instructedAmount = Amounts.parseOrThrow(status.amount);
+    denomSel = await getInitialDenomsSelection(
+      wex,
+      exchangeBaseUrl,
+      instructedAmount,
+      undefined,
+    );
+  }
+
   const transitionInfo = await ctx.transition({}, async (r) => {
     if (!r) {
       return TransitionResult.stay();
@@ -3002,11 +3008,17 @@ async function processReserveBankStatus(
     if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
       throw Error("invariant failed");
     }
-    if (status.transfer_done) {
+    if (status.status == "confirmed") {
       logger.info("withdrawal: transfer confirmed by bank.");
       const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
       r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now);
       r.status = WithdrawalGroupStatus.PendingQueryingStatus;
+      if (denomSel != null) {
+        r.denomsSel = denomSel;
+        r.rawWithdrawalAmount = denomSel.totalWithdrawCost;
+        r.effectiveWithdrawalAmount = denomSel.totalCoinValue;
+        r.instructedAmount = denomSel.totalWithdrawCost;
+      }
       return TransitionResult.transition(r);
     } else {
       return TransitionResult.stay();
@@ -3389,7 +3401,8 @@ export async function confirmWithdrawal(
 ): Promise<AcceptWithdrawalResponse> {
   const parsedTx = parseTransactionIdentifier(req.transactionId);
   const selectedExchange = req.exchangeBaseUrl;
-  const instructedAmount = Amounts.parseOrThrow(req.amount);
+  const instructedAmount =
+    req.amount == null ? undefined : Amounts.parseOrThrow(req.amount);
 
   if (parsedTx?.tag !== TransactionType.Withdrawal) {
     throw Error("invalid withdrawal transaction ID");
@@ -3412,10 +3425,20 @@ export async function confirmWithdrawal(
     throw Error("not a bank integrated withdrawal");
   }
 
+  let instructedCurrency: string;
+  if (instructedAmount) {
+    instructedCurrency = instructedAmount.currency;
+  } else {
+    if (!withdrawalGroup.wgInfo.bankInfo.currency) {
+      throw Error("currency must be provided by bank");
+    }
+    instructedCurrency = withdrawalGroup.wgInfo.bankInfo.currency;
+  }
+
   const exchange = await fetchFreshExchange(wex, selectedExchange);
   requireExchangeTosAcceptedOrThrow(exchange);
 
-  if (checkWithdrawalHardLimitExceeded(exchange, req.amount)) {
+  if (req.amount && checkWithdrawalHardLimitExceeded(exchange, req.amount)) {
     throw Error("withdrawal would exceed hard KYC limit");
   }
 
@@ -3452,14 +3475,17 @@ export async function confirmWithdrawal(
     bankWireTypes,
   );
 
-  const withdrawalAccountList = await fetchWithdrawalAccountInfo(
-    wex,
-    {
-      exchange,
-      instructedAmount,
-    },
-    wex.cancellationToken,
-  );
+  let withdrawalAccountList: WithdrawalExchangeAccountDetails[] = [];
+  if (instructedAmount) {
+    withdrawalAccountList = await fetchWithdrawalAccountInfo(
+      wex,
+      {
+        exchange,
+        instructedAmount,
+      },
+      wex.cancellationToken,
+    );
+  }
 
   const senderWire = withdrawalGroup.wgInfo.bankInfo.senderWire;
 
@@ -3498,9 +3524,9 @@ export async function confirmWithdrawal(
           await tx.bankAccountsV2.indexes.byPaytoUri.get(senderWire);
         if (existingAccount) {
           // Add currency for existing known bank account if necessary
-          if (existingAccount.currencies?.includes(instructedAmount.currency)) 
{
+          if (existingAccount.currencies?.includes(instructedCurrency)) {
             existingAccount.currencies = [
-              instructedAmount.currency,
+              instructedCurrency,
               ...(existingAccount.currencies ?? []),
             ];
             existingAccount.currencies.sort();
@@ -3511,7 +3537,7 @@ export async function confirmWithdrawal(
 
         const myId = `acct:${encodeCrock(getRandomBytes(32))}`;
         await tx.bankAccountsV2.put({
-          currencies: [instructedAmount.currency],
+          currencies: [instructedCurrency],
           kycCompleted: false,
           paytoUri: senderWire,
           bankAccountId: myId,
@@ -3533,12 +3559,17 @@ export async function confirmWithdrawal(
     wex,
     withdrawalGroup.withdrawalGroupId,
   );
-  const initalDenoms = await getInitialDenomsSelection(
-    wex,
-    exchange.exchangeBaseUrl,
-    instructedAmount,
-    req.forcedDenomSel,
-  );
+
+  let initialDenoms: DenomSelectionState | undefined;
+
+  if (instructedAmount != null) {
+    initialDenoms = await getInitialDenomsSelection(
+      wex,
+      exchange.exchangeBaseUrl,
+      instructedAmount,
+      req.forcedDenomSel,
+    );
+  }
 
   let pending = false;
   await ctx.transition({}, async (rec) => {
@@ -3560,9 +3591,19 @@ export async function confirmWithdrawal(
         rec.exchangeBaseUrl = exchange.exchangeBaseUrl;
         rec.instructedAmount = req.amount;
         rec.restrictAge = req.restrictAge;
-        rec.denomsSel = initalDenoms;
-        rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost;
-        rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue;
+        if (initialDenoms != null) {
+          rec.denomsSel = initialDenoms;
+          rec.rawWithdrawalAmount = initialDenoms.totalWithdrawCost;
+          rec.effectiveWithdrawalAmount = initialDenoms.totalCoinValue;
+        } else {
+          rec.denomsSel = undefined;
+          rec.rawWithdrawalAmount = Amounts.stringify(
+            Amounts.zeroOfCurrency(instructedCurrency),
+          );
+          rec.effectiveWithdrawalAmount = Amounts.stringify(
+            Amounts.zeroOfCurrency(instructedCurrency),
+          );
+        }
         checkDbInvariant(
           rec.wgInfo.withdrawalType === WithdrawalRecordType.BankIntegrated,
           "withdrawal type mismatch",
@@ -3583,6 +3624,7 @@ export async function confirmWithdrawal(
 
   await wex.taskScheduler.resetTaskRetries(ctx.taskId);
 
+  // FIXME: Merge with transaction above!
   const res = await wex.db.runReadWriteTx(
     {
       storeNames: ["exchanges"],
@@ -3660,14 +3702,20 @@ export async function acceptBankIntegratedWithdrawal(
     contents: "prepared acceptBankIntegratedWithdrawal",
   });
 
-  let amount: AmountString;
+  let amount: AmountString | undefined;
   if (p.info.amount == null) {
     if (req.amount == null) {
-      throw Error(
-        "amount required, as withdrawal operation has flexible amount",
-      );
+      if (p.info.editableAmount) {
+        throw Error(
+          "amount required, as withdrawal operation has flexible amount",
+        );
+      }
+      // Amount will be determined by the bank only after withdrawal has
+      // been confirmed by the wallet.
+      amount = undefined;
+    } else {
+      amount = Amounts.stringify(req.amount);
     }
-    amount = Amounts.stringify(req.amount);
   } else {
     if (req.amount == null) {
       amount = p.info.amount;
@@ -3686,7 +3734,7 @@ export async function acceptBankIntegratedWithdrawal(
 
   logger.info(`confirming withdrawal with tx ${p.transactionId}`);
   await confirmWithdrawal(wex, {
-    amount: Amounts.stringify(amount),
+    amount: amount == null ? undefined : Amounts.stringify(amount),
     exchangeBaseUrl: selectedExchange,
     transactionId: p.transactionId,
     restrictAge: req.restrictAge,
@@ -3783,7 +3831,7 @@ async function fetchAccount(
   cancellationToken: CancellationToken,
 ): Promise<WithdrawalExchangeAccountDetails> {
   let paytoUri: string;
-  let transferAmount: AmountString | undefined = undefined;
+  let transferAmount: AmountString | undefined;
   let currencySpecification: CurrencySpecification | undefined = undefined;
   if (acct.conversion_url != null) {
     const reqUrl = new URL("cashin-rate", acct.conversion_url);
@@ -3860,9 +3908,7 @@ async function fetchAccount(
     currencySpecification,
     creditRestrictions: acct.credit_restrictions,
   };
-  if (transferAmount != null) {
-    acctInfo.transferAmount = transferAmount;
-  }
+  acctInfo.transferAmount = transferAmount;
   return acctInfo;
 }
 

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