taler-typescript-core

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

commit 2dc0f4f8c9104cc296613cf3abba876f35055f7e
parent 2ad56046aad4b172e6e75bdfb7d88c8bb27cdfc5
Author: Florian Dold <dold@taler.net>
Date:   Thu, 20 Aug 2026 19:06:47 +0200

wallet-core: wait for payment session replay

Diffstat:
Mpackages/taler-wallet-core/src/pay-merchant.test.ts | 22++++++++++++++++++++++
Mpackages/taler-wallet-core/src/pay-merchant.ts | 41+++++++++++++++++++++++++++++------------
2 files changed, 51 insertions(+), 12 deletions(-)

diff --git a/packages/taler-wallet-core/src/pay-merchant.test.ts b/packages/taler-wallet-core/src/pay-merchant.test.ts @@ -23,7 +23,9 @@ import { import assert from "node:assert"; import { test } from "node:test"; import { + PurchaseStatus, RefundGroupStatus, + WalletPurchase, WalletRefundGroup, WalletToken, } from "./db-common.js"; @@ -31,6 +33,7 @@ import { WalletDbTransaction } from "./dbtx.js"; import { getCoinsToSpendForMerchantRepair, getRefundTotals, + isPaymentSessionComplete, releasePaymentTokensInTx, setRefundGroupEffectiveAmount, splitPaymentOutputTokenSignatures, @@ -205,3 +208,22 @@ test("claimed contract terms are bound to the requested order and nonce", () => /wallet nonce/, ); }); + +test("payment replay waits for the requested session to finish", () => { + const purchase = { + timestampFirstSuccessfulPay: 1, + lastSessionId: "new-session", + purchaseStatus: PurchaseStatus.PendingPayingReplay, + } as Pick< + WalletPurchase, + "timestampFirstSuccessfulPay" | "lastSessionId" | "purchaseStatus" + >; + + assert.strictEqual( + isPaymentSessionComplete(purchase, "new-session"), + false, + ); + purchase.purchaseStatus = PurchaseStatus.Done; + assert.strictEqual(isPaymentSessionComplete(purchase, "new-session"), true); + assert.strictEqual(isPaymentSessionComplete(purchase, "other-session"), false); +}); diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -2338,17 +2338,12 @@ async function waitPaymentResult( const d = await expectProposalDownload(ctx.wex, purchase); - if (txRes.purchase.timestampFirstSuccessfulPay) { - if ( - waitSessionId == null || - txRes.purchase.lastSessionId === waitSessionId - ) { - return { - type: ConfirmPayResultType.Done, - contractTerms: d.contractTermsRaw, - transactionId: ctx.transactionId, - }; - } + if (isPaymentSessionComplete(txRes.purchase, waitSessionId)) { + return { + type: ConfirmPayResultType.Done, + contractTerms: d.contractTermsRaw, + transactionId: ctx.transactionId, + }; } if (txRes.retryRecord && txRes.retryRecord.retryInfo.retryCounter > 0) { @@ -2391,6 +2386,28 @@ async function waitPaymentResult( }); } +export function isPaymentSessionComplete( + purchase: Pick< + WalletPurchase, + "timestampFirstSuccessfulPay" | "lastSessionId" | "purchaseStatus" + >, + waitSessionId?: string, +): boolean { + if (!purchase.timestampFirstSuccessfulPay) { + return false; + } + if (waitSessionId == null) { + return true; + } + if (purchase.lastSessionId !== waitSessionId) { + return false; + } + return ![ + PurchaseStatus.PendingPayingReplay, + PurchaseStatus.SuspendedPayingReplay, + ].includes(purchase.purchaseStatus); +} + export async function getChoicesForPayment( wex: WalletExecutionContext, transactionId: string, @@ -2768,7 +2785,7 @@ export async function confirmPay( lastError: undefined, }; } - return waitPaymentResult(wex, proposalId); + return waitPaymentResult(wex, proposalId, sessionIdOverride); } logger.trace("confirmPay: purchase record does not exist yet");