wip: http-client

This commit is contained in:
Sebastian 2023-10-13 23:59:26 -03:00
parent 8ae4ad9342
commit 36b7918a79
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
4 changed files with 1289 additions and 1 deletions

View File

@ -0,0 +1,523 @@
/*
This file is part of GNU Taler
(C) 2022 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/>
*/
import {
AmountJson,
Amounts,
Logger
} from "@gnu-taler/taler-util";
import {
createPlatformHttpLib,
expectSuccessResponseOrThrow,
HttpRequestLibrary,
readSuccessResponseJsonOrThrow
} from "@gnu-taler/taler-util/http";
import { AccessToken, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashouts, codecForCashoutStatusResponse, codecForConversionRatesResponse, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse, codecForTokenSuccessResponse, TalerAuthentication, TalerCorebankApi } from "./types.js";
import { addPaginationParams, makeBasicAuthHeader, makeBearerTokenAuthHeader, PaginationParams, UserAndPassword, UserAndToken } from "./utils.js";
const logger = new Logger("http-client/core-bank.ts");
export class TalerCoreBankHttpClient {
httpLib: HttpRequestLibrary;
constructor(
private baseUrl: string,
httpClient?: HttpRequestLibrary,
) {
this.httpLib = httpClient ?? createPlatformHttpLib();
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token
*
* @returns
*/
async createAccessToken(
auth: UserAndPassword,
body: TalerAuthentication.TokenRequest,
): Promise<TalerAuthentication.TokenSuccessResponse> {
const url = new URL(`accounts/${auth.username}/token`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
headers: {
Authorization: makeBasicAuthHeader(auth.username, auth.password),
},
body
});
return readSuccessResponseJsonOrThrow(resp, codecForTokenSuccessResponse());
}
async deleteAccessToken(
auth: UserAndToken,
): Promise<void> {
const url = new URL(`accounts/${auth.username}/token`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token),
}
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
*
*/
async getConfig(): Promise<TalerCorebankApi.Config> {
const url = new URL(`config`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET"
});
return readSuccessResponseJsonOrThrow(resp, codecForCoreBankConfig());
}
//
// ACCOUNTS
//
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts
*
*/
async createAccount(auth: AccessToken, body: TalerCorebankApi.RegisterAccountRequest): Promise<void> {
const url = new URL(`accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
headers: {
Authorization: makeBearerTokenAuthHeader(auth)
},
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/api-corebank.html#delete--accounts-$USERNAME
*
*/
async deleteAccount(auth: UserAndToken): Promise<void> {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "DELETE",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME
*
*/
async updateAccount(auth: UserAndToken, body: TalerCorebankApi.AccountReconfiguration): Promise<void> {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME-auth
*
*/
async updatePassword(auth: UserAndToken, body: TalerCorebankApi.AccountPasswordChange): Promise<void> {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "PATCH",
body,
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/get-$BANK_API_BASE_URL-public-accounts
*
*/
async getPublicAccounts(): Promise<TalerCorebankApi.PublicAccountsResponse> {
const url = new URL(`public-accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
},
});
return readSuccessResponseJsonOrThrow(resp, codecForPublicAccountsResponse());
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts
*
*/
async getAccounts(auth: AccessToken): Promise<TalerCorebankApi.ListBankAccountsResponse> {
const url = new URL(`accounts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
Authorization: makeBearerTokenAuthHeader(auth)
},
});
return readSuccessResponseJsonOrThrow(resp, codecForListBankAccountsResponse());
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
*
*/
async getAccount(auth: UserAndToken): Promise<TalerCorebankApi.AccountData> {
const url = new URL(`accounts/${auth.username}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return readSuccessResponseJsonOrThrow(resp, codecForAccountData());
}
//
// TRANSACTIONS
//
/**
* https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions
*
*/
async getTransactions(auth: UserAndToken, pagination?: PaginationParams): Promise<TalerCorebankApi.BankAccountTransactionsResponse> {
const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
addPaginationParams(url, pagination)
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionsResponse());
}
/**
* https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions-$transaction_id
*
*/
async getTransactionById(auth: UserAndToken, txid: number): Promise<TalerCorebankApi.BankAccountTransactionInfo> {
const url = new URL(`accounts/${auth.username}/transactions/${String(txid)}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionInfo());
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-transactions
*
*/
async createTransaction(auth: UserAndToken, body: TalerCorebankApi.CreateBankAccountTransactionCreate): Promise<void> {
const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
body,
});
return expectSuccessResponseOrThrow(resp);
}
//
// WITHDRAWALS
//
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
*
*/
async createWithdrawal(auth: UserAndToken, body: TalerCorebankApi.BankAccountCreateWithdrawalRequest): Promise<TalerCorebankApi.BankAccountCreateWithdrawalResponse> {
const url = new URL(`accounts/${auth.username}/withdrawals`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
body,
});
return readSuccessResponseJsonOrThrow(resp, codecForBankAccountCreateWithdrawalResponse());
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
*
*/
async getWithdrawalById(wid: string): Promise<TalerCorebankApi.BankAccountGetWithdrawalResponse> {
const url = new URL(`withdrawals/${wid}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
return readSuccessResponseJsonOrThrow(resp, codecForBankAccountGetWithdrawalResponse());
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort
*
*/
async abortWithdrawalById(wid: string): Promise<void> {
const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm
*
*/
async confirmWithdrawalById(wid: string): Promise<void> {
const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
});
return expectSuccessResponseOrThrow(resp);
}
//
// CASHOUTS
//
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts
*
*/
async createCashout(auth: UserAndToken, body: TalerCorebankApi.CashoutRequest): Promise<TalerCorebankApi.CashoutPending> {
const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
body,
});
return readSuccessResponseJsonOrThrow(resp, codecForCashoutPending());
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-abort
*
*/
async abortCashoutById(auth: UserAndToken, cid: string): Promise<void> {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}/abort`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
*
*/
async confirmCashoutById(auth: UserAndToken, cid: string, body: TalerCorebankApi.CashoutConfirmRequest): Promise<void> {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}/confirm`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
body,
});
return expectSuccessResponseOrThrow(resp);
}
/**
* https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
*
*/
async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson }): Promise<TalerCorebankApi.CashoutConversionResponse> {
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_debit", Amounts.stringify(conversion.credit))
}
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
return readSuccessResponseJsonOrThrow(resp, codecForCashoutConversionResponse());
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
*
*/
async getAccountCashouts(auth: UserAndToken): Promise<TalerCorebankApi.Cashouts> {
const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return readSuccessResponseJsonOrThrow(resp, codecForCashouts());
}
/**
* https://docs.taler.net/core/api-corebank.html#get--cashouts
*
*/
async getGlobalCashouts(auth: AccessToken): Promise<TalerCorebankApi.GlobalCashouts> {
const url = new URL(`cashouts`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
Authorization: makeBearerTokenAuthHeader(auth)
},
});
return readSuccessResponseJsonOrThrow(resp, codecForGlobalCashouts());
}
/**
* https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
*
*/
async getCashoutById(auth: UserAndToken, cid: string): Promise<TalerCorebankApi.CashoutStatusResponse> {
const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
headers: {
Authorization: makeBearerTokenAuthHeader(auth.token)
},
});
return readSuccessResponseJsonOrThrow(resp, codecForCashoutStatusResponse());
}
//
// CONVERSION RATE
//
/**
* https://docs.taler.net/core/api-corebank.html#get--conversion-rates
*
*/
async getConversionRates(): Promise<TalerCorebankApi.ConversionRatesResponse> {
const url = new URL(`conversion-rates`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
return readSuccessResponseJsonOrThrow(resp, codecForConversionRatesResponse());
}
//
// MONITOR
//
/**
* https://docs.taler.net/core/api-corebank.html#get--monitor
*
*/
async getMonitor(params: { timeframe: TalerCorebankApi.MonitorTimeframeParam, which: number }): Promise<TalerCorebankApi.MonitorResponse> {
const url = new URL(`monitor`, this.baseUrl);
url.searchParams.set("timeframe", params.timeframe.toString())
url.searchParams.set("which", String(params.which))
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
});
return readSuccessResponseJsonOrThrow(resp, codecForMonitorResponse());
}
//
// Others API
//
/**
* https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
*
*/
getIntegrationAPI(): TalerBankIntegrationHttpClient {
const url = new URL(`taler-integration`, this.baseUrl);
return new TalerBankIntegrationHttpClient(url.href, this.httpLib)
}
/**
* https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
*
*/
getWireGatewayAPI(username: string): TalerWireGatewayHttpClient {
const url = new URL(`accounts/${username}/taler-wire-gateway`, this.baseUrl);
return new TalerWireGatewayHttpClient(url.href, username, this.httpLib)
}
/**
* https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
*
*/
getRevenueAPI(username: string): TalerRevenueHttpClient {
const url = new URL(`accounts/${username}/taler-revenue`, this.baseUrl);
return new TalerRevenueHttpClient(url.href, username, this.httpLib,)
}
}
export class TalerBankIntegrationHttpClient {
httpLib: HttpRequestLibrary;
constructor(
private baseUrl: string,
httpClient?: HttpRequestLibrary,
) {
this.httpLib = httpClient ?? createPlatformHttpLib();
}
}
export class TalerWireGatewayHttpClient {
httpLib: HttpRequestLibrary;
constructor(
private baseUrl: string,
private username: string,
httpClient?: HttpRequestLibrary,
) {
this.httpLib = httpClient ?? createPlatformHttpLib();
}
}
export class TalerRevenueHttpClient {
httpLib: HttpRequestLibrary;
constructor(
private baseUrl: string,
private username: string,
httpClient?: HttpRequestLibrary,
) {
this.httpLib = httpClient ?? createPlatformHttpLib();
}
}

View File

@ -0,0 +1,703 @@
import { codecForAmountJson, codecForAmountString } from "../amounts.js";
import { TalerCorebankApiClient } from "../bank-api-client.js";
import { Codec, buildCodecForObject, codecForAny, codecForBoolean, codecForConstString, codecForEither, codecForList, codecForMap, codecForNumber, codecForString, codecOptional } from "../codec.js";
import { codecForTimestamp } from "../time.js";
type HashCode = string;
type EddsaPublicKey = string;
type EddsaSignature = string;
type WireTransferIdentifierRawP = string;
type RelativeTime = {
d_us: number | "forever"
};
type ImageDataUrl = string;
interface WithId {
id: string;
}
interface Timestamp {
// Milliseconds since epoch, or the special
// value "forever" to represent an event that will
// never happen.
t_s: number | "never";
}
interface Duration {
d_us: number | "forever";
}
interface WithId {
id: string;
}
type UUID = string;
type Integer = number;
type Amount = string;
export interface LoginToken {
token: AccessToken,
expiration: Timestamp,
}
// token used to get loginToken
// must forget after used
declare const __ac_token: unique symbol;
export type AccessToken = string & {
[__ac_token]: true;
};
export namespace TalerAuthentication {
export interface TokenRequest {
// Service-defined scope for the token.
// Typical scopes would be "readonly" or "readwrite".
scope: string;
// Server may impose its own upper bound
// on the token validity duration
duration?: RelativeTime;
// Is the token refreshable into a new token during its
// validity?
// Refreshable tokens effectively provide indefinite
// access if they are refreshed in time.
refreshable?: boolean;
}
export interface TokenSuccessResponse {
// Expiration determined by the server.
// Can be based on the token_duration
// from the request, but ultimately the
// server decides the expiration.
expiration: Timestamp;
// Opque access token.
access_token: AccessToken;
}
}
interface CurrencySpecification {
// Name of the currency.
name: string;
// Decimal separator for fractional digits.
decimal_separator: string;
// how many digits the user may enter after the decimal_separator
num_fractional_input_digits: Integer;
// Number of fractional digits to render in normal font and size.
num_fractional_normal_digits: Integer;
// Number of fractional digits to render always, if needed by
// padding with zeros.
num_fractional_trailing_zero_digits: Integer;
// Whether the currency name should be rendered before (true) or
// after (false) the numeric value
is_currency_name_leading: boolean;
// map of powers of 10 to alternative currency names / symbols, must
// always have an entry under "0" that defines the base name,
// e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC".
// Communicates the currency symbol to be used.
alt_unit_names: { [log10: string]: string };
}
export const codecForAccessToken = codecForString as () => Codec<AccessToken>;
export const codecForTokenSuccessResponse =
(): Codec<TalerAuthentication.TokenSuccessResponse> =>
buildCodecForObject<TalerAuthentication.TokenSuccessResponse>()
.property("access_token", codecForAccessToken())
.property("expiration", codecForTimestamp)
.build("TalerAuthentication.TokenSuccessResponse")
export const codecForCurrencySpecificiation =
(): Codec<CurrencySpecification> =>
buildCodecForObject<CurrencySpecification>()
.property("name", codecForString())
.property("decimal_separator", codecForString())
.property("num_fractional_input_digits", codecForNumber())
.property("num_fractional_normal_digits", codecForNumber())
.property("num_fractional_trailing_zero_digits", codecForNumber())
.property("is_currency_name_leading", codecForBoolean())
.property("alt_unit_names", codecForMap(codecForString()))
.build("CurrencySpecification")
export const codecForCoreBankConfig =
(): Codec<TalerCorebankApi.Config> =>
buildCodecForObject<TalerCorebankApi.Config>()
.property("name", codecForString())
.property("version", codecForString())
.property("have_cashout", codecOptional(codecForBoolean()))
.property("currency", codecForCurrencySpecificiation())
.property("fiat_currency", codecOptional(codecForCurrencySpecificiation()))
.build("TalerCorebankApi.Config")
const codecForBalance = (): Codec<TalerCorebankApi.Balance> =>
buildCodecForObject<TalerCorebankApi.Balance>()
.property("amount", codecForAmountString())
.property("credit_debit_indicator", codecForEither(codecForConstString("credit"), codecForConstString("debit")))
.build("TalerCorebankApi.Balance")
const codecForPublicAccount = (): Codec<TalerCorebankApi.PublicAccount> =>
buildCodecForObject<TalerCorebankApi.PublicAccount>()
.property("account_name", codecForString())
.property("balance", codecForBalance())
.property("payto_uri", codecForPaytoURI())
.build("TalerCorebankApi.PublicAccount")
export const codecForPublicAccountsResponse =
(): Codec<TalerCorebankApi.PublicAccountsResponse> =>
buildCodecForObject<TalerCorebankApi.PublicAccountsResponse>()
.property("public_accounts", codecForList(codecForPublicAccount()))
.build("TalerCorebankApi.PublicAccountsResponse")
export const codecForAccountMinimalData =
(): Codec<TalerCorebankApi.AccountMinimalData> =>
buildCodecForObject<TalerCorebankApi.AccountMinimalData>()
.property("balance", codecForBalance())
.property("debit_threshold", codecForAmountString())
.property("name", codecForString())
.property("username", codecForString())
.build("TalerCorebankApi.AccountMinimalData")
export const codecForListBankAccountsResponse =
(): Codec<TalerCorebankApi.ListBankAccountsResponse> =>
buildCodecForObject<TalerCorebankApi.ListBankAccountsResponse>()
.property("accounts", codecForList(codecForAccountMinimalData()))
.build("TalerCorebankApi.ListBankAccountsResponse")
export const codecForAccountData =
(): Codec<TalerCorebankApi.AccountData> =>
buildCodecForObject<TalerCorebankApi.AccountData>()
.property("name", codecForString())
.property("balance", codecForBalance())
.property("payto_uri", codecForPaytoURI())
.property("debit_threshold", codecForAmountString())
.property("contact_data", codecOptional(codecForChallengeContactData()))
.property("cashout_payto_uri", codecOptional(codecForPaytoURI()))
.build("TalerCorebankApi.AccountData")
export const codecForChallengeContactData =
(): Codec<TalerCorebankApi.ChallengeContactData> =>
buildCodecForObject<TalerCorebankApi.ChallengeContactData>()
.property("email", codecOptional(codecForString()))
.property("phone", codecOptional(codecForString()))
.build("TalerCorebankApi.ChallengeContactData")
export const codecForBankAccountTransactionsResponse =
(): Codec<TalerCorebankApi.BankAccountTransactionsResponse> =>
buildCodecForObject<TalerCorebankApi.BankAccountTransactionsResponse>()
.property("transactions", codecForList(codecForBankAccountTransactionInfo()))
.build("TalerCorebankApi.BankAccountTransactionsResponse");
export const codecForBankAccountTransactionInfo =
(): Codec<TalerCorebankApi.BankAccountTransactionInfo> =>
buildCodecForObject<TalerCorebankApi.BankAccountTransactionInfo>()
.property("amount", codecForAmountString())
.property("creditor_payto_uri", codecForPaytoURI())
.property("date", codecForTimestamp)
.property("debtor_payto_uri", codecForPaytoURI())
.property("direction", codecForEither(codecForConstString("debit"), codecForConstString("credit")))
.property("row_id", codecForNumber())
.property("subject", codecForString())
.build("TalerCorebankApi.BankAccountTransactionInfo");
export const codecForBankAccountCreateWithdrawalResponse =
(): Codec<TalerCorebankApi.BankAccountCreateWithdrawalResponse> =>
buildCodecForObject<TalerCorebankApi.BankAccountCreateWithdrawalResponse>()
.property("taler_withdraw_uri", codecForTalerWithdrawalURI())
.property("withdrawal_id", codecForString())
.build("TalerCorebankApi.BankAccountCreateWithdrawalResponse");
export const codecForBankAccountGetWithdrawalResponse =
(): Codec<TalerCorebankApi.BankAccountGetWithdrawalResponse> =>
buildCodecForObject<TalerCorebankApi.BankAccountGetWithdrawalResponse>()
.property("aborted", codecForBoolean())
.property("amount", codecForAmountString())
.property("confirmation_done", codecForBoolean())
.property("selected_exchange_account", codecOptional(codecForString()))
.property("selected_reserve_pub", codecOptional(codecForString()))
.property("selection_done", (codecForBoolean()))
.build("TalerCorebankApi.BankAccountGetWithdrawalResponse");
export const codecForCashoutPending =
(): Codec<TalerCorebankApi.CashoutPending> =>
buildCodecForObject<TalerCorebankApi.CashoutPending>()
.property("cashout_id", codecForString())
.build("TalerCorebankApi.CashoutPending");
export const codecForCashoutConversionResponse =
(): Codec<TalerCorebankApi.CashoutConversionResponse> =>
buildCodecForObject<TalerCorebankApi.CashoutConversionResponse>()
.property("amount_credit", codecForAmountString())
.property("amount_debit", codecForAmountString())
.build("TalerCorebankApi.CashoutConversionResponse");
export const codecForCashouts =
(): Codec<TalerCorebankApi.Cashouts> =>
buildCodecForObject<TalerCorebankApi.Cashouts>()
.property("cashouts", codecForList(codecForCashoutInfo()))
.build("TalerCorebankApi.Cashouts");
export const codecForCashoutInfo =
(): Codec<TalerCorebankApi.CashoutInfo> =>
buildCodecForObject<TalerCorebankApi.CashoutInfo>()
.property("cashout_id", codecForString())
.property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed"),))
.build("TalerCorebankApi.CashoutInfo");
export const codecForGlobalCashouts =
(): Codec<TalerCorebankApi.GlobalCashouts> =>
buildCodecForObject<TalerCorebankApi.GlobalCashouts>()
.property("cashouts", codecForList(codecForGlobalCashoutInfo()))
.build("TalerCorebankApi.GlobalCashouts");
export const codecForGlobalCashoutInfo =
(): Codec<TalerCorebankApi.GlobalCashoutInfo> =>
buildCodecForObject<TalerCorebankApi.GlobalCashoutInfo>()
.property("cashout_id", codecForString())
.property("username", codecForString())
.property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed"),))
.build("TalerCorebankApi.GlobalCashoutInfo");
export const codecForCashoutStatusResponse =
(): Codec<TalerCorebankApi.CashoutStatusResponse> =>
buildCodecForObject<TalerCorebankApi.CashoutStatusResponse>()
.property("amount_credit", codecForAmountString())
.property("amount_debit", codecForAmountString())
.property("confirmation_time", codecForTimestamp)
.property("creation_time", codecForTimestamp)
.property("credit_payto_uri", codecForPaytoURI())
.property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed")))
.property("subject", codecForString())
.build("TalerCorebankApi.CashoutStatusResponse");
export const codecForConversionRatesResponse =
(): Codec<TalerCorebankApi.ConversionRatesResponse> =>
buildCodecForObject<TalerCorebankApi.ConversionRatesResponse>()
.property("buy_at_ratio", codecForDecimalNumber())
.property("buy_in_fee", codecForDecimalNumber())
.property("sell_at_ratio", codecForDecimalNumber())
.property("sell_out_fee", codecForDecimalNumber())
.build("TalerCorebankApi.ConversionRatesResponse");
export const codecForMonitorResponse =
(): Codec<TalerCorebankApi.MonitorResponse> =>
buildCodecForObject<TalerCorebankApi.MonitorResponse>()
.property("cashinCount", codecForNumber())
.property("cashinExternalVolume", codecForAmountString())
.property("cashoutCount", codecForNumber())
.property("cashoutExternalVolume", codecForAmountString())
.property("talerPayoutCount", codecForNumber())
.property("talerPayoutInternalVolume", codecForAmountString())
.build("TalerCorebankApi.MonitorResponse");
// export const codecFor =
// (): Codec<TalerCorebankApi.PublicAccountsResponse> =>
// buildCodecForObject<TalerCorebankApi.PublicAccountsResponse>()
// .property("", codecForString())
// .build("TalerCorebankApi.PublicAccountsResponse");
type EmailAddress = string;
type PhoneNumber = string;
type DecimalNumber = string;
const codecForPaytoURI = codecForString
const codecForTalerWithdrawalURI = codecForString
const codecForDecimalNumber = codecForString
enum TanChannel {
SMS = "sms",
EMAIL = "email",
FILE = "file"
}
export namespace TalerCorebankApi {
export interface Config {
// Name of this API, always "circuit".
name: string;
// API version in the form $n:$n:$n
version: string;
// If 'true', the server provides local currency
// conversion support.
// If missing or false, some parts of the API
// are not supported and return 404.
have_cashout?: boolean;
// How the bank SPA should render the currency.
currency: CurrencySpecification;
// Fiat currency. That is the currency in which
// cash-out operations ultimately wire money.
// Only applicable if have_cashout=true.
fiat_currency?: CurrencySpecification;
}
export interface BankAccountCreateWithdrawalRequest {
// Amount to withdraw.
amount: Amount;
}
export interface BankAccountCreateWithdrawalResponse {
// ID of the withdrawal, can be used to view/modify the withdrawal operation.
withdrawal_id: string;
// URI that can be passed to the wallet to initiate the withdrawal.
taler_withdraw_uri: string;
}
export interface BankAccountGetWithdrawalResponse {
// Amount that will be withdrawn with this withdrawal operation.
amount: Amount;
// Was the withdrawal aborted?
aborted: boolean;
// Has the withdrawal been confirmed by the bank?
// The wire transfer for a withdrawal is only executed once
// both confirmation_done is true and selection_done is true.
confirmation_done: boolean;
// Did the wallet select reserve details?
selection_done: boolean;
// Reserve public key selected by the exchange,
// only non-null if selection_done is true.
selected_reserve_pub: string | undefined;
// Exchange account selected by the wallet, or by the bank
// (with the default exchange) in case the wallet did not provide one
// through the Integration API.
selected_exchange_account: string | undefined;
}
export interface BankAccountTransactionsResponse {
transactions: BankAccountTransactionInfo[];
}
export interface BankAccountTransactionInfo {
creditor_payto_uri: string;
debtor_payto_uri: string;
amount: Amount;
direction: "debit" | "credit";
subject: string;
// Transaction unique ID. Matches
// $transaction_id from the URI.
row_id: number;
date: Timestamp;
}
export interface CreateBankAccountTransactionCreate {
// Address in the Payto format of the wire transfer receiver.
// It needs at least the 'message' query string parameter.
payto_uri: string;
// Transaction amount (in the $currency:x.y format), optional.
// However, when not given, its value must occupy the 'amount'
// query string parameter of the 'payto' field. In case it
// is given in both places, the paytoUri's takes the precedence.
amount?: string;
}
export interface RegisterAccountRequest {
// Username
username: string;
// Password.
password: string;
// Legal name of the account owner
name: string;
// Defaults to false.
is_public?: boolean;
// Is this a taler exchange account?
// If true:
// - incoming transactions to the account that do not
// have a valid reserve public key are automatically
// - the account provides the taler-wire-gateway-api endpoints
// Defaults to false.
is_taler_exchange?: boolean;
// Addresses where to send the TAN for transactions.
// Currently only used for cashouts.
// If missing, cashouts will fail.
// In the future, might be used for other transactions
// as well.
challenge_contact_data?: ChallengeContactData;
// 'payto' address pointing a bank account
// external to the libeufin-bank.
// Payments will be sent to this bank account
// when the user wants to convert the local currency
// back to fiat currency outside libeufin-bank.
cashout_payto_uri?: string;
// Internal payto URI of this bank account.
// Used mostly for testing.
internal_payto_uri?: string;
}
export interface ChallengeContactData {
// E-Mail address
email?: EmailAddress;
// Phone number.
phone?: PhoneNumber;
}
export interface AccountReconfiguration {
// Addresses where to send the TAN for transactions.
// Currently only used for cashouts.
// If missing, cashouts will fail.
// In the future, might be used for other transactions
// as well.
challenge_contact_data?: ChallengeContactData;
// 'payto' address pointing a bank account
// external to the libeufin-bank.
// Payments will be sent to this bank account
// when the user wants to convert the local currency
// back to fiat currency outside libeufin-bank.
cashout_address?: string;
// Legal name associated with $username.
// When missing, the old name is kept.
name?: string;
// If present, change the is_exchange configuration.
// See RegisterAccountRequest
is_exchange?: boolean;
}
export interface AccountPasswordChange {
// New password.
new_password: string;
}
export interface PublicAccountsResponse {
public_accounts: PublicAccount[];
}
export interface PublicAccount {
payto_uri: string;
balance: Balance;
// The account name (=username) of the
// libeufin-bank account.
account_name: string;
}
export interface ListBankAccountsResponse {
accounts: AccountMinimalData[];
}
export interface Balance {
amount: Amount;
credit_debit_indicator: "credit" | "debit";
}
export interface AccountMinimalData {
// Username
username: string;
// Legal name of the account owner.
name: string;
// current balance of the account
balance: Balance;
// Number indicating the max debit allowed for the requesting user.
debit_threshold: Amount;
}
export interface AccountData {
// Legal name of the account owner.
name: string;
// Available balance on the account.
balance: Balance;
// payto://-URI of the account.
payto_uri: string;
// Number indicating the max debit allowed for the requesting user.
debit_threshold: Amount;
contact_data?: ChallengeContactData;
// 'payto' address pointing the bank account
// where to send cashouts. This field is optional
// because not all the accounts are required to participate
// in the merchants' circuit. One example is the exchange:
// that never cashouts. Registering these accounts can
// be done via the access API.
cashout_payto_uri?: string;
}
export interface CashoutRequest {
// Optional subject to associate to the
// cashout operation. This data will appear
// as the incoming wire transfer subject in
// the user's external bank account.
subject?: string;
// That is the plain amount that the user specified
// to cashout. Its $currency is the (regional) currency of the
// bank instance.
amount_debit: Amount;
// That is the amount that will effectively be
// transferred by the bank to the user's bank
// account, that is external to the regional currency.
// It is expressed in the fiat currency and
// is calculated after the cashout fee and the
// exchange rate. See the /cashout-rates call.
// The client needs to calculate this amount
// correctly based on the amount_debit and the cashout rate,
// otherwise the request will fail.
amount_credit: Amount;
// Which channel the TAN should be sent to. If
// this field is missing, it defaults to SMS.
// The default choice prefers to change the communication
// channel respect to the one used to issue this request.
tan_channel?: TanChannel;
}
export interface CashoutPending {
// ID identifying the operation being created
// and now waiting for the TAN confirmation.
cashout_id: string;
}
export interface CashoutConfirmRequest {
// the TAN that confirms $CASHOUT_ID.
tan: string;
}
export interface CashoutConversionResponse {
// Amount that the user will get deducted from their regional
// bank account, according to the 'amount_credit' value.
amount_debit: Amount;
// Amount that the user will receive in their fiat
// bank account, according to 'amount_debit'.
amount_credit: Amount;
}
export interface Cashouts {
// Every string represents a cash-out operation ID.
cashouts: CashoutInfo[];
}
export interface CashoutInfo {
cashout_id: string;
status: "pending" | "confirmed";
}
export interface GlobalCashouts {
// Every string represents a cash-out operation ID.
cashouts: GlobalCashoutInfo[];
}
export interface GlobalCashoutInfo {
cashout_id: string;
username: string;
status: "pending" | "confirmed";
}
export interface CashoutStatusResponse {
status: "pending" | "confirmed";
// Amount debited to the internal
// regional currency bank account.
amount_debit: Amount;
// Amount credited to the external bank account.
amount_credit: Amount;
// Transaction subject.
subject: string;
// Fiat bank account that will receive the cashed out amount.
// Specified as a payto URI.
credit_payto_uri: string;
// Time when the cashout was created.
creation_time: Timestamp;
// Time when the cashout was confirmed via its TAN.
// Missing when the operation wasn't confirmed yet.
confirmation_time?: Timestamp;
}
export interface ConversionRatesResponse {
// Exchange rate to buy the local currency from the external one
buy_at_ratio: DecimalNumber;
// Exchange rate to sell the local currency for the external one
sell_at_ratio: DecimalNumber;
// Fee to subtract after applying the buy ratio.
buy_in_fee: DecimalNumber;
// Fee to subtract after applying the sell ratio.
sell_out_fee: DecimalNumber;
}
export enum MonitorTimeframeParam{
hour, day, month, year, decade,
}
export interface MonitorResponse {
// This number identifies how many cashin operations
// took place in the timeframe specified in the request.
// This number corresponds to how many withdrawals have
// been initiated by a wallet owner. Note: wallet owners
// are NOT required to be customers of the libeufin-bank.
cashinCount: number;
// This amount accounts how much external currency has been
// spent to withdraw Taler coins in the internal currency.
// The exact amount of internal currency being created can be
// calculated using the advertised conversion rates.
cashinExternalVolume: Amount;
// This number identifies how many cashout operations were
// confirmed in the timeframe speficied in the request.
cashoutCount: number;
// This amount corresponds to how much *external* currency was
// paid by the libeufin-bank administrator to fulfill all the
// confirmed cashouts related to the timeframe specified in the
// request.
cashoutExternalVolume: Amount;
// This number identifies how many payments were made by a
// Taler exchange to a merchant bank account in the internal
// currency, in the timeframe specified in the request.
talerPayoutCount: number;
// This amount accounts the overall *internal* currency that
// has been paid by a Taler exchange to a merchant internal
// bank account, in the timeframe specified in the request.
talerPayoutInternalVolume: Amount;
}
}

View File

@ -0,0 +1,62 @@
import { base64FromArrayBuffer } from "../base64.js";
import { stringToBytes } from "../taler-crypto.js";
import { AccessToken, TalerAuthentication } from "./types.js";
/**
* Helper function to generate the "Authorization" HTTP header.
*/
export function makeBasicAuthHeader(username: string, password: string): string {
const auth = `${username}:${password}`;
const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth));
return `Basic ${authEncoded}`;
}
/**
* rfc8959
* @param token
* @returns
*/
export function makeBearerTokenAuthHeader(token: AccessToken): string {
return `Bearer secret-token:${token}`;
}
/**
* https://bugs.gnunet.org/view.php?id=7949
*/
export function addPaginationParams(url: URL, pagination?: PaginationParams) {
if (!pagination) return;
if (pagination.timoutMs) {
url.searchParams.set("long_poll_ms", String(pagination.timoutMs))
}
if (pagination.offset) {
url.searchParams.set("start", pagination.offset)
}
if (pagination.limit) {
url.searchParams.set("delta", String(pagination.limit))
}
}
export type UserAndPassword = {
username: string,
password: string,
}
export type UserAndToken = {
username: string,
token: AccessToken,
}
export type PaginationParams = {
/**
* row identifier as the starting point of the query
*/
offset?: string,
/**
* max number of element in the result response
*/
limit?: number,
/**
* milliseconds the server should wait for at least one result to be shown
*/
timoutMs?: number,
}

View File

@ -50,7 +50,7 @@ export interface HttpResponse {
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
export interface HttpRequestOptions {
method?: "POST" | "PUT" | "GET" | "DELETE";
method?: "POST" | "PATCH" | "PUT" | "GET" | "DELETE";
headers?: { [name: string]: string };
/**