taler-typescript-core

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

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:
Mpackages/taler-merchant-webui/src/hooks/testing.tsx | 2+-
Mpackages/taler-util/src/http-client/bank-integration.ts | 55++++++++++++++++++++++++++++++++++++++++++++++++-------
Mpackages/taler-wallet-core/src/withdraw.ts | 33+++++++++++++++++----------------
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()) {