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: add BalanceKycIn


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: add BalanceKycInit states
Date: Wed, 21 Aug 2024 20:47:17 +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 f85ddb7a4 wallet-core: add BalanceKycInit states
f85ddb7a4 is described below

commit f85ddb7a46bb46b635662a5ba258195bb46fce91
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Aug 21 20:47:10 2024 +0200

    wallet-core: add BalanceKycInit states
---
 .../src/types-taler-wallet-transactions.ts         |   1 +
 packages/taler-wallet-core/src/balance.ts          |   2 +
 packages/taler-wallet-core/src/common.ts           |  52 +++++
 packages/taler-wallet-core/src/db.ts               |  49 +++--
 packages/taler-wallet-core/src/exchanges.ts        |  89 +++------
 .../taler-wallet-core/src/pay-peer-pull-credit.ts  | 126 ++++++++++--
 .../taler-wallet-core/src/pay-peer-push-credit.ts  | 217 ++++++++++++++-------
 packages/taler-wallet-core/src/withdraw.ts         | 116 +++++++++--
 8 files changed, 472 insertions(+), 180 deletions(-)

diff --git a/packages/taler-util/src/types-taler-wallet-transactions.ts 
b/packages/taler-util/src/types-taler-wallet-transactions.ts
index 018c3a041..675878e0c 100644
--- a/packages/taler-util/src/types-taler-wallet-transactions.ts
+++ b/packages/taler-util/src/types-taler-wallet-transactions.ts
@@ -129,6 +129,7 @@ export enum TransactionMinorState {
   KycRequired = "kyc",
   MergeKycRequired = "merge-kyc",
   BalanceKycRequired = "balance-kyc",
+  BalanceKycInit = "balance-kyc-init",
   Track = "track",
   SubmitPayment = "submit-payment",
   RebindSession = "rebind-session",
diff --git a/packages/taler-wallet-core/src/balance.ts 
b/packages/taler-wallet-core/src/balance.ts
index 3b61ee4de..396efda1f 100644
--- a/packages/taler-wallet-core/src/balance.ts
+++ b/packages/taler-wallet-core/src/balance.ts
@@ -363,6 +363,8 @@ export async function getBalancesInsideTransaction(
         case WithdrawalGroupStatus.PendingBalanceKyc:
         case WithdrawalGroupStatus.SuspendedBalanceKyc:
         case WithdrawalGroupStatus.SuspendedKyc:
+        case WithdrawalGroupStatus.PendingBalanceKycInit:
+        case WithdrawalGroupStatus.SuspendedBalanceKycInit:
         case WithdrawalGroupStatus.PendingKyc: {
           checkDbInvariant(
             wg.denomsSel !== undefined,
diff --git a/packages/taler-wallet-core/src/common.ts 
b/packages/taler-wallet-core/src/common.ts
index 668ebe5c1..02dca20e8 100644
--- a/packages/taler-wallet-core/src/common.ts
+++ b/packages/taler-wallet-core/src/common.ts
@@ -863,6 +863,58 @@ export async function genericWaitForState(
   }
 }
 
+/**
+ * Wait until the wallet is in a particular state.
+ *
+ * Two functions must be provided:
+ * 1. checkState, which checks if the wallet is in the
+ *    desired state.
+ * 2. filterNotification, which checks whether a notification
+ *    might have lead to a state change.
+ */
+export async function genericWaitForStateVal<T>(
+  wex: WalletExecutionContext,
+  args: {
+    checkState: () => Promise<T | undefined>;
+    filterNotification: (notif: WalletNotification) => boolean;
+  },
+): Promise<T> {
+  await wex.taskScheduler.ensureRunning();
+
+  // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax.
+  const flag = new AsyncFlag();
+  // Raise purchaseNotifFlag whenever we get a notification
+  // about our refresh.
+  const cancelNotif = wex.ws.addNotificationListener((notif) => {
+    if (args.filterNotification(notif)) {
+      flag.raise();
+    }
+  });
+  const unregisterOnCancelled = wex.cancellationToken.onCancelled((reason) => {
+    cancelNotif();
+    flag.raise();
+  });
+
+  try {
+    while (true) {
+      if (wex.cancellationToken.isCancelled) {
+        throw Error("cancelled");
+      }
+      const val = await args.checkState();
+      if (val != null) {
+        return val;
+      }
+      // Wait for the next transition
+      await flag.wait();
+      flag.reset();
+    }
+  } catch (e) {
+    unregisterOnCancelled();
+    cancelNotif();
+    throw e;
+  }
+}
+
 export function requireExchangeTosAcceptedOrThrow(
   exchange: ReadyExchangeSummary,
 ): void {
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 2cee19f8e..c48a43f11 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -311,10 +311,19 @@ export enum WithdrawalGroupStatus {
 
   /**
    * Exchange wants KYC info from the user.
+   * KYC link is ready.
    */
   PendingBalanceKyc = 0x0100_0006,
   SuspendedBalanceKyc = 0x0110_006,
 
+  /**
+   * Exchange wants KYC info from the user.
+   *
+   * KYC link is not ready yet, the KYC process is still initializing.
+   */
+  PendingBalanceKycInit = 0x0100_0007,
+  SuspendedBalanceKycInit = 0x0110_007,
+
   /**
    * Proposed to the user, has can choose to accept/refuse.
    */
@@ -1461,6 +1470,7 @@ export interface KycPendingInfo {
   paytoHash: string;
   requirementRow: number;
 }
+
 /**
  * Group of withdrawal operations that need to be executed.
  * (Either for a normal withdrawal or from a reward.)
@@ -1936,24 +1946,33 @@ export interface PeerPushDebitRecord {
 }
 
 export enum PeerPullPaymentCreditStatus {
+  /**
+   * Typically the initial state of the peer-pull-credit transaction,
+   * purse will be created.
+   */
   PendingCreatePurse = 0x0100_0000,
+  SuspendedCreatePurse = 0x0110_0000,
+
   /**
    * Purse created, waiting for the other party to accept the
    * invoice and deposit money into it.
    */
   PendingReady = 0x0100_0001,
-  PendingMergeKycRequired = 0x0100_0002,
-  PendingWithdrawing = 0x0100_0003,
-  PendingBalanceKycRequired = 0x0100_0004,
-
-  AbortingDeletePurse = 0x0103_0000,
-
-  SuspendedCreatePurse = 0x0110_0000,
   SuspendedReady = 0x0110_0001,
+
+  PendingMergeKycRequired = 0x0100_0002,
   SuspendedMergeKycRequired = 0x0110_0002,
+
+  PendingWithdrawing = 0x0100_0003,
   SuspendedWithdrawing = 0x0110_0003,
+
+  PendingBalanceKycRequired = 0x0100_0004,
   SuspendedBalanceKycRequired = 0x0110_0004,
 
+  PendingBalanceKycInit = 0x0100_0005,
+  SuspendedBalanceKycInit = 0x0110_0005,
+
+  AbortingDeletePurse = 0x0103_0000,
   SuspendedAbortingDeletePurse = 0x0113_0000,
 
   Done = 0x0500_0000,
@@ -2014,25 +2033,31 @@ export interface PeerPullCreditRecord {
 
   kycUrl?: string;
 
+  kycAccessToken?: string;
+
   withdrawalGroupId: string | undefined;
 }
 
 export enum PeerPushCreditStatus {
   PendingMerge = 0x0100_0000,
+  SuspendedMerge = 0x0110_0000,
+
   PendingMergeKycRequired = 0x0100_0001,
+  SuspendedMergeKycRequired = 0x0110_0001,
+
   /**
    * Merge was successful and withdrawal group has been created, now
    * everything is in the hand of the withdrawal group.
    */
   PendingWithdrawing = 0x0100_0002,
+  SuspendedWithdrawing = 0x0110_0002,
 
   PendingBalanceKycRequired = 0x0100_0003,
-
-  SuspendedMerge = 0x0110_0000,
-  SuspendedMergeKycRequired = 0x0110_0001,
-  SuspendedWithdrawing = 0x0110_0002,
   SuspendedBalanceKycRequired = 0x0110_0003,
 
+  PendingBalanceKycInit = 0x0100_0004,
+  SuspendedBalanceKycInit = 0x0110_0004,
+
   DialogProposed = 0x0101_0000,
 
   Done = 0x0500_0000,
@@ -2087,6 +2112,8 @@ export interface PeerPushPaymentIncomingRecord {
   kycInfo?: KycPendingInfo;
 
   kycUrl?: string;
+
+  kycAccessToken?: string;
 }
 
 export enum PeerPullDebitRecordStatus {
diff --git a/packages/taler-wallet-core/src/exchanges.ts 
b/packages/taler-wallet-core/src/exchanges.ts
index fca2d4012..1820751f8 100644
--- a/packages/taler-wallet-core/src/exchanges.ts
+++ b/packages/taler-wallet-core/src/exchanges.ts
@@ -404,6 +404,23 @@ async function internalGetExchangeScopeInfo(
   };
 }
 
+function getKycStatusFromReserveStatus(
+  status: ReserveRecordStatus,
+): ExchangeWalletKycStatus {
+  switch (status) {
+    case ReserveRecordStatus.Done:
+      return ExchangeWalletKycStatus.Done;
+    // FIXME: Do we handle the suspended state?
+    case ReserveRecordStatus.SuspendedLegiInit:
+    case ReserveRecordStatus.PendingLegiInit:
+      return ExchangeWalletKycStatus.LegiInit;
+    // FIXME: Do we handle the suspended state?
+    case ReserveRecordStatus.SuspendedLegi:
+    case ReserveRecordStatus.PendingLegi:
+      return ExchangeWalletKycStatus.Legi;
+  }
+}
+
 async function makeExchangeListItem(
   tx: WalletDbReadOnlyTransaction<
     ["globalCurrencyExchanges", "globalCurrencyAuditors"]
@@ -425,24 +442,10 @@ async function makeExchangeListItem(
     scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails);
   }
 
-  let walletKycStatus: ExchangeWalletKycStatus | undefined = undefined;
-  if (reserveRec) {
-    switch (reserveRec.status) {
-      case ReserveRecordStatus.Done:
-        walletKycStatus = ExchangeWalletKycStatus.Done;
-        break;
-      // FIXME: Do we handle the suspended state?
-      case ReserveRecordStatus.SuspendedLegiInit:
-      case ReserveRecordStatus.PendingLegiInit:
-        walletKycStatus = ExchangeWalletKycStatus.LegiInit;
-        break;
-      // FIXME: Do we handle the suspended state?
-      case ReserveRecordStatus.SuspendedLegi:
-      case ReserveRecordStatus.PendingLegi:
-        walletKycStatus = ExchangeWalletKycStatus.Legi;
-        break;
-    }
-  }
+  let walletKycStatus: ExchangeWalletKycStatus | undefined =
+    reserveRec && reserveRec.status
+      ? getKycStatusFromReserveStatus(reserveRec.status)
+      : undefined;
 
   const listItem: ExchangeListItem = {
     exchangeBaseUrl: r.baseUrl,
@@ -2970,6 +2973,8 @@ export type BalanceThresholdCheckResult =
   | {
       result: "violation";
       nextThreshold: AmountString;
+      walletKycStatus: ExchangeWalletKycStatus | undefined;
+      walletKycAccessToken: string | undefined;
     };
 
 export async function checkIncomingAmountLegalUnderKycBalanceThreshold(
@@ -3013,8 +3018,9 @@ export async function 
checkIncomingAmountLegalUnderKycBalanceThreshold(
       // Check if we already have KYC for a sufficient threshold.
 
       const reserveId = exchangeRec.currentMergeReserveRowId;
+      let reserveRec: ReserveRecord | undefined;
       if (reserveId) {
-        const reserveRec = await tx.reserves.get(reserveId);
+        reserveRec = await tx.reserves.get(reserveId);
         checkDbInvariant(!!reserveRec, "reserve");
         // FIXME: also consider KYC expiration!
         if (reserveRec.thresholdNext) {
@@ -3066,53 +3072,16 @@ export async function 
checkIncomingAmountLegalUnderKycBalanceThreshold(
         return {
           result: "violation",
           nextThreshold: limNext ?? limViolated,
+          walletKycStatus: reserveRec?.status
+            ? getKycStatusFromReserveStatus(reserveRec.status)
+            : undefined,
+          walletKycAccessToken: reserveRec?.kycAccessToken,
         };
       }
     },
   );
 }
 
-/**
- * Wait until it is allowed for the user to add the given amount
- * to the wallet balance, either because the balance is low enough
- * or KYC was completed.
- */
-export async function waitIncomingAmountLegalUnderKycBalanceThreshold(
-  wex: WalletExecutionContext,
-  exchangeBaseUrl: string,
-  amountExpected: AmountLike,
-): Promise<void> {
-  await genericWaitForState(wex, {
-    async checkState(): Promise<boolean> {
-      const checkRes = await checkIncomingAmountLegalUnderKycBalanceThreshold(
-        wex,
-        exchangeBaseUrl,
-        amountExpected,
-      );
-      logger.info(
-        `balance check result for ${exchangeBaseUrl} +${amountExpected}: ${j2s(
-          checkRes,
-        )}`,
-      );
-      if (checkRes.result === "ok") {
-        return true;
-      }
-      await handleStartExchangeWalletKyc(wex, {
-        amount: checkRes.nextThreshold,
-        exchangeBaseUrl,
-      });
-      return false;
-    },
-    filterNotification(notif) {
-      return (
-        (notif.type === NotificationType.ExchangeStateTransition &&
-          notif.exchangeBaseUrl === exchangeBaseUrl) ||
-        notif.type === NotificationType.BalanceChange
-      );
-    },
-  });
-}
-
 /**
  * Wait until kyc has passed for the wallet.
  *
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
index ac50f2e0e..e9a3970c6 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -24,6 +24,7 @@ import {
   CheckPeerPullCreditResponse,
   ContractTermsUtil,
   ExchangeReservePurseRequest,
+  ExchangeWalletKycStatus,
   HttpStatusCode,
   InitiatePeerPullCreditRequest,
   InitiatePeerPullCreditResponse,
@@ -65,6 +66,7 @@ import {
   TransitionResult,
   TransitionResultType,
   constructTaskIdentifier,
+  genericWaitForStateVal,
   requireExchangeTosAcceptedOrThrow,
   runWithClientCancellation,
 } from "./common.js";
@@ -84,11 +86,11 @@ import {
   timestampPreciseToDb,
 } from "./db.js";
 import {
+  BalanceThresholdCheckResult,
   checkIncomingAmountLegalUnderKycBalanceThreshold,
   fetchFreshExchange,
   getScopeForAllExchanges,
   handleStartExchangeWalletKyc,
-  waitIncomingAmountLegalUnderKycBalanceThreshold,
 } from "./exchanges.js";
 import {
   codecForExchangePurseStatus,
@@ -415,16 +417,20 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
           case PeerPullPaymentCreditStatus.PendingBalanceKycRequired:
             newStatus = 
PeerPullPaymentCreditStatus.SuspendedBalanceKycRequired;
             break;
-          case PeerPullPaymentCreditStatus.Done:
+          case PeerPullPaymentCreditStatus.PendingBalanceKycInit:
+            newStatus = PeerPullPaymentCreditStatus.SuspendedBalanceKycInit;
+            break;
           case PeerPullPaymentCreditStatus.SuspendedBalanceKycRequired:
           case PeerPullPaymentCreditStatus.SuspendedCreatePurse:
           case PeerPullPaymentCreditStatus.SuspendedMergeKycRequired:
           case PeerPullPaymentCreditStatus.SuspendedReady:
           case PeerPullPaymentCreditStatus.SuspendedWithdrawing:
+          case PeerPullPaymentCreditStatus.SuspendedBalanceKycInit:
+          case PeerPullPaymentCreditStatus.SuspendedAbortingDeletePurse:
+          case PeerPullPaymentCreditStatus.Done:
           case PeerPullPaymentCreditStatus.Aborted:
           case PeerPullPaymentCreditStatus.Failed:
           case PeerPullPaymentCreditStatus.Expired:
-          case PeerPullPaymentCreditStatus.SuspendedAbortingDeletePurse:
             break;
           default:
             assertUnreachable(pullCreditRec.status);
@@ -475,6 +481,8 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
           case PeerPullPaymentCreditStatus.Expired:
           case PeerPullPaymentCreditStatus.PendingBalanceKycRequired:
           case PeerPullPaymentCreditStatus.SuspendedBalanceKycRequired:
+          case PeerPullPaymentCreditStatus.PendingBalanceKycInit:
+          case PeerPullPaymentCreditStatus.SuspendedBalanceKycInit:
             break;
           case PeerPullPaymentCreditStatus.AbortingDeletePurse:
           case PeerPullPaymentCreditStatus.SuspendedAbortingDeletePurse:
@@ -520,12 +528,16 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
           case PeerPullPaymentCreditStatus.PendingWithdrawing:
           case PeerPullPaymentCreditStatus.PendingReady:
           case PeerPullPaymentCreditStatus.PendingBalanceKycRequired:
+          case PeerPullPaymentCreditStatus.PendingBalanceKycInit:
           case PeerPullPaymentCreditStatus.AbortingDeletePurse:
           case PeerPullPaymentCreditStatus.Done:
           case PeerPullPaymentCreditStatus.Failed:
           case PeerPullPaymentCreditStatus.Expired:
           case PeerPullPaymentCreditStatus.Aborted:
             break;
+          case PeerPullPaymentCreditStatus.SuspendedBalanceKycInit:
+            newStatus = PeerPullPaymentCreditStatus.PendingBalanceKycInit;
+            break;
           case PeerPullPaymentCreditStatus.SuspendedCreatePurse:
             newStatus = PeerPullPaymentCreditStatus.PendingCreatePurse;
             break;
@@ -581,6 +593,8 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
         switch (pullCreditRec.status) {
           case PeerPullPaymentCreditStatus.PendingBalanceKycRequired:
           case PeerPullPaymentCreditStatus.SuspendedBalanceKycRequired:
+          case PeerPullPaymentCreditStatus.PendingBalanceKycInit:
+          case PeerPullPaymentCreditStatus.SuspendedBalanceKycInit:
           case PeerPullPaymentCreditStatus.PendingCreatePurse:
           case PeerPullPaymentCreditStatus.PendingMergeKycRequired:
             newStatus = PeerPullPaymentCreditStatus.AbortingDeletePurse;
@@ -942,7 +956,7 @@ async function handlePeerPullCreditCreatePurse(
       if (rec.status !== PeerPullPaymentCreditStatus.PendingCreatePurse) {
         return TransitionResult.stay();
       }
-      rec.status = PeerPullPaymentCreditStatus.PendingBalanceKycRequired;
+      rec.status = PeerPullPaymentCreditStatus.PendingBalanceKycInit;
       return TransitionResult.transition(rec);
     });
     return TaskRunResult.progress();
@@ -1114,6 +1128,7 @@ export async function processPeerPullCredit(
     case PeerPullPaymentCreditStatus.PendingWithdrawing:
       return handlePeerPullCreditWithdrawing(wex, pullIni);
     case PeerPullPaymentCreditStatus.PendingBalanceKycRequired:
+    case PeerPullPaymentCreditStatus.PendingBalanceKycInit:
       return processPeerPullCreditBalanceKyc(ctx, pullIni);
     case PeerPullPaymentCreditStatus.Aborted:
     case PeerPullPaymentCreditStatus.Failed:
@@ -1124,6 +1139,7 @@ export async function processPeerPullCredit(
     case PeerPullPaymentCreditStatus.SuspendedMergeKycRequired:
     case PeerPullPaymentCreditStatus.SuspendedReady:
     case PeerPullPaymentCreditStatus.SuspendedWithdrawing:
+    case PeerPullPaymentCreditStatus.SuspendedBalanceKycInit:
       break;
     default:
       assertUnreachable(pullIni.status);
@@ -1139,22 +1155,80 @@ async function processPeerPullCreditBalanceKyc(
   const exchangeBaseUrl = peerInc.exchangeBaseUrl;
   const amount = peerInc.estimatedAmountEffective;
 
-  await waitIncomingAmountLegalUnderKycBalanceThreshold(
-    ctx.wex,
-    exchangeBaseUrl,
-    amount,
-  );
-  await ctx.transition({}, async (rec) => {
-    if (!rec) {
-      return TransitionResult.stay();
-    }
-    if (rec.status !== PeerPullPaymentCreditStatus.PendingBalanceKycRequired) {
-      return TransitionResult.stay();
-    }
-    rec.status = PeerPullPaymentCreditStatus.PendingCreatePurse;
-    return TransitionResult.transition(rec);
+  const ret = await genericWaitForStateVal(ctx.wex, {
+    async checkState(): Promise<BalanceThresholdCheckResult | undefined> {
+      const checkRes = await checkIncomingAmountLegalUnderKycBalanceThreshold(
+        ctx.wex,
+        exchangeBaseUrl,
+        amount,
+      );
+      logger.info(
+        `balance check result for ${exchangeBaseUrl} +${amount}: ${j2s(
+          checkRes,
+        )}`,
+      );
+      if (checkRes.result === "ok") {
+        return checkRes;
+      }
+      if (
+        peerInc.status === PeerPullPaymentCreditStatus.PendingBalanceKycInit &&
+        checkRes.walletKycStatus === ExchangeWalletKycStatus.Legi
+      ) {
+        return checkRes;
+      }
+      await handleStartExchangeWalletKyc(ctx.wex, {
+        amount: checkRes.nextThreshold,
+        exchangeBaseUrl,
+      });
+      return undefined;
+    },
+    filterNotification(notif) {
+      return (
+        (notif.type === NotificationType.ExchangeStateTransition &&
+          notif.exchangeBaseUrl === exchangeBaseUrl) ||
+        notif.type === NotificationType.BalanceChange
+      );
+    },
   });
-  return TaskRunResult.progress();
+
+  if (ret.result === "ok") {
+    await ctx.transition({}, async (rec) => {
+      if (!rec) {
+        return TransitionResult.stay();
+      }
+      if (
+        rec.status !== PeerPullPaymentCreditStatus.PendingBalanceKycRequired
+      ) {
+        return TransitionResult.stay();
+      }
+      rec.status = PeerPullPaymentCreditStatus.PendingCreatePurse;
+      return TransitionResult.transition(rec);
+    });
+    return TaskRunResult.progress();
+  } else if (
+    peerInc.status === PeerPullPaymentCreditStatus.PendingBalanceKycInit &&
+    ret.walletKycStatus === ExchangeWalletKycStatus.Legi
+  ) {
+    await ctx.transition({}, async (rec) => {
+      if (!rec) {
+        return TransitionResult.stay();
+      }
+      if (rec.status !== PeerPullPaymentCreditStatus.PendingBalanceKycInit) {
+        return TransitionResult.stay();
+      }
+      rec.status = PeerPullPaymentCreditStatus.PendingBalanceKycRequired;
+      delete rec.kycInfo;
+      rec.kycAccessToken = ret.walletKycAccessToken;
+      rec.kycUrl = new URL(
+        `kyc-spa/${ret.walletKycAccessToken}`,
+        exchangeBaseUrl,
+      ).href;
+      return TransitionResult.transition(rec);
+    });
+    return TaskRunResult.progress();
+  } else {
+    throw Error("not reached");
+  }
 }
 
 async function processPeerPullCreditKycRequired(
@@ -1547,6 +1621,16 @@ export function computePeerPullCreditTransactionState(
         major: TransactionMajorState.Suspended,
         minor: TransactionMinorState.BalanceKycRequired,
       };
+    case PeerPullPaymentCreditStatus.PendingBalanceKycInit:
+      return {
+        major: TransactionMajorState.Pending,
+        minor: TransactionMinorState.BalanceKycInit,
+      };
+    case PeerPullPaymentCreditStatus.SuspendedBalanceKycInit:
+      return {
+        major: TransactionMajorState.Suspended,
+        minor: TransactionMinorState.BalanceKycInit,
+      };
   }
 }
 
@@ -1606,5 +1690,9 @@ export function computePeerPullCreditTransactionActions(
       return [TransactionAction.Suspend, TransactionAction.Abort];
     case PeerPullPaymentCreditStatus.SuspendedBalanceKycRequired:
       return [TransactionAction.Resume, TransactionAction.Abort];
+    case PeerPullPaymentCreditStatus.PendingBalanceKycInit:
+      return [TransactionAction.Suspend, TransactionAction.Abort];
+    case PeerPullPaymentCreditStatus.SuspendedBalanceKycInit:
+      return [TransactionAction.Resume, TransactionAction.Abort];
   }
 }
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
index 6f2b15d20..ed59f0d4d 100644
--- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -21,6 +21,7 @@ import {
   ConfirmPeerPushCreditRequest,
   ContractTermsUtil,
   ExchangePurseMergeRequest,
+  ExchangeWalletKycStatus,
   HttpStatusCode,
   Logger,
   NotificationType,
@@ -62,6 +63,7 @@ import {
   TransitionResult,
   TransitionResultType,
   constructTaskIdentifier,
+  genericWaitForStateVal,
   requireExchangeTosAcceptedOrThrow,
 } from "./common.js";
 import {
@@ -79,11 +81,11 @@ import {
   timestampPreciseToDb,
 } from "./db.js";
 import {
+  BalanceThresholdCheckResult,
   checkIncomingAmountLegalUnderKycBalanceThreshold,
   fetchFreshExchange,
   getScopeForAllExchanges,
   handleStartExchangeWalletKyc,
-  waitIncomingAmountLegalUnderKycBalanceThreshold,
 } from "./exchanges.js";
 import {
   codecForExchangePurseStatus,
@@ -362,60 +364,48 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
   }
 
   async suspendTransaction(): Promise<void> {
-    const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this;
-    const transitionInfo = await wex.db.runReadWriteTx(
-      { storeNames: ["peerPushCredit", "transactionsMeta"] },
-      async (tx) => {
-        const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
-        if (!pushCreditRec) {
-          logger.warn(`peer push credit ${peerPushCreditId} not found`);
-          return;
-        }
-        let newStatus: PeerPushCreditStatus | undefined = undefined;
-        switch (pushCreditRec.status) {
-          case PeerPushCreditStatus.DialogProposed:
-          case PeerPushCreditStatus.Done:
-          case PeerPushCreditStatus.SuspendedMerge:
-          case PeerPushCreditStatus.SuspendedMergeKycRequired:
-          case PeerPushCreditStatus.SuspendedWithdrawing:
-          case PeerPushCreditStatus.SuspendedBalanceKycRequired:
-          case PeerPushCreditStatus.Failed:
-          case PeerPushCreditStatus.Aborted:
-            break;
-          case PeerPushCreditStatus.PendingBalanceKycRequired:
-            newStatus = PeerPushCreditStatus.SuspendedBalanceKycRequired;
-            break;
-          case PeerPushCreditStatus.PendingMergeKycRequired:
-            newStatus = PeerPushCreditStatus.SuspendedMergeKycRequired;
-            break;
-          case PeerPushCreditStatus.PendingMerge:
-            newStatus = PeerPushCreditStatus.SuspendedMerge;
-            break;
-          case PeerPushCreditStatus.PendingWithdrawing:
-            // FIXME: Suspend internal withdrawal transaction!
-            newStatus = PeerPushCreditStatus.SuspendedWithdrawing;
-            break;
-          default:
-            assertUnreachable(pushCreditRec.status);
-        }
-        if (newStatus != null) {
-          const oldTxState =
-            computePeerPushCreditTransactionState(pushCreditRec);
-          pushCreditRec.status = newStatus;
-          const newTxState =
-            computePeerPushCreditTransactionState(pushCreditRec);
-          await tx.peerPushCredit.put(pushCreditRec);
-          await this.updateTransactionMeta(tx);
-          return {
-            oldTxState,
-            newTxState,
-          };
-        }
-        return undefined;
-      },
-    );
-    notifyTransition(wex, transactionId, transitionInfo);
-    wex.taskScheduler.stopShepherdTask(retryTag);
+    await this.transition({}, async (pushCreditRec) => {
+      if (!pushCreditRec) {
+        return TransitionResult.stay();
+      }
+      let newStatus: PeerPushCreditStatus | undefined = undefined;
+      switch (pushCreditRec.status) {
+        case PeerPushCreditStatus.DialogProposed:
+        case PeerPushCreditStatus.Done:
+        case PeerPushCreditStatus.SuspendedMerge:
+        case PeerPushCreditStatus.SuspendedMergeKycRequired:
+        case PeerPushCreditStatus.SuspendedWithdrawing:
+        case PeerPushCreditStatus.SuspendedBalanceKycRequired:
+        case PeerPushCreditStatus.SuspendedBalanceKycInit:
+        case PeerPushCreditStatus.Failed:
+        case PeerPushCreditStatus.Aborted:
+          break;
+        case PeerPushCreditStatus.PendingBalanceKycRequired:
+          newStatus = PeerPushCreditStatus.SuspendedBalanceKycRequired;
+          break;
+        case PeerPushCreditStatus.PendingBalanceKycInit:
+          newStatus = PeerPushCreditStatus.SuspendedBalanceKycInit;
+          break;
+        case PeerPushCreditStatus.PendingMergeKycRequired:
+          newStatus = PeerPushCreditStatus.SuspendedMergeKycRequired;
+          break;
+        case PeerPushCreditStatus.PendingMerge:
+          newStatus = PeerPushCreditStatus.SuspendedMerge;
+          break;
+        case PeerPushCreditStatus.PendingWithdrawing:
+          // FIXME: Suspend internal withdrawal transaction!
+          newStatus = PeerPushCreditStatus.SuspendedWithdrawing;
+          break;
+        default:
+          assertUnreachable(pushCreditRec.status);
+      }
+      if (newStatus != null) {
+        pushCreditRec.status = newStatus;
+        return TransitionResult.transition(pushCreditRec);
+      } else {
+        return TransitionResult.stay();
+      }
+    });
   }
 
   async abortTransaction(): Promise<void> {
@@ -443,6 +433,8 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
           case PeerPushCreditStatus.PendingWithdrawing:
           case PeerPushCreditStatus.PendingMergeKycRequired:
           case PeerPushCreditStatus.PendingMerge:
+          case PeerPushCreditStatus.PendingBalanceKycInit:
+          case PeerPushCreditStatus.SuspendedBalanceKycInit:
             newStatus = PeerPushCreditStatus.Aborted;
             break;
           default:
@@ -481,13 +473,14 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
         let newStatus: PeerPushCreditStatus | undefined = undefined;
         switch (pushCreditRec.status) {
           case PeerPushCreditStatus.DialogProposed:
-          case PeerPushCreditStatus.Done:
           case PeerPushCreditStatus.PendingMergeKycRequired:
           case PeerPushCreditStatus.PendingMerge:
           case PeerPushCreditStatus.PendingWithdrawing:
+          case PeerPushCreditStatus.PendingBalanceKycRequired:
+          case PeerPushCreditStatus.PendingBalanceKycInit:
+          case PeerPushCreditStatus.Done:
           case PeerPushCreditStatus.Aborted:
           case PeerPushCreditStatus.Failed:
-          case PeerPushCreditStatus.PendingBalanceKycRequired:
             break;
           case PeerPushCreditStatus.SuspendedMerge:
             newStatus = PeerPushCreditStatus.PendingMerge;
@@ -502,6 +495,9 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
           case PeerPushCreditStatus.SuspendedBalanceKycRequired:
             newStatus = PeerPushCreditStatus.PendingBalanceKycRequired;
             break;
+          case PeerPushCreditStatus.SuspendedBalanceKycInit:
+            newStatus = PeerPushCreditStatus.PendingBalanceKycInit;
+            break;
           default:
             assertUnreachable(pushCreditRec.status);
         }
@@ -551,6 +547,8 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
           case PeerPushCreditStatus.SuspendedWithdrawing:
           case PeerPushCreditStatus.PendingBalanceKycRequired:
           case PeerPushCreditStatus.SuspendedBalanceKycRequired:
+          case PeerPushCreditStatus.PendingBalanceKycInit:
+          case PeerPushCreditStatus.SuspendedBalanceKycInit:
             newStatus = PeerPushCreditStatus.Failed;
             break;
           default:
@@ -925,7 +923,7 @@ async function handlePendingMerge(
       if (rec.status !== PeerPushCreditStatus.PendingMerge) {
         return TransitionResult.stay();
       }
-      rec.status = PeerPushCreditStatus.PendingBalanceKycRequired;
+      rec.status = PeerPushCreditStatus.PendingBalanceKycInit;
       return TransitionResult.transition(rec);
     });
     return TaskRunResult.progress();
@@ -1181,6 +1179,7 @@ export async function processPeerPushCredit(
     case PeerPushCreditStatus.PendingWithdrawing: {
       return handlePendingWithdrawing(wex, peerInc);
     }
+    case PeerPushCreditStatus.PendingBalanceKycInit:
     case PeerPushCreditStatus.PendingBalanceKycRequired: {
       return await processPeerPushCreditBalanceKyc(ctx, peerInc);
     }
@@ -1196,22 +1195,80 @@ async function processPeerPushCreditBalanceKyc(
   const exchangeBaseUrl = peerInc.exchangeBaseUrl;
   const amount = peerInc.estimatedAmountEffective;
 
-  await waitIncomingAmountLegalUnderKycBalanceThreshold(
-    ctx.wex,
-    exchangeBaseUrl,
-    amount,
-  );
-  await ctx.transition({}, async (rec) => {
-    if (!rec) {
-      return TransitionResult.stay();
-    }
-    if (rec.status !== PeerPushCreditStatus.PendingBalanceKycRequired) {
-      return TransitionResult.stay();
-    }
-    rec.status = PeerPushCreditStatus.PendingMerge;
-    return TransitionResult.transition(rec);
+  const ret = await genericWaitForStateVal(ctx.wex, {
+    async checkState(): Promise<BalanceThresholdCheckResult | undefined> {
+      const checkRes = await checkIncomingAmountLegalUnderKycBalanceThreshold(
+        ctx.wex,
+        exchangeBaseUrl,
+        amount,
+      );
+      logger.info(
+        `balance check result for ${exchangeBaseUrl} +${amount}: ${j2s(
+          checkRes,
+        )}`,
+      );
+      if (checkRes.result === "ok") {
+        return checkRes;
+      }
+      if (
+        peerInc.status === PeerPushCreditStatus.PendingBalanceKycInit &&
+        checkRes.walletKycStatus === ExchangeWalletKycStatus.Legi
+      ) {
+        return checkRes;
+      }
+      await handleStartExchangeWalletKyc(ctx.wex, {
+        amount: checkRes.nextThreshold,
+        exchangeBaseUrl,
+      });
+      return undefined;
+    },
+    filterNotification(notif) {
+      return (
+        (notif.type === NotificationType.ExchangeStateTransition &&
+          notif.exchangeBaseUrl === exchangeBaseUrl) ||
+        notif.type === NotificationType.BalanceChange
+      );
+    },
   });
-  return TaskRunResult.progress();
+
+  if (ret.result === "ok") {
+    await ctx.transition({}, async (rec) => {
+      if (!rec) {
+        return TransitionResult.stay();
+      }
+      if (
+        rec.status !== PeerPushCreditStatus.PendingBalanceKycRequired
+      ) {
+        return TransitionResult.stay();
+      }
+      rec.status = PeerPushCreditStatus.PendingMerge;
+      return TransitionResult.transition(rec);
+    });
+    return TaskRunResult.progress();
+  } else if (
+    peerInc.status === PeerPushCreditStatus.PendingBalanceKycInit &&
+    ret.walletKycStatus === ExchangeWalletKycStatus.Legi
+  ) {
+    await ctx.transition({}, async (rec) => {
+      if (!rec) {
+        return TransitionResult.stay();
+      }
+      if (rec.status !== PeerPushCreditStatus.PendingBalanceKycInit) {
+        return TransitionResult.stay();
+      }
+      rec.status = PeerPushCreditStatus.PendingBalanceKycRequired;
+      delete rec.kycInfo;
+      rec.kycAccessToken = ret.walletKycAccessToken;
+      rec.kycUrl = new URL(
+        `kyc-spa/${ret.walletKycAccessToken}`,
+        exchangeBaseUrl,
+      ).href;
+      return TransitionResult.transition(rec);
+    });
+    return TaskRunResult.progress();
+  } else {
+    throw Error("not reached");
+  }
 }
 
 export async function confirmPeerPushCredit(
@@ -1325,6 +1382,16 @@ export function computePeerPushCreditTransactionState(
         major: TransactionMajorState.Suspended,
         minor: TransactionMinorState.BalanceKycRequired,
       };
+    case PeerPushCreditStatus.PendingBalanceKycInit:
+      return {
+        major: TransactionMajorState.Pending,
+        minor: TransactionMinorState.BalanceKycInit,
+      };
+    case PeerPushCreditStatus.SuspendedBalanceKycInit:
+      return {
+        major: TransactionMajorState.Suspended,
+        minor: TransactionMinorState.BalanceKycInit,
+      };
     default:
       assertUnreachable(pushCreditRecord.status);
   }
@@ -1366,6 +1433,10 @@ export function computePeerPushCreditTransactionActions(
       return [TransactionAction.Suspend, TransactionAction.Abort];
     case PeerPushCreditStatus.SuspendedBalanceKycRequired:
       return [TransactionAction.Resume, TransactionAction.Abort];
+    case PeerPushCreditStatus.PendingBalanceKycInit:
+      return [TransactionAction.Suspend, TransactionAction.Abort];
+    case PeerPushCreditStatus.SuspendedBalanceKycInit:
+      return [TransactionAction.Resume, TransactionAction.Abort];
     case PeerPushCreditStatus.Aborted:
       return [TransactionAction.Delete];
     case PeerPushCreditStatus.Failed:
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index d4038f008..8c56adb49 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -47,6 +47,7 @@ import {
   ExchangeBatchWithdrawRequest,
   ExchangeListItem,
   ExchangeUpdateStatus,
+  ExchangeWalletKycStatus,
   ExchangeWireAccount,
   ExchangeWithdrawBatchResponse,
   ExchangeWithdrawRequest,
@@ -122,6 +123,7 @@ import {
   TransitionResultType,
   constructTaskIdentifier,
   genericWaitForState,
+  genericWaitForStateVal,
   makeCoinAvailable,
   makeCoinsVisible,
   requireExchangeTosAcceptedOrThrow,
@@ -157,6 +159,7 @@ import {
 } from "./denomSelection.js";
 import { isWithdrawableDenom } from "./denominations.js";
 import {
+  BalanceThresholdCheckResult,
   ExchangeWireDetails,
   ReadyExchangeSummary,
   checkIncomingAmountLegalUnderKycBalanceThreshold,
@@ -168,7 +171,6 @@ import {
   listExchanges,
   lookupExchangeByUri,
   markExchangeUsed,
-  waitIncomingAmountLegalUnderKycBalanceThreshold,
 } from "./exchanges.js";
 import { DbAccess } from "./query.js";
 import {
@@ -629,6 +631,8 @@ export class WithdrawTransactionContext implements 
TransactionContext {
           case WithdrawalGroupStatus.PendingQueryingStatus:
           case WithdrawalGroupStatus.PendingBalanceKyc:
           case WithdrawalGroupStatus.SuspendedBalanceKyc:
+          case WithdrawalGroupStatus.PendingBalanceKycInit:
+          case WithdrawalGroupStatus.SuspendedBalanceKycInit:
             newStatus = WithdrawalGroupStatus.AbortedExchange;
             break;
           case WithdrawalGroupStatus.PendingReady:
@@ -845,6 +849,16 @@ export function computeWithdrawalTransactionStatus(
         major: TransactionMajorState.Suspended,
         minor: TransactionMinorState.BalanceKycRequired,
       };
+    case WithdrawalGroupStatus.PendingBalanceKycInit:
+      return {
+        major: TransactionMajorState.Pending,
+        minor: TransactionMinorState.BalanceKycInit,
+      };
+    case WithdrawalGroupStatus.SuspendedBalanceKycInit:
+      return {
+        major: TransactionMajorState.Suspended,
+        minor: TransactionMinorState.BalanceKycInit,
+      };
   }
 }
 
@@ -924,6 +938,14 @@ export function computeWithdrawalTransactionActions(
       ];
     case WithdrawalGroupStatus.SuspendedBalanceKyc:
       return [TransactionAction.Resume, TransactionAction.Abort];
+    case WithdrawalGroupStatus.PendingBalanceKycInit:
+      return [
+        TransactionAction.Suspend,
+        TransactionAction.Retry,
+        TransactionAction.Abort,
+      ];
+    case WithdrawalGroupStatus.SuspendedBalanceKycInit:
+      return [TransactionAction.Resume, TransactionAction.Abort];
   }
 }
 
@@ -943,22 +965,80 @@ async function processWithdrawalGroupBalanceKyc(
       "invalid state (expected withdrawal group to have effective withdrawal 
amount)",
     );
   }
-  await waitIncomingAmountLegalUnderKycBalanceThreshold(
-    ctx.wex,
-    exchangeBaseUrl,
-    amount,
-  );
-  await ctx.transition({}, async (wg) => {
-    if (!wg) {
-      return TransitionResult.stay();
-    }
-    if (wg.status !== WithdrawalGroupStatus.PendingBalanceKyc) {
-      return TransitionResult.stay();
-    }
-    wg.status = WithdrawalGroupStatus.PendingReady;
-    return TransitionResult.transition(wg);
+
+  const ret = await genericWaitForStateVal(ctx.wex, {
+    async checkState(): Promise<BalanceThresholdCheckResult | undefined> {
+      const checkRes = await checkIncomingAmountLegalUnderKycBalanceThreshold(
+        ctx.wex,
+        exchangeBaseUrl,
+        amount,
+      );
+      logger.info(
+        `balance check result for ${exchangeBaseUrl} +${amount}: ${j2s(
+          checkRes,
+        )}`,
+      );
+      if (checkRes.result === "ok") {
+        return checkRes;
+      }
+      if (
+        withdrawalGroup.status ===
+          WithdrawalGroupStatus.PendingBalanceKycInit &&
+        checkRes.walletKycStatus === ExchangeWalletKycStatus.Legi
+      ) {
+        return checkRes;
+      }
+      await handleStartExchangeWalletKyc(ctx.wex, {
+        amount: checkRes.nextThreshold,
+        exchangeBaseUrl,
+      });
+      return undefined;
+    },
+    filterNotification(notif) {
+      return (
+        (notif.type === NotificationType.ExchangeStateTransition &&
+          notif.exchangeBaseUrl === exchangeBaseUrl) ||
+        notif.type === NotificationType.BalanceChange
+      );
+    },
   });
-  return TaskRunResult.progress();
+
+  if (ret.result === "ok") {
+    await ctx.transition({}, async (wg) => {
+      if (!wg) {
+        return TransitionResult.stay();
+      }
+      if (wg.status !== WithdrawalGroupStatus.PendingBalanceKyc) {
+        return TransitionResult.stay();
+      }
+      wg.status = WithdrawalGroupStatus.PendingReady;
+      return TransitionResult.transition(wg);
+    });
+    return TaskRunResult.progress();
+  } else if (
+    withdrawalGroup.status === WithdrawalGroupStatus.PendingBalanceKycInit &&
+    ret.walletKycStatus === ExchangeWalletKycStatus.Legi
+  ) {
+    await ctx.transition({}, async (wg) => {
+      if (!wg) {
+        return TransitionResult.stay();
+      }
+      if (wg.status !== WithdrawalGroupStatus.PendingBalanceKycInit) {
+        return TransitionResult.stay();
+      }
+      wg.status = WithdrawalGroupStatus.PendingBalanceKyc;
+      wg.kycPending = undefined;
+      wg.kycAccessToken = ret.walletKycAccessToken;
+      wg.kycUrl = new URL(
+        `kyc-spa/${ret.walletKycAccessToken}`,
+        exchangeBaseUrl,
+      ).href;
+      return TransitionResult.transition(wg);
+    });
+    return TaskRunResult.progress();
+  } else {
+    throw Error("not reached");
+  }
 }
 
 async function processWithdrawalGroupDialogProposed(
@@ -2194,7 +2274,7 @@ async function processWithdrawalGroupPendingReady(
       if (!wg) {
         return TransitionResult.stay();
       }
-      wg.status = WithdrawalGroupStatus.PendingBalanceKyc;
+      wg.status = WithdrawalGroupStatus.PendingBalanceKycInit;
       return TransitionResult.transition(wg);
     });
     return TaskRunResult.progress();
@@ -2389,6 +2469,7 @@ export async function processWithdrawalGroup(
     case WithdrawalGroupStatus.DialogProposed:
       return await processWithdrawalGroupDialogProposed(ctx, withdrawalGroup);
     case WithdrawalGroupStatus.PendingBalanceKyc:
+    case WithdrawalGroupStatus.PendingBalanceKycInit:
       return await processWithdrawalGroupBalanceKyc(ctx, withdrawalGroup);
     case WithdrawalGroupStatus.AbortedBank:
     case WithdrawalGroupStatus.AbortedExchange:
@@ -2400,6 +2481,7 @@ export async function processWithdrawalGroup(
     case WithdrawalGroupStatus.SuspendedRegisteringBank:
     case WithdrawalGroupStatus.SuspendedWaitConfirmBank:
     case WithdrawalGroupStatus.SuspendedBalanceKyc:
+    case WithdrawalGroupStatus.SuspendedBalanceKycInit:
     case WithdrawalGroupStatus.Done:
     case WithdrawalGroupStatus.FailedBankAborted:
     case WithdrawalGroupStatus.AbortedUserRefused:

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