commit 47fcbd02c29e18f607f055e9ca24c7394e5f333b
parent 86fb1a197bc113b312dba98bd2f1ee37db0b2cd0
Author: Florian Dold <florian@dold.me>
Date: Thu, 9 Jul 2026 20:15:22 +0200
wallet-core: use client lib for long-polling bank integration API
Diffstat:
3 files changed, 66 insertions(+), 24 deletions(-)
diff --git a/packages/taler-merchant-webui/src/hooks/testing.tsx b/packages/taler-merchant-webui/src/hooks/testing.tsx
@@ -173,7 +173,7 @@ export class ApiMockEnvironment extends MockEnvironment {
);
const bankIntegration = new TalerBankIntegrationHttpClient(
bankCore.getIntegrationAPI().href,
- mockHttpClient,
+ { httpClient: mockHttpClient },
);
const bankRevenue = new TalerRevenueHttpClient(
bankCore.getRevenueAPI("a").href,
diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts
@@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { CancellationToken } from "../CancellationToken.js";
import { HttpRequestLibrary, readTalerErrorResponse } from "../http-common.js";
import { HttpStatusCode } from "../http-status-codes.js";
import { createPlatformHttpLib } from "../http.js";
@@ -21,6 +22,8 @@ import { LibtoolVersion } from "../libtool-version.js";
import { Logger } from "../logging.js";
import {
FailCasesByMethod,
+ OperationFail,
+ OperationOk,
ResultByMethod,
carefullyParseConfig,
opEmptySuccess,
@@ -30,13 +33,15 @@ import {
opUnknownHttpFailure,
} from "../operation.js";
import { TalerErrorCode } from "../taler-error-codes.js";
+import { Duration } from "../time.js";
import {
BankWithdrawalOperationPostRequest,
+ BankWithdrawalOperationPostResponse,
+ BankWithdrawalOperationStatus,
WithdrawalOperationStatusFlag,
codecForBankWithdrawalOperationPostResponse,
codecForBankWithdrawalOperationStatus,
} from "../types-taler-bank-integration.js";
-import { LongPollParams } from "../types-taler-common.js";
import { codecForIntegrationBankConfig } from "../types-taler-corebank.js";
import { codecForTalerErrorDetail } from "../types-taler-wallet.js";
import { addLongPollingParam } from "./utils.js";
@@ -57,12 +62,20 @@ export class TalerBankIntegrationHttpClient {
public static readonly PROTOCOL_VERSION = "5:0:0";
httpLib: HttpRequestLibrary;
+ cancellationToken?: CancellationToken;
+ timeout?: Duration;
constructor(
readonly baseUrl: string,
- httpClient?: HttpRequestLibrary,
+ args: {
+ httpClient?: HttpRequestLibrary;
+ cancellationToken?: CancellationToken;
+ timeout?: Duration;
+ } = {},
) {
- this.httpLib = httpClient ?? createPlatformHttpLib();
+ this.httpLib = args.httpClient ?? createPlatformHttpLib();
+ this.cancellationToken = args.cancellationToken;
+ this.timeout = args.timeout;
}
static isCompatible(version: string): boolean {
@@ -78,6 +91,8 @@ export class TalerBankIntegrationHttpClient {
const url = new URL(`config`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
+ cancellationToken: this.cancellationToken,
+ timeout: this.timeout,
});
switch (resp.status) {
case HttpStatusCode.Ok:
@@ -100,8 +115,12 @@ export class TalerBankIntegrationHttpClient {
woid: string,
params?: {
old_state?: WithdrawalOperationStatusFlag;
- } & LongPollParams,
- ) {
+ timeoutMs?: number;
+ },
+ ): Promise<
+ | OperationOk<BankWithdrawalOperationStatus>
+ | OperationFail<HttpStatusCode.NotFound>
+ > {
const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl);
addLongPollingParam(url, params);
if (params) {
@@ -112,6 +131,8 @@ export class TalerBankIntegrationHttpClient {
}
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
+ cancellationToken: this.cancellationToken,
+ timeout: this.timeout,
});
switch (resp.status) {
case HttpStatusCode.Ok:
@@ -131,10 +152,22 @@ export class TalerBankIntegrationHttpClient {
async completeWithdrawalOperationById(
woid: string,
body: BankWithdrawalOperationPostRequest,
- ) {
+ ): Promise<
+ | OperationFail<HttpStatusCode.NotFound>
+ | OperationOk<BankWithdrawalOperationPostResponse>
+ | OperationFail<TalerErrorCode.BANK_UPDATE_ABORT_CONFLICT>
+ | OperationFail<TalerErrorCode.BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT>
+ | OperationFail<TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT>
+ | OperationFail<TalerErrorCode.BANK_UNKNOWN_ACCOUNT>
+ | OperationFail<TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE>
+ | OperationFail<TalerErrorCode.BANK_AMOUNT_DIFFERS>
+ | OperationFail<TalerErrorCode.BANK_UNALLOWED_DEBIT>
+ > {
const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
+ cancellationToken: this.cancellationToken,
+ timeout: this.timeout,
body,
});
switch (resp.status) {
@@ -176,10 +209,18 @@ export class TalerBankIntegrationHttpClient {
* https://docs.taler.net/core/api-bank-integration.html#post-$BANK_API_BASE_URL-withdrawal-operation-$wopid
*
*/
- async abortWithdrawalOperationById(woid: string) {
+ async abortWithdrawalOperationById(
+ woid: string,
+ ): Promise<
+ | OperationFail<HttpStatusCode.NotFound>
+ | OperationOk<undefined>
+ | OperationFail<HttpStatusCode.Conflict>
+ > {
const url = new URL(`withdrawal-operation/${woid}/abort`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
+ cancellationToken: this.cancellationToken,
+ timeout: this.timeout,
});
switch (resp.status) {
case HttpStatusCode.NoContent:
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
@@ -1186,7 +1186,9 @@ export async function getBankWithdrawalInfo(
const bankApi = new TalerBankIntegrationHttpClient(
uriResult.bankIntegrationApiBaseUrl,
- http,
+ {
+ httpClient: http,
+ },
);
const { body: config } = await bankApi.getConfig();
@@ -3378,24 +3380,23 @@ async function processReserveBankStatus(
if (!uriResult) {
throw Error(`can't parse withdrawal URL ${bankInfo.talerWithdrawUri}`);
}
- const bankStatusUrl = new URL(
- `withdrawal-operation/${uriResult.withdrawalOperationId}`,
+ const bankClient = new TalerBankIntegrationHttpClient(
uriResult.bankIntegrationApiBaseUrl,
- );
- bankStatusUrl.searchParams.set("long_poll_ms", "30000");
- bankStatusUrl.searchParams.set("old_state", "selected");
-
- logger.info(`long-polling for withdrawal operation at ${bankStatusUrl.href}`);
- const statusResp = await cancelableFetch(wex, bankStatusUrl, {
- timeout: getReserveRequestTimeout(withdrawalGroup),
- });
- logger.info(
- `long-polling for withdrawal operation returned status ${statusResp.status}`,
+ {
+ httpClient: wex.http,
+ cancellationToken: wex.cancellationToken,
+ timeout: getReserveRequestTimeout(withdrawalGroup),
+ },
);
- const status = await readSuccessResponseJsonOrThrow(
- statusResp,
- codecForBankWithdrawalOperationStatus(),
+ const status = succeedOrThrow(
+ await bankClient.getWithdrawalOperationById(
+ uriResult.withdrawalOperationId,
+ {
+ old_state: "selected",
+ timeoutMs: 30000,
+ },
+ ),
);
if (logger.shouldLogTrace()) {