taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 77552c979f06ad5a169aa7f74c6a34d269c21222
parent 1e556e68289d7303ffd844cad896fa05ed89078f
Author: Florian Dold <florian@dold.me>
Date:   Sat, 22 Feb 2025 20:44:03 +0100

wallet-core: delay withdrawal attempt when KYC is not done

We do this instead of long-polling for KYC completion, as KYC could be
unfinished due to an operation other than withdrawal

Diffstat:
Mpackages/taler-wallet-core/src/db.ts | 6+++++-
Mpackages/taler-wallet-core/src/withdraw.ts | 45++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 49 insertions(+), 2 deletions(-)

diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts @@ -1512,6 +1512,11 @@ export interface WithdrawalGroupRecord { kycAccessToken?: string; /** + * Delay to wait until the next withdrawal attempt. + */ + kycWithdrawalDelay?: TalerProtocolDuration; + + /** * Secret seed used to derive planchets. * Stored since planchets are created lazily. */ @@ -1885,7 +1890,6 @@ export enum PeerPushDebitStatus { PendingReady = 0x0100_0001, AbortingDeletePurse = 0x0103_0000, - SuspendedCreatePurse = 0x0110_0000, SuspendedReady = 0x0110_0001, SuspendedAbortingDeletePurse = 0x0113_0000, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -2102,7 +2102,28 @@ async function processWithdrawalGroupPendingKyc( }); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { logger.info("kyc not done yet, long-poll remains pending"); - return TaskRunResult.longpollReturnedPending(); + // We know that KYC isn't done, but we don't know whether + // it's required for the withdrawal. It might only + // be required for a *different* operation. + // Thus we attempt withdrawal after a delay. + await ctx.transition({}, async (rec) => { + if (!rec) { + return TransitionResult.stay(); + } + switch (rec.status) { + case WithdrawalGroupStatus.PendingKyc: { + // Try withdrawal again. + rec.status = WithdrawalGroupStatus.PendingReady; + rec.kycWithdrawalDelay = Duration.toTalerProtocolDuration( + Duration.fromSpec({ minutes: 40 }), + ); + return TransitionResult.transition(rec); + } + default: + return TransitionResult.stay(); + } + }); + return TaskRunResult.progress(); } else { throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`); } @@ -2261,6 +2282,28 @@ async function processWithdrawalGroupPendingReady( const { withdrawalGroupId } = withdrawalGroup; const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); + if (withdrawalGroup.kycWithdrawalDelay) { + // We've been asked to delay the next withdrawal, + // as we're waiting for KYC. + const delay = Duration.fromTalerProtocolDuration( + withdrawalGroup.kycWithdrawalDelay, + ); + await ctx.transition({}, async (wg) => { + if (!wg) { + return TransitionResult.stay(); + } + switch (wg.status) { + case WithdrawalGroupStatus.PendingReady: + delete wg.kycWithdrawalDelay; + return TransitionResult.transition(wg); + default: + } + return TransitionResult.stay(); + }); + const nextRun = AbsoluteTime.addDuration(AbsoluteTime.now(), delay); + return TaskRunResult.runAgainAt(nextRun); + } + checkDbInvariant( withdrawalGroup.denomsSel !== undefined, "can't process uninitialized exchange",