/* 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 */ /** * Imports. */ import { AmountJson, Amounts } from "../amounts.js"; 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 { FailCasesByMethod, ResultByMethod, opEmptySuccess, opKnownHttpFailure, opSuccessFromHttp, opUnknownFailure, } from "../operation.js"; import { TalerErrorCode } from "../taler-error-codes.js"; import { codecForTalerErrorDetail } from "../wallet-types.js"; import { AccessToken, TalerBankConversionApi, codecForCashinConversionResponse, codecForCashoutConversionResponse, codecForConversionBankConfig, } from "./types.js"; import { CacheEvictor, makeBearerTokenAuthHeader, nullEvictor, } from "./utils.js"; export type TalerBankConversionResultByMethod< prop extends keyof TalerBankConversionHttpClient, > = ResultByMethod; export type TalerBankConversionErrorsByMethod< prop extends keyof TalerBankConversionHttpClient, > = FailCasesByMethod; export enum TalerBankConversionCacheEviction { UPDATE_RATE, } /** * The API is used by the wallets. */ export class TalerBankConversionHttpClient { public readonly PROTOCOL_VERSION = "0:0:0"; httpLib: HttpRequestLibrary; cacheEvictor: CacheEvictor; constructor( readonly baseUrl: string, httpClient?: HttpRequestLibrary, cacheEvictor?: CacheEvictor, ) { this.httpLib = httpClient ?? createPlatformHttpLib(); this.cacheEvictor = cacheEvictor ?? nullEvictor; } isCompatible(version: string): boolean { const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); return compare?.compatible ?? false; } /** * https://docs.taler.net/core/api-bank-conversion-info.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, codecForConversionBankConfig()); case HttpStatusCode.NotImplemented: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-bank-conversion-info.html#get--cashin-rate * */ async getCashinRate(conversion: { debit?: AmountJson; credit?: AmountJson }) { const url = new URL(`cashin-rate`, this.baseUrl); if (conversion.debit) { url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit)); } if (conversion.credit) { url.searchParams.set( "amount_credit", Amounts.stringify(conversion.credit), ); } const resp = await this.httpLib.fetch(url.href, { method: "GET", }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForCashinConversionResponse()); case HttpStatusCode.BadRequest: { const body = await resp.json(); const details = codecForTalerErrorDetail().decode(body); switch (details.code) { case TalerErrorCode.GENERIC_PARAMETER_MISSING: return opKnownHttpFailure(resp.status, resp); case TalerErrorCode.GENERIC_PARAMETER_MALFORMED: return opKnownHttpFailure(resp.status, resp); case TalerErrorCode.GENERIC_CURRENCY_MISMATCH: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, body); } } case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotImplemented: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-bank-conversion-info.html#get--cashout-rate * */ async getCashoutRate(conversion: { debit?: AmountJson; credit?: AmountJson; }) { const url = new URL(`cashout-rate`, this.baseUrl); if (conversion.debit) { url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit)); } if (conversion.credit) { url.searchParams.set( "amount_credit", Amounts.stringify(conversion.credit), ); } const resp = await this.httpLib.fetch(url.href, { method: "GET", }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForCashoutConversionResponse()); case HttpStatusCode.BadRequest: { const body = await resp.json(); const details = codecForTalerErrorDetail().decode(body); switch (details.code) { case TalerErrorCode.GENERIC_PARAMETER_MISSING: return opKnownHttpFailure(resp.status, resp); case TalerErrorCode.GENERIC_PARAMETER_MALFORMED: return opKnownHttpFailure(resp.status, resp); case TalerErrorCode.GENERIC_CURRENCY_MISMATCH: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, body); } } case HttpStatusCode.Conflict: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotImplemented: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } /** * https://docs.taler.net/core/api-bank-conversion-info.html#post--conversion-rate * */ async updateConversionRate( auth: AccessToken, body: TalerBankConversionApi.ConversionRate, ) { const url = new URL(`conversion-rate`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", headers: { Authorization: makeBearerTokenAuthHeader(auth), }, body, }); switch (resp.status) { case HttpStatusCode.NoContent: { this.cacheEvictor.notifySuccess( TalerBankConversionCacheEviction.UPDATE_RATE, ); return opEmptySuccess(resp); } case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotImplemented: return opKnownHttpFailure(resp.status, resp); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } }