diff options
Diffstat (limited to 'packages/taler-util/src/http-client/bank-core.ts')
-rw-r--r-- | packages/taler-util/src/http-client/bank-core.ts | 1032 |
1 files changed, 1032 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..97c1727ff --- /dev/null +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -0,0 +1,1032 @@ +/* + This file is part of GNU Taler + (C) 2022-2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { + AbsoluteTime, + HttpStatusCode, + LibtoolVersion, + LongPollParams, + OperationAlternative, + OperationFail, + OperationOk, + TalerErrorCode, + codecForChallenge, + codecForTanTransmission, + opKnownAlternativeFailure, + opKnownHttpFailure, + opKnownTalerFailure +} from "@gnu-taler/taler-util"; +import { + HttpRequestLibrary, + createPlatformHttpLib, + readTalerErrorResponse, +} from "@gnu-taler/taler-util/http"; +import { + FailCasesByMethod, + ResultByMethod, + opEmptySuccess, + opFixedSuccess, + opSuccessFromHttp, + opUnknownFailure, +} from "../operation.js"; +import { + AccessToken, + PaginationParams, + TalerCorebankApi, + UserAndToken, + WithdrawalOperationStatus, + codecForAccountData, + codecForBankAccountCreateWithdrawalResponse, + codecForBankAccountTransactionInfo, + codecForBankAccountTransactionsResponse, + codecForCashoutPending, + codecForCashoutStatusResponse, + codecForCashouts, + codecForCoreBankConfig, + codecForCreateTransactionResponse, + codecForGlobalCashouts, + codecForListBankAccountsResponse, + codecForMonitorResponse, + codecForPublicAccountsResponse, + codecForRegisterAccountResponse, + codecForWithdrawalPublicInfo, +} from "./types.js"; +import { + CacheEvictor, + IdempotencyRetry, + addLongPollingParam, + addPaginationParams, + makeBearerTokenAuthHeader, + nullEvictor, +} from "./utils.js"; + +export type TalerCoreBankResultByMethod< + prop extends keyof TalerCoreBankHttpClient, +> = ResultByMethod<TalerCoreBankHttpClient, prop>; +export type TalerCoreBankErrorsByMethod< + prop extends keyof TalerCoreBankHttpClient, +> = FailCasesByMethod<TalerCoreBankHttpClient, prop>; + +export enum TalerCoreBankCacheEviction { + DELETE_ACCOUNT, + CREATE_ACCOUNT, + UPDATE_ACCOUNT, + UPDATE_PASSWORD, + CREATE_TRANSACTION, + CONFIRM_WITHDRAWAL, + ABORT_WITHDRAWAL, + CREATE_WITHDRAWAL, + CREATE_CASHOUT, +} +/** + * Protocol version spoken with the core bank. + * + * Endpoint must be ordered in the same way that in the docs + * Response code (http and taler) must have the same order that in the docs + * That way is easier to see changes + * + * Uses libtool's current:revision:age versioning. + */ +export class TalerCoreBankHttpClient { + public readonly PROTOCOL_VERSION = "4:0:0"; + + httpLib: HttpRequestLibrary; + cacheEvictor: CacheEvictor<TalerCoreBankCacheEviction>; + constructor( + readonly baseUrl: string, + httpClient?: HttpRequestLibrary, + cacheEvictor?: CacheEvictor<TalerCoreBankCacheEviction>, + ) { + this.httpLib = httpClient ?? createPlatformHttpLib(); + this.cacheEvictor = cacheEvictor ?? nullEvictor; + } + + isCompatible(version: string): boolean { + const compare = LibtoolVersion.compare(this.PROTOCOL_VERSION, version); + return compare?.compatible ?? false; + } + + /** + * https://docs.taler.net/core/api-corebank.html#config + * + */ + async getConfig() { + const url = new URL(`config`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForCoreBankConfig()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // + // ACCOUNTS + // + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts + * + */ + async createAccount( + auth: AccessToken | undefined, + body: TalerCorebankApi.RegisterAccountRequest, + ) { + const url = new URL(`accounts`, this.baseUrl); + const headers: Record<string, string> = {}; + if (auth) { + headers.Authorization = makeBearerTokenAuthHeader(auth); + } + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + headers: headers, + }); + switch (resp.status) { + case HttpStatusCode.Ok: { + await this.cacheEvictor.notifySuccess( + TalerCoreBankCacheEviction.CREATE_ACCOUNT, + ); + return opSuccessFromHttp(resp, codecForRegisterAccountResponse()); + } + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_UNALLOWED_DEBIT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_NON_ADMIN_SET_TAN_CHANNEL: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_MISSING_TAN_INFO: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + /** + * https://docs.taler.net/core/api-corebank.html#delete--accounts-$USERNAME + * + */ + async deleteAccount(auth: UserAndToken, cid?: string) { + const url = new URL(`accounts/${auth.username}`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "DELETE", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + "X-Challenge-Id": cid, + }, + }); + switch (resp.status) { + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForChallenge(), + ); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME + * + */ + async updateAccount( + auth: UserAndToken, + body: TalerCorebankApi.AccountReconfiguration, + cid?: string, + ) { + 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), + "X-Challenge-Id": cid, + }, + }); + switch (resp.status) { + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForChallenge(), + ); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_NON_ADMIN_PATCH_LEGAL_NAME: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_NON_ADMIN_PATCH_CASHOUT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_NON_ADMIN_PATCH_DEBT_LIMIT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_TAN_CHANNEL_NOT_SUPPORTED: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_MISSING_TAN_INFO: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME-auth + * + */ + async updatePassword( + auth: UserAndToken, + body: TalerCorebankApi.AccountPasswordChange, + cid?: string, + ) { + const url = new URL(`accounts/${auth.username}/auth`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "PATCH", + body, + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + "X-Challenge-Id": cid, + }, + }); + switch (resp.status) { + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForChallenge(), + ); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_PATCH_BAD_OLD_PASSWORD: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--public-accounts + * + */ + async getPublicAccounts( + filter: { account?: string } = {}, + pagination?: PaginationParams, + ) { + const url = new URL(`public-accounts`, this.baseUrl); + addPaginationParams(url, pagination); + if (filter.account !== undefined) { + url.searchParams.set("filter_name", filter.account); + } + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForPublicAccountsResponse()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ public_accounts: [] }); + case HttpStatusCode.NotFound: + return opFixedSuccess({ public_accounts: [] }); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--accounts + * + */ + async getAccounts( + auth: AccessToken, + filter: { account?: string } = {}, + pagination?: PaginationParams, + ) { + const url = new URL(`accounts`, this.baseUrl); + addPaginationParams(url, pagination); + if (filter.account !== undefined) { + url.searchParams.set("filter_name", filter.account); + } + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForListBankAccountsResponse()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ accounts: [] }); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME + * + */ + async getAccount(auth: UserAndToken) { + const url = new URL(`accounts/${auth.username}`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAccountData()); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // + // TRANSACTIONS + // + + /** + * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-transactions + * + */ + async getTransactions( + auth: UserAndToken, + params?: PaginationParams & LongPollParams, + ) { + const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl); + addPaginationParams(url, params); + addLongPollingParam(url, params); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp( + resp, + codecForBankAccountTransactionsResponse(), + ); + case HttpStatusCode.NoContent: + return opFixedSuccess({ transactions: [] }); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-transactions-$TRANSACTION_ID + * + */ + async getTransactionById(auth: UserAndToken, txid: number) { + 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), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForBankAccountTransactionInfo()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-transactions + * + */ + async createTransaction( + auth: UserAndToken, + body: TalerCorebankApi.CreateTransactionRequest, + idempotencyCheck: IdempotencyRetry | undefined, + cid?: string, + ): Promise< + //manually definition all return types because of recursion + | OperationOk<TalerCorebankApi.CreateTransactionResponse> + | OperationAlternative<HttpStatusCode.Accepted, TalerCorebankApi.Challenge> + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.BadRequest> + | OperationFail<HttpStatusCode.Unauthorized> + | OperationFail<TalerErrorCode.BANK_UNALLOWED_DEBIT> + | OperationFail<TalerErrorCode.BANK_ADMIN_CREDITOR> + | OperationFail<TalerErrorCode.BANK_SAME_ACCOUNT> + | OperationFail<TalerErrorCode.BANK_UNKNOWN_CREDITOR> + | OperationFail<TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED> + > { + const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl); + if (idempotencyCheck) { + body.request_uid = idempotencyCheck.uid + } + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + "X-Challenge-Id": cid, + }, + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForCreateTransactionResponse()); + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForChallenge(), + ); + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_ADMIN_CREDITOR: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_SAME_ACCOUNT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_UNKNOWN_CREDITOR: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_UNALLOWED_DEBIT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: + if (!idempotencyCheck) { + return opKnownTalerFailure(details.code, details); + } + const nextRetry = idempotencyCheck.next(); + return this.createTransaction(auth, body, nextRetry, cid); + default: + return opUnknownFailure(resp, details); + } + } + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // + // WITHDRAWALS + // + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-withdrawals + * + */ + async createWithdrawal( + auth: UserAndToken, + body: TalerCorebankApi.BankAccountCreateWithdrawalRequest, + ) { + 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, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp( + resp, + codecForBankAccountCreateWithdrawalResponse(), + ); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + //FIXME: missing in docs + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-withdrawals-$WITHDRAWAL_ID-confirm + * + */ + async confirmWithdrawalById(auth: UserAndToken, wid: string, cid?: string) { + const url = new URL( + `accounts/${auth.username}/withdrawals/${wid}/confirm`, + this.baseUrl, + ); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + "X-Challenge-Id": cid, + }, + }); + switch (resp.status) { + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForChallenge(), + ); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + //FIXME: missing in docs + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_UNALLOWED_DEBIT: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-withdrawals-$WITHDRAWAL_ID-abort + * + */ + async abortWithdrawalById(auth: UserAndToken, wid: string) { + const url = new URL( + `accounts/${auth.username}/withdrawals/${wid}/abort`, + this.baseUrl, + ); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + }, + }); + switch (resp.status) { + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + //FIXME: missing in docs + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--withdrawals-$WITHDRAWAL_ID + * + */ + async getWithdrawalById( + wid: string, + params?: { + old_state?: WithdrawalOperationStatus; + } & LongPollParams, + ) { + const url = new URL(`withdrawals/${wid}`, this.baseUrl); + addLongPollingParam(url, params); + if (params) { + url.searchParams.set( + "old_state", + !params.old_state ? "pending" : params.old_state, + ); + } + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForWithdrawalPublicInfo()); + //FIXME: missing in docs + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // + // CASHOUTS + // + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts + * + */ + async createCashout( + auth: UserAndToken, + body: TalerCorebankApi.CashoutRequest, + cid?: string, + ) { + 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), + "X-Challenge-Id": cid, + }, + body, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForCashoutPending()); + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForChallenge(), + ); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_BAD_CONVERSION: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_UNALLOWED_DEBIT: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + case HttpStatusCode.BadGateway: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + case HttpStatusCode.NotImplemented: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID + * + */ + async getCashoutById(auth: UserAndToken, cid: number) { + 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), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForCashoutStatusResponse()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotImplemented: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts + * + */ + async getAccountCashouts(auth: UserAndToken, pagination?: PaginationParams) { + const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl); + addPaginationParams(url, pagination); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForCashouts()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ cashouts: [] }); + case HttpStatusCode.NotImplemented: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#get--cashouts + * + */ + async getGlobalCashouts(auth: AccessToken, pagination?: PaginationParams) { + const url = new URL(`cashouts`, this.baseUrl); + addPaginationParams(url, pagination); + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForGlobalCashouts()); + case HttpStatusCode.NoContent: + return opFixedSuccess({ cashouts: [] }); + case HttpStatusCode.NotImplemented: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // + // 2FA + // + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-challenge-$CHALLENGE_ID + * + */ + async sendChallenge(auth: UserAndToken, cid: string) { + const url = new URL( + `accounts/${auth.username}/challenge/${cid}`, + this.baseUrl, + ); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForTanTransmission()); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.BadGateway: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-challenge-$CHALLENGE_ID-confirm + * + */ + async confirmChallenge( + auth: UserAndToken, + cid: string, + body: TalerCorebankApi.ChallengeSolve, + ) { + const url = new URL( + `accounts/${auth.username}/challenge/${cid}/confirm`, + this.baseUrl, + ); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token), + }, + body, + }); + switch (resp.status) { + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: { + const details = await readTalerErrorResponse(resp); + switch (details.code) { + case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED: + return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: + return opKnownTalerFailure(details.code, details); + default: + return opUnknownFailure(resp, details); + } + } + case HttpStatusCode.TooManyRequests: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // + // MONITOR + // + + /** + * https://docs.taler.net/core/api-corebank.html#get--monitor + * + */ + async getMonitor( + auth: AccessToken, + params: { + timeframe?: TalerCorebankApi.MonitorTimeframeParam; + date?: AbsoluteTime; + } = {}, + ) { + const url = new URL(`monitor`, this.baseUrl); + if (params.timeframe) { + url.searchParams.set( + "timeframe", + TalerCorebankApi.MonitorTimeframeParam[params.timeframe], + ); + } + if (params.date) { + const { t_s: seconds } = AbsoluteTime.toProtocolTimestamp(params.date); + if (seconds !== "never") { + url.searchParams.set("date_s", String(seconds)); + } + } + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForMonitorResponse()); + case HttpStatusCode.BadRequest: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // + // Others API + // + + /** + * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api + * + */ + getIntegrationAPI(): URL { + return new URL(`taler-integration/`, this.baseUrl); + } + + /** + * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api + * + */ + getWireGatewayAPI(username: string): URL { + return new URL(`accounts/${username}/taler-wire-gateway/`, this.baseUrl); + } + + /** + * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api + * + */ + getRevenueAPI(username: string): URL { + return new URL(`accounts/${username}/taler-revenue/`, this.baseUrl); + } + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token + * + */ + getAuthenticationAPI(username: string): URL { + return new URL(`accounts/${username}/`, this.baseUrl); + } + + /** + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token + * + */ + getConversionInfoAPI(): URL { + return new URL(`conversion-info/`, this.baseUrl); + } +} |