taler-typescript-core

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

commit f529e0c83f37893b280adc7f830a69340f06d261
parent 5a19d41cc1f18aaff9cea37acfdff14f9a972d1a
Author: Florian Dold <dold@taler.net>
Date:   Thu, 23 Jul 2026 01:01:10 +0200

wallet: retry and cancel deposit and pay-template requests

Diffstat:
Mpackages/taler-wallet-core/src/deposits.ts | 15++++++++++++++-
Mpackages/taler-wallet-core/src/pay-merchant.ts | 43+++++++++++++++++++++++++++++--------------
Mpackages/taler-wallet-core/src/pay-template.ts | 46++++++++++++++++++++++++++++++++++------------
3 files changed, 77 insertions(+), 27 deletions(-)

diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -126,6 +126,7 @@ import { WalletDbTransaction } from "./dbtx.js"; import { ReadyExchangeSummary, fetchFreshExchange, + fetchFreshExchangeWithRetryNow, getExchangeDetailsInTx, getExchangeWireFee, getScopeForAllExchanges, @@ -2108,6 +2109,18 @@ export async function createDepositGroup( wex: WalletExecutionContext, req: CreateDepositGroupRequest, ): Promise<CreateDepositGroupResponse> { + return await runWithMaybeProgressContext( + wex, + "createDepositGroup", + req.progressToken, + () => internalCreateDepositGroup(wex, req), + ); +} + +async function internalCreateDepositGroup( + wex: WalletExecutionContext, + req: CreateDepositGroupRequest, +): Promise<CreateDepositGroupResponse> { const depositPayto = Result.orUndefined( Paytos.fromString(req.depositPaytoUri), ); @@ -2173,7 +2186,7 @@ export async function createDepositGroup( const exchanges: ReadyExchangeSummary[] = []; for (const exchangeBaseUrl of usedExchangesSet) { - exchanges.push(await fetchFreshExchange(wex, exchangeBaseUrl)); + exchanges.push(await fetchFreshExchangeWithRetryNow(wex, exchangeBaseUrl)); } if (checkDepositHardLimitExceeded(exchanges, req.amount)) { diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -169,6 +169,7 @@ import { getScopeForAllExchanges, } from "./exchanges.js"; import { instantiateTemplate, instantiateTemplateRaw } from "./pay-template.js"; +import { runWithMaybeProgressContext } from "./progress.js"; import { calculateRefreshOutput, createRefreshGroup, @@ -2303,28 +2304,42 @@ export async function preparePayForTemplate( wex: WalletExecutionContext, req: PreparePayTemplateRequest, ): Promise<PreparePayResult> { - const payUri = await instantiateTemplate(wex, req); - return await preparePayForUri(wex, payUri); + return runWithMaybeProgressContext( + wex, + "preparePayForTemplate", + req.progressToken, + async () => { + const payUri = await instantiateTemplate(wex, req); + return await preparePayForUri(wex, payUri); + }, + ); } export async function preparePayForTemplateV2( wex: WalletExecutionContext, req: PreparePayTemplateRequest, ): Promise<PreparePayV2Result> { - const res = await instantiateTemplateRaw(wex, req); - const proposalRes = await createOrReusePurchase( + return runWithMaybeProgressContext( wex, - res.merchantBaseUrl, - res.orderId, - res.sessionId, - res.claimToken, - undefined, - req.talerPayTemplateUri, - ); + "preparePayForTemplateV2", + req.progressToken, + async () => { + const res = await instantiateTemplateRaw(wex, req); + const proposalRes = await createOrReusePurchase( + wex, + res.merchantBaseUrl, + res.orderId, + res.sessionId, + res.claimToken, + undefined, + req.talerPayTemplateUri, + ); - return { - transactionId: proposalRes.transactionId, - }; + return { + transactionId: proposalRes.transactionId, + }; + }, + ); } /** diff --git a/packages/taler-wallet-core/src/pay-template.ts b/packages/taler-wallet-core/src/pay-template.ts @@ -40,6 +40,10 @@ import { } from "@gnu-taler/taler-util"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { cancelableFetch } from "./common.js"; +import { + runWithMaybeProgressContext, + runWithProgressRetries, +} from "./progress.js"; import { WalletExecutionContext } from "./wallet.js"; const logger = new Logger("pay-template.ts"); @@ -164,6 +168,18 @@ export async function checkPayForTemplate( wex: WalletExecutionContext, req: CheckPayTemplateRequest, ): Promise<CheckPayTemplateReponse> { + return runWithMaybeProgressContext( + wex, + "checkPayForTemplate", + req.progressToken, + () => internalCheckPayForTemplate(wex, req), + ); +} + +async function internalCheckPayForTemplate( + wex: WalletExecutionContext, + req: CheckPayTemplateRequest, +): Promise<CheckPayTemplateReponse> { const parsedUri = Result.orUndefined( TalerUris.parseRestricted( req.talerPayTemplateUri, @@ -181,11 +197,14 @@ export async function checkPayForTemplate( wex.cancellationToken, ); - const templateDetails = succeedOrThrow( - await merchantApi.useTemplateGetInfo(parsedUri.templateId), + // Retried under a progress context, like the other merchant queries here. + const templateDetails = await runWithProgressRetries(wex, async () => + succeedOrThrow(await merchantApi.useTemplateGetInfo(parsedUri.templateId)), ); - const cfg = succeedOrThrow(await merchantApi.getConfig()); + const cfg = await runWithProgressRetries(wex, async () => + succeedOrThrow(await merchantApi.getConfig()), + ); const currSet = new Set(Object.keys(cfg.currencies)); currSet.add(cfg.currency); @@ -235,8 +254,9 @@ export async function instantiateTemplateRaw( ); logger.trace(`parsed URI: ${j2s(parsedUri)}`); - const templateInfo = succeedOrThrow( - await merchantApi.useTemplateGetInfo(parsedUri.templateId), + // Retried under a progress context. + const templateInfo = await runWithProgressRetries(wex, async () => + succeedOrThrow(await merchantApi.useTemplateGetInfo(parsedUri.templateId)), ); let templateDetails: UsingTemplateDetailsRequest; @@ -283,14 +303,16 @@ export async function instantiateTemplateRaw( `templates/${parsedUri.templateId}`, parsedUri.merchantBaseUrl, ); - const httpReq = await cancelableFetch(wex, reqUrl, { - method: "POST", - body: templateDetails, + // Retried under a progress context. Instantiating a template creates a + // fresh order server-side, so a retry after a failure just starts another + // one; the order the wallet ends up paying is the one this call returns. + const resp = await runWithProgressRetries(wex, async () => { + const httpReq = await cancelableFetch(wex, reqUrl, { + method: "POST", + body: templateDetails, + }); + return readSuccessResponseJsonOrThrow(httpReq, codecForPostOrderResponse()); }); - const resp = await readSuccessResponseJsonOrThrow( - httpReq, - codecForPostOrderResponse(), - ); return { merchantBaseUrl: parsedUri.merchantBaseUrl,