diff options
Diffstat (limited to 'packages/taler-util/src/http-client/bank-conversion.ts')
-rw-r--r-- | packages/taler-util/src/http-client/bank-conversion.ts | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/packages/taler-util/src/http-client/bank-conversion.ts b/packages/taler-util/src/http-client/bank-conversion.ts new file mode 100644 index 000000000..cb14d8b34 --- /dev/null +++ b/packages/taler-util/src/http-client/bank-conversion.ts @@ -0,0 +1,223 @@ +/* + 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 <http://www.gnu.org/licenses/> + */ + +/** + * 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<TalerBankConversionHttpClient, prop>; +export type TalerBankConversionErrorsByMethod< + prop extends keyof TalerBankConversionHttpClient, +> = FailCasesByMethod<TalerBankConversionHttpClient, prop>; + +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<TalerBankConversionCacheEviction>; + + constructor( + readonly baseUrl: string, + httpClient?: HttpRequestLibrary, + cacheEvictor?: CacheEvictor<TalerBankConversionCacheEviction>, + ) { + 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)); + } + } +} |