taler-typescript-core

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

commit e1e8d150e3eb6d832f65810cc3fb189f943b8466
parent 5c0b8673e85c3136a994f1645b28a825e63783d0
Author: Florian Dold <dold@taler.net>
Date:   Tue,  3 Nov 2026 09:35:40 +0100

wallet-core: handle denomination errors on KYC-blocked planchets

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-kyc-withdrawal-recovery.ts | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 7++++++-
Mpackages/taler-wallet-core/src/withdraw.ts | 5++++-
3 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-kyc-withdrawal-recovery.ts b/packages/taler-harness/src/integrationtests/test-kyc-withdrawal-recovery.ts @@ -29,6 +29,8 @@ import { KycTestEnv, configureSimpleKycInfoRule, createKycTestkudosEnvironmentFull, + postAmlDecisionNoRules, + applyTimeTravelV2, } from "../harness/environments.js"; async function withdraw( env: KycTestEnv, @@ -117,4 +119,71 @@ export async function runKycWithdrawalCumulativeHardTest(t: GlobalTestState) { ); } +export async function runKycWithdrawalExpiryTest(t: GlobalTestState) { + t.allowLogs({ + file: "wallet-wallet-stderr.log", + level: "WARNING", + messages: [ + [ + /^marking denomination [0-9A-Z]+ of http:\/\/localhost:8081\/ as lost$/, + 7, + ], + [/^redenomination required for withdrawal [0-9A-Z]+$/, 1], + ], + justification: + "Time travel expires the seven original denomination families while KYC is pending.", + }); + t.allowLog({ + file: "exchange-httpd-testexchange-1-stderr.log", + level: "WARNING", + message: + /^External protocol violation detected at \.\.\/src\/exchange\/taler-exchange-httpd_post-withdraw\.c:\d+\.$/, + maxOccurrences: 1, + justification: + "The initial retry deliberately submits expired denominations before wallet redenomination.", + }); + const env = await createKycTestkudosEnvironmentFull(t, { + walletPersistent: true, + adjustExchangeConfig(config) { + configureSimpleKycInfoRule(config, "withdraw"); + }, + }); + const user = await env.bankClient.createRandomBankUser(); + env.bankClient.setAuth({ username: user.username, password: user.password }); + const id = await withdraw(env, user, "TESTKUDOS:20", { + major: Major.Pending, + minor: Minor.KycRequired, + }); + const blocked = await env.walletClient.call(Op.GetTransactionById, { + transactionId: id, + }); + await env.walletClient.call(Op.SuspendTransaction, { transactionId: id }); + await postAmlDecisionNoRules(t, { + exchangeBaseUrl: env.exchange.baseUrl, + paytoHash: blocked.kycPaytoHash!, + amlPub: env.amlKeypair.pub, + amlPriv: env.amlKeypair.priv, + }); + await env.merchant.stop(); + await applyTimeTravelV2(14 * 86400000, { + exchange: env.exchange, + walletClient: env.walletClient, + }); + await env.walletClient.call(Op.ResumeTransaction, { transactionId: id }); + await env.walletClient.call(Op.TestingWaitTransactionState, { + transactionId: id, + txState: { major: Major.Done }, + }); + const tx = await env.walletClient.call(Op.GetTransactionById, { + transactionId: id, + }); + + assert.equal( + tx.txState.major, + Major.Done, + "Cleared KYC withdrawal should redenominate expired coins and complete", + ); +} + runKycWithdrawalCumulativeHardTest.suites = ["wallet"]; +runKycWithdrawalExpiryTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -115,7 +115,10 @@ import { runKycSkipExpirationTest } from "./test-kyc-skip-expiration.js"; import { runKycTwoFormsTest } from "./test-kyc-two-forms.js"; import { runKycWalletDepositAbortTest } from "./test-kyc-wallet-deposit-abort.js"; import { runKycWithdrawalCumulativeTest } from "./test-kyc-withdrawal-cumulative.js"; -import { runKycWithdrawalCumulativeHardTest } from "./test-kyc-withdrawal-recovery.js"; +import { + runKycWithdrawalCumulativeHardTest, + runKycWithdrawalExpiryTest, +} from "./test-kyc-withdrawal-recovery.js"; import { runKycWithdrawalVerbotenTest } from "./test-kyc-withdrawal-verboten.js"; import { runLibeufinBankTest } from "./test-libeufin-bank.js"; import { runLibeufinConversionTest } from "./test-libeufin-conversion.js"; @@ -475,6 +478,8 @@ const allTests: TestMainFunction[] = [ runKycBalanceWithdrawalZeroLimitTest, withWalletDbBackend(runKycWithdrawalCumulativeHardTest, "default"), withWalletDbBackend(runKycWithdrawalCumulativeHardTest, "indexeddb"), + withWalletDbBackend(runKycWithdrawalExpiryTest, "default"), + withWalletDbBackend(runKycWithdrawalExpiryTest, "indexeddb"), withWalletDbBackend(runKycWithdrawalCumulativeTest, "default"), withWalletDbBackend(runKycWithdrawalCumulativeTest, "indexeddb"), runKycNewMeasureTest, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -3307,7 +3307,10 @@ async function processWithdrawalGroupPendingReady( await wex.runWalletDbTx(async (tx) => { const planchets = await tx.getPlanchetsByGroup(withdrawalGroupId); for (const p of planchets) { - if (p.planchetStatus !== PlanchetStatus.Pending) { + if ( + p.planchetStatus !== PlanchetStatus.Pending && + p.planchetStatus !== PlanchetStatus.KycRequired + ) { continue; } if (!p.lastError) {