/* This file is part of GNU Taler (C) 2022-2024 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import { HttpRequestLibrary, readTalerErrorResponse } from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; import { LibtoolVersion } from "../libtool-version.js"; import { Logger } from "../logging.js"; import { FailCasesByMethod, ResultByMethod, opEmptySuccess, opKnownHttpFailure, opKnownTalerFailure, opSuccessFromHttp, opUnknownFailure, } from "../operation.js"; import { TalerErrorCode } from "../taler-error-codes.js"; import { BankWithdrawalOperationPostRequest, WithdrawalOperationStatus, 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"; export type TalerBankIntegrationResultByMethod< prop extends keyof TalerBankIntegrationHttpClient, > = ResultByMethod; export type TalerBankIntegrationErrorsByMethod< prop extends keyof TalerBankIntegrationHttpClient, > = FailCasesByMethod; const logger = new Logger("bank-integration.ts"); /** * The API is used by the wallets. */ export class TalerBankIntegrationHttpClient { public static readonly PROTOCOL_VERSION = "2:0:1"; public readonly PROTOCOL_VERSION = TalerBankIntegrationHttpClient.PROTOCOL_VERSION; httpLib: HttpRequestLibrary; constructor( readonly baseUrl: string, httpClient?: HttpRequestLibrary, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); } isCompatible(version: string): boolean { const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); return compare?.compatible ?? false; } /** * https://docs.taler.net/core/api-bank-integration.html#get--config * */ async getConfig() { const url = new URL(`config`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "GET", }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForIntegrationBankConfig()); default: logger.warn(`config request failed, status ${resp.status}`); return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-bank-integration.html#get--withdrawal-operation-$WITHDRAWAL_ID * */ async getWithdrawalOperationById( woid: string, params?: { old_state?: WithdrawalOperationStatus; } & LongPollParams, ) { const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl); addLongPollingParam(url, params); if (params) { url.searchParams.set( "old_state", !params.old_state ? "pending" : params.old_state, ); } const resp = await this.httpLib.fetch(url.href, { method: "GET", }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForBankWithdrawalOperationStatus()); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-bank-integration.html#post-$BANK_API_BASE_URL-withdrawal-operation-$wopid * */ async completeWithdrawalOperationById( woid: string, body: BankWithdrawalOperationPostRequest, ) { const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", body, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp( resp, codecForBankWithdrawalOperationPostResponse(), ); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: { const body = await readTalerErrorResponse(resp); const details = codecForTalerErrorDetail().decode(body); switch (details.code) { case TalerErrorCode.BANK_WITHDRAWAL_OPERATION_RESERVE_SELECTION_CONFLICT: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_DUPLICATE_RESERVE_PUB_SUBJECT: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_UNKNOWN_ACCOUNT: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_AMOUNT_DIFFERS: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_AMOUNT_REQUIRED: return opKnownTalerFailure(details.code, details); default: return opUnknownFailure(resp, details); } } default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-bank-integration.html#post-$BANK_API_BASE_URL-withdrawal-operation-$wopid * */ async abortWithdrawalOperationById(woid: string) { const url = new URL(`withdrawal-operation/${woid}/abort`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", }); switch (resp.status) { case HttpStatusCode.NoContent: return opEmptySuccess(resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } }