diff options
| author | Özgür Kesim <oec-taler@kesim.org> | 2023-10-17 12:04:44 +0200 | 
|---|---|---|
| committer | Özgür Kesim <oec-taler@kesim.org> | 2023-10-17 12:04:44 +0200 | 
| commit | fba664f9a3c35dfeb5bc5ac28f0baea09ff9b8a0 (patch) | |
| tree | 6d069afdecade1b13914f4d13020d0331c2f4036 /packages/taler-util/src/http-client | |
| parent | def5ecda6fc4015417779af0a829d3f8aad4dd83 (diff) | |
| parent | aca3bc9423f15354913d0114cafbd4bd1782d801 (diff) | |
Merge branch 'master' into age-withdrawHEADage-withdraw
Diffstat (limited to 'packages/taler-util/src/http-client')
| -rw-r--r-- | packages/taler-util/src/http-client/bank-core.ts | 489 | ||||
| -rw-r--r-- | packages/taler-util/src/http-client/bank-integration.ts | 47 | ||||
| -rw-r--r-- | packages/taler-util/src/http-client/bank-revenue.ts | 32 | ||||
| -rw-r--r-- | packages/taler-util/src/http-client/bank-wire.ts | 98 | ||||
| -rw-r--r-- | packages/taler-util/src/http-client/index.ts | 0 | ||||
| -rw-r--r-- | packages/taler-util/src/http-client/types.ts | 1183 | ||||
| -rw-r--r-- | packages/taler-util/src/http-client/utils.ts | 68 | 
7 files changed, 1917 insertions, 0 deletions
| diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts new file mode 100644 index 000000000..c77f9ddda --- /dev/null +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -0,0 +1,489 @@ +/* + 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"; +import { TalerRevenueHttpClient } from "./bank-revenue.js"; +import { TalerWireGatewayHttpClient } from "./bank-wire.js"; +import { TalerBankIntegrationHttpClient } from "./bank-integration.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,) +  } + +} + diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts new file mode 100644 index 000000000..cd6462417 --- /dev/null +++ b/packages/taler-util/src/http-client/bank-integration.ts @@ -0,0 +1,47 @@ +import { HttpRequestLibrary, readSuccessResponseJsonOrThrow } from "../http-common.js"; +import { createPlatformHttpLib } from "../http.js"; +import { +  TalerBankIntegrationApi, +  codecForBankWithdrawalOperationPostResponse, +  codecForBankWithdrawalOperationStatus +} from "./types.js"; + +export class TalerBankIntegrationHttpClient { +  httpLib: HttpRequestLibrary; + +  constructor( +    private baseUrl: string, +    httpClient?: HttpRequestLibrary, +  ) { +    this.httpLib = httpClient ?? createPlatformHttpLib(); +  } + +  /** +   * https://docs.taler.net/core/api-bank-integration.html#get-$BANK_API_BASE_URL-withdrawal-operation-$wopid +   *  +   */ +  async getWithdrawalOperationById(woid: string, timeoutMs?: number): Promise<TalerBankIntegrationApi.BankWithdrawalOperationStatus> { +    const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl); +    if (timeoutMs) { +      url.searchParams.set("long_poll_ms", String(timeoutMs)) +    } +    const resp = await this.httpLib.fetch(url.href, { +      method: "GET" +    }); +    return readSuccessResponseJsonOrThrow(resp, codecForBankWithdrawalOperationStatus()); +  } + +  /** +   * https://docs.taler.net/core/api-bank-integration.html#post-$BANK_API_BASE_URL-withdrawal-operation-$wopid +   *  +   */ +  async completeWithdrawalOperationById(woid: string): Promise<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse> { +    const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl); +    const resp = await this.httpLib.fetch(url.href, { +      method: "POST", +    }); +    return readSuccessResponseJsonOrThrow(resp, codecForBankWithdrawalOperationPostResponse()); +  } + +} + diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts new file mode 100644 index 000000000..99ff71457 --- /dev/null +++ b/packages/taler-util/src/http-client/bank-revenue.ts @@ -0,0 +1,32 @@ +import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js"; +import { createPlatformHttpLib } from "../http.js"; +import { TalerRevenueApi, codecForMerchantIncomingHistory } from "./types.js"; +import { UserAndPassword } from "./utils.js"; + +export class TalerRevenueHttpClient { +  httpLib: HttpRequestLibrary; + +  constructor( +    private baseUrl: string, +    private username: string, +    httpClient?: HttpRequestLibrary, +  ) { +    this.httpLib = httpClient ?? createPlatformHttpLib(); +  } + +  /** +   * https://docs.taler.net/core/api-bank-revenue.html#get-$BASE_URL-history +   *  +   * @returns  +   */ +  async getHistory(auth: string): Promise<TalerRevenueApi.MerchantIncomingHistory> { +    const url = new URL(`history`, this.baseUrl); +    const resp = await this.httpLib.fetch(url.href, { +      method: "GET", +      headers: { +        Authorization: makeBasicAuthHeader(this.username, auth), +      } +    }); +    return readSuccessResponseJsonOrThrow(resp, codecForMerchantIncomingHistory()); +  } +}
\ No newline at end of file diff --git a/packages/taler-util/src/http-client/bank-wire.ts b/packages/taler-util/src/http-client/bank-wire.ts new file mode 100644 index 000000000..9f2b859ed --- /dev/null +++ b/packages/taler-util/src/http-client/bank-wire.ts @@ -0,0 +1,98 @@ +import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js"; +import { createPlatformHttpLib } from "../http.js"; +import { TalerWireGatewayApi, codecForAddIncomingResponse, codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse } from "./types.js"; +import { PaginationParams, UserAndPassword, addPaginationParams } from "./utils.js"; + +/** + * The API is used by the exchange to trigger transactions and query  + * incoming transactions, as well as by the auditor to query incoming  + * and outgoing transactions. + *  + * https://docs.taler.net/core/api-bank-wire.html + */ +export class TalerWireGatewayHttpClient { +  httpLib: HttpRequestLibrary; + +  constructor( +    private baseUrl: string, +    private username: string, +    httpClient?: HttpRequestLibrary, +  ) { +    this.httpLib = httpClient ?? createPlatformHttpLib(); +  } + +  /** +   * https://docs.taler.net/core/api-bank-wire.html#post-$BASE_URL-transfer +   *  +   */ +  async transfer( +    auth: string, +    body: TalerWireGatewayApi.TransferRequest, +  ): Promise<TalerWireGatewayApi.TransferResponse> { +    const url = new URL(`transfer`, this.baseUrl); +    const resp = await this.httpLib.fetch(url.href, { +      method: "POST", +      headers: { +        Authorization: makeBasicAuthHeader(this.username, auth), +      }, +      body +    }); +    return readSuccessResponseJsonOrThrow(resp, codecForTransferResponse()); +  } + +  /** +   * https://docs.taler.net/core/api-bank-wire.html#get-$BASE_URL-history-incoming +   *  +   */ +  async getHistoryIncoming( +    auth: string, +    pagination?: PaginationParams +  ): Promise<TalerWireGatewayApi.IncomingHistory> { +    const url = new URL(`history/incoming`, this.baseUrl); +    addPaginationParams(url, pagination) +    const resp = await this.httpLib.fetch(url.href, { +      method: "GET", +      headers: { +        Authorization: makeBasicAuthHeader(this.username, auth), +      } +    }); +    return readSuccessResponseJsonOrThrow(resp, codecForIncomingHistory()); +  } +  /** +   * https://docs.taler.net/core/api-bank-wire.html#get-$BASE_URL-history-outgoing +   *  +   */ +  async getHistoryOutgoing( +    auth: string, +    pagination?: PaginationParams +  ): Promise<TalerWireGatewayApi.OutgoingHistory> { +    const url = new URL(`history/outgoing`, this.baseUrl); +    addPaginationParams(url, pagination) +    const resp = await this.httpLib.fetch(url.href, { +      method: "GET", +      headers: { +        Authorization: makeBasicAuthHeader(this.username, auth), +      } +    }); +    return readSuccessResponseJsonOrThrow(resp, codecForOutgoingHistory()); +  } +  /** +   * https://docs.taler.net/core/api-bank-wire.html#post-$BASE_URL-admin-add-incoming +   *  +   */ +  async addIncoming( +    auth: string, +    body: TalerWireGatewayApi.AddIncomingRequest, +  ): Promise<TalerWireGatewayApi.AddIncomingResponse> { +    const url = new URL(`admin/add-incoming`, this.baseUrl); +    const resp = await this.httpLib.fetch(url.href, { +      method: "POST", +      headers: { +        Authorization: makeBasicAuthHeader(this.username, auth), +      }, +      body +    }); +    return readSuccessResponseJsonOrThrow(resp, codecForAddIncomingResponse()); +  } +} + diff --git a/packages/taler-util/src/http-client/index.ts b/packages/taler-util/src/http-client/index.ts new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/packages/taler-util/src/http-client/index.ts diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts new file mode 100644 index 000000000..66ac39f59 --- /dev/null +++ b/packages/taler-util/src/http-client/types.ts @@ -0,0 +1,1183 @@ +import { codecForAmountString } from "../amounts.js"; +import { Codec, buildCodecForObject, buildCodecForUnion, codecForBoolean, codecForConstString, codecForEither, codecForList, codecForMap, codecForNumber, codecForString, codecOptional } from "../codec.js"; +import { codecForTimestamp } from "../time.js"; + + +/// +/// HASH +/// + +// 64-byte hash code. +type HashCode = string; + +// 32-byte hash code. +type ShortHashCode = string; + +// 16-byte salt. +type WireSalt = string; + +type SHA256HashCode = ShortHashCode; + +type SHA512HashCode = HashCode; + +// 32-byte nonce value, must only be used once. +type CSNonce = string; + +// 32-byte nonce value, must only be used once. +type RefreshMasterSeed = string; + +// 32-byte value representing a point on Curve25519. +type Cs25519Point = string; + +// 32-byte value representing a scalar multiplier +// for scalar operations on points on Curve25519. +type Cs25519Scalar = string; + +/// +/// KEYS +/// + +// 16-byte access token used to authorize access. +type ClaimToken = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented  using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +type EddsaPublicKey = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented  using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +type EddsaPrivateKey = string; + +// Edx25519 public keys are points on Curve25519 and represented using the +// standard 256 bits Ed25519 compact format converted to Crockford +// Base32. +type Edx25519PublicKey = string; + +// Edx25519 private keys are always points on Curve25519 +// and represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +type Edx25519PrivateKey = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented  using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +type EcdhePublicKey = string; + +// Point on Curve25519 represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +type CsRPublic = string; + +// EdDSA and ECDHE public keys always point on Curve25519 +// and represented  using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +type EcdhePrivateKey = string; + +type CoinPublicKey = EddsaPublicKey; + +// RSA public key converted to Crockford Base32. +type RsaPublicKey = string; + +type Integer = number; + +type WireTransferIdentifierRawP = string; +// Subset of numbers:  Integers in the +// inclusive range 0 .. (2^53 - 1). +type SafeUint64 = number; + +// The string must be a data URL according to RFC 2397 +// with explicit mediatype and base64 parameters. +// +//     data:<mediatype>;base64,<data> +// +// Supported mediatypes are image/jpeg and image/png. +// Invalid strings will be rejected by the wallet. +type ImageDataUrl = string; + + +// <Currency>:<DecimalAmount>.  +type Amount = string; + +type WadId = string; + +interface Timestamp { +  // Seconds since epoch, or the special +  // value "never" to represent an event that will +  // never happen. +  t_s: number | "never"; +} + +interface RelativeTime { +  // Duration in microseconds or "forever" +  // to represent an infinite duration. Numeric +  // values are capped at 2^53 - 1 inclusive. +  d_us: number | "forever"; +} + +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; +  } +} + +export 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 codecForBankVersion = +  (): Codec<TalerBankIntegrationApi.BankVersion> => +    buildCodecForObject<TalerBankIntegrationApi.BankVersion>() +      .property("currency", codecForCurrencyName()) +      .property("currency_specification", codecForCurrencySpecificiation()) +      .property("name", codecForConstString("taler-bank-integration")) +      .property("version", codecForLibtoolVersion()) +      .build("TalerBankIntegrationApi.BankVersion"); + +export const codecForBankWithdrawalOperationStatus = +  (): Codec<TalerBankIntegrationApi.BankWithdrawalOperationStatus> => +    buildCodecForObject<TalerBankIntegrationApi.BankWithdrawalOperationStatus>() +      .property("aborted", codecForBoolean()) +      .property("amount", codecForAmountString()) +      .property("confirm_transfer_url", codecOptional(codecForURL())) +      .property("selection_done", codecForBoolean()) +      .property("sender_wire", codecForPaytoURI()) +      .property("suggested_exchange", codecOptional(codecForString())) +      .property("transfer_done", codecForBoolean()) +      .property("wire_types", codecForList(codecForString())) +      .build("TalerBankIntegrationApi.BankWithdrawalOperationStatus"); + +export const codecForBankWithdrawalOperationPostResponse = +  (): Codec<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse> => +    buildCodecForObject<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse>() +      .property("confirm_transfer_url", codecOptional(codecForURL())) +      .property("transfer_done", codecForBoolean()) +      .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse"); + +export const codecForMerchantIncomingHistory = +  (): Codec<TalerRevenueApi.MerchantIncomingHistory> => +    buildCodecForObject<TalerRevenueApi.MerchantIncomingHistory>() +      .property("credit_account", codecForPaytoURI()) +      .property("incoming_transactions", codecForList(codecForMerchantIncomingBankTransaction())) +      .build("TalerRevenueApi.MerchantIncomingHistory"); + +export const codecForMerchantIncomingBankTransaction = +  (): Codec<TalerRevenueApi.MerchantIncomingBankTransaction> => +    buildCodecForObject<TalerRevenueApi.MerchantIncomingBankTransaction>() +      .property("amount", codecForAmountString()) +      .property("date", codecForTimestamp) +      .property("debit_account", codecForPaytoURI()) +      .property("exchange_url", codecForURL()) +      .property("row_id", codecForNumber()) +      .property("wtid", codecForString()) +      .build("TalerRevenueApi.MerchantIncomingBankTransaction"); + +export const codecForTransferResponse = +  (): Codec<TalerWireGatewayApi.TransferResponse> => +    buildCodecForObject<TalerWireGatewayApi.TransferResponse>() +      .property("row_id", codecForNumber()) +      .property("timestamp", codecForTimestamp) +      .build("TalerWireGatewayApi.TransferResponse"); + +export const codecForIncomingHistory = +  (): Codec<TalerWireGatewayApi.IncomingHistory> => +    buildCodecForObject<TalerWireGatewayApi.IncomingHistory>() +      .property("credit_account", codecForString()) +      .property("incoming_transactions", codecForList(codecForIncomingBankTransaction())) +      .build("TalerWireGatewayApi.IncomingHistory"); + +export const codecForIncomingBankTransaction = (): Codec<TalerWireGatewayApi.IncomingBankTransaction> => buildCodecForUnion<TalerWireGatewayApi.IncomingBankTransaction>() +  .discriminateOn("type") +  .alternative("RESERVE", codecForIncomingReserveTransaction()) +  .alternative("WAD", codecForIncomingWadTransaction()) +  .build("TalerWireGatewayApi.IncomingBankTransaction"); + +export const codecForIncomingReserveTransaction = +  (): Codec<TalerWireGatewayApi.IncomingReserveTransaction> => +    buildCodecForObject<TalerWireGatewayApi.IncomingReserveTransaction>() +      .property("amount", codecForAmountString()) +      .property("date", codecForTimestamp) +      .property("debit_account", codecForPaytoURI()) +      .property("reserve_pub", codecForString()) +      .property("row_id", codecForNumber()) +      .property("type", codecForConstString("RESERVE")) +      .build("TalerWireGatewayApi.IncomingReserveTransaction"); + +export const codecForIncomingWadTransaction = +  (): Codec<TalerWireGatewayApi.IncomingWadTransaction> => +    buildCodecForObject<TalerWireGatewayApi.IncomingWadTransaction>() +      .property("amount", codecForAmountString()) +      .property("credit_account", codecForPaytoURI()) +      .property("date", codecForTimestamp) +      .property("debit_account", codecForPaytoURI()) +      .property("origin_exchange_url", codecForURL()) +      .property("row_id", codecForNumber()) +      .property("type", codecForConstString("WAD")) +      .property("wad_id", codecForString()) +      .build("TalerWireGatewayApi.IncomingWadTransaction"); + +export const codecForOutgoingHistory = +  (): Codec<TalerWireGatewayApi.OutgoingHistory> => +    buildCodecForObject<TalerWireGatewayApi.OutgoingHistory>() +      .property("debit_account", codecForString()) +      .property("outgoing_transactions", codecForList(codecForOutgoingBankTransaction())) +      .build("TalerWireGatewayApi.OutgoingHistory"); + +export const codecForOutgoingBankTransaction = +  (): Codec<TalerWireGatewayApi.OutgoingBankTransaction> => +    buildCodecForObject<TalerWireGatewayApi.OutgoingBankTransaction>() +      .property("amount", codecForAmountString()) +      .property("credit_account", codecForPaytoURI()) +      .property("date", codecForTimestamp) +      .property("exchange_base_url", codecForURL()) +      .property("row_id", codecForNumber()) +      .property("wtid", codecForString()) +      .build("TalerWireGatewayApi.OutgoingBankTransaction"); + +export const codecForAddIncomingResponse = +  (): Codec<TalerWireGatewayApi.AddIncomingResponse> => +    buildCodecForObject<TalerWireGatewayApi.AddIncomingResponse>() +      .property("row_id", codecForNumber()) +      .property("timestamp", codecForTimestamp) +      .build("TalerWireGatewayApi.AddIncomingResponse"); + +// export const codecFor = +//   (): Codec<TalerWireGatewayApi.PublicAccountsResponse> => +//     buildCodecForObject<TalerWireGatewayApi.PublicAccountsResponse>() +//       .property("", codecForString()) +//       .build("TalerWireGatewayApi.PublicAccountsResponse"); + + +type EmailAddress = string; +type PhoneNumber = string; +type DecimalNumber = string; + +const codecForURL = codecForString +const codecForLibtoolVersion = codecForString +const codecForCurrencyName = codecForString +const codecForPaytoURI = codecForString +const codecForTalerWithdrawalURI = codecForString +const codecForDecimalNumber = codecForString + +enum TanChannel { +  SMS = "sms", +  EMAIL = "email", +  FILE = "file" +} + +export namespace TalerWireGatewayApi { + +  export interface TransferResponse { + +    // Timestamp that indicates when the wire transfer will be executed. +    // In cases where the wire transfer gateway is unable to know when +    // the wire transfer will be executed, the time at which the request +    // has been received and stored will be returned. +    // The purpose of this field is for debugging (humans trying to find +    // the transaction) as well as for taxation (determining which +    // time period a transaction belongs to). +    timestamp: Timestamp; + +    // Opaque ID of the transaction that the bank has made. +    row_id: SafeUint64; +  } + +  export interface TransferRequest { +    // Nonce to make the request idempotent.  Requests with the same +    // transaction_uid that differ in any of the other fields +    // are rejected. +    request_uid: HashCode; + +    // Amount to transfer. +    amount: Amount; + +    // Base URL of the exchange.  Shall be included by the bank gateway +    // in the appropriate section of the wire transfer details. +    exchange_base_url: string; + +    // Wire transfer identifier chosen by the exchange, +    // used by the merchant to identify the Taler order(s) +    // associated with this wire transfer. +    wtid: ShortHashCode; + +    // The recipient's account identifier as a payto URI. +    credit_account: string; +  } + +  export interface IncomingHistory { + +    // Array of incoming transactions. +    incoming_transactions: IncomingBankTransaction[]; + +    // Payto URI to identify the receiver of funds. +    // This must be one of the exchange's bank accounts. +    // Credit account is shared by all incoming transactions +    // as per the nature of the request. +    credit_account: string; + +  } + +  // Union discriminated by the "type" field. +  export type IncomingBankTransaction = +    | IncomingReserveTransaction +    | IncomingWadTransaction; + +  export interface IncomingReserveTransaction { +    type: "RESERVE"; + +    // Opaque identifier of the returned record. +    row_id: SafeUint64; + +    // Date of the transaction. +    date: Timestamp; + +    // Amount transferred. +    amount: Amount; + +    // Payto URI to identify the sender of funds. +    debit_account: string; + +    // The reserve public key extracted from the transaction details. +    reserve_pub: EddsaPublicKey; + +  } + +  export interface IncomingWadTransaction { +    type: "WAD"; + +    // Opaque identifier of the returned record. +    row_id: SafeUint64; + +    // Date of the transaction. +    date: Timestamp; + +    // Amount transferred. +    amount: Amount; + +    // Payto URI to identify the receiver of funds. +    // This must be one of the exchange's bank accounts. +    credit_account: string; + +    // Payto URI to identify the sender of funds. +    debit_account: string; + +    // Base URL of the exchange that originated the wad. +    origin_exchange_url: string; + +    // The reserve public key extracted from the transaction details. +    wad_id: WadId; +  } + + +  export interface OutgoingHistory { + +    // Array of outgoing transactions. +    outgoing_transactions: OutgoingBankTransaction[]; + +    // Payto URI to identify the sender of funds. +    // This must be one of the exchange's bank accounts. +    // Credit account is shared by all incoming transactions +    // as per the nature of the request. +    debit_account: string; + +  } + +  export interface OutgoingBankTransaction { + +    // Opaque identifier of the returned record. +    row_id: SafeUint64; + +    // Date of the transaction. +    date: Timestamp; + +    // Amount transferred. +    amount: Amount; + +    // Payto URI to identify the receiver of funds. +    credit_account: string; + +    // The wire transfer ID in the outgoing transaction. +    wtid: ShortHashCode; + +    // Base URL of the exchange. +    exchange_base_url: string; +  } + +  export interface AddIncomingRequest { +    // Amount to transfer. +    amount: Amount; + +    // Reserve public key that is included in the wire transfer details +    // to identify the reserve that is being topped up. +    reserve_pub: EddsaPublicKey; + +    // Account (as payto URI) that makes the wire transfer to the exchange. +    // Usually this account must be created by the test harness before this API is +    // used.  An exception is the "exchange-fakebank", where any debit account can be +    // specified, as it is automatically created. +    debit_account: string; +  } + +  export interface AddIncomingResponse { + +    // Timestamp that indicates when the wire transfer will be executed. +    // In cases where the wire transfer gateway is unable to know when +    // the wire transfer will be executed, the time at which the request +    // has been received and stored will be returned. +    // The purpose of this field is for debugging (humans trying to find +    // the transaction) as well as for taxation (determining which +    // time period a transaction belongs to). +    timestamp: Timestamp; + +    // Opaque ID of the transaction that the bank has made. +    row_id: SafeUint64; +  } + + + +} + +export namespace TalerRevenueApi { +  export interface MerchantIncomingHistory { + +    // Array of incoming transactions. +    incoming_transactions: MerchantIncomingBankTransaction[]; + +    // Payto URI to identify the receiver of funds. +    // This must be one of the merchant's bank accounts. +    // Credit account is shared by all incoming transactions +    // as per the nature of the request. +    credit_account: string; + +  } + +  export interface MerchantIncomingBankTransaction { + +    // Opaque identifier of the returned record. +    row_id: SafeUint64; + +    // Date of the transaction. +    date: Timestamp; + +    // Amount transferred. +    amount: Amount; + +    // Payto URI to identify the sender of funds. +    debit_account: string; + +    // Base URL of the exchange where the transfer originated form. +    exchange_url: string; + +    // The wire transfer identifier. +    wtid: WireTransferIdentifierRawP; +  } +} + +export namespace TalerBankIntegrationApi { +  export interface BankVersion { +    // libtool-style representation of the Bank protocol version, see +    // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning +    // The format is "current:revision:age". +    version: string; + +    // Currency used by this bank. +    currency: string; + +    // How the bank SPA should render this currency. +    currency_specification: CurrencySpecification; + +    // Name of the API. +    name: "taler-bank-integration"; +  } + +  export interface BankWithdrawalOperationStatus { +    // Indicates whether the withdrawal was aborted. +    aborted: boolean; + +    // Has the wallet selected parameters for the withdrawal operation +    // (exchange and reserve public key) and successfully sent it +    // to the bank? +    selection_done: boolean; + +    // The transfer has been confirmed and registered by the bank. +    // Does not guarantee that the funds have arrived at the exchange already. +    transfer_done: boolean; + +    // Amount that will be withdrawn with this operation +    // (raw amount without fee considerations). +    amount: Amount; + +    // Bank account of the customer that is withdrawing, as a +    // payto URI. +    sender_wire?: string; + +    // Suggestion for an exchange given by the bank. +    suggested_exchange?: string; + +    // URL that the user needs to navigate to in order to +    // complete some final confirmation (e.g. 2FA). +    // It may contain withdrawal operation id +    confirm_transfer_url?: string; + +    // Wire transfer types supported by the bank. +    wire_types: string[]; +  } + +  export interface BankWithdrawalOperationPostRequest { + +    // Reserve public key. +    reserve_pub: string; + +    // Payto address of the exchange selected for the withdrawal. +    selected_exchange: string; +  } + +  export interface BankWithdrawalOperationPostResponse { + +    // The transfer has been confirmed and registered by the bank. +    // Does not guarantee that the funds have arrived at the exchange already. +    transfer_done: boolean; + +    // URL that the user needs to navigate to in order to +    // complete some final confirmation (e.g. 2FA). +    // +    // Only applicable when transfer_done is false. +    // It may contain withdrawal operation id +    confirm_transfer_url?: string; +  } + + +} +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; +  } + + +} diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts new file mode 100644 index 000000000..4588f945c --- /dev/null +++ b/packages/taler-util/src/http-client/utils.ts @@ -0,0 +1,68 @@ +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) +  } +  const order = !pagination || pagination.order === "asc" ? 1 : -1 +  const limit = !pagination || !pagination.limit || pagination.limit === 0 ? 5 : Math.abs(pagination.limit) +  //always send delta +  url.searchParams.set("delta", String(order * 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 +   * always greater than 0 +   */ +  limit?: number, +  /** +   * milliseconds the server should wait for at least one result to be shown +   */ +  timoutMs?: number, +  /** +   * order +   */ +  order: "asc" | "dec" +} | 
