diff options
author | Sebastian <sebasjm@gmail.com> | 2023-10-23 15:49:13 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2023-10-23 15:49:35 -0300 |
commit | c66f06eb73e53a8804b8c2b36ff45972836c633c (patch) | |
tree | 18461a0e29124326ccb70331971d5914f3ff56b3 /packages | |
parent | 2512665e9462690162a5bae5fab3337217943269 (diff) | |
download | wallet-core-c66f06eb73e53a8804b8c2b36ff45972836c633c.tar.xz |
update conversion rate info, added test-bank-api in taler harness
Diffstat (limited to 'packages')
-rw-r--r-- | packages/demobank-ui/src/pages/UpdateAccountPassword.tsx | 6 | ||||
-rw-r--r-- | packages/demobank-ui/src/pages/business/CreateCashout.tsx | 14 | ||||
-rw-r--r-- | packages/taler-harness/src/index.ts | 87 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/bank-core.ts | 116 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/types.ts | 23 |
5 files changed, 151 insertions, 95 deletions
diff --git a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx index ef3737e81..f23285528 100644 --- a/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx +++ b/packages/demobank-ui/src/pages/UpdateAccountPassword.tsx @@ -47,7 +47,7 @@ export function UpdateAccountPassword({ if (!!errors || !password || !token) return; await withRuntimeErrorHandling(i18n, async () => { const resp = await api.updatePassword({ username: accountName, token }, { - // old_password: current, + old_password: current, new_password: password, }); if (resp.type === "ok") { @@ -61,7 +61,9 @@ export function UpdateAccountPassword({ }) case "no-rights": return notify({ type: "error", - title: i18n.str`This user have no right on to change the password.` + title: current ? + i18n.str`This user have no right on to change the password.` : + i18n.str`This user have no right on to change the password or the old password doesn't match.` }) case "not-found": return notify({ type: "error", diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx index 5595d3b51..a3a48eed4 100644 --- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx +++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx @@ -110,14 +110,14 @@ export function CreateCashout({ } } - if (resultRatios.type === "fail") { - switch (resultRatios.case) { - case "not-supported": return <div>cashout operations are not supported</div> - default: assertUnreachable(resultRatios.case) - } - } + // if (resultRatios.type === "fail") { + // switch (resultRatios.case) { + // case "not-supported": return <div>cashout operations are not supported</div> + // default: assertUnreachable(resultRatios.case) + // } + // } - const ratio = resultRatios.body + // const ratio = resultRatios.body const account = { balance: Amounts.parseOrThrow(resultAccount.body.balance.amount), diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts index 09b953a22..0f93abdbe 100644 --- a/packages/taler-harness/src/index.ts +++ b/packages/taler-harness/src/index.ts @@ -18,22 +18,27 @@ * Imports. */ import { - addPaytoQueryParams, + AccessToken, + AmountString, Amounts, - TalerCorebankApiClient, Configuration, - decodeCrock, - j2s, + Duration, + HttpStatusCode, + LibtoolVersion, Logger, MerchantApiClient, - rsaBlind, - setGlobalLogLevelFromString, - RegisterAccountRequest, - HttpStatusCode, MerchantInstanceConfig, - Duration, + RegisterAccountRequest, + TalerCoreBankHttpClient, + TalerCorebankApiClient, + addPaytoQueryParams, + decodeCrock, + encodeCrock, generateIban, - AmountString, + getRandomBytes, + j2s, + rsaBlind, + setGlobalLogLevelFromString, } from "@gnu-taler/taler-util"; import { clk } from "@gnu-taler/taler-util/clk"; import { @@ -42,8 +47,8 @@ import { } from "@gnu-taler/taler-util/http"; import { CryptoDispatcher, - downloadExchangeInfo, SynchronousCryptoWorkerFactoryPlain, + downloadExchangeInfo, topupReserveWithDemobank, } from "@gnu-taler/taler-wallet-core"; import { deepStrictEqual } from "assert"; @@ -652,6 +657,66 @@ deploymentCli process.exit(2); }); + deploymentCli + .subcommand("testBankAPI", "test-bank-api", { + help: "test api compatibility.", + }) + .requiredArgument("corebankApiBaseUrl", clk.STRING) + .maybeOption("adminPwd", ["--admin-password"], clk.STRING) + .action(async (args) => { + const httpLib = createPlatformHttpLib(); + const api = new TalerCoreBankHttpClient(args.testBankAPI.corebankApiBaseUrl, httpLib); + + { + logger.info("compatibility") + const resp = await api.getConfig() + if (!LibtoolVersion.compare(resp.body.version, api.PROTOCOL_VERSION)?.compatible) { + logger.error("The API client is not compatible with the server", api.PROTOCOL_VERSION, resp.body.version) + process.exit(1) + } + } + if (!args.testBankAPI.adminPwd) { + logger.info("test completed") + process.exit(0) + } + + let token: AccessToken; + { + logger.info("login admin") + const resp = await api.getAuthenticationAPI("admin").createAccessToken(args.testBankAPI.adminPwd, { + scope: "readwrite" + }) + if (resp.type === "fail") { + logger.error("login failed", resp.detail) + process.exit(1) + } + token = resp.body.access_token; + } + + logger.info("account management") + const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); + { + const resp = await api.createAccount(token, { name: username, password: "123", username }) + if (resp.type === "fail") { + logger.error("create account failed", resp.detail) + process.exit(1) + } + } + { + const resp = await api.updateAccount({username, token}, { challenge_contact_data: {email: "asd"} }) + if (resp.type === "fail") { + logger.error("create account failed", resp.detail) + process.exit(1) + } + } + + + logger.info("test completed") + + }); + + + deploymentCli .subcommand("coincfg", "gen-coin-config", { help: "Generate a coin/denomination configuration for the exchange.", diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts index c49a094e4..bd35552f7 100644 --- a/packages/taler-util/src/http-client/bank-core.ts +++ b/packages/taler-util/src/http-client/bank-core.ts @@ -74,7 +74,6 @@ export class TalerCoreBankHttpClient { method: "GET" }); switch (resp.status) { - //FIXME: missing in docs case HttpStatusCode.Ok: return opSuccess(resp, codecForCoreBankConfig()) default: return opUnknownFailure(resp, await resp.text()) } @@ -98,10 +97,9 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - //FIXME: NOT IN THE DOOOCS case HttpStatusCode.Created: return opEmptySuccess() - case HttpStatusCode.NoContent: 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); @@ -128,6 +126,7 @@ 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); @@ -156,7 +155,11 @@ export class TalerCoreBankHttpClient { switch (resp.status) { case HttpStatusCode.NoContent: return opEmptySuccess() case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); + //FIXME: missing error code for cases: + // * change legal name + // * admin tries to change its own account + case HttpStatusCode.Forbidden: return opKnownFailure("cant-change-legal-name-or-admin", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -176,12 +179,9 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.NoContent: return opEmptySuccess() - //FIXME: missing in docs case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); - //FIXME: missing in docs - case HttpStatusCode.Unauthorized: return opKnownFailure("no-rights", resp); - //FIXME: missing in docs - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Forbidden: return opKnownFailure("old-password-invalid-or-not-allowed", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -192,7 +192,6 @@ export class TalerCoreBankHttpClient { */ async getPublicAccounts(pagination?: PaginationParams) { const url = new URL(`public-accounts`, this.baseUrl); - //FIXME: missing pagination in docs addPaginationParams(url, pagination) const resp = await this.httpLib.fetch(url.href, { method: "GET", @@ -200,10 +199,9 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - //FIXME: missing in docs case HttpStatusCode.Ok: return opSuccess(resp, codecForPublicAccountsResponse()) - //FIXME: missing in docs case HttpStatusCode.NoContent: return opFixedSuccess({ public_accounts: [] }) + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("public-account-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -212,9 +210,12 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#get--accounts * */ - async getAccounts(auth: AccessToken, pagination?: PaginationParams) { + async getAccounts(auth: AccessToken, filter: {account? : string} = {}, pagination?: PaginationParams) { const url = new URL(`accounts`, this.baseUrl); addPaginationParams(url, pagination) + if (filter.account) { + url.searchParams.set("filter_name", filter.account) + } const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { @@ -224,7 +225,8 @@ export class TalerCoreBankHttpClient { switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForListBankAccountsResponse()) case HttpStatusCode.NoContent: return opFixedSuccess({ accounts: [] }) - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Forbidden: return opKnownFailure("no-rights", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -243,12 +245,9 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForAccountData()) - //FIXME: missing in docs (401 when not found?) - case HttpStatusCode.Unauthorized: return opKnownFailure("not-found", resp); - //FIXME: missing in docs + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); - //FIXME: missing in docs - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Forbidden: return opKnownFailure("no-rights", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -272,12 +271,9 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountTransactionsResponse()) - //FIXME: missing in docs case HttpStatusCode.NoContent: return opFixedSuccess({ transactions: [] }) - //FIXME: missing in docs case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); - //FIXME: missing in docs - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -296,10 +292,8 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountTransactionInfo()) - //FIXME: missing in docs case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp); - //FIXME: missing in docs - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -318,12 +312,12 @@ export class TalerCoreBankHttpClient { body, }); switch (resp.status) { - //FIXME: fix docs... it should be NoContent + //FIXME: remove this after server has been updated case HttpStatusCode.Ok: return opEmptySuccess() case HttpStatusCode.NoContent: return opEmptySuccess() + //FIXME: check when the server add codes spec case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); - //FIXME: missing in docs - case HttpStatusCode.Forbidden: return opKnownFailure("unauthorized", resp); + case HttpStatusCode.Unauthorized: return opKnownFailure("unauthorized", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -346,8 +340,9 @@ export class TalerCoreBankHttpClient { body, }); switch (resp.status) { - //FIXME: missing in docs case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountCreateWithdrawalResponse()) + case HttpStatusCode.PreconditionFailed: return opKnownFailure("insufficient-funds", resp); + //FIXME: remove when server is updated case HttpStatusCode.Forbidden: return opKnownFailure("insufficient-funds", resp); default: return opUnknownFailure(resp, await resp.text()) } @@ -363,7 +358,6 @@ export class TalerCoreBankHttpClient { method: "GET", }); switch (resp.status) { - //FIXME: missing in docs case HttpStatusCode.Ok: return opSuccess(resp, codecForBankAccountGetWithdrawalResponse()) case HttpStatusCode.NotFound: return opKnownFailure("not-found", resp) default: return opUnknownFailure(resp, await resp.text()) @@ -380,7 +374,7 @@ export class TalerCoreBankHttpClient { method: "POST", }); switch (resp.status) { - //FIXME: fix docs... it should be NoContent + //FIXME: remove when the server is fixed case HttpStatusCode.Ok: return opEmptySuccess() case HttpStatusCode.NoContent: return opEmptySuccess() case HttpStatusCode.Conflict: return opKnownFailure("previously-confirmed", resp); @@ -398,7 +392,7 @@ export class TalerCoreBankHttpClient { method: "POST", }); switch (resp.status) { - //FIXME: fix docs... it should be NoContent + //FIXME: remove when the server is fixed case HttpStatusCode.Ok: return opEmptySuccess() case HttpStatusCode.NoContent: return opEmptySuccess() case HttpStatusCode.Conflict: return opKnownFailure("previously-aborted", resp); @@ -426,15 +420,14 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Accepted: return opSuccess(resp, codecForCashoutPending()) - //FIXME: it should be precondition-failed + //FIXME: change when the server has been updated case HttpStatusCode.Conflict: return opKnownFailure("no-contact-info", resp); //FIXME: missing in the docs - case HttpStatusCode.Forbidden: return opKnownFailure("no-allowed", 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); - case HttpStatusCode.ServiceUnavailable: return opKnownFailure("tan-not-supported", resp); + //FIXME: check the code response to tell cashout or tan not supported + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-or-tan-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -455,6 +448,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); + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -477,6 +471,7 @@ 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); + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -500,6 +495,7 @@ export class TalerCoreBankHttpClient { 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.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -508,8 +504,9 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts * */ - async getAccountCashouts(auth: UserAndToken) { + 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: { @@ -518,9 +515,8 @@ export class TalerCoreBankHttpClient { }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForCashouts()) - // FIXME: not in docs (maybe for admin and bank) - case HttpStatusCode.NotFound: return opFixedSuccess({ cashouts: [] }); case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] }); + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -529,8 +525,9 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#get--cashouts * */ - async getGlobalCashouts(auth: AccessToken) { + async getGlobalCashouts(auth: AccessToken, pagination?: PaginationParams) { const url = new URL(`cashouts`, this.baseUrl); + addPaginationParams(url, pagination) const resp = await this.httpLib.fetch(url.href, { method: "GET", headers: { @@ -540,6 +537,7 @@ export class TalerCoreBankHttpClient { switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForGlobalCashouts()) case HttpStatusCode.NoContent: return opFixedSuccess({ cashouts: [] }); + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -557,29 +555,9 @@ export class TalerCoreBankHttpClient { }, }); switch (resp.status) { - //FIXME: missing in docs case HttpStatusCode.Ok: return opSuccess(resp, codecForCashoutStatusResponse()) case HttpStatusCode.NotFound: return opKnownFailure("already-aborted", resp); - default: return opUnknownFailure(resp, await resp.text()) - } - } - - // - // CONVERSION RATE - // - - /** - * https://docs.taler.net/core/api-corebank.html#get--conversion-rates - * - */ - async getConversionRates() { - const url = new URL(`conversion-rates`, this.baseUrl); - const resp = await this.httpLib.fetch(url.href, { - method: "GET", - }); - switch (resp.status) { - case HttpStatusCode.Ok: return opSuccess(resp, codecForConversionRatesResponse()) - case HttpStatusCode.NotFound: return opKnownFailure("not-supported", resp); + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("cashout-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } @@ -592,17 +570,23 @@ export class TalerCoreBankHttpClient { * https://docs.taler.net/core/api-corebank.html#get--monitor * */ - async getMonitor(params: { timeframe: TalerCorebankApi.MonitorTimeframeParam, which: number }) { + async getMonitor(params: { timeframe?: TalerCorebankApi.MonitorTimeframeParam, which?: number }) { const url = new URL(`monitor`, this.baseUrl); - url.searchParams.set("timeframe", params.timeframe.toString()) - url.searchParams.set("which", String(params.which)) + if (params.timeframe) { + url.searchParams.set("timeframe", params.timeframe.toString()) + } + if (params.which) { + url.searchParams.set("which", String(params.which)) + } const resp = await this.httpLib.fetch(url.href, { method: "GET", }); switch (resp.status) { case HttpStatusCode.Ok: return opSuccess(resp, codecForMonitorResponse()) - case HttpStatusCode.NotFound: return opKnownFailure("not-supported", resp); case HttpStatusCode.BadRequest: return opKnownFailure("invalid-input", resp); + //FIXME remove when server is updated + case HttpStatusCode.NotFound: return opKnownFailure("monitor-not-supported", resp); + case HttpStatusCode.ServiceUnavailable: return opKnownFailure("monitor-not-supported", resp); default: return opUnknownFailure(resp, await resp.text()) } } diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index be6e72a2e..8af134280 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -263,6 +263,7 @@ export const codecForCoreBankConfig = .property("have_cashout", codecOptional(codecForBoolean())) .property("currency", codecForCurrencySpecificiation()) .property("fiat_currency", codecOptional(codecForCurrencySpecificiation())) + .property("conversion_info", codecOptional(codecForConversionRatesResponse())) .build("TalerCorebankApi.Config") const codecForBalance = (): Codec<TalerCorebankApi.Balance> => @@ -419,10 +420,10 @@ export const codecForConversionRatesResponse = export const codecForMonitorResponse = (): Codec<TalerCorebankApi.MonitorResponse> => buildCodecForObject<TalerCorebankApi.MonitorResponse>() - .property("cashinCount", codecForNumber()) - .property("cashinExternalVolume", codecForAmountString()) - .property("cashoutCount", codecForNumber()) - .property("cashoutExternalVolume", codecForAmountString()) + .property("cashinCount", codecOptional(codecForNumber())) + .property("cashinExternalVolume", codecOptional(codecForAmountString())) + .property("cashoutCount", codecOptional(codecForNumber())) + .property("cashoutExternalVolume", codecOptional(codecForAmountString())) .property("talerPayoutCount", codecForNumber()) .property("talerPayoutInternalVolume", codecForAmountString()) .build("TalerCorebankApi.MonitorResponse"); @@ -871,6 +872,11 @@ export namespace TalerCorebankApi { // cash-out operations ultimately wire money. // Only applicable if have_cashout=true. fiat_currency?: CurrencySpecification; + + // Extra conversion rate information. + // Only present if conversion is supported and the server opts in + // to report the static conversion rate. + conversion_info?: ConversionRatesResponse } export interface BankAccountCreateWithdrawalRequest { @@ -1015,7 +1021,6 @@ export namespace TalerCorebankApi { export interface AccountPasswordChange { - // FIXME: missing in docs // New password. new_password: string; // Old password. If present, chec that the old password matches. @@ -1203,23 +1208,23 @@ export namespace TalerCorebankApi { // 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 |