commit 8e57ab32d8bff66fdf7575abcdb1161832c54b2f
parent 42a21c376bca1cabeeb7047d6b1c52432d7212d5
Author: Florian Dold <dold@taler.net>
Date: Fri, 31 Jul 2026 15:47:40 +0200
wallet: find a purchase whose merchant base URL differs only in instance case
A downloaded proposal is filed under the base URL from the contract terms, so
re-scanning a taler:// URI that spells the instance differently claimed the
order again instead of returning the existing transaction.
Diffstat:
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -33,6 +33,7 @@ import {
AmountString,
assertUnreachable,
BlindedDonationReceiptKeyPair,
+ canonicalizeMerchantInstanceUrl,
checkDbInvariant,
checkLogicInvariant,
ChoiceSelectionDetail,
@@ -1496,8 +1497,23 @@ async function createOrReusePurchase(
}> {
// Find existing proposals from the same merchant
// with the same order ID.
+ //
+ // Once a proposal has been downloaded its merchantBaseUrl is the one from
+ // the contract terms, which always spells the instance in lower case. A
+ // taler:// URI may spell it differently -- the merchant accepts any casing
+ // -- so look the record up under the canonical spelling as well, or
+ // re-scanning such a URI would claim the order a second time instead of
+ // returning the transaction that already exists.
+ const canonicalBaseUrl = canonicalizeMerchantInstanceUrl(merchantBaseUrl);
const oldProposals = await wex.runWalletDbTx(async (tx) => {
- return tx.getPurchasesByUrlAndOrderId(merchantBaseUrl, orderId);
+ const found = await tx.getPurchasesByUrlAndOrderId(
+ merchantBaseUrl,
+ orderId,
+ );
+ if (found.length > 0 || canonicalBaseUrl === merchantBaseUrl) {
+ return found;
+ }
+ return tx.getPurchasesByUrlAndOrderId(canonicalBaseUrl, orderId);
});
if (oldProposals.length > 1) {