commit c043647f7a83fe1d2e05107924b3fea3a7ff7838
parent 404af52f6ca6936335ab84ae66b31a59b47df607
Author: Florian Dold <florian@dold.me>
Date: Wed, 15 Jul 2026 15:20:44 +0200
wallet-core: actually exclude broken coin in reselection for payments
Diffstat:
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -1611,6 +1611,7 @@ function setCoinSel(rec: PurchaseRecord, coinSel: PayCoinSelection): void {
async function reselectCoinsTx(
tx: WalletIndexedDbTransaction,
ctx: PayMerchantTransactionContext,
+ excludeCoinPub?: string,
): Promise<void> {
const p = await tx.purchases.get(ctx.proposalId);
if (!p) {
@@ -1643,6 +1644,12 @@ async function reselectCoinsTx(
if (payCoinSelection) {
for (let i = 0; i < payCoinSelection.coinPubs.length; i++) {
const coinPub = payCoinSelection.coinPubs[i];
+ if (excludeCoinPub != null && coinPub === excludeCoinPub) {
+ // Exclude the coin that the exchange reported as broken
+ // (e.g. double-spent) so re-selection doesn't just pick the
+ // same failing coin again.
+ continue;
+ }
const contrib = payCoinSelection.coinContributions[i];
prevPayCoins.push({
coinPub,
@@ -1755,6 +1762,8 @@ async function handleInsufficientFunds(
logger.trace(`got error details: ${j2s(err)}`);
+ let brokenCoinPub: string | undefined;
+
if (
err.code === TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS
) {
@@ -1774,8 +1783,8 @@ async function handleInsufficientFunds(
return TaskRunResult.progress();
}
- const brokenCoinPub = (exchangeReply as any).coin_pub;
- logger.trace(`excluded broken coin pub=${brokenCoinPub}`);
+ logger.trace(`excluding broken coin pub=${brokenCoinPub}`);
+ brokenCoinPub = (exchangeReply as any).coin_pub;
if (!brokenCoinPub) {
await ctx.failTransaction(proposal.purchaseStatus, {
@@ -1809,7 +1818,7 @@ async function handleInsufficientFunds(
// TODO: also do token re-selection.
await wex.runLegacyWalletDbTx(async (tx) => {
- await reselectCoinsTx(tx, ctx);
+ await reselectCoinsTx(tx, ctx, brokenCoinPub);
});
wex.ws.notify({