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:
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,
};
}