taler-typescript-core

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

commit 6ec338581bb8af8b355265ddbeeeb25213ef7f08
parent 115773435d43f66493f7b45032659a4ba6f0e943
Author: Florian Dold <florian@dold.me>
Date:   Sat, 26 Apr 2025 02:09:05 +0200

wallet-core: fix transition to paid-by-other in merchant payments

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-payment-share-idempotency.ts | 6+++++-
Mpackages/taler-wallet-core/src/pay-merchant.ts | 43+++++++++++++++++++++++--------------------
2 files changed, 28 insertions(+), 21 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-payment-share-idempotency.ts b/packages/taler-harness/src/integrationtests/test-payment-share-idempotency.ts @@ -163,7 +163,7 @@ export async function runPaymentShareIdempotencyTest(t: GlobalTestState) { { talerPayUri: privatePayUri }, ); - t.assertTrue( + t.assertTrue( claimSecondWalletAgain.status === PreparePayResultType.PaymentPossible, ); @@ -238,6 +238,8 @@ export async function runPaymentShareIdempotencyTest(t: GlobalTestState) { { talerPayUri: privatePayUri }, ); + console.log(`second claim tx id: ${claimSecondWallet.transactionId}`); + t.assertTrue( claimSecondWallet.status === PreparePayResultType.PaymentPossible, ); @@ -283,6 +285,8 @@ export async function runPaymentShareIdempotencyTest(t: GlobalTestState) { claimSecondWalletAgain.status === PreparePayResultType.AlreadyConfirmed, ); + t.logStep("scenario-a-b-wait-done"); + await secondWallet.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: claimSecondWalletAgain.transactionId, txState: { diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -1351,6 +1351,8 @@ async function createOrReusePurchase( proposalId: string; transactionId: TransactionIdStr; }> { + // Find existing proposals from the same merchant + // with the same order ID. const oldProposals = await wex.db.runReadOnlyTx( { storeNames: ["purchases"] }, async (tx) => { @@ -1363,25 +1365,21 @@ async function createOrReusePurchase( const oldProposal = oldProposals.find((p) => { return ( - p.downloadSessionId === sessionId && - (!noncePriv || p.noncePriv === noncePriv) && - p.claimToken === claimToken + (!noncePriv || p.noncePriv === noncePriv) && p.claimToken === claimToken ); }); - // If we have already claimed this proposal with the same sessionId - // nonce and claim token, reuse it. */ - if ( - oldProposal && - oldProposal.downloadSessionId === sessionId && - (!noncePriv || oldProposal.noncePriv === noncePriv) && - oldProposal.claimToken === claimToken - ) { + // If we have already claimed this proposal with the same + // nonce and claim token, reuse it. + if (oldProposal) { logger.info( `Found old proposal (status=${ PurchaseStatus[oldProposal.purchaseStatus] }) for order ${orderId} at ${merchantBaseUrl}`, ); - const ctx = new PayMerchantTransactionContext(wex, oldProposal.proposalId); + const oldCtx = new PayMerchantTransactionContext( + wex, + oldProposal.proposalId, + ); if (oldProposal.shared || oldProposal.createdFromShared) { const download = await expectProposalDownload(wex, oldProposal); const paid = await checkIfOrderIsAlreadyPaid( @@ -1401,25 +1399,30 @@ async function createOrReusePurchase( logger.warn("purchase does not exist anymore"); return; } + // The order is only paid by another wallet + // if the merchant says it's paid but the local + // wallet is still in a dialog state. + switch (p.purchaseStatus) { + case PurchaseStatus.DialogProposed: + case PurchaseStatus.DialogShared: + break; + default: + return undefined; + } const oldTxState = computePayMerchantTransactionState(p); p.purchaseStatus = PurchaseStatus.FailedPaidByOther; const newTxState = computePayMerchantTransactionState(p); await tx.purchases.put(p); - await ctx.updateTransactionMeta(tx); + await oldCtx.updateTransactionMeta(tx); return { oldTxState, newTxState, balanceEffect: BalanceEffect.Any }; }, ); - - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Payment, - proposalId: oldProposal.proposalId, - }); - notifyTransition(wex, transactionId, transitionInfo); + notifyTransition(wex, oldCtx.transactionId, transitionInfo); } } return { proposalId: oldProposal.proposalId, - transactionId: ctx.transactionId, + transactionId: oldCtx.transactionId, }; }