taler-typescript-core

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

commit 0997ade82af82e625fb0b4da40f2543bac38992d
parent 4d3cad1b51fefea6902cc7841f7ad104ca4fda04
Author: Florian Dold <florian@dold.me>
Date:   Mon, 29 Jun 2026 19:56:50 +0200

wallet-core: properly initialize kycAuthTransferOptions in deposit transactions

This commit also expands the tops-nexus-swt test to cover this.

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-tops-nexus-swt.ts | 39+++++++++++++++++++++++++++++++++++++--
Mpackages/taler-wallet-core/src/deposits.ts | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 102 insertions(+), 23 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-tops-nexus-swt.ts b/packages/taler-harness/src/integrationtests/test-tops-nexus-swt.ts @@ -23,8 +23,8 @@ import { j2s, Logger, Paytos, - TalerProtocolDuration, TransactionMajorState, + TransactionMinorState, TransactionType, WithdrawalType, } from "@gnu-taler/taler-util"; @@ -36,7 +36,6 @@ import { ExchangeService, GlobalTestState, LibeufinNexusService, - MerchantService, NexusBankAccountInfo, setupDb, } from "../harness/harness.js"; @@ -155,6 +154,42 @@ export async function runTopsNexusSwtTest(t: GlobalTestState) { major: TransactionMajorState.Done, }, }); + + // Now, deposit to a *different* bank account to test KYC auth for the wallet. + + // Random IBAN generated via "libeufin-nexus testing iban gen --country CH" + const walletIban = "CH5210395CX7GTDCRC3C4"; + + const walletPayto = Paytos.toFullString( + Paytos.createIban(walletIban as IbanString, undefined, { + "receiver-name": "Alice", + }), + ); + + const depositRes = await walletClient.call( + WalletApiOperation.CreateDepositGroup, + { + amount: "CHF:5", + depositPaytoUri: walletPayto, + }, + ); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: depositRes.transactionId, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycAuthRequired, + }, + }); + + const txDet = await walletClient.call(WalletApiOperation.GetTransactionById, { + transactionId: depositRes.transactionId, + }); + + console.log(j2s(txDet)); + + t.assertTrue(txDet.type == TransactionType.Deposit); + t.assertTrue((txDet.kycAuthTransferInfo?.transferOptions.length ?? 0) > 0); } runTopsNexusSwtTest.suites = ["tops", "libeufin"]; diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -1042,6 +1042,42 @@ async function processDepositGroupAborting( return waitForRefreshOnDepositGroup(wex, depositGroup); } +async function getAuthTransferDetails( + wex: WalletExecutionContext, + depositGroup: DepositGroupRecord, + exchangeBaseUrl: string, +): Promise<{ + options: TransferOptionRaw[]; + transferExpiry: TalerProtocolTimestamp | undefined; +}> { + let options: TransferOptionRaw[] = []; + let transferExpiry: TalerProtocolTimestamp | undefined = undefined; + + const exch = await fetchFreshExchange(wex, exchangeBaseUrl); + + for (const acct of exch.wireInfo.accounts) { + const optRes = await getTransferOptionsRaw(wex, { + acct, + reservePriv: depositGroup.merchantPriv, + reservePub: depositGroup.merchantPub, + transferAmount: exch.tinyAmount, + }); + options.push(...optRes.transferOptions); + if (!transferExpiry) { + transferExpiry = optRes.transferExpiry; + } else { + transferExpiry = AbsoluteTime.toProtocolTimestamp( + AbsoluteTime.min( + AbsoluteTime.fromProtocolTimestamp(optRes.transferExpiry), + AbsoluteTime.fromProtocolTimestamp(transferExpiry), + ), + ); + } + } + + return { options, transferExpiry }; +} + /** * Process the transaction in states where KYC is required. * Used for both the deposit KYC and aggregate KYC. @@ -1126,27 +1162,13 @@ async function processDepositGroupPendingKyc( let transferExpiry: TalerProtocolTimestamp | undefined = undefined; if (algoRes.requiresAuth) { - const exch = await fetchFreshExchange(wex, kycInfo.exchangeBaseUrl); - - for (const acct of exch.wireInfo.accounts) { - const optRes = await getTransferOptionsRaw(wex, { - acct, - reservePriv: depositGroup.merchantPriv, - reservePub: depositGroup.merchantPub, - transferAmount: exch.tinyAmount, - }); - options.push(...optRes.transferOptions); - if (!transferExpiry) { - transferExpiry = optRes.transferExpiry; - } else { - transferExpiry = AbsoluteTime.toProtocolTimestamp( - AbsoluteTime.min( - AbsoluteTime.fromProtocolTimestamp(optRes.transferExpiry), - AbsoluteTime.fromProtocolTimestamp(transferExpiry), - ), - ); - } - } + const res = await getAuthTransferDetails( + wex, + depositGroup, + kycInfo.exchangeBaseUrl, + ); + options = res.options; + transferExpiry = res.transferExpiry; } // Now store the result. @@ -1172,6 +1194,9 @@ async function processDepositGroupPendingKyc( case DepositOperationStatus.PendingDepositKycAuth: if (!requiresAuth) { newDg.operationStatus = DepositOperationStatus.PendingDepositKyc; + } else { + newDg.kycAuthTransferOptions = options; + newDg.kycAuthTransferExpiry = transferExpiry; } break; default: @@ -1319,6 +1344,18 @@ async function transitionToKycRequired( const ctx = new DepositTransactionContext(wex, depositGroupId); + let options: TransferOptionRaw[] = []; + let transferExpiry: TalerProtocolTimestamp | undefined = undefined; + if (args.badKycAuth) { + const res = await getAuthTransferDetails( + wex, + depositGroup, + args.exchangeUrl, + ); + options = res.options; + transferExpiry = res.transferExpiry; + } + await wex.runLegacyWalletDbTx(async (tx) => { const [dg, h] = await ctx.getRecordHandle(tx); if (!dg) { @@ -1336,6 +1373,8 @@ async function transitionToKycRequired( case DepositOperationStatus.PendingDeposit: if (args.badKycAuth) { dg.operationStatus = DepositOperationStatus.PendingDepositKycAuth; + dg.kycAuthTransferExpiry = transferExpiry; + dg.kycAuthTransferOptions = options; } else { dg.operationStatus = DepositOperationStatus.PendingDepositKyc; } @@ -1343,11 +1382,16 @@ async function transitionToKycRequired( case DepositOperationStatus.PendingDepositKycAuth: if (!args.badKycAuth) { dg.operationStatus = DepositOperationStatus.PendingDepositKyc; + } else { + dg.kycAuthTransferExpiry = transferExpiry; + dg.kycAuthTransferOptions = options; } break; case DepositOperationStatus.PendingDepositKyc: if (args.badKycAuth) { dg.operationStatus = DepositOperationStatus.PendingDepositKycAuth; + dg.kycAuthTransferExpiry = transferExpiry; + dg.kycAuthTransferOptions = options; } break; default: