taler-typescript-core

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

commit cd439e8e793b5a49ba14cdc18119063731c18df8
parent 4dfd246f086a3cdde84a74184ba3d2deb436ce10
Author: Florian Dold <florian@dold.me>
Date:   Wed,  9 Jul 2025 01:56:08 +0200

wallet-core,harness: fix DD64 issues, make tests pass

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-exchange-kyc-auth.ts | 7+++++--
Mpackages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts | 1+
Mpackages/taler-harness/src/integrationtests/test-tops-peer.ts | 15++++++++++++---
Mpackages/taler-util/src/types-taler-wallet-transactions.ts | 1+
Mpackages/taler-wallet-core/src/deposits.ts | 66++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mpackages/taler-wallet-core/src/kyc.ts | 20++++++++++++++++----
Mpackages/taler-wallet-core/src/pay-peer-pull-credit.ts | 34+++++++++++++++++++++++++---------
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 34+++++++++++++++++++++++++---------
Mpackages/taler-wallet-core/src/withdraw.ts | 50+++++++++++++++++++++++++++++++++++++++-----------
9 files changed, 172 insertions(+), 56 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-exchange-kyc-auth.ts b/packages/taler-harness/src/integrationtests/test-exchange-kyc-auth.ts @@ -179,8 +179,11 @@ export async function runExchangeKycAuthTest(t: GlobalTestState) { }); console.log(j2s(checkResp1)); - - t.assertDeepEqual(checkResp1.case, HttpStatusCode.Accepted); + // Time-sensitive which status will be returned. + t.assertTrue( + checkResp1.case === HttpStatusCode.Accepted || + checkResp1.case === HttpStatusCode.Ok, + ); } const reservePair2 = createEddsaKeyPair(); diff --git a/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts @@ -142,6 +142,7 @@ export async function runKycFormWithdrawalTest(t: GlobalTestState) { body: { FULL_NAME: "Alice Abc", DATE_OF_BIRTH: "2000-01-01", + FORM_ID: "test", }, }, ); diff --git a/packages/taler-harness/src/integrationtests/test-tops-peer.ts b/packages/taler-harness/src/integrationtests/test-tops-peer.ts @@ -185,11 +185,20 @@ export async function runTopsPeerTest(t: GlobalTestState) { console.log(j2s(infoResp2)); if ( - infoResp2.case === "ok" && - infoResp2.body.requirements[0].form === "accept-tos" + !( + infoResp2.case === "ok" && + infoResp2.body.requirements[0].form === "accept-tos" + ) ) { - t.fail("requirements include ToS, but client wants to do p2p"); + t.fail("requirements need to include ToS (due to zero measure)"); } + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: prepareResp.transactionId, + txState: { + major: TransactionMajorState.Done, + }, + }); } runTopsPeerTest.suites = ["wallet"]; diff --git a/packages/taler-util/src/types-taler-wallet-transactions.ts b/packages/taler-util/src/types-taler-wallet-transactions.ts @@ -193,6 +193,7 @@ export enum TransactionMinorState { Unknown = "unknown", Deposit = "deposit", KycRequired = "kyc", + KycInit = "kyc-init", MergeKycRequired = "merge-kyc", BalanceKycRequired = "balance-kyc", BalanceKycInit = "balance-kyc-init", diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -629,20 +629,34 @@ export function computeDepositTransactionStatus( minor: TransactionMinorState.Deposit, }; case DepositOperationStatus.PendingAggregateKyc: - return { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.KycRequired, - }; + if (dg.kycInfo?.accessToken != null) { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycRequired, + }; + } else { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycInit, + }; + } case DepositOperationStatus.LegacyPendingTrack: return { major: TransactionMajorState.Pending, minor: TransactionMinorState.Track, }; case DepositOperationStatus.SuspendedAggregateKyc: - return { - major: TransactionMajorState.Suspended, - minor: TransactionMinorState.KycRequired, - }; + if (dg.kycInfo?.accessToken != null) { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.KycRequired, + }; + } else { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.KycInit, + }; + } case DepositOperationStatus.LegacySuspendedTrack: return { major: TransactionMajorState.Suspended, @@ -675,17 +689,31 @@ export function computeDepositTransactionStatus( major: TransactionMajorState.SuspendedAborting, }; case DepositOperationStatus.PendingDepositKyc: - return { - major: TransactionMajorState.Pending, - // We lie to the UI by hiding the specific KYC state. - minor: TransactionMinorState.KycRequired, - }; + if (dg.kycInfo?.accessToken != null) { + return { + major: TransactionMajorState.Pending, + // We lie to the UI by hiding the specific KYC state. + minor: TransactionMinorState.KycRequired, + }; + } else { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycInit, + }; + } case DepositOperationStatus.SuspendedDepositKyc: - return { - major: TransactionMajorState.Suspended, - // We lie to the UI by hiding the specific KYC state. - minor: TransactionMinorState.KycRequired, - }; + if (dg.kycInfo?.accessToken != null) { + return { + major: TransactionMajorState.Suspended, + // We lie to the UI by hiding the specific KYC state. + minor: TransactionMinorState.KycRequired, + }; + } else { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.KycInit, + }; + } case DepositOperationStatus.PendingDepositKycAuth: return { major: TransactionMajorState.Pending, @@ -1074,6 +1102,7 @@ async function processDepositGroupPendingKyc( lastCheckStatus: maybeKycInfo.lastCheckStatus, lastDeny: maybeKycInfo.lastDeny, lastRuleGen: maybeKycInfo.lastRuleGen, + haveAccessToken: maybeKycInfo.accessToken != null, }; } @@ -1111,6 +1140,7 @@ async function processDepositGroupPendingKyc( kycInfo.lastCheckCode = algoRes.updatedStatus.lastCheckCode; kycInfo.lastDeny = algoRes.updatedStatus.lastDeny; kycInfo.lastRuleGen = algoRes.updatedStatus.lastRuleGen; + kycInfo.accessToken = algoRes.updatedStatus.accessToken; const requiresAuth = algoRes.requiresAuth; diff --git a/packages/taler-wallet-core/src/kyc.ts b/packages/taler-wallet-core/src/kyc.ts @@ -324,6 +324,7 @@ export interface GenericKycStatusReq { readonly lastRuleGen?: number | undefined; readonly lastAmlReview?: boolean | undefined; readonly lastDeny?: DbPreciseTimestamp | undefined; + readonly haveAccessToken: boolean; } export interface GenericKycStatusResp { @@ -331,6 +332,7 @@ export interface GenericKycStatusResp { taskResult: TaskRunResult; requiresAuth?: boolean; updatedStatus?: { + accessToken?: string; lastCheckStatus?: number | undefined; lastCheckCode?: number | undefined; lastRuleGen?: number | undefined; @@ -349,7 +351,7 @@ export function isKycOperationDue(st: GenericKycStatusReq): boolean { AbsoluteTime.isExpired( AbsoluteTime.addDuration( timestampAbsoluteFromDb(st.lastDeny), - Duration.fromSpec({ minutes: 2 }), + Duration.fromSpec({ minutes: 30 }), ), ) ); @@ -381,11 +383,11 @@ export async function runKycCheckAlgo( let doLongpoll: boolean; - if (st.lastCheckCode == null) { + if (st.lastCheckStatus == null || !st.haveAccessToken) { doLongpoll = false; } else if ( st.lastCheckStatus === HttpStatusCode.Forbidden || - st.lastCheckCode === HttpStatusCode.Conflict + st.lastCheckStatus === HttpStatusCode.Conflict ) { doLongpoll = true; url.searchParams.set("lpt", "1"); @@ -458,7 +460,13 @@ export async function runKycCheckAlgo( updatedStatus.lastDeny = undefined; break; case HttpStatusCode.Ok: { + const resp = await readSuccessResponseJsonOrThrow( + kycStatusRes, + codecForAccountKycStatus(), + ); + updatedStatus.lastRuleGen = resp.rule_gen; updatedStatus.lastDeny = undefined; + updatedStatus.accessToken = resp.access_token; break; } case HttpStatusCode.Accepted: @@ -466,6 +474,8 @@ export async function runKycCheckAlgo( kycStatusRes, codecForAccountKycStatus(), ); + updatedStatus.accessToken = resp.access_token; + updatedStatus.lastRuleGen = resp.rule_gen; exposedLimits = resp.limits; rst.taskResult = TaskRunResult.longpollReturnedPending(); break; @@ -481,7 +491,9 @@ export async function runKycCheckAlgo( } if (exposedLimits) { - switch (await checkLimit(exposedLimits, "DEPOSIT", st.amount)) { + const checkRes = await checkLimit(exposedLimits, st.operation, st.amount); + logger.trace(`limit check result: ${LimitCheckResult[checkRes]}`); + switch (checkRes) { case LimitCheckResult.Allowed: updatedStatus.lastDeny = undefined; break; diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -567,6 +567,7 @@ async function processPendingMergeKycRequired( lastCheckStatus: pullIni.kycLastCheckStatus, lastDeny: pullIni.kycLastDeny, lastRuleGen: pullIni.kycLastRuleGen, + haveAccessToken: pullIni.kycAccessToken != null, }; } @@ -590,6 +591,7 @@ async function processPendingMergeKycRequired( rec.kycLastCheckCode = updatedStatus.lastCheckCode; rec.kycLastDeny = updatedStatus.lastDeny; rec.kycLastRuleGen = updatedStatus.lastRuleGen; + rec.kycAccessToken = updatedStatus.accessToken; return TransitionResultType.Transition; }); return algoRes.taskResult; @@ -1141,10 +1143,29 @@ export function computePeerPullCreditTransactionState( minor: TransactionMinorState.CreatePurse, }; case PeerPullPaymentCreditStatus.PendingMergeKycRequired: - return { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.MergeKycRequired, - }; + if (pullCreditRecord.kycAccessToken != null) { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.MergeKycRequired, + }; + } else { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycInit, + }; + } + case PeerPullPaymentCreditStatus.SuspendedMergeKycRequired: + if (pullCreditRecord.kycAccessToken != null) { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.MergeKycRequired, + }; + } else { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.KycInit, + }; + } case PeerPullPaymentCreditStatus.PendingReady: return { major: TransactionMajorState.Pending, @@ -1174,11 +1195,6 @@ export function computePeerPullCreditTransactionState( major: TransactionMajorState.Pending, minor: TransactionMinorState.Withdraw, }; - case PeerPullPaymentCreditStatus.SuspendedMergeKycRequired: - return { - major: TransactionMajorState.Suspended, - minor: TransactionMinorState.MergeKycRequired, - }; case PeerPullPaymentCreditStatus.Aborted: return { major: TransactionMajorState.Aborted, diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -650,6 +650,7 @@ async function processPeerPushDebitMergeKyc( lastCheckStatus: peerInc.kycLastCheckStatus, lastDeny: peerInc.kycLastDeny, lastRuleGen: peerInc.kycLastRuleGen, + haveAccessToken: peerInc.kycAccessToken != null, }; } @@ -673,6 +674,7 @@ async function processPeerPushDebitMergeKyc( rec.kycLastCheckCode = updatedStatus.lastCheckCode; rec.kycLastDeny = updatedStatus.lastDeny; rec.kycLastRuleGen = updatedStatus.lastRuleGen; + rec.kycAccessToken = updatedStatus.accessToken; return TransitionResultType.Transition; }); return algoRes.taskResult; @@ -1198,10 +1200,29 @@ export function computePeerPushCreditTransactionState( major: TransactionMajorState.Done, }; case PeerPushCreditStatus.PendingMergeKycRequired: - return { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.MergeKycRequired, - }; + if (pushCreditRecord.kycAccessToken) { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.MergeKycRequired, + }; + } else { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycInit, + }; + } + case PeerPushCreditStatus.SuspendedMergeKycRequired: + if (pushCreditRecord.kycAccessToken) { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.MergeKycRequired, + }; + } else { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.KycInit, + }; + } case PeerPushCreditStatus.PendingWithdrawing: return { major: TransactionMajorState.Pending, @@ -1212,11 +1233,6 @@ export function computePeerPushCreditTransactionState( major: TransactionMajorState.Suspended, minor: TransactionMinorState.Merge, }; - case PeerPushCreditStatus.SuspendedMergeKycRequired: - return { - major: TransactionMajorState.Suspended, - minor: TransactionMinorState.MergeKycRequired, - }; case PeerPushCreditStatus.SuspendedWithdrawing: return { major: TransactionMajorState.Suspended, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -857,15 +857,29 @@ export function computeWithdrawalTransactionStatus( minor: TransactionMinorState.WithdrawCoins, }; case WithdrawalGroupStatus.PendingKyc: - return { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.KycRequired, - }; + if (!!wgRecord.kycAccessToken) { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycRequired, + }; + } else { + return { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycInit, + }; + } case WithdrawalGroupStatus.SuspendedKyc: - return { - major: TransactionMajorState.Suspended, - minor: TransactionMinorState.KycRequired, - }; + if (!!wgRecord.kycAccessToken) { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.KycRequired, + }; + } else { + return { + major: TransactionMajorState.Suspended, + minor: TransactionMinorState.KycInit, + }; + } case WithdrawalGroupStatus.FailedAbortingBank: return { major: TransactionMajorState.Failed, @@ -1475,8 +1489,12 @@ async function transitionKycRequired( planchet.planchetStatus = PlanchetStatus.KycRequired; await tx.planchets.put(planchet); } - if (wg2.status !== WithdrawalGroupStatus.PendingReady) { - return TransitionResult.stay(); + switch (wg2.status) { + case WithdrawalGroupStatus.PendingReady: + case WithdrawalGroupStatus.PendingKyc: + break; + default: + return TransitionResult.stay(); } wg2.kycPaytoHash = legiRequiredResp.h_payto; wg2.kycLastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now()); @@ -2317,6 +2335,7 @@ async function processWithdrawalGroupPendingKyc( lastCheckStatus: withdrawalGroup.kycLastCheckStatus, lastDeny: withdrawalGroup.kycLastDeny, lastRuleGen: withdrawalGroup.kycLastRuleGen, + haveAccessToken: withdrawalGroup.kycAccessToken != null, }; } @@ -2343,6 +2362,7 @@ async function processWithdrawalGroupPendingKyc( rec.kycLastCheckCode = updatedStatus.lastCheckCode; rec.kycLastDeny = updatedStatus.lastDeny; rec.kycLastRuleGen = updatedStatus.lastRuleGen; + rec.kycAccessToken = updatedStatus.accessToken; return TransitionResult.transition(rec); }); @@ -2503,6 +2523,12 @@ async function processWithdrawalGroupPendingReady( const { withdrawalGroupId } = withdrawalGroup; const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); + logger.trace( + `processWithdrawalGroupPendingReady in DB state ${ + WithdrawalGroupStatus[withdrawalGroup.status] + }`, + ); + checkDbInvariant( withdrawalGroup.denomsSel !== undefined, "can't process uninitialized exchange", @@ -2512,7 +2538,7 @@ async function processWithdrawalGroupPendingReady( "can't get funding uri from uninitialized wg", ); const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl; - logger.trace(`updating exchange beofre processing wg`); + logger.trace(`updating exchange before processing wg`); const exch = await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl); if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) { @@ -2600,6 +2626,8 @@ async function processWithdrawalGroupPendingReady( ); } + logger.trace(`withdrawing ${numTotalCoins} coins`); + for (let i = 0; i < numTotalCoins; i += maxBatchSize) { let resp: WithdrawalBatchResult; if (exchangeVer.current >= 26) {