/*
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));
}
}
}