diff options
Diffstat (limited to 'packages')
-rw-r--r-- | packages/taler-util/src/http-client/bank-core.ts | 207 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/types.ts | 86 | ||||
-rw-r--r-- | packages/taler-util/src/operation.ts | 23 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 2 |
4 files changed, 202 insertions, 116 deletions
diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts index 35f216220..1107a3c93 100644 --- a/packages/taler-util/src/http-client/bank-core.ts +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -18,7 +18,9 @@ import { AmountJson, Amounts, HttpStatusCode, - LibtoolVersion + LibtoolVersion, + TalerErrorCode, + codecForTalerErrorDetail } from "@gnu-taler/taler-util"; import { HttpRequestLibrary, @@ -92,16 +94,19 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Created: return opEmptySuccess() - case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); - //FIXME: check when the server add code spec - case HttpStatusCode.Forbidden: { - if (body.username === "bank" || body.username === "admin") { - return opKnownFailure("unable-to-create", resp); - } else { - return opKnownFailure("unauthorized", resp); + case HttpStatusCode.BadRequest: return opKnownFailure("invalid-phone-or-email", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Conflict: { + const body = await resp.json() + const details = codecForTalerErrorDetail().decode(body) + switch (details.code) { + case TalerErrorCode.BANK_REGISTER_USERNAME_REUSE: return opKnownFailure("username-already-exists", resp); + case TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE: return opKnownFailure("payto-already-exists", resp); + case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownFailure("insufficient-funds", resp); + case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return opKnownFailure("username-reserved", resp); + default: return opUnknownFailure(resp, body) } } - case HttpStatusCode.Conflict: return opKnownFailure("already-exist", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -120,19 +125,16 @@ export class TalerCoreBankHttpClient { switch (resp.status) { case HttpStatusCode.NoContent: return opEmptySuccess() case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); - //FIXME: check when the server add code spec - case HttpStatusCode.Forbidden: { - if (auth.username === "bank" || auth.username === "admin") { - return opKnownFailure("unable-to-delete", resp); - } else { - return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Conflict: { + const body = await resp.json() + const details = codecForTalerErrorDetail().decode(body) + switch (details.code) { + case TalerErrorCode.BANK_RESERVED_USERNAME_CONFLICT: return opKnownFailure("username-reserved", resp); + case TalerErrorCode.BANK_ACCOUNT_BALANCE_NOT_ZERO: return opKnownFailure("balance-not-zero", resp); + default: return opUnknownFailure(resp, body) } } - //FIXME: this should be forbidden - case HttpStatusCode.Unauthorized: { - return opKnownFailure("unauthorized", resp); - } - case HttpStatusCode.Conflict: return opKnownFailure("balance-not-zero", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -223,10 +225,7 @@ export class TalerCoreBankHttpClient { switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForListBankAccountsResponse()) case HttpStatusCode.NoContent: return opFixedSuccess({ accounts: [] }) - case HttpStatusCode.Forbidden: return opKnownFailure("no-rights", resp); - case HttpStatusCode.Unauthorized: { - return opKnownFailure("unauthorized", resp); - } + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -247,7 +246,6 @@ export class TalerCoreBankHttpClient { case HttpStatusCode.Ok: return opSuccess(resp, codecForAccountData()) case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); - case HttpStatusCode.Forbidden: return opKnownFailure("no-rights", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -312,14 +310,21 @@ export class TalerCoreBankHttpClient { body, }); switch (resp.status) { - //FIXME: return txid - //FIXME: remove this after server has been updated - case HttpStatusCode.Ok: return opEmptySuccess() + //FIXME: expect txid as response case HttpStatusCode.NoContent: return opEmptySuccess() - case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", resp); - //FIXME: check when the server add codes spec case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); + case HttpStatusCode.Conflict: { + const body = await resp.json() + const details = codecForTalerErrorDetail().decode(body) + switch (details.code) { + case TalerErrorCode.BANK_SAME_ACCOUNT: return opKnownFailure("creditor-same", resp); + case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return opKnownFailure("creditor-not-found", resp); + case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownFailure("insufficient-funds", resp); + default: return opUnknownFailure(resp, body) + } + } default: return opUnknownFailure(resp, await resp.text()) } } @@ -343,69 +348,74 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountCreateWithdrawalResponse()) + case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", resp); case HttpStatusCode.Conflict: return opKnownFailure("insufficient-funds", resp); case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); - case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", resp); - //FIXME: remove when server is updated - case HttpStatusCode.Forbidden: return opKnownFailure("insufficient-funds", resp); default: return opUnknownFailure(resp, await resp.text()) } } /** - * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals + * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort * */ - async getWithdrawalById(wid: string) { - const url = new URL(`withdrawals/${wid}`, this.baseUrl); + async abortWithdrawalById(wid: string) { + const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { - method: "GET", + method: "POST", }); switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountGetWithdrawalResponse()) + case HttpStatusCode.NoContent: return opEmptySuccess() + //FIXME: missing in docs case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp) case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp) + case HttpStatusCode.Conflict: return opKnownFailure("previously-confirmed", resp); default: return opUnknownFailure(resp, await resp.text()) } } /** - * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort + * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm * */ - async abortWithdrawalById(wid: string) { - const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl); + async confirmWithdrawalById(wid: string) { + const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", }); switch (resp.status) { - //FIXME: remove when the server is fixed - case HttpStatusCode.Ok: return opEmptySuccess() case HttpStatusCode.NoContent: return opEmptySuccess() + //FIXME: missing in docs case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp) case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp) - case HttpStatusCode.Conflict: return opKnownFailure("previously-confirmed", resp); + case HttpStatusCode.Conflict: { + const body = await resp.json() + const details = codecForTalerErrorDetail().decode(body) + switch (details.code) { + case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownFailure("insufficient-funds", resp); + case TalerErrorCode.BANK_CONFIRM_INCOMPLETE: return opKnownFailure("no-exchange-or-reserve-selected", resp); + case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT: return opKnownFailure("previously-aborted", resp); + default: return opUnknownFailure(resp, body) + } + } default: return opUnknownFailure(resp, await resp.text()) } } /** - * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm + * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals * */ - async confirmWithdrawalById(wid: string) { - const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl); + async getWithdrawalById(wid: string) { + const url = new URL(`withdrawals/${wid}`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { - method: "POST", + method: "GET", }); switch (resp.status) { - //FIXME: remove when the server is fixed - case HttpStatusCode.Ok: return opEmptySuccess() - case HttpStatusCode.NoContent: return opEmptySuccess() + case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountGetWithdrawalResponse()) + //FIXME: missing in docs case HttpStatusCode.BadRequest: return opKnownFailure("invalid-id", resp) case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp) - case HttpStatusCode.Conflict: return opKnownFailure("previously-aborted", resp); - case HttpStatusCode.UnprocessableEntity: return opKnownFailure("no-exchange-or-reserve-selected", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -429,14 +439,18 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Accepted: return opSuccess(resp, codecForCashoutPending()) - //FIXME: change when the server has been updated - case HttpStatusCode.Conflict: return opKnownFailure("no-contact-info", resp); - //FIXME: missing in the docs - case HttpStatusCode.PreconditionFailed: return opKnownFailure("no-enough-balance", resp); - //FIXME: missing in the docs - case HttpStatusCode.BadRequest: return opKnownFailure("incorrect-exchange-rate", resp); - //FIXME: check the code response to tell cashout or tan not supported - case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-or-tan-not-supported", resp); + case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", resp) + case HttpStatusCode.Conflict: { + const body = await resp.json() + const details = codecForTalerErrorDetail().decode(body) + switch (details.code) { + case TalerErrorCode.BANK_BAD_CONVERSION: return opKnownFailure("incorrect-exchange-rate", resp); + case TalerErrorCode.BANK_MISSING_TAN_INFO: return opKnownFailure("no-contact-info", resp); + case TalerErrorCode.BANK_UNALLOWED_DEBIT: return opKnownFailure("no-enough-balance", resp); + default: return opUnknownFailure(resp, body) + } + } + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -457,6 +471,7 @@ export class TalerCoreBankHttpClient { case HttpStatusCode.NoContent: return opEmptySuccess() case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); case HttpStatusCode.Conflict: return opKnownFailure("already-confirmed", resp); + //FIXME: should be 404 ? case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } @@ -480,42 +495,40 @@ export class TalerCoreBankHttpClient { case HttpStatusCode.Forbidden: return opKnownFailure("wrong-tan-or-credential", resp); case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); case HttpStatusCode.Conflict: return opKnownFailure("cashout-address-changed", resp); + //FIXME: should be 404 ? case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } /** - * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm + * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts * */ - async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson }) { - const url = new URL(`cashout-rate`, this.baseUrl); - if (conversion.debit) { - url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit)) - } - if (conversion.credit) { - url.searchParams.set("amount_debit", Amounts.stringify(conversion.credit)) - } + async getAccountCashouts(auth: UserAndToken, pagination?: PaginationParams) { + const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl); + addPaginationParams(url, pagination) const resp = await this.httpLib.fetch(url.href, { method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth.token) + }, }); switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutConversionResponse()) - case HttpStatusCode.BadRequest: return opKnownFailure("wrong-calculation", resp); - case HttpStatusCode.NotFound: return opKnownFailure("not-supported", resp); + case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts()) + case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] }); + case HttpStatusCode.NotFound: return opKnownFailure("account-not-found", resp);; case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } /** - * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts + * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID * */ - async getAccountCashouts(auth: UserAndToken, pagination?: PaginationParams) { - const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl); - addPaginationParams(url, pagination) + async getCashoutById(auth: UserAndToken, cid: string) { + const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { @@ -523,8 +536,8 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts()) - case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] }); + case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutStatusResponse()) + case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } @@ -552,20 +565,28 @@ export class TalerCoreBankHttpClient { } /** - * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID + * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm * */ - async getCashoutById(auth: UserAndToken, cid: string) { - const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, this.baseUrl); + async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson }) { + const url = new URL(`cashout-rate`, this.baseUrl); + if (conversion.debit) { + url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit)) + } + if (conversion.credit) { + url.searchParams.set("amount_debit", Amounts.stringify(conversion.credit)) + } const resp = await this.httpLib.fetch(url.href, { method: "GET", - headers: { - Authorization: makeBearerTokenAuthHeader(auth.token) - }, }); switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutStatusResponse()) - case HttpStatusCode.NotFound: return opKnownFailure("already-aborted", resp); + case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutConversionResponse()) + // FIXME: error code for + // * the requested currency was not supported + // * the calculation is not correct + case HttpStatusCode.BadRequest: return opKnownFailure("wrong-calculation", resp); + case HttpStatusCode.NotFound: return opKnownFailure("not-supported", resp); + //FIXME: should be 404 ? case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } @@ -579,22 +600,26 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#get--monitor * */ - async getMonitor(params: { timeframe?: TalerCorebankApi.MonitorTimeframeParam, which?: number } = {}) { + async getMonitor(auth: AccessToken, params: { timeframe?: TalerCorebankApi.MonitorTimeframeParam, which?: number } = {}) { const url = new URL(`monitor`, this.baseUrl); if (params.timeframe) { - url.searchParams.set("timeframe", params.timeframe.toString()) + url.searchParams.set("timeframe", TalerCorebankApi.MonitorTimeframeParam[params.timeframe]) } if (params.which) { url.searchParams.set("which", String(params.which)) } const resp = await this.httpLib.fetch(url.href, { method: "GET", + headers: { + Authorization: makeBearerTokenAuthHeader(auth) + }, }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForMonitorResponse()) case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); //FIXME remove when server is updated - case HttpStatusCode.NotFound: return opKnownFailure("monitor-not-supported", resp); + //FIXME: should be 404 ? case HttpStatusCode.ServiceUnavailable: return opKnownFailure("monitor-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } @@ -641,5 +666,3 @@ export class TalerCoreBankHttpClient { } } - - diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 1bb8f99c1..95d0f8dd6 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -192,9 +192,6 @@ export interface CurrencySpecification { // Name of the currency. name: string; - // Decimal separator for fractional digits. - decimal_separator: string; - // how many digits the user may enter after the decimal_separator num_fractional_input_digits: Integer; @@ -205,10 +202,6 @@ export interface CurrencySpecification { // padding with zeros. num_fractional_trailing_zero_digits: Integer; - // Whether the currency name should be rendered before (true) or - // after (false) the numeric value - is_currency_name_leading: boolean; - // map of powers of 10 to alternative currency names / symbols, must // always have an entry under "0" that defines the base name, // e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC". @@ -228,11 +221,9 @@ export const codecForCurrencySpecificiation = (): Codec<CurrencySpecification> => buildCodecForObject<CurrencySpecification>() .property("name", codecForString()) - .property("decimal_separator", codecForString()) .property("num_fractional_input_digits", codecForNumber()) .property("num_fractional_normal_digits", codecForNumber()) .property("num_fractional_trailing_zero_digits", codecForNumber()) - .property("is_currency_name_leading", codecForBoolean()) .property("alt_unit_names", codecForMap(codecForString())) .build("CurrencySpecification") @@ -241,6 +232,8 @@ export const codecForCoreBankConfig = buildCodecForObject<TalerCorebankApi.Config>() .property("name", codecForString()) .property("version", codecForString()) + .property("allow_deletions", codecForBoolean()) + .property("allow_registrations", codecForBoolean()) .property("have_cashout", codecOptional(codecForBoolean())) .property("currency", codecForCurrencySpecificiation()) .property("fiat_currency", codecOptional(codecForCurrencySpecificiation())) @@ -398,16 +391,32 @@ export const codecForConversionRatesResponse = .property("sell_out_fee", codecForDecimalNumber()) .build("TalerCorebankApi.ConversionRatesResponse"); -export const codecForMonitorResponse = - (): Codec<TalerCorebankApi.MonitorResponse> => - buildCodecForObject<TalerCorebankApi.MonitorResponse>() - .property("cashinCount", codecOptional(codecForNumber())) - .property("cashinExternalVolume", codecOptional(codecForAmountString())) - .property("cashoutCount", codecOptional(codecForNumber())) - .property("cashoutExternalVolume", codecOptional(codecForAmountString())) + +export const codecForMonitorResponse = (): Codec<TalerCorebankApi.MonitorResponse> => buildCodecForUnion<TalerCorebankApi.MonitorResponse>() + .discriminateOn("type") + .alternative("just-payouts", codecForMonitorResponseJustPayout()) + .alternative("with-cashout", codecForMonitorResponseWithCashout()) + .build("TalerWireGatewayApi.IncomingBankTransaction"); + +export const codecForMonitorResponseJustPayout = + (): Codec<TalerCorebankApi.MonitorJustPayouts> => + buildCodecForObject<TalerCorebankApi.MonitorJustPayouts>() + .property("type", codecForConstString("just-payouts")) .property("talerPayoutCount", codecForNumber()) .property("talerPayoutInternalVolume", codecForAmountString()) - .build("TalerCorebankApi.MonitorResponse"); + .build("TalerCorebankApi.MonitorJustPayouts"); + +export const codecForMonitorResponseWithCashout = + (): Codec<TalerCorebankApi.MonitorWithCashout> => + buildCodecForObject<TalerCorebankApi.MonitorWithCashout>() + .property("type", codecForConstString("with-cashout")) + .property("cashinCount", (codecForNumber())) + .property("cashinExternalVolume", (codecForAmountString())) + .property("cashoutCount", (codecForNumber())) + .property("cashoutExternalVolume", (codecForAmountString())) + .property("talerPayoutCount", codecForNumber()) + .property("talerPayoutInternalVolume", codecForAmountString()) + .build("TalerCorebankApi.MonitorWithCashout"); export const codecForBankVersion = (): Codec<TalerBankIntegrationApi.BankVersion> => @@ -839,6 +848,14 @@ export namespace TalerCorebankApi { // API version in the form $n:$n:$n version: string; + // 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', the server provides local currency // conversion support. // If missing or false, some parts of the API @@ -997,6 +1014,11 @@ export namespace TalerCorebankApi { // If present, change the is_exchange configuration. // See RegisterAccountRequest is_exchange?: boolean; + + // If present, change the max debit allowed for this user + // Only admin can change this property. + debit_threshold?: AmountString + } @@ -1181,30 +1203,50 @@ export namespace TalerCorebankApi { hour, day, month, year, decade, } - export interface MonitorResponse { + export type MonitorResponse = + | MonitorJustPayouts + | MonitorWithCashout; + + // Monitoring stats when conversion is not supported + export interface MonitorJustPayouts { + type: "just-payouts"; + + // This number identifies how many payments were made by a + // Taler exchange to a merchant bank account in the internal + // currency, in the timeframe specified in the request. + talerPayoutCount: number; + + // This amount accounts the overall *internal* currency that + // has been paid by a Taler exchange to a merchant internal + // bank account, in the timeframe specified in the request. + talerPayoutInternalVolume: AmountString; + } + // Monitoring stats when conversion is supported + export interface MonitorWithCashout { + type: "with-cashout"; // This number identifies how many cashin operations // took place in the timeframe specified in the request. // This number corresponds to how many withdrawals have // been initiated by a wallet owner. Note: wallet owners // are NOT required to be customers of the libeufin-bank. - cashinCount?: number; + cashinCount: number; // This amount accounts how much external currency has been // spent to withdraw Taler coins in the internal currency. // The exact amount of internal currency being created can be // calculated using the advertised conversion rates. - cashinExternalVolume?: AmountString; + cashinExternalVolume: AmountString; // This number identifies how many cashout operations were // confirmed in the timeframe speficied in the request. - cashoutCount?: number; + cashoutCount: number; // This amount corresponds to how much *external* currency was // paid by the libeufin-bank administrator to fulfill all the // confirmed cashouts related to the timeframe specified in the // request. - cashoutExternalVolume?: AmountString; + cashoutExternalVolume: AmountString; // This number identifies how many payments were made by a // Taler exchange to a merchant bank account in the internal diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts index 8adbbb0e2..9d7594d14 100644 --- a/packages/taler-util/src/operation.ts +++ b/packages/taler-util/src/operation.ts @@ -67,8 +67,29 @@ export async function failOrThrow<E>(s: E, cb: () => Promise<OperationResult<unk export type ResultByMethod<TT extends object, p extends keyof TT> = TT[p] extends (...args: any[]) => infer Ret ? Ret extends Promise<infer Result> ? - Result : + Result extends OperationResult<any, any> ? Result : never : never : //api always use Promises never; //error cases just for functions export type FailCasesByMethod<TT extends object, p extends keyof TT> = Exclude<ResultByMethod<TT, p>, OperationOk<any>> + +type MethodsOfOperations<T extends object> = keyof { + [P in keyof T as + //when the property is a function + T[P] extends (...args: any[]) => infer Ret ? + // that returns a promise + Ret extends Promise<infer Result> ? + // of an operation + Result extends OperationResult<any, any> ? + P : never + : never + : never]: any +} + +type AllKnownCases<t extends object, d extends keyof t> = "success" | FailCasesByMethod<t, d>["case"] + +export type TestForApi<ApiType extends object> = { + [OpType in MethodsOfOperations<ApiType> as `test_${OpType & string}`]: { + [c in AllKnownCases<ApiType, OpType>]: undefined | ((api: ApiType) => Promise<void>); + }; +}; diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 7503a4665..aaec09b66 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -592,7 +592,7 @@ export interface ConfirmPayResultPending { export const codecForTalerErrorDetail = (): Codec<TalerErrorDetail> => buildCodecForObject<TalerErrorDetail>() .property("code", codecForNumber()) - .property("when", codecForAbsoluteTime) + .property("when", codecOptional(codecForAbsoluteTime)) .property("hint", codecOptional(codecForString())) .build("TalerErrorDetail"); |