diff options
Diffstat (limited to 'packages/taler-util/src')
49 files changed, 8957 insertions, 7416 deletions
diff --git a/packages/taler-util/src/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts index f58757fb5..ef3ea2ff7 100644 --- a/packages/taler-util/src/MerchantApiClient.ts +++ b/packages/taler-util/src/MerchantApiClient.ts @@ -15,12 +15,6 @@ */ import { codecForAny } from "./codec.js"; -import { - TalerMerchantApi, - codecForMerchantConfig, - codecForMerchantOrderPrivateStatusResponse, - codecForPostOrderResponse, -} from "./http-client/types.js"; import { HttpStatusCode } from "./http-status-codes.js"; import { createPlatformHttpLib, @@ -28,7 +22,6 @@ import { readSuccessResponseJsonOrThrow, readTalerErrorResponse, } from "./http.js"; -import { FacadeCredentials } from "./libeufin-api-types.js"; import { LibtoolVersion } from "./libtool-version.js"; import { Logger } from "./logging.js"; import { @@ -41,8 +34,16 @@ import { opSuccessFromHttp, opUnknownFailure, } from "./operation.js"; -import { AmountString } from "./taler-types.js"; import { TalerProtocolDuration } from "./time.js"; +import { AmountString } from "./types-taler-common.js"; +import { + OtpDeviceAddDetails, + codecForTalerMerchantConfigResponse, + codecForMerchantOrderPrivateStatusResponse, + codecForPostOrderResponse, +} from "./types-taler-merchant.js"; + +import * as TalerMerchantApi from "./types-taler-merchant.js"; const logger = new Logger("MerchantApiClient.ts"); @@ -87,21 +88,6 @@ export interface DeleteTippingReserveArgs { purge?: boolean; } -interface MerchantBankAccount { - // The payto:// URI where the wallet will send coins. - payto_uri: string; - - // Optional base URL for a facade where the - // merchant backend can see incoming wire - // transfers to reconcile its accounting - // with that of the exchange. Used by - // taler-merchant-wirewatch. - credit_facade_url?: string; - - // Credentials for accessing the credit facade. - credit_facade_credentials?: FacadeCredentials; -} - export interface MerchantInstanceConfig { auth: MerchantAuthConfiguration; id: string; @@ -119,23 +105,6 @@ export interface PrivateOrderStatusQuery { sessionId?: string; } -export interface OtpDeviceAddDetails { - // Device ID to use. - otp_device_id: string; - - // Human-readable description for the device. - otp_device_description: string; - - // A base64-encoded key - otp_key: string; - - // Algorithm for computing the POS confirmation. - otp_algorithm: number; - - // Counter for counter-based OTP devices. - otp_ctr?: number; -} - /** * Client for the GNU Taler merchant backend. */ @@ -325,14 +294,14 @@ export class MerchantApiClient { * https://docs.taler.net/core/api-merchant.html#get--config * */ - async getConfig(): Promise<OperationOk<TalerMerchantApi.VersionResponse>> { + async getConfig(): Promise<OperationOk<TalerMerchantApi.TalerMerchantConfigResponse>> { const url = new URL(`config`, this.baseUrl); const resp = await this.httpClient.fetch(url.href, { method: "GET", }); switch (resp.status) { case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForMerchantConfig()); + return opSuccessFromHttp(resp, codecForTalerMerchantConfigResponse()); default: return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } diff --git a/packages/taler-util/src/ReserveStatus.ts b/packages/taler-util/src/ReserveStatus.ts index 3a30755ce..325dae68d 100644 --- a/packages/taler-util/src/ReserveStatus.ts +++ b/packages/taler-util/src/ReserveStatus.ts @@ -23,7 +23,7 @@ */ import { codecForAmountString } from "./amounts.js"; import { Codec, buildCodecForObject } from "./codec.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; /** * Status of a reserve. diff --git a/packages/taler-util/src/ReserveTransaction.ts b/packages/taler-util/src/ReserveTransaction.ts index 7a3c69d07..0237ad724 100644 --- a/packages/taler-util/src/ReserveTransaction.ts +++ b/packages/taler-util/src/ReserveTransaction.ts @@ -38,7 +38,7 @@ import { EddsaSignatureString, EddsaPublicKeyString, CoinPublicKeyString, -} from "./taler-types.js"; +} from "./types-taler-common.js"; import { AbsoluteTime, codecForTimestamp, diff --git a/packages/taler-util/src/amounts.test.ts b/packages/taler-util/src/amounts.test.ts index 449a6319a..d1be17824 100644 --- a/packages/taler-util/src/amounts.test.ts +++ b/packages/taler-util/src/amounts.test.ts @@ -17,7 +17,7 @@ import test from "ava"; import { Amounts, AmountJson, amountMaxValue } from "./amounts.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; const jAmt = ( value: number, diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts index 82a3d3b68..e173e8b96 100644 --- a/packages/taler-util/src/amounts.ts +++ b/packages/taler-util/src/amounts.ts @@ -31,7 +31,7 @@ import { renderContext, } from "./codec.js"; import { CurrencySpecification } from "./index.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; /** * Number of fractional units that one value unit represents. diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts index e1409087f..642232962 100644 --- a/packages/taler-util/src/bank-api-client.ts +++ b/packages/taler-util/src/bank-api-client.ts @@ -22,6 +22,7 @@ * Imports. */ import { + AccountData, AmountString, base64FromArrayBuffer, buildCodecForObject, @@ -36,6 +37,7 @@ import { opEmptySuccess, opKnownHttpFailure, opUnknownFailure, + RegisterAccountRequest, stringToBytes, TalerError, TalerErrorCode, @@ -46,7 +48,6 @@ import { expectSuccessResponseOrThrow, HttpRequestLibrary, readSuccessResponseJsonOrThrow, - readSuccessResponseTextOrThrow, readTalerErrorResponse, } from "@gnu-taler/taler-util/http"; @@ -152,83 +153,11 @@ export class WireGatewayApiClient { } } -export interface ChallengeContactData { - // E-Mail address - email?: string; - - // Phone number. - phone?: string; -} - export interface AccountBalance { amount: AmountString; credit_debit_indicator: "credit" | "debit"; } -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. - payto_uri?: string; -} - -export interface AccountData { - // Legal name of the account owner. - name: string; - - // Available balance on the account. - balance: AccountBalance; - - // payto://-URI of the account. - payto_uri: string; - - // Number indicating the max debit allowed for the requesting user. - debit_threshold: AmountString; - - 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 ConfirmWithdrawalArgs { withdrawalOperationId: string; } diff --git a/packages/taler-util/src/clk.ts b/packages/taler-util/src/clk.ts index 60969af69..914e0cd18 100644 --- a/packages/taler-util/src/clk.ts +++ b/packages/taler-util/src/clk.ts @@ -23,7 +23,7 @@ import { readlinePrompt, pathBasename, } from "#compat-impl"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; export namespace clk { class Converter<T> {} diff --git a/packages/taler-util/src/errors.ts b/packages/taler-util/src/errors.ts index deb5d017b..41d470972 100644 --- a/packages/taler-util/src/errors.ts +++ b/packages/taler-util/src/errors.ts @@ -188,7 +188,7 @@ export function makeErrorDetail<C extends TalerErrorCode>( hint?: string, ): TalerErrorDetail { if (!hint && !(detail as any).hint) { - hint = getDefaultHint(code); + hint = getDefaultTalerErrorHint(code); } const when = AbsoluteTime.now(); return { code, when, hint, ...detail }; @@ -210,7 +210,7 @@ export function summarizeTalerErrorDetail(ed: TalerErrorDetail): string { return `Error (${ed.code}/${errName})`; } -function getDefaultHint(code: number): string { +export function getDefaultTalerErrorHint(code: number): string { const errName = TalerErrorCode[code]; if (errName) { return `Error (${errName})`; @@ -257,7 +257,7 @@ export function makeTalerErrorDetail<C extends TalerErrorCode>( hint?: string, ): TalerErrorDetail { if (!hint) { - hint = getDefaultHint(code); + hint = getDefaultTalerErrorHint(code); } return { code, hint, ...errBody }; } @@ -279,7 +279,7 @@ export class TalerError<T = any> extends Error { cause?: Error, ): TalerError { if (!hint) { - hint = getDefaultHint(code); + hint = getDefaultTalerErrorHint(code); } const when = AbsoluteTime.now(); return new TalerError<unknown>({ code, when, hint, ...detail }, cause); diff --git a/packages/taler-util/src/http-client/authentication.ts b/packages/taler-util/src/http-client/authentication.ts index 8897a2fa0..fa48f3da6 100644 --- a/packages/taler-util/src/http-client/authentication.ts +++ b/packages/taler-util/src/http-client/authentication.ts @@ -33,10 +33,10 @@ import { } from "../operation.js"; import { AccessToken, - TalerAuthentication, + TokenRequest, codecForTokenSuccessResponse, codecForTokenSuccessResponseMerchant, -} from "./types.js"; +} from "../types-taler-common.js"; import { makeBearerTokenAuthHeader } from "./utils.js"; export class TalerAuthenticationHttpClient { @@ -64,7 +64,7 @@ export class TalerAuthenticationHttpClient { async createAccessTokenBasic( username: string, password: string, - body: TalerAuthentication.TokenRequest, + body: TokenRequest, ) { const url = new URL(`token`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { @@ -91,10 +91,7 @@ export class TalerAuthenticationHttpClient { * * @returns */ - async createAccessTokenBearer( - token: AccessToken, - body: TalerAuthentication.TokenRequest, - ) { + async createAccessTokenBearer(token: AccessToken, body: TokenRequest) { const url = new URL(`token`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", diff --git a/packages/taler-util/src/http-client/bank-conversion.ts b/packages/taler-util/src/http-client/bank-conversion.ts index cb14d8b34..5001b9de5 100644 --- a/packages/taler-util/src/http-client/bank-conversion.ts +++ b/packages/taler-util/src/http-client/bank-conversion.ts @@ -31,14 +31,14 @@ import { opUnknownFailure, } from "../operation.js"; import { TalerErrorCode } from "../taler-error-codes.js"; -import { codecForTalerErrorDetail } from "../wallet-types.js"; import { - AccessToken, - TalerBankConversionApi, + ConversionRate, codecForCashinConversionResponse, codecForCashoutConversionResponse, codecForConversionBankConfig, -} from "./types.js"; +} from "../types-taler-bank-conversion.js"; +import { AccessToken } from "../types-taler-common.js"; +import { codecForTalerErrorDetail } from "../types-taler-wallet.js"; import { CacheEvictor, makeBearerTokenAuthHeader, @@ -193,10 +193,7 @@ export class TalerBankConversionHttpClient { * https://docs.taler.net/core/api-bank-conversion-info.html#post--conversion-rate * */ - async updateConversionRate( - auth: AccessToken, - body: TalerBankConversionApi.ConversionRate, - ) { + async updateConversionRate(auth: AccessToken, body: ConversionRate) { const url = new URL(`conversion-rate`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts index 6c8051ada..2639018a8 100644 --- a/packages/taler-util/src/http-client/bank-core.ts +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -16,15 +16,18 @@ import { AbsoluteTime, + AccessToken, HttpStatusCode, LibtoolVersion, LongPollParams, OperationAlternative, OperationFail, OperationOk, + PaginationParams, + TalerError, TalerErrorCode, - codecForChallenge, - codecForTanTransmission, + UserAndToken, + codecForTalerCommonConfigResponse, opKnownAlternativeFailure, opKnownHttpFailure, opKnownTalerFailure, @@ -32,6 +35,7 @@ import { import { HttpRequestLibrary, createPlatformHttpLib, + readSuccessResponseJsonOrThrow, readTalerErrorResponse, } from "@gnu-taler/taler-util/http"; import { @@ -42,12 +46,8 @@ import { opSuccessFromHttp, opUnknownFailure, } from "../operation.js"; +import { WithdrawalOperationStatus } from "../types-taler-bank-integration.js"; import { - AccessToken, - PaginationParams, - TalerCorebankApi, - UserAndToken, - WithdrawalOperationStatus, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountTransactionInfo, @@ -55,6 +55,7 @@ import { codecForCashoutPending, codecForCashoutStatusResponse, codecForCashouts, + codecForChallenge, codecForCoreBankConfig, codecForCreateTransactionResponse, codecForGlobalCashouts, @@ -62,8 +63,9 @@ import { codecForMonitorResponse, codecForPublicAccountsResponse, codecForRegisterAccountResponse, + codecForTanTransmission, codecForWithdrawalPublicInfo, -} from "./types.js"; +} from "../types-taler-corebank.js"; import { CacheEvictor, IdempotencyRetry, @@ -73,6 +75,8 @@ import { nullEvictor, } from "./utils.js"; +import * as TalerCorebankApi from "../types-taler-corebank.js"; + export type TalerCoreBankResultByMethod< prop extends keyof TalerCoreBankHttpClient, > = ResultByMethod<TalerCoreBankHttpClient, prop>; @@ -91,6 +95,7 @@ export enum TalerCoreBankCacheEviction { CREATE_WITHDRAWAL, CREATE_CASHOUT, } + /** * Protocol version spoken with the core bank. * @@ -123,14 +128,47 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#config * */ - async getConfig() { + async getConfig(): Promise< + OperationFail<HttpStatusCode.NotFound> | OperationOk<TalerCorebankApi.TalerCorebankConfigResponse> + > { 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.Ok: { + const minBody = await readSuccessResponseJsonOrThrow( + resp, + codecForTalerCommonConfigResponse(), + ); + // FIXME: Re-enable the check once fakebank and libeufin-bank return the name. + // const expectedName = "taler-corebank"; + // if (minBody.name !== expectedName) { + // throw TalerError.fromUncheckedDetail({ + // code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + // requestUrl: resp.requestUrl, + // httpStatusCode: resp.status, + // detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`, + // }); + // } + if (!this.isCompatible(minBody.version)) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unsupported protocol version, client supports ${this.PROTOCOL_VERSION}, server supports ${minBody.version}`, + }); + } + // Now that we've checked the basic body, re-parse the full response. + const body = await readSuccessResponseJsonOrThrow( + resp, + codecForCoreBankConfig(), + ); + return { + type: "ok", + body, + }; + } case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts index 1e0f7e79c..8e98bb783 100644 --- a/packages/taler-util/src/http-client/bank-integration.ts +++ b/packages/taler-util/src/http-client/bank-integration.ts @@ -29,15 +29,15 @@ import { opUnknownFailure, } from "../operation.js"; import { TalerErrorCode } from "../taler-error-codes.js"; -import { codecForTalerErrorDetail } from "../wallet-types.js"; import { - LongPollParams, - TalerBankIntegrationApi, + BankWithdrawalOperationPostRequest, WithdrawalOperationStatus, codecForBankWithdrawalOperationPostResponse, codecForBankWithdrawalOperationStatus, - codecForIntegrationBankConfig, -} from "./types.js"; +} from "../types-taler-bank-integration.js"; +import { LongPollParams } from "../types-taler-common.js"; +import { codecForIntegrationBankConfig } from "../types-taler-corebank.js"; +import { codecForTalerErrorDetail } from "../types-taler-wallet.js"; import { addLongPollingParam } from "./utils.js"; export type TalerBankIntegrationResultByMethod< @@ -84,7 +84,7 @@ export class TalerBankIntegrationHttpClient { case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForIntegrationBankConfig()); default: - logger.warn(`config request failed, status ${resp.status}`) + logger.warn(`config request failed, status ${resp.status}`); return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } @@ -126,7 +126,7 @@ export class TalerBankIntegrationHttpClient { */ async completeWithdrawalOperationById( woid: string, - body: TalerBankIntegrationApi.BankWithdrawalOperationPostRequest, + body: BankWithdrawalOperationPostRequest, ) { const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts index 8331856a9..6acff91f6 100644 --- a/packages/taler-util/src/http-client/bank-revenue.ts +++ b/packages/taler-util/src/http-client/bank-revenue.ts @@ -30,12 +30,11 @@ import { opSuccessFromHttp, opUnknownFailure, } from "../operation.js"; +import { LongPollParams, PaginationParams } from "../types-taler-common.js"; import { - LongPollParams, - PaginationParams, codecForRevenueConfig, codecForRevenueIncomingHistory, -} from "./types.js"; +} from "../types-taler-revenue.js"; import { addLongPollingParam, addPaginationParams } from "./utils.js"; export type TalerBankRevenueResultByMethod< @@ -120,7 +119,10 @@ export class TalerRevenueHttpClient { return opSuccessFromHttp(resp, codecForRevenueIncomingHistory()); // FIXME: missing in docs case HttpStatusCode.NoContent: - return opFixedSuccess({incoming_transactions: [], credit_account: "" }); + return opFixedSuccess({ + incoming_transactions: [], + credit_account: "", + }); case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Unauthorized: diff --git a/packages/taler-util/src/http-client/bank-wire.ts b/packages/taler-util/src/http-client/bank-wire.ts index a8c976a80..84df50208 100644 --- a/packages/taler-util/src/http-client/bank-wire.ts +++ b/packages/taler-util/src/http-client/bank-wire.ts @@ -14,7 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { HttpRequestLibrary, makeBasicAuthHeader, readTalerErrorResponse } from "../http-common.js"; +import { + HttpRequestLibrary, + makeBasicAuthHeader, + readTalerErrorResponse, +} from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; import { @@ -26,16 +30,16 @@ import { opUnknownFailure, } from "../operation.js"; import { - LongPollParams, - PaginationParams, - TalerWireGatewayApi, codecForAddIncomingResponse, codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse, -} from "./types.js"; +} from "../types-taler-wire-gateway.js"; import { addLongPollingParam, addPaginationParams } from "./utils.js"; +import { LongPollParams, PaginationParams } from "../types-taler-common.js"; +import * as TalerWireGatewayApi from "../types-taler-wire-gateway.js"; + export type TalerWireGatewayResultByMethod< prop extends keyof TalerWireGatewayHttpClient, > = ResultByMethod<TalerWireGatewayHttpClient, prop>; diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts index d2f0dd201..1b0fda300 100644 --- a/packages/taler-util/src/http-client/challenger.ts +++ b/packages/taler-util/src/http-client/challenger.ts @@ -1,3 +1,24 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Imports. + */ import { HttpRequestLibrary, readTalerErrorResponse } from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; @@ -11,7 +32,6 @@ import { opUnknownFailure, } from "../operation.js"; import { - AccessToken, codecForChallengeInvalidPinResponse, codecForChallengeResponse, codecForChallengeSetupResponse, @@ -20,7 +40,8 @@ import { codecForChallengerAuthResponse, codecForChallengerInfoResponse, codecForChallengerTermsOfServiceResponse, -} from "./types.js"; +} from "../types-taler-challenger.js"; +import { AccessToken } from "../types-taler-common.js"; import { CacheEvictor, makeBearerTokenAuthHeader, diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts index 68d68267f..9b7635cb4 100644 --- a/packages/taler-util/src/http-client/exchange.ts +++ b/packages/taler-util/src/http-client/exchange.ts @@ -1,39 +1,64 @@ -import { HttpRequestLibrary, readTalerErrorResponse } from "../http-common.js"; +import { + HttpRequestLibrary, + readSuccessResponseJsonOrThrow, + readTalerErrorResponse, +} from "../http-common.js"; import { HttpStatusCode } from "../http-status-codes.js"; import { createPlatformHttpLib } from "../http.js"; import { LibtoolVersion } from "../libtool-version.js"; import { hash } from "../nacl-fast.js"; import { FailCasesByMethod, + OperationFail, + OperationOk, ResultByMethod, opEmptySuccess, opFixedSuccess, + opKnownAlternativeFailure, opKnownHttpFailure, opSuccessFromHttp, - opUnknownFailure, + opUnknownFailure } from "../operation.js"; import { TalerSignaturePurpose, - amountToBuffer, - bufferForUint32, buildSigPS, decodeCrock, eddsaSign, encodeCrock, stringToBytes, - timestampRoundedToBuffer, + timestampRoundedToBuffer } from "../taler-crypto.js"; import { + AccessToken, + AmountString, OfficerAccount, PaginationParams, + ReserveAccount, SigningKey, - TalerExchangeApi, - codecForAmlDecisionDetails, - codecForAmlRecords, + codecForTalerCommonConfigResponse +} from "../types-taler-common.js"; +import { + AmlDecisionRequest, + ExchangeVersionResponse, + KycRequirementInformationId, + WalletKycRequest, + codecForAccountKycStatus, + codecForAmlDecisionsResponse, + codecForAmlKycAttributes, + codecForAmlWalletKycCheckResponse, + codecForAvailableMeasureSummary, + codecForEventCounter, codecForExchangeConfig, codecForExchangeKeys, -} from "./types.js"; -import { CacheEvictor, addPaginationParams, nullEvictor } from "./utils.js"; + codecForKycProcessClientInformation, + codecForKycProcessStartInformation, + codecForLegitimizationNeededResponse +} from "../types-taler-exchange.js"; +import { CacheEvictor, addMerchantPaginationParams, nullEvictor } from "./utils.js"; + +import { TalerError } from "../errors.js"; +import { TalerErrorCode } from "../taler-error-codes.js"; +import { codecForEmptyObject } from "../types-taler-wallet.js"; export type TalerExchangeResultByMethod< prop extends keyof TalerExchangeHttpClient, @@ -46,12 +71,11 @@ export enum TalerExchangeCacheEviction { CREATE_DESCISION, } - /** */ export class TalerExchangeHttpClient { httpLib: HttpRequestLibrary; - public readonly PROTOCOL_VERSION = "18:0:1"; + public readonly PROTOCOL_VERSION = "20:0:0"; cacheEvictor: CacheEvictor<TalerExchangeCacheEviction>; constructor( @@ -92,14 +116,47 @@ export class TalerExchangeHttpClient { * https://docs.taler.net/core/api-exchange.html#get--config * */ - async getConfig() { + async getConfig(): Promise< + | OperationFail<HttpStatusCode.NotFound> + | OperationOk<ExchangeVersionResponse> + > { 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, codecForExchangeConfig()); + case HttpStatusCode.Ok: { + const minBody = await readSuccessResponseJsonOrThrow( + resp, + codecForTalerCommonConfigResponse(), + ); + const expectedName = "taler-exchange"; + if (minBody.name !== expectedName) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`, + }); + } + if (!this.isCompatible(minBody.version)) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unsupported protocol version, client supports ${this.PROTOCOL_VERSION}, server supports ${minBody.version}`, + }); + } + // Now that we've checked the basic body, re-parse the full response. + const body = await readSuccessResponseJsonOrThrow( + resp, + codecForExchangeConfig(), + ); + return { + type: "ok", + body, + }; + } case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: @@ -127,6 +184,166 @@ export class TalerExchangeHttpClient { // TERMS // + // KYC operations + // + + /** + * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet + * + */ + async notifyKycBalanceLimit(account: ReserveAccount, balance: AmountString) { + const url = new URL(`kyc-wallet`, this.baseUrl); + + const body: WalletKycRequest = { + balance, + reserve_pub: account.id, + reserve_sig: encodeCrock(account.signingKey), + } + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAmlWalletKycCheckResponse()); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.Forbidden: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.UnavailableForLegalReasons: + return opKnownAlternativeFailure(resp, resp.status, codecForLegitimizationNeededResponse()); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--kyc-wallet + * + */ + async checkKycStatus(account: ReserveAccount, requirementId: number, params: { + timeout?: number, + } = {}) { + const url = new URL(`kyc-check/${String(requirementId)}`, this.baseUrl); + + if (params.timeout !== undefined) { + url.searchParams.set("timeout_ms", String(params.timeout)); + } + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + "Account-Owner-Signature": buildKYCQuerySignature(account.signingKey), + }, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAccountKycStatus()); + case HttpStatusCode.Accepted: + return opKnownAlternativeFailure(resp, resp.status, codecForAccountKycStatus()); + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.Forbidden: + 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-exchange.html#get--kyc-info-$ACCESS_TOKEN + * + */ + async checkKycInfo(token: AccessToken, known: KycRequirementInformationId[], params: { + timeout?: number, + } = {}) { + const url = new URL(`kyc-info/${token}`, this.baseUrl); + + if (params.timeout !== undefined) { + url.searchParams.set("timeout_ms", String(params.timeout)); + } + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + "If-None-Match": known.length ? known.join(",") : undefined + } + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForKycProcessClientInformation()); + case HttpStatusCode.NoContent: + return opKnownAlternativeFailure(resp, HttpStatusCode.NoContent, codecForEmptyObject()); + case HttpStatusCode.NotModified: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + + /** + * https://docs.taler.net/core/api-exchange.html#post--kyc-upload-$ID + * + */ + async uploadKycForm(requirement: KycRequirementInformationId, body: object) { + const url = new URL(`kyc-upload/${requirement}`, this.baseUrl); + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + }); + + switch (resp.status) { + case HttpStatusCode.NoContent: + return opEmptySuccess(resp); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.PayloadTooLarge: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#post--kyc-start-$ID + * + */ + async startKycProcess(requirement: KycRequirementInformationId, body: object = {}) { + const url = new URL(`kyc-start/${requirement}`, this.baseUrl); + + + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForKycProcessStartInformation()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Conflict: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.PayloadTooLarge: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + // // AML operations // @@ -134,34 +351,206 @@ export class TalerExchangeHttpClient { * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions-$STATE * */ - async getDecisionsByState( - auth: OfficerAccount, - state: TalerExchangeApi.AmlState, - pagination?: PaginationParams, - ) { - const url = new URL( - `aml/${auth.id}/decisions/${TalerExchangeApi.AmlState[state]}`, - this.baseUrl, - ); - addPaginationParams(url, pagination); + // async getDecisionsByState( + // auth: OfficerAccount, + // state: TalerExchangeApi.AmlState, + // pagination?: PaginationParams, + // ) { + // const url = new URL( + // `aml/${auth.id}/decisions/${TalerExchangeApi.AmlState[state]}`, + // this.baseUrl, + // ); + // addPaginationParams(url, pagination); + + // const resp = await this.httpLib.fetch(url.href, { + // method: "GET", + // headers: { + // "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), + // }, + // }); + + // switch (resp.status) { + // case HttpStatusCode.Ok: + // return opSuccessFromHttp(resp, codecForAmlRecords()); + // case HttpStatusCode.NoContent: + // return opFixedSuccess({ records: [] }); + // //this should be unauthorized + // case HttpStatusCode.Forbidden: + // return opKnownHttpFailure(resp.status, resp); + // case HttpStatusCode.Unauthorized: + // 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-exchange.html#get--aml-$OFFICER_PUB-decision-$H_PAYTO + // * + // */ + // async getDecisionDetails(auth: OfficerAccount, account: string) { + // const url = new URL(`aml/${auth.id}/decision/${account}`, this.baseUrl); + + // const resp = await this.httpLib.fetch(url.href, { + // method: "GET", + // headers: { + // "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), + // }, + // }); + + // switch (resp.status) { + // case HttpStatusCode.Ok: + // return opSuccessFromHttp(resp, codecForAmlDecisionDetails()); + // case HttpStatusCode.NoContent: + // return opFixedSuccess({ aml_history: [], kyc_attributes: [] }); + // //this should be unauthorized + // case HttpStatusCode.Forbidden: + // return opKnownHttpFailure(resp.status, resp); + // case HttpStatusCode.Unauthorized: + // 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-exchange.html#post--aml-$OFFICER_PUB-decision + // * + // */ + // async addDecisionDetails( + // auth: OfficerAccount, + // decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">, + // ) { + // const url = new URL(`aml/${auth.id}/decision`, this.baseUrl); + + // const body = buildDecisionSignature(auth.signingKey, decision); + // const resp = await this.httpLib.fetch(url.href, { + // method: "POST", + // body, + // }); + + // switch (resp.status) { + // case HttpStatusCode.NoContent: + // return opEmptySuccess(resp); + // //FIXME: this should be unauthorized + // case HttpStatusCode.Forbidden: + // return opKnownHttpFailure(resp.status, resp); + // case HttpStatusCode.Unauthorized: + // return opKnownHttpFailure(resp.status, resp); + // //FIXME: this two need to be split by error code + // 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-exchange.html#get--aml-$OFFICER_PUB-measures + * + */ + async getAmlMesasures(auth: OfficerAccount) { + const url = new URL(`aml/${auth.id}/measures`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), + "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey), }, }); switch (resp.status) { case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForAmlRecords()); + return opSuccessFromHttp(resp, codecForAvailableMeasureSummary()); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-measures + * + */ + async getAmlKycStatistics(auth: OfficerAccount, name: string, filter: { + since?: Date + until?: Date + } = {}) { + const url = new URL(`aml/${auth.id}/kyc-statistics/${name}`, this.baseUrl); + + if (filter.since !== undefined) { + url.searchParams.set( + "start_date", + String(filter.since.getTime()) + ); + } + if (filter.until !== undefined) { + url.searchParams.set( + "end_date", + String(filter.until.getTime()) + ); + } + + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey), + }, + }); + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForEventCounter()); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decisions + * + */ + async getAmlDecisions(auth: OfficerAccount, params: PaginationParams & { + account?: string, + active?: boolean, + investigation?: boolean, + } = {}) { + const url = new URL(`aml/${auth.id}/decisions`, this.baseUrl); + + addMerchantPaginationParams(url, params); + if (params.account !== undefined) { + url.searchParams.set("h_payto", params.account); + } + if (params.active !== undefined) { + url.searchParams.set("active", params.active ? "YES" : "NO"); + } + if (params.investigation !== undefined) { + url.searchParams.set("investigation", params.investigation ? "YES" : "NO"); + } + + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers: { + "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey), + }, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForAmlDecisionsResponse()); case HttpStatusCode.NoContent: return opFixedSuccess({ records: [] }); - //this should be unauthorized case HttpStatusCode.Forbidden: return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.Unauthorized: - return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: @@ -172,29 +561,27 @@ export class TalerExchangeHttpClient { } /** - * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-decision-$H_PAYTO + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO * */ - async getDecisionDetails(auth: OfficerAccount, account: string) { - const url = new URL(`aml/${auth.id}/decision/${account}`, this.baseUrl); + async getAmlAttributesForAccount(auth: OfficerAccount, account: string, params: PaginationParams = {}) { + const url = new URL(`aml/${auth.id}/attributes/${account}`, this.baseUrl); + addMerchantPaginationParams(url, params); const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { - "Taler-AML-Officer-Signature": buildQuerySignature(auth.signingKey), + "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey), }, }); switch (resp.status) { case HttpStatusCode.Ok: - return opSuccessFromHttp(resp, codecForAmlDecisionDetails()); + return opSuccessFromHttp(resp, codecForAmlKycAttributes()); case HttpStatusCode.NoContent: - return opFixedSuccess({ aml_history: [], kyc_attributes: [] }); - //this should be unauthorized + return opFixedSuccess({ details: [] }); case HttpStatusCode.Forbidden: return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.Unauthorized: - return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: @@ -204,31 +591,28 @@ export class TalerExchangeHttpClient { } } + /** - * https://docs.taler.net/core/api-exchange.html#post--aml-$OFFICER_PUB-decision + * https://docs.taler.net/core/api-exchange.html#get--aml-$OFFICER_PUB-attributes-$H_PAYTO * */ - async addDecisionDetails( - auth: OfficerAccount, - decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">, - ) { + async makeAmlDesicion(auth: OfficerAccount, decision: Omit<AmlDecisionRequest, "officer_sig">) { const url = new URL(`aml/${auth.id}/decision`, this.baseUrl); - const body = buildDecisionSignature(auth.signingKey, decision); + const body = buildAMLDecisionSignature(auth.signingKey, decision); const resp = await this.httpLib.fetch(url.href, { method: "POST", + headers: { + "Taler-AML-Officer-Signature": buildAMLQuerySignature(auth.signingKey), + }, body, }); switch (resp.status) { case HttpStatusCode.NoContent: return opEmptySuccess(resp); - //FIXME: this should be unauthorized case HttpStatusCode.Forbidden: return opKnownHttpFailure(resp.status, resp); - case HttpStatusCode.Unauthorized: - return opKnownHttpFailure(resp.status, resp); - //FIXME: this two need to be split by error code case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Conflict: @@ -237,30 +621,39 @@ export class TalerExchangeHttpClient { return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } } + +} + +function buildKYCQuerySignature(key: SigningKey): string { + const sigBlob = buildSigPS( + TalerSignaturePurpose.AML_QUERY, + ).build(); + + return encodeCrock(eddsaSign(sigBlob, key)); } -function buildQuerySignature(key: SigningKey): string { +function buildAMLQuerySignature(key: SigningKey): string { const sigBlob = buildSigPS( - TalerSignaturePurpose.TALER_SIGNATURE_AML_QUERY, + TalerSignaturePurpose.AML_QUERY, ).build(); return encodeCrock(eddsaSign(sigBlob, key)); } -function buildDecisionSignature( +function buildAMLDecisionSignature( key: SigningKey, - decision: Omit<TalerExchangeApi.AmlDecision, "officer_sig">, -): TalerExchangeApi.AmlDecision { + decision: Omit<AmlDecisionRequest, "officer_sig">, +): AmlDecisionRequest { const zero = new Uint8Array(new ArrayBuffer(64)); - const sigBlob = buildSigPS(TalerSignaturePurpose.TALER_SIGNATURE_AML_DECISION) + const sigBlob = buildSigPS(TalerSignaturePurpose.AML_DECISION) //TODO: new need the null terminator, also in the exchange .put(hash(stringToBytes(decision.justification))) //check null .put(timestampRoundedToBuffer(decision.decision_time)) - .put(amountToBuffer(decision.new_threshold)) + // .put(amountToBuffer(decision.new_threshold)) .put(decodeCrock(decision.h_payto)) .put(zero) //kyc_requirement - .put(bufferForUint32(decision.new_state)) + // .put(bufferForUint32(decision.new_state)) .build(); const officer_sig = encodeCrock(eddsaSign(sigBlob, key)); diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts index 10afdc8eb..e765d286b 100644 --- a/packages/taler-util/src/http-client/merchant.ts +++ b/packages/taler-util/src/http-client/merchant.ts @@ -19,19 +19,24 @@ import { FailCasesByMethod, HttpStatusCode, LibtoolVersion, + OperationFail, + OperationOk, PaginationParams, ResultByMethod, + TalerError, + TalerErrorCode, TalerMerchantApi, + TalerMerchantConfigResponse, codecForAbortResponse, codecForAccountAddResponse, codecForAccountKycRedirects, codecForAccountsSummaryResponse, codecForBankAccountDetail, - codecForBankAccountEntry, + codecForCategoryListResponse, + codecForCategoryProductList, codecForClaimResponse, codecForInstancesResponse, codecForInventorySummaryResponse, - codecForMerchantConfig, codecForMerchantOrderPrivateStatusResponse, codecForMerchantPosProductDetail, codecForMerchantRefundResponse, @@ -47,6 +52,8 @@ import { codecForStatusGoto, codecForStatusPaid, codecForStatusStatusUnpaid, + codecForTalerCommonConfigResponse, + codecForTalerMerchantConfigResponse, codecForTansferList, codecForTemplateDetails, codecForTemplateSummaryResponse, @@ -64,6 +71,7 @@ import { HttpRequestLibrary, HttpResponse, createPlatformHttpLib, + readSuccessResponseJsonOrThrow, readTalerErrorResponse, } from "@gnu-taler/taler-util/http"; import { opSuccessFromHttp, opUnknownFailure } from "../operation.js"; @@ -93,6 +101,9 @@ export enum TalerMerchantInstanceCacheEviction { CREATE_PRODUCT, UPDATE_PRODUCT, DELETE_PRODUCT, + CREATE_CATEGORY, + UPDATE_CATEGORY, + DELETE_CATEGORY, CREATE_TRANSFER, DELETE_TRANSFER, CREATE_DEVICE, @@ -109,11 +120,13 @@ export enum TalerMerchantInstanceCacheEviction { DELETE_TOKENFAMILY, LAST, } + export enum TalerMerchantManagementCacheEviction { CREATE_INSTANCE = TalerMerchantInstanceCacheEviction.LAST + 1, UPDATE_INSTANCE, DELETE_INSTANCE, } + /** * Protocol version spoken with the core bank. * @@ -124,7 +137,7 @@ export enum TalerMerchantManagementCacheEviction { * Uses libtool's current:revision:age versioning. */ export class TalerMerchantInstanceHttpClient { - public readonly PROTOCOL_VERSION = "15:0:0"; + public readonly PROTOCOL_VERSION = "16:0:0"; readonly httpLib: HttpRequestLibrary; readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>; @@ -145,17 +158,48 @@ export class TalerMerchantInstanceHttpClient { /** * https://docs.taler.net/core/api-merchant.html#get--config - * */ - async getConfig() { + async getConfig(): Promise< + | OperationFail<HttpStatusCode.NotFound> + | OperationOk<TalerMerchantConfigResponse> + > { 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, codecForMerchantConfig()); + case HttpStatusCode.Ok: { + const minBody = await readSuccessResponseJsonOrThrow( + resp, + codecForTalerCommonConfigResponse(), + ); + const expectedName = "taler-merchant"; + if (minBody.name !== expectedName) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unexpected server component name (got ${minBody.name}, expected ${expectedName}})`, + }); + } + if (!this.isCompatible(minBody.version)) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION, + requestUrl: resp.requestUrl, + httpStatusCode: resp.status, + detail: `Unsupported protocol version, client supports ${this.PROTOCOL_VERSION}, server supports ${minBody.version}`, + }); + } + // Now that we've checked the basic body, re-parse the full response. + const body = await readSuccessResponseJsonOrThrow( + resp, + codecForTalerMerchantConfigResponse(), + ); + return { + type: "ok", + body, + }; + } case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); default: @@ -467,7 +511,7 @@ export class TalerMerchantInstanceHttpClient { * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private * */ - async getCurrentInstanceDetails(token: AccessToken) { + async getCurrentInstanceDetails(token: AccessToken | undefined) { const url = new URL(`private`, this.baseUrl); const headers: Record<string, string> = {}; @@ -662,7 +706,10 @@ export class TalerMerchantInstanceHttpClient { /** * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-accounts */ - async listBankAccounts(token: AccessToken, params?: PaginationParams) { + async listBankAccounts( + token: AccessToken | undefined, + params?: PaginationParams, + ) { const url = new URL(`private/accounts`, this.baseUrl); // addMerchantPaginationParams(url, params); @@ -754,6 +801,173 @@ export class TalerMerchantInstanceHttpClient { // /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-categories + */ + async listCategories( + token: AccessToken | undefined, + params?: PaginationParams, + ) { + const url = new URL(`private/categories`, this.baseUrl); + + // addMerchantPaginationParams(url, params); + + const headers: Record<string, string> = {}; + if (token) { + headers.Authorization = makeBearerTokenAuthHeader(token); + } + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForCategoryListResponse()); + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.Unauthorized: // FIXME: missing in docs + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + } + } + + /** + * https://docs.taler.net/core/api-merchant.html#get-[-instances-$INSTANCE]-private-categories-$CATEGORY_ID + */ + async getCategoryDetails(token: AccessToken | undefined, cId: string) { + const url = new URL(`private/categories/${cId}`, this.baseUrl); + + const headers: Record<string, string> = {}; + if (token) { + headers.Authorization = makeBearerTokenAuthHeader(token); + } + const resp = await this.httpLib.fetch(url.href, { + method: "GET", + headers, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForCategoryProductList()); + case HttpStatusCode.Unauthorized: // FIXME: missing in docs + 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-merchant.html#post-[-instances-$INSTANCE]-private-categories + */ + async addCategory( + token: AccessToken | undefined, + body: TalerMerchantApi.CategoryCreateRequest, + ) { + const url = new URL(`private/categories`, this.baseUrl); + + const headers: Record<string, string> = {}; + if (token) { + headers.Authorization = makeBearerTokenAuthHeader(token); + } + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + headers, + }); + + switch (resp.status) { + case HttpStatusCode.Ok: { + this.cacheEvictor.notifySuccess( + TalerMerchantInstanceCacheEviction.CREATE_CATEGORY, + ); + return opEmptySuccess(resp); + } + case HttpStatusCode.Unauthorized: // FIXME: missing in docs + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: // FIXME: missing in docs + 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-merchant.html#post-[-instances-$INSTANCE]-private-categories + */ + async updateCategory( + token: AccessToken | undefined, + cid: string, + body: TalerMerchantApi.CategoryCreateRequest, + ) { + const url = new URL(`private/categories/${cid}`, this.baseUrl); + + const headers: Record<string, string> = {}; + if (token) { + headers.Authorization = makeBearerTokenAuthHeader(token); + } + const resp = await this.httpLib.fetch(url.href, { + method: "PATCH", + body, + headers, + }); + + switch (resp.status) { + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess( + TalerMerchantInstanceCacheEviction.UPDATE_CATEGORY, + ); + return opEmptySuccess(resp); + } + case HttpStatusCode.Unauthorized: // FIXME: missing in docs + return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.NotFound: // FIXME: missing in docs + 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-merchant.html#delete-[-instances-$INSTANCE]-private-categories-$CATEGORY_ID + */ + async deleteCategory(token: AccessToken | undefined, cId: string) { + const url = new URL(`private/categories/${cId}`, this.baseUrl); + + const headers: Record<string, string> = {}; + if (token) { + headers.Authorization = makeBearerTokenAuthHeader(token); + } + const resp = await this.httpLib.fetch(url.href, { + method: "DELETE", + headers, + }); + + switch (resp.status) { + case HttpStatusCode.NoContent: { + this.cacheEvictor.notifySuccess( + TalerMerchantInstanceCacheEviction.DELETE_CATEGORY, + ); + return opEmptySuccess(resp); + } + case HttpStatusCode.Unauthorized: // FIXME: missing in docs + 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-merchant.html#post-[-instances-$INSTANCE]-private-products */ async addProduct( @@ -913,7 +1127,7 @@ export class TalerMerchantInstanceHttpClient { } /** - * https://docs.taler.net/core/api-merchant.html#reserving-inventory + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-products-$PRODUCT_ID-lock */ async lockProduct( token: AccessToken | undefined, @@ -951,7 +1165,7 @@ export class TalerMerchantInstanceHttpClient { } /** - * https://docs.taler.net/core/api-merchant.html#removing-products-from-inventory + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-products-$PRODUCT_ID */ async deleteProduct(token: AccessToken | undefined, productId: string) { const url = new URL(`private/products/${productId}`, this.baseUrl); diff --git a/packages/taler-util/src/http-client/officer-account.ts b/packages/taler-util/src/http-client/officer-account.ts index 2c1426be2..612fd815e 100644 --- a/packages/taler-util/src/http-client/officer-account.ts +++ b/packages/taler-util/src/http-client/officer-account.ts @@ -17,8 +17,11 @@ import { EncryptionNonce, LockedAccount, + LockedReserve, OfficerAccount, OfficerId, + ReserveAccount, + ReserveId, SigningKey, createEddsaKeyPair, decodeCrock, @@ -50,8 +53,8 @@ export async function unlockOfficerAccount( rawKey, rawPassword, password, - ).catch((e: Error) => { - throw new UnwrapKeyError(e.message); + ).catch((e) => { + throw new UnwrapKeyError(e instanceof Error ? e.message : String(e)); })) as SigningKey; const publicKey = eddsaGetPublic(signingKey); @@ -96,6 +99,71 @@ export async function createNewOfficerAccount( return { id: accountId, signingKey, safe }; } +/** + * Restore previous session and unlock account with password + * + * @param salt string from which crypto params will be derived + * @param key secured private key + * @param password password for the private key + * @returns + */ +export async function unlockWalletKycAccount( + account: LockedReserve, + password: string, +): Promise<ReserveAccount> { + const rawKey = decodeCrock(account); + const rawPassword = stringToBytes(password); + + const signingKey = (await decryptWithDerivedKey( + rawKey, + rawPassword, + password, + ).catch((e) => { + throw new UnwrapKeyError(e instanceof Error ? e.message : String(e)); + })) as SigningKey; + + const publicKey = eddsaGetPublic(signingKey); + + const accountId = encodeCrock(publicKey) as ReserveId; + + return { id: accountId, signingKey }; +} + +/** + * Create new account (secured private key) + * secured with the given password + * + * @param extraNonce + * @param password + * @returns + */ +export async function createNewWalletKycAccount( + extraNonce: EncryptionNonce, + password: string, +): Promise<OfficerAccount & { safe: LockedAccount }> { + const { eddsaPriv, eddsaPub } = createEddsaKeyPair(); + + const key = stringToBytes(password); + + const localRnd = getRandomBytesF(24); + const mergedRnd: EncryptionNonce = extraNonce + ? kdf(24, stringToBytes("aml-officer"), extraNonce, localRnd) + : localRnd; + + const protectedPrivKey = await encryptWithDerivedKey( + mergedRnd, + key, + eddsaPriv, + password, + ); + + const signingKey = eddsaPriv as SigningKey; + const accountId = encodeCrock(eddsaPub) as OfficerId; + const safe = encodeCrock(protectedPrivKey) as LockedAccount; + + return { id: accountId, signingKey, safe }; +} + export class UnwrapKeyError extends Error { public cause: string; constructor(cause: string) { diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts deleted file mode 100644 index 3816b1598..000000000 --- a/packages/taler-util/src/http-client/types.ts +++ /dev/null @@ -1,5548 +0,0 @@ -import { codecForAmountString } from "../amounts.js"; -import { - Codec, - buildCodecForObject, - buildCodecForUnion, - codecForAny, - codecForBoolean, - codecForConstNumber, - codecForConstString, - codecForEither, - codecForList, - codecForMap, - codecForNumber, - codecForString, - codecOptional, - codecOptionalDefault, -} from "../codec.js"; -import { PaytoString, codecForPaytoString } from "../payto.js"; -import { - AmountString, - ExchangeWireAccount, - InternationalizedString, - codecForExchangeWireAccount, - codecForInternationalizedString, - codecForLocation, -} from "../taler-types.js"; -import { TalerUriString, codecForTalerUriString } from "../taleruri.js"; -import { - AbsoluteTime, - TalerProtocolDuration, - TalerProtocolTimestamp, - codecForDuration, - codecForTimestamp, -} from "../time.js"; - -export type UserAndPassword = { - username: string; - password: string; -}; - -export type UserAndToken = { - username: string; - token: AccessToken; -}; - -declare const opaque_OfficerAccount: unique symbol; -export type LockedAccount = string & { [opaque_OfficerAccount]: true }; - -declare const opaque_OfficerId: unique symbol; -export type OfficerId = string & { [opaque_OfficerId]: true }; - -declare const opaque_OfficerSigningKey: unique symbol; -export type SigningKey = Uint8Array & { [opaque_OfficerSigningKey]: true }; - -export interface OfficerAccount { - id: OfficerId; - signingKey: SigningKey; -} - -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; - /** - * order - */ - order?: "asc" | "dec"; -}; - -export type LongPollParams = { - /** - * milliseconds the server should wait for at least one result to be shown - */ - timeoutMs?: number; -}; -/// -/// HASH -/// - -// 64-byte hash code. -type HashCode = string; - -type PaytoHash = string; - -type AmlOfficerPublicKeyP = 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; - -type WadId = string; - -type Timestamp = TalerProtocolTimestamp; - -type RelativeTime = TalerProtocolDuration; - -export interface LoginToken { - token: AccessToken; - expiration: Timestamp; -} - -declare const __ac_token: unique symbol; -/** - * Use `createAccessToken(string)` function to build one. - */ -export type AccessToken = string & { - [__ac_token]: true; -}; - -/** - * Create a rfc8959 access token. - * Adds secret-token: prefix if there is none. - * Encode the token with rfc7230 to send in a http header. - * - * @param token - * @returns - */ -export function createRFC8959AccessTokenEncoded(token: string): AccessToken { - return ( - token.startsWith("secret-token:") - ? token - : `secret-token:${encodeURIComponent(token)}` - ) as AccessToken; -} - -/** - * Create a rfc8959 access token. - * Adds secret-token: prefix if there is none. - * - * @param token - * @returns - */ -export function createRFC8959AccessTokenPlain(token: string): AccessToken { - return ( - token.startsWith("secret-token:") ? token : `secret-token:${token}` - ) as AccessToken; -} - -/** - * Convert string to access token. - * - * @param clientSecret - * @returns - */ -export function createClientSecretAccessToken( - clientSecret: string, -): AccessToken { - return clientSecret as AccessToken; -} - -declare const __officer_signature: unique symbol; -export type OfficerSignature = string & { - [__officer_signature]: 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 TokenSuccessResponseMerchant { - // 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. - token: AccessToken; - } -} - -// DD51 https://docs.taler.net/design-documents/051-fractional-digits.html -export interface CurrencySpecification { - // Name of the currency. - name: 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; - - // 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 }; -} - -//FIXME: implement this codec -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 codecForTokenSuccessResponseMerchant = - (): Codec<TalerAuthentication.TokenSuccessResponseMerchant> => - buildCodecForObject<TalerAuthentication.TokenSuccessResponseMerchant>() - .property("token", codecForAccessToken()) - .property("expiration", codecForTimestamp) - .build("TalerAuthentication.TokenSuccessResponseMerchant"); - -export const codecForCurrencySpecificiation = - (): Codec<CurrencySpecification> => - buildCodecForObject<CurrencySpecification>() - .property("name", codecForString()) - .property("num_fractional_input_digits", codecForNumber()) - .property("num_fractional_normal_digits", codecForNumber()) - .property("num_fractional_trailing_zero_digits", codecForNumber()) - .property("alt_unit_names", codecForMap(codecForString())) - .build("CurrencySpecification"); - -export const codecForIntegrationBankConfig = - (): Codec<TalerCorebankApi.IntegrationConfig> => - buildCodecForObject<TalerCorebankApi.IntegrationConfig>() - .property("name", codecForConstString("taler-bank-integration")) - .property("version", codecForString()) - .property("currency", codecForString()) - .property("currency_specification", codecForCurrencySpecificiation()) - .build("TalerCorebankApi.IntegrationConfig"); - -export const codecForCoreBankConfig = (): Codec<TalerCorebankApi.Config> => - buildCodecForObject<TalerCorebankApi.Config>() - .property("name", codecForConstString("libeufin-bank")) - .property("version", codecForString()) - .property("bank_name", codecForString()) - .property("base_url", codecOptional(codecForString())) - .property("allow_conversion", codecForBoolean()) - .property("allow_registrations", codecForBoolean()) - .property("allow_deletions", codecForBoolean()) - .property("allow_edit_name", codecForBoolean()) - .property("allow_edit_cashout_payto_uri", codecForBoolean()) - .property("default_debit_threshold", codecForAmountString()) - .property("currency", codecForString()) - .property("currency_specification", codecForCurrencySpecificiation()) - .property( - "supported_tan_channels", - codecForList( - codecForEither( - codecForConstString(TalerCorebankApi.TanChannel.SMS), - codecForConstString(TalerCorebankApi.TanChannel.EMAIL), - ), - ), - ) - .property("wire_type", codecOptionalDefault(codecForString(), "iban")) - .property("wire_transfer_fees", codecOptional(codecForAmountString())) - .build("TalerCorebankApi.Config"); - -//FIXME: implement this codec -export const codecForURN = codecForString; - -export const codecForExchangeConfigInfo = - (): Codec<TalerMerchantApi.ExchangeConfigInfo> => - buildCodecForObject<TalerMerchantApi.ExchangeConfigInfo>() - .property("base_url", codecForString()) - .property("currency", codecForString()) - .property("master_pub", codecForString()) - .build("TalerMerchantApi.ExchangeConfigInfo"); - -export const codecForMerchantConfig = - (): Codec<TalerMerchantApi.VersionResponse> => - buildCodecForObject<TalerMerchantApi.VersionResponse>() - .property("name", codecForConstString("taler-merchant")) - .property("currency", codecForString()) - .property("version", codecForString()) - .property("currencies", codecForMap(codecForCurrencySpecificiation())) - .property("exchanges", codecForList(codecForExchangeConfigInfo())) - .build("TalerMerchantApi.VersionResponse"); - -export const codecForClaimResponse = - (): Codec<TalerMerchantApi.ClaimResponse> => - buildCodecForObject<TalerMerchantApi.ClaimResponse>() - .property("contract_terms", codecForContractTerms()) - .property("sig", codecForString()) - .build("TalerMerchantApi.ClaimResponse"); - -export const codecForPaymentResponse = - (): Codec<TalerMerchantApi.PaymentResponse> => - buildCodecForObject<TalerMerchantApi.PaymentResponse>() - .property("pos_confirmation", codecOptional(codecForString())) - .property("sig", codecForString()) - .build("TalerMerchantApi.PaymentResponse"); - -export const codecForStatusPaid = (): Codec<TalerMerchantApi.StatusPaid> => - buildCodecForObject<TalerMerchantApi.StatusPaid>() - .property("refund_amount", codecForAmountString()) - .property("refund_pending", codecForBoolean()) - .property("refund_taken", codecForAmountString()) - .property("refunded", codecForBoolean()) - .property("type", codecForConstString("paid")) - .build("TalerMerchantApi.StatusPaid"); - -export const codecForStatusGoto = - (): Codec<TalerMerchantApi.StatusGotoResponse> => - buildCodecForObject<TalerMerchantApi.StatusGotoResponse>() - .property("public_reorder_url", codecForURL()) - .property("type", codecForConstString("goto")) - .build("TalerMerchantApi.StatusGotoResponse"); - -export const codecForStatusStatusUnpaid = - (): Codec<TalerMerchantApi.StatusUnpaidResponse> => - buildCodecForObject<TalerMerchantApi.StatusUnpaidResponse>() - .property("type", codecForConstString("unpaid")) - .property("already_paid_order_id", codecOptional(codecForString())) - .property("fulfillment_url", codecOptional(codecForString())) - .property("taler_pay_uri", codecForTalerUriString()) - .build("TalerMerchantApi.PaymentResponse"); - -export const codecForPaidRefundStatusResponse = - (): Codec<TalerMerchantApi.PaidRefundStatusResponse> => - buildCodecForObject<TalerMerchantApi.PaidRefundStatusResponse>() - .property("pos_confirmation", codecOptional(codecForString())) - .property("refunded", codecForBoolean()) - .build("TalerMerchantApi.PaidRefundStatusResponse"); - -export const codecForMerchantAbortPayRefundSuccessStatus = - (): Codec<TalerMerchantApi.MerchantAbortPayRefundSuccessStatus> => - buildCodecForObject<TalerMerchantApi.MerchantAbortPayRefundSuccessStatus>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("exchange_status", codecForConstNumber(200)) - .property("type", codecForConstString("success")) - .build("TalerMerchantApi.MerchantAbortPayRefundSuccessStatus"); - -export const codecForMerchantAbortPayRefundFailureStatus = - (): Codec<TalerMerchantApi.MerchantAbortPayRefundFailureStatus> => - buildCodecForObject<TalerMerchantApi.MerchantAbortPayRefundFailureStatus>() - .property("exchange_code", codecForNumber()) - .property("exchange_reply", codecForAny()) - .property("exchange_status", codecForNumber()) - .property("type", codecForConstString("failure")) - .build("TalerMerchantApi.MerchantAbortPayRefundFailureStatus"); - -export const codecForMerchantAbortPayRefundStatus = - (): Codec<TalerMerchantApi.MerchantAbortPayRefundStatus> => - buildCodecForUnion<TalerMerchantApi.MerchantAbortPayRefundStatus>() - .discriminateOn("type") - .alternative("success", codecForMerchantAbortPayRefundSuccessStatus()) - .alternative("failure", codecForMerchantAbortPayRefundFailureStatus()) - .build("TalerMerchantApi.MerchantAbortPayRefundStatus"); - -export const codecForAbortResponse = - (): Codec<TalerMerchantApi.AbortResponse> => - buildCodecForObject<TalerMerchantApi.AbortResponse>() - .property("refunds", codecForList(codecForMerchantAbortPayRefundStatus())) - .build("TalerMerchantApi.AbortResponse"); - -export const codecForWalletRefundResponse = - (): Codec<TalerMerchantApi.WalletRefundResponse> => - buildCodecForObject<TalerMerchantApi.WalletRefundResponse>() - .property("merchant_pub", codecForString()) - .property("refund_amount", codecForAmountString()) - .property("refunds", codecForList(codecForMerchantCoinRefundStatus())) - .build("TalerMerchantApi.AbortResponse"); - -export const codecForMerchantCoinRefundSuccessStatus = - (): Codec<TalerMerchantApi.MerchantCoinRefundSuccessStatus> => - buildCodecForObject<TalerMerchantApi.MerchantCoinRefundSuccessStatus>() - .property("type", codecForConstString("success")) - .property("coin_pub", codecForString()) - .property("exchange_status", codecForConstNumber(200)) - .property("exchange_sig", codecForString()) - .property("rtransaction_id", codecForNumber()) - .property("refund_amount", codecForAmountString()) - .property("exchange_pub", codecForString()) - .property("execution_time", codecForTimestamp) - .build("TalerMerchantApi.MerchantCoinRefundSuccessStatus"); - -export const codecForMerchantCoinRefundFailureStatus = - (): Codec<TalerMerchantApi.MerchantCoinRefundFailureStatus> => - buildCodecForObject<TalerMerchantApi.MerchantCoinRefundFailureStatus>() - .property("type", codecForConstString("failure")) - .property("coin_pub", codecForString()) - .property("exchange_status", codecForNumber()) - .property("rtransaction_id", codecForNumber()) - .property("refund_amount", codecForAmountString()) - .property("exchange_code", codecOptional(codecForNumber())) - .property("exchange_reply", codecOptional(codecForAny())) - .property("execution_time", codecForTimestamp) - .build("TalerMerchantApi.MerchantCoinRefundFailureStatus"); - -export const codecForMerchantCoinRefundStatus = - (): Codec<TalerMerchantApi.MerchantCoinRefundStatus> => - buildCodecForUnion<TalerMerchantApi.MerchantCoinRefundStatus>() - .discriminateOn("type") - .alternative("success", codecForMerchantCoinRefundSuccessStatus()) - .alternative("failure", codecForMerchantCoinRefundFailureStatus()) - .build("TalerMerchantApi.MerchantCoinRefundStatus"); - -export const codecForQueryInstancesResponse = - (): Codec<TalerMerchantApi.QueryInstancesResponse> => - buildCodecForObject<TalerMerchantApi.QueryInstancesResponse>() - .property("name", codecForString()) - .property("user_type", codecForString()) - .property("email", codecOptional(codecForString())) - .property("website", codecOptional(codecForString())) - .property("logo", codecOptional(codecForString())) - .property("merchant_pub", codecForString()) - .property("address", codecForLocation()) - .property("jurisdiction", codecForLocation()) - .property("use_stefan", codecForBoolean()) - .property("default_wire_transfer_delay", codecForDuration) - .property("default_pay_delay", codecForDuration) - .property( - "auth", - buildCodecForObject<{ - method: "external" | "token"; - }>() - .property( - "method", - codecForEither( - codecForConstString("token"), - codecForConstString("external"), - ), - ) - .build("TalerMerchantApi.QueryInstancesResponse.auth"), - ) - .build("TalerMerchantApi.QueryInstancesResponse"); - -export const codecForAccountKycRedirects = - (): Codec<TalerMerchantApi.AccountKycRedirects> => - buildCodecForObject<TalerMerchantApi.AccountKycRedirects>() - .property( - "pending_kycs", - codecForList(codecForMerchantAccountKycRedirect()), - ) - .property("timeout_kycs", codecForList(codecForExchangeKycTimeout())) - - .build("TalerMerchantApi.AccountKycRedirects"); - -export const codecForMerchantAccountKycRedirect = - (): Codec<TalerMerchantApi.MerchantAccountKycRedirect> => - buildCodecForObject<TalerMerchantApi.MerchantAccountKycRedirect>() - .property("kyc_url", codecForURL()) - .property("aml_status", codecForNumber()) - .property("exchange_url", codecForURL()) - .property("payto_uri", codecForPaytoString()) - .build("TalerMerchantApi.MerchantAccountKycRedirect"); - -export const codecForExchangeKycTimeout = - (): Codec<TalerMerchantApi.ExchangeKycTimeout> => - buildCodecForObject<TalerMerchantApi.ExchangeKycTimeout>() - .property("exchange_url", codecForURL()) - .property("exchange_code", codecForNumber()) - .property("exchange_http_status", codecForNumber()) - .build("TalerMerchantApi.ExchangeKycTimeout"); - -export const codecForAccountAddResponse = - (): Codec<TalerMerchantApi.AccountAddResponse> => - buildCodecForObject<TalerMerchantApi.AccountAddResponse>() - .property("h_wire", codecForString()) - .property("salt", codecForString()) - .build("TalerMerchantApi.AccountAddResponse"); - -export const codecForAccountsSummaryResponse = - (): Codec<TalerMerchantApi.AccountsSummaryResponse> => - buildCodecForObject<TalerMerchantApi.AccountsSummaryResponse>() - .property("accounts", codecForList(codecForBankAccountEntry())) - .build("TalerMerchantApi.AccountsSummaryResponse"); - -export const codecForBankAccountEntry = - (): Codec<TalerMerchantApi.BankAccountEntry> => - buildCodecForObject<TalerMerchantApi.BankAccountEntry>() - .property("payto_uri", codecForPaytoString()) - .property("h_wire", codecForString()) - .property("active", codecOptional(codecForBoolean())) - .build("TalerMerchantApi.BankAccountEntry"); - -export const codecForBankAccountDetail = - (): Codec<TalerMerchantApi.BankAccountDetail> => - buildCodecForObject<TalerMerchantApi.BankAccountDetail>() - .property("payto_uri", codecForPaytoString()) - .property("h_wire", codecForString()) - .property("salt", codecForString()) - .property("credit_facade_url", codecOptional(codecForURL())) - .property("active", codecOptional(codecForBoolean())) - .build("TalerMerchantApi.BankAccountEntry"); - -export const codecForInventorySummaryResponse = - (): Codec<TalerMerchantApi.InventorySummaryResponse> => - buildCodecForObject<TalerMerchantApi.InventorySummaryResponse>() - .property("products", codecForList(codecForInventoryEntry())) - .build("TalerMerchantApi.InventorySummaryResponse"); - -export const codecForInventoryEntry = - (): Codec<TalerMerchantApi.InventoryEntry> => - buildCodecForObject<TalerMerchantApi.InventoryEntry>() - .property("product_id", codecForString()) - .property("product_serial", codecForNumber()) - .build("TalerMerchantApi.InventoryEntry"); - -export const codecForMerchantPosProductDetail = - (): Codec<TalerMerchantApi.MerchantPosProductDetail> => - buildCodecForObject<TalerMerchantApi.MerchantPosProductDetail>() - .property("product_serial", codecForNumber()) - .property("product_id", codecOptional(codecForString())) - .property("categories", codecForList(codecForNumber())) - .property("description", codecForString()) - .property("description_i18n", codecForInternationalizedString()) - .property("unit", codecForString()) - .property("price", codecForAmountString()) - .property("image", codecForString()) - .property("taxes", codecOptional(codecForList(codecForTax()))) - .property("total_stock", codecForNumber()) - .property("minimum_age", codecOptional(codecForNumber())) - .build("TalerMerchantApi.MerchantPosProductDetail"); - -export const codecForMerchantCategory = - (): Codec<TalerMerchantApi.MerchantCategory> => - buildCodecForObject<TalerMerchantApi.MerchantCategory>() - .property("id", codecForNumber()) - .property("name", codecForString()) - .property("name_i18n", codecForInternationalizedString()) - .build("TalerMerchantApi.MerchantCategory"); - -export const codecForFullInventoryDetailsResponse = - (): Codec<TalerMerchantApi.FullInventoryDetailsResponse> => - buildCodecForObject<TalerMerchantApi.FullInventoryDetailsResponse>() - .property("categories", codecForList(codecForMerchantCategory())) - .property("products", codecForList(codecForMerchantPosProductDetail())) - .build("TalerMerchantApi.FullInventoryDetailsResponse"); - -export const codecForProductDetail = - (): Codec<TalerMerchantApi.ProductDetail> => - buildCodecForObject<TalerMerchantApi.ProductDetail>() - .property("description", codecForString()) - .property("description_i18n", codecForInternationalizedString()) - .property("unit", codecForString()) - .property("price", codecForAmountString()) - .property("image", codecForString()) - .property("taxes", codecOptional(codecForList(codecForTax()))) - .property("address", codecOptional(codecForLocation())) - .property("next_restock", codecOptional(codecForTimestamp)) - .property("total_stock", codecForNumber()) - .property("total_sold", codecForNumber()) - .property("total_lost", codecForNumber()) - .property("minimum_age", codecOptional(codecForNumber())) - .build("TalerMerchantApi.ProductDetail"); - -export const codecForTax = (): Codec<TalerMerchantApi.Tax> => - buildCodecForObject<TalerMerchantApi.Tax>() - .property("name", codecForString()) - .property("tax", codecForAmountString()) - .build("TalerMerchantApi.Tax"); - -export const codecForPostOrderResponse = - (): Codec<TalerMerchantApi.PostOrderResponse> => - buildCodecForObject<TalerMerchantApi.PostOrderResponse>() - .property("order_id", codecForString()) - .property("token", codecOptional(codecForString())) - .build("TalerMerchantApi.PostOrderResponse"); - -export const codecForOutOfStockResponse = - (): Codec<TalerMerchantApi.OutOfStockResponse> => - buildCodecForObject<TalerMerchantApi.OutOfStockResponse>() - .property("product_id", codecForString()) - .property("available_quantity", codecForNumber()) - .property("requested_quantity", codecForNumber()) - .property("restock_expected", codecForTimestamp) - .build("TalerMerchantApi.OutOfStockResponse"); - -export const codecForOrderHistory = (): Codec<TalerMerchantApi.OrderHistory> => - buildCodecForObject<TalerMerchantApi.OrderHistory>() - .property("orders", codecForList(codecForOrderHistoryEntry())) - .build("TalerMerchantApi.OrderHistory"); - -export const codecForOrderHistoryEntry = - (): Codec<TalerMerchantApi.OrderHistoryEntry> => - buildCodecForObject<TalerMerchantApi.OrderHistoryEntry>() - .property("order_id", codecForString()) - .property("row_id", codecForNumber()) - .property("timestamp", codecForTimestamp) - .property("amount", codecForAmountString()) - .property("summary", codecForString()) - .property("refundable", codecForBoolean()) - .property("paid", codecForBoolean()) - .build("TalerMerchantApi.OrderHistoryEntry"); - -export const codecForMerchant = (): Codec<TalerMerchantApi.Merchant> => - buildCodecForObject<TalerMerchantApi.Merchant>() - .property("name", codecForString()) - .property("email", codecOptional(codecForString())) - .property("logo", codecOptional(codecForString())) - .property("website", codecOptional(codecForString())) - .property("address", codecOptional(codecForLocation())) - .property("jurisdiction", codecOptional(codecForLocation())) - .build("TalerMerchantApi.MerchantInfo"); - -export const codecForExchange = (): Codec<TalerMerchantApi.Exchange> => - buildCodecForObject<TalerMerchantApi.Exchange>() - .property("master_pub", codecForString()) - .property("priority", codecForNumber()) - .property("url", codecForString()) - .build("TalerMerchantApi.Exchange"); - -export const codecForContractTerms = - (): Codec<TalerMerchantApi.ContractTerms> => - buildCodecForObject<TalerMerchantApi.ContractTerms>() - .property("order_id", codecForString()) - .property("fulfillment_url", codecOptional(codecForString())) - .property("fulfillment_message", codecOptional(codecForString())) - .property( - "fulfillment_message_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("merchant_base_url", codecForString()) - .property("h_wire", codecForString()) - .property("auto_refund", codecOptional(codecForDuration)) - .property("wire_method", codecForString()) - .property("summary", codecForString()) - .property( - "summary_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("nonce", codecForString()) - .property("amount", codecForAmountString()) - .property("pay_deadline", codecForTimestamp) - .property("refund_deadline", codecForTimestamp) - .property("wire_transfer_deadline", codecForTimestamp) - .property("timestamp", codecForTimestamp) - .property("delivery_location", codecOptional(codecForLocation())) - .property("delivery_date", codecOptional(codecForTimestamp)) - .property("max_fee", codecForAmountString()) - .property("merchant", codecForMerchant()) - .property("merchant_pub", codecForString()) - .property("exchanges", codecForList(codecForExchange())) - .property("products", codecForList(codecForProduct())) - .property("extra", codecForAny()) - .build("TalerMerchantApi.ContractTerms"); - -export const codecForProduct = (): Codec<TalerMerchantApi.Product> => - buildCodecForObject<TalerMerchantApi.Product>() - .property("product_id", codecOptional(codecForString())) - .property("description", codecForString()) - .property( - "description_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("quantity", codecOptional(codecForNumber())) - .property("unit", codecOptional(codecForString())) - .property("price", codecOptional(codecForAmountString())) - .property("image", codecOptional(codecForString())) - .property("taxes", codecOptional(codecForList(codecForTax()))) - .property("delivery_date", codecOptional(codecForTimestamp)) - .build("TalerMerchantApi.Product"); - -export const codecForCheckPaymentPaidResponse = - (): Codec<TalerMerchantApi.CheckPaymentPaidResponse> => - buildCodecForObject<TalerMerchantApi.CheckPaymentPaidResponse>() - .property("order_status", codecForConstString("paid")) - .property("refunded", codecForBoolean()) - .property("refund_pending", codecForBoolean()) - .property("wired", codecForBoolean()) - .property("deposit_total", codecForAmountString()) - .property("exchange_code", codecForNumber()) - .property("exchange_http_status", codecForNumber()) - .property("refund_amount", codecForAmountString()) - .property("contract_terms", codecForContractTerms()) - .property("wire_reports", codecForList(codecForTransactionWireReport())) - .property("wire_details", codecForList(codecForTransactionWireTransfer())) - .property("refund_details", codecForList(codecForRefundDetails())) - .property("order_status_url", codecForURL()) - .build("TalerMerchantApi.CheckPaymentPaidResponse"); - -export const codecForCheckPaymentUnpaidResponse = - (): Codec<TalerMerchantApi.CheckPaymentUnpaidResponse> => - buildCodecForObject<TalerMerchantApi.CheckPaymentUnpaidResponse>() - .property("order_status", codecForConstString("unpaid")) - .property("taler_pay_uri", codecForTalerUriString()) - .property("creation_time", codecForTimestamp) - .property("summary", codecForString()) - .property("total_amount", codecForAmountString()) - .property("already_paid_order_id", codecOptional(codecForString())) - .property("already_paid_fulfillment_url", codecOptional(codecForString())) - .property("order_status_url", codecForString()) - .build("TalerMerchantApi.CheckPaymentPaidResponse"); - -export const codecForCheckPaymentClaimedResponse = - (): Codec<TalerMerchantApi.CheckPaymentClaimedResponse> => - buildCodecForObject<TalerMerchantApi.CheckPaymentClaimedResponse>() - .property("order_status", codecForConstString("claimed")) - .property("contract_terms", codecForContractTerms()) - .build("TalerMerchantApi.CheckPaymentClaimedResponse"); - -export const codecForMerchantOrderPrivateStatusResponse = - (): Codec<TalerMerchantApi.MerchantOrderStatusResponse> => - buildCodecForUnion<TalerMerchantApi.MerchantOrderStatusResponse>() - .discriminateOn("order_status") - .alternative("paid", codecForCheckPaymentPaidResponse()) - .alternative("unpaid", codecForCheckPaymentUnpaidResponse()) - .alternative("claimed", codecForCheckPaymentClaimedResponse()) - .build("TalerMerchantApi.MerchantOrderStatusResponse"); - -export const codecForRefundDetails = - (): Codec<TalerMerchantApi.RefundDetails> => - buildCodecForObject<TalerMerchantApi.RefundDetails>() - .property("reason", codecForString()) - .property("pending", codecForBoolean()) - .property("timestamp", codecForTimestamp) - .property("amount", codecForAmountString()) - .build("TalerMerchantApi.RefundDetails"); - -export const codecForTransactionWireTransfer = - (): Codec<TalerMerchantApi.TransactionWireTransfer> => - buildCodecForObject<TalerMerchantApi.TransactionWireTransfer>() - .property("exchange_url", codecForURL()) - .property("wtid", codecForString()) - .property("execution_time", codecForTimestamp) - .property("amount", codecForAmountString()) - .property("confirmed", codecForBoolean()) - .build("TalerMerchantApi.TransactionWireTransfer"); - -export const codecForTransactionWireReport = - (): Codec<TalerMerchantApi.TransactionWireReport> => - buildCodecForObject<TalerMerchantApi.TransactionWireReport>() - .property("code", codecForNumber()) - .property("hint", codecForString()) - .property("exchange_code", codecForNumber()) - .property("exchange_http_status", codecForNumber()) - .property("coin_pub", codecForString()) - .build("TalerMerchantApi.TransactionWireReport"); - -export const codecForMerchantRefundResponse = - (): Codec<TalerMerchantApi.MerchantRefundResponse> => - buildCodecForObject<TalerMerchantApi.MerchantRefundResponse>() - .property("taler_refund_uri", codecForTalerUriString()) - .property("h_contract", codecForString()) - .build("TalerMerchantApi.MerchantRefundResponse"); - -export const codecForTansferList = (): Codec<TalerMerchantApi.TransferList> => - buildCodecForObject<TalerMerchantApi.TransferList>() - .property("transfers", codecForList(codecForTransferDetails())) - .build("TalerMerchantApi.TransferList"); - -export const codecForTransferDetails = - (): Codec<TalerMerchantApi.TransferDetails> => - buildCodecForObject<TalerMerchantApi.TransferDetails>() - .property("credit_amount", codecForAmountString()) - .property("wtid", codecForString()) - .property("payto_uri", codecForPaytoString()) - .property("exchange_url", codecForURL()) - .property("transfer_serial_id", codecForNumber()) - .property("execution_time", codecOptional(codecForTimestamp)) - .property("verified", codecOptional(codecForBoolean())) - .property("confirmed", codecOptional(codecForBoolean())) - .build("TalerMerchantApi.TransferDetails"); - -export const codecForOtpDeviceSummaryResponse = - (): Codec<TalerMerchantApi.OtpDeviceSummaryResponse> => - buildCodecForObject<TalerMerchantApi.OtpDeviceSummaryResponse>() - .property("otp_devices", codecForList(codecForOtpDeviceEntry())) - .build("TalerMerchantApi.OtpDeviceSummaryResponse"); - -export const codecForOtpDeviceEntry = - (): Codec<TalerMerchantApi.OtpDeviceEntry> => - buildCodecForObject<TalerMerchantApi.OtpDeviceEntry>() - .property("otp_device_id", codecForString()) - .property("device_description", codecForString()) - .build("TalerMerchantApi.OtpDeviceEntry"); - -export const codecForOtpDeviceDetails = - (): Codec<TalerMerchantApi.OtpDeviceDetails> => - buildCodecForObject<TalerMerchantApi.OtpDeviceDetails>() - .property("device_description", codecForString()) - .property("otp_algorithm", codecForNumber()) - .property("otp_ctr", codecOptional(codecForNumber())) - .property("otp_timestamp", codecForNumber()) - .property("otp_code", codecOptional(codecForString())) - .build("TalerMerchantApi.OtpDeviceDetails"); - -export const codecForTemplateSummaryResponse = - (): Codec<TalerMerchantApi.TemplateSummaryResponse> => - buildCodecForObject<TalerMerchantApi.TemplateSummaryResponse>() - .property("templates", codecForList(codecForTemplateEntry())) - .build("TalerMerchantApi.TemplateSummaryResponse"); - -export const codecForTemplateEntry = - (): Codec<TalerMerchantApi.TemplateEntry> => - buildCodecForObject<TalerMerchantApi.TemplateEntry>() - .property("template_id", codecForString()) - .property("template_description", codecForString()) - .build("TalerMerchantApi.TemplateEntry"); - -export const codecForTemplateDetails = - (): Codec<TalerMerchantApi.TemplateDetails> => - buildCodecForObject<TalerMerchantApi.TemplateDetails>() - .property("template_description", codecForString()) - .property("otp_id", codecOptional(codecForString())) - .property("template_contract", codecForTemplateContractDetails()) - .property( - "editable_defaults", - codecOptional(codecForTemplateContractDetailsDefaults()), - ) - .build("TalerMerchantApi.TemplateDetails"); - -export const codecForTemplateContractDetails = - (): Codec<TalerMerchantApi.TemplateContractDetails> => - buildCodecForObject<TalerMerchantApi.TemplateContractDetails>() - .property("summary", codecOptional(codecForString())) - .property("currency", codecOptional(codecForString())) - .property("amount", codecOptional(codecForAmountString())) - .property("minimum_age", codecForNumber()) - .property("pay_duration", codecForDuration) - .build("TalerMerchantApi.TemplateContractDetails"); - -export const codecForTemplateContractDetailsDefaults = - (): Codec<TalerMerchantApi.TemplateContractDetailsDefaults> => - buildCodecForObject<TalerMerchantApi.TemplateContractDetailsDefaults>() - .property("summary", codecOptional(codecForString())) - .property("currency", codecOptional(codecForString())) - .property("amount", codecOptional(codecForAmountString())) - .build("TalerMerchantApi.TemplateContractDetailsDefaults"); - -export const codecForWalletTemplateDetails = - (): Codec<TalerMerchantApi.WalletTemplateDetails> => - buildCodecForObject<TalerMerchantApi.WalletTemplateDetails>() - .property("template_contract", codecForTemplateContractDetails()) - .property( - "editable_defaults", - codecOptional(codecForTemplateContractDetailsDefaults()), - ) - .build("TalerMerchantApi.WalletTemplateDetails"); - -export const codecForWebhookSummaryResponse = - (): Codec<TalerMerchantApi.WebhookSummaryResponse> => - buildCodecForObject<TalerMerchantApi.WebhookSummaryResponse>() - .property("webhooks", codecForList(codecForWebhookEntry())) - .build("TalerMerchantApi.WebhookSummaryResponse"); - -export const codecForWebhookEntry = (): Codec<TalerMerchantApi.WebhookEntry> => - buildCodecForObject<TalerMerchantApi.WebhookEntry>() - .property("webhook_id", codecForString()) - .property("event_type", codecForString()) - .build("TalerMerchantApi.WebhookEntry"); - -export const codecForWebhookDetails = - (): Codec<TalerMerchantApi.WebhookDetails> => - buildCodecForObject<TalerMerchantApi.WebhookDetails>() - .property("event_type", codecForString()) - .property("url", codecForString()) - .property("http_method", codecForString()) - .property("header_template", codecOptional(codecForString())) - .property("body_template", codecOptional(codecForString())) - .build("TalerMerchantApi.WebhookDetails"); - -export const codecForTokenFamilyKind = - (): Codec<TalerMerchantApi.TokenFamilyKind> => - codecForEither( - codecForConstString("discount"), - codecForConstString("subscription"), - ) as any; //FIXME: create a codecForEnum -export const codecForTokenFamilyDetails = - (): Codec<TalerMerchantApi.TokenFamilyDetails> => - buildCodecForObject<TalerMerchantApi.TokenFamilyDetails>() - .property("slug", codecForString()) - .property("name", codecForString()) - .property("description", codecForString()) - .property("description_i18n", codecForInternationalizedString()) - .property("valid_after", codecForTimestamp) - .property("valid_before", codecForTimestamp) - .property("duration", codecForDuration) - .property("kind", codecForTokenFamilyKind()) - .property("issued", codecForNumber()) - .property("redeemed", codecForNumber()) - .build("TalerMerchantApi.TokenFamilyDetails"); - -export const codecForTokenFamiliesList = - (): Codec<TalerMerchantApi.TokenFamiliesList> => - buildCodecForObject<TalerMerchantApi.TokenFamiliesList>() - .property("token_families", codecForList(codecForTokenFamilySummary())) - .build("TalerMerchantApi.TokenFamiliesList"); - -export const codecForTokenFamilySummary = - (): Codec<TalerMerchantApi.TokenFamilySummary> => - buildCodecForObject<TalerMerchantApi.TokenFamilySummary>() - .property("slug", codecForString()) - .property("name", codecForString()) - .property("valid_after", codecForTimestamp) - .property("valid_before", codecForTimestamp) - .property("kind", codecForTokenFamilyKind()) - .build("TalerMerchantApi.TokenFamilySummary"); - -export const codecForInstancesResponse = - (): Codec<TalerMerchantApi.InstancesResponse> => - buildCodecForObject<TalerMerchantApi.InstancesResponse>() - .property("instances", codecForList(codecForInstance())) - .build("TalerMerchantApi.InstancesResponse"); - -export const codecForInstance = (): Codec<TalerMerchantApi.Instance> => - buildCodecForObject<TalerMerchantApi.Instance>() - .property("name", codecForString()) - .property("user_type", codecForString()) - .property("website", codecOptional(codecForString())) - .property("logo", codecOptional(codecForString())) - .property("id", codecForString()) - .property("merchant_pub", codecForString()) - .property("payment_targets", codecForList(codecForString())) - .property("deleted", codecForBoolean()) - .build("TalerMerchantApi.Instance"); - -export const codecForExchangeConfig = - (): Codec<TalerExchangeApi.ExchangeVersionResponse> => - buildCodecForObject<TalerExchangeApi.ExchangeVersionResponse>() - .property("version", codecForString()) - .property("name", codecForConstString("taler-exchange")) - .property("implementation", codecOptional(codecForURN())) - .property("currency", codecForString()) - .property("currency_specification", codecForCurrencySpecificiation()) - .property("supported_kyc_requirements", codecForList(codecForString())) - .build("TalerExchangeApi.ExchangeVersionResponse"); - -export const codecForExchangeKeys = - (): Codec<TalerExchangeApi.ExchangeKeysResponse> => - buildCodecForObject<TalerExchangeApi.ExchangeKeysResponse>() - .property("version", codecForString()) - .property("base_url", codecForString()) - .property("currency", codecForString()) - .build("TalerExchangeApi.ExchangeKeysResponse"); - -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("username", codecForString()) - .property("balance", codecForBalance()) - .property("payto_uri", codecForPaytoString()) - .property("is_taler_exchange", codecForBoolean()) - .property("row_id", codecOptional(codecForNumber())) - .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("username", codecForString()) - .property("name", codecForString()) - .property("payto_uri", codecForPaytoString()) - .property("balance", codecForBalance()) - .property("row_id", codecForNumber()) - .property("debit_threshold", codecForAmountString()) - .property("min_cashout", codecOptional(codecForAmountString())) - .property("is_public", codecForBoolean()) - .property("is_taler_exchange", codecForBoolean()) - .property( - "status", - codecOptional( - codecForEither( - codecForConstString("active"), - codecForConstString("deleted"), - ), - ), - ) - .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", codecForPaytoString()) - .property("debit_threshold", codecForAmountString()) - .property("min_cashout", codecOptional(codecForAmountString())) - .property("contact_data", codecOptional(codecForChallengeContactData())) - .property("cashout_payto_uri", codecOptional(codecForPaytoString())) - .property("is_public", codecForBoolean()) - .property("is_taler_exchange", codecForBoolean()) - .property( - "tan_channel", - codecOptional( - codecForEither( - codecForConstString(TalerCorebankApi.TanChannel.SMS), - codecForConstString(TalerCorebankApi.TanChannel.EMAIL), - ), - ), - ) - .property( - "status", - codecOptional( - codecForEither( - codecForConstString("active"), - codecForConstString("deleted"), - ), - ), - ) - .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 codecForWithdrawalPublicInfo = - (): Codec<TalerCorebankApi.WithdrawalPublicInfo> => - buildCodecForObject<TalerCorebankApi.WithdrawalPublicInfo>() - .property( - "status", - codecForEither( - codecForConstString("pending"), - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - ), - ) - .property("amount", codecOptional(codecForAmountString())) - .property("username", codecForString()) - .property("selected_reserve_pub", codecOptional(codecForString())) - .property( - "selected_exchange_account", - codecOptional(codecForPaytoString()), - ) - .build("TalerCorebankApi.WithdrawalPublicInfo"); - -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("creditor_payto_uri", codecForPaytoString()) - .property("debtor_payto_uri", codecForPaytoString()) - .property("amount", codecForAmountString()) - .property( - "direction", - codecForEither( - codecForConstString("debit"), - codecForConstString("credit"), - ), - ) - .property("subject", codecForString()) - .property("row_id", codecForNumber()) - .property("date", codecForTimestamp) - .build("TalerCorebankApi.BankAccountTransactionInfo"); - -export const codecForCreateTransactionResponse = - (): Codec<TalerCorebankApi.CreateTransactionResponse> => - buildCodecForObject<TalerCorebankApi.CreateTransactionResponse>() - .property("row_id", codecForNumber()) - .build("TalerCorebankApi.CreateTransactionResponse"); - -export const codecForRegisterAccountResponse = - (): Codec<TalerCorebankApi.RegisterAccountResponse> => - buildCodecForObject<TalerCorebankApi.RegisterAccountResponse>() - .property("internal_payto_uri", codecForPaytoString()) - .build("TalerCorebankApi.RegisterAccountResponse"); - -export const codecForBankAccountCreateWithdrawalResponse = - (): Codec<TalerCorebankApi.BankAccountCreateWithdrawalResponse> => - buildCodecForObject<TalerCorebankApi.BankAccountCreateWithdrawalResponse>() - .property("taler_withdraw_uri", codecForTalerUriString()) - .property("withdrawal_id", codecForString()) - .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse"); - -export const codecForCashoutPending = - (): Codec<TalerCorebankApi.CashoutResponse> => - buildCodecForObject<TalerCorebankApi.CashoutResponse>() - .property("cashout_id", codecForNumber()) - .build("TalerCorebankApi.CashoutPending"); - -export const codecForCashoutConversionResponse = - (): Codec<TalerBankConversionApi.CashoutConversionResponse> => - buildCodecForObject<TalerBankConversionApi.CashoutConversionResponse>() - .property("amount_credit", codecForAmountString()) - .property("amount_debit", codecForAmountString()) - .build("TalerCorebankApi.CashoutConversionResponse"); - -export const codecForCashinConversionResponse = - (): Codec<TalerBankConversionApi.CashinConversionResponse> => - buildCodecForObject<TalerBankConversionApi.CashinConversionResponse>() - .property("amount_credit", codecForAmountString()) - .property("amount_debit", codecForAmountString()) - .build("TalerCorebankApi.CashinConversionResponse"); - -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", codecForNumber()) - .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", codecForNumber()) - .property("username", codecForString()) - .build("TalerCorebankApi.GlobalCashoutInfo"); - -export const codecForCashoutStatusResponse = - (): Codec<TalerCorebankApi.CashoutStatusResponse> => - buildCodecForObject<TalerCorebankApi.CashoutStatusResponse>() - .property("amount_debit", codecForAmountString()) - .property("amount_credit", codecForAmountString()) - .property("subject", codecForString()) - .property("creation_time", codecForTimestamp) - .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> => - buildCodecForUnion<TalerCorebankApi.MonitorResponse>() - .discriminateOn("type") - .alternative("no-conversions", codecForMonitorNoConversion()) - .alternative("with-conversions", codecForMonitorWithCashout()) - .build("TalerWireGatewayApi.IncomingBankTransaction"); - -export const codecForMonitorNoConversion = - (): Codec<TalerCorebankApi.MonitorNoConversion> => - buildCodecForObject<TalerCorebankApi.MonitorNoConversion>() - .property("type", codecForConstString("no-conversions")) - .property("talerInCount", codecForNumber()) - .property("talerInVolume", codecForAmountString()) - .property("talerOutCount", codecForNumber()) - .property("talerOutVolume", codecForAmountString()) - .build("TalerCorebankApi.MonitorJustPayouts"); - -export const codecForMonitorWithCashout = - (): Codec<TalerCorebankApi.MonitorWithConversion> => - buildCodecForObject<TalerCorebankApi.MonitorWithConversion>() - .property("type", codecForConstString("with-conversions")) - .property("cashinCount", codecForNumber()) - .property("cashinFiatVolume", codecForAmountString()) - .property("cashinRegionalVolume", codecForAmountString()) - .property("cashoutCount", codecForNumber()) - .property("cashoutFiatVolume", codecForAmountString()) - .property("cashoutRegionalVolume", codecForAmountString()) - .property("talerInCount", codecForNumber()) - .property("talerInVolume", codecForAmountString()) - .property("talerOutCount", codecForNumber()) - .property("talerOutVolume", codecForAmountString()) - .build("TalerCorebankApi.MonitorWithCashout"); - -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( - "status", - codecForEither( - codecForConstString("pending"), - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - ), - ) - .property("amount", codecOptional(codecForAmountString())) - .property("currency", codecOptional(codecForCurrencyName())) - .property("suggested_amount", codecOptional(codecForAmountString())) - .property("card_fees", codecOptional(codecForAmountString())) - .property("sender_wire", codecOptional(codecForPaytoString())) - .property("suggested_exchange", codecOptional(codecForURL())) - .property("confirm_transfer_url", codecOptional(codecForURL())) - .property("wire_types", codecForList(codecForString())) - .property("selected_reserve_pub", codecOptional(codecForString())) - .property("selected_exchange_account", codecOptional(codecForString())) - .property("max_amount", codecOptional(codecForAmountString())) - .build("TalerBankIntegrationApi.BankWithdrawalOperationStatus"); - -export const codecForBankWithdrawalOperationPostResponse = - (): Codec<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse> => - buildCodecForObject<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse>() - .property( - "status", - codecForEither( - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - ), - ) - .property("confirm_transfer_url", codecOptional(codecForURL())) - .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse"); - -export const codecForRevenueConfig = (): Codec<TalerRevenueApi.RevenueConfig> => - buildCodecForObject<TalerRevenueApi.RevenueConfig>() - .property("name", codecForConstString("taler-revenue")) - .property("version", codecForString()) - .property("currency", codecForString()) - .property("implementation", codecOptional(codecForString())) - .build("TalerRevenueApi.RevenueConfig"); - -export const codecForRevenueIncomingHistory = - (): Codec<TalerRevenueApi.RevenueIncomingHistory> => - buildCodecForObject<TalerRevenueApi.RevenueIncomingHistory>() - .property("credit_account", codecForPaytoString()) - .property( - "incoming_transactions", - codecForList(codecForRevenueIncomingBankTransaction()), - ) - .build("TalerRevenueApi.MerchantIncomingHistory"); - -export const codecForRevenueIncomingBankTransaction = - (): Codec<TalerRevenueApi.RevenueIncomingBankTransaction> => - buildCodecForObject<TalerRevenueApi.RevenueIncomingBankTransaction>() - .property("amount", codecForAmountString()) - .property("date", codecForTimestamp) - .property("debit_account", codecForPaytoString()) - .property("row_id", codecForNumber()) - .property("subject", codecForString()) - .build("TalerRevenueApi.RevenueIncomingBankTransaction"); - -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", codecForPaytoString()) - .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", codecForPaytoString()) - .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", codecForPaytoString()) - .property("date", codecForTimestamp) - .property("debit_account", codecForPaytoString()) - .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", codecForPaytoString()) - .property( - "outgoing_transactions", - codecForList(codecForOutgoingBankTransaction()), - ) - .build("TalerWireGatewayApi.OutgoingHistory"); - -export const codecForOutgoingBankTransaction = - (): Codec<TalerWireGatewayApi.OutgoingBankTransaction> => - buildCodecForObject<TalerWireGatewayApi.OutgoingBankTransaction>() - .property("amount", codecForAmountString()) - .property("credit_account", codecForPaytoString()) - .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 codecForAmlRecords = (): Codec<TalerExchangeApi.AmlRecords> => - buildCodecForObject<TalerExchangeApi.AmlRecords>() - .property("records", codecForList(codecForAmlRecord())) - .build("TalerExchangeApi.AmlRecords"); - -export const codecForAmlRecord = (): Codec<TalerExchangeApi.AmlRecord> => - buildCodecForObject<TalerExchangeApi.AmlRecord>() - .property("current_state", codecForNumber()) - .property("h_payto", codecForString()) - .property("rowid", codecForNumber()) - .property("threshold", codecForAmountString()) - .build("TalerExchangeApi.AmlRecord"); - -export const codecForAmlDecisionDetails = - (): Codec<TalerExchangeApi.AmlDecisionDetails> => - buildCodecForObject<TalerExchangeApi.AmlDecisionDetails>() - .property("aml_history", codecForList(codecForAmlDecisionDetail())) - .property("kyc_attributes", codecForList(codecForKycDetail())) - .build("TalerExchangeApi.AmlDecisionDetails"); - -export const codecForAmlDecisionDetail = - (): Codec<TalerExchangeApi.AmlDecisionDetail> => - buildCodecForObject<TalerExchangeApi.AmlDecisionDetail>() - .property("justification", codecForString()) - .property("new_state", codecForNumber()) - .property("decision_time", codecForTimestamp) - .property("new_threshold", codecForAmountString()) - .property("decider_pub", codecForString()) - .build("TalerExchangeApi.AmlDecisionDetail"); - -export const codecForChallenge = (): Codec<TalerCorebankApi.Challenge> => - buildCodecForObject<TalerCorebankApi.Challenge>() - .property("challenge_id", codecForNumber()) - .build("TalerCorebankApi.Challenge"); - -export const codecForTanTransmission = - (): Codec<TalerCorebankApi.TanTransmission> => - buildCodecForObject<TalerCorebankApi.TanTransmission>() - .property( - "tan_channel", - codecForEither( - codecForConstString(TalerCorebankApi.TanChannel.SMS), - codecForConstString(TalerCorebankApi.TanChannel.EMAIL), - ), - ) - .property("tan_info", codecForString()) - .build("TalerCorebankApi.TanTransmission"); - -interface KycDetail { - provider_section: string; - attributes?: Object; - collection_time: Timestamp; - expiration_time: Timestamp; -} -export const codecForKycDetail = (): Codec<TalerExchangeApi.KycDetail> => - buildCodecForObject<TalerExchangeApi.KycDetail>() - .property("provider_section", codecForString()) - .property("attributes", codecOptional(codecForAny())) - .property("collection_time", codecForTimestamp) - .property("expiration_time", codecForTimestamp) - .build("TalerExchangeApi.KycDetail"); - -export const codecForAmlDecision = (): Codec<TalerExchangeApi.AmlDecision> => - buildCodecForObject<TalerExchangeApi.AmlDecision>() - .property("justification", codecForString()) - .property("new_threshold", codecForAmountString()) - .property("h_payto", codecForString()) - .property("new_state", codecForNumber()) - .property("officer_sig", codecForString()) - .property("decision_time", codecForTimestamp) - .property("kyc_requirements", codecOptional(codecForList(codecForString()))) - .build("TalerExchangeApi.AmlDecision"); - -export const codecForConversionInfo = - (): Codec<TalerBankConversionApi.ConversionInfo> => - buildCodecForObject<TalerBankConversionApi.ConversionInfo>() - .property("cashin_fee", codecForAmountString()) - .property("cashin_min_amount", codecForAmountString()) - .property("cashin_ratio", codecForDecimalNumber()) - .property( - "cashin_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) - .property("cashin_tiny_amount", codecForAmountString()) - .property("cashout_fee", codecForAmountString()) - .property("cashout_min_amount", codecForAmountString()) - .property("cashout_ratio", codecForDecimalNumber()) - .property( - "cashout_rounding_mode", - codecForEither( - codecForConstString("zero"), - codecForConstString("up"), - codecForConstString("nearest"), - ), - ) - .property("cashout_tiny_amount", codecForAmountString()) - .build("ConversionBankConfig.ConversionInfo"); - -export const codecForConversionBankConfig = - (): Codec<TalerBankConversionApi.IntegrationConfig> => - buildCodecForObject<TalerBankConversionApi.IntegrationConfig>() - .property("name", codecForConstString("taler-conversion-info")) - .property("version", codecForString()) - .property("regional_currency", codecForString()) - .property( - "regional_currency_specification", - codecForCurrencySpecificiation(), - ) - .property("fiat_currency", codecForString()) - .property("fiat_currency_specification", codecForCurrencySpecificiation()) - - .property("conversion_rate", codecForConversionInfo()) - .build("ConversionBankConfig.IntegrationConfig"); - -export const codecForChallengerTermsOfServiceResponse = - (): Codec<ChallengerApi.ChallengerTermsOfServiceResponse> => - buildCodecForObject<ChallengerApi.ChallengerTermsOfServiceResponse>() - .property("name", codecForConstString("challenger")) - .property("version", codecForString()) - .property("implementation", codecOptional(codecForString())) - .property("restrictions", codecOptional(codecForMap(codecForAny()))) - .property( - "address_type", - codecForEither( - codecForConstString("phone"), - codecForConstString("email"), - ), - ) - .build("ChallengerApi.ChallengerTermsOfServiceResponse"); - -export const codecForChallengeSetupResponse = - (): Codec<ChallengerApi.ChallengeSetupResponse> => - buildCodecForObject<ChallengerApi.ChallengeSetupResponse>() - .property("nonce", codecForString()) - .build("ChallengerApi.ChallengeSetupResponse"); - -export const codecForChallengeStatus = - (): Codec<ChallengerApi.ChallengeStatus> => - buildCodecForObject<ChallengerApi.ChallengeStatus>() - .property("fix_address", codecForBoolean()) - .property("solved", codecForBoolean()) - .property("last_address", codecOptional(codecForMap(codecForAny()))) - .property("changes_left", codecForNumber()) - .property("retransmission_time", codecForTimestamp) - .property("pin_transmissions_left", codecForNumber()) - .property("auth_attempts_left", codecForNumber()) - .build("ChallengerApi.ChallengeStatus"); - -export const codecForChallengeResponse = - (): Codec<ChallengerApi.ChallengeResponse> => - buildCodecForUnion<ChallengerApi.ChallengeResponse>() - .discriminateOn("type") - .alternative("completed", codecForChallengeRedirect()) - .alternative("created", codecForChallengeCreateResponse()) - .build("ChallengerApi.ChallengeResponse"); - -export const codecForChallengeCreateResponse = - (): Codec<ChallengerApi.ChallengeCreateResponse> => - buildCodecForObject<ChallengerApi.ChallengeCreateResponse>() - .property("attempts_left", codecForNumber()) - .property("type", codecForConstString("created")) - .property("address", codecForAny()) - .property("transmitted", codecForBoolean()) - .property("retransmission_time", codecForTimestamp) - .build("ChallengerApi.ChallengeCreateResponse"); - -export const codecForChallengeRedirect = - (): Codec<ChallengerApi.ChallengeRedirect> => - buildCodecForObject<ChallengerApi.ChallengeRedirect>() - .property("type", codecForConstString("completed")) - .property("redirect_url", codecForString()) - .build("ChallengerApi.ChallengeRedirect"); - -export const codecForChallengeInvalidPinResponse = - (): Codec<ChallengerApi.InvalidPinResponse> => - buildCodecForObject<ChallengerApi.InvalidPinResponse>() - .property("ec", codecOptional(codecForNumber())) - .property("hint", codecForAny()) - .property("type", codecForConstString("pending")) - .property("addresses_left", codecForNumber()) - .property("pin_transmissions_left", codecForNumber()) - .property("auth_attempts_left", codecForNumber()) - .property("exhausted", codecForBoolean()) - .property("no_challenge", codecForBoolean()) - .build("ChallengerApi.InvalidPinResponse"); - -export const codecForChallengeSolveResponse = - (): Codec<ChallengerApi.ChallengeSolveResponse> => - buildCodecForUnion<ChallengerApi.ChallengeSolveResponse>() - .discriminateOn("type") - .alternative("completed", codecForChallengeRedirect()) - .alternative("pending", codecForChallengeInvalidPinResponse()) - .build("ChallengerApi.ChallengeSolveResponse"); - -export const codecForChallengerAuthResponse = - (): Codec<ChallengerApi.ChallengerAuthResponse> => - buildCodecForObject<ChallengerApi.ChallengerAuthResponse>() - .property("access_token", codecForString()) - .property("token_type", codecForAny()) - .property("expires_in", codecForNumber()) - .build("ChallengerApi.ChallengerAuthResponse"); - -export const codecForChallengerInfoResponse = - (): Codec<ChallengerApi.ChallengerInfoResponse> => - buildCodecForObject<ChallengerApi.ChallengerInfoResponse>() - .property("id", codecForNumber()) - .property("address", codecForAny()) - .property("address_type", codecForString()) - .property("expires", codecForTimestamp) - .build("ChallengerApi.ChallengerInfoResponse"); - -export const codecForTemplateEditableDetails = - (): Codec<TalerMerchantApi.TemplateEditableDetails> => - buildCodecForObject<TalerMerchantApi.TemplateEditableDetails>() - .property("summary", codecOptional(codecForString())) - .property("currency", codecOptional(codecForString())) - .property("amount", codecOptional(codecForAmountString())) - .build("TemplateEditableDetails"); - -export const codecForMerchantReserveCreateConfirmation = - (): Codec<TalerMerchantApi.MerchantReserveCreateConfirmation> => - buildCodecForObject<TalerMerchantApi.MerchantReserveCreateConfirmation>() - .property("accounts", codecForList(codecForExchangeWireAccount())) - .property("reserve_pub", codecForString()) - .build("MerchantReserveCreateConfirmation"); - -type EmailAddress = string; -type PhoneNumber = string; -type EddsaSignature = string; -// base32 encoded RSA blinded signature. -type BlindedRsaSignature = string; -type Base32 = string; - -type DecimalNumber = string; -type RsaSignature = string; -type Float = number; -type LibtoolVersion = string; -// The type of a coin's blinded envelope depends on the cipher that is used -// for signing with a denomination key. -type CoinEnvelope = RSACoinEnvelope | CSCoinEnvelope; -// For denomination signatures based on RSA, the planchet is just a blinded -// coin's public EdDSA key. -interface RSACoinEnvelope { - cipher: "RSA" | "RSA+age_restricted"; - rsa_blinded_planchet: string; // Crockford Base32 encoded -} -// For denomination signatures based on Blind Clause-Schnorr, the planchet -// consists of the public nonce and two Curve25519 scalars which are two -// blinded challenges in the Blinded Clause-Schnorr signature scheme. -// See https://taler.net/papers/cs-thesis.pdf for details. -interface CSCoinEnvelope { - cipher: "CS" | "CS+age_restricted"; - cs_nonce: string; // Crockford Base32 encoded - cs_blinded_c0: string; // Crockford Base32 encoded - cs_blinded_c1: string; // Crockford Base32 encoded -} -// Secret for blinding/unblinding. -// An RSA blinding secret, which is basically -// a 256-bit nonce, converted to Crockford Base32. -type DenominationBlindingKeyP = string; - -//FIXME: implement this codec -const codecForURL = codecForString; -//FIXME: implement this codec -const codecForLibtoolVersion = codecForString; -//FIXME: implement this codec -const codecForCurrencyName = codecForString; -//FIXME: implement this codec -const codecForDecimalNumber = codecForString; - -export type WithdrawalOperationStatus = - | "pending" - | "selected" - | "aborted" - | "confirmed"; - -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: AmountString; - - // 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: PaytoString; - } - - 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. - - // undefined if incoming transaction is empty - credit_account?: PaytoString; - } - - // 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: AmountString; - - // Payto URI to identify the sender of funds. - debit_account: PaytoString; - - // 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: AmountString; - - // Payto URI to identify the receiver of funds. - // This must be one of the exchange's bank accounts. - credit_account: PaytoString; - - // Payto URI to identify the sender of funds. - debit_account: PaytoString; - - // 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. - - // undefined if outgoing transactions is empty - debit_account?: PaytoString; - } - - export interface OutgoingBankTransaction { - // Opaque identifier of the returned record. - row_id: SafeUint64; - - // Date of the transaction. - date: Timestamp; - - // Amount transferred. - amount: AmountString; - - // Payto URI to identify the receiver of funds. - credit_account: PaytoString; - - // 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: AmountString; - - // 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: PaytoString; - } - - 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 RevenueConfig { - // Name of the API. - name: "taler-revenue"; - - // 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 gateway. - currency: string; - - // URN of the implementation (needed to interpret 'revision' in version). - // @since v0, may become mandatory in the future. - implementation?: string; - } - - export interface RevenueIncomingHistory { - // Array of incoming transactions. - incoming_transactions: RevenueIncomingBankTransaction[]; - - // Payto URI to identify the receiver of funds. - // Credit account is shared by all incoming transactions - // as per the nature of the request. - credit_account: string; - } - - export interface RevenueIncomingBankTransaction { - // Opaque identifier of the returned record. - row_id: SafeUint64; - - // Date of the transaction. - date: Timestamp; - - // Amount transferred. - amount: AmountString; - - // Payto URI to identify the sender of funds. - debit_account: string; - - // The wire transfer subject. - subject: string; - } -} - -export namespace TalerBankConversionApi { - export interface ConversionInfo { - // Exchange rate to buy regional currency from fiat - cashin_ratio: DecimalNumber; - - // Exchange rate to sell regional currency for fiat - cashout_ratio: DecimalNumber; - - // Fee to subtract after applying the cashin ratio. - cashin_fee: AmountString; - - // Fee to subtract after applying the cashout ratio. - cashout_fee: AmountString; - - // Minimum amount authorised for cashin, in fiat before conversion - cashin_min_amount: AmountString; - - // Minimum amount authorised for cashout, in regional before conversion - cashout_min_amount: AmountString; - - // Smallest possible regional amount, converted amount is rounded to this amount - cashin_tiny_amount: AmountString; - - // Smallest possible fiat amount, converted amount is rounded to this amount - cashout_tiny_amount: AmountString; - - // Rounding mode used during cashin conversion - cashin_rounding_mode: "zero" | "up" | "nearest"; - - // Rounding mode used during cashout conversion - cashout_rounding_mode: "zero" | "up" | "nearest"; - } - - export interface IntegrationConfig { - // 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; - - // Name of the API. - name: "taler-conversion-info"; - - // Currency used by this bank. - regional_currency: string; - - // How the bank SPA should render this currency. - regional_currency_specification: CurrencySpecification; - - // External currency used during conversion. - fiat_currency: string; - - // How the bank SPA should render this currency. - fiat_currency_specification: CurrencySpecification; - - // Extra conversion rate information. - // Only present if server opts in to report the static conversion rate. - conversion_rate: ConversionInfo; - } - - export interface CashinConversionResponse { - // Amount that the user will get deducted from their fiat - // bank account, according to the 'amount_credit' value. - amount_debit: AmountString; - // Amount that the user will receive in their regional - // bank account, according to 'amount_debit'. - amount_credit: AmountString; - } - - export interface CashoutConversionResponse { - // Amount that the user will get deducted from their regional - // bank account, according to the 'amount_credit' value. - amount_debit: AmountString; - // Amount that the user will receive in their fiat - // bank account, according to 'amount_debit'. - amount_credit: AmountString; - } - - export type RoundingMode = "zero" | "up" | "nearest"; - - export interface ConversionRate { - // Exchange rate to buy regional currency from fiat - cashin_ratio: DecimalNumber; - - // Fee to subtract after applying the cashin ratio. - cashin_fee: AmountString; - - // Minimum amount authorised for cashin, in fiat before conversion - cashin_min_amount: AmountString; - - // Smallest possible regional amount, converted amount is rounded to this amount - cashin_tiny_amount: AmountString; - - // Rounding mode used during cashin conversion - cashin_rounding_mode: RoundingMode; - - // Exchange rate to sell regional currency for fiat - cashout_ratio: DecimalNumber; - - // Fee to subtract after applying the cashout ratio. - cashout_fee: AmountString; - - // Minimum amount authorised for cashout, in regional before conversion - cashout_min_amount: AmountString; - - // Smallest possible fiat amount, converted amount is rounded to this amount - cashout_tiny_amount: AmountString; - - // Rounding mode used during cashout conversion - cashout_rounding_mode: RoundingMode; - } -} - -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 { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: WithdrawalOperationStatus; - - // Currency used for the withdrawal. - // MUST be present when amount is absent. - // @since v2, may become mandatory in the future. - currency?: string; - - // Amount that will be withdrawn with this operation - // (raw amount without fee considerations). Only - // given once the amount is fixed and cannot be changed. - // Optional since **vC2EC**. - amount?: AmountString | undefined; - - // Suggestion for the amount to be withdrawn with this - // operation. Given if a suggestion was made but the - // user may still change the amount. - // Optional since **vC2EC**. - suggested_amount?: AmountString | undefined; - - // Maximum amount that the wallet can choose to withdraw. - // Only applicable when the amount is not fixed. - // @since **vC2EC**. - max_amount?: AmountString | undefined; - - // The non-Taler card fees the customer will have - // to pay to the bank / payment service provider - // they are using to make the withdrawal. - // @since **vC2EC** - card_fees?: AmountString | undefined; - - // Bank account of the customer that is debiting, as an - // RFC 8905 payto URI. - sender_wire?: PaytoString; - - // Base URL of the suggested exchange. The bank may have - // neither a suggestion nor a requirement for the exchange. - // This value is typically set in the bank's configuration. - suggested_exchange?: string; - - // Base URL of an exchange that must be used. Optional, - // not given *unless* a particular exchange is mandatory. - // This value is typically set in the bank's configuration. - // @since **vC2EC** - required_exchange?: string; - - // URL that the user needs to navigate to in order to - // complete some final confirmation (e.g. 2FA). - // Only applicable when status is selected or pending. - // It may contain the withdrawal operation id. - confirm_transfer_url?: string; - - // Wire transfer types supported by the bank. - wire_types: string[]; - - // Reserve public key selected by the exchange, - // only non-null if status is selected or confirmed. - selected_reserve_pub?: string; - - // Exchange account selected by the wallet; - // only non-null if status is selected or confirmed. - // @since **v1** - selected_exchange_account?: string; - } - - export interface BankWithdrawalOperationPostRequest { - // Reserve public key that should become the wire transfer - // subject to fund the withdrawal. - reserve_pub: string; - - // Payto address of the exchange selected for the withdrawal. - selected_exchange: PaytoString; - - // Selected amount to be transferred. Optional if the - // backend already knows the amount. - // @since **vC2EC** - amount?: AmountString | undefined; - } - - export interface BankWithdrawalOperationPostResponse { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: Omit<"pending", WithdrawalOperationStatus>; - - // URL that the user needs to navigate to in order to - // complete some final confirmation (e.g. 2FA). - // - // Only applicable when status is selected or pending. - // It may contain withdrawal operation id - confirm_transfer_url?: string; - } -} - -export namespace TalerCorebankApi { - export interface IntegrationConfig { - // 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: string; - - // How the bank SPA should render this currency. - currency_specification: CurrencySpecification; - - // Name of the API. - name: "taler-bank-integration"; - } - export interface Config { - // Name of this API, always "taler-corebank". - name: "libeufin-bank"; - // name: "taler-corebank"; - - // API version in the form $n:$n:$n - version: string; - - // Bank display name to be used in user interfaces. - // For consistency use "Taler Bank" if missing. - // @since v4, will become mandatory in the next version. - bank_name: string; - - // Advertised base URL to use when you sharing an URL with another - // program. - // @since v4. - base_url?: string; - - // If 'true' the server provides local currency conversion support - // If 'false' some parts of the API are not supported and return 501 - allow_conversion: boolean; - - // If 'true' anyone can register - // If 'false' only the admin can - allow_registrations: boolean; - - // If 'true' account can delete themselves - // If 'false' only the admin can delete accounts - allow_deletions: boolean; - - // If 'true' anyone can edit their name - // If 'false' only admin can - allow_edit_name: boolean; - - // If 'true' anyone can edit their cashout account - // If 'false' only the admin - allow_edit_cashout_payto_uri: boolean; - - // Default debt limit for newly created accounts - default_debit_threshold: AmountString; - - // Currency used by this bank. - currency: string; - - // How the bank SPA should render this currency. - currency_specification: CurrencySpecification; - - // TAN channels supported by the server - supported_tan_channels: TanChannel[]; - - // Wire transfer type supported by the bank. - // Default to 'iban' is missing - // @since v4, may become mandatory in the future. - wire_type: string; - - // Wire transfer execution fees. - // @since v4, will become mandatory in the next version. - wire_transfer_fees?: AmountString; - } - - export interface BankAccountCreateWithdrawalRequest { - // Amount to withdraw. If given, the wallet - // cannot change the amount. - // Optional since **vC2EC**. - amount?: AmountString; - - // Suggested amount to withdraw. The wallet can - // still change the suggestion. - // @since **vC2EC** - suggested_amount?: AmountString; - } - - 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: TalerUriString; - } - export interface WithdrawalPublicInfo { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: WithdrawalOperationStatus; - - // Amount that will be withdrawn with this operation - // (raw amount without fee considerations). - amount?: AmountString; - - // Suggestion for the amount to be withdrawn with this - // operation. Given if a suggestion was made but the - // user may still change the amount. - // Optional since **vC2EC**. - suggested_amount?: AmountString; - - // Account username - username: string; - - // Reserve public key selected by the exchange, - // only non-null if status is selected or confirmed. - selected_reserve_pub?: string; - - // Exchange account selected by the wallet - // only non-null if status is selected or confirmed. - selected_exchange_account?: PaytoString; - } - - export interface BankAccountTransactionsResponse { - transactions: BankAccountTransactionInfo[]; - } - - export interface BankAccountTransactionInfo { - creditor_payto_uri: PaytoString; - debtor_payto_uri: PaytoString; - - amount: AmountString; - direction: "debit" | "credit"; - - subject: string; - - // Transaction unique ID. Matches - // $transaction_id from the URI. - row_id: number; - date: Timestamp; - } - - export interface CreateTransactionRequest { - // Address in the Payto format of the wire transfer receiver. - // It needs at least the 'message' query string parameter. - payto_uri: PaytoString; - - // 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?: AmountString; - - // Nonce to make the request idempotent. Requests with the same - // request_uid that differ in any of the other fields - // are rejected. - // @since v4, will become mandatory in the next version. - request_uid?: ShortHashCode; - } - - export interface CreateTransactionResponse { - // ID identifying the transaction being created - row_id: Integer; - } - - export interface RegisterAccountResponse { - // Internal payto URI of this bank account. - internal_payto_uri: PaytoString; - } - - 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. - contact_data?: ChallengeContactData; - - // 'payto' address of a fiat bank account. - // Payments will be sent to this bank account - // when the user wants to convert the regional currency - // back to fiat currency outside bank. - cashout_payto_uri?: PaytoString; - - // Internal payto URI of this bank account. - // Used mostly for testing. - payto_uri?: PaytoString; - - // If present, set the max debit allowed for this user - // Only admin can set this property. - debit_threshold?: AmountString; - - // If present, set a custom minimum cashout amount for this account. - // Only admin can set this property - // @since v4 - min_cashout?: AmountString; - - // If present, enables 2FA and set the TAN channel used for challenges - // Only admin can set this property, other user can reconfig their account - // after creation. - tan_channel?: TanChannel; - } - - 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. - // Only admin can change this property. - contact_data?: ChallengeContactData; - - // 'payto' URI of a fiat bank account. - // Payments will be sent to this bank account - // when the user wants to convert the regional currency - // back to fiat currency outside bank. - // Only admin can change this property if not allowed in config - cashout_payto_uri?: PaytoString; - - // If present, change the legal name associated with $username. - // Only admin can change this property if not allowed in config - name?: string; - - // Make this account visible to anyone? - is_public?: boolean; - - // If present, change the max debit allowed for this user - // Only admin can change this property. - debit_threshold?: AmountString; - - // If present, change the custom minimum cashout amount for this account. - // Only admin can set this property - // @since v4 - min_cashout?: AmountString; - - // If present, enables 2FA and set the TAN channel used for challenges - tan_channel?: TanChannel | null; - } - - export interface AccountPasswordChange { - // New password. - new_password: string; - // Old password. If present, check that the old password matches. - // Optional for admin account. - old_password?: string; - } - - export interface PublicAccountsResponse { - public_accounts: PublicAccount[]; - } - export interface PublicAccount { - // Username of the account - username: string; - - // Internal payto URI of this bank account. - payto_uri: string; - - // Current balance of the account - balance: Balance; - - // Is this a taler exchange account? - is_taler_exchange: boolean; - - // Opaque unique ID used for pagination. - // @since v4, will become mandatory in the future. - row_id?: Integer; - } - - export interface ListBankAccountsResponse { - accounts: AccountMinimalData[]; - } - export interface Balance { - amount: AmountString; - credit_debit_indicator: "credit" | "debit"; - } - export interface AccountMinimalData { - // Username - username: string; - - // Legal name of the account owner. - name: string; - - // Internal payto URI of this bank account. - payto_uri: PaytoString; - - // current balance of the account - balance: Balance; - - // Number indicating the max debit allowed for the requesting user. - debit_threshold: AmountString; - - // Custom minimum cashout amount for this account. - // If null or absent, the global conversion fee is used. - // @since v4 - min_cashout?: AmountString; - - // Is this account visible to anyone? - is_public: boolean; - - // Is this a taler exchange account? - is_taler_exchange: boolean; - - // Opaque unique ID used for pagination. - // @since v4, will become mandatory in the future. - row_id?: Integer; - - // Current status of the account - // active: the account can be used - // deleted: the account has been deleted but is retained for compliance - // reasons, only the administrator can access it - // Default to 'active' is missing - // @since v4, will become mandatory in the next version. - status?: "active" | "deleted"; - } - - 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: PaytoString; - - // Number indicating the max debit allowed for the requesting user. - debit_threshold: AmountString; - - // Custom minimum cashout amount for this account. - // If null or absent, the global conversion fee is used. - // @since v4 - min_cashout?: AmountString; - - 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?: PaytoString; - - // Is this account visible to anyone? - is_public: boolean; - - // Is this a taler exchange account? - is_taler_exchange: boolean; - - // Is 2FA enabled and what channel is used for challenges? - tan_channel?: TanChannel; - - // Current status of the account - // active: the account can be used - // deleted: the account has been deleted but is retained for compliance - // reasons, only the administrator can access it - // Default to 'active' is missing - // @since v4, will become mandatory in the next version. - status?: "active" | "deleted"; - } - - export interface CashoutRequest { - // Nonce to make the request idempotent. Requests with the same - // request_uid that differ in any of the other fields - // are rejected. - request_uid: ShortHashCode; - - // Optional subject to associate to the - // cashout operation. This data will appear - // as the incoming wire transfer subject in - // the user's fiat 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: AmountString; - - // 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: AmountString; - } - - export interface CashoutResponse { - // ID identifying the operation being created - cashout_id: number; - } - - /** - * @deprecated since 4, use 2fa - */ - export interface CashoutConfirmRequest { - // the TAN that confirms $CASHOUT_ID. - tan: string; - } - - export interface Cashouts { - // Every string represents a cash-out operation ID. - cashouts: CashoutInfo[]; - } - - export interface CashoutInfo { - cashout_id: number; - } - export interface GlobalCashouts { - // Every string represents a cash-out operation ID. - cashouts: GlobalCashoutInfo[]; - } - export interface GlobalCashoutInfo { - cashout_id: number; - username: string; - } - - export interface CashoutStatusResponse { - // Amount debited to the internal - // regional currency bank account. - amount_debit: AmountString; - - // Amount credited to the external bank account. - amount_credit: AmountString; - - // Transaction subject. - subject: string; - - // Time when the cashout was created. - creation_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 type MonitorResponse = MonitorNoConversion | MonitorWithConversion; - - // Monitoring stats when conversion is not supported - export interface MonitorNoConversion { - type: "no-conversions"; - - // How many payments were made to a Taler exchange by another - // bank account. - talerInCount: number; - - // Overall volume that has been paid to a Taler - // exchange by another bank account. - talerInVolume: AmountString; - - // How many payments were made by a Taler exchange to another - // bank account. - talerOutCount: number; - - // Overall volume that has been paid by a Taler - // exchange to another bank account. - talerOutVolume: AmountString; - } - // Monitoring stats when conversion is supported - export interface MonitorWithConversion { - type: "with-conversions"; - - // How many cashin operations were confirmed by a - // wallet owner. Note: wallet owners - // are NOT required to be customers of the libeufin-bank. - cashinCount: number; - - // Overall regional currency that has been paid by the regional admin account - // to regional bank accounts to fulfill all the confirmed cashin operations. - cashinRegionalVolume: AmountString; - - // Overall fiat currency that has been paid to the fiat admin account - // by fiat bank accounts to fulfill all the confirmed cashin operations. - cashinFiatVolume: AmountString; - - // How many cashout operations were confirmed. - cashoutCount: number; - - // Overall regional currency that has been paid to the regional admin account - // by fiat bank accounts to fulfill all the confirmed cashout operations. - cashoutRegionalVolume: AmountString; - - // Overall fiat currency that has been paid by the fiat admin account - // to fiat bank accounts to fulfill all the confirmed cashout operations. - cashoutFiatVolume: AmountString; - - // How many payments were made to a Taler exchange by another - // bank account. - talerInCount: number; - - // Overall volume that has been paid to a Taler - // exchange by another bank account. - talerInVolume: AmountString; - - // How many payments were made by a Taler exchange to another - // bank account. - talerOutCount: number; - - // Overall volume that has been paid by a Taler - // exchange to another bank account. - talerOutVolume: AmountString; - } - export interface TanTransmission { - // Channel of the last successful transmission of the TAN challenge. - tan_channel: TanChannel; - - // Info of the last successful transmission of the TAN challenge. - tan_info: string; - } - - export interface Challenge { - // Unique identifier of the challenge to solve to run this protected - // operation. - challenge_id: number; - } - - export interface ChallengeSolve { - // The TAN code that solves $CHALLENGE_ID - tan: string; - } - - export enum TanChannel { - SMS = "sms", - EMAIL = "email", - } -} - -export namespace TalerExchangeApi { - export enum AmlState { - normal = 0, - pending = 1, - frozen = 2, - } - - export interface AmlRecords { - // Array of AML records matching the query. - records: AmlRecord[]; - } - export interface AmlRecord { - // Which payto-address is this record about. - // Identifies a GNU Taler wallet or an affected bank account. - h_payto: PaytoHash; - - // What is the current AML state. - current_state: AmlState; - - // Monthly transaction threshold before a review will be triggered - threshold: AmountString; - - // RowID of the record. - rowid: Integer; - } - - export interface AmlDecisionDetails { - // Array of AML decisions made for this account. Possibly - // contains only the most recent decision if "history" was - // not set to 'true'. - aml_history: AmlDecisionDetail[]; - - // Array of KYC attributes obtained for this account. - kyc_attributes: KycDetail[]; - } - export interface AmlDecisionDetail { - // What was the justification given? - justification: string; - - // What is the new AML state. - new_state: Integer; - - // When was this decision made? - decision_time: Timestamp; - - // What is the new AML decision threshold (in monthly transaction volume)? - new_threshold: AmountString; - - // Who made the decision? - decider_pub: AmlOfficerPublicKeyP; - } - export interface KycDetail { - // Name of the configuration section that specifies the provider - // which was used to collect the KYC details - provider_section: string; - - // The collected KYC data. NULL if the attribute data could not - // be decrypted (internal error of the exchange, likely the - // attribute key was changed). - attributes?: Object; - - // Time when the KYC data was collected - collection_time: Timestamp; - - // Time when the validity of the KYC data will expire - expiration_time: Timestamp; - } - - export interface AmlDecision { - // Human-readable justification for the decision. - justification: string; - - // At what monthly transaction volume should the - // decision be automatically reviewed? - new_threshold: AmountString; - - // Which payto-address is the decision about? - // Identifies a GNU Taler wallet or an affected bank account. - h_payto: PaytoHash; - - // What is the new AML state (e.g. frozen, unfrozen, etc.) - // Numerical values are defined in AmlDecisionState. - new_state: Integer; - - // Signature by the AML officer over a - // TALER_MasterAmlOfficerStatusPS. - // Must have purpose TALER_SIGNATURE_MASTER_AML_KEY. - officer_sig: EddsaSignature; - - // When was the decision made? - decision_time: Timestamp; - - // Optional argument to impose new KYC requirements - // that the customer has to satisfy to unblock transactions. - kyc_requirements?: string[]; - } - - export interface ExchangeVersionResponse { - // libtool-style representation of the Exchange protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Name of the protocol. - name: "taler-exchange"; - - // URN of the implementation (needed to interpret 'revision' in version). - // @since v18, may become mandatory in the future. - implementation?: string; - - // Currency supported by this exchange, given - // as a currency code ("USD" or "EUR"). - currency: string; - - // How wallets should render this currency. - currency_specification: CurrencySpecification; - - // Names of supported KYC requirements. - supported_kyc_requirements: string[]; - } - - export type AccountRestriction = - | RegexAccountRestriction - | DenyAllAccountRestriction; - // Account restriction that disables this type of - // account for the indicated operation categorically. - export interface DenyAllAccountRestriction { - type: "deny"; - } - // Accounts interacting with this type of account - // restriction must have a payto://-URI matching - // the given regex. - export interface RegexAccountRestriction { - type: "regex"; - - // Regular expression that the payto://-URI of the - // partner account must follow. The regular expression - // should follow posix-egrep, but without support for character - // classes, GNU extensions, back-references or intervals. See - // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html - // for a description of the posix-egrep syntax. Applications - // may support regexes with additional features, but exchanges - // must not use such regexes. - payto_regex: string; - - // Hint for a human to understand the restriction - // (that is hopefully easier to comprehend than the regex itself). - human_hint: string; - - // Map from IETF BCP 47 language tags to localized - // human hints. - human_hint_i18n?: { [lang_tag: string]: string }; - } - - export interface WireAccount { - // payto:// URI identifying the account and wire method - payto_uri: PaytoString; - - // URI to convert amounts from or to the currency used by - // this wire account of the exchange. Missing if no - // conversion is applicable. - conversion_url?: string; - - // Restrictions that apply to bank accounts that would send - // funds to the exchange (crediting this exchange bank account). - // Optional, empty array for unrestricted. - credit_restrictions: AccountRestriction[]; - - // Restrictions that apply to bank accounts that would receive - // funds from the exchange (debiting this exchange bank account). - // Optional, empty array for unrestricted. - debit_restrictions: AccountRestriction[]; - - // Signature using the exchange's offline key over - // a TALER_MasterWireDetailsPS - // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. - master_sig: EddsaSignature; - } - - export interface ExchangeKeysResponse { - // libtool-style representation of the Exchange protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // The exchange's base URL. - base_url: string; - - // The exchange's currency or asset unit. - currency: string; - - /** - * FIXME: PARTIALLY IMPLEMENTED!! - */ - - // How wallets should render this currency. - // currency_specification: CurrencySpecification; - - // // Absolute cost offset for the STEFAN curve used - // // to (over) approximate fees payable by amount. - // stefan_abs: AmountString; - - // // Factor to multiply the logarithm of the amount - // // with to (over) approximate fees payable by amount. - // // Note that the total to be paid is first to be - // // divided by the smallest denomination to obtain - // // the value that the logarithm is to be taken of. - // stefan_log: AmountString; - - // // Linear cost factor for the STEFAN curve used - // // to (over) approximate fees payable by amount. - // // - // // Note that this is a scalar, as it is multiplied - // // with the actual amount. - // stefan_lin: Float; - - // // Type of the asset. "fiat", "crypto", "regional" - // // or "stock". Wallets should adjust their UI/UX - // // based on this value. - // asset_type: string; - - // // Array of wire accounts operated by the exchange for - // // incoming wire transfers. - // accounts: WireAccount[]; - - // // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank") - // // to wire fees. - // wire_fees: { method: AggregateTransferFee[] }; - - // // List of exchanges that this exchange is partnering - // // with to enable wallet-to-wallet transfers. - // wads: ExchangePartner[]; - - // // Set to true if this exchange allows the use - // // of reserves for rewards. - // // @deprecated in protocol v18. - // rewards_allowed: false; - - // // EdDSA master public key of the exchange, used to sign entries - // // in denoms and signkeys. - // master_public_key: EddsaPublicKey; - - // // Relative duration until inactive reserves are closed; - // // not signed (!), can change without notice. - // reserve_closing_delay: RelativeTime; - - // // Threshold amounts beyond which wallet should - // // trigger the KYC process of the issuing - // // exchange. Optional option, if not given there is no limit. - // // Currency must match currency. - // wallet_balance_limit_without_kyc?: AmountString[]; - - // // Denominations offered by this exchange - // denominations: DenomGroup[]; - - // // Compact EdDSA signature (binary-only) over the - // // contatentation of all of the master_sigs (in reverse - // // chronological order by group) in the arrays under - // // "denominations". Signature of TALER_ExchangeKeySetPS - // exchange_sig: EddsaSignature; - - // // Public EdDSA key of the exchange that was used to generate the signature. - // // Should match one of the exchange's signing keys from signkeys. It is given - // // explicitly as the client might otherwise be confused by clock skew as to - // // which signing key was used for the exchange_sig. - // exchange_pub: EddsaPublicKey; - - // // Denominations for which the exchange currently offers/requests recoup. - // recoup: Recoup[]; - - // // Array of globally applicable fees by time range. - // global_fees: GlobalFees[]; - - // // The date when the denomination keys were last updated. - // list_issue_date: Timestamp; - - // // Auditors of the exchange. - // auditors: AuditorKeys[]; - - // // The exchange's signing keys. - // signkeys: SignKey[]; - - // // Optional field with a dictionary of (name, object) pairs defining the - // // supported and enabled extensions, such as age_restriction. - // extensions?: { name: ExtensionManifest }; - - // // Signature by the exchange master key of the SHA-256 hash of the - // // normalized JSON-object of field extensions, if it was set. - // // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS. - // extensions_sig?: EddsaSignature; - } - - interface ExtensionManifest { - // The criticality of the extension MUST be provided. It has the same - // semantics as "critical" has for extensions in X.509: - // - if "true", the client must "understand" the extension before - // proceeding, - // - if "false", clients can safely skip extensions they do not - // understand. - // (see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2) - critical: boolean; - - // The version information MUST be provided in Taler's protocol version - // ranges notation, see - // https://docs.taler.net/core/api-common.html#protocol-version-ranges - version: LibtoolVersion; - - // Optional configuration object, defined by the feature itself - config?: object; - } - - interface SignKey { - // The actual exchange's EdDSA signing public key. - key: EddsaPublicKey; - - // Initial validity date for the signing key. - stamp_start: Timestamp; - - // Date when the exchange will stop using the signing key, allowed to overlap - // slightly with the next signing key's validity to allow for clock skew. - stamp_expire: Timestamp; - - // Date when all signatures made by the signing key expire and should - // henceforth no longer be considered valid in legal disputes. - stamp_end: Timestamp; - - // Signature over key and stamp_expire by the exchange master key. - // Signature of TALER_ExchangeSigningKeyValidityPS. - // Must have purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY. - master_sig: EddsaSignature; - } - - interface AuditorKeys { - // The auditor's EdDSA signing public key. - auditor_pub: EddsaPublicKey; - - // The auditor's URL. - auditor_url: string; - - // The auditor's name (for humans). - auditor_name: string; - - // An array of denomination keys the auditor affirms with its signature. - // Note that the message only includes the hash of the public key, while the - // signature is actually over the expanded information including expiration - // times and fees. The exact format is described below. - denomination_keys: AuditorDenominationKey[]; - } - interface AuditorDenominationKey { - // Hash of the public RSA key used to sign coins of the respective - // denomination. Note that the auditor's signature covers more than just - // the hash, but this other information is already provided in denoms and - // thus not repeated here. - denom_pub_h: HashCode; - - // Signature of TALER_ExchangeKeyValidityPS. - auditor_sig: EddsaSignature; - } - - interface GlobalFees { - // What date (inclusive) does these fees go into effect? - start_date: Timestamp; - - // What date (exclusive) does this fees stop going into effect? - end_date: Timestamp; - - // Account history fee, charged when a user wants to - // obtain a reserve/account history. - history_fee: AmountString; - - // Annual fee charged for having an open account at the - // exchange. Charged to the account. If the account - // balance is insufficient to cover this fee, the account - // is automatically deleted/closed. (Note that the exchange - // will keep the account history around for longer for - // regulatory reasons.) - account_fee: AmountString; - - // Purse fee, charged only if a purse is abandoned - // and was not covered by the account limit. - purse_fee: AmountString; - - // How long will the exchange preserve the account history? - // After an account was deleted/closed, the exchange will - // retain the account history for legal reasons until this time. - history_expiration: RelativeTime; - - // Non-negative number of concurrent purses that any - // account holder is allowed to create without having - // to pay the purse_fee. - purse_account_limit: Integer; - - // How long does an exchange keep a purse around after a purse - // has expired (or been successfully merged)? A 'GET' request - // for a purse will succeed until the purse expiration time - // plus this value. - purse_timeout: RelativeTime; - - // Signature of TALER_GlobalFeesPS. - master_sig: EddsaSignature; - } - - interface Recoup { - // Hash of the public key of the denomination that is being revoked under - // emergency protocol (see /recoup). - h_denom_pub: HashCode; - - // We do not include any signature here, as the primary use-case for - // this emergency involves the exchange having lost its signing keys, - // so such a signature here would be pretty worthless. However, the - // exchange will not honor /recoup requests unless they are for - // denomination keys listed here. - } - - interface AggregateTransferFee { - // Per transfer wire transfer fee. - wire_fee: AmountString; - - // Per transfer closing fee. - closing_fee: AmountString; - - // What date (inclusive) does this fee go into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - start_date: Timestamp; - - // What date (exclusive) does this fee stop going into effect? - // The different fees must cover the full time period in which - // any of the denomination keys are valid without overlap. - end_date: Timestamp; - - // Signature of TALER_MasterWireFeePS with - // purpose TALER_SIGNATURE_MASTER_WIRE_FEES. - sig: EddsaSignature; - } - - interface ExchangePartner { - // Base URL of the partner exchange. - partner_base_url: string; - - // Public master key of the partner exchange. - partner_master_pub: EddsaPublicKey; - - // Per exchange-to-exchange transfer (wad) fee. - wad_fee: AmountString; - - // Exchange-to-exchange wad (wire) transfer frequency. - wad_frequency: RelativeTime; - - // When did this partnership begin (under these conditions)? - start_date: Timestamp; - - // How long is this partnership expected to last? - end_date: Timestamp; - - // Signature using the exchange's offline key over - // TALER_WadPartnerSignaturePS - // with purpose TALER_SIGNATURE_MASTER_PARTNER_DETAILS. - master_sig: EddsaSignature; - } - - type DenomGroup = - | DenomGroupRsa - | DenomGroupCs - | DenomGroupRsaAgeRestricted - | DenomGroupCsAgeRestricted; - interface DenomGroupRsa extends DenomGroupCommon { - cipher: "RSA"; - - denoms: ({ - rsa_pub: RsaPublicKey; - } & DenomCommon)[]; - } - interface DenomGroupCs extends DenomGroupCommon { - cipher: "CS"; - - denoms: ({ - cs_pub: Cs25519Point; - } & DenomCommon)[]; - } - - // Binary representation of the age groups. - // The bits set in the mask mark the edges at the beginning of a next age - // group. F.e. for the age groups - // 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-21, 21-* - // the following bits are set: - // - // 31 24 16 8 0 - // | | | | | - // oooooooo oo1oo1o1 o1o1o1o1 ooooooo1 - // - // A value of 0 means that the exchange does not support the extension for - // age-restriction. - type AgeMask = Integer; - - interface DenomGroupRsaAgeRestricted extends DenomGroupCommon { - cipher: "RSA+age_restricted"; - age_mask: AgeMask; - - denoms: ({ - rsa_pub: RsaPublicKey; - } & DenomCommon)[]; - } - interface DenomGroupCsAgeRestricted extends DenomGroupCommon { - cipher: "CS+age_restricted"; - age_mask: AgeMask; - - denoms: ({ - cs_pub: Cs25519Point; - } & DenomCommon)[]; - } - // Common attributes for all denomination groups - interface DenomGroupCommon { - // How much are coins of this denomination worth? - value: AmountString; - - // Fee charged by the exchange for withdrawing a coin of this denomination. - fee_withdraw: AmountString; - - // Fee charged by the exchange for depositing a coin of this denomination. - fee_deposit: AmountString; - - // Fee charged by the exchange for refreshing a coin of this denomination. - fee_refresh: AmountString; - - // Fee charged by the exchange for refunding a coin of this denomination. - fee_refund: AmountString; - } - interface DenomCommon { - // Signature of TALER_DenominationKeyValidityPS. - master_sig: EddsaSignature; - - // When does the denomination key become valid? - stamp_start: Timestamp; - - // When is it no longer possible to withdraw coins - // of this denomination? - stamp_expire_withdraw: Timestamp; - - // When is it no longer possible to deposit coins - // of this denomination? - stamp_expire_deposit: Timestamp; - - // Timestamp indicating by when legal disputes relating to these coins must - // be settled, as the exchange will afterwards destroy its evidence relating to - // transactions involving this coin. - stamp_expire_legal: Timestamp; - - // Set to 'true' if the exchange somehow "lost" - // the private key. The denomination was not - // necessarily revoked, but still cannot be used - // to withdraw coins at this time (theoretically, - // the private key could be recovered in the - // future; coins signed with the private key - // remain valid). - lost?: boolean; - } - type DenominationKey = RsaDenominationKey | CSDenominationKey; - interface RsaDenominationKey { - cipher: "RSA"; - - // 32-bit age mask. - age_mask: Integer; - - // RSA public key - rsa_public_key: RsaPublicKey; - } - interface CSDenominationKey { - cipher: "CS"; - - // 32-bit age mask. - age_mask: Integer; - - // Public key of the denomination. - cs_public_key: Cs25519Point; - } -} - -export namespace TalerMerchantApi { - export interface VersionResponse { - // libtool-style representation of the Merchant protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // Name of the protocol. - name: "taler-merchant"; - - // URN of the implementation (needed to interpret 'revision' in version). - // @since **v8**, may become mandatory in the future. - implementation?: string; - - // Default (!) currency supported by this backend. - // This is the currency that the backend should - // suggest by default to the user when entering - // amounts. See currencies for a list of - // supported currencies and how to render them. - currency: string; - - // How services should render currencies supported - // by this backend. Maps - // currency codes (e.g. "EUR" or "KUDOS") to - // the respective currency specification. - // All currencies in this map are supported by - // the backend. Note that the actual currency - // specifications are a *hint* for applications - // that would like *advice* on how to render amounts. - // Applications *may* ignore the currency specification - // if they know how to render currencies that they are - // used with. - currencies: { [currency: string]: CurrencySpecification }; - - // Array of exchanges trusted by the merchant. - // Since protocol **v6**. - exchanges: ExchangeConfigInfo[]; - } - - export interface ExchangeConfigInfo { - // Base URL of the exchange REST API. - base_url: string; - - // Currency for which the merchant is configured - // to trust the exchange. - // May not be the one the exchange actually uses, - // but is the only one we would trust this exchange for. - currency: string; - - // Offline master public key of the exchange. The - // /keys data must be signed with this public - // key for us to trust it. - master_pub: EddsaPublicKey; - } - export interface ClaimRequest { - // Nonce to identify the wallet that claimed the order. - nonce: string; - - // Token that authorizes the wallet to claim the order. - // *Optional* as the merchant may not have required it - // (create_token set to false in PostOrderRequest). - token?: ClaimToken; - } - - export interface ClaimResponse { - // Contract terms of the claimed order - contract_terms: ContractTerms; - - // Signature by the merchant over the contract terms. - sig: EddsaSignature; - } - - export interface PaymentResponse { - // Signature on TALER_PaymentResponsePS with the public - // key of the merchant instance. - sig: EddsaSignature; - - // Text to be shown to the point-of-sale staff as a proof of - // payment. - pos_confirmation?: string; - } - - export interface PaymentStatusRequestParams { - // Hash of the order’s contract terms (this is used to - // authenticate the wallet/customer in case - // $ORDER_ID is guessable). - // Required once an order was claimed. - contractTermHash?: string; - // Authorizes the request via the claim token that - // was returned in the PostOrderResponse. Used with - // unclaimed orders only. Whether token authorization is - // required is determined by the merchant when the - // frontend creates the order. - claimToken?: string; - // Session ID that the payment must be bound to. - // If not specified, the payment is not session-bound. - sessionId?: string; - // If specified, the merchant backend will wait up to - // timeout_ms milliseconds for completion of the payment - // before sending the HTTP response. A client must never - // rely on this behavior, as the merchant backend may return - // a response immediately. - timeout?: number; - // If set to “yes”, poll for the order’s pending refunds - // to be picked up. timeout_ms specifies how long we - // will wait for the refund. - awaitRefundObtained?: boolean; - // Indicates that we are polling for a refund above the - // given AMOUNT. timeout_ms will specify how long we - // will wait for the refund. - refund?: AmountString; - // Since protocol v9 refunded orders are only returned - // under “already_paid_order_id” if this flag is set - // explicitly to “YES”. - allowRefundedForRepurchase?: boolean; - } - export interface GetKycStatusRequestParams { - // If specified, the KYC check should return - // the KYC status only for this wire account. - // Otherwise, for all wire accounts. - wireHash?: string; - // If specified, the KYC check should return - // the KYC status only for the given exchange. - // Otherwise, for all exchanges we interacted with. - exchangeURL?: string; - // If specified, the merchant will wait up to - // timeout_ms milliseconds for the exchanges to - // confirm completion of the KYC process(es). - timeout?: number; - } - export interface GetOtpDeviceRequestParams { - // Timestamp in seconds to use when calculating - // the current OTP code of the device. Since protocol v10. - faketime?: number; - // Price to use when calculating the current OTP - // code of the device. Since protocol v10. - price?: AmountString; - } - export interface GetOrderRequestParams { - // Session ID that the payment must be bound to. - // If not specified, the payment is not session-bound. - sessionId?: string; - // Timeout in milliseconds to wait for a payment if - // the answer would otherwise be negative (long polling). - timeout?: number; - // Since protocol v9 refunded orders are only returned - // under “already_paid_order_id” if this flag is set - // explicitly to “YES”. - allowRefundedForRepurchase?: boolean; - } - export interface ListWireTransferRequestParams { - // Filter for transfers to the given bank account - // (subject and amount MUST NOT be given in the payto URI). - paytoURI?: string; - // Filter for transfers executed before the given timestamp. - before?: number; - // Filter for transfers executed after the given timestamp. - after?: number; - // At most return the given number of results. Negative for - // descending in execution time, positive for ascending in - // execution time. Default is -20. - limit?: number; - // Starting transfer_serial_id for an iteration. - offset?: string; - // Filter transfers by verification status. - verified?: boolean; - order?: "asc" | "dec"; - } - export interface ListOrdersRequestParams { - // If set to yes, only return paid orders, if no only - // unpaid orders. Do not give (or use “all”) to see all - // orders regardless of payment status. - paid?: boolean; - // If set to yes, only return refunded orders, if no only - // unrefunded orders. Do not give (or use “all”) to see - // all orders regardless of refund status. - refunded?: boolean; - // If set to yes, only return wired orders, if no only - // orders with missing wire transfers. Do not give (or - // use “all”) to see all orders regardless of wire transfer - // status. - wired?: boolean; - // At most return the given number of results. Negative - // for descending by row ID, positive for ascending by - // row ID. Default is 20. Since protocol v12. - limit?: number; - // Non-negative date in seconds after the UNIX Epoc, see delta - // for its interpretation. If not specified, we default to the - // oldest or most recent entry, depending on delta. - date?: AbsoluteTime; - // Starting product_serial_id for an iteration. - // Since protocol v12. - offset?: string; - // Timeout in milliseconds to wait for additional orders if the - // answer would otherwise be negative (long polling). Only useful - // if delta is positive. Note that the merchant MAY still return - // a response that contains fewer than delta orders. - timeout?: number; - // Since protocol v6. Filters by session ID. - sessionId?: string; - // Since protocol v6. Filters by fulfillment URL. - fulfillmentUrl?: string; - - order?: "asc" | "dec"; - } - - export interface PayRequest { - // The coins used to make the payment. - coins: CoinPaySig[]; - - // Custom inputs from the wallet for the contract. - wallet_data?: Object; - - // The session for which the payment is made (or replayed). - // Only set for session-based payments. - session_id?: string; - } - export interface CoinPaySig { - // Signature by the coin. - coin_sig: EddsaSignature; - - // Public key of the coin being spent. - coin_pub: EddsaPublicKey; - - // Signature made by the denomination public key. - ub_sig: RsaSignature; - - // The hash of the denomination public key associated with this coin. - h_denom: HashCode; - - // The amount that is subtracted from this coin with this payment. - contribution: AmountString; - - // URL of the exchange this coin was withdrawn from. - exchange_url: string; - } - - export interface StatusPaid { - type: "paid"; - - // Was the payment refunded (even partially, via refund or abort)? - refunded: boolean; - - // Is any amount of the refund still waiting to be picked up (even partially)? - refund_pending: boolean; - - // Amount that was refunded in total. - refund_amount: AmountString; - - // Amount that already taken by the wallet. - refund_taken: AmountString; - } - export interface StatusGotoResponse { - type: "goto"; - // The client should go to the reorder URL, there a fresh - // order might be created as this one is taken by another - // customer or wallet (or repurchase detection logic may - // apply). - public_reorder_url: string; - } - export interface StatusUnpaidResponse { - type: "unpaid"; - // URI that the wallet must process to complete the payment. - taler_pay_uri: string; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - fulfillment_url?: string; - - // Alternative order ID which was paid for already in the same session. - // Only given if the same product was purchased before in the same session. - already_paid_order_id?: string; - } - - export interface PaidRefundStatusResponse { - // Text to be shown to the point-of-sale staff as a proof of - // payment (present only if reusable OTP algorithm is used). - pos_confirmation?: string; - - // True if the order has been subjected to - // refunds. False if it was simply paid. - refunded: boolean; - } - export interface PaidRequest { - // Signature on TALER_PaymentResponsePS with the public - // key of the merchant instance. - sig: EddsaSignature; - - // Hash of the order's contract terms (this is used to authenticate the - // wallet/customer and to enable signature verification without - // database access). - h_contract: HashCode; - - // Hash over custom inputs from the wallet for the contract. - wallet_data_hash?: HashCode; - - // Session id for which the payment is proven. - session_id: string; - } - - export interface AbortRequest { - // Hash of the order's contract terms (this is used to authenticate the - // wallet/customer in case $ORDER_ID is guessable). - h_contract: HashCode; - - // List of coins the wallet would like to see refunds for. - // (Should be limited to the coins for which the original - // payment succeeded, as far as the wallet knows.) - coins: AbortingCoin[]; - } - interface AbortingCoin { - // Public key of a coin for which the wallet is requesting an abort-related refund. - coin_pub: EddsaPublicKey; - - // The amount to be refunded (matches the original contribution) - contribution: AmountString; - - // URL of the exchange this coin was withdrawn from. - exchange_url: string; - } - export interface AbortResponse { - // List of refund responses about the coins that the wallet - // requested an abort for. In the same order as the coins - // from the original request. - // The rtransaction_id is implied to be 0. - refunds: MerchantAbortPayRefundStatus[]; - } - export type MerchantAbortPayRefundStatus = - | MerchantAbortPayRefundSuccessStatus - | MerchantAbortPayRefundFailureStatus; - // Details about why a refund failed. - export interface MerchantAbortPayRefundFailureStatus { - // Used as tag for the sum type RefundStatus sum type. - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: Integer; - - // Taler error code from the exchange reply, if available. - exchange_code?: Integer; - - // If available, HTTP reply from the exchange. - exchange_reply?: Object; - } - // Additional details needed to verify the refund confirmation signature - // (h_contract_terms and merchant_pub) are already known - // to the wallet and thus not included. - export interface MerchantAbortPayRefundSuccessStatus { - // Used as tag for the sum type MerchantCoinRefundStatus sum type. - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // The EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund. - exchange_sig: EddsaSignature; - - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKey; - } - - export interface WalletRefundRequest { - // Hash of the order's contract terms (this is used to authenticate the - // wallet/customer). - h_contract: HashCode; - } - export interface WalletRefundResponse { - // Amount that was refunded in total. - refund_amount: AmountString; - - // Successful refunds for this payment, empty array for none. - refunds: MerchantCoinRefundStatus[]; - - // Public key of the merchant. - merchant_pub: EddsaPublicKey; - } - export type MerchantCoinRefundStatus = - | MerchantCoinRefundSuccessStatus - | MerchantCoinRefundFailureStatus; - // Details about why a refund failed. - export interface MerchantCoinRefundFailureStatus { - // Used as tag for the sum type RefundStatus sum type. - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: Integer; - - // Taler error code from the exchange reply, if available. - exchange_code?: Integer; - - // If available, HTTP reply from the exchange. - exchange_reply?: Object; - - // Refund transaction ID. - rtransaction_id: Integer; - - // Public key of a coin that was refunded. - coin_pub: EddsaPublicKey; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; - - // Timestamp when the merchant approved the refund. - // Useful for grouping refunds. - execution_time: Timestamp; - } - // Additional details needed to verify the refund confirmation signature - // (h_contract_terms and merchant_pub) are already known - // to the wallet and thus not included. - export interface MerchantCoinRefundSuccessStatus { - // Used as tag for the sum type MerchantCoinRefundStatus sum type. - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // The EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund. - exchange_sig: EddsaSignature; - - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKey; - - // Refund transaction ID. - rtransaction_id: Integer; - - // Public key of a coin that was refunded. - coin_pub: EddsaPublicKey; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; - - // Timestamp when the merchant approved the refund. - // Useful for grouping refunds. - execution_time: Timestamp; - } - - interface RewardInformation { - // Exchange from which the reward will be withdrawn. Needed by the - // wallet to determine denominations, fees, etc. - exchange_url: string; - - // URL where to go after obtaining the reward. - next_url: string; - - // (Remaining) amount of the reward (including fees). - reward_amount: AmountString; - - // Timestamp indicating when the reward is set to expire (may be in the past). - // Note that rewards that have expired MAY also result in a 404 response. - expiration: Timestamp; - } - - interface RewardPickupRequest { - // List of planchets the wallet wants to use for the reward. - planchets: PlanchetDetail[]; - } - interface PlanchetDetail { - // Hash of the denomination's public key (hashed to reduce - // bandwidth consumption). - denom_pub_hash: HashCode; - - // Coin's blinded public key. - coin_ev: CoinEnvelope; - } - interface RewardResponse { - // Blind RSA signatures over the planchets. - // The order of the signatures matches the planchets list. - blind_sigs: BlindSignature[]; - } - interface BlindSignature { - // The (blind) RSA signature. Still needs to be unblinded. - blind_sig: BlindedRsaSignature; - } - - export interface InstanceConfigurationMessage { - // Name of the merchant instance to create (will become $INSTANCE). - // Must match the regex ^[A-Za-z0-9][A-Za-z0-9_.@-]+$. - id: string; - - // Merchant name corresponding to this instance. - name: string; - - // Type of the user (business or individual). - // Defaults to 'business'. Should become mandatory field - // in the future, left as optional for API compatibility for now. - user_type?: string; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // Authentication settings for this instance - auth: InstanceAuthConfigurationMessage; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; - } - - export interface InstanceAuthConfigurationMessage { - // Type of authentication. - // "external": The mechant backend does not do - // any authentication checks. Instead an API - // gateway must do the authentication. - // "token": The merchant checks an auth token. - // See "token" for details. - method: "external" | "token"; - - // For method "token", this field is mandatory. - // The token MUST begin with the string "secret-token:". - // After the auth token has been set (with method "token"), - // the value must be provided in a "Authorization: Bearer $token" - // header. - token?: AccessToken; - } - - export interface InstanceReconfigurationMessage { - // Merchant name corresponding to this instance. - name: string; - - // Type of the user (business or individual). - // Defaults to 'business'. Should become mandatory field - // in the future, left as optional for API compatibility for now. - user_type?: string; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; - } - - export interface InstancesResponse { - // List of instances that are present in the backend (see Instance). - instances: Instance[]; - } - - export interface Instance { - // Merchant name corresponding to this instance. - name: string; - - // Type of the user ("business" or "individual"). - user_type: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // Merchant instance this response is about ($INSTANCE). - id: string; - - // Public key of the merchant/instance, in Crockford Base32 encoding. - merchant_pub: EddsaPublicKey; - - // List of the payment targets supported by this instance. Clients can - // specify the desired payment target in /order requests. Note that - // front-ends do not have to support wallets selecting payment targets. - payment_targets: string[]; - - // Has this instance been deleted (but not purged)? - deleted: boolean; - } - - export interface QueryInstancesResponse { - // Merchant name corresponding to this instance. - name: string; - - // Type of the user ("business" or "individual"). - user_type: string; - - // Merchant email for customer contact. - email?: string; - - // Merchant public website. - website?: string; - - // Merchant logo. - logo?: ImageDataUrl; - - // Public key of the merchant/instance, in Crockford Base32 encoding. - merchant_pub: EddsaPublicKey; - - // The merchant's physical address (to be put into contracts). - address: Location; - - // The jurisdiction under which the merchant conducts its business - // (to be put into contracts). - jurisdiction: Location; - - // Use STEFAN curves to determine default fees? - // If false, no fees are allowed by default. - // Can always be overridden by the frontend on a per-order basis. - use_stefan: boolean; - - // If the frontend does NOT specify an execution date, how long should - // we tell the exchange to wait to aggregate transactions before - // executing the wire transfer? This delay is added to the current - // time when we generate the advisory execution time for the exchange. - default_wire_transfer_delay: RelativeTime; - - // If the frontend does NOT specify a payment deadline, how long should - // offers we make be valid by default? - default_pay_delay: RelativeTime; - - // Authentication configuration. - // Does not contain the token when token auth is configured. - auth: { - method: "external" | "token"; - }; - } - - export interface AccountKycRedirects { - // Array of pending KYCs. - pending_kycs: MerchantAccountKycRedirect[]; - - // Array of exchanges with no reply. - timeout_kycs: ExchangeKycTimeout[]; - } - - export interface MerchantAccountKycRedirect { - // URL that the user should open in a browser to - // proceed with the KYC process (as returned - // by the exchange's /kyc-check/ endpoint). - // Optional, missing if the account is blocked - // due to AML and not due to KYC. - kyc_url?: string; - - // AML status of the account. - aml_status: Integer; - - // Base URL of the exchange this is about. - exchange_url: string; - - // Our bank wire account this is about. - payto_uri: PaytoString; - } - - export interface ExchangeKycTimeout { - // Base URL of the exchange this is about. - exchange_url: string; - - // Numeric error code indicating errors the exchange - // returned, or TALER_EC_INVALID for none. - exchange_code: number; - - // HTTP status code returned by the exchange when we asked for - // information about the KYC status. - // 0 if there was no response at all. - exchange_http_status: number; - } - - export interface AccountAddDetails { - // payto:// URI of the account. - payto_uri: PaytoString; - - // URL from where the merchant can download information - // about incoming wire transfers to this account. - credit_facade_url?: string; - - // Credentials to use when accessing the credit facade. - // Never returned on a GET (as this may be somewhat - // sensitive data). Can be set in POST - // or PATCH requests to update (or delete) credentials. - // To really delete credentials, set them to the type: "none". - credit_facade_credentials?: FacadeCredentials; - } - - export type FacadeCredentials = - | NoFacadeCredentials - | BasicAuthFacadeCredentials; - export interface NoFacadeCredentials { - type: "none"; - } - export interface BasicAuthFacadeCredentials { - type: "basic"; - - // Username to use to authenticate - username: string; - - // Password to use to authenticate - password: string; - } - export interface AccountAddResponse { - // Hash over the wire details (including over the salt). - h_wire: HashCode; - - // Salt used to compute h_wire. - salt: HashCode; - } - - export interface AccountPatchDetails { - // URL from where the merchant can download information - // about incoming wire transfers to this account. - credit_facade_url?: string; - - // Credentials to use when accessing the credit facade. - // Never returned on a GET (as this may be somewhat - // sensitive data). Can be set in POST - // or PATCH requests to update (or delete) credentials. - // To really delete credentials, set them to the type: "none". - // If the argument is omitted, the old credentials - // are simply preserved. - credit_facade_credentials?: FacadeCredentials; - } - - export interface AccountsSummaryResponse { - // List of accounts that are known for the instance. - accounts: BankAccountEntry[]; - } - - // TODO: missing in docs - export interface BankAccountEntry { - // payto:// URI of the account. - payto_uri: PaytoString; - - // Hash over the wire details (including over the salt). - h_wire: HashCode; - - // true if this account is active, - // false if it is historic. - active?: boolean; - } - export interface BankAccountDetail { - // payto:// URI of the account. - payto_uri: PaytoString; - - // Hash over the wire details (including over the salt). - h_wire: HashCode; - - // Salt used to compute h_wire. - salt: HashCode; - - // URL from where the merchant can download information - // about incoming wire transfers to this account. - credit_facade_url?: string; - - // true if this account is active, - // false if it is historic. - active?: boolean; - } - - export interface ProductAddDetail { - // Product ID to use. - product_id: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions. - description_i18n?: { [lang_tag: string]: string }; - - // Unit in which the product is measured (liters, kilograms, packages, etc.). - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: AmountString; - - // An optional base64-encoded product image. - image?: ImageDataUrl; - - // A list of taxes paid by the merchant for one unit of this product. - taxes?: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Identifies where the product is in stock. - address?: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - - export interface ProductPatchDetail { - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions. - description_i18n?: { [lang_tag: string]: string }; - - // Unit in which the product is measured (liters, kilograms, packages, etc.). - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: AmountString; - - // An optional base64-encoded product image. - image?: ImageDataUrl; - - // A list of taxes paid by the merchant for one unit of this product. - taxes?: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.). - total_lost?: Integer; - - // Identifies where the product is in stock. - address?: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age?: Integer; - } - - export interface InventorySummaryResponse { - // List of products that are present in the inventory. - products: InventoryEntry[]; - } - - export interface InventoryEntry { - // Product identifier, as found in the product. - product_id: string; - // product_serial_id of the product in the database. - product_serial: Integer; - } - - export interface FullInventoryDetailsResponse { - // List of products that are present in the inventory. - products: MerchantPosProductDetail[]; - - // List of categories in the inventory. - categories: MerchantCategory[]; - } - - export interface MerchantPosProductDetail { - // A unique numeric ID of the product - product_serial: number; - - // A merchant-internal unique identifier for the product - product_id?: string; - - // A list of category IDs this product belongs to. - // Typically, a product only belongs to one category, but more than one is supported. - categories: number[]; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions. - description_i18n: { [lang_tag: string]: string }; - - // Unit in which the product is measured (liters, kilograms, packages, etc.). - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: AmountString; - - // An optional base64-encoded product image. - image?: ImageDataUrl; - - // A list of taxes paid by the merchant for one unit of this product. - taxes?: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // Optional, if missing treat as "infinite". - total_stock?: Integer; - - // Minimum age buyer must have (in years). - minimum_age?: Integer; - } - - export interface MerchantCategory { - // A unique numeric ID of the category - id: number; - - // The name of the category. This will be shown to users and used in the order summary. - name: string; - - // Map from IETF BCP 47 language tags to localized names - name_i18n?: { [lang_tag: string]: string }; - } - - export interface ProductDetail { - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions. - description_i18n: { [lang_tag: string]: string }; - - // Unit in which the product is measured (liters, kilograms, packages, etc.). - unit: string; - - // The price for one unit of the product. Zero is used - // to imply that this product is not sold separately, or - // that the price is not fixed, and must be supplied by the - // front-end. If non-zero, this price MUST include applicable - // taxes. - price: AmountString; - - // An optional base64-encoded product image. - image: ImageDataUrl; - - // A list of taxes paid by the merchant for one unit of this product. - taxes?: Tax[]; - - // Number of units of the product in stock in sum in total, - // including all existing sales ever. Given in product-specific - // units. - // A value of -1 indicates "infinite" (i.e. for "electronic" books). - total_stock: Integer; - - // Number of units of the product that have already been sold. - total_sold: Integer; - - // Number of units of the product that were lost (spoiled, stolen, etc.). - total_lost: Integer; - - // Identifies where the product is in stock. - address?: Location; - - // Identifies when we expect the next restocking to happen. - next_restock?: Timestamp; - - // Minimum age buyer must have (in years). - minimum_age?: Integer; - } - export interface LockRequest { - // UUID that identifies the frontend performing the lock - // Must be unique for the lifetime of the lock. - lock_uuid: string; - - // How long does the frontend intend to hold the lock? - duration: RelativeTime; - - // How many units should be locked? - quantity: Integer; - } - - export interface PostOrderRequest { - // The order must at least contain the minimal - // order detail, but can override all. - order: Order; - - // If set, the backend will then set the refund deadline to the current - // time plus the specified delay. If it's not set, refunds will not be - // possible. - refund_delay?: RelativeTime; - - // Specifies the payment target preferred by the client. Can be used - // to select among the various (active) wire methods supported by the instance. - payment_target?: string; - - // Specifies that some products are to be included in the - // order from the inventory. For these inventory management - // is performed (so the products must be in stock) and - // details are completed from the product data of the backend. - inventory_products?: MinimalInventoryProduct[]; - - // Specifies a lock identifier that was used to - // lock a product in the inventory. Only useful if - // inventory_products is set. Used in case a frontend - // reserved quantities of the individual products while - // the shopping cart was being built. Multiple UUIDs can - // be used in case different UUIDs were used for different - // products (i.e. in case the user started with multiple - // shopping sessions that were combined during checkout). - lock_uuids?: string[]; - - // Should a token for claiming the order be generated? - // False can make sense if the ORDER_ID is sufficiently - // high entropy to prevent adversarial claims (like it is - // if the backend auto-generates one). Default is 'true'. - create_token?: boolean; - - // OTP device ID to associate with the order. - // This parameter is optional. - otp_id?: string; - } - - export type Order = MinimalOrderDetail & Partial<ContractTerms>; - - export interface MinimalOrderDetail { - // Amount to be paid by the customer. - amount: AmountString; - - // Short summary of the order. - summary: string; - - // See documentation of fulfillment_url in ContractTerms. - // Either fulfillment_url or fulfillment_message must be specified. - // When creating an order, the fulfillment URL can - // contain ${ORDER_ID} which will be substituted with the - // order ID of the newly created order. - fulfillment_url?: string; - - // See documentation of fulfillment_message in ContractTerms. - // Either fulfillment_url or fulfillment_message must be specified. - fulfillment_message?: string; - } - - export interface MinimalInventoryProduct { - // Which product is requested (here mandatory!). - product_id: string; - - // How many units of the product are requested. - quantity: Integer; - } - - export interface PostOrderResponse { - // Order ID of the response that was just created. - order_id: string; - - // Token that authorizes the wallet to claim the order. - // Provided only if "create_token" was set to 'true' - // in the request. - token?: ClaimToken; - } - export interface OutOfStockResponse { - // Product ID of an out-of-stock item. - product_id: string; - - // Requested quantity. - requested_quantity: Integer; - - // Available quantity (must be below requested_quantity). - available_quantity: Integer; - - // When do we expect the product to be again in stock? - // Optional, not given if unknown. - restock_expected?: Timestamp; - } - - export interface OrderHistory { - // Timestamp-sorted array of all orders matching the query. - // The order of the sorting depends on the sign of delta. - orders: OrderHistoryEntry[]; - } - export interface OrderHistoryEntry { - // Order ID of the transaction related to this entry. - order_id: string; - - // Row ID of the order in the database. - row_id: number; - - // When the order was created. - timestamp: Timestamp; - - // The amount of money the order is for. - amount: AmountString; - - // The summary of the order. - summary: string; - - // Whether some part of the order is refundable, - // that is the refund deadline has not yet expired - // and the total amount refunded so far is below - // the value of the original transaction. - refundable: boolean; - - // Whether the order has been paid or not. - paid: boolean; - } - - export type MerchantOrderStatusResponse = - | CheckPaymentPaidResponse - | CheckPaymentClaimedResponse - | CheckPaymentUnpaidResponse; - export interface CheckPaymentPaidResponse { - // The customer paid for this contract. - order_status: "paid"; - - // Was the payment refunded (even partially)? - refunded: boolean; - - // True if there are any approved refunds that the wallet has - // not yet obtained. - refund_pending: boolean; - - // Did the exchange wire us the funds? - wired: boolean; - - // Total amount the exchange deposited into our bank account - // for this contract, excluding fees. - deposit_total: AmountString; - - // Numeric error code indicating errors the exchange - // encountered tracking the wire transfer for this purchase (before - // we even got to specific coin issues). - // 0 if there were no issues. - exchange_code: number; - - // HTTP status code returned by the exchange when we asked for - // information to track the wire transfer for this purchase. - // 0 if there were no issues. - exchange_http_status: number; - - // Total amount that was refunded, 0 if refunded is false. - refund_amount: AmountString; - - // Contract terms. - contract_terms: ContractTerms; - - // The wire transfer status from the exchange for this order if - // available, otherwise empty array. - wire_details: TransactionWireTransfer[]; - - // Reports about trouble obtaining wire transfer details, - // empty array if no trouble were encountered. - wire_reports: TransactionWireReport[]; - - // The refund details for this order. One entry per - // refunded coin; empty array if there are no refunds. - refund_details: RefundDetails[]; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - order_status_url: string; - } - export interface CheckPaymentClaimedResponse { - // A wallet claimed the order, but did not yet pay for the contract. - order_status: "claimed"; - - // Contract terms. - contract_terms: ContractTerms; - } - export interface CheckPaymentUnpaidResponse { - // The order was neither claimed nor paid. - order_status: "unpaid"; - - // URI that the wallet must process to complete the payment. - taler_pay_uri: string; - - // when was the order created - creation_time: Timestamp; - - // Order summary text. - summary: string; - - // Total amount of the order (to be paid by the customer). - total_amount: AmountString; - - // Alternative order ID which was paid for already in the same session. - // Only given if the same product was purchased before in the same session. - already_paid_order_id?: string; - - // Fulfillment URL of an already paid order. Only given if under this - // session an already paid order with a fulfillment URL exists. - already_paid_fulfillment_url?: string; - - // Status URL, can be used as a redirect target for the browser - // to show the order QR code / trigger the wallet. - order_status_url: string; - - // We do we NOT return the contract terms here because they may not - // exist in case the wallet did not yet claim them. - } - export interface RefundDetails { - // Reason given for the refund. - reason: string; - - // Set to true if a refund is still available for the wallet for this payment. - pending: boolean; - - // When was the refund approved. - timestamp: Timestamp; - - // Total amount that was refunded (minus a refund fee). - amount: AmountString; - } - export interface TransactionWireTransfer { - // Responsible exchange. - exchange_url: string; - - // 32-byte wire transfer identifier. - wtid: Base32; - - // Execution time of the wire transfer. - execution_time: Timestamp; - - // Total amount that has been wire transferred - // to the merchant. - amount: AmountString; - - // Was this transfer confirmed by the merchant via the - // POST /transfers API, or is it merely claimed by the exchange? - confirmed: boolean; - } - export interface TransactionWireReport { - // Numerical error code. - code: number; - - // Human-readable error description. - hint: string; - - // Numerical error code from the exchange. - exchange_code: number; - - // HTTP status code received from the exchange. - exchange_http_status: number; - - // Public key of the coin for which we got the exchange error. - coin_pub: CoinPublicKey; - } - - export interface ForgetRequest { - // Array of valid JSON paths to forgettable fields in the order's - // contract terms. - fields: string[]; - } - - export interface RefundRequest { - // Amount to be refunded. - refund: AmountString; - - // Human-readable refund justification. - reason: string; - } - export interface MerchantRefundResponse { - // URL (handled by the backend) that the wallet should access to - // trigger refund processing. - // taler://refund/... - taler_refund_uri: string; - - // Contract hash that a client may need to authenticate an - // HTTP request to obtain the above URI in a wallet-friendly way. - h_contract: HashCode; - } - - export interface TransferInformation { - // How much was wired to the merchant (minus fees). - credit_amount: AmountString; - - // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value). - wtid: WireTransferIdentifierRawP; - - // Target account that received the wire transfer. - payto_uri: PaytoString; - - // Base URL of the exchange that made the wire transfer. - exchange_url: string; - } - - export interface TransferList { - // List of all the transfers that fit the filter that we know. - transfers: TransferDetails[]; - } - export interface TransferDetails { - // How much was wired to the merchant (minus fees). - credit_amount: AmountString; - - // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value). - wtid: WireTransferIdentifierRawP; - - // Target account that received the wire transfer. - payto_uri: PaytoString; - - // Base URL of the exchange that made the wire transfer. - exchange_url: string; - - // Serial number identifying the transfer in the merchant backend. - // Used for filtering via offset. - transfer_serial_id: number; - - // Time of the execution of the wire transfer by the exchange, according to the exchange - // Only provided if we did get an answer from the exchange. - execution_time?: Timestamp; - - // True if we checked the exchange's answer and are happy with it. - // False if we have an answer and are unhappy, missing if we - // do not have an answer from the exchange. - verified?: boolean; - - // True if the merchant uses the POST /transfers API to confirm - // that this wire transfer took place (and it is thus not - // something merely claimed by the exchange). - confirmed?: boolean; - } - - export interface OtpDeviceAddDetails { - // Device ID to use. - otp_device_id: string; - - // Human-readable description for the device. - otp_device_description: string; - - // A key encoded with RFC 3548 Base32. - // IMPORTANT: This is not using the typical - // Taler base32-crockford encoding. - // Instead it uses the RFC 3548 encoding to - // be compatible with the TOTP standard. - otp_key: string; - - // Algorithm for computing the POS confirmation. - // "NONE" or 0: No algorithm (no pos confirmation will be generated) - // "TOTP_WITHOUT_PRICE" or 1: Without amounts (typical OTP device) - // "TOTP_WITH_PRICE" or 2: With amounts (special-purpose OTP device) - // The "String" variants are supported @since protocol **v7**. - otp_algorithm: Integer | string; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; - } - - export interface OtpDevicePatchDetails { - // Human-readable description for the device. - otp_device_description: string; - - // A key encoded with RFC 3548 Base32. - // IMPORTANT: This is not using the typical - // Taler base32-crockford encoding. - // Instead it uses the RFC 3548 encoding to - // be compatible with the TOTP standard. - otp_key: string; - - // Algorithm for computing the POS confirmation. - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; - } - - export interface OtpDeviceSummaryResponse { - // Array of devices that are present in our backend. - otp_devices: OtpDeviceEntry[]; - } - export interface OtpDeviceEntry { - // Device identifier. - otp_device_id: string; - - // Human-readable description for the device. - device_description: string; - } - - export interface OtpDeviceDetails { - // Human-readable description for the device. - device_description: string; - - // Algorithm for computing the POS confirmation. - // - // Currently, the following numbers are defined: - // 0: None - // 1: TOTP without price - // 2: TOTP with price - otp_algorithm: Integer; - - // Counter for counter-based OTP devices. - otp_ctr?: Integer; - - // Current time for time-based OTP devices. - // Will match the faketime argument of the - // query if one was present, otherwise the current - // time at the backend. - // - // Available since protocol **v10**. - otp_timestamp: Integer; - - // Current OTP confirmation string of the device. - // Matches exactly the string that would be returned - // as part of a payment confirmation for the given - // amount and time (so may contain multiple OTP codes). - // - // If the otp_algorithm is time-based, the code is - // returned for the current time, or for the faketime - // if a TIMESTAMP query argument was provided by the client. - // - // When using OTP with counters, the counter is **NOT** - // increased merely because this endpoint created - // an OTP code (this is a GET request, after all!). - // - // If the otp_algorithm requires an amount, the - // amount argument must be specified in the - // query, otherwise the otp_code is not - // generated. - // - // This field is *optional* in the response, as it is - // only provided if we could compute it based on the - // otp_algorithm and matching client query arguments. - // - // Available since protocol **v10**. - otp_code?: string; - } - export interface TemplateAddDetails { - // Template ID to use. - template_id: string; - - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; - - // Key-value pairs matching a subset of the - // fields from template_contract that are - // user-editable defaults for this template. - // Since protocol **v13**. - editable_defaults?: TemplateContractDetailsDefaults; - } - export interface TemplateContractDetails { - // Human-readable summary for the template. - summary?: string; - - // Required currency for payments to the template. - // The user may specify any amount, but it must be - // in this currency. - // This parameter is optional and should not be present - // if "amount" is given. - currency?: string; - - // The price is imposed by the merchant and cannot be changed by the customer. - // This parameter is optional. - amount?: AmountString; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age: Integer; - - // The time the customer need to pay before his order will be deleted. - // It is deleted if the customer did not pay and if the duration is over. - pay_duration: RelativeTime; - } - - export interface TemplateContractDetailsDefaults { - summary?: string; - - currency?: string; - - /** - * Amount *or* a plain currency string. - */ - amount?: string; - } - - export interface TemplatePatchDetails { - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; - - // Key-value pairs matching a subset of the - // fields from template_contract that are - // user-editable defaults for this template. - // Since protocol **v13**. - editable_defaults?: TemplateContractDetailsDefaults; - } - - export interface TemplateSummaryResponse { - // List of templates that are present in our backend. - templates: TemplateEntry[]; - } - - export interface TemplateEntry { - // Template identifier, as found in the template. - template_id: string; - - // Human-readable description for the template. - template_description: string; - } - - export interface WalletTemplateDetails { - // Hard-coded information about the contrac terms - // for this template. - template_contract: TemplateContractDetails; - - // Key-value pairs matching a subset of the - // fields from template_contract that are - // user-editable defaults for this template. - // Since protocol **v13**. - editable_defaults?: TemplateContractDetailsDefaults; - } - - export interface TemplateDetails { - // Human-readable description for the template. - template_description: string; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - - // Additional information in a separate template. - template_contract: TemplateContractDetails; - - // Key-value pairs matching a subset of the - // fields from template_contract that are - // user-editable defaults for this template. - // Since protocol **v13**. - editable_defaults?: TemplateContractDetailsDefaults; - } - export interface UsingTemplateDetails { - // Summary of the template - summary?: string; - - // The amount entered by the customer. - amount?: AmountString; - } - - export interface WebhookAddDetails { - // Webhook ID to use. - webhook_id: string; - - // The event of the webhook: why the webhook is used. - event_type: string; - - // URL of the webhook where the customer will be redirected. - url: string; - - // Method used by the webhook - http_method: string; - - // Header template of the webhook - header_template?: string; - - // Body template by the webhook - body_template?: string; - } - - export interface WebhookPatchDetails { - // The event of the webhook: why the webhook is used. - event_type: string; - - // URL of the webhook where the customer will be redirected. - url: string; - - // Method used by the webhook - http_method: string; - - // Header template of the webhook - header_template?: string; - - // Body template by the webhook - body_template?: string; - } - - export interface WebhookSummaryResponse { - // Return webhooks that are present in our backend. - webhooks: WebhookEntry[]; - } - - export interface WebhookEntry { - // Webhook identifier, as found in the webhook. - webhook_id: string; - - // The event of the webhook: why the webhook is used. - event_type: string; - } - - export interface WebhookDetails { - // The event of the webhook: why the webhook is used. - event_type: string; - - // URL of the webhook where the customer will be redirected. - url: string; - - // Method used by the webhook - http_method: string; - - // Header template of the webhook - header_template?: string; - - // Body template by the webhook - body_template?: string; - } - - export interface TokenFamilyCreateRequest { - // Identifier for the token family consisting of unreserved characters - // according to RFC 3986. - slug: string; - - // Human-readable name for the token family. - name: string; - - // Human-readable description for the token family. - description: string; - - // Optional map from IETF BCP 47 language tags to localized descriptions. - description_i18n?: { [lang_tag: string]: string }; - - // Start time of the token family's validity period. - // If not specified, merchant backend will use the current time. - valid_after?: Timestamp; - - // End time of the token family's validity period. - valid_before: Timestamp; - - // Validity duration of an issued token. - duration: RelativeTime; - - // Kind of the token family. - kind: TokenFamilyKind; - } - - export enum TokenFamilyKind { - Discount = "discount", - Subscription = "subscription", - } - - export interface TokenFamilyUpdateRequest { - // Human-readable name for the token family. - name: string; - - // Human-readable description for the token family. - description: string; - - // Optional map from IETF BCP 47 language tags to localized descriptions. - description_i18n: { [lang_tag: string]: string }; - - // Start time of the token family's validity period. - valid_after: Timestamp; - - // End time of the token family's validity period. - valid_before: Timestamp; - - // Validity duration of an issued token. - duration: RelativeTime; - } - - export interface TokenFamiliesList { - // All configured token families of this instance. - token_families: TokenFamilySummary[]; - } - - export interface TokenFamilySummary { - // Identifier for the token family consisting of unreserved characters - // according to RFC 3986. - slug: string; - - // Human-readable name for the token family. - name: string; - - // Start time of the token family's validity period. - valid_after: Timestamp; - - // End time of the token family's validity period. - valid_before: Timestamp; - - // Kind of the token family. - kind: TokenFamilyKind; - } - - export interface TokenFamilyDetails { - // Identifier for the token family consisting of unreserved characters - // according to RFC 3986. - slug: string; - - // Human-readable name for the token family. - name: string; - - // Human-readable description for the token family. - description: string; - - // Optional map from IETF BCP 47 language tags to localized descriptions. - description_i18n?: { [lang_tag: string]: string }; - - // Start time of the token family's validity period. - valid_after: Timestamp; - - // End time of the token family's validity period. - valid_before: Timestamp; - - // Validity duration of an issued token. - duration: RelativeTime; - - // Kind of the token family. - kind: TokenFamilyKind; - - // How many tokens have been issued for this family. - issued: Integer; - - // How many tokens have been redeemed for this family. - redeemed: Integer; - } - export interface ContractTerms { - // Human-readable description of the whole purchase. - summary: string; - - // Map from IETF BCP 47 language tags to localized summaries. - summary_i18n?: { [lang_tag: string]: string }; - - // Unique, free-form identifier for the proposal. - // Must be unique within a merchant instance. - // For merchants that do not store proposals in their DB - // before the customer paid for them, the order_id can be used - // by the frontend to restore a proposal from the information - // encoded in it (such as a short product identifier and timestamp). - order_id: string; - - // Total price for the transaction. - // The exchange will subtract deposit fees from that amount - // before transferring it to the merchant. - amount: AmountString; - - // URL where the same contract could be ordered again (if - // available). Returned also at the public order endpoint - // for people other than the actual buyer (hence public, - // in case order IDs are guessable). - public_reorder_url?: string; - - // URL that will show that the order was successful after - // it has been paid for. Optional. When POSTing to the - // merchant, the placeholder "${ORDER_ID}" will be - // replaced with the actual order ID (useful if the - // order ID is generated server-side and needs to be - // in the URL). - // Note that this placeholder can only be used once. - // Either fulfillment_url or fulfillment_message must be specified. - fulfillment_url?: string; - - // Message shown to the customer after paying for the order. - // Either fulfillment_url or fulfillment_message must be specified. - fulfillment_message?: string; - - // Map from IETF BCP 47 language tags to localized fulfillment - // messages. - fulfillment_message_i18n?: { [lang_tag: string]: string }; - - // Maximum total deposit fee accepted by the merchant for this contract. - // Overrides defaults of the merchant instance. - max_fee: AmountString; - - // List of products that are part of the purchase (see Product). - products: Product[]; - - // Time when this contract was generated. - timestamp: Timestamp; - - // After this deadline has passed, no refunds will be accepted. - refund_deadline: Timestamp; - - // After this deadline, the merchant won't accept payments for the contract. - pay_deadline: Timestamp; - - // Transfer deadline for the exchange. Must be in the - // deposit permissions of coins used to pay for this order. - wire_transfer_deadline: Timestamp; - - // Merchant's public key used to sign this proposal; this information - // is typically added by the backend. Note that this can be an ephemeral key. - merchant_pub: EddsaPublicKey; - - // Base URL of the (public!) merchant backend API. - // Must be an absolute URL that ends with a slash. - merchant_base_url: string; - - // More info about the merchant, see below. - merchant: Merchant; - - // The hash of the merchant instance's wire details. - h_wire: HashCode; - - // Wire transfer method identifier for the wire method associated with h_wire. - // The wallet may only select exchanges via a matching auditor if the - // exchange also supports this wire method. - // The wire transfer fees must be added based on this wire transfer method. - wire_method: string; - - // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - exchanges: Exchange[]; - - // Delivery location for (all!) products. - delivery_location?: Location; - - // Time indicating when the order should be delivered. - // May be overwritten by individual products. - delivery_date?: Timestamp; - - // Nonce generated by the wallet and echoed by the merchant - // in this field when the proposal is generated. - nonce: string; - - // Specifies for how long the wallet should try to get an - // automatic refund for the purchase. If this field is - // present, the wallet should wait for a few seconds after - // the purchase and then automatically attempt to obtain - // a refund. The wallet should probe until "delay" - // after the payment was successful (i.e. via long polling - // or via explicit requests with exponential back-off). - // - // In particular, if the wallet is offline - // at that time, it MUST repeat the request until it gets - // one response from the merchant after the delay has expired. - // If the refund is granted, the wallet MUST automatically - // recover the payment. This is used in case a merchant - // knows that it might be unable to satisfy the contract and - // desires for the wallet to attempt to get the refund without any - // customer interaction. Note that it is NOT an error if the - // merchant does not grant a refund. - auto_refund?: RelativeTime; - - // Extra data that is only interpreted by the merchant frontend. - // Useful when the merchant needs to store extra information on a - // contract without storing it separately in their database. - extra?: any; - - // Minimum age the buyer must have (in years). Default is 0. - // This value is at least as large as the maximum over all - // minimum age requirements of the products in this contract. - // It might also be set independent of any product, due to - // legal requirements. - minimum_age?: Integer; - } - - export interface Product { - // Merchant-internal identifier for the product. - product_id?: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions. - description_i18n?: { [lang_tag: string]: string }; - - // The number of units of the product to deliver to the customer. - quantity?: Integer; - - // Unit in which the product is measured (liters, kilograms, packages, etc.). - unit?: string; - - // The price of the product; this is the total price for quantity times unit of this product. - price?: AmountString; - - // An optional base64-encoded product image. - image?: ImageDataUrl; - - // A list of taxes paid by the merchant for this product. Can be empty. - taxes?: Tax[]; - - // Time indicating when this product should be delivered. - delivery_date?: Timestamp; - } - - export interface Tax { - // The name of the tax. - name: string; - - // Amount paid in tax. - tax: AmountString; - } - export interface Merchant { - // The merchant's legal name of business. - name: string; - - // Label for a location with the business address of the merchant. - email?: string; - - // Label for a location with the business address of the merchant. - website?: string; - - // An optional base64-encoded product image. - logo?: ImageDataUrl; - - // Label for a location with the business address of the merchant. - address?: Location; - - // Label for a location that denotes the jurisdiction for disputes. - // Some of the typical fields for a location (such as a street address) may be absent. - jurisdiction?: Location; - } - // Delivery location, loosely modeled as a subset of - // ISO20022's PostalAddress25. - export interface Location { - // Nation with its own government. - country?: string; - - // Identifies a subdivision of a country such as state, region, county. - country_subdivision?: string; - - // Identifies a subdivision within a country sub-division. - district?: string; - - // Name of a built-up area, with defined boundaries, and a local government. - town?: string; - - // Specific location name within the town. - town_location?: string; - - // Identifier consisting of a group of letters and/or numbers that - // is added to a postal address to assist the sorting of mail. - post_code?: string; - - // Name of a street or thoroughfare. - street?: string; - - // Name of the building or house. - building_name?: string; - - // Number that identifies the position of a building on a street. - building_number?: string; - - // Free-form address lines, should not exceed 7 elements. - address_lines?: string[]; - } - interface Auditor { - // Official name. - name: string; - - // Auditor's public key. - auditor_pub: EddsaPublicKey; - - // Base URL of the auditor. - url: string; - } - export interface Exchange { - // The exchange's base URL. - url: string; - - // How much would the merchant like to use this exchange. - // The wallet should use a suitable exchange with high - // priority. The following priority values are used, but - // it should be noted that they are NOT in any way normative. - // - // 0: likely it will not work (recently seen with account - // restriction that would be bad for this merchant) - // 512: merchant does not know, might be down (merchant - // did not yet get /wire response). - // 1024: good choice (recently confirmed working) - priority: Integer; - - // Master public key of the exchange. - master_pub: EddsaPublicKey; - } - - export interface MerchantReserveCreateConfirmation { - // Public key identifying the reserve. - reserve_pub: EddsaPublicKey; - - // Wire accounts of the exchange where to transfer the funds. - accounts: ExchangeWireAccount[]; - } - - export interface TemplateEditableDetails { - // Human-readable summary for the template. - summary?: string; - - // Required currency for payments to the template. - // The user may specify any amount, but it must be - // in this currency. - // This parameter is optional and should not be present - // if "amount" is given. - currency?: string; - - // The price is imposed by the merchant and cannot be changed by the customer. - // This parameter is optional. - amount?: AmountString; - } - - export interface MerchantTemplateContractDetails { - // Human-readable summary for the template. - summary?: string; - - // The price is imposed by the merchant and cannot be changed by the customer. - // This parameter is optional. - amount?: string; - - // Minimum age buyer must have (in years). Default is 0. - minimum_age: number; - - // The time the customer need to pay before his order will be deleted. - // It is deleted if the customer did not pay and if the duration is over. - pay_duration: TalerProtocolDuration; - } - - export interface MerchantTemplateAddDetails { - // Template ID to use. - template_id: string; - - // Human-readable description for the template. - template_description: string; - - // A base64-encoded image selected by the merchant. - // This parameter is optional. - // We are not sure about it. - image?: string; - - editable_defaults?: TemplateEditableDetails; - - // Additional information in a separate template. - template_contract: MerchantTemplateContractDetails; - - // OTP device ID. - // This parameter is optional. - otp_id?: string; - } -} - -export namespace ChallengerApi { - export interface ChallengerTermsOfServiceResponse { - // Name of the service - name: "challenger"; - - // libtool-style representation of the Challenger protocol version, see - // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning - // The format is "current:revision:age". - version: string; - - // URN of the implementation (needed to interpret 'revision' in version). - // @since v0, may become mandatory in the future. - implementation?: string; - - // Object; map of keys (names of the fields of the address - // to be entered by the user) to objects with a "regex" (string) - // containing an extended Posix regular expression for allowed - // address field values, and a "hint"/"hint_i18n" giving a - // human-readable explanation to display if the value entered - // by the user does not match the regex. Keys that are not mapped - // to such an object have no restriction on the value provided by - // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration. - restrictions: Record<string, Restriction> | undefined; - - // @since v2. - address_type: "email" | "phone"; - } - - export interface ChallengeSetupResponse { - // Nonce to use when constructing /authorize endpoint. - nonce: string; - } - - export interface Restriction { - regex?: string; - hint?: string; - hint_i18n?: InternationalizedString; - } - - export interface ChallengeStatus { - // indicates if the given address cannot be changed anymore, the - // form should be read-only if set to true. - fix_address: boolean; - - // form values from the previous submission if available, details depend - // on the ADDRESS_TYPE, should be used to pre-populate the form - last_address: Record<string, string> | undefined; - - // number of times the address can still be changed, may or may not be - // shown to the user - changes_left: Integer; - - // is the challenge already solved? - solved: boolean; - - // when we would re-transmit the challenge the next - // time (at the earliest) if requested by the user - // only present if challenge already created - // @since v2 - retransmission_time: Timestamp; - - // how many times might the PIN still be retransmitted - // only present if challenge already created - // @since v2 - pin_transmissions_left: Integer; - - // how many times might the user still try entering the PIN code - // only present if challenge already created - // @since v2 - auth_attempts_left: Integer; - } - - export type ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse; - - export interface ChallengeRedirect { - type: "completed"; - // challenge is completed, use should redirect here - redirect_url: string; - } - - export interface ChallengeCreateResponse { - type: "created"; - // how many more attempts are allowed, might be shown to the user, - // highlighting might be appropriate for low values such as 1 or 2 (the - // form will never be used if the value is zero) - attempts_left: Integer; - - // the address that is being validated, might be shown or not - address: Object; - - // true if we just retransmitted the challenge, false if we sent a - // challenge recently and thus refused to transmit it again this time; - // might make a useful hint to the user - transmitted: boolean; - - // timestamp explaining when we would re-transmit the challenge the next - // time (at the earliest) if requested by the user - retransmission_time: TalerProtocolTimestamp; - } - - export type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse; - - export interface InvalidPinResponse { - type: "pending"; - - // numeric Taler error code, should be shown to indicate the error - // compactly for reporting to developers - ec?: number; - - // human-readable Taler error code, should be shown for the user to - // understand the error - hint: string; - - // how many times is the user still allowed to change the address; - // if 0, the user should not be shown a link to jump to the - // address entry form - addresses_left: Integer; - - // how many times might the PIN still be retransmitted - pin_transmissions_left: Integer; - - // how many times might the user still try entering the PIN code - auth_attempts_left: Integer; - - // if true, the PIN was not even evaluated as the user previously - // exhausted the number of attempts - exhausted: boolean; - - // if true, the PIN was not even evaluated as no challenge was ever - // issued (the user must have skipped the step of providing their - // address first!) - no_challenge: boolean; - } - - export interface ChallengerAuthResponse { - // Token used to authenticate access in /info. - access_token: string; - - // Type of the access token. - token_type: "Bearer"; - - // Amount of time that an access token is valid (in seconds). - expires_in: Integer; - } - - export interface ChallengerInfoResponse { - // Unique ID of the record within Challenger - // (identifies the rowid of the token). - id: Integer; - - // Address that was validated. - // Key-value pairs, details depend on the - // address_type. - address: Object; - - // Type of the address. - address_type: string; - - // How long do we consider the address to be - // valid for this user. - expires: Timestamp; - } -} diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts index bf186ce46..baea3ce6f 100644 --- a/packages/taler-util/src/http-client/utils.ts +++ b/packages/taler-util/src/http-client/utils.ts @@ -19,7 +19,11 @@ */ import { base64FromArrayBuffer } from "../base64.js"; import { encodeCrock, getRandomBytes, stringToBytes } from "../taler-crypto.js"; -import { AccessToken, LongPollParams, PaginationParams } from "./types.js"; +import { + AccessToken, + LongPollParams, + PaginationParams, +} from "../types-taler-common.js"; /** * Helper function to generate the "Authorization" HTTP header. @@ -99,17 +103,17 @@ export class IdempotencyRetry { private constructor(timesLeft: number, maxTimesLeft: number) { this.timesLeft = timesLeft; this.maxTries = maxTimesLeft; - this.uid = encodeCrock(getRandomBytes(32)) + this.uid = encodeCrock(getRandomBytes(32)); } - + static tryFiveTimes() { - return new IdempotencyRetry(5, 5) + return new IdempotencyRetry(5, 5); } next(): IdempotencyRetry | undefined { - const left = this.timesLeft -1 + const left = this.timesLeft - 1; if (left <= 0) { - return undefined + return undefined; } return new IdempotencyRetry(left, this.maxTries); } diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts index 34be5a1d4..7e7173ac5 100644 --- a/packages/taler-util/src/http-common.ts +++ b/packages/taler-util/src/http-common.ts @@ -28,7 +28,7 @@ import { import { Logger } from "./logging.js"; import { TalerErrorCode } from "./taler-error-codes.js"; import { AbsoluteTime, Duration } from "./time.js"; -import { TalerErrorDetail } from "./wallet-types.js"; +import { TalerErrorDetail } from "./types-taler-wallet.js"; const textEncoder = new TextEncoder(); @@ -147,7 +147,7 @@ export async function readTalerErrorResponse( let errJson; try { errJson = await httpResponse.json(); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -155,7 +155,7 @@ export async function readTalerErrorResponse( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Couldn't parse JSON format from error response", ); @@ -188,7 +188,7 @@ export async function readUnexpectedResponseDetails( let errJson; try { errJson = await httpResponse.json(); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -196,7 +196,7 @@ export async function readUnexpectedResponseDetails( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Couldn't parse JSON format from error response", ); @@ -239,7 +239,7 @@ export async function readSuccessResponseJsonOrErrorCode<T>( let respJson; try { respJson = await httpResponse.json(); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -247,7 +247,7 @@ export async function readSuccessResponseJsonOrErrorCode<T>( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Couldn't parse JSON format from response", ); @@ -255,7 +255,7 @@ export async function readSuccessResponseJsonOrErrorCode<T>( let parsedResponse: T; try { parsedResponse = codec.decode(respJson); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -263,7 +263,7 @@ export async function readSuccessResponseJsonOrErrorCode<T>( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Response invalid", ); @@ -274,14 +274,14 @@ export async function readSuccessResponseJsonOrErrorCode<T>( }; } -export async function readResponseJsonOrErrorCode<T>( +export async function readResponseJsonOrThrow<T>( httpResponse: HttpResponse, codec: Codec<T>, -): Promise<{ isError: boolean; response: T }> { +): Promise<T> { let respJson; try { respJson = await httpResponse.json(); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -289,7 +289,7 @@ export async function readResponseJsonOrErrorCode<T>( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Couldn't parse JSON format from response", ); @@ -297,7 +297,7 @@ export async function readResponseJsonOrErrorCode<T>( let parsedResponse: T; try { parsedResponse = codec.decode(respJson); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -305,18 +305,14 @@ export async function readResponseJsonOrErrorCode<T>( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Response invalid", ); } - return { - isError: !(httpResponse.status >= 200 && httpResponse.status < 300), - response: parsedResponse, - }; + return parsedResponse; } - type HttpErrorDetails = { requestUrl: string; requestMethod: string; @@ -377,7 +373,7 @@ export async function readSuccessResponseTextOrErrorCode<T>( let errJson; try { errJson = await httpResponse.json(); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -385,7 +381,7 @@ export async function readSuccessResponseTextOrErrorCode<T>( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Couldn't parse JSON format from error response", ); @@ -423,7 +419,7 @@ export async function checkSuccessResponseOrThrow( let errJson; try { errJson = await httpResponse.json(); - } catch (e: any) { + } catch (e) { throw TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { @@ -431,7 +427,7 @@ export async function checkSuccessResponseOrThrow( requestMethod: httpResponse.requestMethod, httpStatusCode: httpResponse.status, response: await httpResponse.text(), - validationError: e.toString(), + validationError: e instanceof Error ? e.message : String(e), }, "Couldn't parse JSON format from error response", ); @@ -496,7 +492,7 @@ export interface HttpLibArgs { printAsCurl?: boolean; } -export function encodeBody(body: any): ArrayBuffer { +export function encodeBody(body: unknown): ArrayBuffer { if (body == null) { return new ArrayBuffer(0); } @@ -506,6 +502,8 @@ export function encodeBody(body: any): ArrayBuffer { return body.buffer; } else if (body instanceof ArrayBuffer) { return body; + } else if (typeof body === "object" && body.constructor.name === "FormData") { + return body as ArrayBuffer; } else if (typeof body === "object") { return textEncoder.encode(JSON.stringify(body)).buffer; } diff --git a/packages/taler-util/src/http-impl.qtart.ts b/packages/taler-util/src/http-impl.qtart.ts index 6ccd35b83..fbc80489c 100644 --- a/packages/taler-util/src/http-impl.qtart.ts +++ b/packages/taler-util/src/http-impl.qtart.ts @@ -167,7 +167,7 @@ export class HttpLibImpl implements HttpRequestLibrary { requestMethod: method, httpStatusCode: 0, }, - `Request timed out`, + `Request timed out (timeout ${opt?.timeout?.d_ms ?? "none"})`, ); } throw e; diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index 287e03584..d6a2fa614 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -2,62 +2,77 @@ import { TalerErrorCode } from "./taler-error-codes.js"; export { TalerErrorCode }; -export * from "./CancellationToken.js"; -export * from "./MerchantApiClient.js"; -export { RequestThrottler } from "./RequestThrottler.js"; -export * from "./ReserveStatus.js"; -export * from "./ReserveTransaction.js"; -export { TaskThrottler } from "./TaskThrottler.js"; -export * from "./amounts.js"; -export * from "./backup-types.js"; -export * from "./bank-api-client.js"; -export * from "./base64.js"; -export * from "./bitcoin.js"; -export * from "./codec.js"; -export * from "./contract-terms.js"; -export * from "./errors.js"; -export { fnutil } from "./fnutils.js"; -export * from "./helpers.js"; -export * from "./http-client/authentication.js"; -export * from "./http-client/bank-conversion.js"; -export * from "./http-client/bank-core.js"; -export * from "./http-client/bank-integration.js"; -export * from "./http-client/bank-revenue.js"; -export * from "./http-client/bank-wire.js"; -export * from "./http-client/challenger.js"; -export * from "./http-client/exchange.js"; -export * from "./http-client/merchant.js"; -export * from "./http-client/officer-account.js"; -export * from "./http-client/types.js"; -export { CacheEvictor } from "./http-client/utils.js"; -export * from "./http-status-codes.js"; -export * from "./i18n.js"; -export * from "./iban.js"; -export * from "./invariants.js"; -export * from "./kdf.js"; -export * from "./libeufin-api-types.js"; -export * from "./libtool-version.js"; -export * from "./logging.js"; -export { - crypto_sign_keyPair_fromSeed, - randomBytes, - secretbox, - secretbox_open, - setPRNG, -} from "./nacl-fast.js"; -export * from "./notifications.js"; -export * from "./observability.js"; -export * from "./operation.js"; -export * from "./payto.js"; -export * from "./promises.js"; -export * from "./qr.js"; -export * from "./rfc3548.js"; -export * from "./taler-crypto.js"; -export * from "./taler-types.js"; -export * from "./taleruri.js"; -export * from "./time.js"; -export * from "./timer.js"; -export * from "./transaction-test-data.js"; -export * from "./transactions-types.js"; -export * from "./url.js"; -export * from "./wallet-types.js"; + export * from "./amounts.js"; + export * from "./bank-api-client.js"; + export * from "./base64.js"; + export * from "./bitcoin.js"; + export * from "./CancellationToken.js"; + export * from "./codec.js"; + export * from "./contract-terms.js"; + export * from "./errors.js"; + export { fnutil } from "./fnutils.js"; + export * from "./helpers.js"; + export * from "./http-client/authentication.js"; + export * from "./http-client/bank-conversion.js"; + export * from "./http-client/bank-core.js"; + export * from "./http-client/bank-integration.js"; + export * from "./http-client/bank-revenue.js"; + export * from "./http-client/bank-wire.js"; + export * from "./http-client/challenger.js"; + export * from "./http-client/exchange.js"; + export * from "./http-client/merchant.js"; + export * from "./http-client/officer-account.js"; + export { CacheEvictor } from "./http-client/utils.js"; + export * from "./http-status-codes.js"; + export * from "./i18n.js"; + export * from "./iban.js"; + export * from "./invariants.js"; + export * from "./kdf.js"; + export * from "./libtool-version.js"; + export * from "./logging.js"; + export * from "./MerchantApiClient.js"; + export { + crypto_sign_keyPair_fromSeed, + randomBytes, + secretbox, + secretbox_open, + setPRNG + } from "./nacl-fast.js"; + export * from "./notifications.js"; + export * from "./observability.js"; + export * from "./operation.js"; + export * from "./payto.js"; + export * from "./promises.js"; + export * from "./qr.js"; + export { RequestThrottler } from "./RequestThrottler.js"; + export * from "./ReserveStatus.js"; + export * from "./ReserveTransaction.js"; + export * from "./rfc3548.js"; + export * from "./taler-crypto.js"; + export * from "./taleruri.js"; + export { TaskThrottler } from "./TaskThrottler.js"; + export * from "./time.js"; + export * from "./timer.js"; + export * from "./transaction-test-data.js"; + export * from "./url.js"; + +export * from "./types-taler-bank-conversion.js"; +export * from "./types-taler-bank-integration.js"; +export * from "./types-taler-common.js"; +export * from "./types-taler-corebank.js"; +export * from "./types-taler-exchange.js"; +export * from "./types-taler-merchant.js"; +export * from "./types-taler-sync.js"; +export * from "./types-taler-wallet-transactions.js"; +export * from "./types-taler-wallet.js"; + +export * as TalerBankConversionApi from "./types-taler-bank-conversion.js"; +export * as TalerBankIntegrationApi from "./types-taler-bank-integration.js"; +export * as ChallengerApi from "./types-taler-challenger.js"; +export * as TalerCorebankApi from "./types-taler-corebank.js"; +export * as TalerExchangeApi from "./types-taler-exchange.js"; +export * as TalerMerchantApi from "./types-taler-merchant.js"; +export * as TalerRevenueApi from "./types-taler-revenue.js"; +export * as TalerWireGatewayApi from "./types-taler-wire-gateway.js"; + +export * from "./taler-signatures.js"; diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts index 49952295a..c3e0070bc 100644 --- a/packages/taler-util/src/notifications.ts +++ b/packages/taler-util/src/notifications.ts @@ -23,12 +23,12 @@ * Imports. */ import { AbsoluteTime } from "./time.js"; -import { TransactionState } from "./transactions-types.js"; +import { TransactionState } from "./types-taler-wallet-transactions.js"; import { ExchangeEntryState, TalerErrorDetail, TransactionIdStr, -} from "./wallet-types.js"; +} from "./types-taler-wallet.js"; export enum NotificationType { BalanceChange = "balance-change", @@ -181,6 +181,7 @@ export type ObservabilityEvent = type: ObservabilityEventType.DbQueryFinishError; name: string; location: string; + error: TalerErrorDetail; } | { type: ObservabilityEventType.RequestStart; diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts index 2d17238dc..6b5eb61a6 100644 --- a/packages/taler-util/src/operation.ts +++ b/packages/taler-util/src/operation.ts @@ -19,7 +19,7 @@ */ import { HttpResponse, - readResponseJsonOrErrorCode, + readResponseJsonOrThrow, readSuccessResponseJsonOrThrow, readTalerErrorResponse, } from "./http-common.js"; @@ -127,7 +127,7 @@ export async function opKnownAlternativeFailure<T extends HttpStatusCode, B>( s: T, codec: Codec<B>, ): Promise<OperationAlternative<T, B>> { - const body = (await readResponseJsonOrErrorCode(resp, codec)).response; + const body = await readResponseJsonOrThrow(resp, codec); return { type: "fail", case: s, body }; } diff --git a/packages/taler-util/src/payto.test.ts b/packages/taler-util/src/payto.test.ts index 1b6907cbb..3b28c4714 100644 --- a/packages/taler-util/src/payto.test.ts +++ b/packages/taler-util/src/payto.test.ts @@ -16,7 +16,7 @@ import test from "ava"; -import { PaytoString, parsePaytoUri, stringifyPaytoUri } from "./payto.js"; +import { PaytoString, addPaytoQueryParams, parsePaytoUri, stringifyPaytoUri } from "./payto.js"; test("basic payto parsing", (t) => { const r1 = parsePaytoUri("https://example.com/"); @@ -42,3 +42,15 @@ test("parsing payto with % carh", (t) => { t.is(stringifyPaytoUri(parsePaytoUri(payto1)!), payto1); }); + +test("adding payto query params", (t) => { + const payto1 = + "payto://iban/DE1231231231?receiver-name=John%20Doe" as PaytoString; + const out1 = addPaytoQueryParams(payto1, {}); + t.deepEqual(payto1, out1); + + const out2 = addPaytoQueryParams(payto1, { + foo: "42", + }); + t.deepEqual(out2, "payto://iban/DE1231231231?receiver-name=John%20Doe&foo=42"); +}); diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts index 2b55cdf64..f50f97e58 100644 --- a/packages/taler-util/src/payto.ts +++ b/packages/taler-util/src/payto.ts @@ -25,7 +25,6 @@ import { } from "./codec.js"; import { AccessToken, - bytesToString, codecForAccessToken, codecOptional, hashTruncate32, @@ -39,8 +38,9 @@ export type PaytoUri = | PaytoUriTalerBank | PaytoUriBitcoin; -declare const __payto_str: unique symbol; -export type PaytoString = string & { [__payto_str]: true }; +// declare const __payto_str: unique symbol; +// export type PaytoString = string & { [__payto_str]: true }; +export type PaytoString = string; export function codecForPaytoString(): Codec<PaytoString> { return { @@ -159,14 +159,20 @@ export function buildPayto( } /** - * Add query parameters to a payto URI + * Add query parameters to a payto URI. + * + * Existing parameters are preserved. */ export function addPaytoQueryParams( s: string, params: { [name: string]: string }, ): string { const [acct, search] = s.slice(paytoPfx.length).split("?"); - const paramList = !params ? [] : Object.entries(params); + const searchParams = new URLSearchParams(search || ""); + for (const [paramKey, paramValue] of Object.entries(params)) { + searchParams.set(paramKey, paramValue); + } + const paramList = [...searchParams.entries()]; if (paramList.length === 0) { return paytoPfx + acct; } @@ -207,9 +213,9 @@ export function stringifyPaytoUri(p: PaytoUri): PaytoString { return url.href as PaytoString; } -export function hashPaytoUri(p: PaytoUri): string { - const paytoUri = stringifyPaytoUri(p); - return bytesToString(hashTruncate32(stringToBytes(paytoUri + "\0"))); +export function hashPaytoUri(p: PaytoUri | string): Uint8Array { + const paytoUri = typeof p === "string" ? p : stringifyPaytoUri(p); + return hashTruncate32(stringToBytes(paytoUri + "\0")); } /** diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts index 950161b10..ca57e22e2 100644 --- a/packages/taler-util/src/taler-crypto.ts +++ b/packages/taler-util/src/taler-crypto.ts @@ -30,14 +30,13 @@ import { hmacSha256, hmacSha512 } from "./kdf.js"; import { Logger } from "./logging.js"; import * as nacl from "./nacl-fast.js"; import { secretbox } from "./nacl-fast.js"; +import { TalerProtocolDuration, TalerProtocolTimestamp } from "./time.js"; +import { CoinPublicKeyString, HashCodeString } from "./types-taler-common.js"; import { CoinEnvelope, - CoinPublicKeyString, DenomKeyType, DenominationPubKey, - HashCodeString, -} from "./taler-types.js"; -import { TalerProtocolDuration, TalerProtocolTimestamp } from "./time.js"; +} from "./types-taler-exchange.js"; export type Flavor<T, FlavorT extends string> = T & { _flavor?: `taler.${FlavorT}`; @@ -987,6 +986,7 @@ export enum TalerSignaturePurpose { MERCHANT_REFUND = 1102, WALLET_COIN_RECOUP = 1203, WALLET_COIN_LINK = 1204, + WALLET_ACCOUNT_SETUP = 1205, WALLET_COIN_RECOUP_REFRESH = 1206, WALLET_AGE_ATTESTATION = 1207, WALLET_PURSE_CREATE = 1210, @@ -998,9 +998,10 @@ export enum TalerSignaturePurpose { WALLET_COIN_HISTORY = 1209, EXCHANGE_CONFIRM_RECOUP = 1039, EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041, - TALER_SIGNATURE_AML_DECISION = 1350, - TALER_SIGNATURE_AML_QUERY = 1351, - TALER_SIGNATURE_MASTER_AML_KEY = 1017, + AML_DECISION = 1350, + AML_QUERY = 1351, + MASTER_AML_KEY = 1017, + KYC_AUTH = 1360, ANASTASIS_POLICY_UPLOAD = 1400, ANASTASIS_POLICY_DOWNLOAD = 1401, SYNC_BACKUP_UPLOAD = 1450, diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts index a1b6ccc77..3877932e1 100644 --- a/packages/taler-util/src/taler-error-codes.ts +++ b/packages/taler-util/src/taler-error-codes.ts @@ -49,6 +49,14 @@ export enum TalerErrorCode { /** + * The client does not support the protocol version advertised by the server. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION = 3, + + + /** * The response we got from the server was not in the expected format. Most likely, the server does not speak the GNU Taler protocol. Check the URL and/or the network connection to the server. * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * (A value of 0 indicates that the error is generated client-side). @@ -177,6 +185,14 @@ export enum TalerErrorCode { /** + * A segment in the path of the URL provided by the client is malformed. Check that you are using the correct encoding for the URL. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + GENERIC_PATH_SEGMENT_MALFORMED = 29, + + + /** * The currency involved in the operation is not acceptable for this server. Check your configuration and make sure the currency specified for a given service provider is one of the currencies supported by that provider. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). @@ -2033,6 +2049,38 @@ export enum TalerErrorCode { /** + * The form has been previously uploaded, and may only be filed once. The user should be redirected to their main KYC page and see if any other steps need to be taken. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_FORM_ALREADY_UPLOADED = 1941, + + + /** + * The internal state of the exchange specifying KYC measures is malformed. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_MEASURES_MALFORMED = 1942, + + + /** + * The specified index does not refer to a valid KYC measure. Please check the URL. + * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_MEASURE_INDEX_INVALID = 1943, + + + /** + * The operation is not supported by the selected KYC logic. This is either caused by a configuration change or some invalid use of the API. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_INVALID_LOGIC_TO_CHECK = 1944, + + + /** * The exchange does not know a contract under the given contract public key. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -4145,6 +4193,14 @@ export enum TalerErrorCode { /** + * The wallet's information about the exchange is outdated. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_EXCHANGE_ENTRY_OUTDATED = 7039, + + + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). * (A value of 0 indicates that the error is generated client-side). diff --git a/packages/taler-util/src/taler-signatures.ts b/packages/taler-util/src/taler-signatures.ts new file mode 100644 index 000000000..5c9690528 --- /dev/null +++ b/packages/taler-util/src/taler-signatures.ts @@ -0,0 +1,69 @@ +/* + This file is part of GNU Taler + (C) 2024 GNUnet e.V. + + 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 { canonicalJson } from "./index.js"; +import { + bufferForUint64, + buildSigPS, + decodeCrock, + eddsaSign, + hash, + stringToBytes, + TalerSignaturePurpose, + timestampRoundedToBuffer, +} from "./taler-crypto.js"; +import { AmlDecisionRequestWithoutSignature } from "./types-taler-exchange.js"; + +/** + * Implementation of Taler protocol signatures. + * + * In this file, we have implementations of signatures that are not used in the wallet, + * but in other places (tests, SPAs, ...). + */ + +/** + * Signature for the POST /aml/$OFFICER_PUB/decisions endpoint. + */ +export function signAmlDecision( + priv: Uint8Array, + decision: AmlDecisionRequestWithoutSignature, +): Uint8Array { + const builder = buildSigPS(TalerSignaturePurpose.AML_DECISION); + + const flags: number = decision.keep_investigating ? 1 : 0; + + builder.put(timestampRoundedToBuffer(decision.decision_time)); + builder.put(decodeCrock(decision.h_payto)); + builder.put(hash(stringToBytes(decision.justification))); + builder.put(hash(stringToBytes(canonicalJson(decision.properties) + "\0"))); + builder.put(hash(stringToBytes(canonicalJson(decision.new_rules) + "\0"))); + if (decision.new_measure != null) { + builder.put(hash(stringToBytes(decision.new_measure))); + } else { + builder.put(new Uint8Array(64)); + } + builder.put(bufferForUint64(flags)); + + const sigBlob = builder.build(); + + return eddsaSign(sigBlob, priv); +} + +export function signAmlQuery(key: Uint8Array): Uint8Array { + const sigBlob = buildSigPS(TalerSignaturePurpose.AML_QUERY).build(); + + return eddsaSign(sigBlob, key); +} diff --git a/packages/taler-util/src/taleruri.test.ts b/packages/taler-util/src/taleruri.test.ts index d80470dab..694260330 100644 --- a/packages/taler-util/src/taleruri.test.ts +++ b/packages/taler-util/src/taleruri.test.ts @@ -15,7 +15,7 @@ */ import test from "ava"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; import { parseAddExchangeUri, parseDevExperimentUri, diff --git a/packages/taler-util/src/taleruri.ts b/packages/taler-util/src/taleruri.ts index d3186d2f5..b4455fba4 100644 --- a/packages/taler-util/src/taleruri.ts +++ b/packages/taler-util/src/taleruri.ts @@ -27,7 +27,7 @@ import { Codec, Context, DecodingError, renderContext } from "./codec.js"; import { canonicalizeBaseUrl } from "./helpers.js"; import { opFixedSuccess, opKnownTalerFailure } from "./operation.js"; import { TalerErrorCode } from "./taler-error-codes.js"; -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; import { URL, URLSearchParams } from "./url.js"; /** diff --git a/packages/taler-util/src/transaction-test-data.ts b/packages/taler-util/src/transaction-test-data.ts index 378028144..85f4684bb 100644 --- a/packages/taler-util/src/transaction-test-data.ts +++ b/packages/taler-util/src/transaction-test-data.ts @@ -18,8 +18,8 @@ import { TransactionType, PaymentStatus, TransactionMajorState, -} from "./transactions-types.js"; -import { RefreshReason } from "./wallet-types.js"; +} from "./types-taler-wallet-transactions.js"; +import { RefreshReason } from "./types-taler-wallet.js"; /** * Sample transaction list entries. diff --git a/packages/taler-util/src/twrpc.ts b/packages/taler-util/src/twrpc.ts index d221630d0..368e04e27 100644 --- a/packages/taler-util/src/twrpc.ts +++ b/packages/taler-util/src/twrpc.ts @@ -14,8 +14,6 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { CoreApiResponse } from "./wallet-types.js"; - /** * Implementation for the wallet-core IPC protocol. * diff --git a/packages/taler-util/src/libeufin-api-types.ts b/packages/taler-util/src/type-override.d.ts index aa3d0cb7a..703b60331 100644 --- a/packages/taler-util/src/libeufin-api-types.ts +++ b/packages/taler-util/src/type-override.d.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2023 Taler Systems S.A. + (C) 2021-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 @@ -14,18 +14,19 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -export type FacadeCredentials = - | NoFacadeCredentials - | BasicAuthFacadeCredentials; -export interface NoFacadeCredentials { - type: "none"; -} -export interface BasicAuthFacadeCredentials { - type: "basic"; - - // Username to use to authenticate - username: string; - - // Password to use to authenticate - password: string; +/** + * define unknown type of catch function + */ +interface Promise<T> { + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch<TResult = never>( + onrejected?: + | ((reason: unknown) => TResult | PromiseLike<TResult>) + | undefined + | null, + ): Promise<T | TResult>; } diff --git a/packages/taler-util/src/types-taler-bank-conversion.ts b/packages/taler-util/src/types-taler-bank-conversion.ts new file mode 100644 index 000000000..31cfa5aad --- /dev/null +++ b/packages/taler-util/src/types-taler-bank-conversion.ts @@ -0,0 +1,200 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { codecForAmountString } from "./amounts.js"; +import { + Codec, + buildCodecForObject, + codecForConstString, + codecForEither, + codecForString, +} from "./codec.js"; +import { + AmountString, + CurrencySpecification, + DecimalNumber, + codecForCurrencySpecificiation, + codecForDecimalNumber, +} from "./types-taler-common.js"; + +export interface ConversionInfo { + // Exchange rate to buy regional currency from fiat + cashin_ratio: DecimalNumber; + + // Exchange rate to sell regional currency for fiat + cashout_ratio: DecimalNumber; + + // Fee to subtract after applying the cashin ratio. + cashin_fee: AmountString; + + // Fee to subtract after applying the cashout ratio. + cashout_fee: AmountString; + + // Minimum amount authorised for cashin, in fiat before conversion + cashin_min_amount: AmountString; + + // Minimum amount authorised for cashout, in regional before conversion + cashout_min_amount: AmountString; + + // Smallest possible regional amount, converted amount is rounded to this amount + cashin_tiny_amount: AmountString; + + // Smallest possible fiat amount, converted amount is rounded to this amount + cashout_tiny_amount: AmountString; + + // Rounding mode used during cashin conversion + cashin_rounding_mode: "zero" | "up" | "nearest"; + + // Rounding mode used during cashout conversion + cashout_rounding_mode: "zero" | "up" | "nearest"; +} + +export interface TalerConversionInfoConfig { + // 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; + + // Name of the API. + name: "taler-conversion-info"; + + // Currency used by this bank. + regional_currency: string; + + // How the bank SPA should render this currency. + regional_currency_specification: CurrencySpecification; + + // External currency used during conversion. + fiat_currency: string; + + // How the bank SPA should render this currency. + fiat_currency_specification: CurrencySpecification; + + // Extra conversion rate information. + // Only present if server opts in to report the static conversion rate. + conversion_rate: ConversionInfo; +} + +export interface CashinConversionResponse { + // Amount that the user will get deducted from their fiat + // bank account, according to the 'amount_credit' value. + amount_debit: AmountString; + // Amount that the user will receive in their regional + // bank account, according to 'amount_debit'. + amount_credit: AmountString; +} + +export interface CashoutConversionResponse { + // Amount that the user will get deducted from their regional + // bank account, according to the 'amount_credit' value. + amount_debit: AmountString; + // Amount that the user will receive in their fiat + // bank account, according to 'amount_debit'. + amount_credit: AmountString; +} + +export type RoundingMode = "zero" | "up" | "nearest"; + +export interface ConversionRate { + // Exchange rate to buy regional currency from fiat + cashin_ratio: DecimalNumber; + + // Fee to subtract after applying the cashin ratio. + cashin_fee: AmountString; + + // Minimum amount authorised for cashin, in fiat before conversion + cashin_min_amount: AmountString; + + // Smallest possible regional amount, converted amount is rounded to this amount + cashin_tiny_amount: AmountString; + + // Rounding mode used during cashin conversion + cashin_rounding_mode: RoundingMode; + + // Exchange rate to sell regional currency for fiat + cashout_ratio: DecimalNumber; + + // Fee to subtract after applying the cashout ratio. + cashout_fee: AmountString; + + // Minimum amount authorised for cashout, in regional before conversion + cashout_min_amount: AmountString; + + // Smallest possible fiat amount, converted amount is rounded to this amount + cashout_tiny_amount: AmountString; + + // Rounding mode used during cashout conversion + cashout_rounding_mode: RoundingMode; +} + +export const codecForCashoutConversionResponse = + (): Codec<CashoutConversionResponse> => + buildCodecForObject<CashoutConversionResponse>() + .property("amount_credit", codecForAmountString()) + .property("amount_debit", codecForAmountString()) + .build("TalerCorebankApi.CashoutConversionResponse"); + +export const codecForCashinConversionResponse = + (): Codec<CashinConversionResponse> => + buildCodecForObject<CashinConversionResponse>() + .property("amount_credit", codecForAmountString()) + .property("amount_debit", codecForAmountString()) + .build("TalerCorebankApi.CashinConversionResponse"); + +export const codecForConversionInfo = (): Codec<ConversionInfo> => + buildCodecForObject<ConversionInfo>() + .property("cashin_fee", codecForAmountString()) + .property("cashin_min_amount", codecForAmountString()) + .property("cashin_ratio", codecForDecimalNumber()) + .property( + "cashin_rounding_mode", + codecForEither( + codecForConstString("zero"), + codecForConstString("up"), + codecForConstString("nearest"), + ), + ) + .property("cashin_tiny_amount", codecForAmountString()) + .property("cashout_fee", codecForAmountString()) + .property("cashout_min_amount", codecForAmountString()) + .property("cashout_ratio", codecForDecimalNumber()) + .property( + "cashout_rounding_mode", + codecForEither( + codecForConstString("zero"), + codecForConstString("up"), + codecForConstString("nearest"), + ), + ) + .property("cashout_tiny_amount", codecForAmountString()) + .build("ConversionBankConfig.ConversionInfo"); + +export const codecForConversionBankConfig = (): Codec<TalerConversionInfoConfig> => + buildCodecForObject<TalerConversionInfoConfig>() + .property("name", codecForConstString("taler-conversion-info")) + .property("version", codecForString()) + .property("regional_currency", codecForString()) + .property( + "regional_currency_specification", + codecForCurrencySpecificiation(), + ) + .property("fiat_currency", codecForString()) + .property("fiat_currency_specification", codecForCurrencySpecificiation()) + + .property("conversion_rate", codecForConversionInfo()) + .build("ConversionBankConfig.IntegrationConfig"); diff --git a/packages/taler-util/src/types-taler-bank-integration.ts b/packages/taler-util/src/types-taler-bank-integration.ts new file mode 100644 index 000000000..02161474f --- /dev/null +++ b/packages/taler-util/src/types-taler-bank-integration.ts @@ -0,0 +1,193 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { Codec, buildCodecForObject, codecForConstString, codecForEither, codecOptional } from "./codec.js"; +import { codecForAmountString, codecForList, codecForString } from "./index.js"; +import { PaytoString, codecForPaytoString } from "./payto.js"; +import { AmountString, CurrencySpecification, codecForCurrencyName, codecForCurrencySpecificiation, codecForLibtoolVersion, codecForURLString } from "./types-taler-common.js"; + +export type WithdrawalOperationStatus = + | "pending" + | "selected" + | "aborted" + | "confirmed"; + +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 { + // Current status of the operation + // pending: the operation is pending parameters selection (exchange and reserve public key) + // selected: the operations has been selected and is pending confirmation + // aborted: the operation has been aborted + // confirmed: the transfer has been confirmed and registered by the bank + status: WithdrawalOperationStatus; + + // Currency used for the withdrawal. + // MUST be present when amount is absent. + // @since v2, may become mandatory in the future. + currency?: string; + + // Amount that will be withdrawn with this operation + // (raw amount without fee considerations). Only + // given once the amount is fixed and cannot be changed. + // Optional since **vC2EC**. + amount?: AmountString | undefined; + + // Suggestion for the amount to be withdrawn with this + // operation. Given if a suggestion was made but the + // user may still change the amount. + // Optional since **vC2EC**. + suggested_amount?: AmountString | undefined; + + // Maximum amount that the wallet can choose to withdraw. + // Only applicable when the amount is not fixed. + // @since **vC2EC**. + max_amount?: AmountString | undefined; + + // The non-Taler card fees the customer will have + // to pay to the bank / payment service provider + // they are using to make the withdrawal. + // @since **vC2EC** + card_fees?: AmountString | undefined; + + // Bank account of the customer that is debiting, as an + // RFC 8905 payto URI. + sender_wire?: PaytoString; + + // Base URL of the suggested exchange. The bank may have + // neither a suggestion nor a requirement for the exchange. + // This value is typically set in the bank's configuration. + suggested_exchange?: string; + + // Base URL of an exchange that must be used. Optional, + // not given *unless* a particular exchange is mandatory. + // This value is typically set in the bank's configuration. + // @since **vC2EC** + required_exchange?: string; + + // URL that the user needs to navigate to in order to + // complete some final confirmation (e.g. 2FA). + // Only applicable when status is selected or pending. + // It may contain the withdrawal operation id. + confirm_transfer_url?: string; + + // Wire transfer types supported by the bank. + wire_types: string[]; + + // Reserve public key selected by the exchange, + // only non-null if status is selected or confirmed. + selected_reserve_pub?: string; + + // Exchange account selected by the wallet; + // only non-null if status is selected or confirmed. + // @since **v1** + selected_exchange_account?: string; +} + +export interface BankWithdrawalOperationPostRequest { + // Reserve public key that should become the wire transfer + // subject to fund the withdrawal. + reserve_pub: string; + + // Payto address of the exchange selected for the withdrawal. + selected_exchange: PaytoString; + + // Selected amount to be transferred. Optional if the + // backend already knows the amount. + // @since **vC2EC** + amount?: AmountString | undefined; +} + +export interface BankWithdrawalOperationPostResponse { + // Current status of the operation + // pending: the operation is pending parameters selection (exchange and reserve public key) + // selected: the operations has been selected and is pending confirmation + // aborted: the operation has been aborted + // confirmed: the transfer has been confirmed and registered by the bank + status: Omit<"pending", WithdrawalOperationStatus>; + + // URL that the user needs to navigate to in order to + // complete some final confirmation (e.g. 2FA). + // + // Only applicable when status is selected or pending. + // It may contain withdrawal operation id + confirm_transfer_url?: string; +} + + +export const codecForBankVersion = + (): Codec<BankVersion> => + buildCodecForObject<BankVersion>() + .property("currency", codecForCurrencyName()) + .property("currency_specification", codecForCurrencySpecificiation()) + .property("name", codecForConstString("taler-bank-integration")) + .property("version", codecForLibtoolVersion()) + .build("TalerBankIntegrationApi.BankVersion"); + +export const codecForBankWithdrawalOperationStatus = + (): Codec<BankWithdrawalOperationStatus> => + buildCodecForObject<BankWithdrawalOperationStatus>() + .property( + "status", + codecForEither( + codecForConstString("pending"), + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) + .property("amount", codecOptional(codecForAmountString())) + .property("currency", codecOptional(codecForCurrencyName())) + .property("suggested_amount", codecOptional(codecForAmountString())) + .property("card_fees", codecOptional(codecForAmountString())) + .property("sender_wire", codecOptional(codecForPaytoString())) + .property("suggested_exchange", codecOptional(codecForURLString())) + .property("confirm_transfer_url", codecOptional(codecForURLString())) + .property("wire_types", codecForList(codecForString())) + .property("selected_reserve_pub", codecOptional(codecForString())) + .property("selected_exchange_account", codecOptional(codecForString())) + .property("max_amount", codecOptional(codecForAmountString())) + .build("TalerBankIntegrationApi.BankWithdrawalOperationStatus"); + +export const codecForBankWithdrawalOperationPostResponse = + (): Codec<BankWithdrawalOperationPostResponse> => + buildCodecForObject<BankWithdrawalOperationPostResponse>() + .property( + "status", + codecForEither( + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) + .property("confirm_transfer_url", codecOptional(codecForURLString())) + .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse");
\ No newline at end of file diff --git a/packages/taler-util/src/types-taler-challenger.ts b/packages/taler-util/src/types-taler-challenger.ts new file mode 100644 index 000000000..70ac35ee9 --- /dev/null +++ b/packages/taler-util/src/types-taler-challenger.ts @@ -0,0 +1,293 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + Codec, + buildCodecForObject, + buildCodecForUnion, + codecForAny, + codecForBoolean, + codecForConstString, + codecForEither, + codecForMap, + codecForNumber, + codecForString, + codecOptional, +} from "./codec.js"; +import { TalerProtocolTimestamp, codecForTimestamp } from "./time.js"; +import { + Integer, + InternationalizedString, + Timestamp, +} from "./types-taler-common.js"; + +export interface ChallengerTermsOfServiceResponse { + // Name of the service + name: "challenger"; + + // libtool-style representation of the Challenger protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since v0, may become mandatory in the future. + implementation?: string; + + // Object; map of keys (names of the fields of the address + // to be entered by the user) to objects with a "regex" (string) + // containing an extended Posix regular expression for allowed + // address field values, and a "hint"/"hint_i18n" giving a + // human-readable explanation to display if the value entered + // by the user does not match the regex. Keys that are not mapped + // to such an object have no restriction on the value provided by + // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration. + restrictions: Record<string, Restriction> | undefined; + + // @since v2. + address_type: "email" | "phone"; +} + +export interface ChallengeSetupResponse { + // Nonce to use when constructing /authorize endpoint. + nonce: string; +} + +export interface Restriction { + regex?: string; + hint?: string; + hint_i18n?: InternationalizedString; +} + +export interface ChallengeStatus { + // indicates if the given address cannot be changed anymore, the + // form should be read-only if set to true. + fix_address: boolean; + + // form values from the previous submission if available, details depend + // on the ADDRESS_TYPE, should be used to pre-populate the form + last_address: Record<string, string> | undefined; + + // number of times the address can still be changed, may or may not be + // shown to the user + changes_left: Integer; + + // is the challenge already solved? + solved: boolean; + + // when we would re-transmit the challenge the next + // time (at the earliest) if requested by the user + // only present if challenge already created + // @since v2 + retransmission_time: Timestamp; + + // how many times might the PIN still be retransmitted + // only present if challenge already created + // @since v2 + pin_transmissions_left: Integer; + + // how many times might the user still try entering the PIN code + // only present if challenge already created + // @since v2 + auth_attempts_left: Integer; +} + +export type ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse; + +export interface ChallengeRedirect { + type: "completed"; + // challenge is completed, use should redirect here + redirect_url: string; +} + +export interface ChallengeCreateResponse { + type: "created"; + // how many more attempts are allowed, might be shown to the user, + // highlighting might be appropriate for low values such as 1 or 2 (the + // form will never be used if the value is zero) + attempts_left: Integer; + + // the address that is being validated, might be shown or not + address: Object; + + // true if we just retransmitted the challenge, false if we sent a + // challenge recently and thus refused to transmit it again this time; + // might make a useful hint to the user + transmitted: boolean; + + // timestamp explaining when we would re-transmit the challenge the next + // time (at the earliest) if requested by the user + retransmission_time: TalerProtocolTimestamp; +} + +export type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse; + +export interface InvalidPinResponse { + type: "pending"; + + // numeric Taler error code, should be shown to indicate the error + // compactly for reporting to developers + ec?: number; + + // human-readable Taler error code, should be shown for the user to + // understand the error + hint: string; + + // how many times is the user still allowed to change the address; + // if 0, the user should not be shown a link to jump to the + // address entry form + addresses_left: Integer; + + // how many times might the PIN still be retransmitted + pin_transmissions_left: Integer; + + // how many times might the user still try entering the PIN code + auth_attempts_left: Integer; + + // if true, the PIN was not even evaluated as the user previously + // exhausted the number of attempts + exhausted: boolean; + + // if true, the PIN was not even evaluated as no challenge was ever + // issued (the user must have skipped the step of providing their + // address first!) + no_challenge: boolean; +} + +export interface ChallengerAuthResponse { + // Token used to authenticate access in /info. + access_token: string; + + // Type of the access token. + token_type: "Bearer"; + + // Amount of time that an access token is valid (in seconds). + expires_in: Integer; +} + +export interface ChallengerInfoResponse { + // Unique ID of the record within Challenger + // (identifies the rowid of the token). + id: Integer; + + // Address that was validated. + // Key-value pairs, details depend on the + // address_type. + address: Object; + + // Type of the address. + address_type: string; + + // How long do we consider the address to be + // valid for this user. + expires: Timestamp; +} + +export const codecForChallengerTermsOfServiceResponse = + (): Codec<ChallengerTermsOfServiceResponse> => + buildCodecForObject<ChallengerTermsOfServiceResponse>() + .property("name", codecForConstString("challenger")) + .property("version", codecForString()) + .property("implementation", codecOptional(codecForString())) + .property("restrictions", codecOptional(codecForMap(codecForAny()))) + .property( + "address_type", + codecForEither( + codecForConstString("phone"), + codecForConstString("email"), + ), + ) + .build("ChallengerApi.ChallengerTermsOfServiceResponse"); + +export const codecForChallengeSetupResponse = + (): Codec<ChallengeSetupResponse> => + buildCodecForObject<ChallengeSetupResponse>() + .property("nonce", codecForString()) + .build("ChallengerApi.ChallengeSetupResponse"); + +export const codecForChallengeStatus = (): Codec<ChallengeStatus> => + buildCodecForObject<ChallengeStatus>() + .property("fix_address", codecForBoolean()) + .property("solved", codecForBoolean()) + .property("last_address", codecOptional(codecForMap(codecForAny()))) + .property("changes_left", codecForNumber()) + .property("retransmission_time", codecForTimestamp) + .property("pin_transmissions_left", codecForNumber()) + .property("auth_attempts_left", codecForNumber()) + .build("ChallengerApi.ChallengeStatus"); + +export const codecForChallengeResponse = (): Codec<ChallengeResponse> => + buildCodecForUnion<ChallengeResponse>() + .discriminateOn("type") + .alternative("completed", codecForChallengeRedirect()) + .alternative("created", codecForChallengeCreateResponse()) + .build("ChallengerApi.ChallengeResponse"); + +export const codecForChallengeCreateResponse = + (): Codec<ChallengeCreateResponse> => + buildCodecForObject<ChallengeCreateResponse>() + .property("attempts_left", codecForNumber()) + .property("type", codecForConstString("created")) + .property("address", codecForAny()) + .property("transmitted", codecForBoolean()) + .property("retransmission_time", codecForTimestamp) + .build("ChallengerApi.ChallengeCreateResponse"); + +export const codecForChallengeRedirect = (): Codec<ChallengeRedirect> => + buildCodecForObject<ChallengeRedirect>() + .property("type", codecForConstString("completed")) + .property("redirect_url", codecForString()) + .build("ChallengerApi.ChallengeRedirect"); + +export const codecForChallengeInvalidPinResponse = + (): Codec<InvalidPinResponse> => + buildCodecForObject<InvalidPinResponse>() + .property("ec", codecOptional(codecForNumber())) + .property("hint", codecForAny()) + .property("type", codecForConstString("pending")) + .property("addresses_left", codecForNumber()) + .property("pin_transmissions_left", codecForNumber()) + .property("auth_attempts_left", codecForNumber()) + .property("exhausted", codecForBoolean()) + .property("no_challenge", codecForBoolean()) + .build("ChallengerApi.InvalidPinResponse"); + +export const codecForChallengeSolveResponse = + (): Codec<ChallengeSolveResponse> => + buildCodecForUnion<ChallengeSolveResponse>() + .discriminateOn("type") + .alternative("completed", codecForChallengeRedirect()) + .alternative("pending", codecForChallengeInvalidPinResponse()) + .build("ChallengerApi.ChallengeSolveResponse"); + +export const codecForChallengerAuthResponse = + (): Codec<ChallengerAuthResponse> => + buildCodecForObject<ChallengerAuthResponse>() + .property("access_token", codecForString()) + .property("token_type", codecForAny()) + .property("expires_in", codecForNumber()) + .build("ChallengerApi.ChallengerAuthResponse"); + +export const codecForChallengerInfoResponse = + (): Codec<ChallengerInfoResponse> => + buildCodecForObject<ChallengerInfoResponse>() + .property("id", codecForNumber()) + .property("address", codecForAny()) + .property("address_type", codecForString()) + .property("expires", codecForTimestamp) + .build("ChallengerApi.ChallengerInfoResponse"); diff --git a/packages/taler-util/src/types-taler-common.ts b/packages/taler-util/src/types-taler-common.ts new file mode 100644 index 000000000..6fc314f25 --- /dev/null +++ b/packages/taler-util/src/types-taler-common.ts @@ -0,0 +1,588 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * Type and schema definitions and helpers for the core GNU Taler protocol. + * + * Even though the rest of the wallet uses camelCase for fields, use snake_case + * here, since that's the convention for the Taler JSON+HTTP API. + */ + +/** + * Imports. + */ + +import { codecForAmountString } from "./amounts.js"; +import { + Codec, + buildCodecForObject, + codecForAny, + codecForBoolean, + codecForConstString, + codecForList, + codecForMap, + codecForNumber, + codecForString, + codecOptional, +} from "./codec.js"; +import { codecForEither } from "./index.js"; +import { + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForTimestamp, +} from "./time.js"; + +// 64-byte hash code. +export type HashCode = string; + +export type PaytoHash = string; + +export type AmlOfficerPublicKeyP = string; + +// 32-byte hash code. +export type ShortHashCode = string; + +export type SHA256HashCode = ShortHashCode; + +export type SHA512HashCode = HashCode; + +// 32-byte nonce value, must only be used once. +export type CSNonce = string; + +// 32-byte nonce value, must only be used once. +export type RefreshMasterSeed = string; + +// 32-byte value representing a scalar multiplier +// for scalar operations on points on Curve25519. +export type Cs25519Scalar = string; + +/// +/// KEYS +/// + +// 16-byte access token used to authorize access. +export 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. +export 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. +export type EddsaPrivateKey = string; + +// Edx25519 public keys are points on Curve25519 and represented using the +// standard 256 bits Ed25519 compact format converted to Crockford +// Base32. +//export 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. +//export 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. +export type EcdhePublicKey = string; + +// Point on Curve25519 represented using the standard 256 bits Ed25519 compact format, +// converted to Crockford Base32. +export 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. +export type EcdhePrivateKey = string; + +export type CoinPublicKey = EddsaPublicKey; + +// RSA public key converted to Crockford Base32. +export type RsaPublicKey = string; + +export type WireTransferIdentifierRawP = string; +// Subset of numbers: Integers in the +// inclusive range 0 .. (2^53 - 1). +export type SafeUint64 = number; + +export type WadId = string; + +export type Timestamp = TalerProtocolTimestamp; + +export type RelativeTime = TalerProtocolDuration; + +export type RsaSignature = string; + +export type BlindedRsaSignature = string; + +/** + * DD51 https://docs.taler.net/design-documents/051-fractional-digits.html + */ +export interface CurrencySpecification { + // Name of the currency. + name: 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; + + // 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 interface InternationalizedString { + [lang_tag: string]: string; +} + +export type RsaPublicKeySring = string; +export type AgeMask = 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. +export type ImageDataUrl = string; + +/** + * 32-byte value representing a point on Curve25519. + */ +export type Cs25519Point = string; + +/** + * Response from the bank. + */ +export class WithdrawOperationStatusResponse { + status: "selected" | "aborted" | "confirmed" | "pending"; + + selection_done: boolean; + + transfer_done: boolean; + + aborted: boolean; + + amount: string | undefined; + + sender_wire?: string; + + suggested_exchange?: string; + + confirm_transfer_url?: string; + + wire_types: string[]; +} + +export type LitAmountString = `${string}:${number}`; + +export type LibtoolVersionString = string; + +export type DecimalNumber = string; + +declare const __amount_str: unique symbol; +export type AmountString = + | (string & { [__amount_str]: true }) + | LitAmountString; +// export type AmountString = string; +export type Base32String = string; +export type EddsaSignatureString = string; +export type EddsaPublicKeyString = string; +export type EddsaPrivateKeyString = string; +export type CoinPublicKeyString = string; + +// FIXME: implement this codec +export const codecForURLString = codecForString; +// FIXME: implement this codec +export const codecForLibtoolVersion = codecForString; +// FIXME: implement this codec +export const codecForCurrencyName = codecForString; +// FIXME: implement this codec +export const codecForDecimalNumber = codecForString; + +export const codecForInternationalizedString = + (): Codec<InternationalizedString> => codecForMap(codecForString()); + +export const codecForWithdrawOperationStatusResponse = + (): Codec<WithdrawOperationStatusResponse> => + buildCodecForObject<WithdrawOperationStatusResponse>() + .property( + "status", + codecForEither( + codecForConstString("selected"), + codecForConstString("confirmed"), + codecForConstString("aborted"), + codecForConstString("pending"), + ), + ) + .property("selection_done", codecForBoolean()) + .property("transfer_done", codecForBoolean()) + .property("aborted", codecForBoolean()) + .property("amount", codecOptional(codecForString())) + .property("sender_wire", codecOptional(codecForString())) + .property("suggested_exchange", codecOptional(codecForString())) + .property("confirm_transfer_url", codecOptional(codecForString())) + .property("wire_types", codecForList(codecForString())) + .build("WithdrawOperationStatusResponse"); + +export const codecForCurrencySpecificiation = + (): Codec<CurrencySpecification> => + buildCodecForObject<CurrencySpecification>() + .property("name", codecForString()) + .property("num_fractional_input_digits", codecForNumber()) + .property("num_fractional_normal_digits", codecForNumber()) + .property("num_fractional_trailing_zero_digits", codecForNumber()) + .property("alt_unit_names", codecForMap(codecForString())) + .build("CurrencySpecification"); + +export interface TalerCommonConfigResponse { + name: string; + version: string; +} + +export const codecForTalerCommonConfigResponse = + (): Codec<TalerCommonConfigResponse> => + buildCodecForObject<TalerCommonConfigResponse>() + .property("name", codecForString()) + .property("version", codecForString()) + .build("TalerCommonConfigResponse"); + +export enum ExchangeProtocolVersion { + /** + * Current version supported by the wallet. + */ + V12 = 12, +} + +export enum MerchantProtocolVersion { + /** + * Current version supported by the wallet. + */ + V3 = 3, +} + +export type HashCodeString = string; + +export type WireSalt = string; + +export interface WalletKycUuid { + // UUID that the wallet should use when initiating + // the KYC check. + requirement_row: number; + + // Hash of the payto:// account URI for the wallet. + h_payto: string; +} + +export const codecForWalletKycUuid = (): Codec<WalletKycUuid> => + buildCodecForObject<WalletKycUuid>() + .property("requirement_row", codecForNumber()) + .property("h_payto", codecForString()) + .build("WalletKycUuid"); + +export interface MerchantUsingTemplateDetails { + summary?: string; + amount?: AmountString; +} + +export type Integer = number; + +export interface BankConversionInfoConfig { + // 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; + + // Name of the API. + name: "taler-conversion-info"; + + regional_currency: string; + + fiat_currency: string; + + // Currency used by this bank. + regional_currency_specification: CurrencySpecification; + + // External currency used during conversion. + fiat_currency_specification: CurrencySpecification; +} + +export const codecForBankConversionInfoConfig = + (): Codec<BankConversionInfoConfig> => + buildCodecForObject<BankConversionInfoConfig>() + .property("name", codecForConstString("taler-conversion-info")) + .property("version", codecForString()) + .property("fiat_currency", codecForString()) + .property("regional_currency", codecForString()) + .property("fiat_currency_specification", codecForCurrencySpecificiation()) + .property( + "regional_currency_specification", + codecForCurrencySpecificiation(), + ) + .build("BankConversionInfoConfig"); + +export interface DenominationExpiredMessage { + // Taler error code. Note that beyond + // expiration this message format is also + // used if the key is not yet valid, or + // has been revoked. + code: number; + + // Signature by the exchange over a + // TALER_DenominationExpiredAffirmationPS. + // Must have purpose TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED. + exchange_sig: EddsaSignatureString; + + // Public key of the exchange used to create + // the 'exchange_sig. + exchange_pub: EddsaPublicKeyString; + + // Hash of the denomination public key that is unknown. + h_denom_pub: HashCodeString; + + // When was the signature created. + timestamp: TalerProtocolTimestamp; + + // What kind of operation was requested that now + // failed? + oper: string; +} + +export const codecForDenominationExpiredMessage = () => + buildCodecForObject<DenominationExpiredMessage>() + .property("code", codecForNumber()) + .property("exchange_sig", codecForString()) + .property("exchange_pub", codecForString()) + .property("h_denom_pub", codecForString()) + .property("timestamp", codecForTimestamp) + .property("oper", codecForString()) + .build("DenominationExpiredMessage"); + +export interface CoinHistoryResponse { + // Current balance of the coin. + balance: AmountString; + + // Hash of the coin's denomination. + h_denom_pub: HashCodeString; + + // Transaction history for the coin. + history: any[]; +} + +export const codecForCoinHistoryResponse = () => + buildCodecForObject<CoinHistoryResponse>() + .property("balance", codecForAmountString()) + .property("h_denom_pub", codecForString()) + .property("history", codecForAny()) + .build("CoinHistoryResponse"); + +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 TokenSuccessResponseMerchant { + // 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. + token: AccessToken; +} + +//FIXME: implement this codec +export const codecForAccessToken = codecForString as () => Codec<AccessToken>; +export const codecForTokenSuccessResponse = (): Codec<TokenSuccessResponse> => + buildCodecForObject<TokenSuccessResponse>() + .property("access_token", codecForAccessToken()) + .property("expiration", codecForTimestamp) + .build("TalerAuthentication.TokenSuccessResponse"); + +export const codecForTokenSuccessResponseMerchant = + (): Codec<TokenSuccessResponseMerchant> => + buildCodecForObject<TokenSuccessResponseMerchant>() + .property("token", codecForAccessToken()) + .property("expiration", codecForTimestamp) + .build("TalerAuthentication.TokenSuccessResponseMerchant"); + +// FIXME: implement this codec +export const codecForURN = codecForString; + +declare const __ac_token: unique symbol; + +/** + * Use `createAccessToken(string)` function to build one. + */ +export type AccessToken = string & { + [__ac_token]: true; +}; + +/** + * Create a rfc8959 access token. + * Adds secret-token: prefix if there is none. + * Encode the token with rfc7230 to send in a http header. + * + * @param token + * @returns + */ +export function createRFC8959AccessTokenEncoded(token: string): AccessToken { + return ( + token.startsWith("secret-token:") + ? token + : `secret-token:${encodeURIComponent(token)}` + ) as AccessToken; +} + +/** + * Create a rfc8959 access token. + * Adds secret-token: prefix if there is none. + * + * @param token + * @returns + */ +export function createRFC8959AccessTokenPlain(token: string): AccessToken { + return ( + token.startsWith("secret-token:") ? token : `secret-token:${token}` + ) as AccessToken; +} + +/** + * Convert string to access token. + * + * @param clientSecret + * @returns + */ +export function createClientSecretAccessToken( + clientSecret: string, +): AccessToken { + return clientSecret as AccessToken; +} + +export type UserAndPassword = { + username: string; + password: string; +}; + +export type UserAndToken = { + username: string; + token: AccessToken; +}; + +declare const opaque_OfficerAccount: unique symbol; +/** + * Sealed private key for AML officer + */ +export type LockedAccount = string & { [opaque_OfficerAccount]: true }; + +declare const opaque_OfficerId: unique symbol; +/** + * Public key for AML officer + */ +export type OfficerId = string & { [opaque_OfficerId]: true }; + +declare const opaque_OfficerSigningKey: unique symbol; +/** + * Private key for AML officer + */ +export type SigningKey = Uint8Array & { [opaque_OfficerSigningKey]: true }; + +export interface OfficerAccount { + id: OfficerId; + signingKey: SigningKey; +} + + +declare const opaque_ReserveAccount: unique symbol; +/** + * Sealed private key for AML officer + */ +export type LockedReserve = string & { [opaque_ReserveAccount]: true }; + +declare const opaque_ReserveId: unique symbol; +/** + * Public key for AML officer + */ +export type ReserveId = string & { [opaque_ReserveId]: true }; + +export interface ReserveAccount { + id: ReserveId; + signingKey: SigningKey; +} + + +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; + /** + * order + */ + order?: "asc" | "dec"; +}; + +export type LongPollParams = { + /** + * milliseconds the server should wait for at least one result to be shown + */ + timeoutMs?: number; +}; + +export interface LoginToken { + token: AccessToken; + expiration: Timestamp; +} diff --git a/packages/taler-util/src/types-taler-corebank.ts b/packages/taler-util/src/types-taler-corebank.ts new file mode 100644 index 000000000..994623e4d --- /dev/null +++ b/packages/taler-util/src/types-taler-corebank.ts @@ -0,0 +1,916 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + Codec, + buildCodecForObject, + codecForBoolean, + codecForConstString, + codecForString, + codecOptional, +} from "./codec.js"; +import { + buildCodecForUnion, + codecForAmountString, + codecForEither, + codecForList, + codecForNumber, + codecForTalerUriString, + codecForTimestamp, + codecOptionalDefault, +} from "./index.js"; +import { PaytoString, codecForPaytoString } from "./payto.js"; +import { TalerUriString } from "./taleruri.js"; +import { WithdrawalOperationStatus } from "./types-taler-bank-integration.js"; +import { + AmountString, + CurrencySpecification, + DecimalNumber, + Integer, + ShortHashCode, + Timestamp, + codecForCurrencySpecificiation, + codecForDecimalNumber, +} from "./types-taler-common.js"; + +export interface IntegrationConfig { + // 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: string; + + // How the bank SPA should render this currency. + currency_specification: CurrencySpecification; + + // Name of the API. + name: "taler-bank-integration"; +} + +export interface TalerCorebankConfigResponse { + /** + * Name of this API, always "taler-corebank". + * + * For legacy reasons, libeufin-bank will also be accepted for some time. + */ + name: "libeufin-bank" | "taler-corebank"; + + // API version in the form $n:$n:$n + version: string; + + // Bank display name to be used in user interfaces. + // For consistency use "Taler Bank" if missing. + // @since v4, will become mandatory in the next version. + bank_name?: string; + + // Advertised base URL to use when you sharing an URL with another + // program. + // @since v4. + base_url?: string; + + // If 'true' the server provides local currency conversion support + // If 'false' some parts of the API are not supported and return 501 + allow_conversion?: boolean; + + // If 'true' anyone can register + // If 'false' only the admin can + allow_registrations?: boolean; + + // If 'true' account can delete themselves + // If 'false' only the admin can delete accounts + allow_deletions?: boolean; + + // If 'true' anyone can edit their name + // If 'false' only admin can + allow_edit_name?: boolean; + + // If 'true' anyone can edit their cashout account + // If 'false' only the admin + allow_edit_cashout_payto_uri?: boolean; + + // Default debt limit for newly created accounts + default_debit_threshold?: AmountString; + + // Currency used by this bank. + currency: string; + + // How the bank SPA should render this currency. + currency_specification: CurrencySpecification; + + // TAN channels supported by the server + supported_tan_channels?: TanChannel[]; + + // Wire transfer type supported by the bank. + // Default to 'iban' is missing + // @since v4, may become mandatory in the future. + wire_type?: string; + + // Wire transfer execution fees. + // @since v4, will become mandatory in the next version. + wire_transfer_fees?: AmountString; +} + +export interface BankAccountCreateWithdrawalRequest { + // Amount to withdraw. If given, the wallet + // cannot change the amount. + // Optional since **vC2EC**. + amount?: AmountString; + + // Suggested amount to withdraw. The wallet can + // still change the suggestion. + // @since **vC2EC** + suggested_amount?: AmountString; +} + +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: TalerUriString; +} +export interface WithdrawalPublicInfo { + // Current status of the operation + // pending: the operation is pending parameters selection (exchange and reserve public key) + // selected: the operations has been selected and is pending confirmation + // aborted: the operation has been aborted + // confirmed: the transfer has been confirmed and registered by the bank + status: WithdrawalOperationStatus; + + // Amount that will be withdrawn with this operation + // (raw amount without fee considerations). + amount?: AmountString; + + // Suggestion for the amount to be withdrawn with this + // operation. Given if a suggestion was made but the + // user may still change the amount. + // Optional since **vC2EC**. + suggested_amount?: AmountString; + + // Account username + username: string; + + // Reserve public key selected by the exchange, + // only non-null if status is selected or confirmed. + selected_reserve_pub?: string; + + // Exchange account selected by the wallet + // only non-null if status is selected or confirmed. + selected_exchange_account?: PaytoString; +} + +export interface BankAccountTransactionsResponse { + transactions: BankAccountTransactionInfo[]; +} + +export interface BankAccountTransactionInfo { + creditor_payto_uri: PaytoString; + debtor_payto_uri: PaytoString; + + amount: AmountString; + direction: "debit" | "credit"; + + subject: string; + + // Transaction unique ID. Matches + // $transaction_id from the URI. + row_id: number; + date: Timestamp; +} + +export interface CreateTransactionRequest { + // Address in the Payto format of the wire transfer receiver. + // It needs at least the 'message' query string parameter. + payto_uri: PaytoString; + + // 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?: AmountString; + + // Nonce to make the request idempotent. Requests with the same + // request_uid that differ in any of the other fields + // are rejected. + // @since v4, will become mandatory in the next version. + request_uid?: ShortHashCode; +} + +export interface CreateTransactionResponse { + // ID identifying the transaction being created + row_id: Integer; +} + +export interface RegisterAccountResponse { + // Internal payto URI of this bank account. + internal_payto_uri: PaytoString; +} + +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. + contact_data?: ChallengeContactData; + + // 'payto' address of a fiat bank account. + // Payments will be sent to this bank account + // when the user wants to convert the regional currency + // back to fiat currency outside bank. + cashout_payto_uri?: PaytoString; + + // Internal payto URI of this bank account. + // Used mostly for testing. + payto_uri?: PaytoString; + + // If present, set the max debit allowed for this user + // Only admin can set this property. + debit_threshold?: AmountString; + + // If present, set a custom minimum cashout amount for this account. + // Only admin can set this property + // @since v4 + min_cashout?: AmountString; + + // If present, enables 2FA and set the TAN channel used for challenges + // Only admin can set this property, other user can reconfig their account + // after creation. + tan_channel?: TanChannel; +} + +export type EmailAddress = string; +export type PhoneNumber = 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. + // Only admin can change this property. + contact_data?: ChallengeContactData; + + // 'payto' URI of a fiat bank account. + // Payments will be sent to this bank account + // when the user wants to convert the regional currency + // back to fiat currency outside bank. + // Only admin can change this property if not allowed in config + cashout_payto_uri?: PaytoString; + + // If present, change the legal name associated with $username. + // Only admin can change this property if not allowed in config + name?: string; + + // Make this account visible to anyone? + is_public?: boolean; + + // If present, change the max debit allowed for this user + // Only admin can change this property. + debit_threshold?: AmountString; + + // If present, change the custom minimum cashout amount for this account. + // Only admin can set this property + // @since v4 + min_cashout?: AmountString; + + // If present, enables 2FA and set the TAN channel used for challenges + tan_channel?: TanChannel | null; +} + +export interface AccountPasswordChange { + // New password. + new_password: string; + // Old password. If present, check that the old password matches. + // Optional for admin account. + old_password?: string; +} + +export interface PublicAccountsResponse { + public_accounts: PublicAccount[]; +} +export interface PublicAccount { + // Username of the account + username: string; + + // Internal payto URI of this bank account. + payto_uri: string; + + // Current balance of the account + balance: Balance; + + // Is this a taler exchange account? + is_taler_exchange: boolean; + + // Opaque unique ID used for pagination. + // @since v4, will become mandatory in the future. + row_id?: Integer; +} + +export interface ListBankAccountsResponse { + accounts: AccountMinimalData[]; +} +export interface Balance { + amount: AmountString; + credit_debit_indicator: "credit" | "debit"; +} +export interface AccountMinimalData { + // Username + username: string; + + // Legal name of the account owner. + name: string; + + // Internal payto URI of this bank account. + payto_uri: PaytoString; + + // current balance of the account + balance: Balance; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: AmountString; + + // Custom minimum cashout amount for this account. + // If null or absent, the global conversion fee is used. + // @since v4 + min_cashout?: AmountString; + + // Is this account visible to anyone? + is_public: boolean; + + // Is this a taler exchange account? + is_taler_exchange: boolean; + + // Opaque unique ID used for pagination. + // @since v4, will become mandatory in the future. + row_id?: Integer; + + // Current status of the account + // active: the account can be used + // deleted: the account has been deleted but is retained for compliance + // reasons, only the administrator can access it + // Default to 'active' is missing + // @since v4, will become mandatory in the next version. + status?: "active" | "deleted"; +} + +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: PaytoString; + + // Number indicating the max debit allowed for the requesting user. + debit_threshold: AmountString; + + // Custom minimum cashout amount for this account. + // If null or absent, the global conversion fee is used. + // @since v4 + min_cashout?: AmountString; + + 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?: PaytoString; + + // Is this account visible to anyone? + is_public: boolean; + + // Is this a taler exchange account? + is_taler_exchange: boolean; + + // Is 2FA enabled and what channel is used for challenges? + tan_channel?: TanChannel; + + // Current status of the account + // active: the account can be used + // deleted: the account has been deleted but is retained for compliance + // reasons, only the administrator can access it + // Default to 'active' is missing + // @since v4, will become mandatory in the next version. + status?: "active" | "deleted"; +} + +export interface CashoutRequest { + // Nonce to make the request idempotent. Requests with the same + // request_uid that differ in any of the other fields + // are rejected. + request_uid: ShortHashCode; + + // Optional subject to associate to the + // cashout operation. This data will appear + // as the incoming wire transfer subject in + // the user's fiat 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: AmountString; + + // 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: AmountString; +} + +export interface CashoutResponse { + // ID identifying the operation being created + cashout_id: number; +} + +/** + * @deprecated since 4, use 2fa + */ +export interface CashoutConfirmRequest { + // the TAN that confirms $CASHOUT_ID. + tan: string; +} + +export interface Cashouts { + // Every string represents a cash-out operation ID. + cashouts: CashoutInfo[]; +} + +export interface CashoutInfo { + cashout_id: number; +} +export interface GlobalCashouts { + // Every string represents a cash-out operation ID. + cashouts: GlobalCashoutInfo[]; +} +export interface GlobalCashoutInfo { + cashout_id: number; + username: string; +} + +export interface CashoutStatusResponse { + // Amount debited to the internal + // regional currency bank account. + amount_debit: AmountString; + + // Amount credited to the external bank account. + amount_credit: AmountString; + + // Transaction subject. + subject: string; + + // Time when the cashout was created. + creation_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 type MonitorResponse = MonitorNoConversion | MonitorWithConversion; + +// Monitoring stats when conversion is not supported +export interface MonitorNoConversion { + type: "no-conversions"; + + // How many payments were made to a Taler exchange by another + // bank account. + talerInCount: number; + + // Overall volume that has been paid to a Taler + // exchange by another bank account. + talerInVolume: AmountString; + + // How many payments were made by a Taler exchange to another + // bank account. + talerOutCount: number; + + // Overall volume that has been paid by a Taler + // exchange to another bank account. + talerOutVolume: AmountString; +} +// Monitoring stats when conversion is supported +export interface MonitorWithConversion { + type: "with-conversions"; + + // How many cashin operations were confirmed by a + // wallet owner. Note: wallet owners + // are NOT required to be customers of the libeufin-bank. + cashinCount: number; + + // Overall regional currency that has been paid by the regional admin account + // to regional bank accounts to fulfill all the confirmed cashin operations. + cashinRegionalVolume: AmountString; + + // Overall fiat currency that has been paid to the fiat admin account + // by fiat bank accounts to fulfill all the confirmed cashin operations. + cashinFiatVolume: AmountString; + + // How many cashout operations were confirmed. + cashoutCount: number; + + // Overall regional currency that has been paid to the regional admin account + // by fiat bank accounts to fulfill all the confirmed cashout operations. + cashoutRegionalVolume: AmountString; + + // Overall fiat currency that has been paid by the fiat admin account + // to fiat bank accounts to fulfill all the confirmed cashout operations. + cashoutFiatVolume: AmountString; + + // How many payments were made to a Taler exchange by another + // bank account. + talerInCount: number; + + // Overall volume that has been paid to a Taler + // exchange by another bank account. + talerInVolume: AmountString; + + // How many payments were made by a Taler exchange to another + // bank account. + talerOutCount: number; + + // Overall volume that has been paid by a Taler + // exchange to another bank account. + talerOutVolume: AmountString; +} +export interface TanTransmission { + // Channel of the last successful transmission of the TAN challenge. + tan_channel: TanChannel; + + // Info of the last successful transmission of the TAN challenge. + tan_info: string; +} + +export interface Challenge { + // Unique identifier of the challenge to solve to run this protected + // operation. + challenge_id: number; +} + +export interface ChallengeSolve { + // The TAN code that solves $CHALLENGE_ID + tan: string; +} + +export enum TanChannel { + SMS = "sms", + EMAIL = "email", +} + +export const codecForIntegrationBankConfig = (): Codec<IntegrationConfig> => + buildCodecForObject<IntegrationConfig>() + .property("name", codecForConstString("taler-bank-integration")) + .property("version", codecForString()) + .property("currency", codecForString()) + .property("currency_specification", codecForCurrencySpecificiation()) + .build("TalerCorebankApi.IntegrationConfig"); + +export const codecForCoreBankConfig = (): Codec<TalerCorebankConfigResponse> => + buildCodecForObject<TalerCorebankConfigResponse>() + .property( + "name", + codecForEither( + codecForConstString("taler-corebank"), + codecForConstString("libeufin-bank"), + ), + ) + .property("version", codecForString()) + .property("bank_name", codecOptional(codecForString())) + .property("base_url", codecOptional(codecForString())) + .property("allow_conversion", codecOptional(codecForBoolean())) + .property("allow_registrations", codecOptional(codecForBoolean())) + .property("allow_deletions", codecOptional(codecForBoolean())) + .property("allow_edit_name", codecOptional(codecForBoolean())) + .property("allow_edit_cashout_payto_uri", codecOptional(codecForBoolean())) + .property("default_debit_threshold", codecOptional(codecForAmountString())) + .property("currency", codecForString()) + .property("currency_specification", codecForCurrencySpecificiation()) + .property( + "supported_tan_channels", + codecOptional( + codecForList( + codecForEither( + codecForConstString(TanChannel.SMS), + codecForConstString(TanChannel.EMAIL), + ), + ), + ), + ) + .property("wire_type", codecOptionalDefault(codecForString(), "iban")) + .property("wire_transfer_fees", codecOptional(codecForAmountString())) + .build("TalerCorebankApi.Config"); + +const codecForBalance = (): Codec<Balance> => + buildCodecForObject<Balance>() + .property("amount", codecForAmountString()) + .property( + "credit_debit_indicator", + codecForEither( + codecForConstString("credit"), + codecForConstString("debit"), + ), + ) + .build("TalerCorebankApi.Balance"); + +const codecForPublicAccount = (): Codec<PublicAccount> => + buildCodecForObject<PublicAccount>() + .property("username", codecForString()) + .property("balance", codecForBalance()) + .property("payto_uri", codecForPaytoString()) + .property("is_taler_exchange", codecForBoolean()) + .property("row_id", codecOptional(codecForNumber())) + .build("TalerCorebankApi.PublicAccount"); + +export const codecForPublicAccountsResponse = + (): Codec<PublicAccountsResponse> => + buildCodecForObject<PublicAccountsResponse>() + .property("public_accounts", codecForList(codecForPublicAccount())) + .build("TalerCorebankApi.PublicAccountsResponse"); + +export const codecForAccountMinimalData = (): Codec<AccountMinimalData> => + buildCodecForObject<AccountMinimalData>() + .property("username", codecForString()) + .property("name", codecForString()) + .property("payto_uri", codecForPaytoString()) + .property("balance", codecForBalance()) + .property("row_id", codecForNumber()) + .property("debit_threshold", codecForAmountString()) + .property("min_cashout", codecOptional(codecForAmountString())) + .property("is_public", codecForBoolean()) + .property("is_taler_exchange", codecForBoolean()) + .property( + "status", + codecOptional( + codecForEither( + codecForConstString("active"), + codecForConstString("deleted"), + ), + ), + ) + .build("TalerCorebankApi.AccountMinimalData"); + +export const codecForListBankAccountsResponse = + (): Codec<ListBankAccountsResponse> => + buildCodecForObject<ListBankAccountsResponse>() + .property("accounts", codecForList(codecForAccountMinimalData())) + .build("TalerCorebankApi.ListBankAccountsResponse"); + +export const codecForAccountData = (): Codec<AccountData> => + buildCodecForObject<AccountData>() + .property("name", codecForString()) + .property("balance", codecForBalance()) + .property("payto_uri", codecForPaytoString()) + .property("debit_threshold", codecForAmountString()) + .property("min_cashout", codecOptional(codecForAmountString())) + .property("contact_data", codecOptional(codecForChallengeContactData())) + .property("cashout_payto_uri", codecOptional(codecForPaytoString())) + .property("is_public", codecForBoolean()) + .property("is_taler_exchange", codecForBoolean()) + .property( + "tan_channel", + codecOptional( + codecForEither( + codecForConstString(TanChannel.SMS), + codecForConstString(TanChannel.EMAIL), + ), + ), + ) + .property( + "status", + codecOptional( + codecForEither( + codecForConstString("active"), + codecForConstString("deleted"), + ), + ), + ) + .build("TalerCorebankApi.AccountData"); + +export const codecForChallengeContactData = (): Codec<ChallengeContactData> => + buildCodecForObject<ChallengeContactData>() + .property("email", codecOptional(codecForString())) + .property("phone", codecOptional(codecForString())) + .build("TalerCorebankApi.ChallengeContactData"); + +export const codecForWithdrawalPublicInfo = (): Codec<WithdrawalPublicInfo> => + buildCodecForObject<WithdrawalPublicInfo>() + .property( + "status", + codecForEither( + codecForConstString("pending"), + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) + .property("amount", codecOptional(codecForAmountString())) + .property("username", codecForString()) + .property("selected_reserve_pub", codecOptional(codecForString())) + .property("selected_exchange_account", codecOptional(codecForPaytoString())) + .build("TalerCorebankApi.WithdrawalPublicInfo"); + +export const codecForBankAccountTransactionsResponse = + (): Codec<BankAccountTransactionsResponse> => + buildCodecForObject<BankAccountTransactionsResponse>() + .property( + "transactions", + codecForList(codecForBankAccountTransactionInfo()), + ) + .build("TalerCorebankApi.BankAccountTransactionsResponse"); + +export const codecForBankAccountTransactionInfo = + (): Codec<BankAccountTransactionInfo> => + buildCodecForObject<BankAccountTransactionInfo>() + .property("creditor_payto_uri", codecForPaytoString()) + .property("debtor_payto_uri", codecForPaytoString()) + .property("amount", codecForAmountString()) + .property( + "direction", + codecForEither( + codecForConstString("debit"), + codecForConstString("credit"), + ), + ) + .property("subject", codecForString()) + .property("row_id", codecForNumber()) + .property("date", codecForTimestamp) + .build("TalerCorebankApi.BankAccountTransactionInfo"); + +export const codecForCreateTransactionResponse = + (): Codec<CreateTransactionResponse> => + buildCodecForObject<CreateTransactionResponse>() + .property("row_id", codecForNumber()) + .build("TalerCorebankApi.CreateTransactionResponse"); + +export const codecForRegisterAccountResponse = + (): Codec<RegisterAccountResponse> => + buildCodecForObject<RegisterAccountResponse>() + .property("internal_payto_uri", codecForPaytoString()) + .build("TalerCorebankApi.RegisterAccountResponse"); + +export const codecForBankAccountCreateWithdrawalResponse = + (): Codec<BankAccountCreateWithdrawalResponse> => + buildCodecForObject<BankAccountCreateWithdrawalResponse>() + .property("taler_withdraw_uri", codecForTalerUriString()) + .property("withdrawal_id", codecForString()) + .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse"); + +export const codecForCashoutPending = (): Codec<CashoutResponse> => + buildCodecForObject<CashoutResponse>() + .property("cashout_id", codecForNumber()) + .build("TalerCorebankApi.CashoutPending"); + +export const codecForCashouts = (): Codec<Cashouts> => + buildCodecForObject<Cashouts>() + .property("cashouts", codecForList(codecForCashoutInfo())) + .build("TalerCorebankApi.Cashouts"); + +export const codecForCashoutInfo = (): Codec<CashoutInfo> => + buildCodecForObject<CashoutInfo>() + .property("cashout_id", codecForNumber()) + .build("TalerCorebankApi.CashoutInfo"); + +export const codecForGlobalCashouts = (): Codec<GlobalCashouts> => + buildCodecForObject<GlobalCashouts>() + .property("cashouts", codecForList(codecForGlobalCashoutInfo())) + .build("TalerCorebankApi.GlobalCashouts"); + +export const codecForGlobalCashoutInfo = (): Codec<GlobalCashoutInfo> => + buildCodecForObject<GlobalCashoutInfo>() + .property("cashout_id", codecForNumber()) + .property("username", codecForString()) + .build("TalerCorebankApi.GlobalCashoutInfo"); + +export const codecForCashoutStatusResponse = (): Codec<CashoutStatusResponse> => + buildCodecForObject<CashoutStatusResponse>() + .property("amount_debit", codecForAmountString()) + .property("amount_credit", codecForAmountString()) + .property("subject", codecForString()) + .property("creation_time", codecForTimestamp) + .build("TalerCorebankApi.CashoutStatusResponse"); + +export const codecForConversionRatesResponse = + (): Codec<ConversionRatesResponse> => + buildCodecForObject<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<MonitorResponse> => + buildCodecForUnion<MonitorResponse>() + .discriminateOn("type") + .alternative("no-conversions", codecForMonitorNoConversion()) + .alternative("with-conversions", codecForMonitorWithCashout()) + .build("TalerWireGatewayApi.IncomingBankTransaction"); + +export const codecForMonitorNoConversion = (): Codec<MonitorNoConversion> => + buildCodecForObject<MonitorNoConversion>() + .property("type", codecForConstString("no-conversions")) + .property("talerInCount", codecForNumber()) + .property("talerInVolume", codecForAmountString()) + .property("talerOutCount", codecForNumber()) + .property("talerOutVolume", codecForAmountString()) + .build("TalerCorebankApi.MonitorJustPayouts"); + +export const codecForMonitorWithCashout = (): Codec<MonitorWithConversion> => + buildCodecForObject<MonitorWithConversion>() + .property("type", codecForConstString("with-conversions")) + .property("cashinCount", codecForNumber()) + .property("cashinFiatVolume", codecForAmountString()) + .property("cashinRegionalVolume", codecForAmountString()) + .property("cashoutCount", codecForNumber()) + .property("cashoutFiatVolume", codecForAmountString()) + .property("cashoutRegionalVolume", codecForAmountString()) + .property("talerInCount", codecForNumber()) + .property("talerInVolume", codecForAmountString()) + .property("talerOutCount", codecForNumber()) + .property("talerOutVolume", codecForAmountString()) + .build("TalerCorebankApi.MonitorWithCashout"); + +export const codecForChallenge = (): Codec<Challenge> => + buildCodecForObject<Challenge>() + .property("challenge_id", codecForNumber()) + .build("TalerCorebankApi.Challenge"); + +export const codecForTanTransmission = (): Codec<TanTransmission> => + buildCodecForObject<TanTransmission>() + .property( + "tan_channel", + codecForEither( + codecForConstString(TanChannel.SMS), + codecForConstString(TanChannel.EMAIL), + ), + ) + .property("tan_info", codecForString()) + .build("TalerCorebankApi.TanTransmission"); diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/types-taler-exchange.ts index ac42ca278..0cd722239 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/types-taler-exchange.ts @@ -1,51 +1,43 @@ /* This file is part of GNU Taler - (C) 2019 GNUnet e.V. + (C) 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 + terms of the GNU Affero 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 + You should have received a copy of the GNU Affero General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> - */ - -/** - * Type and schema definitions and helpers for the core GNU Taler protocol. - * - * Even though the rest of the wallet uses camelCase for fields, use snake_case - * here, since that's the convention for the Taler JSON+HTTP API. - */ -/** - * Imports. + SPDX-License-Identifier: AGPL-3.0-or-later */ -import { Amounts, codecForAmountString } from "./amounts.js"; import { Codec, buildCodecForObject, - buildCodecForUnion, codecForAny, - codecForBoolean, - codecForConstString, codecForList, - codecForMap, codecForNumber, codecForString, codecForStringURL, codecOptional, } from "./codec.js"; -import { strcmp } from "./helpers.js"; import { - CurrencySpecification, + PaytoString, + buildCodecForUnion, + codecForAmountString, + codecForBoolean, + codecForConstString, codecForCurrencySpecificiation, codecForEither, - codecForProduct, + codecForMap, + codecForURN, + codecOptionalDefault, + strcmp, } from "./index.js"; import { Edx25519PublicKeyEnc } from "./taler-crypto.js"; import { @@ -54,11 +46,105 @@ import { codecForDuration, codecForTimestamp, } from "./time.js"; +import { + AccessToken, + AmlOfficerPublicKeyP, + AmountString, + Base32String, + CoinPublicKeyString, + Cs25519Point, + CurrencySpecification, + EddsaPublicKeyString, + EddsaSignatureString, + HashCodeString, + Integer, + InternationalizedString, + LibtoolVersionString, + PaytoHash, + RelativeTime, + RsaPublicKey, + RsaPublicKeySring, + Timestamp, + WireSalt, + codecForAccessToken, + codecForInternationalizedString, + codecForURLString, +} from "./types-taler-common.js"; + +export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; + +export interface RsaDenominationPubKey { + readonly cipher: DenomKeyType.Rsa; + readonly rsa_public_key: string; + readonly age_mask: number; +} + +export interface CsDenominationPubKey { + readonly cipher: DenomKeyType.ClauseSchnorr; + readonly age_mask: number; + readonly cs_public_key: string; +} + +export namespace DenominationPubKey { + export function cmp( + p1: DenominationPubKey, + p2: DenominationPubKey, + ): -1 | 0 | 1 { + if (p1.cipher < p2.cipher) { + return -1; + } else if (p1.cipher > p2.cipher) { + return +1; + } else if ( + p1.cipher === DenomKeyType.Rsa && + p2.cipher === DenomKeyType.Rsa + ) { + if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { + return -1; + } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { + return 1; + } + return strcmp(p1.rsa_public_key, p2.rsa_public_key); + } else if ( + p1.cipher === DenomKeyType.ClauseSchnorr && + p2.cipher === DenomKeyType.ClauseSchnorr + ) { + if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { + return -1; + } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { + return 1; + } + return strcmp(p1.cs_public_key, p2.cs_public_key); + } else { + throw Error("unsupported cipher"); + } + } +} + +export const codecForRsaDenominationPubKey = () => + buildCodecForObject<RsaDenominationPubKey>() + .property("cipher", codecForConstString(DenomKeyType.Rsa)) + .property("rsa_public_key", codecForString()) + .property("age_mask", codecForNumber()) + .build("DenominationPubKey"); + +export const codecForCsDenominationPubKey = () => + buildCodecForObject<CsDenominationPubKey>() + .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr)) + .property("cs_public_key", codecForString()) + .property("age_mask", codecForNumber()) + .build("CsDenominationPubKey"); + +export const codecForDenominationPubKey = () => + buildCodecForUnion<DenominationPubKey>() + .discriminateOn("cipher") + .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey()) + .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) + .build("DenominationPubKey"); /** * Denomination as found in the /keys response from the exchange. */ -export class ExchangeDenomination { +export interface ExchangeDenomination { /** * Value of one coin of the denomination. */ @@ -121,7 +207,7 @@ export class ExchangeDenomination { /** * Signature by the auditor that a particular denomination key is audited. */ -export class AuditorDenomSig { +export interface AuditorDenomSig { /** * Denomination public key's hash. */ @@ -136,7 +222,7 @@ export class AuditorDenomSig { /** * Auditor information as given by the exchange in /keys. */ -export class ExchangeAuditor { +export interface ExchangeAuditor { /** * Auditor's public key. */ @@ -299,402 +385,10 @@ export interface CoinDepositPermission { } /** - * Information about an exchange as stored inside a - * merchant's contract terms. - */ -export interface ExchangeHandle { - // The exchange's base URL. - url: string; - - // Master public key of the exchange. - master_pub: EddsaPublicKeyString; -} - -export interface AuditorHandle { - /** - * Official name of the auditor. - */ - name: string; - - /** - * Master public signing key of the auditor. - */ - auditor_pub: EddsaPublicKeyString; - - /** - * Base URL of the auditor. - */ - url: string; -} - -// Delivery location, loosely modeled as a subset of -// ISO20022's PostalAddress25. -export interface Location { - // Nation with its own government. - country?: string; - - // Identifies a subdivision of a country such as state, region, county. - country_subdivision?: string; - - // Identifies a subdivision within a country sub-division. - district?: string; - - // Name of a built-up area, with defined boundaries, and a local government. - town?: string; - - // Specific location name within the town. - town_location?: string; - - // Identifier consisting of a group of letters and/or numbers that - // is added to a postal address to assist the sorting of mail. - post_code?: string; - - // Name of a street or thoroughfare. - street?: string; - - // Name of the building or house. - building_name?: string; - - // Number that identifies the position of a building on a street. - building_number?: string; - - // Free-form address lines, should not exceed 7 elements. - address_lines?: string[]; -} - -export interface MerchantInfo { - // The merchant's legal name of business. - name: string; - - // Label for a location with the business address of the merchant. - email?: string; - - // Label for a location with the business address of the merchant. - website?: string; - - // An optional base64-encoded product image. - logo?: ImageDataUrl; - - // Label for a location with the business address of the merchant. - address?: Location; - - // Label for a location that denotes the jurisdiction for disputes. - // Some of the typical fields for a location (such as a street address) may be absent. - jurisdiction?: Location; -} - -export interface Tax { - // the name of the tax - name: string; - - // amount paid in tax - tax: AmountString; -} - -export interface Product { - // merchant-internal identifier for the product. - product_id?: string; - - // Human-readable product description. - description: string; - - // Map from IETF BCP 47 language tags to localized descriptions - description_i18n?: InternationalizedString; - - // The number of units of the product to deliver to the customer. - quantity?: Integer; - - // The unit in which the product is measured (liters, kilograms, packages, etc.) - unit?: string; - - // The price of the product; this is the total price for quantity times unit of this product. - price?: AmountString; - - // An optional base64-encoded product image - image?: ImageDataUrl; - - // a list of taxes paid by the merchant for this product. Can be empty. - taxes?: Tax[]; - - // time indicating when this product should be delivered - delivery_date?: TalerProtocolTimestamp; -} - -export interface InternationalizedString { - [lang_tag: string]: string; -} - -/** - * Contract terms from a merchant. - * FIXME: Add type field! - */ -export interface MerchantContractTerms { - // The hash of the merchant instance's wire details. - h_wire: string; - - // Specifies for how long the wallet should try to get an - // automatic refund for the purchase. If this field is - // present, the wallet should wait for a few seconds after - // the purchase and then automatically attempt to obtain - // a refund. The wallet should probe until "delay" - // after the payment was successful (i.e. via long polling - // or via explicit requests with exponential back-off). - // - // In particular, if the wallet is offline - // at that time, it MUST repeat the request until it gets - // one response from the merchant after the delay has expired. - // If the refund is granted, the wallet MUST automatically - // recover the payment. This is used in case a merchant - // knows that it might be unable to satisfy the contract and - // desires for the wallet to attempt to get the refund without any - // customer interaction. Note that it is NOT an error if the - // merchant does not grant a refund. - auto_refund?: TalerProtocolDuration; - - // Wire transfer method identifier for the wire method associated with h_wire. - // The wallet may only select exchanges via a matching auditor if the - // exchange also supports this wire method. - // The wire transfer fees must be added based on this wire transfer method. - wire_method: string; - - // Human-readable description of the whole purchase. - summary: string; - - // Map from IETF BCP 47 language tags to localized summaries. - summary_i18n?: InternationalizedString; - - // Unique, free-form identifier for the proposal. - // Must be unique within a merchant instance. - // For merchants that do not store proposals in their DB - // before the customer paid for them, the order_id can be used - // by the frontend to restore a proposal from the information - // encoded in it (such as a short product identifier and timestamp). - order_id: string; - - // Total price for the transaction. - // The exchange will subtract deposit fees from that amount - // before transferring it to the merchant. - amount: string; - - // Nonce generated by the wallet and echoed by the merchant - // in this field when the proposal is generated. - nonce: string; - - // After this deadline, the merchant won't accept payments for the contract. - pay_deadline: TalerProtocolTimestamp; - - // More info about the merchant, see below. - merchant: MerchantInfo; - - // Merchant's public key used to sign this proposal; this information - // is typically added by the backend. Note that this can be an ephemeral key. - merchant_pub: string; - - // Time indicating when the order should be delivered. - // May be overwritten by individual products. - delivery_date?: TalerProtocolTimestamp; - - // Delivery location for (all!) products. - delivery_location?: Location; - - // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. - exchanges: ExchangeHandle[]; - - // List of products that are part of the purchase (see Product). - products?: Product[]; - - // After this deadline has passed, no refunds will be accepted. - refund_deadline: TalerProtocolTimestamp; - - // Transfer deadline for the exchange. Must be in the - // deposit permissions of coins used to pay for this order. - wire_transfer_deadline: TalerProtocolTimestamp; - - // Time when this contract was generated. - timestamp: TalerProtocolTimestamp; - - // Base URL of the (public!) merchant backend API. - // Must be an absolute URL that ends with a slash. - merchant_base_url: string; - - // URL that will show that the order was successful after - // it has been paid for. Optional, but either fulfillment_url - // or fulfillment_message must be specified in every - // contract terms. - // - // If a non-unique fulfillment URL is used, a customer can only - // buy the order once and will be redirected to a previous purchase - // when trying to buy an order with the same fulfillment URL a second - // time. This is useful for digital goods that a customer only needs - // to buy once but should be able to repeatedly download. - // - // For orders where the customer is expected to be able to make - // repeated purchases (for equivalent goods), the fulfillment URL - // should be made unique for every order. The easiest way to do - // this is to include a unique order ID in the fulfillment URL. - // - // When POSTing to the merchant, the placeholder text "${ORDER_ID}" - // is be replaced with the actual order ID (useful if the - // order ID is generated server-side and needs to be - // in the URL). Note that this placeholder can only be used once. - // Front-ends may use other means to generate a unique fulfillment URL. - fulfillment_url?: string; - - // URL where the same contract could be ordered again (if - // available). Returned also at the public order endpoint - // for people other than the actual buyer (hence public, - // in case order IDs are guessable). - public_reorder_url?: string; - - // Message shown to the customer after paying for the order. - // Either fulfillment_url or fulfillment_message must be specified. - fulfillment_message?: string; - - // Map from IETF BCP 47 language tags to localized fulfillment - // messages. - fulfillment_message_i18n?: InternationalizedString; - - // Maximum total deposit fee accepted by the merchant for this contract. - // Overrides defaults of the merchant instance. - max_fee: string; - - // Extra data that is only interpreted by the merchant frontend. - // Useful when the merchant needs to store extra information on a - // contract without storing it separately in their database. - // Must really be an Object (not a string, integer, float or array). - extra?: any; - - // Minimum age the buyer must have (in years). Default is 0. - // This value is at least as large as the maximum over all - // minimum age requirements of the products in this contract. - // It might also be set independent of any product, due to - // legal requirements. - minimum_age?: Integer; -} - -/** - * Refund permission in the format that the merchant gives it to us. - */ -export interface MerchantAbortPayRefundDetails { - /** - * Amount to be refunded. - */ - refund_amount: string; - - /** - * Fee for the refund. - */ - refund_fee: string; - - /** - * Public key of the coin being refunded. - */ - coin_pub: string; - - /** - * Refund transaction ID between merchant and exchange. - */ - rtransaction_id: number; - - /** - * Exchange's key used for the signature. - */ - exchange_pub?: string; - - /** - * Exchange's signature to confirm the refund. - */ - exchange_sig?: string; - - /** - * Error replay from the exchange (if any). - */ - exchange_reply?: any; - - /** - * Error code from the exchange (if any). - */ - exchange_code?: number; - - /** - * HTTP status code of the exchange's response - * to the merchant's refund request. - */ - exchange_http_status: number; -} - -/** - * Planchet detail sent to the merchant. - */ -export interface TipPlanchetDetail { - /** - * Hashed denomination public key. - */ - denom_pub_hash: string; - - /** - * Coin's blinded public key. - */ - coin_ev: CoinEnvelope; -} - -/** - * Request sent to the merchant to pick up a tip. - */ -export interface TipPickupRequest { - /** - * Identifier of the tip. - */ - tip_id: string; - - /** - * List of planchets the wallet wants to use for the tip. - */ - planchets: TipPlanchetDetail[]; -} - -/** - * Reserve signature, defined as separate class to facilitate - * schema validation. - */ -export interface MerchantBlindSigWrapperV1 { - /** - * Reserve signature. - */ - blind_sig: string; -} - -/** - * Response of the merchant - * to the TipPickupRequest. - */ -export interface MerchantTipResponseV1 { - /** - * The order of the signatures matches the planchets list. - */ - blind_sigs: MerchantBlindSigWrapperV1[]; -} - -export interface MerchantBlindSigWrapperV2 { - blind_sig: BlindedDenominationSignature; -} - -/** - * Response of the merchant - * to the TipPickupRequest. - */ -export interface MerchantTipResponseV2 { - /** - * The order of the signatures matches the planchets list. - */ - blind_sigs: MerchantBlindSigWrapperV2[]; -} - -/** * Element of the payback list that the * exchange gives us in /keys. */ -export class Recoup { +export interface Recoup { /** * The hash of the denomination public key for which the payback is offered. */ @@ -702,20 +396,9 @@ export class Recoup { } /** - * Structure of one exchange signing key in the /keys response. - */ -export class ExchangeSignKeyJson { - stamp_start: TalerProtocolTimestamp; - stamp_expire: TalerProtocolTimestamp; - stamp_end: TalerProtocolTimestamp; - key: EddsaPublicKeyString; - master_sig: EddsaSignatureString; -} - -/** * Structure that the exchange gives us in /keys. */ -export class ExchangeKeysJson { +export interface ExchangeKeysJson { /** * Canonical, public base URL of the exchange. */ @@ -765,6 +448,108 @@ export class ExchangeKeysJson { wire_fees: { [methodName: string]: WireFeesJson[] }; denominations: DenomGroup[]; + + // Threshold amounts beyond which wallet should + // trigger the KYC process of the issuing exchange. + // Optional option, if not given there is no limit. + // Currency must match currency. + wallet_balance_limit_without_kyc?: AmountString[]; +} + +export interface ExchangeMeltRequest { + coin_pub: CoinPublicKeyString; + confirm_sig: EddsaSignatureString; + denom_pub_hash: HashCodeString; + denom_sig: UnblindedSignature; + rc: string; + value_with_fee: AmountString; + age_commitment_hash?: HashCodeString; +} + +export interface ExchangeMeltResponse { + /** + * Which of the kappa indices does the client not have to reveal. + */ + noreveal_index: number; + + /** + * Signature of TALER_RefreshMeltConfirmationPS whereby the exchange + * affirms the successful melt and confirming the noreveal_index + */ + exchange_sig: EddsaSignatureString; + + /* + * public EdDSA key of the exchange that was used to generate the signature. + * Should match one of the exchange's signing keys from /keys. Again given + * explicitly as the client might otherwise be confused by clock skew as to + * which signing key was used. + */ + exchange_pub: EddsaPublicKeyString; + + /* + * Base URL to use for operations on the refresh context + * (so the reveal operation). If not given, + * the base URL is the same as the one used for this request. + * Can be used if the base URL for /refreshes/ differs from that + * for /coins/, i.e. for load balancing. Clients SHOULD + * respect the refresh_base_url if provided. Any HTTP server + * belonging to an exchange MUST generate a 307 or 308 redirection + * to the correct base URL should a client uses the wrong base + * URL, or if the base URL has changed since the melt. + * + * When melting the same coin twice (technically allowed + * as the response might have been lost on the network), + * the exchange may return different values for the refresh_base_url. + */ + refresh_base_url?: string; +} + +export interface ExchangeRevealItem { + ev_sig: BlindedDenominationSignature; +} + +export interface ExchangeRevealResponse { + // List of the exchange's blinded RSA signatures on the new coins. + ev_sigs: ExchangeRevealItem[]; +} + +export const codecForDenomination = (): Codec<ExchangeDenomination> => + buildCodecForObject<ExchangeDenomination>() + .property("value", codecForString()) + .property("denom_pub", codecForDenominationPubKey()) + .property("fee_withdraw", codecForString()) + .property("fee_deposit", codecForString()) + .property("fee_refresh", codecForString()) + .property("fee_refund", codecForString()) + .property("stamp_start", codecForTimestamp) + .property("stamp_expire_withdraw", codecForTimestamp) + .property("stamp_expire_legal", codecForTimestamp) + .property("stamp_expire_deposit", codecForTimestamp) + .property("master_sig", codecForString()) + .build("Denomination"); + +export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => + buildCodecForObject<AuditorDenomSig>() + .property("denom_pub_h", codecForString()) + .property("auditor_sig", codecForString()) + .build("AuditorDenomSig"); + +export const codecForAuditor = (): Codec<ExchangeAuditor> => + buildCodecForObject<ExchangeAuditor>() + .property("auditor_pub", codecForString()) + .property("auditor_url", codecForString()) + .property("denomination_keys", codecForList(codecForAuditorDenomSig())) + .build("Auditor"); + +/** + * Structure of one exchange signing key in the /keys response. + */ +export class ExchangeSignKeyJson { + stamp_start: TalerProtocolTimestamp; + stamp_expire: TalerProtocolTimestamp; + stamp_end: TalerProtocolTimestamp; + key: EddsaPublicKeyString; + master_sig: EddsaSignatureString; } export type DenomGroup = @@ -823,15 +608,6 @@ export interface DenomCommon { lost?: boolean; } -export type RsaPublicKeySring = string; -export type AgeMask = number; -export type ImageDataUrl = string; - -/** - * 32-byte value representing a point on Curve25519. - */ -export type Cs25519Point = string; - export interface DenomGroupRsa extends DenomGroupCommon { cipher: "RSA"; @@ -867,48 +643,6 @@ export interface DenomGroupCsAgeRestricted extends DenomGroupCommon { } & DenomCommon)[]; } -export interface GlobalFees { - // What date (inclusive) does these fees go into effect? - start_date: TalerProtocolTimestamp; - - // What date (exclusive) does this fees stop going into effect? - end_date: TalerProtocolTimestamp; - - // Account history fee, charged when a user wants to - // obtain a reserve/account history. - history_fee: AmountString; - - // Annual fee charged for having an open account at the - // exchange. Charged to the account. If the account - // balance is insufficient to cover this fee, the account - // is automatically deleted/closed. (Note that the exchange - // will keep the account history around for longer for - // regulatory reasons.) - account_fee: AmountString; - - // Purse fee, charged only if a purse is abandoned - // and was not covered by the account limit. - purse_fee: AmountString; - - // How long will the exchange preserve the account history? - // After an account was deleted/closed, the exchange will - // retain the account history for legal reasons until this time. - history_expiration: TalerProtocolDuration; - - // Non-negative number of concurrent purses that any - // account holder is allowed to create without having - // to pay the purse_fee. - purse_account_limit: number; - - // How long does an exchange keep a purse around after a purse - // has expired (or been successfully merged)? A 'GET' request - // for a purse will succeed until the purse expiration time - // plus this value. - purse_timeout: TalerProtocolDuration; - - // Signature of TALER_GlobalFeesPS. - master_sig: string; -} /** * Wire fees as announced by the exchange. */ @@ -939,538 +673,166 @@ export class WireFeesJson { end_date: TalerProtocolTimestamp; } -/** - * Proposal returned from the contract URL. - */ -export class Proposal { - /** - * Contract terms for the propoal. - * Raw, un-decoded JSON object. - */ - contract_terms: any; - - /** - * Signature over contract, made by the merchant. The public key used for signing - * must be contract_terms.merchant_pub. - */ - sig: string; -} - -/** - * Response from the internal merchant API. - */ -export class CheckPaymentResponse { - order_status: string; - refunded: boolean | undefined; - refunded_amount: string | undefined; - contract_terms: any | undefined; - taler_pay_uri: string | undefined; - contract_url: string | undefined; -} - -/** - * Response from the bank. - */ -export class WithdrawOperationStatusResponse { - status: "selected" | "aborted" | "confirmed" | "pending"; - - selection_done: boolean; - - transfer_done: boolean; - - aborted: boolean; - - amount: string | undefined; - - sender_wire?: string; - - suggested_exchange?: string; - - confirm_transfer_url?: string; - - wire_types: string[]; -} - -/** - * Response from the merchant. - */ -export class RewardPickupGetResponse { - reward_amount: string; - - exchange_url: string; - - next_url?: string; - - expiration: TalerProtocolTimestamp; -} - -export enum DenomKeyType { - Rsa = "RSA", - ClauseSchnorr = "CS", -} - -export namespace DenomKeyType { - export function toIntTag(t: DenomKeyType): number { - switch (t) { - case DenomKeyType.Rsa: - return 1; - case DenomKeyType.ClauseSchnorr: - return 2; - } - } -} - -export interface RsaBlindedDenominationSignature { - cipher: DenomKeyType.Rsa; - blinded_rsa_signature: string; -} - -export interface CSBlindedDenominationSignature { - cipher: DenomKeyType.ClauseSchnorr; -} - -export type BlindedDenominationSignature = - | RsaBlindedDenominationSignature - | CSBlindedDenominationSignature; - -export const codecForRsaBlindedDenominationSignature = () => - buildCodecForObject<RsaBlindedDenominationSignature>() - .property("cipher", codecForConstString(DenomKeyType.Rsa)) - .property("blinded_rsa_signature", codecForString()) - .build("RsaBlindedDenominationSignature"); - -export const codecForBlindedDenominationSignature = () => - buildCodecForUnion<BlindedDenominationSignature>() - .discriminateOn("cipher") - .alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature()) - .build("BlindedDenominationSignature"); - -export class ExchangeWithdrawResponse { - ev_sig: BlindedDenominationSignature; -} - -export class ExchangeWithdrawBatchResponse { - ev_sigs: ExchangeWithdrawResponse[]; -} - -export interface MerchantPayResponse { - sig: string; - pos_confirmation?: string; -} - -export interface ExchangeMeltRequest { - coin_pub: CoinPublicKeyString; - confirm_sig: EddsaSignatureString; - denom_pub_hash: HashCodeString; - denom_sig: UnblindedSignature; - rc: string; - value_with_fee: AmountString; - age_commitment_hash?: HashCodeString; -} - -export interface ExchangeMeltResponse { - /** - * Which of the kappa indices does the client not have to reveal. - */ - noreveal_index: number; +export interface ExchangeWireAccount { + // payto:// URI identifying the account and wire method + payto_uri: string; - /** - * Signature of TALER_RefreshMeltConfirmationPS whereby the exchange - * affirms the successful melt and confirming the noreveal_index - */ - exchange_sig: EddsaSignatureString; + // URI to convert amounts from or to the currency used by + // this wire account of the exchange. Missing if no + // conversion is applicable. + conversion_url?: string; - /* - * public EdDSA key of the exchange that was used to generate the signature. - * Should match one of the exchange's signing keys from /keys. Again given - * explicitly as the client might otherwise be confused by clock skew as to - * which signing key was used. - */ - exchange_pub: EddsaPublicKeyString; + // Restrictions that apply to bank accounts that would send + // funds to the exchange (crediting this exchange bank account). + // Optional, empty array for unrestricted. + credit_restrictions: AccountRestriction[]; - /* - * Base URL to use for operations on the refresh context - * (so the reveal operation). If not given, - * the base URL is the same as the one used for this request. - * Can be used if the base URL for /refreshes/ differs from that - * for /coins/, i.e. for load balancing. Clients SHOULD - * respect the refresh_base_url if provided. Any HTTP server - * belonging to an exchange MUST generate a 307 or 308 redirection - * to the correct base URL should a client uses the wrong base - * URL, or if the base URL has changed since the melt. - * - * When melting the same coin twice (technically allowed - * as the response might have been lost on the network), - * the exchange may return different values for the refresh_base_url. - */ - refresh_base_url?: string; -} + // Restrictions that apply to bank accounts that would receive + // funds from the exchange (debiting this exchange bank account). + // Optional, empty array for unrestricted. + debit_restrictions: AccountRestriction[]; -export interface ExchangeRevealItem { - ev_sig: BlindedDenominationSignature; -} + // Signature using the exchange's offline key over + // a TALER_MasterWireDetailsPS + // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. + master_sig: EddsaSignatureString; -export interface ExchangeRevealResponse { - // List of the exchange's blinded RSA signatures on the new coins. - ev_sigs: ExchangeRevealItem[]; + // Display label wallets should use to show this + // bank account. + // Since protocol **v19**. + bank_label?: string; + priority?: number; } -interface MerchantOrderStatusPaid { - // Was the payment refunded (even partially, via refund or abort)? - refunded: boolean; - - // Is any amount of the refund still waiting to be picked up (even partially)? - refund_pending: boolean; +export const codecForExchangeWireAccount = (): Codec<ExchangeWireAccount> => + buildCodecForObject<ExchangeWireAccount>() + .property("conversion_url", codecOptional(codecForStringURL())) + .property("credit_restrictions", codecForList(codecForAny())) + .property("debit_restrictions", codecForList(codecForAny())) + .property("master_sig", codecForString()) + .property("payto_uri", codecForString()) + .property("bank_label", codecOptional(codecForString())) + .property("priority", codecOptional(codecForNumber())) + .build("WireAccount"); - // Amount that was refunded in total. +export interface ExchangeRefundRequest { + // Amount to be refunded, can be a fraction of the + // coin's total deposit value (including deposit fee); + // must be larger than the refund fee. refund_amount: AmountString; - // Amount that already taken by the wallet. - refund_taken: AmountString; -} - -interface MerchantOrderRefundResponse { - /** - * Amount that was refunded in total. - */ - refund_amount: AmountString; + // SHA-512 hash of the contact of the merchant with the customer. + h_contract_terms: HashCodeString; - /** - * Successful refunds for this payment, empty array for none. - */ - refunds: MerchantCoinRefundStatus[]; + // 64-bit transaction id of the refund transaction between merchant and customer. + rtransaction_id: number; - /** - * Public key of the merchant. - */ + // EdDSA public key of the merchant. merchant_pub: EddsaPublicKeyString; -} - -export type MerchantCoinRefundStatus = - | MerchantCoinRefundSuccessStatus - | MerchantCoinRefundFailureStatus; -export interface MerchantCoinRefundSuccessStatus { - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; + // EdDSA signature of the merchant over a + // TALER_RefundRequestPS with purpose + // TALER_SIGNATURE_MERCHANT_REFUND + // affirming the refund. + merchant_sig: EddsaPublicKeyString; +} - // the EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund +export interface ExchangeRefundSuccessResponse { + // The EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND over + // a TALER_RecoupRefreshConfirmationPS + // using a current signing key of the + // exchange affirming the successful refund. exchange_sig: EddsaSignatureString; - // public EdDSA key of the exchange that was used to generate the signature. + // Public EdDSA key of the exchange that was used to generate the signature. // Should match one of the exchange's signing keys from /keys. It is given // explicitly as the client might otherwise be confused by clock skew as to // which signing key was used. exchange_pub: EddsaPublicKeyString; - - // Refund transaction ID. - rtransaction_id: number; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKeyString; - - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; - - execution_time: TalerProtocolTimestamp; } -export interface MerchantCoinRefundFailureStatus { - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: number; - - // Taler error code from the exchange reply, if available. - exchange_code?: number; - - // If available, HTTP reply from the exchange. - exchange_reply?: any; - - // Refund transaction ID. - rtransaction_id: number; - - // public key of a coin that was refunded - coin_pub: EddsaPublicKeyString; +export const codecForExchangeRefundSuccessResponse = + (): Codec<ExchangeRefundSuccessResponse> => + buildCodecForObject<ExchangeRefundSuccessResponse>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .build("ExchangeRefundSuccessResponse"); - // Amount that was refunded, including refund fee charged by the exchange - // to the customer. - refund_amount: AmountString; +export type AccountRestriction = + | RegexAccountRestriction + | DenyAllAccountRestriction; - execution_time: TalerProtocolTimestamp; +export interface DenyAllAccountRestriction { + type: "deny"; } -export interface MerchantOrderStatusUnpaid { - /** - * URI that the wallet must process to complete the payment. - */ - taler_pay_uri: string; +// Accounts interacting with this type of account +// restriction must have a payto://-URI matching +// the given regex. +export interface RegexAccountRestriction { + type: "regex"; - /** - * Alternative order ID which was paid for already in the same session. - * - * Only given if the same product was purchased before in the same session. - */ - already_paid_order_id?: string; -} + // Regular expression that the payto://-URI of the + // partner account must follow. The regular expression + // should follow posix-egrep, but without support for character + // classes, GNU extensions, back-references or intervals. See + // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html + // for a description of the posix-egrep syntax. Applications + // may support regexes with additional features, but exchanges + // must not use such regexes. + payto_regex: string; -/** - * Response body for the following endpoint: - * - * POST {talerBankIntegrationApi}/withdrawal-operation/{wopid} - */ -export interface BankWithdrawalOperationPostResponse { - // Current status of the operation - // pending: the operation is pending parameters selection (exchange and reserve public key) - // selected: the operations has been selected and is pending confirmation - // aborted: the operation has been aborted - // confirmed: the transfer has been confirmed and registered by the bank - status: "selected" | "aborted" | "confirmed" | "pending"; - - // URL that the user needs to navigate to in order to - // complete some final confirmation (e.g. 2FA). - // - // Only applicable when status is selected or pending. - // It may contain withdrawal operation id - confirm_transfer_url?: string; + // Hint for a human to understand the restriction + // (that is hopefully easier to comprehend than the regex itself). + human_hint: string; - // Deprecated field use status instead - // 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; + // Map from IETF BCP 47 language tags to localized + // human hints. + human_hint_i18n?: InternationalizedString; } -export const codeForBankWithdrawalOperationPostResponse = - (): Codec<BankWithdrawalOperationPostResponse> => - buildCodecForObject<BankWithdrawalOperationPostResponse>() - .property( - "status", - codecForEither( - codecForConstString("selected"), - codecForConstString("confirmed"), - codecForConstString("aborted"), - codecForConstString("pending"), - ), - ) - .property("confirm_transfer_url", codecOptional(codecForString())) - .property("transfer_done", codecForBoolean()) - .build("BankWithdrawalOperationPostResponse"); - -export type DenominationPubKey = RsaDenominationPubKey | CsDenominationPubKey; +export type CoinEnvelope = CoinEnvelopeRsa | CoinEnvelopeCs; -export interface RsaDenominationPubKey { - readonly cipher: DenomKeyType.Rsa; - readonly rsa_public_key: string; - readonly age_mask: number; +export interface CoinEnvelopeRsa { + cipher: DenomKeyType.Rsa; + rsa_blinded_planchet: string; } -export interface CsDenominationPubKey { - readonly cipher: DenomKeyType.ClauseSchnorr; - readonly age_mask: number; - readonly cs_public_key: string; +export interface CoinEnvelopeCs { + cipher: DenomKeyType.ClauseSchnorr; + // FIXME: add remaining fields } -export namespace DenominationPubKey { - export function cmp( - p1: DenominationPubKey, - p2: DenominationPubKey, - ): -1 | 0 | 1 { - if (p1.cipher < p2.cipher) { - return -1; - } else if (p1.cipher > p2.cipher) { - return +1; - } else if ( - p1.cipher === DenomKeyType.Rsa && - p2.cipher === DenomKeyType.Rsa - ) { - if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { - return -1; - } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { - return 1; - } - return strcmp(p1.rsa_public_key, p2.rsa_public_key); - } else if ( - p1.cipher === DenomKeyType.ClauseSchnorr && - p2.cipher === DenomKeyType.ClauseSchnorr - ) { - if ((p1.age_mask ?? 0) < (p2.age_mask ?? 0)) { - return -1; - } else if ((p1.age_mask ?? 0) > (p2.age_mask ?? 0)) { - return 1; - } - return strcmp(p1.cs_public_key, p2.cs_public_key); - } else { - throw Error("unsupported cipher"); - } - } +export interface ExchangeWithdrawRequest { + denom_pub_hash: HashCodeString; + reserve_sig: EddsaSignatureString; + coin_ev: CoinEnvelope; } -export const codecForRsaDenominationPubKey = () => - buildCodecForObject<RsaDenominationPubKey>() - .property("cipher", codecForConstString(DenomKeyType.Rsa)) - .property("rsa_public_key", codecForString()) - .property("age_mask", codecForNumber()) - .build("DenominationPubKey"); - -export const codecForCsDenominationPubKey = () => - buildCodecForObject<CsDenominationPubKey>() - .property("cipher", codecForConstString(DenomKeyType.ClauseSchnorr)) - .property("cs_public_key", codecForString()) - .property("age_mask", codecForNumber()) - .build("CsDenominationPubKey"); - -export const codecForDenominationPubKey = () => - buildCodecForUnion<DenominationPubKey>() - .discriminateOn("cipher") - .alternative(DenomKeyType.Rsa, codecForRsaDenominationPubKey()) - .alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey()) - .build("DenominationPubKey"); - -export type LitAmountString = `${string}:${number}`; - -declare const __amount_str: unique symbol; -export type AmountString = - | (string & { [__amount_str]: true }) - | LitAmountString; -// export type AmountString = string; -export type Base32String = string; -export type EddsaSignatureString = string; -export type EddsaPublicKeyString = string; -export type EddsaPrivateKeyString = string; -export type CoinPublicKeyString = string; - -export const codecForDenomination = (): Codec<ExchangeDenomination> => - buildCodecForObject<ExchangeDenomination>() - .property("value", codecForString()) - .property("denom_pub", codecForDenominationPubKey()) - .property("fee_withdraw", codecForString()) - .property("fee_deposit", codecForString()) - .property("fee_refresh", codecForString()) - .property("fee_refund", codecForString()) - .property("stamp_start", codecForTimestamp) - .property("stamp_expire_withdraw", codecForTimestamp) - .property("stamp_expire_legal", codecForTimestamp) - .property("stamp_expire_deposit", codecForTimestamp) - .property("master_sig", codecForString()) - .build("Denomination"); - -export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => - buildCodecForObject<AuditorDenomSig>() - .property("denom_pub_h", codecForString()) - .property("auditor_sig", codecForString()) - .build("AuditorDenomSig"); - -export const codecForAuditor = (): Codec<ExchangeAuditor> => - buildCodecForObject<ExchangeAuditor>() - .property("auditor_pub", codecForString()) - .property("auditor_url", codecForString()) - .property("denomination_keys", codecForList(codecForAuditorDenomSig())) - .build("Auditor"); +export interface ExchangeBatchWithdrawRequest { + planchets: ExchangeWithdrawRequest[]; +} -export const codecForExchangeHandle = (): Codec<ExchangeHandle> => - buildCodecForObject<ExchangeHandle>() - .property("master_pub", codecForString()) - .property("url", codecForString()) - .build("ExchangeHandle"); +export interface ExchangeRefreshRevealRequest { + new_denoms_h: HashCodeString[]; + coin_evs: CoinEnvelope[]; + /** + * kappa - 1 transfer private keys (ephemeral ECDHE keys). + */ + transfer_privs: string[]; -export const codecForAuditorHandle = (): Codec<AuditorHandle> => - buildCodecForObject<AuditorHandle>() - .property("name", codecForString()) - .property("auditor_pub", codecForString()) - .property("url", codecForString()) - .build("AuditorHandle"); - -export const codecForLocation = (): Codec<Location> => - buildCodecForObject<Location>() - .property("country", codecOptional(codecForString())) - .property("country_subdivision", codecOptional(codecForString())) - .property("building_name", codecOptional(codecForString())) - .property("building_number", codecOptional(codecForString())) - .property("district", codecOptional(codecForString())) - .property("street", codecOptional(codecForString())) - .property("post_code", codecOptional(codecForString())) - .property("town", codecOptional(codecForString())) - .property("town_location", codecOptional(codecForString())) - .property("address_lines", codecOptional(codecForList(codecForString()))) - .build("Location"); - -export const codecForMerchantInfo = (): Codec<MerchantInfo> => - buildCodecForObject<MerchantInfo>() - .property("name", codecForString()) - .property("address", codecOptional(codecForLocation())) - .property("jurisdiction", codecOptional(codecForLocation())) - .build("MerchantInfo"); - -export const codecForInternationalizedString = - (): Codec<InternationalizedString> => codecForMap(codecForString()); - -export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> => - buildCodecForObject<MerchantContractTerms>() - .property("order_id", codecForString()) - .property("fulfillment_url", codecOptional(codecForString())) - .property("fulfillment_message", codecOptional(codecForString())) - .property( - "fulfillment_message_i18n", - codecOptional(codecForInternationalizedString()), - ) - .property("merchant_base_url", codecForString()) - .property("h_wire", codecForString()) - .property("auto_refund", codecOptional(codecForDuration)) - .property("wire_method", codecForString()) - .property("summary", codecForString()) - .property("summary_i18n", codecOptional(codecForInternationalizedString())) - .property("nonce", codecForString()) - .property("amount", codecForAmountString()) - .property("pay_deadline", codecForTimestamp) - .property("refund_deadline", codecForTimestamp) - .property("wire_transfer_deadline", codecForTimestamp) - .property("timestamp", codecForTimestamp) - .property("delivery_location", codecOptional(codecForLocation())) - .property("delivery_date", codecOptional(codecForTimestamp)) - .property("max_fee", codecForAmountString()) - .property("merchant", codecForMerchantInfo()) - .property("merchant_pub", codecForString()) - .property("exchanges", codecForList(codecForExchangeHandle())) - .property("products", codecOptional(codecForList(codecForProduct()))) - .property("extra", codecForAny()) - .property("minimum_age", codecOptional(codecForNumber())) - .build("MerchantContractTerms"); + transfer_pub: EddsaPublicKeyString; -export const codecForPeerContractTerms = (): Codec<PeerContractTerms> => - buildCodecForObject<PeerContractTerms>() - .property("summary", codecForString()) - .property("amount", codecForAmountString()) - .property("purse_expiration", codecForTimestamp) - .build("PeerContractTerms"); + link_sigs: EddsaSignatureString[]; -export const codecForMerchantRefundPermission = - (): Codec<MerchantAbortPayRefundDetails> => - buildCodecForObject<MerchantAbortPayRefundDetails>() - .property("refund_amount", codecForAmountString()) - .property("refund_fee", codecForAmountString()) - .property("coin_pub", codecForString()) - .property("rtransaction_id", codecForNumber()) - .property("exchange_http_status", codecForNumber()) - .property("exchange_code", codecOptional(codecForNumber())) - .property("exchange_reply", codecOptional(codecForAny())) - .property("exchange_sig", codecOptional(codecForString())) - .property("exchange_pub", codecOptional(codecForString())) - .build("MerchantRefundPermission"); - -export const codecForBlindSigWrapperV2 = (): Codec<MerchantBlindSigWrapperV2> => - buildCodecForObject<MerchantBlindSigWrapperV2>() - .property("blind_sig", codecForBlindedDenominationSignature()) - .build("MerchantBlindSigWrapperV2"); - -export const codecForMerchantTipResponseV2 = (): Codec<MerchantTipResponseV2> => - buildCodecForObject<MerchantTipResponseV2>() - .property("blind_sigs", codecForList(codecForBlindSigWrapperV2())) - .build("MerchantTipResponseV2"); + /** + * Iff the corresponding denomination has support for age restriction, + * the client MUST provide the original age commitment, i.e. the vector + * of public keys. + */ + old_age_commitment?: Edx25519PublicKeyEnc[]; +} export const codecForRecoup = (): Codec<Recoup> => buildCodecForObject<Recoup>() @@ -1506,7 +868,10 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => buildCodecForObject<ExchangeKeysJson>() .property("base_url", codecForString()) .property("currency", codecForString()) - .property("currency_specification", codecOptional(codecForCurrencySpecificiation())) + .property( + "currency_specification", + codecOptional(codecForCurrencySpecificiation()), + ) .property("master_public_key", codecForString()) .property("auditors", codecForList(codecForAuditor())) .property("list_issue_date", codecForTimestamp) @@ -1518,6 +883,10 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> => .property("accounts", codecForList(codecForExchangeWireAccount())) .property("wire_fees", codecForMap(codecForList(codecForWireFeesJson()))) .property("denominations", codecForList(codecForNgDenominations)) + .property( + "wallet_balance_limit_without_kyc", + codecOptional(codecForList(codecForAmountString())), + ) .build("ExchangeKeysJson"); export const codecForWireFeesJson = (): Codec<WireFeesJson> => @@ -1529,53 +898,6 @@ export const codecForWireFeesJson = (): Codec<WireFeesJson> => .property("end_date", codecForTimestamp) .build("WireFeesJson"); -export const codecForProposal = (): Codec<Proposal> => - buildCodecForObject<Proposal>() - .property("contract_terms", codecForAny()) - .property("sig", codecForString()) - .build("Proposal"); - -export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> => - buildCodecForObject<CheckPaymentResponse>() - .property("order_status", codecForString()) - .property("refunded", codecOptional(codecForBoolean())) - .property("refunded_amount", codecOptional(codecForString())) - .property("contract_terms", codecOptional(codecForAny())) - .property("taler_pay_uri", codecOptional(codecForString())) - .property("contract_url", codecOptional(codecForString())) - .build("CheckPaymentResponse"); - -export const codecForWithdrawOperationStatusResponse = - (): Codec<WithdrawOperationStatusResponse> => - buildCodecForObject<WithdrawOperationStatusResponse>() - .property( - "status", - codecForEither( - codecForConstString("selected"), - codecForConstString("confirmed"), - codecForConstString("aborted"), - codecForConstString("pending"), - ), - ) - .property("selection_done", codecForBoolean()) - .property("transfer_done", codecForBoolean()) - .property("aborted", codecForBoolean()) - .property("amount", codecOptional(codecForString())) - .property("sender_wire", codecOptional(codecForString())) - .property("suggested_exchange", codecOptional(codecForString())) - .property("confirm_transfer_url", codecOptional(codecForString())) - .property("wire_types", codecForList(codecForString())) - .build("WithdrawOperationStatusResponse"); - -export const codecForRewardPickupGetResponse = - (): Codec<RewardPickupGetResponse> => - buildCodecForObject<RewardPickupGetResponse>() - .property("reward_amount", codecForString()) - .property("exchange_url", codecForString()) - .property("next_url", codecOptional(codecForString())) - .property("expiration", codecForTimestamp) - .build("TipPickupGetResponse"); - export const codecForRecoupConfirmation = (): Codec<RecoupConfirmation> => buildCodecForObject<RecoupConfirmation>() .property("reserve_pub", codecOptional(codecForString())) @@ -1587,18 +909,61 @@ export const codecForWithdrawResponse = (): Codec<ExchangeWithdrawResponse> => .property("ev_sig", codecForBlindedDenominationSignature()) .build("WithdrawResponse"); +export class ExchangeWithdrawResponse { + ev_sig: BlindedDenominationSignature; +} + +export class ExchangeWithdrawBatchResponse { + ev_sigs: ExchangeWithdrawResponse[]; +} + +export enum DenomKeyType { + Rsa = "RSA", + ClauseSchnorr = "CS", +} + +export namespace DenomKeyType { + export function toIntTag(t: DenomKeyType): number { + switch (t) { + case DenomKeyType.Rsa: + return 1; + case DenomKeyType.ClauseSchnorr: + return 2; + } + } +} + +export interface RsaBlindedDenominationSignature { + cipher: DenomKeyType.Rsa; + blinded_rsa_signature: string; +} + +export interface CSBlindedDenominationSignature { + cipher: DenomKeyType.ClauseSchnorr; +} + +export type BlindedDenominationSignature = + | RsaBlindedDenominationSignature + | CSBlindedDenominationSignature; + +export const codecForRsaBlindedDenominationSignature = () => + buildCodecForObject<RsaBlindedDenominationSignature>() + .property("cipher", codecForConstString(DenomKeyType.Rsa)) + .property("blinded_rsa_signature", codecForString()) + .build("RsaBlindedDenominationSignature"); + +export const codecForBlindedDenominationSignature = () => + buildCodecForUnion<BlindedDenominationSignature>() + .discriminateOn("cipher") + .alternative(DenomKeyType.Rsa, codecForRsaBlindedDenominationSignature()) + .build("BlindedDenominationSignature"); + export const codecForExchangeWithdrawBatchResponse = (): Codec<ExchangeWithdrawBatchResponse> => buildCodecForObject<ExchangeWithdrawBatchResponse>() .property("ev_sigs", codecForList(codecForWithdrawResponse())) .build("WithdrawBatchResponse"); -export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => - buildCodecForObject<MerchantPayResponse>() - .property("sig", codecForString()) - .property("pos_confirmation", codecOptional(codecForString())) - .build("MerchantPayResponse"); - export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> => buildCodecForObject<ExchangeMeltResponse>() .property("exchange_pub", codecForString()) @@ -1618,93 +983,6 @@ export const codecForExchangeRevealResponse = .property("ev_sigs", codecForList(codecForExchangeRevealItem())) .build("ExchangeRevealResponse"); -export const codecForMerchantOrderStatusPaid = - (): Codec<MerchantOrderStatusPaid> => - buildCodecForObject<MerchantOrderStatusPaid>() - .property("refund_amount", codecForAmountString()) - .property("refund_taken", codecForAmountString()) - .property("refund_pending", codecForBoolean()) - .property("refunded", codecForBoolean()) - .build("MerchantOrderStatusPaid"); - -export const codecForMerchantOrderStatusUnpaid = - (): Codec<MerchantOrderStatusUnpaid> => - buildCodecForObject<MerchantOrderStatusUnpaid>() - .property("taler_pay_uri", codecForString()) - .property("already_paid_order_id", codecOptional(codecForString())) - .build("MerchantOrderStatusUnpaid"); - -export interface AbortRequest { - // hash of the order's contract terms (this is used to authenticate the - // wallet/customer in case $ORDER_ID is guessable). - h_contract: string; - - // List of coins the wallet would like to see refunds for. - // (Should be limited to the coins for which the original - // payment succeeded, as far as the wallet knows.) - coins: AbortingCoin[]; -} - -export interface AbortingCoin { - // Public key of a coin for which the wallet is requesting an abort-related refund. - coin_pub: EddsaPublicKeyString; - - // The amount to be refunded (matches the original contribution) - contribution: AmountString; - - // URL of the exchange this coin was withdrawn from. - exchange_url: string; -} - -export interface AbortResponse { - // List of refund responses about the coins that the wallet - // requested an abort for. In the same order as the 'coins' - // from the original request. - // The rtransaction_id is implied to be 0. - refunds: MerchantAbortPayRefundStatus[]; -} - -export type MerchantAbortPayRefundStatus = - | MerchantAbortPayRefundSuccessStatus - | MerchantAbortPayRefundFailureStatus; - -// Details about why a refund failed. -export interface MerchantAbortPayRefundFailureStatus { - // Used as tag for the sum type RefundStatus sum type. - type: "failure"; - - // HTTP status of the exchange request, must NOT be 200. - exchange_status: number; - - // Taler error code from the exchange reply, if available. - exchange_code?: number; - - // If available, HTTP reply from the exchange. - exchange_reply?: unknown; -} - -// Additional details needed to verify the refund confirmation signature -// (h_contract_terms and merchant_pub) are already known -// to the wallet and thus not included. -export interface MerchantAbortPayRefundSuccessStatus { - // Used as tag for the sum type MerchantCoinRefundStatus sum type. - type: "success"; - - // HTTP status of the exchange request, 200 (integer) required for refund confirmations. - exchange_status: 200; - - // the EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the - // exchange affirming the successful refund - exchange_sig: string; - - // public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: string; -} - export interface FutureKeysResponse { future_denoms: any[]; @@ -1727,185 +1005,6 @@ export const codecForKeysManagementResponse = (): Codec<FutureKeysResponse> => .property("signkey_secmod_public_key", codecForAny()) .build("FutureKeysResponse"); -export interface MerchantConfigResponse { - currency: string; - name: string; - version: string; -} - -export const codecForMerchantConfigResponse = - (): Codec<MerchantConfigResponse> => - buildCodecForObject<MerchantConfigResponse>() - .property("currency", codecForString()) - .property("name", codecForString()) - .property("version", codecForString()) - .build("MerchantConfigResponse"); - -export enum ExchangeProtocolVersion { - /** - * Current version supported by the wallet. - */ - V12 = 12, -} - -export enum MerchantProtocolVersion { - /** - * Current version supported by the wallet. - */ - V3 = 3, -} - -export type CoinEnvelope = CoinEnvelopeRsa | CoinEnvelopeCs; - -export interface CoinEnvelopeRsa { - cipher: DenomKeyType.Rsa; - rsa_blinded_planchet: string; -} - -export interface CoinEnvelopeCs { - cipher: DenomKeyType.ClauseSchnorr; - // FIXME: add remaining fields -} - -export type HashCodeString = string; - -export interface ExchangeWithdrawRequest { - denom_pub_hash: HashCodeString; - reserve_sig: EddsaSignatureString; - coin_ev: CoinEnvelope; -} - -export interface ExchangeBatchWithdrawRequest { - planchets: ExchangeWithdrawRequest[]; -} - -export interface ExchangeRefreshRevealRequest { - new_denoms_h: HashCodeString[]; - coin_evs: CoinEnvelope[]; - /** - * kappa - 1 transfer private keys (ephemeral ECDHE keys). - */ - transfer_privs: string[]; - - transfer_pub: EddsaPublicKeyString; - - link_sigs: EddsaSignatureString[]; - - /** - * Iff the corresponding denomination has support for age restriction, - * the client MUST provide the original age commitment, i.e. the vector - * of public keys. - */ - old_age_commitment?: Edx25519PublicKeyEnc[]; -} - -interface DepositConfirmationSignature { - // The EdDSA signature of `TALER_DepositConfirmationPS` using a current - // `signing key of the exchange <sign-key-priv>` affirming the successful - // deposit and that the exchange will transfer the funds after the refund - // deadline, or as soon as possible if the refund deadline is zero. - exchange_sig: EddsaSignatureString; -} - -export interface BatchDepositSuccess { - // Optional base URL of the exchange for looking up wire transfers - // associated with this transaction. If not given, - // the base URL is the same as the one used for this request. - // Can be used if the base URL for ``/transactions/`` differs from that - // for ``/coins/``, i.e. for load balancing. Clients SHOULD - // respect the ``transaction_base_url`` if provided. Any HTTP server - // belonging to an exchange MUST generate a 307 or 308 redirection - // to the correct base URL should a client uses the wrong base - // URL, or if the base URL has changed since the deposit. - transaction_base_url?: string; - - // Timestamp when the deposit was received by the exchange. - exchange_timestamp: TalerProtocolTimestamp; - - // `Public EdDSA key of the exchange <sign-key-pub>` that was used to - // generate the signature. - // Should match one of the exchange's signing keys from ``/keys``. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; - - // Array of deposit confirmation signatures from the exchange - // Entries must be in the same order the coins were given - // in the batch deposit request. - exchange_sig: EddsaSignatureString; -} - -export const codecForBatchDepositSuccess = (): Codec<BatchDepositSuccess> => - buildCodecForObject<BatchDepositSuccess>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("exchange_timestamp", codecForTimestamp) - .property("transaction_base_url", codecOptional(codecForString())) - .build("BatchDepositSuccess"); - -export interface TrackTransactionWired { - // Raw wire transfer identifier of the deposit. - wtid: Base32String; - - // When was the wire transfer given to the bank. - execution_time: TalerProtocolTimestamp; - - // The contribution of this coin to the total (without fees) - coin_contribution: AmountString; - - // Binary-only Signature_ with purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE - // over a TALER_ConfirmWirePS - // whereby the exchange affirms the successful wire transfer. - exchange_sig: EddsaSignatureString; - - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. Again given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; -} - -export const codecForTackTransactionWired = (): Codec<TrackTransactionWired> => - buildCodecForObject<TrackTransactionWired>() - .property("wtid", codecForString()) - .property("execution_time", codecForTimestamp) - .property("coin_contribution", codecForAmountString()) - .property("exchange_sig", codecForString()) - .property("exchange_pub", codecForString()) - .build("TackTransactionWired"); - -interface TrackTransactionAccepted { - // Legitimization target that the merchant should - // use to check for its KYC status using - // the /kyc-check/$REQUIREMENT_ROW/... endpoint. - // Optional, not present if the deposit has not - // yet been aggregated to the point that a KYC - // need has been evaluated. - requirement_row?: number; - - // True if the KYC check for the merchant has been - // satisfied. False does not mean that KYC - // is strictly needed, unless also a - // legitimization_uuid is provided. - kyc_ok: boolean; - - // Time by which the exchange currently thinks the deposit will be executed. - // Actual execution may be later if the KYC check is not satisfied by then. - execution_time: TalerProtocolTimestamp; -} - -export const codecForTackTransactionAccepted = - (): Codec<TrackTransactionAccepted> => - buildCodecForObject<TrackTransactionAccepted>() - .property("requirement_row", codecOptional(codecForNumber())) - .property("kyc_ok", codecForBoolean()) - .property("execution_time", codecForTimestamp) - .build("TackTransactionAccepted"); - -export type TrackTransaction = - | ({ type: "accepted" } & TrackTransactionAccepted) - | ({ type: "wired" } & TrackTransactionWired); - export interface PurseDeposit { /** * Amount to be deposited, can be a fraction of the @@ -2130,7 +1229,111 @@ export interface ExchangeDepositRequest { h_age_commitment?: string; } -export type WireSalt = string; +export type TrackTransaction = + | ({ type: "accepted" } & TrackTransactionAccepted) + | ({ type: "wired" } & TrackTransactionWired); + +export interface BatchDepositSuccess { + // Optional base URL of the exchange for looking up wire transfers + // associated with this transaction. If not given, + // the base URL is the same as the one used for this request. + // Can be used if the base URL for ``/transactions/`` differs from that + // for ``/coins/``, i.e. for load balancing. Clients SHOULD + // respect the ``transaction_base_url`` if provided. Any HTTP server + // belonging to an exchange MUST generate a 307 or 308 redirection + // to the correct base URL should a client uses the wrong base + // URL, or if the base URL has changed since the deposit. + transaction_base_url?: string; + + // Timestamp when the deposit was received by the exchange. + exchange_timestamp: TalerProtocolTimestamp; + + // `Public EdDSA key of the exchange <sign-key-pub>` that was used to + // generate the signature. + // Should match one of the exchange's signing keys from ``/keys``. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; + + // Array of deposit confirmation signatures from the exchange + // Entries must be in the same order the coins were given + // in the batch deposit request. + exchange_sig: EddsaSignatureString; +} + +export const codecForBatchDepositSuccess = (): Codec<BatchDepositSuccess> => + buildCodecForObject<BatchDepositSuccess>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("exchange_timestamp", codecForTimestamp) + .property("transaction_base_url", codecOptional(codecForString())) + .build("BatchDepositSuccess"); + +export interface TrackTransactionWired { + // Raw wire transfer identifier of the deposit. + wtid: Base32String; + + // When was the wire transfer given to the bank. + execution_time: TalerProtocolTimestamp; + + // The contribution of this coin to the total (without fees) + coin_contribution: AmountString; + + // Binary-only Signature_ with purpose TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE + // over a TALER_ConfirmWirePS + // whereby the exchange affirms the successful wire transfer. + exchange_sig: EddsaSignatureString; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. Again given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; +} + +export const codecForTackTransactionWired = (): Codec<TrackTransactionWired> => + buildCodecForObject<TrackTransactionWired>() + .property("wtid", codecForString()) + .property("execution_time", codecForTimestamp) + .property("coin_contribution", codecForAmountString()) + .property("exchange_sig", codecForString()) + .property("exchange_pub", codecForString()) + .build("TackTransactionWired"); + +export interface TrackTransactionAccepted { + // Legitimization target that the merchant should + // use to check for its KYC status using + // the /kyc-check/$REQUIREMENT_ROW/... endpoint. + // Optional, not present if the deposit has not + // yet been aggregated to the point that a KYC + // need has been evaluated. + requirement_row?: number; + + // True if the KYC check for the merchant has been + // satisfied. False does not mean that KYC + // is strictly needed, unless also a + // legitimization_uuid is provided. + kyc_ok: boolean; + + // Time by which the exchange currently thinks the deposit will be executed. + // Actual execution may be later if the KYC check is not satisfied by then. + execution_time: TalerProtocolTimestamp; +} + +export const codecForTackTransactionAccepted = + (): Codec<TrackTransactionAccepted> => + buildCodecForObject<TrackTransactionAccepted>() + .property("requirement_row", codecOptional(codecForNumber())) + .property("kyc_ok", codecForBoolean()) + .property("execution_time", codecForTimestamp) + .build("TackTransactionAccepted"); + +export const codecForPeerContractTerms = (): Codec<PeerContractTerms> => + buildCodecForObject<PeerContractTerms>() + .property("summary", codecForString()) + .property("amount", codecForAmountString()) + .property("purse_expiration", codecForTimestamp) + .build("PeerContractTerms"); export interface ExchangeBatchDepositRequest { // The merchant's account details. @@ -2184,7 +1387,7 @@ export interface BatchDepositRequestCoin { // Amount to be deposited, can be a fraction of the // coin's total value. - contribution: Amounts; + contribution: AmountString; // Signature over `TALER_DepositRequestPS`, made by the customer with the // `coin's private key <coin-priv>`. @@ -2193,106 +1396,148 @@ export interface BatchDepositRequestCoin { h_age_commitment?: string; } -export interface WalletKycUuid { - // UUID that the wallet should use when initiating - // the KYC check. - requirement_row: number; +export interface AvailableMeasureSummary { + // Available original measures that can be + // triggered directly by default rules. + roots: { [measure_name: string]: MeasureInformation }; - // Hash of the payto:// account URI for the wallet. - h_payto: string; + // Available AML programs. + programs: { [prog_name: string]: AmlProgramRequirement }; + + // Available KYC checks. + checks: { [check_name: string]: KycCheckInformation }; } -export const codecForWalletKycUuid = (): Codec<WalletKycUuid> => - buildCodecForObject<WalletKycUuid>() - .property("requirement_row", codecForNumber()) - .property("h_payto", codecForString()) - .build("WalletKycUuid"); +export interface MeasureInformation { + // Name of a KYC check. + check_name: string; + + // Name of an AML program. + prog_name: string; -export interface MerchantUsingTemplateDetails { - summary?: string; - amount?: AmountString; + // Context for the check. Optional. + context?: Object; } -export interface ExchangeRefundRequest { - // Amount to be refunded, can be a fraction of the - // coin's total deposit value (including deposit fee); - // must be larger than the refund fee. - refund_amount: AmountString; +export interface AmlProgramRequirement { + // Description of what the AML program does. + description: string; - // SHA-512 hash of the contact of the merchant with the customer. - h_contract_terms: HashCodeString; + // List of required field names in the context to run this + // AML program. SPA must check that the AML staff is providing + // adequate CONTEXT when defining a measure using this program. + context: string[]; - // 64-bit transaction id of the refund transaction between merchant and customer. - rtransaction_id: number; + // List of required attribute names in the + // input of this AML program. These attributes + // are the minimum that the check must produce + // (it may produce more). + inputs: string[]; +} - // EdDSA public key of the merchant. - merchant_pub: EddsaPublicKeyString; +export interface KycCheckInformation { + // Description of the KYC check. Should be shown + // to the AML staff but will also be shown to the + // client when they initiate the check in the KYC SPA. + description: string; - // EdDSA signature of the merchant over a - // TALER_RefundRequestPS with purpose - // TALER_SIGNATURE_MERCHANT_REFUND - // affirming the refund. - merchant_sig: EddsaPublicKeyString; + // Map from IETF BCP 47 language tags to localized + // description texts. + description_i18n?: { [lang_tag: string]: string }; + + // Names of the fields that the CONTEXT must provide + // as inputs to this check. + // SPA must check that the AML staff is providing + // adequate CONTEXT when defining a measure using + // this check. + requires: string[]; + + // Names of the attributes the check will output. + // SPA must check that the outputs match the + // required inputs when combining a KYC check + // with an AML program into a measure. + outputs: string[]; + + // Name of a root measure taken when this check fails. + fallback: string; } -export interface ExchangeRefundSuccessResponse { - // The EdDSA :ref:signature (binary-only) with purpose - // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND over - // a TALER_RecoupRefreshConfirmationPS - // using a current signing key of the - // exchange affirming the successful refund. - exchange_sig: EddsaSignatureString; +export interface AmlDecisionDetails { + // Array of AML decisions made for this account. Possibly + // contains only the most recent decision if "history" was + // not set to 'true'. + aml_history: AmlDecisionDetail[]; - // Public EdDSA key of the exchange that was used to generate the signature. - // Should match one of the exchange's signing keys from /keys. It is given - // explicitly as the client might otherwise be confused by clock skew as to - // which signing key was used. - exchange_pub: EddsaPublicKeyString; + // Array of KYC attributes obtained for this account. + kyc_attributes: KycDetail[]; } -export const codecForExchangeRefundSuccessResponse = - (): Codec<ExchangeRefundSuccessResponse> => - buildCodecForObject<ExchangeRefundSuccessResponse>() - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .build("ExchangeRefundSuccessResponse"); +export interface AmlDecisionDetail { + // What was the justification given? + justification: string; -export type AccountRestriction = - | RegexAccountRestriction - | DenyAllAccountRestriction; + // What is the new AML state. + new_state: Integer; -export interface DenyAllAccountRestriction { - type: "deny"; + // When was this decision made? + decision_time: Timestamp; + + // What is the new AML decision threshold (in monthly transaction volume)? + new_threshold: AmountString; + + // Who made the decision? + decider_pub: AmlOfficerPublicKeyP; } -// Accounts interacting with this type of account -// restriction must have a payto://-URI matching -// the given regex. -export interface RegexAccountRestriction { - type: "regex"; +export interface KycDetail { + // Name of the configuration section that specifies the provider + // which was used to collect the KYC details + provider_section: string; - // Regular expression that the payto://-URI of the - // partner account must follow. The regular expression - // should follow posix-egrep, but without support for character - // classes, GNU extensions, back-references or intervals. See - // https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html - // for a description of the posix-egrep syntax. Applications - // may support regexes with additional features, but exchanges - // must not use such regexes. - payto_regex: string; + // The collected KYC data. NULL if the attribute data could not + // be decrypted (internal error of the exchange, likely the + // attribute key was changed). + attributes?: Object; - // Hint for a human to understand the restriction - // (that is hopefully easier to comprehend than the regex itself). - human_hint: string; + // Time when the KYC data was collected + collection_time: Timestamp; - // Map from IETF BCP 47 language tags to localized - // human hints. - human_hint_i18n?: InternationalizedString; + // Time when the validity of the KYC data will expire + expiration_time: Timestamp; } -export interface ExchangeWireAccount { +export type AmlDecisionRequestWithoutSignature = Omit< + AmlDecisionRequest, + "officer_sig" +>; + +export interface ExchangeVersionResponse { + // libtool-style representation of the Exchange protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the protocol. + name: "taler-exchange"; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since v18, may become mandatory in the future. + implementation?: string; + + // Currency supported by this exchange, given + // as a currency code ("USD" or "EUR"). + currency: string; + + // How wallets should render this currency. + currency_specification: CurrencySpecification; + + // Names of supported KYC requirements. + supported_kyc_requirements: string[]; +} + +export interface WireAccount { // payto:// URI identifying the account and wire method - payto_uri: string; + payto_uri: PaytoString; // URI to convert amounts from or to the currency used by // this wire account of the exchange. Missing if no @@ -2313,112 +1558,938 @@ export interface ExchangeWireAccount { // a TALER_MasterWireDetailsPS // with purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS. master_sig: EddsaSignatureString; +} - // Display label wallets should use to show this - // bank account. - // Since protocol **v19**. - bank_label?: string; - priority?: number; +export interface WalletKycRequest { + // Balance threshold (not necessarily exact balance) + // to be crossed by the wallet that (may) trigger + // additional KYC requirements. + balance: AmountString; + + // EdDSA signature of the wallet affirming the + // request, must be of purpose + // TALER_SIGNATURE_WALLET_ACCOUNT_SETUP + reserve_sig: EddsaSignatureString; + + // long-term wallet reserve-account + // public key used to create the signature. + reserve_pub: EddsaPublicKeyString; } -export const codecForExchangeWireAccount = (): Codec<ExchangeWireAccount> => - buildCodecForObject<ExchangeWireAccount>() - .property("conversion_url", codecOptional(codecForStringURL())) - .property("credit_restrictions", codecForList(codecForAny())) - .property("debit_restrictions", codecForList(codecForAny())) - .property("master_sig", codecForString()) - .property("payto_uri", codecForString()) - .property("bank_label", codecOptional(codecForString())) - .property("priority", codecOptional(codecForNumber())) - .build("WireAccount"); +export interface WalletKycCheckResponse { + // Next balance limit above which a KYC check + // may be required. Optional, not given if no + // threshold exists (assume infinity). + next_threshold?: AmountString; + + // When does the current set of AML/KYC rules + // expire and the wallet needs to check again + // for updated thresholds. + expiration_time: Timestamp; +} + +// Implemented in this style since exchange +// protocol **v20**. +export interface LegitimizationNeededResponse { + // Numeric error code unique to the condition. + // Should always be TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED. + code: number; + + // Human-readable description of the error, i.e. "missing parameter", + // "commitment violation", ... Should give a human-readable hint + // about the error's nature. Optional, may change without notice! + hint?: string; + + // Hash of the payto:// account URI for which KYC + // is required. + h_payto: PaytoHash; + + // Public key associated with the account. The client must sign + // the initial request for the KYC status using the corresponding + // private key. Will be either a reserve public key or a merchant + // (instance) public key. + // + // Absent if no public key is currently associated + // with the account and the client MUST thus first + // credit the exchange via an inbound wire transfer + // to associate a public key with the debited account. + account_pub?: EddsaPublicKeyString; + + // Identifies a set of measures that were triggered and that are + // now preventing this operation from proceeding. Gives the + // account holder a starting point for understanding why the + // transaction was blocked and how to lift it. The account holder + // should use the number to check for the account's AML/KYC status + // using the /kyc-check/$REQUIREMENT_ROW endpoint. + requirement_row: Integer; +} + +export interface AccountKycStatus { + // Current AML state for the target account. True if + // operations are not happening due to staff processing + // paperwork *or* due to legal requirements (so the + // client cannot do anything but wait). + // + // Note that not every AML staff action may be legally + // exposed to the client, so this is merely a hint that + // a client should be told that AML staff is currently + // reviewing the account. AML staff *may* review + // accounts without this flag being set! + aml_review: boolean; + + // Access token needed to construct the /kyc-spa/ + // URL that the user should open in a browser to + // proceed with the KYC process (optional if the status + // type is 200 Ok, mandatory if the HTTP status + // is 202 Accepted). + access_token: AccessToken; + + // Array with limitations that currently apply to this + // account and that may be increased or lifted if the + // KYC check is passed. + // Note that additional limits *may* exist and not be + // communicated to the client. If such limits are + // reached, this *may* be indicated by the account + // going into aml_review state. However, it is + // also possible that the exchange may legally have + // to deny operations without being allowed to provide + // any justification. + // The limits should be used by the client to + // possibly structure their operations (e.g. withdraw + // what is possible below the limit, ask the user to + // pass KYC checks or withdraw the rest after the time + // limit is passed, warn the user to not withdraw too + // much or even prevent the user from generating a + // request that would cause it to exceed hard limits). + limits?: AccountLimit[]; +} +export interface AccountLimit { + // Operation that is limited. + // Must be one of "WITHDRAW", "DEPOSIT", "P2P-RECEIVE" + // or "WALLET-BALANCE". + operation_type: "WITHDRAW" | "DEPOSIT" | "P2P-RECEIVE" | "WALLET-BALANCE"; + + // Timeframe during which the limit applies. + timeframe: RelativeTime; + + // Maximum amount allowed during the given timeframe. + // Zero if the operation is simply forbidden. + threshold: AmountString; + + // True if this is a soft limit that could be raised + // by passing KYC checks. Clients *may* deliberately + // try to cross limits and trigger measures resulting + // in 451 responses to begin KYC processes. + // Clients that are aware of hard limits *should* + // inform users about the hard limit and prevent flows + // in the UI that would cause violations of hard limits. + soft_limit: boolean; +} + +export interface KycProcessClientInformation { + // Array of requirements. + requirements: KycRequirementInformation[]; + + // True if the client is expected to eventually satisfy all requirements. + // Default (if missing) is false. + is_and_combinator?: boolean; + + // List of available voluntary checks the client could pay for. + // Since **vATTEST**. + voluntary_checks?: { [name: string]: KycCheckPublicInformation }; +} + +declare const opaque_kycReq: unique symbol; +export type KycRequirementInformationId = string & { [opaque_kycReq]: true }; +declare const opaque_formId: unique symbol; +export type KycBuiltInFromId = string & { [opaque_formId]: true }; + +export interface KycRequirementInformation { + // Which form should be used? Common values include "INFO" + // (to just show the descriptions but allow no action), + // "LINK" (to enable the user to obtain a link via + // /kyc-start/) or any build-in form name supported + // by the SPA. + form: "LINK" | "INFO" | KycBuiltInFromId; + + // English description of the requirement. + description: string; + + // Map from IETF BCP 47 language tags to localized + // description texts. + description_i18n?: { [lang_tag: string]: string }; + + // ID of the requirement, useful to construct the + // /kyc-upload/$ID or /kyc-start/$ID endpoint URLs. + // Present if and only if "form" is not "INFO". The + // $ID value may itself contain / or ? and + // basically encode any URL path (and optional arguments). + id?: KycRequirementInformationId; +} + +// Since **vATTEST**. +export interface KycCheckPublicInformation { + // English description of the check. + description: string; + + // Map from IETF BCP 47 language tags to localized + // description texts. + description_i18n?: { [lang_tag: string]: string }; + + // FIXME: is the above in any way sufficient + // to begin the check? Do we not need at least + // something more??!? +} + +export interface EventCounter { + // Number of events of the specified type in + // the given range. + counter: Integer; +} + +export interface AmlDecisionsResponse { + // Array of AML decisions matching the query. + records: AmlDecision[]; +} + +export interface AmlDecision { + // Which payto-address is this record about. + // Identifies a GNU Taler wallet or an affected bank account. + h_payto: PaytoHash; + + // Row ID of the record. Used to filter by offset. + rowid: Integer; + + // Justification for the decision. NULL if none + // is available. + justification?: string; + + // When was the decision made? + decision_time: Timestamp; + + // Free-form properties about the account. + // Can be used to store properties such as PEP, + // risk category, type of business, hits on + // sanctions lists, etc. + properties?: AccountProperties; + + // What are the new rules? + limits: LegitimizationRuleSet; + + // True if the account is under investigation by AML staff + // after this decision. + to_investigate: boolean; + + // True if this is the active decision for the + // account. + is_active: boolean; +} + +// All fields in this object are optional. The actual +// properties collected depend fully on the discretion +// of the exchange operator; +// however, some common fields are standardized +// and thus described here. +export interface AccountProperties { + // True if this is a politically exposed account. + // Rules for classifying accounts as politically + // exposed are country-dependent. + pep?: boolean; + + // True if this is a sanctioned account. + // Rules for classifying accounts as sanctioned + // are country-dependent. + sanctioned?: boolean; + + // True if this is a high-risk account. + // Rules for classifying accounts as at-risk + // are exchange operator-dependent. + high_risk?: boolean; + + // Business domain of the account owner. + // The list of possible business domains is + // operator- or country-dependent. + business_domain?: string; + + // Is the client's account currently frozen? + is_frozen?: boolean; + + // Was the client's account reported to the authorities? + was_reported?: boolean; + + /** + * Additional free-form properties. + */ + [x: string]: any; +} + +export interface LegitimizationRuleSet { + // When does this set of rules expire and + // we automatically transition to the successor + // measure? + expiration_time: Timestamp; + + // Name of the measure to apply when the expiration time is + // reached. If not set, we refer to the default + // set of rules (and the default account state). + successor_measure?: string; + + // Legitimization rules that are to be applied + // to this account. + rules: KycRule[]; -export type Integer = number; + // Custom measures that KYC rules and the + // successor_measure may refer to. + custom_measures: { [measure_name: string]: MeasureInformation }; +} + +export interface AmlDecisionRequest { + // Human-readable justification for the decision. + justification: string; -export interface BankConversionInfoConfig { - // libtool-style representation of the Bank protocol version, see + // Which payto-address is the decision about? + // Identifies a GNU Taler wallet or an affected bank account. + h_payto: PaytoHash; + + // What are the new rules? + // New since protocol **v20**. + new_rules: LegitimizationRuleSet; + + // What are the new account properties? + // New since protocol **v20**. + properties: AccountProperties; + + // New measure to apply immediately to the account. + // Should typically be used to give the user some + // information or request additional information. + // Use "verboten" to communicate to the customer + // that there is no KYC check that could be passed + // to modify the new_rules. + // New since protocol **v20**. + new_measure?: string; + + // True if the account should remain under investigation by AML staff. + // New since protocol **v20**. + keep_investigating: boolean; + + // Signature by the AML officer over a TALER_AmlDecisionPS. + // Must have purpose TALER_SIGNATURE_MASTER_AML_KEY. + officer_sig: EddsaSignatureString; + + // When was the decision made? + decision_time: Timestamp; +} + +export interface KycRule { + // Type of operation to which the rule applies. + operation_type: string; + + // The measures will be taken if the given + // threshold is crossed over the given timeframe. + threshold: AmountString; + + // Over which duration should the threshold be + // computed. All amounts of the respective + // operation_type will be added up for this + // duration and the sum compared to the threshold. + timeframe: RelativeTime; + + // Array of names of measures to apply. + // Names listed can be original measures or + // custom measures from the AmlOutcome. + // A special measure "verboten" is used if the + // threshold may never be crossed. + measures: string[]; + + // If multiple rules apply to the same account + // at the same time, the number with the highest + // rule determines which set of measures will + // be activated and thus become visible for the + // user. + display_priority: Integer; + + // True if the rule (specifically, operation_type, + // threshold, timeframe) and the general nature of + // the measures (verboten or approval required) + // should be exposed to the client. + // Defaults to "false" if not set. + exposed?: boolean; + + // True if all the measures will eventually need to + // be satisfied, false if any of the measures should + // do. Primarily used by the SPA to indicate how + // the measures apply when showing them to the user; + // in the end, AML programs will decide after each + // measure what to do next. + // Default (if missing) is false. + is_and_combinator?: boolean; +} + +export interface KycAttributes { + // Matching KYC attribute history of the account. + details: KycAttributeCollectionEvent[]; +} +export interface KycAttributeCollectionEvent { + // Row ID of the record. Used to filter by offset. + rowid: Integer; + + // Name of the provider + // which was used to collect the attributes. NULL if they were + // just uploaded via a form by the account owner. + provider_name?: string; + + // The collected KYC data. NULL if the attribute data could not + // be decrypted (internal error of the exchange, likely the + // attribute key was changed). + attributes?: Object; + + // Time when the KYC data was collected + collection_time: Timestamp; +} + +export enum AmlState { + normal = 0, + pending = 1, + frozen = 2, +} +export interface ExchangeKeysResponse { + // libtool-style representation of the Exchange protocol version, see // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning // The format is "current:revision:age". version: string; - // Name of the API. - name: "taler-conversion-info"; + // The exchange's base URL. + base_url: string; + + // The exchange's currency or asset unit. + currency: string; + + /** + * FIXME: PARTIALLY IMPLEMENTED!! + */ + + // How wallets should render this currency. + // currency_specification: CurrencySpecification; + + // // Absolute cost offset for the STEFAN curve used + // // to (over) approximate fees payable by amount. + // stefan_abs: AmountString; + + // // Factor to multiply the logarithm of the amount + // // with to (over) approximate fees payable by amount. + // // Note that the total to be paid is first to be + // // divided by the smallest denomination to obtain + // // the value that the logarithm is to be taken of. + // stefan_log: AmountString; + + // // Linear cost factor for the STEFAN curve used + // // to (over) approximate fees payable by amount. + // // + // // Note that this is a scalar, as it is multiplied + // // with the actual amount. + // stefan_lin: Float; + + // // Type of the asset. "fiat", "crypto", "regional" + // // or "stock". Wallets should adjust their UI/UX + // // based on this value. + // asset_type: string; + + // // Array of wire accounts operated by the exchange for + // // incoming wire transfers. + // accounts: WireAccount[]; + + // // Object mapping names of wire methods (i.e. "iban" or "x-taler-bank") + // // to wire fees. + // wire_fees: { method: AggregateTransferFee[] }; + + // // List of exchanges that this exchange is partnering + // // with to enable wallet-to-wallet transfers. + // wads: ExchangePartner[]; + + // // Set to true if this exchange allows the use + // // of reserves for rewards. + // // @deprecated in protocol v18. + // rewards_allowed: false; + + // // EdDSA master public key of the exchange, used to sign entries + // // in denoms and signkeys. + // master_public_key: EddsaPublicKey; + + // // Relative duration until inactive reserves are closed; + // // not signed (!), can change without notice. + // reserve_closing_delay: RelativeTime; + + // // Threshold amounts beyond which wallet should + // // trigger the KYC process of the issuing + // // exchange. Optional option, if not given there is no limit. + // // Currency must match currency. + // wallet_balance_limit_without_kyc?: AmountString[]; + + // // Denominations offered by this exchange + // denominations: DenomGroup[]; - regional_currency: string; + // // Compact EdDSA signature (binary-only) over the + // // contatentation of all of the master_sigs (in reverse + // // chronological order by group) in the arrays under + // // "denominations". Signature of TALER_ExchangeKeySetPS + // exchange_sig: EddsaSignature; - fiat_currency: string; + // // Public EdDSA key of the exchange that was used to generate the signature. + // // Should match one of the exchange's signing keys from signkeys. It is given + // // explicitly as the client might otherwise be confused by clock skew as to + // // which signing key was used for the exchange_sig. + // exchange_pub: EddsaPublicKey; - // Currency used by this bank. - regional_currency_specification: CurrencySpecification; + // // Denominations for which the exchange currently offers/requests recoup. + // recoup: Recoup[]; - // External currency used during conversion. - fiat_currency_specification: CurrencySpecification; + // // Array of globally applicable fees by time range. + // global_fees: GlobalFees[]; + + // // The date when the denomination keys were last updated. + // list_issue_date: Timestamp; + + // // Auditors of the exchange. + // auditors: AuditorKeys[]; + + // // The exchange's signing keys. + // signkeys: SignKey[]; + + // // Optional field with a dictionary of (name, object) pairs defining the + // // supported and enabled extensions, such as age_restriction. + // extensions?: { name: ExtensionManifest }; + + // // Signature by the exchange master key of the SHA-256 hash of the + // // normalized JSON-object of field extensions, if it was set. + // // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS. + // extensions_sig?: EddsaSignature; } -export const codecForBankConversionInfoConfig = - (): Codec<BankConversionInfoConfig> => - buildCodecForObject<BankConversionInfoConfig>() - .property("name", codecForConstString("taler-conversion-info")) - .property("version", codecForString()) - .property("fiat_currency", codecForString()) - .property("regional_currency", codecForString()) - .property("fiat_currency_specification", codecForCurrencySpecificiation()) - .property( - "regional_currency_specification", - codecForCurrencySpecificiation(), - ) - .build("BankConversionInfoConfig"); +interface ExtensionManifest { + // The criticality of the extension MUST be provided. It has the same + // semantics as "critical" has for extensions in X.509: + // - if "true", the client must "understand" the extension before + // proceeding, + // - if "false", clients can safely skip extensions they do not + // understand. + // (see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2) + critical: boolean; + + // The version information MUST be provided in Taler's protocol version + // ranges notation, see + // https://docs.taler.net/core/api-common.html#protocol-version-ranges + version: LibtoolVersionString; + + // Optional configuration object, defined by the feature itself + config?: object; +} + +interface SignKey { + // The actual exchange's EdDSA signing public key. + key: EddsaPublicKeyString; -export interface DenominationExpiredMessage { - // Taler error code. Note that beyond - // expiration this message format is also - // used if the key is not yet valid, or - // has been revoked. - code: number; + // Initial validity date for the signing key. + stamp_start: Timestamp; - // Signature by the exchange over a - // TALER_DenominationExpiredAffirmationPS. - // Must have purpose TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED. - exchange_sig: EddsaSignatureString; + // Date when the exchange will stop using the signing key, allowed to overlap + // slightly with the next signing key's validity to allow for clock skew. + stamp_expire: Timestamp; - // Public key of the exchange used to create - // the 'exchange_sig. - exchange_pub: EddsaPublicKeyString; + // Date when all signatures made by the signing key expire and should + // henceforth no longer be considered valid in legal disputes. + stamp_end: Timestamp; - // Hash of the denomination public key that is unknown. - h_denom_pub: HashCodeString; + // Signature over key and stamp_expire by the exchange master key. + // Signature of TALER_ExchangeSigningKeyValidityPS. + // Must have purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY. + master_sig: EddsaSignatureString; +} - // When was the signature created. - timestamp: TalerProtocolTimestamp; +interface AuditorKeys { + // The auditor's EdDSA signing public key. + auditor_pub: EddsaPublicKeyString; + + // The auditor's URL. + auditor_url: string; - // What kind of operation was requested that now - // failed? - oper: string; + // The auditor's name (for humans). + auditor_name: string; + + // An array of denomination keys the auditor affirms with its signature. + // Note that the message only includes the hash of the public key, while the + // signature is actually over the expanded information including expiration + // times and fees. The exact format is described below. + denomination_keys: AuditorDenominationKey[]; } -export const codecForDenominationExpiredMessage = () => - buildCodecForObject<DenominationExpiredMessage>() - .property("code", codecForNumber()) - .property("exchange_sig", codecForString()) - .property("exchange_pub", codecForString()) - .property("h_denom_pub", codecForString()) - .property("timestamp", codecForTimestamp) - .property("oper", codecForString()) - .build("DenominationExpiredMessage"); +interface AuditorDenominationKey { + // Hash of the public RSA key used to sign coins of the respective + // denomination. Note that the auditor's signature covers more than just + // the hash, but this other information is already provided in denoms and + // thus not repeated here. + denom_pub_h: HashCodeString; -export interface CoinHistoryResponse { - // Current balance of the coin. - balance: AmountString; + // Signature of TALER_ExchangeKeyValidityPS. + auditor_sig: EddsaSignatureString; +} + +export interface GlobalFees { + // What date (inclusive) does these fees go into effect? + start_date: Timestamp; + + // What date (exclusive) does this fees stop going into effect? + end_date: Timestamp; + + // Account history fee, charged when a user wants to + // obtain a reserve/account history. + history_fee: AmountString; + + // Annual fee charged for having an open account at the + // exchange. Charged to the account. If the account + // balance is insufficient to cover this fee, the account + // is automatically deleted/closed. (Note that the exchange + // will keep the account history around for longer for + // regulatory reasons.) + account_fee: AmountString; + + // Purse fee, charged only if a purse is abandoned + // and was not covered by the account limit. + purse_fee: AmountString; - // Hash of the coin's denomination. - h_denom_pub: HashCodeString; + // How long will the exchange preserve the account history? + // After an account was deleted/closed, the exchange will + // retain the account history for legal reasons until this time. + history_expiration: RelativeTime; + + // Non-negative number of concurrent purses that any + // account holder is allowed to create without having + // to pay the purse_fee. + purse_account_limit: Integer; + + // How long does an exchange keep a purse around after a purse + // has expired (or been successfully merged)? A 'GET' request + // for a purse will succeed until the purse expiration time + // plus this value. + purse_timeout: RelativeTime; - // Transaction history for the coin. - history: any[]; + // Signature of TALER_GlobalFeesPS. + master_sig: EddsaSignatureString; } -export const codecForCoinHistoryResponse = () => - buildCodecForObject<CoinHistoryResponse>() - .property("balance", codecForAmountString()) - .property("h_denom_pub", codecForString()) - .property("history", codecForAny()) - .build("CoinHistoryResponse"); +export interface AggregateTransferFee { + // Per transfer wire transfer fee. + wire_fee: AmountString; + + // Per transfer closing fee. + closing_fee: AmountString; + + // What date (inclusive) does this fee go into effect? + // The different fees must cover the full time period in which + // any of the denomination keys are valid without overlap. + start_date: Timestamp; + + // What date (exclusive) does this fee stop going into effect? + // The different fees must cover the full time period in which + // any of the denomination keys are valid without overlap. + end_date: Timestamp; + + // Signature of TALER_MasterWireFeePS with + // purpose TALER_SIGNATURE_MASTER_WIRE_FEES. + sig: EddsaSignatureString; +} + +interface ExchangePartner { + // Base URL of the partner exchange. + partner_base_url: string; + + // Public master key of the partner exchange. + partner_master_pub: EddsaPublicKeyString; + + // Per exchange-to-exchange transfer (wad) fee. + wad_fee: AmountString; + + // Exchange-to-exchange wad (wire) transfer frequency. + wad_frequency: RelativeTime; + + // When did this partnership begin (under these conditions)? + start_date: Timestamp; + + // How long is this partnership expected to last? + end_date: Timestamp; + + // Signature using the exchange's offline key over + // TALER_WadPartnerSignaturePS + // with purpose TALER_SIGNATURE_MASTER_PARTNER_DETAILS. + master_sig: EddsaSignatureString; +} + +// Binary representation of the age groups. +// The bits set in the mask mark the edges at the beginning of a next age +// group. F.e. for the age groups +// 0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-21, 21-* +// the following bits are set: +// +// 31 24 16 8 0 +// | | | | | +// oooooooo oo1oo1o1 o1o1o1o1 ooooooo1 +// +// A value of 0 means that the exchange does not support the extension for +// age-restriction. +type AgeMask = Integer; + +type DenominationKey = RsaDenominationKey | CSDenominationKey; + +interface RsaDenominationKey { + cipher: "RSA"; + + // 32-bit age mask. + age_mask: Integer; + + // RSA public key + rsa_public_key: RsaPublicKey; +} + +interface CSDenominationKey { + cipher: "CS"; + + // 32-bit age mask. + age_mask: Integer; + + // Public key of the denomination. + cs_public_key: Cs25519Point; +} + +export const codecForExchangeConfig = (): Codec<ExchangeVersionResponse> => + buildCodecForObject<ExchangeVersionResponse>() + .property("version", codecForString()) + .property("name", codecForConstString("taler-exchange")) + .property("implementation", codecOptional(codecForURN())) + .property("currency", codecForString()) + .property("currency_specification", codecForCurrencySpecificiation()) + .property("supported_kyc_requirements", codecForList(codecForString())) + .build("TalerExchangeApi.ExchangeVersionResponse"); +export const codecForExchangeKeys = (): Codec<ExchangeKeysResponse> => + buildCodecForObject<ExchangeKeysResponse>() + .property("version", codecForString()) + .property("base_url", codecForString()) + .property("currency", codecForString()) + .build("TalerExchangeApi.ExchangeKeysResponse"); + +export const codecForEventCounter = (): Codec<EventCounter> => + buildCodecForObject<EventCounter>() + .property("counter", codecForNumber()) + .build("TalerExchangeApi.EventCounter"); + +export const codecForAmlDecisionsResponse = (): Codec<AmlDecisionsResponse> => + buildCodecForObject<AmlDecisionsResponse>() + .property("records", codecForList(codecForAmlDecision())) + .build("TalerExchangeApi.AmlDecisionsResponse"); + +export const codecForAvailableMeasureSummary = + (): Codec<AvailableMeasureSummary> => + buildCodecForObject<AvailableMeasureSummary>() + .property("checks", codecForMap(codecForKycCheckInformation())) + .property("programs", codecForMap(codecForAmlProgramRequirement())) + .property("roots", codecForMap(codecForMeasureInformation())) + .build("TalerExchangeApi.AvailableMeasureSummary"); + +export const codecForAmlProgramRequirement = (): Codec<AmlProgramRequirement> => + buildCodecForObject<AmlProgramRequirement>() + .property("description", codecForString()) + .property("context", codecForList(codecForString())) + .property("inputs", codecForList(codecForString())) + .build("TalerExchangeApi.AmlProgramRequirement"); + +export const codecForKycCheckInformation = (): Codec<KycCheckInformation> => + buildCodecForObject<KycCheckInformation>() + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) + .property("fallback", codecForString()) + .property("outputs", codecForList(codecForString())) + .property("requires", codecForList(codecForString())) + .build("TalerExchangeApi.KycCheckInformation"); + +export const codecForMeasureInformation = (): Codec<MeasureInformation> => + buildCodecForObject<MeasureInformation>() + .property("prog_name", codecForString()) + .property("check_name", codecForString()) + .property("context", codecForAny()) + .build("TalerExchangeApi.MeasureInformation"); + +// export const codecForAmlDecisionDetails = (): Codec<AmlDecisionDetails> => +// buildCodecForObject<AmlDecisionDetails>() +// .property("aml_history", codecForList(codecForAmlDecisionDetail())) +// .property("kyc_attributes", codecForList(codecForKycDetail())) +// .build("TalerExchangeApi.AmlDecisionDetails"); + +// export const codecForAmlDecisionDetail = (): Codec<AmlDecisionDetail> => +// buildCodecForObject<AmlDecisionDetail>() +// .property("justification", codecForString()) +// .property("new_state", codecForNumber()) +// .property("decision_time", codecForTimestamp) +// .property("new_threshold", codecForAmountString()) +// .property("decider_pub", codecForString()) +// .build("TalerExchangeApi.AmlDecisionDetail"); + +export const codecForKycDetail = (): Codec<KycDetail> => + buildCodecForObject<KycDetail>() + .property("provider_section", codecForString()) + .property("attributes", codecOptional(codecForAny())) + .property("collection_time", codecForTimestamp) + .property("expiration_time", codecForTimestamp) + .build("TalerExchangeApi.KycDetail"); + +export const codecForAmlDecision = (): Codec<AmlDecision> => + buildCodecForObject<AmlDecision>() + .property("h_payto", codecForString()) + .property("rowid", codecForNumber()) + .property("justification", codecOptional(codecForString())) + .property("decision_time", codecForTimestamp) + .property("properties", codecForAccountProperties()) + .property("limits", codecForLegitimizationRuleSet()) + .property("to_investigate", codecForBoolean()) + .property("is_active", codecForBoolean()) + .build("TalerExchangeApi.AmlDecision"); + +export const codecForAccountProperties = (): Codec<AccountProperties> => + buildCodecForObject<AccountProperties>() + .property("pep", codecOptional(codecForBoolean())) + .property("sanctioned", codecOptional(codecForBoolean())) + .property("high_risk", codecOptional(codecForBoolean())) + .property("business_domain", codecOptional(codecForString())) + .property("is_frozen", codecOptional(codecForBoolean())) + .property("was_reported", codecOptional(codecForBoolean())) + .build("TalerExchangeApi.AccountProperties"); + +export const codecForLegitimizationRuleSet = (): Codec<LegitimizationRuleSet> => + buildCodecForObject<LegitimizationRuleSet>() + .property("expiration_time", codecForTimestamp) + .property("successor_measure", codecOptional(codecForString())) + .property("rules", codecForList(codecForKycRules())) + .property("custom_measures", codecForMap(codecForMeasureInformation())) + .build("TalerExchangeApi.LegitimizationRuleSet"); + +export const codecForKycRules = (): Codec<KycRule> => + buildCodecForObject<KycRule>() + .property("operation_type", codecForString()) + .property("threshold", codecForAmountString()) + .property("timeframe", codecForDuration) + .property("measures", codecForList(codecForString())) + .property("display_priority", codecForNumber()) + .property("exposed", codecOptional(codecForBoolean())) + .property("is_and_combinator", codecOptional(codecForBoolean())) + .build("TalerExchangeApi.KycRule"); + +export const codecForAmlKycAttributes = (): Codec<KycAttributes> => + buildCodecForObject<KycAttributes>() + .property("details", codecForList(codecForKycAttributeCollectionEvent())) + .build("TalerExchangeApi.KycAttributes"); + +export const codecForKycAttributeCollectionEvent = + (): Codec<KycAttributeCollectionEvent> => + buildCodecForObject<KycAttributeCollectionEvent>() + .property("rowid", codecForNumber()) + .property("provider_name", codecOptional(codecForString())) + .property("collection_time", codecForTimestamp) + .property("attributes", codecOptional(codecForAny())) + .build("TalerExchangeApi.KycAttributeCollectionEvent"); + +export const codecForAmlWalletKycCheckResponse = + (): Codec<WalletKycCheckResponse> => + buildCodecForObject<WalletKycCheckResponse>() + .property("next_threshold", codecOptional(codecForAmountString())) + .property("expiration_time", codecForTimestamp) + .build("TalerExchangeApi.WalletKycCheckResponse"); + +export const codecForLegitimizationNeededResponse = + (): Codec<LegitimizationNeededResponse> => + buildCodecForObject<LegitimizationNeededResponse>() + .property("code", codecForNumber()) + .property("hint", codecOptional(codecForString())) + .property("h_payto", codecForString()) + .property("account_pub", codecOptional(codecForString())) + .property("requirement_row", codecForNumber()) + .build("TalerExchangeApi.LegitimizationNeededResponse"); + +export const codecForAccountKycStatus = (): Codec<AccountKycStatus> => + buildCodecForObject<AccountKycStatus>() + .property("aml_review", codecForBoolean()) + .property("access_token", codecForAccessToken()) + .property("limits", codecOptional(codecForList(codecForAccountLimit()))) + .build("TalerExchangeApi.AccountKycStatus"); + +export const codecForAccountLimit = (): Codec<AccountLimit> => + buildCodecForObject<AccountLimit>() + .property( + "operation_type", + codecForEither( + codecForConstString("WITHDRAW"), + codecForConstString("DEPOSIT"), + codecForConstString("P2P-RECEIVE"), + codecForConstString("WALLET-BALANCE"), + ), + ) + .property("timeframe", codecForDuration) + .property("threshold", codecForAmountString()) + .property("soft_limit", codecForBoolean()) + .build("TalerExchangeApi.AccountLimit"); + +export const codecForKycCheckPublicInformation = + (): Codec<KycCheckPublicInformation> => + buildCodecForObject<KycCheckPublicInformation>() + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) + .build("TalerExchangeApi.KycCheckPublicInformation"); + +export const codecForKycRequirementInformationId = + (): Codec<KycRequirementInformationId> => + codecForString() as Codec<KycRequirementInformationId>; +export const codecForKycFormId = (): Codec<KycBuiltInFromId> => + codecForString() as Codec<KycBuiltInFromId>; + +export const codecForKycRequirementInformation = + (): Codec<KycRequirementInformation> => + buildCodecForObject<KycRequirementInformation>() + .property( + "form", + codecForEither( + codecForConstString("LINK"), + codecForConstString("INFO"), + codecForKycFormId(), + ), + ) + .property("description", codecForString()) + .property( + "description_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("id", codecOptional(codecForKycRequirementInformationId())) + .build("TalerExchangeApi.KycRequirementInformation"); + +export const codecForKycProcessClientInformation = + (): Codec<KycProcessClientInformation> => + buildCodecForObject<KycProcessClientInformation>() + .property( + "requirements", + codecOptionalDefault( + codecForList(codecForKycRequirementInformation()), + [], + ), + ) + .property("is_and_combinator", codecOptional(codecForBoolean())) + .property( + "voluntary_checks", + codecOptional(codecForMap(codecForKycCheckPublicInformation())), + ) + .build("TalerExchangeApi.KycProcessClientInformation"); + +export interface KycProcessStartInformation { + // URL to open. + redirect_url: string; +} + +export const codecForKycProcessStartInformation = + (): Codec<KycProcessStartInformation> => + buildCodecForObject<KycProcessStartInformation>() + .property("redirect_url", codecForURLString()) + .build("TalerExchangeApi.KycProcessStartInformation"); diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts new file mode 100644 index 000000000..58cac5e58 --- /dev/null +++ b/packages/taler-util/src/types-taler-merchant.ts @@ -0,0 +1,3451 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { codecForAmountString } from "./amounts.js"; +import { + Codec, + buildCodecForObject, + codecForAny, + codecForBoolean, + codecForList, + codecForNumber, + codecForString, + codecOptional, +} from "./codec.js"; +import { + AccessToken, + CoinEnvelope, + ExchangeWireAccount, + PaytoString, + buildCodecForUnion, + codecForConstNumber, + codecForConstString, + codecForEither, + codecForExchangeWireAccount, + codecForMap, + codecForPaytoString, + codecForTalerUriString, +} from "./index.js"; +import { + AbsoluteTime, + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForDuration, + codecForTimestamp, +} from "./time.js"; +import { + AmountString, + Base32String, + BlindedRsaSignature, + ClaimToken, + CoinPublicKey, + CurrencySpecification, + EddsaPublicKey, + EddsaPublicKeyString, + EddsaSignatureString, + HashCode, + HashCodeString, + ImageDataUrl, + Integer, + InternationalizedString, + RelativeTime, + RsaSignature, + Timestamp, + WireTransferIdentifierRawP, + codecForCurrencySpecificiation, + codecForInternationalizedString, + codecForURLString, +} from "./types-taler-common.js"; + +/** + * Proposal returned from the contract URL. + */ +export interface Proposal { + /** + * Contract terms for the propoal. + * Raw, un-decoded JSON object. + */ + contract_terms: any; + + /** + * Signature over contract, made by the merchant. The public key used for signing + * must be contract_terms.merchant_pub. + */ + sig: string; +} + +export interface MerchantPayResponse { + sig: string; + pos_confirmation?: string; +} + +interface MerchantOrderStatusPaid { + // Was the payment refunded (even partially, via refund or abort)? + refunded: boolean; + + // Is any amount of the refund still waiting to be picked up (even partially)? + refund_pending: boolean; + + // Amount that was refunded in total. + refund_amount: AmountString; + + // Amount that already taken by the wallet. + refund_taken: AmountString; +} + +interface MerchantOrderRefundResponse { + /** + * Amount that was refunded in total. + */ + refund_amount: AmountString; + + /** + * Successful refunds for this payment, empty array for none. + */ + refunds: MerchantCoinRefundStatus[]; + + /** + * Public key of the merchant. + */ + merchant_pub: EddsaPublicKeyString; +} + +/** + * Response from the internal merchant API. + */ +export class CheckPaymentResponse { + order_status: string; + refunded: boolean | undefined; + refunded_amount: string | undefined; + contract_terms: any | undefined; + taler_pay_uri: string | undefined; + contract_url: string | undefined; +} + +export const codecForMerchantRefundPermission = + (): Codec<MerchantAbortPayRefundDetails> => + buildCodecForObject<MerchantAbortPayRefundDetails>() + .property("refund_amount", codecForAmountString()) + .property("refund_fee", codecForAmountString()) + .property("coin_pub", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("exchange_sig", codecOptional(codecForString())) + .property("exchange_pub", codecOptional(codecForString())) + .build("MerchantRefundPermission"); + +export const codecForProposal = (): Codec<Proposal> => + buildCodecForObject<Proposal>() + .property("contract_terms", codecForAny()) + .property("sig", codecForString()) + .build("Proposal"); + +export const codecForCheckPaymentResponse = (): Codec<CheckPaymentResponse> => + buildCodecForObject<CheckPaymentResponse>() + .property("order_status", codecForString()) + .property("refunded", codecOptional(codecForBoolean())) + .property("refunded_amount", codecOptional(codecForString())) + .property("contract_terms", codecOptional(codecForAny())) + .property("taler_pay_uri", codecOptional(codecForString())) + .property("contract_url", codecOptional(codecForString())) + .build("CheckPaymentResponse"); + +export type MerchantCoinRefundStatus = + | MerchantCoinRefundSuccessStatus + | MerchantCoinRefundFailureStatus; + +export interface MerchantCoinRefundSuccessStatus { + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: EddsaSignatureString; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKeyString; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: TalerProtocolTimestamp; +} + +export interface MerchantCoinRefundFailureStatus { + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: any; + + // Refund transaction ID. + rtransaction_id: number; + + // public key of a coin that was refunded + coin_pub: EddsaPublicKeyString; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + execution_time: TalerProtocolTimestamp; +} + +export interface MerchantOrderStatusUnpaid { + /** + * URI that the wallet must process to complete the payment. + */ + taler_pay_uri: string; + + /** + * Alternative order ID which was paid for already in the same session. + * + * Only given if the same product was purchased before in the same session. + */ + already_paid_order_id?: string; +} + +export const codecForMerchantPayResponse = (): Codec<MerchantPayResponse> => + buildCodecForObject<MerchantPayResponse>() + .property("sig", codecForString()) + .property("pos_confirmation", codecOptional(codecForString())) + .build("MerchantPayResponse"); + +export const codecForMerchantOrderStatusPaid = + (): Codec<MerchantOrderStatusPaid> => + buildCodecForObject<MerchantOrderStatusPaid>() + .property("refund_amount", codecForAmountString()) + .property("refund_taken", codecForAmountString()) + .property("refund_pending", codecForBoolean()) + .property("refunded", codecForBoolean()) + .build("MerchantOrderStatusPaid"); + +export const codecForMerchantOrderStatusUnpaid = + (): Codec<MerchantOrderStatusUnpaid> => + buildCodecForObject<MerchantOrderStatusUnpaid>() + .property("taler_pay_uri", codecForString()) + .property("already_paid_order_id", codecOptional(codecForString())) + .build("MerchantOrderStatusUnpaid"); + +export interface AbortRequest { + // hash of the order's contract terms (this is used to authenticate the + // wallet/customer in case $ORDER_ID is guessable). + h_contract: string; + + // List of coins the wallet would like to see refunds for. + // (Should be limited to the coins for which the original + // payment succeeded, as far as the wallet knows.) + coins: AbortingCoin[]; +} + +export interface AbortingCoin { + // Public key of a coin for which the wallet is requesting an abort-related refund. + coin_pub: EddsaPublicKeyString; + + // The amount to be refunded (matches the original contribution) + contribution: AmountString; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; +} + +export interface AbortResponse { + // List of refund responses about the coins that the wallet + // requested an abort for. In the same order as the 'coins' + // from the original request. + // The rtransaction_id is implied to be 0. + refunds: MerchantAbortPayRefundStatus[]; +} + +export type MerchantAbortPayRefundStatus = + | MerchantAbortPayRefundSuccessStatus + | MerchantAbortPayRefundFailureStatus; + +// Details about why a refund failed. +export interface MerchantAbortPayRefundFailureStatus { + // Used as tag for the sum type RefundStatus sum type. + type: "failure"; + + // HTTP status of the exchange request, must NOT be 200. + exchange_status: number; + + // Taler error code from the exchange reply, if available. + exchange_code?: number; + + // If available, HTTP reply from the exchange. + exchange_reply?: unknown; +} + +// Additional details needed to verify the refund confirmation signature +// (h_contract_terms and merchant_pub) are already known +// to the wallet and thus not included. +export interface MerchantAbortPayRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // the EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund + exchange_sig: string; + + // public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: string; +} + +/** + * Information about an exchange as stored inside a + * merchant's contract terms. + */ +export interface ExchangeHandle { + // The exchange's base URL. + url: string; + + // Master public key of the exchange. + master_pub: EddsaPublicKeyString; +} + +export interface AuditorHandle { + /** + * Official name of the auditor. + */ + name: string; + + /** + * Master public signing key of the auditor. + */ + auditor_pub: EddsaPublicKeyString; + + /** + * Base URL of the auditor. + */ + url: string; +} + +// Delivery location, loosely modeled as a subset of +// ISO20022's PostalAddress25. +export interface Location { + // Nation with its own government. + country?: string; + + // Identifies a subdivision of a country such as state, region, county. + country_subdivision?: string; + + // Identifies a subdivision within a country sub-division. + district?: string; + + // Name of a built-up area, with defined boundaries, and a local government. + town?: string; + + // Specific location name within the town. + town_location?: string; + + // Identifier consisting of a group of letters and/or numbers that + // is added to a postal address to assist the sorting of mail. + post_code?: string; + + // Name of a street or thoroughfare. + street?: string; + + // Name of the building or house. + building_name?: string; + + // Number that identifies the position of a building on a street. + building_number?: string; + + // Free-form address lines, should not exceed 7 elements. + address_lines?: string[]; +} + +export interface MerchantInfo { + // The merchant's legal name of business. + name: string; + + // Label for a location with the business address of the merchant. + email?: string; + + // Label for a location with the business address of the merchant. + website?: string; + + // An optional base64-encoded product image. + logo?: ImageDataUrl; + + // Label for a location with the business address of the merchant. + address?: Location; + + // Label for a location that denotes the jurisdiction for disputes. + // Some of the typical fields for a location (such as a street address) may be absent. + jurisdiction?: Location; +} + +export interface Tax { + // the name of the tax + name: string; + + // amount paid in tax + tax: AmountString; +} + +export interface Product { + // merchant-internal identifier for the product. + product_id?: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions + description_i18n?: InternationalizedString; + + // The number of units of the product to deliver to the customer. + quantity?: Integer; + + // The unit in which the product is measured (liters, kilograms, packages, etc.) + unit?: string; + + // The price of the product; this is the total price for quantity times unit of this product. + price?: AmountString; + + // An optional base64-encoded product image + image?: ImageDataUrl; + + // a list of taxes paid by the merchant for this product. Can be empty. + taxes?: Tax[]; + + // time indicating when this product should be delivered + delivery_date?: TalerProtocolTimestamp; +} + +/** + * Contract terms from a merchant. + * FIXME: Add type field! + */ +export interface MerchantContractTerms { + // The hash of the merchant instance's wire details. + h_wire: string; + + // Specifies for how long the wallet should try to get an + // automatic refund for the purchase. If this field is + // present, the wallet should wait for a few seconds after + // the purchase and then automatically attempt to obtain + // a refund. The wallet should probe until "delay" + // after the payment was successful (i.e. via long polling + // or via explicit requests with exponential back-off). + // + // In particular, if the wallet is offline + // at that time, it MUST repeat the request until it gets + // one response from the merchant after the delay has expired. + // If the refund is granted, the wallet MUST automatically + // recover the payment. This is used in case a merchant + // knows that it might be unable to satisfy the contract and + // desires for the wallet to attempt to get the refund without any + // customer interaction. Note that it is NOT an error if the + // merchant does not grant a refund. + auto_refund?: TalerProtocolDuration; + + // Wire transfer method identifier for the wire method associated with h_wire. + // The wallet may only select exchanges via a matching auditor if the + // exchange also supports this wire method. + // The wire transfer fees must be added based on this wire transfer method. + wire_method: string; + + // Human-readable description of the whole purchase. + summary: string; + + // Map from IETF BCP 47 language tags to localized summaries. + summary_i18n?: InternationalizedString; + + // Unique, free-form identifier for the proposal. + // Must be unique within a merchant instance. + // For merchants that do not store proposals in their DB + // before the customer paid for them, the order_id can be used + // by the frontend to restore a proposal from the information + // encoded in it (such as a short product identifier and timestamp). + order_id: string; + + // Total price for the transaction. + // The exchange will subtract deposit fees from that amount + // before transferring it to the merchant. + amount: string; + + // Nonce generated by the wallet and echoed by the merchant + // in this field when the proposal is generated. + nonce: string; + + // After this deadline, the merchant won't accept payments for the contract. + pay_deadline: TalerProtocolTimestamp; + + // More info about the merchant, see below. + merchant: MerchantInfo; + + // Merchant's public key used to sign this proposal; this information + // is typically added by the backend. Note that this can be an ephemeral key. + merchant_pub: string; + + // Time indicating when the order should be delivered. + // May be overwritten by individual products. + delivery_date?: TalerProtocolTimestamp; + + // Delivery location for (all!) products. + delivery_location?: Location; + + // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + exchanges: ExchangeHandle[]; + + // List of products that are part of the purchase (see Product). + products?: Product[]; + + // After this deadline has passed, no refunds will be accepted. + refund_deadline: TalerProtocolTimestamp; + + // Transfer deadline for the exchange. Must be in the + // deposit permissions of coins used to pay for this order. + wire_transfer_deadline: TalerProtocolTimestamp; + + // Time when this contract was generated. + timestamp: TalerProtocolTimestamp; + + // Base URL of the (public!) merchant backend API. + // Must be an absolute URL that ends with a slash. + merchant_base_url: string; + + // URL that will show that the order was successful after + // it has been paid for. Optional, but either fulfillment_url + // or fulfillment_message must be specified in every + // contract terms. + // + // If a non-unique fulfillment URL is used, a customer can only + // buy the order once and will be redirected to a previous purchase + // when trying to buy an order with the same fulfillment URL a second + // time. This is useful for digital goods that a customer only needs + // to buy once but should be able to repeatedly download. + // + // For orders where the customer is expected to be able to make + // repeated purchases (for equivalent goods), the fulfillment URL + // should be made unique for every order. The easiest way to do + // this is to include a unique order ID in the fulfillment URL. + // + // When POSTing to the merchant, the placeholder text "${ORDER_ID}" + // is be replaced with the actual order ID (useful if the + // order ID is generated server-side and needs to be + // in the URL). Note that this placeholder can only be used once. + // Front-ends may use other means to generate a unique fulfillment URL. + fulfillment_url?: string; + + // URL where the same contract could be ordered again (if + // available). Returned also at the public order endpoint + // for people other than the actual buyer (hence public, + // in case order IDs are guessable). + public_reorder_url?: string; + + // Message shown to the customer after paying for the order. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_message?: string; + + // Map from IETF BCP 47 language tags to localized fulfillment + // messages. + fulfillment_message_i18n?: InternationalizedString; + + // Maximum total deposit fee accepted by the merchant for this contract. + // Overrides defaults of the merchant instance. + max_fee: string; + + // Extra data that is only interpreted by the merchant frontend. + // Useful when the merchant needs to store extra information on a + // contract without storing it separately in their database. + // Must really be an Object (not a string, integer, float or array). + extra?: any; + + // Minimum age the buyer must have (in years). Default is 0. + // This value is at least as large as the maximum over all + // minimum age requirements of the products in this contract. + // It might also be set independent of any product, due to + // legal requirements. + minimum_age?: Integer; +} + +/** + * Refund permission in the format that the merchant gives it to us. + */ +export interface MerchantAbortPayRefundDetails { + /** + * Amount to be refunded. + */ + refund_amount: string; + + /** + * Fee for the refund. + */ + refund_fee: string; + + /** + * Public key of the coin being refunded. + */ + coin_pub: string; + + /** + * Refund transaction ID between merchant and exchange. + */ + rtransaction_id: number; + + /** + * Exchange's key used for the signature. + */ + exchange_pub?: string; + + /** + * Exchange's signature to confirm the refund. + */ + exchange_sig?: string; + + /** + * Error replay from the exchange (if any). + */ + exchange_reply?: any; + + /** + * Error code from the exchange (if any). + */ + exchange_code?: number; + + /** + * HTTP status code of the exchange's response + * to the merchant's refund request. + */ + exchange_http_status: number; +} + +/** + * Reserve signature, defined as separate class to facilitate + * schema validation. + */ +export interface MerchantBlindSigWrapperV1 { + /** + * Reserve signature. + */ + blind_sig: string; +} + +export const codecForExchangeHandle = (): Codec<ExchangeHandle> => + buildCodecForObject<ExchangeHandle>() + .property("master_pub", codecForString()) + .property("url", codecForString()) + .build("ExchangeHandle"); + +export const codecForAuditorHandle = (): Codec<AuditorHandle> => + buildCodecForObject<AuditorHandle>() + .property("name", codecForString()) + .property("auditor_pub", codecForString()) + .property("url", codecForString()) + .build("AuditorHandle"); + +export const codecForLocation = (): Codec<Location> => + buildCodecForObject<Location>() + .property("country", codecOptional(codecForString())) + .property("country_subdivision", codecOptional(codecForString())) + .property("building_name", codecOptional(codecForString())) + .property("building_number", codecOptional(codecForString())) + .property("district", codecOptional(codecForString())) + .property("street", codecOptional(codecForString())) + .property("post_code", codecOptional(codecForString())) + .property("town", codecOptional(codecForString())) + .property("town_location", codecOptional(codecForString())) + .property("address_lines", codecOptional(codecForList(codecForString()))) + .build("Location"); + +export const codecForMerchantInfo = (): Codec<MerchantInfo> => + buildCodecForObject<MerchantInfo>() + .property("name", codecForString()) + .property("address", codecOptional(codecForLocation())) + .property("jurisdiction", codecOptional(codecForLocation())) + .build("MerchantInfo"); + +export const codecForMerchantContractTerms = (): Codec<MerchantContractTerms> => + buildCodecForObject<MerchantContractTerms>() + .property("order_id", codecForString()) + .property("fulfillment_url", codecOptional(codecForString())) + .property("fulfillment_message", codecOptional(codecForString())) + .property( + "fulfillment_message_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("merchant_base_url", codecForString()) + .property("h_wire", codecForString()) + .property("auto_refund", codecOptional(codecForDuration)) + .property("wire_method", codecForString()) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .property("nonce", codecForString()) + .property("amount", codecForAmountString()) + .property("pay_deadline", codecForTimestamp) + .property("refund_deadline", codecForTimestamp) + .property("wire_transfer_deadline", codecForTimestamp) + .property("timestamp", codecForTimestamp) + .property("delivery_location", codecOptional(codecForLocation())) + .property("delivery_date", codecOptional(codecForTimestamp)) + .property("max_fee", codecForAmountString()) + .property("merchant", codecForMerchantInfo()) + .property("merchant_pub", codecForString()) + .property("exchanges", codecForList(codecForExchangeHandle())) + .property("products", codecOptional(codecForList(codecForProduct()))) + .property("extra", codecForAny()) + .property("minimum_age", codecOptional(codecForNumber())) + .build("MerchantContractTerms"); + +export interface TalerMerchantConfigResponse { + // libtool-style representation of the Merchant protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // Name of the protocol. + name: "taler-merchant"; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since **v8**, may become mandatory in the future. + implementation?: string; + + // Default (!) currency supported by this backend. + // This is the currency that the backend should + // suggest by default to the user when entering + // amounts. See currencies for a list of + // supported currencies and how to render them. + currency: string; + + // How services should render currencies supported + // by this backend. Maps + // currency codes (e.g. "EUR" or "KUDOS") to + // the respective currency specification. + // All currencies in this map are supported by + // the backend. Note that the actual currency + // specifications are a *hint* for applications + // that would like *advice* on how to render amounts. + // Applications *may* ignore the currency specification + // if they know how to render currencies that they are + // used with. + currencies: { [currency: string]: CurrencySpecification }; + + // Array of exchanges trusted by the merchant. + // Since protocol **v6**. + exchanges: ExchangeConfigInfo[]; +} + +export interface ExchangeConfigInfo { + // Base URL of the exchange REST API. + base_url: string; + + // Currency for which the merchant is configured + // to trust the exchange. + // May not be the one the exchange actually uses, + // but is the only one we would trust this exchange for. + currency: string; + + // Offline master public key of the exchange. The + // /keys data must be signed with this public + // key for us to trust it. + master_pub: EddsaPublicKey; +} + +export interface ClaimRequest { + // Nonce to identify the wallet that claimed the order. + nonce: string; + + // Token that authorizes the wallet to claim the order. + // *Optional* as the merchant may not have required it + // (create_token set to false in PostOrderRequest). + token?: ClaimToken; +} + +export interface ClaimResponse { + // Contract terms of the claimed order + contract_terms: ContractTerms; + + // Signature by the merchant over the contract terms. + sig: EddsaSignatureString; +} + +export interface PaymentResponse { + // Signature on TALER_PaymentResponsePS with the public + // key of the merchant instance. + sig: EddsaSignatureString; + + // Text to be shown to the point-of-sale staff as a proof of + // payment. + pos_confirmation?: string; +} + +export interface PaymentStatusRequestParams { + // Hash of the order’s contract terms (this is used to + // authenticate the wallet/customer in case + // $ORDER_ID is guessable). + // Required once an order was claimed. + contractTermHash?: string; + // Authorizes the request via the claim token that + // was returned in the PostOrderResponse. Used with + // unclaimed orders only. Whether token authorization is + // required is determined by the merchant when the + // frontend creates the order. + claimToken?: string; + // Session ID that the payment must be bound to. + // If not specified, the payment is not session-bound. + sessionId?: string; + // If specified, the merchant backend will wait up to + // timeout_ms milliseconds for completion of the payment + // before sending the HTTP response. A client must never + // rely on this behavior, as the merchant backend may return + // a response immediately. + timeout?: number; + // If set to “yes”, poll for the order’s pending refunds + // to be picked up. timeout_ms specifies how long we + // will wait for the refund. + awaitRefundObtained?: boolean; + // Indicates that we are polling for a refund above the + // given AMOUNT. timeout_ms will specify how long we + // will wait for the refund. + refund?: AmountString; + // Since protocol v9 refunded orders are only returned + // under “already_paid_order_id” if this flag is set + // explicitly to “YES”. + allowRefundedForRepurchase?: boolean; +} +export interface GetKycStatusRequestParams { + // If specified, the KYC check should return + // the KYC status only for this wire account. + // Otherwise, for all wire accounts. + wireHash?: string; + // If specified, the KYC check should return + // the KYC status only for the given exchange. + // Otherwise, for all exchanges we interacted with. + exchangeURL?: string; + // If specified, the merchant will wait up to + // timeout_ms milliseconds for the exchanges to + // confirm completion of the KYC process(es). + timeout?: number; +} +export interface GetOtpDeviceRequestParams { + // Timestamp in seconds to use when calculating + // the current OTP code of the device. Since protocol v10. + faketime?: number; + // Price to use when calculating the current OTP + // code of the device. Since protocol v10. + price?: AmountString; +} +export interface GetOrderRequestParams { + // Session ID that the payment must be bound to. + // If not specified, the payment is not session-bound. + sessionId?: string; + // Timeout in milliseconds to wait for a payment if + // the answer would otherwise be negative (long polling). + timeout?: number; + // Since protocol v9 refunded orders are only returned + // under “already_paid_order_id” if this flag is set + // explicitly to “YES”. + allowRefundedForRepurchase?: boolean; +} +export interface ListWireTransferRequestParams { + // Filter for transfers to the given bank account + // (subject and amount MUST NOT be given in the payto URI). + paytoURI?: string; + // Filter for transfers executed before the given timestamp. + before?: number; + // Filter for transfers executed after the given timestamp. + after?: number; + // At most return the given number of results. Negative for + // descending in execution time, positive for ascending in + // execution time. Default is -20. + limit?: number; + // Starting transfer_serial_id for an iteration. + offset?: string; + // Filter transfers by verification status. + verified?: boolean; + order?: "asc" | "dec"; +} +export interface ListOrdersRequestParams { + // If set to yes, only return paid orders, if no only + // unpaid orders. Do not give (or use “all”) to see all + // orders regardless of payment status. + paid?: boolean; + // If set to yes, only return refunded orders, if no only + // unrefunded orders. Do not give (or use “all”) to see + // all orders regardless of refund status. + refunded?: boolean; + // If set to yes, only return wired orders, if no only + // orders with missing wire transfers. Do not give (or + // use “all”) to see all orders regardless of wire transfer + // status. + wired?: boolean; + // At most return the given number of results. Negative + // for descending by row ID, positive for ascending by + // row ID. Default is 20. Since protocol v12. + limit?: number; + // Non-negative date in seconds after the UNIX Epoc, see delta + // for its interpretation. If not specified, we default to the + // oldest or most recent entry, depending on delta. + date?: AbsoluteTime; + // Starting product_serial_id for an iteration. + // Since protocol v12. + offset?: string; + // Timeout in milliseconds to wait for additional orders if the + // answer would otherwise be negative (long polling). Only useful + // if delta is positive. Note that the merchant MAY still return + // a response that contains fewer than delta orders. + timeout?: number; + // Since protocol v6. Filters by session ID. + sessionId?: string; + // Since protocol v6. Filters by fulfillment URL. + fulfillmentUrl?: string; + + order?: "asc" | "dec"; +} + +export interface PayRequest { + // The coins used to make the payment. + coins: CoinPaySig[]; + + // Custom inputs from the wallet for the contract. + wallet_data?: Object; + + // The session for which the payment is made (or replayed). + // Only set for session-based payments. + session_id?: string; +} + +export interface CoinPaySig { + // Signature by the coin. + coin_sig: EddsaSignatureString; + + // Public key of the coin being spent. + coin_pub: EddsaPublicKey; + + // Signature made by the denomination public key. + ub_sig: RsaSignature; + + // The hash of the denomination public key associated with this coin. + h_denom: HashCodeString; + + // The amount that is subtracted from this coin with this payment. + contribution: AmountString; + + // URL of the exchange this coin was withdrawn from. + exchange_url: string; +} + +export interface StatusPaid { + type: "paid"; + + // Was the payment refunded (even partially, via refund or abort)? + refunded: boolean; + + // Is any amount of the refund still waiting to be picked up (even partially)? + refund_pending: boolean; + + // Amount that was refunded in total. + refund_amount: AmountString; + + // Amount that already taken by the wallet. + refund_taken: AmountString; +} +export interface StatusGotoResponse { + type: "goto"; + // The client should go to the reorder URL, there a fresh + // order might be created as this one is taken by another + // customer or wallet (or repurchase detection logic may + // apply). + public_reorder_url: string; +} +export interface StatusUnpaidResponse { + type: "unpaid"; + // URI that the wallet must process to complete the payment. + taler_pay_uri: string; + + // Status URL, can be used as a redirect target for the browser + // to show the order QR code / trigger the wallet. + fulfillment_url?: string; + + // Alternative order ID which was paid for already in the same session. + // Only given if the same product was purchased before in the same session. + already_paid_order_id?: string; +} + +export interface PaidRefundStatusResponse { + // Text to be shown to the point-of-sale staff as a proof of + // payment (present only if reusable OTP algorithm is used). + pos_confirmation?: string; + + // True if the order has been subjected to + // refunds. False if it was simply paid. + refunded: boolean; +} + +export interface PaidRequest { + // Signature on TALER_PaymentResponsePS with the public + // key of the merchant instance. + sig: EddsaSignatureString; + + // Hash of the order's contract terms (this is used to authenticate the + // wallet/customer and to enable signature verification without + // database access). + h_contract: HashCodeString; + + // Hash over custom inputs from the wallet for the contract. + wallet_data_hash?: HashCodeString; + + // Session id for which the payment is proven. + session_id: string; +} + +export interface AbortRequest { + // Hash of the order's contract terms (this is used to authenticate the + // wallet/customer in case $ORDER_ID is guessable). + h_contract: HashCodeString; + + // List of coins the wallet would like to see refunds for. + // (Should be limited to the coins for which the original + // payment succeeded, as far as the wallet knows.) + coins: AbortingCoin[]; +} + +export interface AbortResponse { + // List of refund responses about the coins that the wallet + // requested an abort for. In the same order as the coins + // from the original request. + // The rtransaction_id is implied to be 0. + refunds: MerchantAbortPayRefundStatus[]; +} + +export interface WalletRefundRequest { + // Hash of the order's contract terms (this is used to authenticate the + // wallet/customer). + h_contract: HashCodeString; +} + +export interface WalletRefundResponse { + // Amount that was refunded in total. + refund_amount: AmountString; + + // Successful refunds for this payment, empty array for none. + refunds: MerchantCoinRefundStatus[]; + + // Public key of the merchant. + merchant_pub: EddsaPublicKey; +} + +// Additional details needed to verify the refund confirmation signature +// (h_contract_terms and merchant_pub) are already known +// to the wallet and thus not included. +export interface MerchantCoinRefundSuccessStatus { + // Used as tag for the sum type MerchantCoinRefundStatus sum type. + type: "success"; + + // HTTP status of the exchange request, 200 (integer) required for refund confirmations. + exchange_status: 200; + + // The EdDSA :ref:signature (binary-only) with purpose + // TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND using a current signing key of the + // exchange affirming the successful refund. + exchange_sig: EddsaSignatureString; + + // Public EdDSA key of the exchange that was used to generate the signature. + // Should match one of the exchange's signing keys from /keys. It is given + // explicitly as the client might otherwise be confused by clock skew as to + // which signing key was used. + exchange_pub: EddsaPublicKey; + + // Refund transaction ID. + rtransaction_id: Integer; + + // Public key of a coin that was refunded. + coin_pub: EddsaPublicKey; + + // Amount that was refunded, including refund fee charged by the exchange + // to the customer. + refund_amount: AmountString; + + // Timestamp when the merchant approved the refund. + // Useful for grouping refunds. + execution_time: Timestamp; +} + +interface RewardInformation { + // Exchange from which the reward will be withdrawn. Needed by the + // wallet to determine denominations, fees, etc. + exchange_url: string; + + // URL where to go after obtaining the reward. + next_url: string; + + // (Remaining) amount of the reward (including fees). + reward_amount: AmountString; + + // Timestamp indicating when the reward is set to expire (may be in the past). + // Note that rewards that have expired MAY also result in a 404 response. + expiration: Timestamp; +} + +interface PlanchetDetail { + // Hash of the denomination's public key (hashed to reduce + // bandwidth consumption). + denom_pub_hash: HashCodeString; + + // Coin's blinded public key. + coin_ev: CoinEnvelope; +} + +interface BlindSignature { + // The (blind) RSA signature. Still needs to be unblinded. + blind_sig: BlindedRsaSignature; +} + +export interface InstanceConfigurationMessage { + // Name of the merchant instance to create (will become $INSTANCE). + // Must match the regex ^[A-Za-z0-9][A-Za-z0-9_.@-]+$. + id: string; + + // Merchant name corresponding to this instance. + name: string; + + // Type of the user (business or individual). + // Defaults to 'business'. Should become mandatory field + // in the future, left as optional for API compatibility for now. + user_type?: string; + + // Merchant email for customer contact. + email?: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // Authentication settings for this instance + auth: InstanceAuthConfigurationMessage; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Use STEFAN curves to determine default fees? + // If false, no fees are allowed by default. + // Can always be overridden by the frontend on a per-order basis. + use_stefan: boolean; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; +} + +export interface InstanceAuthConfigurationMessage { + // Type of authentication. + // "external": The mechant backend does not do + // any authentication checks. Instead an API + // gateway must do the authentication. + // "token": The merchant checks an auth token. + // See "token" for details. + method: "external" | "token"; + + // For method "token", this field is mandatory. + // The token MUST begin with the string "secret-token:". + // After the auth token has been set (with method "token"), + // the value must be provided in a "Authorization: Bearer $token" + // header. + token?: AccessToken; +} + +export interface InstanceReconfigurationMessage { + // Merchant name corresponding to this instance. + name: string; + + // Type of the user (business or individual). + // Defaults to 'business'. Should become mandatory field + // in the future, left as optional for API compatibility for now. + user_type?: string; + + // Merchant email for customer contact. + email?: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Use STEFAN curves to determine default fees? + // If false, no fees are allowed by default. + // Can always be overridden by the frontend on a per-order basis. + use_stefan: boolean; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; +} + +export interface InstancesResponse { + // List of instances that are present in the backend (see Instance). + instances: Instance[]; +} + +export interface Instance { + // Merchant name corresponding to this instance. + name: string; + + // Type of the user ("business" or "individual"). + user_type: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // Merchant instance this response is about ($INSTANCE). + id: string; + + // Public key of the merchant/instance, in Crockford Base32 encoding. + merchant_pub: EddsaPublicKey; + + // List of the payment targets supported by this instance. Clients can + // specify the desired payment target in /order requests. Note that + // front-ends do not have to support wallets selecting payment targets. + payment_targets: string[]; + + // Has this instance been deleted (but not purged)? + deleted: boolean; +} + +export interface QueryInstancesResponse { + // Merchant name corresponding to this instance. + name: string; + + // Type of the user ("business" or "individual"). + user_type: string; + + // Merchant email for customer contact. + email?: string; + + // Merchant public website. + website?: string; + + // Merchant logo. + logo?: ImageDataUrl; + + // Public key of the merchant/instance, in Crockford Base32 encoding. + merchant_pub: EddsaPublicKey; + + // The merchant's physical address (to be put into contracts). + address: Location; + + // The jurisdiction under which the merchant conducts its business + // (to be put into contracts). + jurisdiction: Location; + + // Use STEFAN curves to determine default fees? + // If false, no fees are allowed by default. + // Can always be overridden by the frontend on a per-order basis. + use_stefan: boolean; + + // If the frontend does NOT specify an execution date, how long should + // we tell the exchange to wait to aggregate transactions before + // executing the wire transfer? This delay is added to the current + // time when we generate the advisory execution time for the exchange. + default_wire_transfer_delay: RelativeTime; + + // If the frontend does NOT specify a payment deadline, how long should + // offers we make be valid by default? + default_pay_delay: RelativeTime; + + // Authentication configuration. + // Does not contain the token when token auth is configured. + auth: { + method: "external" | "token"; + }; +} + +export interface AccountKycRedirects { + // Array of pending KYCs. + pending_kycs: MerchantAccountKycRedirect[]; + + // Array of exchanges with no reply. + timeout_kycs: ExchangeKycTimeout[]; +} + +export interface MerchantAccountKycRedirect { + // URL that the user should open in a browser to + // proceed with the KYC process (as returned + // by the exchange's /kyc-check/ endpoint). + // Optional, missing if the account is blocked + // due to AML and not due to KYC. + kyc_url?: string; + + // AML status of the account. + aml_status: Integer; + + // Base URL of the exchange this is about. + exchange_url: string; + + // Our bank wire account this is about. + payto_uri: PaytoString; +} + +export interface ExchangeKycTimeout { + // Base URL of the exchange this is about. + exchange_url: string; + + // Numeric error code indicating errors the exchange + // returned, or TALER_EC_INVALID for none. + exchange_code: number; + + // HTTP status code returned by the exchange when we asked for + // information about the KYC status. + // 0 if there was no response at all. + exchange_http_status: number; +} + +export interface AccountAddDetails { + // payto:// URI of the account. + payto_uri: PaytoString; + + // URL from where the merchant can download information + // about incoming wire transfers to this account. + credit_facade_url?: string; + + // Credentials to use when accessing the credit facade. + // Never returned on a GET (as this may be somewhat + // sensitive data). Can be set in POST + // or PATCH requests to update (or delete) credentials. + // To really delete credentials, set them to the type: "none". + credit_facade_credentials?: FacadeCredentials; +} + +export type FacadeCredentials = + | NoFacadeCredentials + | BasicAuthFacadeCredentials; + +export interface NoFacadeCredentials { + type: "none"; +} + +export interface BasicAuthFacadeCredentials { + type: "basic"; + + // Username to use to authenticate + username: string; + + // Password to use to authenticate + password: string; +} + +export interface AccountAddResponse { + // Hash over the wire details (including over the salt). + h_wire: HashCode; + + // Salt used to compute h_wire. + salt: HashCode; +} + +export interface AccountPatchDetails { + // URL from where the merchant can download information + // about incoming wire transfers to this account. + credit_facade_url?: string; + + // Credentials to use when accessing the credit facade. + // Never returned on a GET (as this may be somewhat + // sensitive data). Can be set in POST + // or PATCH requests to update (or delete) credentials. + // To really delete credentials, set them to the type: "none". + // If the argument is omitted, the old credentials + // are simply preserved. + credit_facade_credentials?: FacadeCredentials; +} + +export interface AccountsSummaryResponse { + // List of accounts that are known for the instance. + accounts: BankAccountEntry[]; +} + +// TODO: missing in docs +export interface BankAccountEntry { + // payto:// URI of the account. + payto_uri: PaytoString; + + // Hash over the wire details (including over the salt). + h_wire: HashCode; + + // true if this account is active, + // false if it is historic. + active?: boolean; +} +export interface BankAccountDetail { + // payto:// URI of the account. + payto_uri: PaytoString; + + // Hash over the wire details (including over the salt). + h_wire: HashCode; + + // Salt used to compute h_wire. + salt: HashCode; + + // URL from where the merchant can download information + // about incoming wire transfers to this account. + credit_facade_url?: string; + + // true if this account is active, + // false if it is historic. + active?: boolean; +} + +export interface CategoryListResponse { + // Array with all of the categories we know. + categories: CategoryListEntry[]; +} + +export interface CategoryListEntry { + // Unique number for the category. + category_id: Integer; + + // Name of the category. + name: string; + + // Translations of the name into various + // languages. + name_i18n?: { [lang_tag: string]: string }; + + // Number of products in this category. + // A product can be in more than one category. + product_count: Integer; +} + +export interface CategoryProductList { + // Name of the category. + name: string; + + // Translations of the name into various + // languages. + name_i18n?: { [lang_tag: string]: string }; + + // The products in this category. + products: ProductSummary[]; +} + +export interface ProductSummary { + // Product ID to use. + product_id: string; + +} + +export interface CategoryCreateRequest { + // Name of the category. + name: string; + + // Translations of the name into various + // languages. + name_i18n?: { [lang_tag: string]: string }; +} + +export interface CategoryCreatedResponse { + // Number of the newly created category. + category_id: Integer; +} + +export interface ProductAddDetail { + // Product ID to use. + product_id: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // Categories into which the product belongs. + // Used in the POS-endpoint. + // Since API version **v16**. + categories?: Integer[]; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes?: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Identifies where the product is in stock. + address?: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age?: Integer; +} + +export interface ProductPatchDetail { + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // Categories into which the product belongs. + // Used in the POS-endpoint. + // Since API version **v16**. + categories?: Integer[]; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes?: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Number of units of the product that were lost (spoiled, stolen, etc.). + total_lost?: Integer; + + // Identifies where the product is in stock. + address?: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age?: Integer; +} + +export interface InventorySummaryResponse { + // List of products that are present in the inventory. + products: InventoryEntry[]; +} + +export interface InventoryEntry { + // Product identifier, as found in the product. + product_id: string; + // product_serial_id of the product in the database. + product_serial: Integer; +} + +export interface FullInventoryDetailsResponse { + // List of products that are present in the inventory. + products: MerchantPosProductDetail[]; + + // List of categories in the inventory. + categories: MerchantCategory[]; +} + +export interface MerchantPosProductDetail { + // A unique numeric ID of the product + product_serial: number; + + // A merchant-internal unique identifier for the product + product_id?: string; + + // A list of category IDs this product belongs to. + // Typically, a product only belongs to one category, but more than one is supported. + categories: number[]; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n: { [lang_tag: string]: string }; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes?: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // Optional, if missing treat as "infinite". + total_stock?: Integer; + + // Minimum age buyer must have (in years). + minimum_age?: Integer; +} + +export interface MerchantCategory { + // A unique numeric ID of the category + id: number; + + // The name of the category. This will be shown to users and used in the order summary. + name: string; + + // Map from IETF BCP 47 language tags to localized names + name_i18n?: { [lang_tag: string]: string }; +} + +export interface ProductDetail { + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n: { [lang_tag: string]: string }; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit: string; + + // Categories into which the product belongs. + // Since API version **v16**. + categories: Integer[]; + + // The price for one unit of the product. Zero is used + // to imply that this product is not sold separately, or + // that the price is not fixed, and must be supplied by the + // front-end. If non-zero, this price MUST include applicable + // taxes. + price: AmountString; + + // An optional base64-encoded product image. + image: ImageDataUrl; + + // A list of taxes paid by the merchant for one unit of this product. + taxes?: Tax[]; + + // Number of units of the product in stock in sum in total, + // including all existing sales ever. Given in product-specific + // units. + // A value of -1 indicates "infinite" (i.e. for "electronic" books). + total_stock: Integer; + + // Number of units of the product that have already been sold. + total_sold: Integer; + + // Number of units of the product that were lost (spoiled, stolen, etc.). + total_lost: Integer; + + // Identifies where the product is in stock. + address?: Location; + + // Identifies when we expect the next restocking to happen. + next_restock?: Timestamp; + + // Minimum age buyer must have (in years). + minimum_age?: Integer; +} +export interface LockRequest { + // UUID that identifies the frontend performing the lock + // Must be unique for the lifetime of the lock. + lock_uuid: string; + + // How long does the frontend intend to hold the lock? + duration: RelativeTime; + + // How many units should be locked? + quantity: Integer; +} + +export interface PostOrderRequest { + // The order must at least contain the minimal + // order detail, but can override all. + order: Order; + + // If set, the backend will then set the refund deadline to the current + // time plus the specified delay. If it's not set, refunds will not be + // possible. + refund_delay?: RelativeTime; + + // Specifies the payment target preferred by the client. Can be used + // to select among the various (active) wire methods supported by the instance. + payment_target?: string; + + // Specifies that some products are to be included in the + // order from the inventory. For these inventory management + // is performed (so the products must be in stock) and + // details are completed from the product data of the backend. + inventory_products?: MinimalInventoryProduct[]; + + // Specifies a lock identifier that was used to + // lock a product in the inventory. Only useful if + // inventory_products is set. Used in case a frontend + // reserved quantities of the individual products while + // the shopping cart was being built. Multiple UUIDs can + // be used in case different UUIDs were used for different + // products (i.e. in case the user started with multiple + // shopping sessions that were combined during checkout). + lock_uuids?: string[]; + + // Should a token for claiming the order be generated? + // False can make sense if the ORDER_ID is sufficiently + // high entropy to prevent adversarial claims (like it is + // if the backend auto-generates one). Default is 'true'. + create_token?: boolean; + + // OTP device ID to associate with the order. + // This parameter is optional. + otp_id?: string; +} + +export type Order = MinimalOrderDetail & Partial<ContractTerms>; + +export interface MinimalOrderDetail { + // Amount to be paid by the customer. + amount: AmountString; + + // Short summary of the order. + summary: string; + + // See documentation of fulfillment_url in ContractTerms. + // Either fulfillment_url or fulfillment_message must be specified. + // When creating an order, the fulfillment URL can + // contain ${ORDER_ID} which will be substituted with the + // order ID of the newly created order. + fulfillment_url?: string; + + // See documentation of fulfillment_message in ContractTerms. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_message?: string; +} + +export interface MinimalInventoryProduct { + // Which product is requested (here mandatory!). + product_id: string; + + // How many units of the product are requested. + quantity: Integer; +} + +export interface PostOrderResponse { + // Order ID of the response that was just created. + order_id: string; + + // Token that authorizes the wallet to claim the order. + // Provided only if "create_token" was set to 'true' + // in the request. + token?: ClaimToken; +} +export interface OutOfStockResponse { + // Product ID of an out-of-stock item. + product_id: string; + + // Requested quantity. + requested_quantity: Integer; + + // Available quantity (must be below requested_quantity). + available_quantity: Integer; + + // When do we expect the product to be again in stock? + // Optional, not given if unknown. + restock_expected?: Timestamp; +} + +export interface OrderHistory { + // Timestamp-sorted array of all orders matching the query. + // The order of the sorting depends on the sign of delta. + orders: OrderHistoryEntry[]; +} + +export interface OrderHistoryEntry { + // Order ID of the transaction related to this entry. + order_id: string; + + // Row ID of the order in the database. + row_id: number; + + // When the order was created. + timestamp: Timestamp; + + // The amount of money the order is for. + amount: AmountString; + + // The summary of the order. + summary: string; + + // Whether some part of the order is refundable, + // that is the refund deadline has not yet expired + // and the total amount refunded so far is below + // the value of the original transaction. + refundable: boolean; + + // Whether the order has been paid or not. + paid: boolean; +} + +export type MerchantOrderStatusResponse = + | CheckPaymentPaidResponse + | CheckPaymentClaimedResponse + | CheckPaymentUnpaidResponse; + +export interface CheckPaymentPaidResponse { + // The customer paid for this contract. + order_status: "paid"; + + // Was the payment refunded (even partially)? + refunded: boolean; + + // True if there are any approved refunds that the wallet has + // not yet obtained. + refund_pending: boolean; + + // Did the exchange wire us the funds? + wired: boolean; + + // Total amount the exchange deposited into our bank account + // for this contract, excluding fees. + deposit_total: AmountString; + + // Numeric error code indicating errors the exchange + // encountered tracking the wire transfer for this purchase (before + // we even got to specific coin issues). + // 0 if there were no issues. + exchange_code: number; + + // HTTP status code returned by the exchange when we asked for + // information to track the wire transfer for this purchase. + // 0 if there were no issues. + exchange_http_status: number; + + // Total amount that was refunded, 0 if refunded is false. + refund_amount: AmountString; + + // Contract terms. + contract_terms: ContractTerms; + + // The wire transfer status from the exchange for this order if + // available, otherwise empty array. + wire_details: TransactionWireTransfer[]; + + // Reports about trouble obtaining wire transfer details, + // empty array if no trouble were encountered. + wire_reports: TransactionWireReport[]; + + // The refund details for this order. One entry per + // refunded coin; empty array if there are no refunds. + refund_details: RefundDetails[]; + + // Status URL, can be used as a redirect target for the browser + // to show the order QR code / trigger the wallet. + order_status_url: string; +} + +export interface CheckPaymentClaimedResponse { + // A wallet claimed the order, but did not yet pay for the contract. + order_status: "claimed"; + + // Contract terms. + contract_terms: ContractTerms; +} + +export interface CheckPaymentUnpaidResponse { + // The order was neither claimed nor paid. + order_status: "unpaid"; + + // URI that the wallet must process to complete the payment. + taler_pay_uri: string; + + // when was the order created + creation_time: Timestamp; + + // Order summary text. + summary: string; + + // Total amount of the order (to be paid by the customer). + total_amount: AmountString; + + // Alternative order ID which was paid for already in the same session. + // Only given if the same product was purchased before in the same session. + already_paid_order_id?: string; + + // Fulfillment URL of an already paid order. Only given if under this + // session an already paid order with a fulfillment URL exists. + already_paid_fulfillment_url?: string; + + // Status URL, can be used as a redirect target for the browser + // to show the order QR code / trigger the wallet. + order_status_url: string; + + // We do we NOT return the contract terms here because they may not + // exist in case the wallet did not yet claim them. +} +export interface RefundDetails { + // Reason given for the refund. + reason: string; + + // Set to true if a refund is still available for the wallet for this payment. + pending: boolean; + + // When was the refund approved. + timestamp: Timestamp; + + // Total amount that was refunded (minus a refund fee). + amount: AmountString; +} + +export interface TransactionWireTransfer { + // Responsible exchange. + exchange_url: string; + + // 32-byte wire transfer identifier. + wtid: Base32String; + + // Execution time of the wire transfer. + execution_time: Timestamp; + + // Total amount that has been wire transferred + // to the merchant. + amount: AmountString; + + // Was this transfer confirmed by the merchant via the + // POST /transfers API, or is it merely claimed by the exchange? + confirmed: boolean; +} + +export interface TransactionWireReport { + // Numerical error code. + code: number; + + // Human-readable error description. + hint: string; + + // Numerical error code from the exchange. + exchange_code: number; + + // HTTP status code received from the exchange. + exchange_http_status: number; + + // Public key of the coin for which we got the exchange error. + coin_pub: CoinPublicKey; +} + +export interface ForgetRequest { + // Array of valid JSON paths to forgettable fields in the order's + // contract terms. + fields: string[]; +} + +export interface RefundRequest { + // Amount to be refunded. + refund: AmountString; + + // Human-readable refund justification. + reason: string; +} +export interface MerchantRefundResponse { + // URL (handled by the backend) that the wallet should access to + // trigger refund processing. + // taler://refund/... + taler_refund_uri: string; + + // Contract hash that a client may need to authenticate an + // HTTP request to obtain the above URI in a wallet-friendly way. + h_contract: HashCode; +} + +export interface TransferInformation { + // How much was wired to the merchant (minus fees). + credit_amount: AmountString; + + // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value). + wtid: WireTransferIdentifierRawP; + + // Target account that received the wire transfer. + payto_uri: PaytoString; + + // Base URL of the exchange that made the wire transfer. + exchange_url: string; +} + +export interface TransferList { + // List of all the transfers that fit the filter that we know. + transfers: TransferDetails[]; +} +export interface TransferDetails { + // How much was wired to the merchant (minus fees). + credit_amount: AmountString; + + // Raw wire transfer identifier identifying the wire transfer (a base32-encoded value). + wtid: WireTransferIdentifierRawP; + + // Target account that received the wire transfer. + payto_uri: PaytoString; + + // Base URL of the exchange that made the wire transfer. + exchange_url: string; + + // Serial number identifying the transfer in the merchant backend. + // Used for filtering via offset. + transfer_serial_id: number; + + // Time of the execution of the wire transfer by the exchange, according to the exchange + // Only provided if we did get an answer from the exchange. + execution_time?: Timestamp; + + // True if we checked the exchange's answer and are happy with it. + // False if we have an answer and are unhappy, missing if we + // do not have an answer from the exchange. + verified?: boolean; + + // True if the merchant uses the POST /transfers API to confirm + // that this wire transfer took place (and it is thus not + // something merely claimed by the exchange). + confirmed?: boolean; +} + +export interface OtpDeviceAddDetails { + // Device ID to use. + otp_device_id: string; + + // Human-readable description for the device. + otp_device_description: string; + + // A key encoded with RFC 3548 Base32. + // IMPORTANT: This is not using the typical + // Taler base32-crockford encoding. + // Instead it uses the RFC 3548 encoding to + // be compatible with the TOTP standard. + otp_key: string; + + // Algorithm for computing the POS confirmation. + // "NONE" or 0: No algorithm (no pos confirmation will be generated) + // "TOTP_WITHOUT_PRICE" or 1: Without amounts (typical OTP device) + // "TOTP_WITH_PRICE" or 2: With amounts (special-purpose OTP device) + // The "String" variants are supported @since protocol **v7**. + otp_algorithm: Integer | string; + + // Counter for counter-based OTP devices. + otp_ctr?: Integer; +} + +export interface OtpDevicePatchDetails { + // Human-readable description for the device. + otp_device_description: string; + + // A key encoded with RFC 3548 Base32. + // IMPORTANT: This is not using the typical + // Taler base32-crockford encoding. + // Instead it uses the RFC 3548 encoding to + // be compatible with the TOTP standard. + otp_key: string; + + // Algorithm for computing the POS confirmation. + otp_algorithm: Integer; + + // Counter for counter-based OTP devices. + otp_ctr?: Integer; +} + +export interface OtpDeviceSummaryResponse { + // Array of devices that are present in our backend. + otp_devices: OtpDeviceEntry[]; +} +export interface OtpDeviceEntry { + // Device identifier. + otp_device_id: string; + + // Human-readable description for the device. + device_description: string; +} + +export interface OtpDeviceDetails { + // Human-readable description for the device. + device_description: string; + + // Algorithm for computing the POS confirmation. + // + // Currently, the following numbers are defined: + // 0: None + // 1: TOTP without price + // 2: TOTP with price + otp_algorithm: Integer; + + // Counter for counter-based OTP devices. + otp_ctr?: Integer; + + // Current time for time-based OTP devices. + // Will match the faketime argument of the + // query if one was present, otherwise the current + // time at the backend. + // + // Available since protocol **v10**. + otp_timestamp: Integer; + + // Current OTP confirmation string of the device. + // Matches exactly the string that would be returned + // as part of a payment confirmation for the given + // amount and time (so may contain multiple OTP codes). + // + // If the otp_algorithm is time-based, the code is + // returned for the current time, or for the faketime + // if a TIMESTAMP query argument was provided by the client. + // + // When using OTP with counters, the counter is **NOT** + // increased merely because this endpoint created + // an OTP code (this is a GET request, after all!). + // + // If the otp_algorithm requires an amount, the + // amount argument must be specified in the + // query, otherwise the otp_code is not + // generated. + // + // This field is *optional* in the response, as it is + // only provided if we could compute it based on the + // otp_algorithm and matching client query arguments. + // + // Available since protocol **v10**. + otp_code?: string; +} +export interface TemplateAddDetails { + // Template ID to use. + template_id: string; + + // Human-readable description for the template. + template_description: string; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + + // Additional information in a separate template. + template_contract: TemplateContractDetails; + + // Key-value pairs matching a subset of the + // fields from template_contract that are + // user-editable defaults for this template. + // Since protocol **v13**. + editable_defaults?: TemplateContractDetailsDefaults; +} +export interface TemplateContractDetails { + // Human-readable summary for the template. + summary?: string; + + // Required currency for payments to the template. + // The user may specify any amount, but it must be + // in this currency. + // This parameter is optional and should not be present + // if "amount" is given. + currency?: string; + + // The price is imposed by the merchant and cannot be changed by the customer. + // This parameter is optional. + amount?: AmountString; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age: Integer; + + // The time the customer need to pay before his order will be deleted. + // It is deleted if the customer did not pay and if the duration is over. + pay_duration: RelativeTime; +} + +export interface TemplateContractDetailsDefaults { + summary?: string; + + currency?: string; + + /** + * Amount *or* a plain currency string. + */ + amount?: string; +} + +export interface TemplatePatchDetails { + // Human-readable description for the template. + template_description: string; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + + // Additional information in a separate template. + template_contract: TemplateContractDetails; + + // Key-value pairs matching a subset of the + // fields from template_contract that are + // user-editable defaults for this template. + // Since protocol **v13**. + editable_defaults?: TemplateContractDetailsDefaults; +} + +export interface TemplateSummaryResponse { + // List of templates that are present in our backend. + templates: TemplateEntry[]; +} + +export interface TemplateEntry { + // Template identifier, as found in the template. + template_id: string; + + // Human-readable description for the template. + template_description: string; +} + +export interface WalletTemplateDetails { + // Hard-coded information about the contrac terms + // for this template. + template_contract: TemplateContractDetails; + + // Key-value pairs matching a subset of the + // fields from template_contract that are + // user-editable defaults for this template. + // Since protocol **v13**. + editable_defaults?: TemplateContractDetailsDefaults; +} + +export interface TemplateDetails { + // Human-readable description for the template. + template_description: string; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; + + // Additional information in a separate template. + template_contract: TemplateContractDetails; + + // Key-value pairs matching a subset of the + // fields from template_contract that are + // user-editable defaults for this template. + // Since protocol **v13**. + editable_defaults?: TemplateContractDetailsDefaults; +} +export interface UsingTemplateDetails { + // Summary of the template + summary?: string; + + // The amount entered by the customer. + amount?: AmountString; +} + +export interface WebhookAddDetails { + // Webhook ID to use. + webhook_id: string; + + // The event of the webhook: why the webhook is used. + event_type: string; + + // URL of the webhook where the customer will be redirected. + url: string; + + // Method used by the webhook + http_method: string; + + // Header template of the webhook + header_template?: string; + + // Body template by the webhook + body_template?: string; +} + +export interface WebhookPatchDetails { + // The event of the webhook: why the webhook is used. + event_type: string; + + // URL of the webhook where the customer will be redirected. + url: string; + + // Method used by the webhook + http_method: string; + + // Header template of the webhook + header_template?: string; + + // Body template by the webhook + body_template?: string; +} + +export interface WebhookSummaryResponse { + // Return webhooks that are present in our backend. + webhooks: WebhookEntry[]; +} + +export interface WebhookEntry { + // Webhook identifier, as found in the webhook. + webhook_id: string; + + // The event of the webhook: why the webhook is used. + event_type: string; +} + +export interface WebhookDetails { + // The event of the webhook: why the webhook is used. + event_type: string; + + // URL of the webhook where the customer will be redirected. + url: string; + + // Method used by the webhook + http_method: string; + + // Header template of the webhook + header_template?: string; + + // Body template by the webhook + body_template?: string; +} + +export interface TokenFamilyCreateRequest { + // Identifier for the token family consisting of unreserved characters + // according to RFC 3986. + slug: string; + + // Human-readable name for the token family. + name: string; + + // Human-readable description for the token family. + description: string; + + // Optional map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // Start time of the token family's validity period. + // If not specified, merchant backend will use the current time. + valid_after?: Timestamp; + + // End time of the token family's validity period. + valid_before: Timestamp; + + // Validity duration of an issued token. + duration: RelativeTime; + + // Kind of the token family. + kind: TokenFamilyKind; +} + +export enum TokenFamilyKind { + Discount = "discount", + Subscription = "subscription", +} + +export interface TokenFamilyUpdateRequest { + // Human-readable name for the token family. + name: string; + + // Human-readable description for the token family. + description: string; + + // Optional map from IETF BCP 47 language tags to localized descriptions. + description_i18n: { [lang_tag: string]: string }; + + // Start time of the token family's validity period. + valid_after: Timestamp; + + // End time of the token family's validity period. + valid_before: Timestamp; + + // Validity duration of an issued token. + duration: RelativeTime; +} + +export interface TokenFamiliesList { + // All configured token families of this instance. + token_families: TokenFamilySummary[]; +} + +export interface TokenFamilySummary { + // Identifier for the token family consisting of unreserved characters + // according to RFC 3986. + slug: string; + + // Human-readable name for the token family. + name: string; + + // Start time of the token family's validity period. + valid_after: Timestamp; + + // End time of the token family's validity period. + valid_before: Timestamp; + + // Kind of the token family. + kind: TokenFamilyKind; +} + +export interface TokenFamilyDetails { + // Identifier for the token family consisting of unreserved characters + // according to RFC 3986. + slug: string; + + // Human-readable name for the token family. + name: string; + + // Human-readable description for the token family. + description: string; + + // Optional map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // Start time of the token family's validity period. + valid_after: Timestamp; + + // End time of the token family's validity period. + valid_before: Timestamp; + + // Validity duration of an issued token. + duration: RelativeTime; + + // Kind of the token family. + kind: TokenFamilyKind; + + // How many tokens have been issued for this family. + issued: Integer; + + // How many tokens have been redeemed for this family. + redeemed: Integer; +} +export interface ContractTerms { + // Human-readable description of the whole purchase. + summary: string; + + // Map from IETF BCP 47 language tags to localized summaries. + summary_i18n?: { [lang_tag: string]: string }; + + // Unique, free-form identifier for the proposal. + // Must be unique within a merchant instance. + // For merchants that do not store proposals in their DB + // before the customer paid for them, the order_id can be used + // by the frontend to restore a proposal from the information + // encoded in it (such as a short product identifier and timestamp). + order_id: string; + + // Total price for the transaction. + // The exchange will subtract deposit fees from that amount + // before transferring it to the merchant. + amount: AmountString; + + // URL where the same contract could be ordered again (if + // available). Returned also at the public order endpoint + // for people other than the actual buyer (hence public, + // in case order IDs are guessable). + public_reorder_url?: string; + + // URL that will show that the order was successful after + // it has been paid for. Optional. When POSTing to the + // merchant, the placeholder "${ORDER_ID}" will be + // replaced with the actual order ID (useful if the + // order ID is generated server-side and needs to be + // in the URL). + // Note that this placeholder can only be used once. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_url?: string; + + // Message shown to the customer after paying for the order. + // Either fulfillment_url or fulfillment_message must be specified. + fulfillment_message?: string; + + // Map from IETF BCP 47 language tags to localized fulfillment + // messages. + fulfillment_message_i18n?: { [lang_tag: string]: string }; + + // Maximum total deposit fee accepted by the merchant for this contract. + // Overrides defaults of the merchant instance. + max_fee: AmountString; + + // List of products that are part of the purchase (see Product). + products: Product[]; + + // Time when this contract was generated. + timestamp: Timestamp; + + // After this deadline has passed, no refunds will be accepted. + refund_deadline: Timestamp; + + // After this deadline, the merchant won't accept payments for the contract. + pay_deadline: Timestamp; + + // Transfer deadline for the exchange. Must be in the + // deposit permissions of coins used to pay for this order. + wire_transfer_deadline: Timestamp; + + // Merchant's public key used to sign this proposal; this information + // is typically added by the backend. Note that this can be an ephemeral key. + merchant_pub: EddsaPublicKey; + + // Base URL of the (public!) merchant backend API. + // Must be an absolute URL that ends with a slash. + merchant_base_url: string; + + // More info about the merchant, see below. + merchant: Merchant; + + // The hash of the merchant instance's wire details. + h_wire: HashCode; + + // Wire transfer method identifier for the wire method associated with h_wire. + // The wallet may only select exchanges via a matching auditor if the + // exchange also supports this wire method. + // The wire transfer fees must be added based on this wire transfer method. + wire_method: string; + + // Exchanges that the merchant accepts even if it does not accept any auditors that audit them. + exchanges: Exchange[]; + + // Delivery location for (all!) products. + delivery_location?: Location; + + // Time indicating when the order should be delivered. + // May be overwritten by individual products. + delivery_date?: Timestamp; + + // Nonce generated by the wallet and echoed by the merchant + // in this field when the proposal is generated. + nonce: string; + + // Specifies for how long the wallet should try to get an + // automatic refund for the purchase. If this field is + // present, the wallet should wait for a few seconds after + // the purchase and then automatically attempt to obtain + // a refund. The wallet should probe until "delay" + // after the payment was successful (i.e. via long polling + // or via explicit requests with exponential back-off). + // + // In particular, if the wallet is offline + // at that time, it MUST repeat the request until it gets + // one response from the merchant after the delay has expired. + // If the refund is granted, the wallet MUST automatically + // recover the payment. This is used in case a merchant + // knows that it might be unable to satisfy the contract and + // desires for the wallet to attempt to get the refund without any + // customer interaction. Note that it is NOT an error if the + // merchant does not grant a refund. + auto_refund?: RelativeTime; + + // Extra data that is only interpreted by the merchant frontend. + // Useful when the merchant needs to store extra information on a + // contract without storing it separately in their database. + extra?: any; + + // Minimum age the buyer must have (in years). Default is 0. + // This value is at least as large as the maximum over all + // minimum age requirements of the products in this contract. + // It might also be set independent of any product, due to + // legal requirements. + minimum_age?: Integer; +} + +export interface Product { + // Merchant-internal identifier for the product. + product_id?: string; + + // Human-readable product description. + description: string; + + // Map from IETF BCP 47 language tags to localized descriptions. + description_i18n?: { [lang_tag: string]: string }; + + // The number of units of the product to deliver to the customer. + quantity?: Integer; + + // Unit in which the product is measured (liters, kilograms, packages, etc.). + unit?: string; + + // The price of the product; this is the total price for quantity times unit of this product. + price?: AmountString; + + // An optional base64-encoded product image. + image?: ImageDataUrl; + + // A list of taxes paid by the merchant for this product. Can be empty. + taxes?: Tax[]; + + // Time indicating when this product should be delivered. + delivery_date?: Timestamp; +} + +export interface Tax { + // The name of the tax. + name: string; + + // Amount paid in tax. + tax: AmountString; +} +export interface Merchant { + // The merchant's legal name of business. + name: string; + + // Label for a location with the business address of the merchant. + email?: string; + + // Label for a location with the business address of the merchant. + website?: string; + + // An optional base64-encoded product image. + logo?: ImageDataUrl; + + // Label for a location with the business address of the merchant. + address?: Location; + + // Label for a location that denotes the jurisdiction for disputes. + // Some of the typical fields for a location (such as a street address) may be absent. + jurisdiction?: Location; +} +// Delivery location, loosely modeled as a subset of +// ISO20022's PostalAddress25. +export interface Location { + // Nation with its own government. + country?: string; + + // Identifies a subdivision of a country such as state, region, county. + country_subdivision?: string; + + // Identifies a subdivision within a country sub-division. + district?: string; + + // Name of a built-up area, with defined boundaries, and a local government. + town?: string; + + // Specific location name within the town. + town_location?: string; + + // Identifier consisting of a group of letters and/or numbers that + // is added to a postal address to assist the sorting of mail. + post_code?: string; + + // Name of a street or thoroughfare. + street?: string; + + // Name of the building or house. + building_name?: string; + + // Number that identifies the position of a building on a street. + building_number?: string; + + // Free-form address lines, should not exceed 7 elements. + address_lines?: string[]; +} +interface Auditor { + // Official name. + name: string; + + // Auditor's public key. + auditor_pub: EddsaPublicKey; + + // Base URL of the auditor. + url: string; +} +export interface Exchange { + // The exchange's base URL. + url: string; + + // How much would the merchant like to use this exchange. + // The wallet should use a suitable exchange with high + // priority. The following priority values are used, but + // it should be noted that they are NOT in any way normative. + // + // 0: likely it will not work (recently seen with account + // restriction that would be bad for this merchant) + // 512: merchant does not know, might be down (merchant + // did not yet get /wire response). + // 1024: good choice (recently confirmed working) + priority: Integer; + + // Master public key of the exchange. + master_pub: EddsaPublicKey; +} + +export interface MerchantReserveCreateConfirmation { + // Public key identifying the reserve. + reserve_pub: EddsaPublicKey; + + // Wire accounts of the exchange where to transfer the funds. + accounts: ExchangeWireAccount[]; +} + +export interface TemplateEditableDetails { + // Human-readable summary for the template. + summary?: string; + + // Required currency for payments to the template. + // The user may specify any amount, but it must be + // in this currency. + // This parameter is optional and should not be present + // if "amount" is given. + currency?: string; + + // The price is imposed by the merchant and cannot be changed by the customer. + // This parameter is optional. + amount?: AmountString; +} + +export interface MerchantTemplateContractDetails { + // Human-readable summary for the template. + summary?: string; + + // The price is imposed by the merchant and cannot be changed by the customer. + // This parameter is optional. + amount?: string; + + // Minimum age buyer must have (in years). Default is 0. + minimum_age: number; + + // The time the customer need to pay before his order will be deleted. + // It is deleted if the customer did not pay and if the duration is over. + pay_duration: TalerProtocolDuration; +} + +export interface MerchantTemplateAddDetails { + // Template ID to use. + template_id: string; + + // Human-readable description for the template. + template_description: string; + + // A base64-encoded image selected by the merchant. + // This parameter is optional. + // We are not sure about it. + image?: string; + + editable_defaults?: TemplateEditableDetails; + + // Additional information in a separate template. + template_contract: MerchantTemplateContractDetails; + + // OTP device ID. + // This parameter is optional. + otp_id?: string; +} + +const codecForExchangeConfigInfo = (): Codec<ExchangeConfigInfo> => + buildCodecForObject<ExchangeConfigInfo>() + .property("base_url", codecForString()) + .property("currency", codecForString()) + .property("master_pub", codecForString()) + .build("TalerMerchantApi.ExchangeConfigInfo"); + +export const codecForTalerMerchantConfigResponse = + (): Codec<TalerMerchantConfigResponse> => + buildCodecForObject<TalerMerchantConfigResponse>() + .property("name", codecForConstString("taler-merchant")) + .property("currency", codecForString()) + .property("version", codecForString()) + .property("currencies", codecForMap(codecForCurrencySpecificiation())) + .property("exchanges", codecForList(codecForExchangeConfigInfo())) + .build("TalerMerchantApi.VersionResponse"); + +export const codecForClaimResponse = (): Codec<ClaimResponse> => + buildCodecForObject<ClaimResponse>() + .property("contract_terms", codecForContractTerms()) + .property("sig", codecForString()) + .build("TalerMerchantApi.ClaimResponse"); + +export const codecForPaymentResponse = (): Codec<PaymentResponse> => + buildCodecForObject<PaymentResponse>() + .property("pos_confirmation", codecOptional(codecForString())) + .property("sig", codecForString()) + .build("TalerMerchantApi.PaymentResponse"); + +export const codecForStatusPaid = (): Codec<StatusPaid> => + buildCodecForObject<StatusPaid>() + .property("refund_amount", codecForAmountString()) + .property("refund_pending", codecForBoolean()) + .property("refund_taken", codecForAmountString()) + .property("refunded", codecForBoolean()) + .property("type", codecForConstString("paid")) + .build("TalerMerchantApi.StatusPaid"); + +export const codecForStatusGoto = (): Codec<StatusGotoResponse> => + buildCodecForObject<StatusGotoResponse>() + .property("public_reorder_url", codecForURLString()) + .property("type", codecForConstString("goto")) + .build("TalerMerchantApi.StatusGotoResponse"); + +export const codecForStatusStatusUnpaid = (): Codec<StatusUnpaidResponse> => + buildCodecForObject<StatusUnpaidResponse>() + .property("type", codecForConstString("unpaid")) + .property("already_paid_order_id", codecOptional(codecForString())) + .property("fulfillment_url", codecOptional(codecForString())) + .property("taler_pay_uri", codecForTalerUriString()) + .build("TalerMerchantApi.PaymentResponse"); + +export const codecForPaidRefundStatusResponse = + (): Codec<PaidRefundStatusResponse> => + buildCodecForObject<PaidRefundStatusResponse>() + .property("pos_confirmation", codecOptional(codecForString())) + .property("refunded", codecForBoolean()) + .build("TalerMerchantApi.PaidRefundStatusResponse"); + +export const codecForMerchantAbortPayRefundSuccessStatus = + (): Codec<MerchantAbortPayRefundSuccessStatus> => + buildCodecForObject<MerchantAbortPayRefundSuccessStatus>() + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("exchange_status", codecForConstNumber(200)) + .property("type", codecForConstString("success")) + .build("TalerMerchantApi.MerchantAbortPayRefundSuccessStatus"); + +export const codecForMerchantAbortPayRefundFailureStatus = + (): Codec<MerchantAbortPayRefundFailureStatus> => + buildCodecForObject<MerchantAbortPayRefundFailureStatus>() + .property("exchange_code", codecForNumber()) + .property("exchange_reply", codecForAny()) + .property("exchange_status", codecForNumber()) + .property("type", codecForConstString("failure")) + .build("TalerMerchantApi.MerchantAbortPayRefundFailureStatus"); + +export const codecForMerchantAbortPayRefundStatus = + (): Codec<MerchantAbortPayRefundStatus> => + buildCodecForUnion<MerchantAbortPayRefundStatus>() + .discriminateOn("type") + .alternative("success", codecForMerchantAbortPayRefundSuccessStatus()) + .alternative("failure", codecForMerchantAbortPayRefundFailureStatus()) + .build("TalerMerchantApi.MerchantAbortPayRefundStatus"); + +export const codecForAbortResponse = (): Codec<AbortResponse> => + buildCodecForObject<AbortResponse>() + .property("refunds", codecForList(codecForMerchantAbortPayRefundStatus())) + .build("TalerMerchantApi.AbortResponse"); + +export const codecForWalletRefundResponse = (): Codec<WalletRefundResponse> => + buildCodecForObject<WalletRefundResponse>() + .property("merchant_pub", codecForString()) + .property("refund_amount", codecForAmountString()) + .property("refunds", codecForList(codecForMerchantCoinRefundStatus())) + .build("TalerMerchantApi.AbortResponse"); + +export const codecForMerchantCoinRefundSuccessStatus = + (): Codec<MerchantCoinRefundSuccessStatus> => + buildCodecForObject<MerchantCoinRefundSuccessStatus>() + .property("type", codecForConstString("success")) + .property("coin_pub", codecForString()) + .property("exchange_status", codecForConstNumber(200)) + .property("exchange_sig", codecForString()) + .property("rtransaction_id", codecForNumber()) + .property("refund_amount", codecForAmountString()) + .property("exchange_pub", codecForString()) + .property("execution_time", codecForTimestamp) + .build("TalerMerchantApi.MerchantCoinRefundSuccessStatus"); + +export const codecForMerchantCoinRefundFailureStatus = + (): Codec<MerchantCoinRefundFailureStatus> => + buildCodecForObject<MerchantCoinRefundFailureStatus>() + .property("type", codecForConstString("failure")) + .property("coin_pub", codecForString()) + .property("exchange_status", codecForNumber()) + .property("rtransaction_id", codecForNumber()) + .property("refund_amount", codecForAmountString()) + .property("exchange_code", codecOptional(codecForNumber())) + .property("exchange_reply", codecOptional(codecForAny())) + .property("execution_time", codecForTimestamp) + .build("TalerMerchantApi.MerchantCoinRefundFailureStatus"); + +export const codecForMerchantCoinRefundStatus = + (): Codec<MerchantCoinRefundStatus> => + buildCodecForUnion<MerchantCoinRefundStatus>() + .discriminateOn("type") + .alternative("success", codecForMerchantCoinRefundSuccessStatus()) + .alternative("failure", codecForMerchantCoinRefundFailureStatus()) + .build("TalerMerchantApi.MerchantCoinRefundStatus"); + +export const codecForQueryInstancesResponse = + (): Codec<QueryInstancesResponse> => + buildCodecForObject<QueryInstancesResponse>() + .property("name", codecForString()) + .property("user_type", codecForString()) + .property("email", codecOptional(codecForString())) + .property("website", codecOptional(codecForString())) + .property("logo", codecOptional(codecForString())) + .property("merchant_pub", codecForString()) + .property("address", codecForLocation()) + .property("jurisdiction", codecForLocation()) + .property("use_stefan", codecForBoolean()) + .property("default_wire_transfer_delay", codecForDuration) + .property("default_pay_delay", codecForDuration) + .property( + "auth", + buildCodecForObject<{ + method: "external" | "token"; + }>() + .property( + "method", + codecForEither( + codecForConstString("token"), + codecForConstString("external"), + ), + ) + .build("TalerMerchantApi.QueryInstancesResponse.auth"), + ) + .build("TalerMerchantApi.QueryInstancesResponse"); + +export const codecForAccountKycRedirects = (): Codec<AccountKycRedirects> => + buildCodecForObject<AccountKycRedirects>() + .property( + "pending_kycs", + codecForList(codecForMerchantAccountKycRedirect()), + ) + .property("timeout_kycs", codecForList(codecForExchangeKycTimeout())) + + .build("TalerMerchantApi.AccountKycRedirects"); + +export const codecForMerchantAccountKycRedirect = + (): Codec<MerchantAccountKycRedirect> => + buildCodecForObject<MerchantAccountKycRedirect>() + .property("kyc_url", codecForURLString()) + .property("aml_status", codecForNumber()) + .property("exchange_url", codecForURLString()) + .property("payto_uri", codecForPaytoString()) + .build("TalerMerchantApi.MerchantAccountKycRedirect"); + +export const codecForExchangeKycTimeout = (): Codec<ExchangeKycTimeout> => + buildCodecForObject<ExchangeKycTimeout>() + .property("exchange_url", codecForURLString()) + .property("exchange_code", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .build("TalerMerchantApi.ExchangeKycTimeout"); + +export const codecForAccountAddResponse = (): Codec<AccountAddResponse> => + buildCodecForObject<AccountAddResponse>() + .property("h_wire", codecForString()) + .property("salt", codecForString()) + .build("TalerMerchantApi.AccountAddResponse"); + +export const codecForAccountsSummaryResponse = + (): Codec<AccountsSummaryResponse> => + buildCodecForObject<AccountsSummaryResponse>() + .property("accounts", codecForList(codecForBankAccountEntry())) + .build("TalerMerchantApi.AccountsSummaryResponse"); + +export const codecForBankAccountEntry = (): Codec<BankAccountEntry> => + buildCodecForObject<BankAccountEntry>() + .property("payto_uri", codecForPaytoString()) + .property("h_wire", codecForString()) + .property("active", codecOptional(codecForBoolean())) + .build("TalerMerchantApi.BankAccountEntry"); + +export const codecForBankAccountDetail = (): Codec<BankAccountDetail> => + buildCodecForObject<BankAccountDetail>() + .property("payto_uri", codecForPaytoString()) + .property("h_wire", codecForString()) + .property("salt", codecForString()) + .property("credit_facade_url", codecOptional(codecForURLString())) + .property("active", codecOptional(codecForBoolean())) + .build("TalerMerchantApi.BankAccountEntry"); + +export const codecForCategoryListResponse = (): Codec<CategoryListResponse> => + buildCodecForObject<CategoryListResponse>() + .property("categories", codecForList(codecForCategoryListEntry())) + .build("TalerMerchantApi.CategoryListResponse"); + +export const codecForCategoryListEntry = (): Codec<CategoryListEntry> => + buildCodecForObject<CategoryListEntry>() + .property("category_id", codecForNumber()) + .property("name", codecForString()) + .property("name_i18n", codecForInternationalizedString()) + .property("product_count", codecForNumber()) + .build("TalerMerchantApi.CategoryListEntry"); + +export const codecForCategoryProductList = (): Codec<CategoryProductList> => + buildCodecForObject<CategoryProductList>() + .property("name", codecForString()) + .property("name_i18n", codecForInternationalizedString()) + .property("products", codecForList(codecForProductSummary())) + .build("TalerMerchantApi.CategoryProductList"); + +export const codecForProductSummary = (): Codec<ProductSummary> => + buildCodecForObject<ProductSummary>() + .property("product_id", codecForString()) + // .property("description", codecForString()) + // .property("description_i18n", codecForInternationalizedString()) + .build("TalerMerchantApi.ProductSummary"); + +export const codecForInventorySummaryResponse = + (): Codec<InventorySummaryResponse> => + buildCodecForObject<InventorySummaryResponse>() + .property("products", codecForList(codecForInventoryEntry())) + .build("TalerMerchantApi.InventorySummaryResponse"); + +export const codecForInventoryEntry = (): Codec<InventoryEntry> => + buildCodecForObject<InventoryEntry>() + .property("product_id", codecForString()) + .property("product_serial", codecForNumber()) + .build("TalerMerchantApi.InventoryEntry"); + +export const codecForMerchantPosProductDetail = + (): Codec<MerchantPosProductDetail> => + buildCodecForObject<MerchantPosProductDetail>() + .property("product_serial", codecForNumber()) + .property("product_id", codecOptional(codecForString())) + .property("categories", codecForList(codecForNumber())) + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) + .property("unit", codecForString()) + .property("price", codecForAmountString()) + .property("image", codecForString()) + .property("taxes", codecOptional(codecForList(codecForTax()))) + .property("total_stock", codecForNumber()) + .property("minimum_age", codecOptional(codecForNumber())) + .build("TalerMerchantApi.MerchantPosProductDetail"); + +export const codecForMerchantCategory = (): Codec<MerchantCategory> => + buildCodecForObject<MerchantCategory>() + .property("id", codecForNumber()) + .property("name", codecForString()) + .property("name_i18n", codecForInternationalizedString()) + .build("TalerMerchantApi.MerchantCategory"); + +export const codecForFullInventoryDetailsResponse = + (): Codec<FullInventoryDetailsResponse> => + buildCodecForObject<FullInventoryDetailsResponse>() + .property("categories", codecForList(codecForMerchantCategory())) + .property("products", codecForList(codecForMerchantPosProductDetail())) + .build("TalerMerchantApi.FullInventoryDetailsResponse"); + +export const codecForProductDetail = (): Codec<ProductDetail> => + buildCodecForObject<ProductDetail>() + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) + .property("unit", codecForString()) + .property("price", codecForAmountString()) + .property("image", codecForString()) + .property("categories", codecForList(codecForNumber())) + .property("taxes", codecOptional(codecForList(codecForTax()))) + .property("address", codecOptional(codecForLocation())) + .property("next_restock", codecOptional(codecForTimestamp)) + .property("total_stock", codecForNumber()) + .property("total_sold", codecForNumber()) + .property("total_lost", codecForNumber()) + .property("minimum_age", codecOptional(codecForNumber())) + .build("TalerMerchantApi.ProductDetail"); + +export const codecForTax = (): Codec<Tax> => + buildCodecForObject<Tax>() + .property("name", codecForString()) + .property("tax", codecForAmountString()) + .build("TalerMerchantApi.Tax"); + +export const codecForPostOrderResponse = (): Codec<PostOrderResponse> => + buildCodecForObject<PostOrderResponse>() + .property("order_id", codecForString()) + .property("token", codecOptional(codecForString())) + .build("TalerMerchantApi.PostOrderResponse"); + +export const codecForOutOfStockResponse = (): Codec<OutOfStockResponse> => + buildCodecForObject<OutOfStockResponse>() + .property("product_id", codecForString()) + .property("available_quantity", codecForNumber()) + .property("requested_quantity", codecForNumber()) + .property("restock_expected", codecForTimestamp) + .build("TalerMerchantApi.OutOfStockResponse"); + +export const codecForOrderHistory = (): Codec<OrderHistory> => + buildCodecForObject<OrderHistory>() + .property("orders", codecForList(codecForOrderHistoryEntry())) + .build("TalerMerchantApi.OrderHistory"); + +export const codecForOrderHistoryEntry = (): Codec<OrderHistoryEntry> => + buildCodecForObject<OrderHistoryEntry>() + .property("order_id", codecForString()) + .property("row_id", codecForNumber()) + .property("timestamp", codecForTimestamp) + .property("amount", codecForAmountString()) + .property("summary", codecForString()) + .property("refundable", codecForBoolean()) + .property("paid", codecForBoolean()) + .build("TalerMerchantApi.OrderHistoryEntry"); + +export const codecForMerchant = (): Codec<Merchant> => + buildCodecForObject<Merchant>() + .property("name", codecForString()) + .property("email", codecOptional(codecForString())) + .property("logo", codecOptional(codecForString())) + .property("website", codecOptional(codecForString())) + .property("address", codecOptional(codecForLocation())) + .property("jurisdiction", codecOptional(codecForLocation())) + .build("TalerMerchantApi.MerchantInfo"); + +export const codecForExchange = (): Codec<Exchange> => + buildCodecForObject<Exchange>() + .property("master_pub", codecForString()) + .property("priority", codecForNumber()) + .property("url", codecForString()) + .build("TalerMerchantApi.Exchange"); + +export const codecForContractTerms = (): Codec<ContractTerms> => + buildCodecForObject<ContractTerms>() + .property("order_id", codecForString()) + .property("fulfillment_url", codecOptional(codecForString())) + .property("fulfillment_message", codecOptional(codecForString())) + .property( + "fulfillment_message_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("merchant_base_url", codecForString()) + .property("h_wire", codecForString()) + .property("auto_refund", codecOptional(codecForDuration)) + .property("wire_method", codecForString()) + .property("summary", codecForString()) + .property("summary_i18n", codecOptional(codecForInternationalizedString())) + .property("nonce", codecForString()) + .property("amount", codecForAmountString()) + .property("pay_deadline", codecForTimestamp) + .property("refund_deadline", codecForTimestamp) + .property("wire_transfer_deadline", codecForTimestamp) + .property("timestamp", codecForTimestamp) + .property("delivery_location", codecOptional(codecForLocation())) + .property("delivery_date", codecOptional(codecForTimestamp)) + .property("max_fee", codecForAmountString()) + .property("merchant", codecForMerchant()) + .property("merchant_pub", codecForString()) + .property("exchanges", codecForList(codecForExchange())) + .property("products", codecForList(codecForProduct())) + .property("extra", codecForAny()) + .build("TalerMerchantApi.ContractTerms"); + +export const codecForProduct = (): Codec<Product> => + buildCodecForObject<Product>() + .property("product_id", codecOptional(codecForString())) + .property("description", codecForString()) + .property( + "description_i18n", + codecOptional(codecForInternationalizedString()), + ) + .property("quantity", codecOptional(codecForNumber())) + .property("unit", codecOptional(codecForString())) + .property("price", codecOptional(codecForAmountString())) + .property("image", codecOptional(codecForString())) + .property("taxes", codecOptional(codecForList(codecForTax()))) + .property("delivery_date", codecOptional(codecForTimestamp)) + .build("TalerMerchantApi.Product"); + +export const codecForCheckPaymentPaidResponse = + (): Codec<CheckPaymentPaidResponse> => + buildCodecForObject<CheckPaymentPaidResponse>() + .property("order_status", codecForConstString("paid")) + .property("refunded", codecForBoolean()) + .property("refund_pending", codecForBoolean()) + .property("wired", codecForBoolean()) + .property("deposit_total", codecForAmountString()) + .property("exchange_code", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .property("refund_amount", codecForAmountString()) + .property("contract_terms", codecForContractTerms()) + .property("wire_reports", codecForList(codecForTransactionWireReport())) + .property("wire_details", codecForList(codecForTransactionWireTransfer())) + .property("refund_details", codecForList(codecForRefundDetails())) + .property("order_status_url", codecForURLString()) + .build("TalerMerchantApi.CheckPaymentPaidResponse"); + +export const codecForCheckPaymentUnpaidResponse = + (): Codec<CheckPaymentUnpaidResponse> => + buildCodecForObject<CheckPaymentUnpaidResponse>() + .property("order_status", codecForConstString("unpaid")) + .property("taler_pay_uri", codecForTalerUriString()) + .property("creation_time", codecForTimestamp) + .property("summary", codecForString()) + .property("total_amount", codecForAmountString()) + .property("already_paid_order_id", codecOptional(codecForString())) + .property("already_paid_fulfillment_url", codecOptional(codecForString())) + .property("order_status_url", codecForString()) + .build("TalerMerchantApi.CheckPaymentPaidResponse"); + +export const codecForCheckPaymentClaimedResponse = + (): Codec<CheckPaymentClaimedResponse> => + buildCodecForObject<CheckPaymentClaimedResponse>() + .property("order_status", codecForConstString("claimed")) + .property("contract_terms", codecForContractTerms()) + .build("TalerMerchantApi.CheckPaymentClaimedResponse"); + +export const codecForMerchantOrderPrivateStatusResponse = + (): Codec<MerchantOrderStatusResponse> => + buildCodecForUnion<MerchantOrderStatusResponse>() + .discriminateOn("order_status") + .alternative("paid", codecForCheckPaymentPaidResponse()) + .alternative("unpaid", codecForCheckPaymentUnpaidResponse()) + .alternative("claimed", codecForCheckPaymentClaimedResponse()) + .build("TalerMerchantApi.MerchantOrderStatusResponse"); + +export const codecForRefundDetails = (): Codec<RefundDetails> => + buildCodecForObject<RefundDetails>() + .property("reason", codecForString()) + .property("pending", codecForBoolean()) + .property("timestamp", codecForTimestamp) + .property("amount", codecForAmountString()) + .build("TalerMerchantApi.RefundDetails"); + +export const codecForTransactionWireTransfer = + (): Codec<TransactionWireTransfer> => + buildCodecForObject<TransactionWireTransfer>() + .property("exchange_url", codecForURLString()) + .property("wtid", codecForString()) + .property("execution_time", codecForTimestamp) + .property("amount", codecForAmountString()) + .property("confirmed", codecForBoolean()) + .build("TalerMerchantApi.TransactionWireTransfer"); + +export const codecForTransactionWireReport = (): Codec<TransactionWireReport> => + buildCodecForObject<TransactionWireReport>() + .property("code", codecForNumber()) + .property("hint", codecForString()) + .property("exchange_code", codecForNumber()) + .property("exchange_http_status", codecForNumber()) + .property("coin_pub", codecForString()) + .build("TalerMerchantApi.TransactionWireReport"); + +export const codecForMerchantRefundResponse = + (): Codec<MerchantRefundResponse> => + buildCodecForObject<MerchantRefundResponse>() + .property("taler_refund_uri", codecForTalerUriString()) + .property("h_contract", codecForString()) + .build("TalerMerchantApi.MerchantRefundResponse"); + +export const codecForTansferList = (): Codec<TransferList> => + buildCodecForObject<TransferList>() + .property("transfers", codecForList(codecForTransferDetails())) + .build("TalerMerchantApi.TransferList"); + +export const codecForTransferDetails = (): Codec<TransferDetails> => + buildCodecForObject<TransferDetails>() + .property("credit_amount", codecForAmountString()) + .property("wtid", codecForString()) + .property("payto_uri", codecForPaytoString()) + .property("exchange_url", codecForURLString()) + .property("transfer_serial_id", codecForNumber()) + .property("execution_time", codecOptional(codecForTimestamp)) + .property("verified", codecOptional(codecForBoolean())) + .property("confirmed", codecOptional(codecForBoolean())) + .build("TalerMerchantApi.TransferDetails"); + +export const codecForOtpDeviceSummaryResponse = + (): Codec<OtpDeviceSummaryResponse> => + buildCodecForObject<OtpDeviceSummaryResponse>() + .property("otp_devices", codecForList(codecForOtpDeviceEntry())) + .build("TalerMerchantApi.OtpDeviceSummaryResponse"); + +export const codecForOtpDeviceEntry = (): Codec<OtpDeviceEntry> => + buildCodecForObject<OtpDeviceEntry>() + .property("otp_device_id", codecForString()) + .property("device_description", codecForString()) + .build("TalerMerchantApi.OtpDeviceEntry"); + +export const codecForOtpDeviceDetails = (): Codec<OtpDeviceDetails> => + buildCodecForObject<OtpDeviceDetails>() + .property("device_description", codecForString()) + .property("otp_algorithm", codecForNumber()) + .property("otp_ctr", codecOptional(codecForNumber())) + .property("otp_timestamp", codecForNumber()) + .property("otp_code", codecOptional(codecForString())) + .build("TalerMerchantApi.OtpDeviceDetails"); + +export const codecForTemplateSummaryResponse = + (): Codec<TemplateSummaryResponse> => + buildCodecForObject<TemplateSummaryResponse>() + .property("templates", codecForList(codecForTemplateEntry())) + .build("TalerMerchantApi.TemplateSummaryResponse"); + +export const codecForTemplateEntry = (): Codec<TemplateEntry> => + buildCodecForObject<TemplateEntry>() + .property("template_id", codecForString()) + .property("template_description", codecForString()) + .build("TalerMerchantApi.TemplateEntry"); + +export const codecForTemplateDetails = (): Codec<TemplateDetails> => + buildCodecForObject<TemplateDetails>() + .property("template_description", codecForString()) + .property("otp_id", codecOptional(codecForString())) + .property("template_contract", codecForTemplateContractDetails()) + .property( + "editable_defaults", + codecOptional(codecForTemplateContractDetailsDefaults()), + ) + .build("TalerMerchantApi.TemplateDetails"); + +export const codecForTemplateContractDetails = + (): Codec<TemplateContractDetails> => + buildCodecForObject<TemplateContractDetails>() + .property("summary", codecOptional(codecForString())) + .property("currency", codecOptional(codecForString())) + .property("amount", codecOptional(codecForAmountString())) + .property("minimum_age", codecForNumber()) + .property("pay_duration", codecForDuration) + .build("TalerMerchantApi.TemplateContractDetails"); + +export const codecForTemplateContractDetailsDefaults = + (): Codec<TemplateContractDetailsDefaults> => + buildCodecForObject<TemplateContractDetailsDefaults>() + .property("summary", codecOptional(codecForString())) + .property("currency", codecOptional(codecForString())) + .property("amount", codecOptional(codecForAmountString())) + .build("TalerMerchantApi.TemplateContractDetailsDefaults"); + +export const codecForWalletTemplateDetails = (): Codec<WalletTemplateDetails> => + buildCodecForObject<WalletTemplateDetails>() + .property("template_contract", codecForTemplateContractDetails()) + .property( + "editable_defaults", + codecOptional(codecForTemplateContractDetailsDefaults()), + ) + .build("TalerMerchantApi.WalletTemplateDetails"); + +export const codecForWebhookSummaryResponse = + (): Codec<WebhookSummaryResponse> => + buildCodecForObject<WebhookSummaryResponse>() + .property("webhooks", codecForList(codecForWebhookEntry())) + .build("TalerMerchantApi.WebhookSummaryResponse"); + +export const codecForWebhookEntry = (): Codec<WebhookEntry> => + buildCodecForObject<WebhookEntry>() + .property("webhook_id", codecForString()) + .property("event_type", codecForString()) + .build("TalerMerchantApi.WebhookEntry"); + +export const codecForWebhookDetails = (): Codec<WebhookDetails> => + buildCodecForObject<WebhookDetails>() + .property("event_type", codecForString()) + .property("url", codecForString()) + .property("http_method", codecForString()) + .property("header_template", codecOptional(codecForString())) + .property("body_template", codecOptional(codecForString())) + .build("TalerMerchantApi.WebhookDetails"); + +export const codecForTokenFamilyKind = (): Codec<TokenFamilyKind> => + codecForEither( + codecForConstString("discount"), + codecForConstString("subscription"), + ) as any; //FIXME: create a codecForEnum +export const codecForTokenFamilyDetails = (): Codec<TokenFamilyDetails> => + buildCodecForObject<TokenFamilyDetails>() + .property("slug", codecForString()) + .property("name", codecForString()) + .property("description", codecForString()) + .property("description_i18n", codecForInternationalizedString()) + .property("valid_after", codecForTimestamp) + .property("valid_before", codecForTimestamp) + .property("duration", codecForDuration) + .property("kind", codecForTokenFamilyKind()) + .property("issued", codecForNumber()) + .property("redeemed", codecForNumber()) + .build("TalerMerchantApi.TokenFamilyDetails"); + +export const codecForTokenFamiliesList = (): Codec<TokenFamiliesList> => + buildCodecForObject<TokenFamiliesList>() + .property("token_families", codecForList(codecForTokenFamilySummary())) + .build("TalerMerchantApi.TokenFamiliesList"); + +export const codecForTokenFamilySummary = (): Codec<TokenFamilySummary> => + buildCodecForObject<TokenFamilySummary>() + .property("slug", codecForString()) + .property("name", codecForString()) + .property("valid_after", codecForTimestamp) + .property("valid_before", codecForTimestamp) + .property("kind", codecForTokenFamilyKind()) + .build("TalerMerchantApi.TokenFamilySummary"); + +export const codecForInstancesResponse = (): Codec<InstancesResponse> => + buildCodecForObject<InstancesResponse>() + .property("instances", codecForList(codecForInstance())) + .build("TalerMerchantApi.InstancesResponse"); + +export const codecForInstance = (): Codec<Instance> => + buildCodecForObject<Instance>() + .property("name", codecForString()) + .property("user_type", codecForString()) + .property("website", codecOptional(codecForString())) + .property("logo", codecOptional(codecForString())) + .property("id", codecForString()) + .property("merchant_pub", codecForString()) + .property("payment_targets", codecForList(codecForString())) + .property("deleted", codecForBoolean()) + .build("TalerMerchantApi.Instance"); + +export const codecForTemplateEditableDetails = + (): Codec<TemplateEditableDetails> => + buildCodecForObject<TemplateEditableDetails>() + .property("summary", codecOptional(codecForString())) + .property("currency", codecOptional(codecForString())) + .property("amount", codecOptional(codecForAmountString())) + .build("TemplateEditableDetails"); + +export const codecForMerchantReserveCreateConfirmation = + (): Codec<MerchantReserveCreateConfirmation> => + buildCodecForObject<MerchantReserveCreateConfirmation>() + .property("accounts", codecForList(codecForExchangeWireAccount())) + .property("reserve_pub", codecForString()) + .build("MerchantReserveCreateConfirmation"); diff --git a/packages/taler-util/src/types-taler-revenue.ts b/packages/taler-util/src/types-taler-revenue.ts new file mode 100644 index 000000000..772871d19 --- /dev/null +++ b/packages/taler-util/src/types-taler-revenue.ts @@ -0,0 +1,95 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { codecForAmountString } from "./amounts.js"; +import { Codec, buildCodecForObject, codecForConstString, codecForList, codecForNumber, codecForString, codecOptional } from "./codec.js"; +import { codecForPaytoString } from "./payto.js"; +import { codecForTimestamp } from "./time.js"; +import { AmountString, SafeUint64, Timestamp } from "./types-taler-common.js"; + +export interface RevenueConfig { + // Name of the API. + name: "taler-revenue"; + + // 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 gateway. + currency: string; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since v0, may become mandatory in the future. + implementation?: string; +} + +export interface RevenueIncomingHistory { + // Array of incoming transactions. + incoming_transactions: RevenueIncomingBankTransaction[]; + + // Payto URI to identify the receiver of funds. + // Credit account is shared by all incoming transactions + // as per the nature of the request. + credit_account: string; +} + +export interface RevenueIncomingBankTransaction { + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: AmountString; + + // Payto URI to identify the sender of funds. + debit_account: string; + + // The wire transfer subject. + subject: string; +} + +export const codecForRevenueConfig = (): Codec<RevenueConfig> => + buildCodecForObject<RevenueConfig>() + .property("name", codecForConstString("taler-revenue")) + .property("version", codecForString()) + .property("currency", codecForString()) + .property("implementation", codecOptional(codecForString())) + .build("TalerRevenueApi.RevenueConfig"); + +export const codecForRevenueIncomingHistory = + (): Codec<RevenueIncomingHistory> => + buildCodecForObject<RevenueIncomingHistory>() + .property("credit_account", codecForPaytoString()) + .property( + "incoming_transactions", + codecForList(codecForRevenueIncomingBankTransaction()), + ) + .build("TalerRevenueApi.MerchantIncomingHistory"); + +export const codecForRevenueIncomingBankTransaction = + (): Codec<RevenueIncomingBankTransaction> => + buildCodecForObject<RevenueIncomingBankTransaction>() + .property("amount", codecForAmountString()) + .property("date", codecForTimestamp) + .property("debit_account", codecForPaytoString()) + .property("row_id", codecForNumber()) + .property("subject", codecForString()) + .build("TalerRevenueApi.RevenueIncomingBankTransaction"); diff --git a/packages/taler-util/src/backup-types.ts b/packages/taler-util/src/types-taler-sync.ts index 8c38b70a6..bb0f4958f 100644 --- a/packages/taler-util/src/backup-types.ts +++ b/packages/taler-util/src/types-taler-sync.ts @@ -1,20 +1,22 @@ /* This file is part of GNU Taler - (C) 2020 Taler Systems S.A. + (C) 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 + terms of the GNU Affero 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 + You should have received a copy of the GNU Affero General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later */ -import { AmountString } from "./taler-types.js"; +import { AmountString } from "./types-taler-common.js"; export interface BackupRecovery { walletRootPriv: string; diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/types-taler-wallet-transactions.ts index b4e2738ee..018c3a041 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/types-taler-wallet-transactions.ts @@ -1,17 +1,19 @@ /* This file is part of GNU Taler - (C) 2019 Taler Systems S.A. + (C) 2019-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 + terms of the GNU Affero 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 + You should have received a copy of the GNU Affero General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later */ /** @@ -36,13 +38,16 @@ import { codecOptional, } from "./codec.js"; import { + TalerPreciseTimestamp, + TalerProtocolDuration, + TalerProtocolTimestamp, +} from "./time.js"; +import { AmountString, InternationalizedString, - MerchantInfo, codecForInternationalizedString, - codecForMerchantInfo, -} from "./taler-types.js"; -import { TalerPreciseTimestamp, TalerProtocolTimestamp } from "./time.js"; +} from "./types-taler-common.js"; +import { MerchantInfo, codecForMerchantInfo } from "./types-taler-merchant.js"; import { RefreshReason, ScopeInfo, @@ -51,7 +56,7 @@ import { TransactionStateFilter, WithdrawalExchangeAccountDetails, codecForScopeInfo, -} from "./wallet-types.js"; +} from "./types-taler-wallet.js"; export interface TransactionsRequest { /** @@ -122,8 +127,8 @@ export enum TransactionMinorState { Unknown = "unknown", Deposit = "deposit", KycRequired = "kyc", - AmlRequired = "aml", MergeKycRequired = "merge-kyc", + BalanceKycRequired = "balance-kyc", Track = "track", SubmitPayment = "submit-payment", RebindSession = "rebind-session", @@ -186,6 +191,11 @@ export interface TransactionCommon { timestamp: TalerPreciseTimestamp; /** + * Scope of this tx + */ + scopes: ScopeInfo[]; + + /** * Transaction state, as per DD37. */ txState: TransactionState; @@ -212,6 +222,16 @@ export interface TransactionCommon { * have the location where the user need to go to complete KYC information. */ kycUrl?: string; + + /** + * KYC payto hash. Useful for testing, not so useful for UIs. + */ + kycPaytoHash?: string; + + /** + * KYC access token. Useful for testing, not so useful for UIs. + */ + kycAccessToken?: string; } export type Transaction = @@ -273,6 +293,12 @@ interface WithdrawalDetailsForManualTransfer { * Is the reserve ready for withdrawal? */ reserveIsReady: boolean; + + /** + * How long does the exchange wait to transfer back funds from a + * reserve? + */ + reserveClosingDelay: TalerProtocolDuration; } interface WithdrawalDetailsForTalerBankIntegrationApi { @@ -739,16 +765,6 @@ export const codecForTransactionByIdRequest = .property("transactionId", codecForString()) .build("TransactionByIdRequest"); -export interface WithdrawalTransactionByURIRequest { - talerWithdrawUri: string; -} - -export const codecForWithdrawalTransactionByURIRequest = - (): Codec<WithdrawalTransactionByURIRequest> => - buildCodecForObject<WithdrawalTransactionByURIRequest>() - .property("talerWithdrawUri", codecForString()) - .build("WithdrawalTransactionByURIRequest"); - export const codecForTransactionsRequest = (): Codec<TransactionsRequest> => buildCodecForObject<TransactionsRequest>() .property("currency", codecOptional(codecForString())) diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/types-taler-wallet.ts index ec401f3f6..31ade3d3d 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/types-taler-wallet.ts @@ -1,17 +1,19 @@ /* This file is part of GNU Taler - (C) 2015-2020 Taler Systems SA + (C) 2019-2024 Taler Systems S.A. - 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 + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. - TALER is distributed in the hope that it will be useful, but WITHOUT ANY + 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 - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + You should have received a copy of the GNU Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later */ /** @@ -28,7 +30,6 @@ * Imports. */ import { AmountJson, codecForAmountString } from "./amounts.js"; -import { BackupRecovery } from "./backup-types.js"; import { Codec, Context, @@ -47,7 +48,10 @@ import { renderContext, } from "./codec.js"; import { + AmountString, CurrencySpecification, + EddsaPrivateKeyString, + InternationalizedString, TalerMerchantApi, TemplateParams, WithdrawalOperationStatus, @@ -59,38 +63,38 @@ import { QrCodeSpec } from "./qr.js"; import { AgeCommitmentProof } from "./taler-crypto.js"; import { TalerErrorCode } from "./taler-error-codes.js"; import { + AbsoluteTime, + TalerPreciseTimestamp, + TalerProtocolDuration, + TalerProtocolTimestamp, + codecForAbsoluteTime, + codecForPreciseTimestamp, + codecForTimestamp, +} from "./time.js"; +import { AccountRestriction, - AmountString, AuditorDenomSig, CoinEnvelope, DenomKeyType, DenominationPubKey, - EddsaPrivateKeyString, ExchangeAuditor, ExchangeWireAccount, - InternationalizedString, - MerchantContractTerms, - MerchantInfo, PeerContractTerms, UnblindedSignature, codecForExchangeWireAccount, - codecForMerchantContractTerms, codecForPeerContractTerms, -} from "./taler-types.js"; +} from "./types-taler-exchange.js"; import { - AbsoluteTime, - TalerPreciseTimestamp, - TalerProtocolDuration, - TalerProtocolTimestamp, - codecForAbsoluteTime, - codecForPreciseTimestamp, - codecForTimestamp, -} from "./time.js"; + MerchantContractTerms, + MerchantInfo, + codecForMerchantContractTerms, +} from "./types-taler-merchant.js"; +import { BackupRecovery } from "./types-taler-sync.js"; import { OrderShortInfo, TransactionState, TransactionType, -} from "./transactions-types.js"; +} from "./types-taler-wallet-transactions.js"; /** * Identifier for a transaction in the wallet. @@ -574,6 +578,47 @@ export type ScopeInfoAuditor = { export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; /** + * Shorter version of stringifyScopeInfo + * + * Format must be stable as it's used in the database. + */ +export function stringifyScopeInfoShort(si: ScopeInfo): string { + switch (si.type) { + case ScopeType.Global: + return `${si.currency}`; + case ScopeType.Exchange: + return `${si.currency}/${encodeURIComponent(si.url)}`; + case ScopeType.Auditor: + return `${si.currency}:${encodeURIComponent(si.url)}`; + } +} +export function parseScopeInfoShort(si: string): ScopeInfo | undefined { + const indexOfColon = si.indexOf(":"); + const indexOfSlash = si.indexOf("/"); + if (indexOfColon === -1 && indexOfSlash === -1) { + return { + type: ScopeType.Global, + currency: si, + }; + } + if (indexOfColon > 0) { + return { + type: ScopeType.Auditor, + currency: si.substring(0, indexOfColon), + url: decodeURIComponent(si.substring(indexOfColon + 1)), + }; + } + if (indexOfSlash > 0) { + return { + type: ScopeType.Exchange, + currency: si.substring(0, indexOfSlash), + url: decodeURIComponent(si.substring(indexOfSlash + 1)), + }; + } + return undefined; +} + +/** * Encode scope info as a string. * * Format must be stable as it's used in the database. @@ -581,7 +626,7 @@ export type ScopeInfo = ScopeInfoGlobal | ScopeInfoExchange | ScopeInfoAuditor; export function stringifyScopeInfo(si: ScopeInfo): string { switch (si.type) { case ScopeType.Global: - return `taler-si:global/${si.currency}}`; + return `taler-si:global/${si.currency}`; case ScopeType.Auditor: return `taler-si:auditor/${si.currency}/${encodeURIComponent(si.url)}`; case ScopeType.Exchange: @@ -1397,6 +1442,19 @@ export enum ExchangeUpdateStatus { UnavailableUpdate = "unavailable-update", Ready = "ready", ReadyUpdate = "ready-update", + OutdatedUpdate = "outdated-update", +} + +export enum ExchangeWalletKycStatus { + Done = "done", + /** + * Wallet needs to request KYC status. + */ + LegiInit = "legi-init", + /** + * User requires KYC or AML. + */ + Legi = "legi", } export interface OperationErrorInfo { @@ -1420,6 +1478,11 @@ export interface ExchangeListItem { exchangeUpdateStatus: ExchangeUpdateStatus; ageRestrictionOptions: number[]; + walletKycStatus?: ExchangeWalletKycStatus; + walletKycReservePub?: string; + walletKycAccessToken?: string; + walletKycUrl?: string; + /** * P2P payments are disabled with this exchange * (e.g. because no global fees are configured). @@ -2375,13 +2438,38 @@ export interface CreateDepositGroupRequest { } export interface PrepareDepositRequest { + /** + * Payto URI to identify the (bank) account that the exchange will wire + * the money to. + */ depositPaytoUri: string; + + /** + * Amount that should be deposited. + * + * Raw amount, fees will be added on top. + */ amount: AmountString; + + /** + * ID provided by the client to cancel the request. + * + * If the same request is made again with the same clientCancellationId, + * all previous requests are cancelled. + * + * The cancelled request will receive an error response with + * an error code that indicates the cancellation. + * + * The cancellation is best-effort, responses might still arrive. + */ + clientCancellationId?: string; } + export const codecForPrepareDepositRequest = (): Codec<PrepareDepositRequest> => buildCodecForObject<PrepareDepositRequest>() .property("amount", codecForAmountString()) .property("depositPaytoUri", codecForString()) + .property("clientCancellationId", codecOptional(codecForString())) .build("PrepareDepositRequest"); export interface PrepareDepositResponse { @@ -2801,6 +2889,19 @@ export interface CheckPeerPushDebitRequest { * FIXME: Allow specifying the instructed amount type. */ amount: AmountString; + + /** + * ID provided by the client to cancel the request. + * + * If the same request is made again with the same clientCancellationId, + * all previous requests are cancelled. + * + * The cancelled request will receive an error response with + * an error code that indicates the cancellation. + * + * The cancellation is best-effort, responses might still arrive. + */ + clientCancellationId?: string; } export const codecForCheckPeerPushDebitRequest = @@ -2808,6 +2909,7 @@ export const codecForCheckPeerPushDebitRequest = buildCodecForObject<CheckPeerPushDebitRequest>() .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .property("amount", codecForAmountString()) + .property("clientCancellationId", codecOptional(codecForString())) .build("CheckPeerPushDebitRequest"); export interface CheckPeerPushDebitResponse { @@ -2940,12 +3042,27 @@ export const codecForAcceptPeerPullPaymentRequest = export interface CheckPeerPullCreditRequest { exchangeBaseUrl?: string; amount: AmountString; + + /** + * ID provided by the client to cancel the request. + * + * If the same request is made again with the same clientCancellationId, + * all previous requests are cancelled. + * + * The cancelled request will receive an error response with + * an error code that indicates the cancellation. + * + * The cancellation is best-effort, responses might still arrive. + */ + clientCancellationId?: string; } + export const codecForPreparePeerPullPaymentRequest = (): Codec<CheckPeerPullCreditRequest> => buildCodecForObject<CheckPeerPullCreditRequest>() .property("amount", codecForAmountString()) .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) + .property("clientCancellationId", codecOptional(codecForString())) .build("CheckPeerPullCreditRequest"); export interface CheckPeerPullCreditResponse { @@ -2959,6 +3076,7 @@ export interface CheckPeerPullCreditResponse { */ numCoins: number; } + export interface InitiatePeerPullCreditRequest { exchangeBaseUrl?: string; partialContractTerms: PeerContractTerms; @@ -3106,6 +3224,11 @@ export interface WalletContractData { minimumAge?: number; } +export interface TestingWaitExchangeStateRequest { + exchangeBaseUrl: string; + walletKycStatus?: ExchangeWalletKycStatus; +} + export interface TestingWaitTransactionRequest { transactionId: TransactionIdStr; txState: TransactionState; @@ -3458,8 +3581,58 @@ export interface GetQrCodesForPaytoResponse { codes: QrCodeSpec[]; } +export interface GetBankingChoicesForPaytoRequest { + paytoUri: string; +} + +export const codecForGetBankingChoicesForPaytoRequest = () => + buildCodecForObject<GetBankingChoicesForPaytoRequest>() + .property("paytoUri", codecForString()) + .build("GetBankingChoicesForPaytoRequest"); + +export interface BankingChoiceSpec { + label: string; + // FIXME: In the future, we might also have some way to return intents here? + type: "link"; + uri: string; +} + +export interface GetBankingChoicesForPaytoResponse { + choices: BankingChoiceSpec[]; +} + export type EmptyObject = Record<string, never>; export const codecForEmptyObject = (): Codec<EmptyObject> => - buildCodecForObject<EmptyObject>() - .build("EmptyObject");
\ No newline at end of file + buildCodecForObject<EmptyObject>().build("EmptyObject"); + +export interface TestingWaitWalletKycRequest { + exchangeBaseUrl: string; + amount: AmountString; + /** + * Do we wait for the KYC to be passed (true), + * or do we already return if legitimization is + * required (false). + */ + passed: boolean; +} + +export const codecForTestingWaitWalletKycRequest = + (): Codec<TestingWaitWalletKycRequest> => + buildCodecForObject<TestingWaitWalletKycRequest>() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForAmountString()) + .property("passed", codecForBoolean()) + .build("TestingWaitWalletKycRequest"); + +export interface StartExchangeWalletKycRequest { + exchangeBaseUrl: string; + amount: AmountString; +} + +export const codecForStartExchangeWalletKycRequest = + (): Codec<StartExchangeWalletKycRequest> => + buildCodecForObject<StartExchangeWalletKycRequest>() + .property("exchangeBaseUrl", codecForString()) + .property("amount", codecForAmountString()) + .build("StartExchangeWalletKycRequest"); diff --git a/packages/taler-util/src/types-taler-wire-gateway.ts b/packages/taler-util/src/types-taler-wire-gateway.ts new file mode 100644 index 000000000..fd1eb7263 --- /dev/null +++ b/packages/taler-util/src/types-taler-wire-gateway.ts @@ -0,0 +1,278 @@ +/* + This file is part of GNU Taler + (C) 2024 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero 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 Affero General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { + Codec, + TalerWireGatewayApi, + buildCodecForObject, + buildCodecForUnion, + codecForAmountString, + codecForConstString, + codecForList, + codecForNumber, + codecForString, +} from "./index.js"; +import { PaytoString, codecForPaytoString } from "./payto.js"; +import { codecForTimestamp } from "./time.js"; +import { + AmountString, + EddsaPublicKey, + HashCode, + SafeUint64, + ShortHashCode, + Timestamp, + WadId, +} from "./types-taler-common.js"; + +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: AmountString; + + // 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: PaytoString; +} + +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. + + // undefined if incoming transaction is empty + credit_account?: PaytoString; +} + +// 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: AmountString; + + // Payto URI to identify the sender of funds. + debit_account: PaytoString; + + // 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: AmountString; + + // Payto URI to identify the receiver of funds. + // This must be one of the exchange's bank accounts. + credit_account: PaytoString; + + // Payto URI to identify the sender of funds. + debit_account: PaytoString; + + // 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. + + // undefined if outgoing transactions is empty + debit_account?: PaytoString; +} + +export interface OutgoingBankTransaction { + // Opaque identifier of the returned record. + row_id: SafeUint64; + + // Date of the transaction. + date: Timestamp; + + // Amount transferred. + amount: AmountString; + + // Payto URI to identify the receiver of funds. + credit_account: PaytoString; + + // 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: AmountString; + + // 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: PaytoString; +} + +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 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", codecForPaytoString()) + .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", codecForPaytoString()) + .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", codecForPaytoString()) + .property("date", codecForTimestamp) + .property("debit_account", codecForPaytoString()) + .property("origin_exchange_url", codecForString()) + .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", codecForPaytoString()) + .property( + "outgoing_transactions", + codecForList(codecForOutgoingBankTransaction()), + ) + .build("TalerWireGatewayApi.OutgoingHistory"); + +export const codecForOutgoingBankTransaction = + (): Codec<TalerWireGatewayApi.OutgoingBankTransaction> => + buildCodecForObject<TalerWireGatewayApi.OutgoingBankTransaction>() + .property("amount", codecForAmountString()) + .property("credit_account", codecForPaytoString()) + .property("date", codecForTimestamp) + .property("exchange_base_url", codecForString()) + .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"); diff --git a/packages/taler-util/src/types-test.ts b/packages/taler-util/src/types.test.ts index 6acd2c26e..ed55fd16c 100644 --- a/packages/taler-util/src/types-test.ts +++ b/packages/taler-util/src/types.test.ts @@ -15,7 +15,7 @@ */ import test from "ava"; -import { codecForMerchantContractTerms as codecForContractTerms } from "./taler-types.js"; +import { codecForContractTerms } from "./index.js"; test("contract terms validation", (t) => { const c = { diff --git a/packages/taler-util/src/whatwg-url.ts b/packages/taler-util/src/whatwg-url.ts index 13abf5397..bc2c682fc 100644 --- a/packages/taler-util/src/whatwg-url.ts +++ b/packages/taler-util/src/whatwg-url.ts @@ -424,6 +424,10 @@ export class URLSearchParamsImpl { return output; } + entries() { + return [...this._list.map((x) => [x[0], x[1]])]; + } + forEach( callbackfn: ( value: string, @@ -1907,6 +1911,7 @@ function parseURL( }); } +const NativeURL = typeof URL !== "undefined" ? URL : undefined; export class URLImpl { //Include URL type for "url" and "base" params. constructor(url: string | URL, base?: string | URL) { @@ -2120,6 +2125,21 @@ export class URLImpl { return this.href; } + static createObjectURL(blob: Blob) { + if (!NativeURL) + throw new Error( + "This method requires a native implementation, which does not exist", + ); + return NativeURL.createObjectURL(blob); + } + static revokeObjectURL(url: string) { + if (!NativeURL) + throw new Error( + "This method requires a native implementation, which does not exist", + ); + return NativeURL.revokeObjectURL(url); + } + // FIXME: type! _url: any; _query: any; |