taler-typescript-core

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

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

wallet-core: expose coins after hard-limit recovery

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-kyc-withdrawal-recovery.ts | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 3+++
Mpackages/taler-wallet-core/src/withdraw.ts | 1+
3 files changed, 65 insertions(+), 0 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 @@ -19,6 +19,11 @@ import { WithdrawalKycStatus, TalerErrorCode, AmountString, + Amounts, + encodeCrock, + Paytos, + LimitOperationType, + TalerProtocolTimestamp, TransactionState, TransactionMajorState as Major, TransactionMinorState as Minor, @@ -29,9 +34,11 @@ import { KycTestEnv, configureSimpleKycInfoRule, createKycTestkudosEnvironmentFull, + postAmlDecision, postAmlDecisionNoRules, applyTimeTravelV2, } from "../harness/environments.js"; +import { coin_u1 } from "../harness/denomStructures.js"; async function withdraw( env: KycTestEnv, user: { username: string }, @@ -185,5 +192,59 @@ export async function runKycWithdrawalExpiryTest(t: GlobalTestState) { ); } +export async function runKycWithdrawalPartialHardTest(t: GlobalTestState) { + const env = await createKycTestkudosEnvironmentFull(t, { + walletPersistent: true, + coinConfig: [{ ...coin_u1("TESTKUDOS"), feeWithdraw: "TESTKUDOS:0" }], + adjustExchangeConfig(config) { + configureSimpleKycInfoRule(config, "withdraw"); + }, + }); + const user = await env.bankClient.createRandomBankUser(); + env.bankClient.setAuth({ username: user.username, password: user.password }); + await withdraw(env, user, "TESTKUDOS:1", { major: Major.Done }); + await postAmlDecision(t, { + exchangeBaseUrl: env.exchange.baseUrl, + paytoHash: encodeCrock(Paytos.hashNormalized(user.accountPaytoUri)), + amlPub: env.amlKeypair.pub, + amlPriv: env.amlKeypair.priv, + newRules: { + custom_measures: {}, + expiration_time: TalerProtocolTimestamp.never(), + rules: [ + { + operation_type: LimitOperationType.withdraw, + display_priority: 1, + measures: ["verboten"], + threshold: "TESTKUDOS:100", + timeframe: { d_us: "forever" }, + exposed: true, + is_and_combinator: false, + }, + ], + }, + }); + const id = await withdraw(env, user, "TESTKUDOS:200", { + major: Major.Failed, + minor: Minor.KycHardLimit, + }); + const tx = await env.walletClient.call(Op.GetTransactionById, { + transactionId: id, + }); + const balances = await env.walletClient.call(Op.GetBalances, {}); + const coins = await env.walletClient.call(Op.DumpCoins, {}); + assert.equal(tx.txState.major, Major.Failed); + assert.ok(coins.coins.length > 1); + t.assertAmountEquals( + balances.balances[0].available, + `TESTKUDOS:${coins.coins.length}`, + ); + assert.ok( + Amounts.cmp(balances.balances[0].available, "TESTKUDOS:1") > 0, + "Coins already withdrawn before hard-limit recovery must appear in the balance", + ); +} + runKycWithdrawalCumulativeHardTest.suites = ["wallet"]; runKycWithdrawalExpiryTest.suites = ["wallet"]; +runKycWithdrawalPartialHardTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -118,6 +118,7 @@ import { runKycWithdrawalCumulativeTest } from "./test-kyc-withdrawal-cumulative import { runKycWithdrawalCumulativeHardTest, runKycWithdrawalExpiryTest, + runKycWithdrawalPartialHardTest, } from "./test-kyc-withdrawal-recovery.js"; import { runKycWithdrawalVerbotenTest } from "./test-kyc-withdrawal-verboten.js"; import { runLibeufinBankTest } from "./test-libeufin-bank.js"; @@ -480,6 +481,8 @@ const allTests: TestMainFunction[] = [ withWalletDbBackend(runKycWithdrawalCumulativeHardTest, "indexeddb"), withWalletDbBackend(runKycWithdrawalExpiryTest, "default"), withWalletDbBackend(runKycWithdrawalExpiryTest, "indexeddb"), + withWalletDbBackend(runKycWithdrawalPartialHardTest, "default"), + withWalletDbBackend(runKycWithdrawalPartialHardTest, "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 @@ -2896,6 +2896,7 @@ async function processWithdrawalKycHardLimitRecovery( if (rec?.status !== WithdrawalGroupStatus.FinalizingKycHardLimit) { return; } + await makeCoinsVisible(wex, tx, ctx.transactionId); rec.status = finalStatus; rec.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now()); if (recoveryFailure) {