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