taler-typescript-core

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

commit 100c07c84536394fd234136cdb297f91a7a927fa
parent eaccc56c2251a0e97242f5ee5c7850d98ad736b5
Author: Florian Dold <dold@taler.net>
Date:   Thu,  3 Sep 2026 15:59:03 +0200

wallet-core: drop a refused refund handoff in a melt conflict

A refund the exchange refuses for good was retried on every melt
conflict, so the history-based recovery behind it was never reached
and the refresh stayed pending.

Diffstat:
Mpackages/taler-wallet-core/src/refresh.ts | 26+++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts @@ -1183,13 +1183,33 @@ async function handleRefreshMeltConflict( delete rg.refundRequests[coinIndex]; await tx.upsertRefreshGroup(rg); }); - } else { - // FIXME: Store the error somewhere in the DB? + return; + } + if ( + refundResp.case === HttpStatusCode.RequestTimeout || + refundResp.case === HttpStatusCode.TooManyRequests || + refundResp.case >= HttpStatusCode.InternalServerError + ) { logger.warn( `Refund request during refresh failed: ${j2s(refundResp.detail)}`, ); + return; } - return; + // The exchange refused the refund for good (it never saw the deposit, + // or already paid it out), so the refund cannot explain the conflict + // and would fail the same way on every retry. Drop the handoff and + // let the coin history decide what is left to melt. + logger.warn( + `Refund handoff during refresh refused: ${j2s(refundResp.detail)}`, + ); + await ctx.wex.runWalletDbTx(async (tx) => { + const rg = await tx.getRefreshGroup(refreshGroup.refreshGroupId); + if (!rg || rg.operationStatus != RefreshOperationStatus.Pending) { + return; + } + delete rg.refundRequests[coinIndex]; + await tx.upsertRefreshGroup(rg); + }); } const historySig = await ctx.wex.cryptoApi.signCoinHistoryRequest({