aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/demobank-ui/src/pages/WithdrawalQRCode.tsx35
-rw-r--r--packages/taler-harness/src/harness/harness.ts3
-rw-r--r--packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts15
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts6
-rw-r--r--packages/taler-util/src/http-client/bank-core.ts489
-rw-r--r--packages/taler-util/src/http-client/bank-integration.ts47
-rw-r--r--packages/taler-util/src/http-client/bank-revenue.ts32
-rw-r--r--packages/taler-util/src/http-client/bank-wire.ts98
-rw-r--r--packages/taler-util/src/http-client/index.ts0
-rw-r--r--packages/taler-util/src/http-client/types.ts1183
-rw-r--r--packages/taler-util/src/http-client/utils.ts68
-rw-r--r--packages/taler-util/src/http-common.ts2
-rw-r--r--packages/taler-util/src/index.ts5
-rw-r--r--packages/taler-util/src/taler-crypto.ts2
-rw-r--r--packages/taler-util/src/taler-types.ts20
-rw-r--r--packages/taler-util/src/transactions-types.ts18
-rw-r--r--packages/taler-util/src/wallet-types.ts21
-rw-r--r--packages/taler-wallet-core/src/dbless.ts25
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts9
-rw-r--r--packages/taler-wallet-core/src/operations/reward.ts6
-rw-r--r--packages/taler-wallet-core/src/operations/testing.ts74
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts11
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts4
-rw-r--r--packages/taler-wallet-core/src/versions.ts15
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts19
-rw-r--r--packages/taler-wallet-core/src/wallet.ts27
26 files changed, 2144 insertions, 90 deletions
diff --git a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
index 91c5da718..35fb94a6c 100644
--- a/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
+++ b/packages/demobank-ui/src/pages/WithdrawalQRCode.tsx
@@ -52,6 +52,41 @@ export function WithdrawalQRCode({
if (result.loading) {
return <Loading />;
}
+ if (result.type === ErrorType.CLIENT && result.status === HttpStatusCode.NotFound) {
+ return <div class="relative ml-auto mr-auto transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
+ <div>
+ <div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100 ">
+ <svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
+ </svg>
+ </div>
+
+ <div class="mt-3 text-center sm:mt-5">
+ <h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
+ <i18n.Translate>Operation not found</i18n.Translate>
+ </h3>
+ <div class="mt-2">
+ <p class="text-sm text-gray-500">
+ <i18n.Translate>
+ This operation is not known by the server. The operation id is wrong or the
+ server deleted the operation information before reaching here.
+ </i18n.Translate>
+ </p>
+ </div>
+ </div>
+ </div>
+ <div class="mt-5 sm:mt-6">
+ <button type="button"
+ class="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+ onClick={async (e) => {
+ e.preventDefault();
+ onClose()
+ }}>
+ <i18n.Translate>Cotinue to dashboard</i18n.Translate>
+ </button>
+ </div>
+ </div>
+ }
return handleNotOkResult(i18n)(result);
}
const { data } = result;
diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts
index 65a19959a..c16200933 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -654,6 +654,9 @@ export class FakebankService
return this.baseUrl;
}
+ // FIXME: Why do we have this function at all?
+ // We now have a unified corebank API, we should just use that
+ // to create bank accounts, also for the exchange.
async createExchangeAccount(
accountName: string,
password: string,
diff --git a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
index 449142809..def2462e0 100644
--- a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
+++ b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
@@ -136,20 +136,22 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
},
);
- await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
-
let p: PendingOperationsResponse;
p = await walletClient.call(WalletApiOperation.GetPendingOperations, {});
console.log("pending operations after first time travel");
console.log(JSON.stringify(p, undefined, 2));
- await withdrawViaBankV2(t, {
+ await walletClient.call(WalletApiOperation.TestingWaitTasksProcessed, {});
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+ const wres2 = await withdrawViaBankV2(t, {
walletClient,
bank,
exchange,
amount: "TESTKUDOS:20",
});
+ await wres2.withdrawalFinishedCond;
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
@@ -165,12 +167,13 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
},
);
+ await walletClient.call(WalletApiOperation.TestingWaitTasksProcessed, {});
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
// At this point, the original coins should've been refreshed.
// It would be too late to refresh them now, as we're past
// the two year deposit expiration.
- await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
-
const orderResp = await merchantClient.createOrder({
order: {
fulfillment_url: "http://example.com",
@@ -195,7 +198,7 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
t.assertTrue(r.status === PreparePayResultType.PaymentPossible);
const cpr = await walletClient.call(WalletApiOperation.ConfirmPay, {
- proposalId: r.proposalId,
+ transactionId: r.transactionId,
});
t.assertTrue(cpr.type === ConfirmPayResultType.Done);
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts
index e3057451e..e26d9f964 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fakebank.ts
@@ -55,12 +55,14 @@ export async function runWithdrawalFakebankTest(t: GlobalTestState) {
accountName: "exchange",
accountPassword: "x",
wireGatewayApiBaseUrl: new URL(
- "/accounts/exchange/taler-wire-gateway",
+ "/accounts/exchange/taler-wire-gateway/",
bank.baseUrl,
).href,
accountPaytoUri: "payto://x-taler-bank/localhost/exchange",
});
+ await bank.createExchangeAccount("exchange", "x");
+
await bank.start();
await bank.pingUntilAvailable();
@@ -93,8 +95,6 @@ export async function runWithdrawalFakebankTest(t: GlobalTestState) {
const balResp = await wallet.client.call(WalletApiOperation.GetBalances, {});
t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available);
-
- await t.shutdown();
}
runWithdrawalFakebankTest.suites = ["wallet"];
diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts
new file mode 100644
index 000000000..c77f9ddda
--- /dev/null
+++ b/packages/taler-util/src/http-client/bank-core.ts
@@ -0,0 +1,489 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import {
+ AmountJson,
+ Amounts,
+ Logger
+} from "@gnu-taler/taler-util";
+import {
+ createPlatformHttpLib,
+ expectSuccessResponseOrThrow,
+ HttpRequestLibrary,
+ readSuccessResponseJsonOrThrow
+} from "@gnu-taler/taler-util/http";
+import { AccessToken, codecForAccountData, codecForBankAccountCreateWithdrawalResponse, codecForBankAccountGetWithdrawalResponse, codecForBankAccountTransactionInfo, codecForBankAccountTransactionsResponse, codecForCashoutConversionResponse, codecForCashoutPending, codecForCashouts, codecForCashoutStatusResponse, codecForConversionRatesResponse, codecForCoreBankConfig, codecForGlobalCashouts, codecForListBankAccountsResponse, codecForMonitorResponse, codecForPublicAccountsResponse, codecForTokenSuccessResponse, TalerAuthentication, TalerCorebankApi } from "./types.js";
+import { addPaginationParams, makeBasicAuthHeader, makeBearerTokenAuthHeader, PaginationParams, UserAndPassword, UserAndToken } from "./utils.js";
+import { TalerRevenueHttpClient } from "./bank-revenue.js";
+import { TalerWireGatewayHttpClient } from "./bank-wire.js";
+import { TalerBankIntegrationHttpClient } from "./bank-integration.js";
+
+const logger = new Logger("http-client/core-bank.ts");
+
+export class TalerCoreBankHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-token
+ *
+ * @returns
+ */
+ async createAccessToken(
+ auth: UserAndPassword,
+ body: TalerAuthentication.TokenRequest,
+ ): Promise<TalerAuthentication.TokenSuccessResponse> {
+ const url = new URL(`accounts/${auth.username}/token`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBasicAuthHeader(auth.username, auth.password),
+ },
+ body
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForTokenSuccessResponse());
+ }
+
+ async deleteAccessToken(
+ auth: UserAndToken,
+ ): Promise<void> {
+ const url = new URL(`accounts/${auth.username}/token`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "DELETE",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token),
+ }
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
+ *
+ */
+ async getConfig(): Promise<TalerCorebankApi.Config> {
+ const url = new URL(`config`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET"
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCoreBankConfig());
+ }
+
+ //
+ // ACCOUNTS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts
+ *
+ */
+ async createAccount(auth: AccessToken, body: TalerCorebankApi.RegisterAccountRequest): Promise<void> {
+ const url = new URL(`accounts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body,
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#delete--accounts-$USERNAME
+ *
+ */
+ async deleteAccount(auth: UserAndToken): Promise<void> {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "DELETE",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME
+ *
+ */
+ async updateAccount(auth: UserAndToken, body: TalerCorebankApi.AccountReconfiguration): Promise<void> {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "PATCH",
+ body,
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#patch--accounts-$USERNAME-auth
+ *
+ */
+ async updatePassword(auth: UserAndToken, body: TalerCorebankApi.AccountPasswordChange): Promise<void> {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "PATCH",
+ body,
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/get-$BANK_API_BASE_URL-public-accounts
+ *
+ */
+ async getPublicAccounts(): Promise<TalerCorebankApi.PublicAccountsResponse> {
+ const url = new URL(`public-accounts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForPublicAccountsResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts
+ *
+ */
+ async getAccounts(auth: AccessToken): Promise<TalerCorebankApi.ListBankAccountsResponse> {
+ const url = new URL(`accounts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForListBankAccountsResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME
+ *
+ */
+ async getAccount(auth: UserAndToken): Promise<TalerCorebankApi.AccountData> {
+ const url = new URL(`accounts/${auth.username}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForAccountData());
+ }
+
+ //
+ // TRANSACTIONS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions
+ *
+ */
+ async getTransactions(auth: UserAndToken, pagination?: PaginationParams): Promise<TalerCorebankApi.BankAccountTransactionsResponse> {
+ const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
+ addPaginationParams(url, pagination)
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionsResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get-$BANK_API_BASE_URL-accounts-$account_name-transactions-$transaction_id
+ *
+ */
+ async getTransactionById(auth: UserAndToken, txid: number): Promise<TalerCorebankApi.BankAccountTransactionInfo> {
+ const url = new URL(`accounts/${auth.username}/transactions/${String(txid)}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountTransactionInfo());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-transactions
+ *
+ */
+ async createTransaction(auth: UserAndToken, body: TalerCorebankApi.CreateBankAccountTransactionCreate): Promise<void> {
+ const url = new URL(`accounts/${auth.username}/transactions`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ //
+ // WITHDRAWALS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
+ *
+ */
+ async createWithdrawal(auth: UserAndToken, body: TalerCorebankApi.BankAccountCreateWithdrawalRequest): Promise<TalerCorebankApi.BankAccountCreateWithdrawalResponse> {
+ const url = new URL(`accounts/${auth.username}/withdrawals`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountCreateWithdrawalResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-accounts-$account_name-withdrawals
+ *
+ */
+ async getWithdrawalById(wid: string): Promise<TalerCorebankApi.BankAccountGetWithdrawalResponse> {
+ const url = new URL(`withdrawals/${wid}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankAccountGetWithdrawalResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-abort
+ *
+ */
+ async abortWithdrawalById(wid: string): Promise<void> {
+ const url = new URL(`withdrawals/${wid}/abort`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post-$BANK_API_BASE_URL-withdrawals-$withdrawal_id-confirm
+ *
+ */
+ async confirmWithdrawalById(wid: string): Promise<void> {
+ const url = new URL(`withdrawals/${wid}/confirm`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ //
+ // CASHOUTS
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts
+ *
+ */
+ async createCashout(auth: UserAndToken, body: TalerCorebankApi.CashoutRequest): Promise<TalerCorebankApi.CashoutPending> {
+ const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashoutPending());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-abort
+ *
+ */
+ async abortCashoutById(auth: UserAndToken, cid: string): Promise<void> {
+ const url = new URL(`accounts/${auth.username}/cashouts/${cid}/abort`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
+ *
+ */
+ async confirmCashoutById(auth: UserAndToken, cid: string, body: TalerCorebankApi.CashoutConfirmRequest): Promise<void> {
+ const url = new URL(`accounts/${auth.username}/cashouts/${cid}/confirm`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ body,
+ });
+ return expectSuccessResponseOrThrow(resp);
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#post--accounts-$USERNAME-cashouts-$CASHOUT_ID-confirm
+ *
+ */
+ async getCashoutRate(conversion: { debit?: AmountJson, credit?: AmountJson }): Promise<TalerCorebankApi.CashoutConversionResponse> {
+ const url = new URL(`cashout-rate`, this.baseUrl);
+ if (conversion.debit) {
+ url.searchParams.set("amount_debit", Amounts.stringify(conversion.debit))
+ }
+ if (conversion.credit) {
+ url.searchParams.set("amount_debit", Amounts.stringify(conversion.credit))
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashoutConversionResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts
+ *
+ */
+ async getAccountCashouts(auth: UserAndToken): Promise<TalerCorebankApi.Cashouts> {
+ const url = new URL(`accounts/${auth.username}/cashouts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashouts());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--cashouts
+ *
+ */
+ async getGlobalCashouts(auth: AccessToken): Promise<TalerCorebankApi.GlobalCashouts> {
+ const url = new URL(`cashouts`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForGlobalCashouts());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--accounts-$USERNAME-cashouts-$CASHOUT_ID
+ *
+ */
+ async getCashoutById(auth: UserAndToken, cid: string): Promise<TalerCorebankApi.CashoutStatusResponse> {
+ const url = new URL(`accounts/${auth.username}/cashouts/${cid}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBearerTokenAuthHeader(auth.token)
+ },
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForCashoutStatusResponse());
+ }
+
+ //
+ // CONVERSION RATE
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--conversion-rates
+ *
+ */
+ async getConversionRates(): Promise<TalerCorebankApi.ConversionRatesResponse> {
+ const url = new URL(`conversion-rates`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForConversionRatesResponse());
+ }
+
+ //
+ // MONITOR
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#get--monitor
+ *
+ */
+ async getMonitor(params: { timeframe: TalerCorebankApi.MonitorTimeframeParam, which: number }): Promise<TalerCorebankApi.MonitorResponse> {
+ const url = new URL(`monitor`, this.baseUrl);
+ url.searchParams.set("timeframe", params.timeframe.toString())
+ url.searchParams.set("which", String(params.which))
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForMonitorResponse());
+ }
+
+ //
+ // Others API
+ //
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
+ *
+ */
+ getIntegrationAPI(): TalerBankIntegrationHttpClient {
+ const url = new URL(`taler-integration`, this.baseUrl);
+ return new TalerBankIntegrationHttpClient(url.href, this.httpLib)
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
+ *
+ */
+ getWireGatewayAPI(username: string): TalerWireGatewayHttpClient {
+ const url = new URL(`accounts/${username}/taler-wire-gateway`, this.baseUrl);
+ return new TalerWireGatewayHttpClient(url.href, username, this.httpLib)
+ }
+
+ /**
+ * https://docs.taler.net/core/api-corebank.html#taler-bank-integration-api
+ *
+ */
+ getRevenueAPI(username: string): TalerRevenueHttpClient {
+ const url = new URL(`accounts/${username}/taler-revenue`, this.baseUrl);
+ return new TalerRevenueHttpClient(url.href, username, this.httpLib,)
+ }
+
+}
+
diff --git a/packages/taler-util/src/http-client/bank-integration.ts b/packages/taler-util/src/http-client/bank-integration.ts
new file mode 100644
index 000000000..cd6462417
--- /dev/null
+++ b/packages/taler-util/src/http-client/bank-integration.ts
@@ -0,0 +1,47 @@
+import { HttpRequestLibrary, readSuccessResponseJsonOrThrow } from "../http-common.js";
+import { createPlatformHttpLib } from "../http.js";
+import {
+ TalerBankIntegrationApi,
+ codecForBankWithdrawalOperationPostResponse,
+ codecForBankWithdrawalOperationStatus
+} from "./types.js";
+
+export class TalerBankIntegrationHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+ /**
+ * https://docs.taler.net/core/api-bank-integration.html#get-$BANK_API_BASE_URL-withdrawal-operation-$wopid
+ *
+ */
+ async getWithdrawalOperationById(woid: string, timeoutMs?: number): Promise<TalerBankIntegrationApi.BankWithdrawalOperationStatus> {
+ const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl);
+ if (timeoutMs) {
+ url.searchParams.set("long_poll_ms", String(timeoutMs))
+ }
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET"
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankWithdrawalOperationStatus());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-bank-integration.html#post-$BANK_API_BASE_URL-withdrawal-operation-$wopid
+ *
+ */
+ async completeWithdrawalOperationById(woid: string): Promise<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse> {
+ const url = new URL(`withdrawal-operation/${woid}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForBankWithdrawalOperationPostResponse());
+ }
+
+}
+
diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts
new file mode 100644
index 000000000..99ff71457
--- /dev/null
+++ b/packages/taler-util/src/http-client/bank-revenue.ts
@@ -0,0 +1,32 @@
+import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js";
+import { createPlatformHttpLib } from "../http.js";
+import { TalerRevenueApi, codecForMerchantIncomingHistory } from "./types.js";
+import { UserAndPassword } from "./utils.js";
+
+export class TalerRevenueHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ private username: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+ /**
+ * https://docs.taler.net/core/api-bank-revenue.html#get-$BASE_URL-history
+ *
+ * @returns
+ */
+ async getHistory(auth: string): Promise<TalerRevenueApi.MerchantIncomingHistory> {
+ const url = new URL(`history`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBasicAuthHeader(this.username, auth),
+ }
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForMerchantIncomingHistory());
+ }
+} \ No newline at end of file
diff --git a/packages/taler-util/src/http-client/bank-wire.ts b/packages/taler-util/src/http-client/bank-wire.ts
new file mode 100644
index 000000000..9f2b859ed
--- /dev/null
+++ b/packages/taler-util/src/http-client/bank-wire.ts
@@ -0,0 +1,98 @@
+import { HttpRequestLibrary, makeBasicAuthHeader, readSuccessResponseJsonOrThrow } from "../http-common.js";
+import { createPlatformHttpLib } from "../http.js";
+import { TalerWireGatewayApi, codecForAddIncomingResponse, codecForIncomingHistory, codecForOutgoingHistory, codecForTransferResponse } from "./types.js";
+import { PaginationParams, UserAndPassword, addPaginationParams } from "./utils.js";
+
+/**
+ * The API is used by the exchange to trigger transactions and query
+ * incoming transactions, as well as by the auditor to query incoming
+ * and outgoing transactions.
+ *
+ * https://docs.taler.net/core/api-bank-wire.html
+ */
+export class TalerWireGatewayHttpClient {
+ httpLib: HttpRequestLibrary;
+
+ constructor(
+ private baseUrl: string,
+ private username: string,
+ httpClient?: HttpRequestLibrary,
+ ) {
+ this.httpLib = httpClient ?? createPlatformHttpLib();
+ }
+
+ /**
+ * https://docs.taler.net/core/api-bank-wire.html#post-$BASE_URL-transfer
+ *
+ */
+ async transfer(
+ auth: string,
+ body: TalerWireGatewayApi.TransferRequest,
+ ): Promise<TalerWireGatewayApi.TransferResponse> {
+ const url = new URL(`transfer`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBasicAuthHeader(this.username, auth),
+ },
+ body
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForTransferResponse());
+ }
+
+ /**
+ * https://docs.taler.net/core/api-bank-wire.html#get-$BASE_URL-history-incoming
+ *
+ */
+ async getHistoryIncoming(
+ auth: string,
+ pagination?: PaginationParams
+ ): Promise<TalerWireGatewayApi.IncomingHistory> {
+ const url = new URL(`history/incoming`, this.baseUrl);
+ addPaginationParams(url, pagination)
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBasicAuthHeader(this.username, auth),
+ }
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForIncomingHistory());
+ }
+ /**
+ * https://docs.taler.net/core/api-bank-wire.html#get-$BASE_URL-history-outgoing
+ *
+ */
+ async getHistoryOutgoing(
+ auth: string,
+ pagination?: PaginationParams
+ ): Promise<TalerWireGatewayApi.OutgoingHistory> {
+ const url = new URL(`history/outgoing`, this.baseUrl);
+ addPaginationParams(url, pagination)
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "GET",
+ headers: {
+ Authorization: makeBasicAuthHeader(this.username, auth),
+ }
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForOutgoingHistory());
+ }
+ /**
+ * https://docs.taler.net/core/api-bank-wire.html#post-$BASE_URL-admin-add-incoming
+ *
+ */
+ async addIncoming(
+ auth: string,
+ body: TalerWireGatewayApi.AddIncomingRequest,
+ ): Promise<TalerWireGatewayApi.AddIncomingResponse> {
+ const url = new URL(`admin/add-incoming`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ headers: {
+ Authorization: makeBasicAuthHeader(this.username, auth),
+ },
+ body
+ });
+ return readSuccessResponseJsonOrThrow(resp, codecForAddIncomingResponse());
+ }
+}
+
diff --git a/packages/taler-util/src/http-client/index.ts b/packages/taler-util/src/http-client/index.ts
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/packages/taler-util/src/http-client/index.ts
diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts
new file mode 100644
index 000000000..66ac39f59
--- /dev/null
+++ b/packages/taler-util/src/http-client/types.ts
@@ -0,0 +1,1183 @@
+import { codecForAmountString } from "../amounts.js";
+import { Codec, buildCodecForObject, buildCodecForUnion, codecForBoolean, codecForConstString, codecForEither, codecForList, codecForMap, codecForNumber, codecForString, codecOptional } from "../codec.js";
+import { codecForTimestamp } from "../time.js";
+
+
+///
+/// HASH
+///
+
+// 64-byte hash code.
+type HashCode = string;
+
+// 32-byte hash code.
+type ShortHashCode = string;
+
+// 16-byte salt.
+type WireSalt = string;
+
+type SHA256HashCode = ShortHashCode;
+
+type SHA512HashCode = HashCode;
+
+// 32-byte nonce value, must only be used once.
+type CSNonce = string;
+
+// 32-byte nonce value, must only be used once.
+type RefreshMasterSeed = string;
+
+// 32-byte value representing a point on Curve25519.
+type Cs25519Point = string;
+
+// 32-byte value representing a scalar multiplier
+// for scalar operations on points on Curve25519.
+type Cs25519Scalar = string;
+
+///
+/// KEYS
+///
+
+// 16-byte access token used to authorize access.
+type ClaimToken = string;
+
+// EdDSA and ECDHE public keys always point on Curve25519
+// and represented using the standard 256 bits Ed25519 compact format,
+// converted to Crockford Base32.
+type EddsaPublicKey = string;
+
+// EdDSA and ECDHE public keys always point on Curve25519
+// and represented using the standard 256 bits Ed25519 compact format,
+// converted to Crockford Base32.
+type EddsaPrivateKey = string;
+
+// Edx25519 public keys are points on Curve25519 and represented using the
+// standard 256 bits Ed25519 compact format converted to Crockford
+// Base32.
+type Edx25519PublicKey = string;
+
+// Edx25519 private keys are always points on Curve25519
+// and represented using the standard 256 bits Ed25519 compact format,
+// converted to Crockford Base32.
+type Edx25519PrivateKey = string;
+
+// EdDSA and ECDHE public keys always point on Curve25519
+// and represented using the standard 256 bits Ed25519 compact format,
+// converted to Crockford Base32.
+type EcdhePublicKey = string;
+
+// Point on Curve25519 represented using the standard 256 bits Ed25519 compact format,
+// converted to Crockford Base32.
+type CsRPublic = string;
+
+// EdDSA and ECDHE public keys always point on Curve25519
+// and represented using the standard 256 bits Ed25519 compact format,
+// converted to Crockford Base32.
+type EcdhePrivateKey = string;
+
+type CoinPublicKey = EddsaPublicKey;
+
+// RSA public key converted to Crockford Base32.
+type RsaPublicKey = string;
+
+type Integer = number;
+
+type WireTransferIdentifierRawP = string;
+// Subset of numbers: Integers in the
+// inclusive range 0 .. (2^53 - 1).
+type SafeUint64 = number;
+
+// The string must be a data URL according to RFC 2397
+// with explicit mediatype and base64 parameters.
+//
+// data:<mediatype>;base64,<data>
+//
+// Supported mediatypes are image/jpeg and image/png.
+// Invalid strings will be rejected by the wallet.
+type ImageDataUrl = string;
+
+
+// <Currency>:<DecimalAmount>.
+type Amount = string;
+
+type WadId = string;
+
+interface Timestamp {
+ // Seconds since epoch, or the special
+ // value "never" to represent an event that will
+ // never happen.
+ t_s: number | "never";
+}
+
+interface RelativeTime {
+ // Duration in microseconds or "forever"
+ // to represent an infinite duration. Numeric
+ // values are capped at 2^53 - 1 inclusive.
+ d_us: number | "forever";
+}
+
+export interface LoginToken {
+ token: AccessToken,
+ expiration: Timestamp,
+}
+// token used to get loginToken
+// must forget after used
+declare const __ac_token: unique symbol;
+export type AccessToken = string & {
+ [__ac_token]: true;
+};
+
+export namespace TalerAuthentication {
+
+ export interface TokenRequest {
+ // Service-defined scope for the token.
+ // Typical scopes would be "readonly" or "readwrite".
+ scope: string;
+
+ // Server may impose its own upper bound
+ // on the token validity duration
+ duration?: RelativeTime;
+
+ // Is the token refreshable into a new token during its
+ // validity?
+ // Refreshable tokens effectively provide indefinite
+ // access if they are refreshed in time.
+ refreshable?: boolean;
+ }
+
+ export interface TokenSuccessResponse {
+ // Expiration determined by the server.
+ // Can be based on the token_duration
+ // from the request, but ultimately the
+ // server decides the expiration.
+ expiration: Timestamp;
+
+ // Opque access token.
+ access_token: AccessToken;
+ }
+}
+
+export interface CurrencySpecification {
+
+ // Name of the currency.
+ name: string;
+
+ // Decimal separator for fractional digits.
+ decimal_separator: string;
+
+ // how many digits the user may enter after the decimal_separator
+ num_fractional_input_digits: Integer;
+
+ // Number of fractional digits to render in normal font and size.
+ num_fractional_normal_digits: Integer;
+
+ // Number of fractional digits to render always, if needed by
+ // padding with zeros.
+ num_fractional_trailing_zero_digits: Integer;
+
+ // Whether the currency name should be rendered before (true) or
+ // after (false) the numeric value
+ is_currency_name_leading: boolean;
+
+ // map of powers of 10 to alternative currency names / symbols, must
+ // always have an entry under "0" that defines the base name,
+ // e.g. "0 => €" or "3 => k€". For BTC, would be "0 => BTC, -3 => mBTC".
+ // Communicates the currency symbol to be used.
+ alt_unit_names: { [log10: string]: string };
+}
+
+export const codecForAccessToken = codecForString as () => Codec<AccessToken>;
+export const codecForTokenSuccessResponse =
+ (): Codec<TalerAuthentication.TokenSuccessResponse> =>
+ buildCodecForObject<TalerAuthentication.TokenSuccessResponse>()
+ .property("access_token", codecForAccessToken())
+ .property("expiration", codecForTimestamp)
+ .build("TalerAuthentication.TokenSuccessResponse")
+
+export const codecForCurrencySpecificiation =
+ (): Codec<CurrencySpecification> =>
+ buildCodecForObject<CurrencySpecification>()
+ .property("name", codecForString())
+ .property("decimal_separator", codecForString())
+ .property("num_fractional_input_digits", codecForNumber())
+ .property("num_fractional_normal_digits", codecForNumber())
+ .property("num_fractional_trailing_zero_digits", codecForNumber())
+ .property("is_currency_name_leading", codecForBoolean())
+ .property("alt_unit_names", codecForMap(codecForString()))
+ .build("CurrencySpecification")
+
+export const codecForCoreBankConfig =
+ (): Codec<TalerCorebankApi.Config> =>
+ buildCodecForObject<TalerCorebankApi.Config>()
+ .property("name", codecForString())
+ .property("version", codecForString())
+ .property("have_cashout", codecOptional(codecForBoolean()))
+ .property("currency", codecForCurrencySpecificiation())
+ .property("fiat_currency", codecOptional(codecForCurrencySpecificiation()))
+ .build("TalerCorebankApi.Config")
+
+const codecForBalance = (): Codec<TalerCorebankApi.Balance> =>
+ buildCodecForObject<TalerCorebankApi.Balance>()
+ .property("amount", codecForAmountString())
+ .property("credit_debit_indicator", codecForEither(codecForConstString("credit"), codecForConstString("debit")))
+ .build("TalerCorebankApi.Balance")
+
+const codecForPublicAccount = (): Codec<TalerCorebankApi.PublicAccount> =>
+ buildCodecForObject<TalerCorebankApi.PublicAccount>()
+ .property("account_name", codecForString())
+ .property("balance", codecForBalance())
+ .property("payto_uri", codecForPaytoURI())
+ .build("TalerCorebankApi.PublicAccount")
+
+export const codecForPublicAccountsResponse =
+ (): Codec<TalerCorebankApi.PublicAccountsResponse> =>
+ buildCodecForObject<TalerCorebankApi.PublicAccountsResponse>()
+ .property("public_accounts", codecForList(codecForPublicAccount()))
+ .build("TalerCorebankApi.PublicAccountsResponse")
+
+
+export const codecForAccountMinimalData =
+ (): Codec<TalerCorebankApi.AccountMinimalData> =>
+ buildCodecForObject<TalerCorebankApi.AccountMinimalData>()
+ .property("balance", codecForBalance())
+ .property("debit_threshold", codecForAmountString())
+ .property("name", codecForString())
+ .property("username", codecForString())
+ .build("TalerCorebankApi.AccountMinimalData")
+
+export const codecForListBankAccountsResponse =
+ (): Codec<TalerCorebankApi.ListBankAccountsResponse> =>
+ buildCodecForObject<TalerCorebankApi.ListBankAccountsResponse>()
+ .property("accounts", codecForList(codecForAccountMinimalData()))
+ .build("TalerCorebankApi.ListBankAccountsResponse")
+
+export const codecForAccountData =
+ (): Codec<TalerCorebankApi.AccountData> =>
+ buildCodecForObject<TalerCorebankApi.AccountData>()
+ .property("name", codecForString())
+ .property("balance", codecForBalance())
+ .property("payto_uri", codecForPaytoURI())
+ .property("debit_threshold", codecForAmountString())
+ .property("contact_data", codecOptional(codecForChallengeContactData()))
+ .property("cashout_payto_uri", codecOptional(codecForPaytoURI()))
+ .build("TalerCorebankApi.AccountData")
+
+
+export const codecForChallengeContactData =
+ (): Codec<TalerCorebankApi.ChallengeContactData> =>
+ buildCodecForObject<TalerCorebankApi.ChallengeContactData>()
+ .property("email", codecOptional(codecForString()))
+ .property("phone", codecOptional(codecForString()))
+ .build("TalerCorebankApi.ChallengeContactData")
+
+export const codecForBankAccountTransactionsResponse =
+ (): Codec<TalerCorebankApi.BankAccountTransactionsResponse> =>
+ buildCodecForObject<TalerCorebankApi.BankAccountTransactionsResponse>()
+ .property("transactions", codecForList(codecForBankAccountTransactionInfo()))
+ .build("TalerCorebankApi.BankAccountTransactionsResponse");
+
+export const codecForBankAccountTransactionInfo =
+ (): Codec<TalerCorebankApi.BankAccountTransactionInfo> =>
+ buildCodecForObject<TalerCorebankApi.BankAccountTransactionInfo>()
+ .property("amount", codecForAmountString())
+ .property("creditor_payto_uri", codecForPaytoURI())
+ .property("date", codecForTimestamp)
+ .property("debtor_payto_uri", codecForPaytoURI())
+ .property("direction", codecForEither(codecForConstString("debit"), codecForConstString("credit")))
+ .property("row_id", codecForNumber())
+ .property("subject", codecForString())
+ .build("TalerCorebankApi.BankAccountTransactionInfo");
+
+export const codecForBankAccountCreateWithdrawalResponse =
+ (): Codec<TalerCorebankApi.BankAccountCreateWithdrawalResponse> =>
+ buildCodecForObject<TalerCorebankApi.BankAccountCreateWithdrawalResponse>()
+ .property("taler_withdraw_uri", codecForTalerWithdrawalURI())
+ .property("withdrawal_id", codecForString())
+ .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse");
+
+export const codecForBankAccountGetWithdrawalResponse =
+ (): Codec<TalerCorebankApi.BankAccountGetWithdrawalResponse> =>
+ buildCodecForObject<TalerCorebankApi.BankAccountGetWithdrawalResponse>()
+ .property("aborted", codecForBoolean())
+ .property("amount", codecForAmountString())
+ .property("confirmation_done", codecForBoolean())
+ .property("selected_exchange_account", codecOptional(codecForString()))
+ .property("selected_reserve_pub", codecOptional(codecForString()))
+ .property("selection_done", (codecForBoolean()))
+ .build("TalerCorebankApi.BankAccountGetWithdrawalResponse");
+
+export const codecForCashoutPending =
+ (): Codec<TalerCorebankApi.CashoutPending> =>
+ buildCodecForObject<TalerCorebankApi.CashoutPending>()
+ .property("cashout_id", codecForString())
+ .build("TalerCorebankApi.CashoutPending");
+
+export const codecForCashoutConversionResponse =
+ (): Codec<TalerCorebankApi.CashoutConversionResponse> =>
+ buildCodecForObject<TalerCorebankApi.CashoutConversionResponse>()
+ .property("amount_credit", codecForAmountString())
+ .property("amount_debit", codecForAmountString())
+ .build("TalerCorebankApi.CashoutConversionResponse");
+
+export const codecForCashouts =
+ (): Codec<TalerCorebankApi.Cashouts> =>
+ buildCodecForObject<TalerCorebankApi.Cashouts>()
+ .property("cashouts", codecForList(codecForCashoutInfo()))
+ .build("TalerCorebankApi.Cashouts");
+
+export const codecForCashoutInfo =
+ (): Codec<TalerCorebankApi.CashoutInfo> =>
+ buildCodecForObject<TalerCorebankApi.CashoutInfo>()
+ .property("cashout_id", codecForString())
+ .property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed"),))
+ .build("TalerCorebankApi.CashoutInfo");
+
+export const codecForGlobalCashouts =
+ (): Codec<TalerCorebankApi.GlobalCashouts> =>
+ buildCodecForObject<TalerCorebankApi.GlobalCashouts>()
+ .property("cashouts", codecForList(codecForGlobalCashoutInfo()))
+ .build("TalerCorebankApi.GlobalCashouts");
+
+export const codecForGlobalCashoutInfo =
+ (): Codec<TalerCorebankApi.GlobalCashoutInfo> =>
+ buildCodecForObject<TalerCorebankApi.GlobalCashoutInfo>()
+ .property("cashout_id", codecForString())
+ .property("username", codecForString())
+ .property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed"),))
+ .build("TalerCorebankApi.GlobalCashoutInfo");
+
+export const codecForCashoutStatusResponse =
+ (): Codec<TalerCorebankApi.CashoutStatusResponse> =>
+ buildCodecForObject<TalerCorebankApi.CashoutStatusResponse>()
+ .property("amount_credit", codecForAmountString())
+ .property("amount_debit", codecForAmountString())
+ .property("confirmation_time", codecForTimestamp)
+ .property("creation_time", codecForTimestamp)
+ .property("credit_payto_uri", codecForPaytoURI())
+ .property("status", codecForEither(codecForConstString("pending"), codecForConstString("confirmed")))
+ .property("subject", codecForString())
+ .build("TalerCorebankApi.CashoutStatusResponse");
+
+export const codecForConversionRatesResponse =
+ (): Codec<TalerCorebankApi.ConversionRatesResponse> =>
+ buildCodecForObject<TalerCorebankApi.ConversionRatesResponse>()
+ .property("buy_at_ratio", codecForDecimalNumber())
+ .property("buy_in_fee", codecForDecimalNumber())
+ .property("sell_at_ratio", codecForDecimalNumber())
+ .property("sell_out_fee", codecForDecimalNumber())
+ .build("TalerCorebankApi.ConversionRatesResponse");
+
+export const codecForMonitorResponse =
+ (): Codec<TalerCorebankApi.MonitorResponse> =>
+ buildCodecForObject<TalerCorebankApi.MonitorResponse>()
+ .property("cashinCount", codecForNumber())
+ .property("cashinExternalVolume", codecForAmountString())
+ .property("cashoutCount", codecForNumber())
+ .property("cashoutExternalVolume", codecForAmountString())
+ .property("talerPayoutCount", codecForNumber())
+ .property("talerPayoutInternalVolume", codecForAmountString())
+ .build("TalerCorebankApi.MonitorResponse");
+
+export const codecForBankVersion =
+ (): Codec<TalerBankIntegrationApi.BankVersion> =>
+ buildCodecForObject<TalerBankIntegrationApi.BankVersion>()
+ .property("currency", codecForCurrencyName())
+ .property("currency_specification", codecForCurrencySpecificiation())
+ .property("name", codecForConstString("taler-bank-integration"))
+ .property("version", codecForLibtoolVersion())
+ .build("TalerBankIntegrationApi.BankVersion");
+
+export const codecForBankWithdrawalOperationStatus =
+ (): Codec<TalerBankIntegrationApi.BankWithdrawalOperationStatus> =>
+ buildCodecForObject<TalerBankIntegrationApi.BankWithdrawalOperationStatus>()
+ .property("aborted", codecForBoolean())
+ .property("amount", codecForAmountString())
+ .property("confirm_transfer_url", codecOptional(codecForURL()))
+ .property("selection_done", codecForBoolean())
+ .property("sender_wire", codecForPaytoURI())
+ .property("suggested_exchange", codecOptional(codecForString()))
+ .property("transfer_done", codecForBoolean())
+ .property("wire_types", codecForList(codecForString()))
+ .build("TalerBankIntegrationApi.BankWithdrawalOperationStatus");
+
+export const codecForBankWithdrawalOperationPostResponse =
+ (): Codec<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse> =>
+ buildCodecForObject<TalerBankIntegrationApi.BankWithdrawalOperationPostResponse>()
+ .property("confirm_transfer_url", codecOptional(codecForURL()))
+ .property("transfer_done", codecForBoolean())
+ .build("TalerBankIntegrationApi.BankWithdrawalOperationPostResponse");
+
+export const codecForMerchantIncomingHistory =
+ (): Codec<TalerRevenueApi.MerchantIncomingHistory> =>
+ buildCodecForObject<TalerRevenueApi.MerchantIncomingHistory>()
+ .property("credit_account", codecForPaytoURI())
+ .property("incoming_transactions", codecForList(codecForMerchantIncomingBankTransaction()))
+ .build("TalerRevenueApi.MerchantIncomingHistory");
+
+export const codecForMerchantIncomingBankTransaction =
+ (): Codec<TalerRevenueApi.MerchantIncomingBankTransaction> =>
+ buildCodecForObject<TalerRevenueApi.MerchantIncomingBankTransaction>()
+ .property("amount", codecForAmountString())
+ .property("date", codecForTimestamp)
+ .property("debit_account", codecForPaytoURI())
+ .property("exchange_url", codecForURL())
+ .property("row_id", codecForNumber())
+ .property("wtid", codecForString())
+ .build("TalerRevenueApi.MerchantIncomingBankTransaction");
+
+export const codecForTransferResponse =
+ (): Codec<TalerWireGatewayApi.TransferResponse> =>
+ buildCodecForObject<TalerWireGatewayApi.TransferResponse>()
+ .property("row_id", codecForNumber())
+ .property("timestamp", codecForTimestamp)
+ .build("TalerWireGatewayApi.TransferResponse");
+
+export const codecForIncomingHistory =
+ (): Codec<TalerWireGatewayApi.IncomingHistory> =>
+ buildCodecForObject<TalerWireGatewayApi.IncomingHistory>()
+ .property("credit_account", codecForString())
+ .property("incoming_transactions", codecForList(codecForIncomingBankTransaction()))
+ .build("TalerWireGatewayApi.IncomingHistory");
+
+export const codecForIncomingBankTransaction = (): Codec<TalerWireGatewayApi.IncomingBankTransaction> => buildCodecForUnion<TalerWireGatewayApi.IncomingBankTransaction>()
+ .discriminateOn("type")
+ .alternative("RESERVE", codecForIncomingReserveTransaction())
+ .alternative("WAD", codecForIncomingWadTransaction())
+ .build("TalerWireGatewayApi.IncomingBankTransaction");
+
+export const codecForIncomingReserveTransaction =
+ (): Codec<TalerWireGatewayApi.IncomingReserveTransaction> =>
+ buildCodecForObject<TalerWireGatewayApi.IncomingReserveTransaction>()
+ .property("amount", codecForAmountString())
+ .property("date", codecForTimestamp)
+ .property("debit_account", codecForPaytoURI())
+ .property("reserve_pub", codecForString())
+ .property("row_id", codecForNumber())
+ .property("type", codecForConstString("RESERVE"))
+ .build("TalerWireGatewayApi.IncomingReserveTransaction");
+
+export const codecForIncomingWadTransaction =
+ (): Codec<TalerWireGatewayApi.IncomingWadTransaction> =>
+ buildCodecForObject<TalerWireGatewayApi.IncomingWadTransaction>()
+ .property("amount", codecForAmountString())
+ .property("credit_account", codecForPaytoURI())
+ .property("date", codecForTimestamp)
+ .property("debit_account", codecForPaytoURI())
+ .property("origin_exchange_url", codecForURL())
+ .property("row_id", codecForNumber())
+ .property("type", codecForConstString("WAD"))
+ .property("wad_id", codecForString())
+ .build("TalerWireGatewayApi.IncomingWadTransaction");
+
+export const codecForOutgoingHistory =
+ (): Codec<TalerWireGatewayApi.OutgoingHistory> =>
+ buildCodecForObject<TalerWireGatewayApi.OutgoingHistory>()
+ .property("debit_account", codecForString())
+ .property("outgoing_transactions", codecForList(codecForOutgoingBankTransaction()))
+ .build("TalerWireGatewayApi.OutgoingHistory");
+
+export const codecForOutgoingBankTransaction =
+ (): Codec<TalerWireGatewayApi.OutgoingBankTransaction> =>
+ buildCodecForObject<TalerWireGatewayApi.OutgoingBankTransaction>()
+ .property("amount", codecForAmountString())
+ .property("credit_account", codecForPaytoURI())
+ .property("date", codecForTimestamp)
+ .property("exchange_base_url", codecForURL())
+ .property("row_id", codecForNumber())
+ .property("wtid", codecForString())
+ .build("TalerWireGatewayApi.OutgoingBankTransaction");
+
+export const codecForAddIncomingResponse =
+ (): Codec<TalerWireGatewayApi.AddIncomingResponse> =>
+ buildCodecForObject<TalerWireGatewayApi.AddIncomingResponse>()
+ .property("row_id", codecForNumber())
+ .property("timestamp", codecForTimestamp)
+ .build("TalerWireGatewayApi.AddIncomingResponse");
+
+// export const codecFor =
+// (): Codec<TalerWireGatewayApi.PublicAccountsResponse> =>
+// buildCodecForObject<TalerWireGatewayApi.PublicAccountsResponse>()
+// .property("", codecForString())
+// .build("TalerWireGatewayApi.PublicAccountsResponse");
+
+
+type EmailAddress = string;
+type PhoneNumber = string;
+type DecimalNumber = string;
+
+const codecForURL = codecForString
+const codecForLibtoolVersion = codecForString
+const codecForCurrencyName = codecForString
+const codecForPaytoURI = codecForString
+const codecForTalerWithdrawalURI = codecForString
+const codecForDecimalNumber = codecForString
+
+enum TanChannel {
+ SMS = "sms",
+ EMAIL = "email",
+ FILE = "file"
+}
+
+export namespace TalerWireGatewayApi {
+
+ export interface TransferResponse {
+
+ // Timestamp that indicates when the wire transfer will be executed.
+ // In cases where the wire transfer gateway is unable to know when
+ // the wire transfer will be executed, the time at which the request
+ // has been received and stored will be returned.
+ // The purpose of this field is for debugging (humans trying to find
+ // the transaction) as well as for taxation (determining which
+ // time period a transaction belongs to).
+ timestamp: Timestamp;
+
+ // Opaque ID of the transaction that the bank has made.
+ row_id: SafeUint64;
+ }
+
+ export interface TransferRequest {
+ // Nonce to make the request idempotent. Requests with the same
+ // transaction_uid that differ in any of the other fields
+ // are rejected.
+ request_uid: HashCode;
+
+ // Amount to transfer.
+ amount: Amount;
+
+ // Base URL of the exchange. Shall be included by the bank gateway
+ // in the appropriate section of the wire transfer details.
+ exchange_base_url: string;
+
+ // Wire transfer identifier chosen by the exchange,
+ // used by the merchant to identify the Taler order(s)
+ // associated with this wire transfer.
+ wtid: ShortHashCode;
+
+ // The recipient's account identifier as a payto URI.
+ credit_account: string;
+ }
+
+ export interface IncomingHistory {
+
+ // Array of incoming transactions.
+ incoming_transactions: IncomingBankTransaction[];
+
+ // Payto URI to identify the receiver of funds.
+ // This must be one of the exchange's bank accounts.
+ // Credit account is shared by all incoming transactions
+ // as per the nature of the request.
+ credit_account: string;
+
+ }
+
+ // Union discriminated by the "type" field.
+ export type IncomingBankTransaction =
+ | IncomingReserveTransaction
+ | IncomingWadTransaction;
+
+ export interface IncomingReserveTransaction {
+ type: "RESERVE";
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount transferred.
+ amount: Amount;
+
+ // Payto URI to identify the sender of funds.
+ debit_account: string;
+
+ // The reserve public key extracted from the transaction details.
+ reserve_pub: EddsaPublicKey;
+
+ }
+
+ export interface IncomingWadTransaction {
+ type: "WAD";
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount transferred.
+ amount: Amount;
+
+ // Payto URI to identify the receiver of funds.
+ // This must be one of the exchange's bank accounts.
+ credit_account: string;
+
+ // Payto URI to identify the sender of funds.
+ debit_account: string;
+
+ // Base URL of the exchange that originated the wad.
+ origin_exchange_url: string;
+
+ // The reserve public key extracted from the transaction details.
+ wad_id: WadId;
+ }
+
+
+ export interface OutgoingHistory {
+
+ // Array of outgoing transactions.
+ outgoing_transactions: OutgoingBankTransaction[];
+
+ // Payto URI to identify the sender of funds.
+ // This must be one of the exchange's bank accounts.
+ // Credit account is shared by all incoming transactions
+ // as per the nature of the request.
+ debit_account: string;
+
+ }
+
+ export interface OutgoingBankTransaction {
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount transferred.
+ amount: Amount;
+
+ // Payto URI to identify the receiver of funds.
+ credit_account: string;
+
+ // The wire transfer ID in the outgoing transaction.
+ wtid: ShortHashCode;
+
+ // Base URL of the exchange.
+ exchange_base_url: string;
+ }
+
+ export interface AddIncomingRequest {
+ // Amount to transfer.
+ amount: Amount;
+
+ // Reserve public key that is included in the wire transfer details
+ // to identify the reserve that is being topped up.
+ reserve_pub: EddsaPublicKey;
+
+ // Account (as payto URI) that makes the wire transfer to the exchange.
+ // Usually this account must be created by the test harness before this API is
+ // used. An exception is the "exchange-fakebank", where any debit account can be
+ // specified, as it is automatically created.
+ debit_account: string;
+ }
+
+ export interface AddIncomingResponse {
+
+ // Timestamp that indicates when the wire transfer will be executed.
+ // In cases where the wire transfer gateway is unable to know when
+ // the wire transfer will be executed, the time at which the request
+ // has been received and stored will be returned.
+ // The purpose of this field is for debugging (humans trying to find
+ // the transaction) as well as for taxation (determining which
+ // time period a transaction belongs to).
+ timestamp: Timestamp;
+
+ // Opaque ID of the transaction that the bank has made.
+ row_id: SafeUint64;
+ }
+
+
+
+}
+
+export namespace TalerRevenueApi {
+ export interface MerchantIncomingHistory {
+
+ // Array of incoming transactions.
+ incoming_transactions: MerchantIncomingBankTransaction[];
+
+ // Payto URI to identify the receiver of funds.
+ // This must be one of the merchant's bank accounts.
+ // Credit account is shared by all incoming transactions
+ // as per the nature of the request.
+ credit_account: string;
+
+ }
+
+ export interface MerchantIncomingBankTransaction {
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount transferred.
+ amount: Amount;
+
+ // Payto URI to identify the sender of funds.
+ debit_account: string;
+
+ // Base URL of the exchange where the transfer originated form.
+ exchange_url: string;
+
+ // The wire transfer identifier.
+ wtid: WireTransferIdentifierRawP;
+ }
+}
+
+export namespace TalerBankIntegrationApi {
+ export interface BankVersion {
+ // libtool-style representation of the Bank protocol version, see
+ // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+ // The format is "current:revision:age".
+ version: string;
+
+ // Currency used by this bank.
+ currency: string;
+
+ // How the bank SPA should render this currency.
+ currency_specification: CurrencySpecification;
+
+ // Name of the API.
+ name: "taler-bank-integration";
+ }
+
+ export interface BankWithdrawalOperationStatus {
+ // Indicates whether the withdrawal was aborted.
+ aborted: boolean;
+
+ // Has the wallet selected parameters for the withdrawal operation
+ // (exchange and reserve public key) and successfully sent it
+ // to the bank?
+ selection_done: boolean;
+
+ // The transfer has been confirmed and registered by the bank.
+ // Does not guarantee that the funds have arrived at the exchange already.
+ transfer_done: boolean;
+
+ // Amount that will be withdrawn with this operation
+ // (raw amount without fee considerations).
+ amount: Amount;
+
+ // Bank account of the customer that is withdrawing, as a
+ // payto URI.
+ sender_wire?: string;
+
+ // Suggestion for an exchange given by the bank.
+ suggested_exchange?: string;
+
+ // URL that the user needs to navigate to in order to
+ // complete some final confirmation (e.g. 2FA).
+ // It may contain withdrawal operation id
+ confirm_transfer_url?: string;
+
+ // Wire transfer types supported by the bank.
+ wire_types: string[];
+ }
+
+ export interface BankWithdrawalOperationPostRequest {
+
+ // Reserve public key.
+ reserve_pub: string;
+
+ // Payto address of the exchange selected for the withdrawal.
+ selected_exchange: string;
+ }
+
+ export interface BankWithdrawalOperationPostResponse {
+
+ // The transfer has been confirmed and registered by the bank.
+ // Does not guarantee that the funds have arrived at the exchange already.
+ transfer_done: boolean;
+
+ // URL that the user needs to navigate to in order to
+ // complete some final confirmation (e.g. 2FA).
+ //
+ // Only applicable when transfer_done is false.
+ // It may contain withdrawal operation id
+ confirm_transfer_url?: string;
+ }
+
+
+}
+export namespace TalerCorebankApi {
+
+ export interface Config {
+ // Name of this API, always "circuit".
+ name: string;
+ // API version in the form $n:$n:$n
+ version: string;
+
+ // If 'true', the server provides local currency
+ // conversion support.
+ // If missing or false, some parts of the API
+ // are not supported and return 404.
+ have_cashout?: boolean;
+
+ // How the bank SPA should render the currency.
+ currency: CurrencySpecification;
+
+ // Fiat currency. That is the currency in which
+ // cash-out operations ultimately wire money.
+ // Only applicable if have_cashout=true.
+ fiat_currency?: CurrencySpecification;
+ }
+
+ export interface BankAccountCreateWithdrawalRequest {
+ // Amount to withdraw.
+ amount: Amount;
+ }
+ export interface BankAccountCreateWithdrawalResponse {
+ // ID of the withdrawal, can be used to view/modify the withdrawal operation.
+ withdrawal_id: string;
+
+ // URI that can be passed to the wallet to initiate the withdrawal.
+ taler_withdraw_uri: string;
+ }
+ export interface BankAccountGetWithdrawalResponse {
+ // Amount that will be withdrawn with this withdrawal operation.
+ amount: Amount;
+
+ // Was the withdrawal aborted?
+ aborted: boolean;
+
+ // Has the withdrawal been confirmed by the bank?
+ // The wire transfer for a withdrawal is only executed once
+ // both confirmation_done is true and selection_done is true.
+ confirmation_done: boolean;
+
+ // Did the wallet select reserve details?
+ selection_done: boolean;
+
+ // Reserve public key selected by the exchange,
+ // only non-null if selection_done is true.
+ selected_reserve_pub: string | undefined;
+
+ // Exchange account selected by the wallet, or by the bank
+ // (with the default exchange) in case the wallet did not provide one
+ // through the Integration API.
+ selected_exchange_account: string | undefined;
+ }
+
+ export interface BankAccountTransactionsResponse {
+ transactions: BankAccountTransactionInfo[];
+ }
+
+ export interface BankAccountTransactionInfo {
+ creditor_payto_uri: string;
+ debtor_payto_uri: string;
+
+ amount: Amount;
+ direction: "debit" | "credit";
+
+ subject: string;
+
+ // Transaction unique ID. Matches
+ // $transaction_id from the URI.
+ row_id: number;
+ date: Timestamp;
+ }
+
+ export interface CreateBankAccountTransactionCreate {
+ // Address in the Payto format of the wire transfer receiver.
+ // It needs at least the 'message' query string parameter.
+ payto_uri: string;
+
+ // Transaction amount (in the $currency:x.y format), optional.
+ // However, when not given, its value must occupy the 'amount'
+ // query string parameter of the 'payto' field. In case it
+ // is given in both places, the paytoUri's takes the precedence.
+ amount?: string;
+ }
+
+ export interface RegisterAccountRequest {
+ // Username
+ username: string;
+
+ // Password.
+ password: string;
+
+ // Legal name of the account owner
+ name: string;
+
+ // Defaults to false.
+ is_public?: boolean;
+
+ // Is this a taler exchange account?
+ // If true:
+ // - incoming transactions to the account that do not
+ // have a valid reserve public key are automatically
+ // - the account provides the taler-wire-gateway-api endpoints
+ // Defaults to false.
+ is_taler_exchange?: boolean;
+
+ // Addresses where to send the TAN for transactions.
+ // Currently only used for cashouts.
+ // If missing, cashouts will fail.
+ // In the future, might be used for other transactions
+ // as well.
+ challenge_contact_data?: ChallengeContactData;
+
+ // 'payto' address pointing a bank account
+ // external to the libeufin-bank.
+ // Payments will be sent to this bank account
+ // when the user wants to convert the local currency
+ // back to fiat currency outside libeufin-bank.
+ cashout_payto_uri?: string;
+
+ // Internal payto URI of this bank account.
+ // Used mostly for testing.
+ internal_payto_uri?: string;
+ }
+ export interface ChallengeContactData {
+
+ // E-Mail address
+ email?: EmailAddress;
+
+ // Phone number.
+ phone?: PhoneNumber;
+ }
+
+ export interface AccountReconfiguration {
+
+ // Addresses where to send the TAN for transactions.
+ // Currently only used for cashouts.
+ // If missing, cashouts will fail.
+ // In the future, might be used for other transactions
+ // as well.
+ challenge_contact_data?: ChallengeContactData;
+
+ // 'payto' address pointing a bank account
+ // external to the libeufin-bank.
+ // Payments will be sent to this bank account
+ // when the user wants to convert the local currency
+ // back to fiat currency outside libeufin-bank.
+ cashout_address?: string;
+
+ // Legal name associated with $username.
+ // When missing, the old name is kept.
+ name?: string;
+
+ // If present, change the is_exchange configuration.
+ // See RegisterAccountRequest
+ is_exchange?: boolean;
+ }
+
+
+ export interface AccountPasswordChange {
+
+ // New password.
+ new_password: string;
+ }
+
+ export interface PublicAccountsResponse {
+ public_accounts: PublicAccount[];
+ }
+ export interface PublicAccount {
+ payto_uri: string;
+
+ balance: Balance;
+
+ // The account name (=username) of the
+ // libeufin-bank account.
+ account_name: string;
+ }
+
+ export interface ListBankAccountsResponse {
+ accounts: AccountMinimalData[];
+ }
+ export interface Balance {
+ amount: Amount;
+ credit_debit_indicator: "credit" | "debit";
+ }
+ export interface AccountMinimalData {
+ // Username
+ username: string;
+
+ // Legal name of the account owner.
+ name: string;
+
+ // current balance of the account
+ balance: Balance;
+
+ // Number indicating the max debit allowed for the requesting user.
+ debit_threshold: Amount;
+ }
+
+ export interface AccountData {
+ // Legal name of the account owner.
+ name: string;
+
+ // Available balance on the account.
+ balance: Balance;
+
+ // payto://-URI of the account.
+ payto_uri: string;
+
+ // Number indicating the max debit allowed for the requesting user.
+ debit_threshold: Amount;
+
+ contact_data?: ChallengeContactData;
+
+ // 'payto' address pointing the bank account
+ // where to send cashouts. This field is optional
+ // because not all the accounts are required to participate
+ // in the merchants' circuit. One example is the exchange:
+ // that never cashouts. Registering these accounts can
+ // be done via the access API.
+ cashout_payto_uri?: string;
+ }
+
+
+ export interface CashoutRequest {
+
+ // Optional subject to associate to the
+ // cashout operation. This data will appear
+ // as the incoming wire transfer subject in
+ // the user's external bank account.
+ subject?: string;
+
+ // That is the plain amount that the user specified
+ // to cashout. Its $currency is the (regional) currency of the
+ // bank instance.
+ amount_debit: Amount;
+
+ // That is the amount that will effectively be
+ // transferred by the bank to the user's bank
+ // account, that is external to the regional currency.
+ // It is expressed in the fiat currency and
+ // is calculated after the cashout fee and the
+ // exchange rate. See the /cashout-rates call.
+ // The client needs to calculate this amount
+ // correctly based on the amount_debit and the cashout rate,
+ // otherwise the request will fail.
+ amount_credit: Amount;
+
+ // Which channel the TAN should be sent to. If
+ // this field is missing, it defaults to SMS.
+ // The default choice prefers to change the communication
+ // channel respect to the one used to issue this request.
+ tan_channel?: TanChannel;
+ }
+
+ export interface CashoutPending {
+ // ID identifying the operation being created
+ // and now waiting for the TAN confirmation.
+ cashout_id: string;
+ }
+
+ export interface CashoutConfirmRequest {
+ // the TAN that confirms $CASHOUT_ID.
+ tan: string;
+ }
+
+ export interface CashoutConversionResponse {
+ // Amount that the user will get deducted from their regional
+ // bank account, according to the 'amount_credit' value.
+ amount_debit: Amount;
+ // Amount that the user will receive in their fiat
+ // bank account, according to 'amount_debit'.
+ amount_credit: Amount;
+ }
+
+ export interface Cashouts {
+ // Every string represents a cash-out operation ID.
+ cashouts: CashoutInfo[];
+ }
+
+ export interface CashoutInfo {
+ cashout_id: string;
+ status: "pending" | "confirmed";
+ }
+ export interface GlobalCashouts {
+ // Every string represents a cash-out operation ID.
+ cashouts: GlobalCashoutInfo[];
+ }
+ export interface GlobalCashoutInfo {
+ cashout_id: string;
+ username: string;
+ status: "pending" | "confirmed";
+ }
+
+ export interface CashoutStatusResponse {
+ status: "pending" | "confirmed";
+
+ // Amount debited to the internal
+ // regional currency bank account.
+ amount_debit: Amount;
+
+ // Amount credited to the external bank account.
+ amount_credit: Amount;
+
+ // Transaction subject.
+ subject: string;
+
+ // Fiat bank account that will receive the cashed out amount.
+ // Specified as a payto URI.
+ credit_payto_uri: string;
+
+ // Time when the cashout was created.
+ creation_time: Timestamp;
+
+ // Time when the cashout was confirmed via its TAN.
+ // Missing when the operation wasn't confirmed yet.
+ confirmation_time?: Timestamp;
+ }
+
+ export interface ConversionRatesResponse {
+
+ // Exchange rate to buy the local currency from the external one
+ buy_at_ratio: DecimalNumber;
+
+ // Exchange rate to sell the local currency for the external one
+ sell_at_ratio: DecimalNumber;
+
+ // Fee to subtract after applying the buy ratio.
+ buy_in_fee: DecimalNumber;
+
+ // Fee to subtract after applying the sell ratio.
+ sell_out_fee: DecimalNumber;
+ }
+
+ export enum MonitorTimeframeParam {
+ hour, day, month, year, decade,
+ }
+
+ export interface MonitorResponse {
+
+ // This number identifies how many cashin operations
+ // took place in the timeframe specified in the request.
+ // This number corresponds to how many withdrawals have
+ // been initiated by a wallet owner. Note: wallet owners
+ // are NOT required to be customers of the libeufin-bank.
+ cashinCount: number;
+
+ // This amount accounts how much external currency has been
+ // spent to withdraw Taler coins in the internal currency.
+ // The exact amount of internal currency being created can be
+ // calculated using the advertised conversion rates.
+ cashinExternalVolume: Amount;
+
+ // This number identifies how many cashout operations were
+ // confirmed in the timeframe speficied in the request.
+ cashoutCount: number;
+
+ // This amount corresponds to how much *external* currency was
+ // paid by the libeufin-bank administrator to fulfill all the
+ // confirmed cashouts related to the timeframe specified in the
+ // request.
+ cashoutExternalVolume: Amount;
+
+ // This number identifies how many payments were made by a
+ // Taler exchange to a merchant bank account in the internal
+ // currency, in the timeframe specified in the request.
+ talerPayoutCount: number;
+
+ // This amount accounts the overall *internal* currency that
+ // has been paid by a Taler exchange to a merchant internal
+ // bank account, in the timeframe specified in the request.
+ talerPayoutInternalVolume: Amount;
+ }
+
+
+}
diff --git a/packages/taler-util/src/http-client/utils.ts b/packages/taler-util/src/http-client/utils.ts
new file mode 100644
index 000000000..4588f945c
--- /dev/null
+++ b/packages/taler-util/src/http-client/utils.ts
@@ -0,0 +1,68 @@
+import { base64FromArrayBuffer } from "../base64.js";
+import { stringToBytes } from "../taler-crypto.js";
+import { AccessToken, TalerAuthentication } from "./types.js";
+
+/**
+ * Helper function to generate the "Authorization" HTTP header.
+ */
+export function makeBasicAuthHeader(username: string, password: string): string {
+ const auth = `${username}:${password}`;
+ const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth));
+ return `Basic ${authEncoded}`;
+}
+
+/**
+ * rfc8959
+ * @param token
+ * @returns
+ */
+export function makeBearerTokenAuthHeader(token: AccessToken): string {
+ return `Bearer secret-token:${token}`;
+}
+
+/**
+ * https://bugs.gnunet.org/view.php?id=7949
+ */
+export function addPaginationParams(url: URL, pagination?: PaginationParams) {
+ if (!pagination) return;
+ if (pagination.timoutMs) {
+ url.searchParams.set("long_poll_ms", String(pagination.timoutMs))
+ }
+ if (pagination.offset) {
+ url.searchParams.set("start", pagination.offset)
+ }
+ const order = !pagination || pagination.order === "asc" ? 1 : -1
+ const limit = !pagination || !pagination.limit || pagination.limit === 0 ? 5 : Math.abs(pagination.limit)
+ //always send delta
+ url.searchParams.set("delta", String(order * limit))
+}
+
+export type UserAndPassword = {
+ username: string,
+ password: string,
+}
+
+export type UserAndToken = {
+ username: string,
+ token: AccessToken,
+}
+
+export type PaginationParams = {
+ /**
+ * row identifier as the starting point of the query
+ */
+ offset?: string,
+ /**
+ * max number of element in the result response
+ * always greater than 0
+ */
+ limit?: number,
+ /**
+ * milliseconds the server should wait for at least one result to be shown
+ */
+ timoutMs?: number,
+ /**
+ * order
+ */
+ order: "asc" | "dec"
+}
diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts
index f25705545..da2fbb9da 100644
--- a/packages/taler-util/src/http-common.ts
+++ b/packages/taler-util/src/http-common.ts
@@ -50,7 +50,7 @@ export interface HttpResponse {
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
export interface HttpRequestOptions {
- method?: "POST" | "PUT" | "GET" | "DELETE";
+ method?: "POST" | "PATCH" | "PUT" | "GET" | "DELETE";
headers?: { [name: string]: string };
/**
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 568e2f438..71d4253f0 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -42,3 +42,8 @@ export * from "./transaction-test-data.js";
export * from "./libeufin-api-types.js";
export * from "./MerchantApiClient.js";
export * from "./bank-api-client.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/types.js";
diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts
index de5be71a1..b68dc1ada 100644
--- a/packages/taler-util/src/taler-crypto.ts
+++ b/packages/taler-util/src/taler-crypto.ts
@@ -123,7 +123,7 @@ function getValue(chr: string): number {
switch (chr) {
case "O":
case "o":
- a = "0;";
+ a = "0";
break;
case "i":
case "I":
diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts
index ebb0291f5..07b989fc1 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -951,7 +951,7 @@ export interface AccountInfo {
/**
* @deprecated
*/
-export interface ExchangeWireJson {}
+export interface ExchangeWireJson { }
/**
* Proposal returned from the contract URL.
@@ -1006,8 +1006,8 @@ export class WithdrawOperationStatusResponse {
/**
* Response from the merchant.
*/
-export class TipPickupGetResponse {
- tip_amount: string;
+export class RewardPickupGetResponse {
+ reward_amount: string;
exchange_url: string;
@@ -1310,12 +1310,6 @@ export const codecForDenominationPubKey = () =>
.alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
.build("DenominationPubKey");
-export const codecForBankWithdrawalOperationPostResponse =
- (): Codec<BankWithdrawalOperationPostResponse> =>
- buildCodecForObject<BankWithdrawalOperationPostResponse>()
- .property("transfer_done", codecForBoolean())
- .build("BankWithdrawalOperationPostResponse");
-
export type AmountString = string;
export type Base32String = string;
export type EddsaSignatureString = string;
@@ -1572,9 +1566,9 @@ export const codecForWithdrawOperationStatusResponse =
.property("wire_types", codecForList(codecForString()))
.build("WithdrawOperationStatusResponse");
-export const codecForTipPickupGetResponse = (): Codec<TipPickupGetResponse> =>
- buildCodecForObject<TipPickupGetResponse>()
- .property("tip_amount", codecForString())
+export const codecForRewardPickupGetResponse = (): Codec<RewardPickupGetResponse> =>
+ buildCodecForObject<RewardPickupGetResponse>()
+ .property("reward_amount", codecForString())
.property("exchange_url", codecForString())
.property("next_url", codecOptional(codecForString()))
.property("expiration", codecForTimestamp)
@@ -1591,7 +1585,7 @@ export const codecForWithdrawResponse = (): Codec<ExchangeWithdrawResponse> =>
.property("ev_sig", codecForBlindedDenominationSignature())
.build("WithdrawResponse");
-export const codecForWithdrawBatchResponse =
+export const codecForExchangeWithdrawBatchResponse =
(): Codec<ExchangeWithdrawBatchResponse> =>
buildCodecForObject<ExchangeWithdrawBatchResponse>()
.property("ev_sigs", codecForList(codecForWithdrawResponse()))
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts
index 63db206bd..88830d82b 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -43,6 +43,8 @@ import {
codecForList,
codecForAny,
codecForBoolean,
+ codecForEither,
+ codecForConstString,
} from "./codec.js";
import {
RefreshReason,
@@ -63,6 +65,13 @@ export interface TransactionsRequest {
search?: string;
/**
+ * Sort order of the transaction items.
+ * By default, items are sorted ascending by their
+ * main timestamp.
+ */
+ sort?: "ascending" | "descending";
+
+ /**
* If true, include all refreshes in the transactions list.
*/
includeRefreshes?: boolean;
@@ -690,6 +699,15 @@ export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
buildCodecForObject<TransactionsRequest>()
.property("currency", codecOptional(codecForString()))
.property("search", codecOptional(codecForString()))
+ .property(
+ "sort",
+ codecOptional(
+ codecForEither(
+ codecForConstString("ascending"),
+ codecForConstString("descending"),
+ ),
+ ),
+ )
.property("includeRefreshes", codecOptional(codecForBoolean()))
.build("TransactionsRequest");
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 9a4e15ae1..49dae9bd4 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -46,6 +46,7 @@ import {
codecOptional,
renderContext,
} from "./codec.js";
+import { CurrencySpecification } from "./index.js";
import { VersionMatchResult } from "./libtool-version.js";
import { PaytoUri } from "./payto.js";
import { AgeCommitmentProof } from "./taler-crypto.js";
@@ -429,16 +430,6 @@ export interface GetCurrencySpecificationResponse {
currencySpecification: CurrencySpecification;
}
-export interface CurrencySpecification {
- decimal_separator: string;
- fractional_input_digits: number;
- fractional_normal_digits: number;
- fractional_trailing_zero_digits: number;
- is_currency_name_leading: boolean;
- name: string;
- alt_unit_names: { [n: number]: string };
-}
-
export interface InitRequest {
skipDefaults?: boolean;
}
@@ -556,11 +547,11 @@ export interface CoinDumpJson {
withdrawal_reserve_pub: string | undefined;
coin_status: CoinStatus;
spend_allocation:
- | {
- id: string;
- amount: string;
- }
- | undefined;
+ | {
+ id: string;
+ amount: string;
+ }
+ | undefined;
/**
* Information about the age restriction
*/
diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts
index 4fc890788..b6b80009f 100644
--- a/packages/taler-wallet-core/src/dbless.ts
+++ b/packages/taler-wallet-core/src/dbless.ts
@@ -49,6 +49,9 @@ import {
Logger,
parsePaytoUri,
UnblindedSignature,
+ ExchangeBatchWithdrawRequest,
+ ExchangeWithdrawBatchResponse,
+ codecForExchangeWithdrawBatchResponse,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -165,25 +168,29 @@ export async function withdrawCoin(args: {
value: Amounts.parseOrThrow(denom.value),
});
- const reqBody: ExchangeWithdrawRequest = {
- denom_pub_hash: planchet.denomPubHash,
- reserve_sig: planchet.withdrawSig,
- coin_ev: planchet.coinEv,
+ const reqBody: ExchangeBatchWithdrawRequest = {
+ planchets: [
+ {
+ denom_pub_hash: planchet.denomPubHash,
+ reserve_sig: planchet.withdrawSig,
+ coin_ev: planchet.coinEv,
+ },
+ ],
};
const reqUrl = new URL(
- `reserves/${planchet.reservePub}/withdraw`,
+ `reserves/${planchet.reservePub}/batch-withdraw`,
exchangeBaseUrl,
).href;
- const resp = await http.postJson(reqUrl, reqBody);
- const r = await readSuccessResponseJsonOrThrow(
+ const resp = await http.fetch(reqUrl, { method: "POST", body: reqBody });
+ const rBatch = await readSuccessResponseJsonOrThrow(
resp,
- codecForWithdrawResponse(),
+ codecForExchangeWithdrawBatchResponse(),
);
const ubSig = await cryptoApi.unblindDenominationSignature({
planchet,
- evSig: r.ev_sig,
+ evSig: rBatch.ev_sigs[0].ev_sig,
});
return {
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts
index 1819aa1b8..7590280bc 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -101,14 +101,14 @@ async function gatherExchangePending(
case ExchangeEntryDbUpdateStatus.Failed:
return;
}
- const opTag = TaskIdentifiers.forExchangeUpdate(exch);
- let opr = await tx.operationRetries.get(opTag);
+ const opUpdateExchangeTag = TaskIdentifiers.forExchangeUpdate(exch);
+ let opr = await tx.operationRetries.get(opUpdateExchangeTag);
const timestampDue = opr?.retryInfo.nextRetry ?? exch.nextRefreshCheckStamp;
resp.pendingOperations.push({
type: PendingTaskType.ExchangeUpdate,
...getPendingCommon(
ws,
- opTag,
+ opUpdateExchangeTag,
AbsoluteTime.fromPreciseTimestamp(timestampPreciseFromDb(timestampDue)),
),
givesLifeness: false,
@@ -119,11 +119,12 @@ async function gatherExchangePending(
// We only schedule a check for auto-refresh if the exchange update
// was successful.
if (!opr?.lastError) {
+ const opCheckRefreshTag = TaskIdentifiers.forExchangeCheckRefresh(exch);
resp.pendingOperations.push({
type: PendingTaskType.ExchangeCheckRefresh,
...getPendingCommon(
ws,
- opTag,
+ opCheckRefreshTag,
AbsoluteTime.fromPreciseTimestamp(
timestampPreciseFromDb(timestampDue),
),
diff --git a/packages/taler-wallet-core/src/operations/reward.ts b/packages/taler-wallet-core/src/operations/reward.ts
index 4e16d977d..ed9927bab 100644
--- a/packages/taler-wallet-core/src/operations/reward.ts
+++ b/packages/taler-wallet-core/src/operations/reward.ts
@@ -23,7 +23,7 @@ import {
Amounts,
BlindedDenominationSignature,
codecForMerchantTipResponseV2,
- codecForTipPickupGetResponse,
+ codecForRewardPickupGetResponse,
CoinStatus,
DenomKeyType,
encodeCrock,
@@ -168,11 +168,11 @@ export async function prepareTip(
const merchantResp = await ws.http.fetch(tipStatusUrl.href);
const tipPickupStatus = await readSuccessResponseJsonOrThrow(
merchantResp,
- codecForTipPickupGetResponse(),
+ codecForRewardPickupGetResponse(),
);
logger.trace(`status ${j2s(tipPickupStatus)}`);
- const amount = Amounts.parseOrThrow(tipPickupStatus.tip_amount);
+ const amount = Amounts.parseOrThrow(tipPickupStatus.reward_amount);
logger.trace("new tip, creating tip record");
await updateExchangeFromUrl(ws, tipPickupStatus.exchange_url);
diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts
index 607d03470..f5bed13dd 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -74,6 +74,7 @@ import {
import { initiatePeerPushDebit } from "./pay-peer-push-debit.js";
import { OpenedPromise, openPromise } from "../index.js";
import { getTransactionById, getTransactions } from "./transactions.js";
+import { getPendingOperations } from "./pending.js";
const logger = new Logger("operations/testing.ts");
@@ -290,7 +291,7 @@ export async function runIntegrationTest(
corebankApiBaseUrl: args.corebankApiBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.info("done withdrawing test balance");
const balance = await getBalances(ws);
@@ -305,7 +306,7 @@ export async function runIntegrationTest(
await makePayment(ws, myMerchant, args.amountToSpend, "hello world");
// Wait until the refresh is done
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.trace("withdrawing test balance for refund");
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
@@ -320,7 +321,7 @@ export async function runIntegrationTest(
});
// Wait until the withdraw is done
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
const { orderId: refundOrderId } = await makePayment(
ws,
@@ -344,7 +345,7 @@ export async function runIntegrationTest(
logger.trace("integration test: applied refund");
// Wait until the refund is done
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.trace("integration test: making payment after refund");
@@ -357,12 +358,17 @@ export async function runIntegrationTest(
logger.trace("integration test: make payment done");
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.trace("integration test: all done!");
}
-export async function waitUntilDone(ws: InternalWalletState): Promise<void> {
+/**
+ * Wait until all transactions are in a final state.
+ */
+export async function waitUntilTransactionsFinal(
+ ws: InternalWalletState,
+): Promise<void> {
logger.info("waiting until all transactions are in a final state");
ws.ensureTaskLoopRunning();
let p: OpenedPromise<void> | undefined = undefined;
@@ -410,6 +416,44 @@ export async function waitUntilDone(ws: InternalWalletState): Promise<void> {
logger.info("done waiting until all transactions are in a final state");
}
+/**
+ * Wait until pending work is processed.
+ */
+export async function waitUntilTasksProcessed(
+ ws: InternalWalletState,
+): Promise<void> {
+ logger.info("waiting until pending work is processed");
+ ws.ensureTaskLoopRunning();
+ let p: OpenedPromise<void> | undefined = undefined;
+ const cancelNotifs = ws.addNotificationListener((notif) => {
+ if (!p) {
+ return;
+ }
+ if (notif.type === NotificationType.PendingOperationProcessed) {
+ p.resolve();
+ }
+ });
+ while (1) {
+ p = openPromise();
+ const pendingTasksResp = await getPendingOperations(ws);
+ logger.info(`waiting on pending ops: ${j2s(pendingTasksResp)}`);
+ let finished = true;
+ for (const task of pendingTasksResp.pendingOperations) {
+ if (task.isDue) {
+ finished = false;
+ }
+ logger.info(`continuing waiting for task ${task.id}`);
+ }
+ if (finished) {
+ break;
+ }
+ // Wait until task is done
+ await p.promise;
+ }
+ logger.info("done waiting until pending work is processed");
+ cancelNotifs();
+}
+
export async function waitUntilRefreshesDone(
ws: InternalWalletState,
): Promise<void> {
@@ -463,7 +507,7 @@ export async function waitUntilRefreshesDone(
logger.info("done waiting until all refreshes are in a final state");
}
-async function waitUntilPendingReady(
+async function waitUntilTransactionPendingReady(
ws: InternalWalletState,
transactionId: string,
): Promise<void> {
@@ -560,7 +604,7 @@ export async function runIntegrationTest2(
corebankApiBaseUrl: args.corebankApiBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.info("done withdrawing test balance");
const balance = await getBalances(ws);
@@ -580,7 +624,7 @@ export async function runIntegrationTest2(
);
// Wait until the refresh is done
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.trace("withdrawing test balance for refund");
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
@@ -595,7 +639,7 @@ export async function runIntegrationTest2(
});
// Wait until the withdraw is done
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
const { orderId: refundOrderId } = await makePayment(
ws,
@@ -619,7 +663,7 @@ export async function runIntegrationTest2(
logger.trace("integration test: applied refund");
// Wait until the refund is done
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.trace("integration test: making payment after refund");
@@ -632,7 +676,7 @@ export async function runIntegrationTest2(
logger.trace("integration test: make payment done");
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
const peerPushInit = await initiatePeerPushDebit(ws, {
partialContractTerms: {
@@ -647,7 +691,7 @@ export async function runIntegrationTest2(
},
});
- await waitUntilPendingReady(ws, peerPushInit.transactionId);
+ await waitUntilTransactionPendingReady(ws, peerPushInit.transactionId);
const peerPushCredit = await preparePeerPushCredit(ws, {
talerUri: peerPushInit.talerUri,
@@ -670,7 +714,7 @@ export async function runIntegrationTest2(
},
});
- await waitUntilPendingReady(ws, peerPullInit.transactionId);
+ await waitUntilTransactionPendingReady(ws, peerPullInit.transactionId);
const peerPullInc = await preparePeerPullDebit(ws, {
talerUri: peerPullInit.talerUri,
@@ -680,7 +724,7 @@ export async function runIntegrationTest2(
peerPullDebitId: peerPullInc.peerPullDebitId,
});
- await waitUntilDone(ws);
+ await waitUntilTransactionsFinal(ws);
logger.trace("integration test: all done!");
}
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index 72c67b153..bebb3d60b 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -1290,9 +1290,16 @@ export async function getTransactions(
const txPending = transactions.filter((x) => isPending(x));
const txNotPending = transactions.filter((x) => !isPending(x));
+ let sortSign: number;
+ if (transactionsRequest?.sort == "descending") {
+ sortSign = -1;
+ } else {
+ sortSign = 1;
+ }
+
const txCmp = (h1: Transaction, h2: Transaction) => {
// Order transactions by timestamp. Newest transactions come first.
- const tsCmp = -AbsoluteTime.cmp(
+ const tsCmp = AbsoluteTime.cmp(
AbsoluteTime.fromPreciseTimestamp(h1.timestamp),
AbsoluteTime.fromPreciseTimestamp(h2.timestamp),
);
@@ -1300,7 +1307,7 @@ export async function getTransactions(
if (tsCmp === 0) {
return Math.sign(txOrder[h1.type] - txOrder[h2.type]);
}
- return tsCmp;
+ return sortSign * tsCmp;
};
txPending.sort(txCmp);
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index eff427bec..245eec4fa 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -33,7 +33,7 @@ import {
codecForReserveStatus,
codecForTalerConfigResponse,
codecForWalletKycUuid,
- codecForWithdrawBatchResponse,
+ codecForExchangeWithdrawBatchResponse,
codecForWithdrawOperationStatusResponse,
codecForWithdrawResponse,
CoinStatus,
@@ -944,7 +944,7 @@ async function processPlanchetExchangeBatchRequest(
}
const r = await readSuccessResponseJsonOrThrow(
resp,
- codecForWithdrawBatchResponse(),
+ codecForExchangeWithdrawBatchResponse(),
);
return {
coinIdxs: requestCoinIdxs,
diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts
index e46c7ebe1..8e0e5561b 100644
--- a/packages/taler-wallet-core/src/versions.ts
+++ b/packages/taler-wallet-core/src/versions.ts
@@ -40,4 +40,17 @@ export const WALLET_BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0";
* Will be replaced with the value from package.json in a
* post-compilation step (inside lib/).
*/
-export const WALLET_CORE_IMPLEMENTATION_VERSION = "0:0:0";
+export const WALLET_CORE_IMPLEMENTATION_VERSION = "1:0:0";
+
+/**
+ * Libtool rules:
+ *
+ * If the library source code has changed at all since the last update,
+ * then increment revision (‘c:r:a’ becomes ‘c:r+1:a’).
+ * If any interfaces have been added, removed, or changed since the last
+ * update, increment current, and set revision to 0.
+ * If any interfaces have been added since the last public release, then
+ * increment age.
+ * If any interfaces have been removed or changed since the last public
+ * release, then set age to 0.
+ */ \ No newline at end of file
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index fadc7aa7f..a8de9ac03 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -224,6 +224,7 @@ export enum WalletApiOperation {
DeleteStoredBackup = "deleteStoredBackup",
RecoverStoredBackup = "recoverStoredBackup",
UpdateExchangeEntry = "updateExchangeEntry",
+ TestingWaitTasksProcessed = "testingWaitTasksProcessed",
}
// group: Initialization
@@ -1007,7 +1008,7 @@ export type TestingSetTimetravelOp = {
/**
* Wait until all transactions are in a final state.
*/
-export type TestingWaitTransactionsFinal = {
+export type TestingWaitTransactionsFinalOp = {
op: WalletApiOperation.TestingWaitTransactionsFinal;
request: EmptyObject;
response: EmptyObject;
@@ -1016,13 +1017,22 @@ export type TestingWaitTransactionsFinal = {
/**
* Wait until all refresh transactions are in a final state.
*/
-export type TestingWaitRefreshesFinal = {
+export type TestingWaitRefreshesFinalOp = {
op: WalletApiOperation.TestingWaitRefreshesFinal;
request: EmptyObject;
response: EmptyObject;
};
/**
+ * Wait until all tasks have been processed and the wallet is idle.
+ */
+export type TestingWaitTasksProcessedOp = {
+ op: WalletApiOperation.TestingWaitTasksProcessed;
+ request: EmptyObject;
+ response: EmptyObject;
+};
+
+/**
* Wait until a transaction is in a particular state.
*/
export type TestingWaitTransactionStateOp = {
@@ -1132,8 +1142,9 @@ export type WalletOperations = {
[WalletApiOperation.Recycle]: RecycleOp;
[WalletApiOperation.ApplyDevExperiment]: ApplyDevExperimentOp;
[WalletApiOperation.ValidateIban]: ValidateIbanOp;
- [WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal;
- [WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal;
+ [WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinalOp;
+ [WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinalOp;
+ [WalletApiOperation.TestingWaitTasksProcessed]: TestingWaitTasksProcessedOp;
[WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp;
[WalletApiOperation.TestingWaitTransactionState]: TestingWaitTransactionStateOp;
[WalletApiOperation.GetCurrencySpecification]: GetCurrencySpecificationOp;
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 571bf07ee..06d9bb9e8 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -252,9 +252,10 @@ import {
runIntegrationTest2,
testPay,
waitTransactionState,
- waitUntilDone,
+ waitUntilTransactionsFinal,
waitUntilRefreshesDone,
withdrawTestBalance,
+ waitUntilTasksProcessed,
} from "./operations/testing.js";
import {
acceptTip,
@@ -1427,6 +1428,10 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
await waitTransactionState(ws, req.transactionId, req.txState);
return {};
}
+ case WalletApiOperation.TestingWaitTasksProcessed: {
+ await waitUntilTasksProcessed(ws);
+ return {};
+ }
case WalletApiOperation.GetCurrencySpecification: {
// Ignore result, just validate in this mock implementation
const req = codecForGetCurrencyInfoRequest().decode(payload);
@@ -1436,9 +1441,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
currencySpecification: {
decimal_separator: ",",
name: "Kudos (Taler Demonstrator)",
- fractional_input_digits: 2,
- fractional_normal_digits: 2,
- fractional_trailing_zero_digits: 2,
+ num_fractional_input_digits: 2,
+ num_fractional_normal_digits: 2,
+ num_fractional_trailing_zero_digits: 2,
is_currency_name_leading: true,
alt_unit_names: {
"0": "ク",
@@ -1451,9 +1456,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
currencySpecification: {
decimal_separator: ",",
name: "Test (Taler Unstable Demonstrator)",
- fractional_input_digits: 0,
- fractional_normal_digits: 0,
- fractional_trailing_zero_digits: 0,
+ num_fractional_input_digits: 0,
+ num_fractional_normal_digits: 0,
+ num_fractional_trailing_zero_digits: 0,
is_currency_name_leading: false,
alt_unit_names: {},
},
@@ -1464,9 +1469,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
currencySpecification: {
decimal_separator: ",",
name: "Unknown",
- fractional_input_digits: 2,
- fractional_normal_digits: 2,
- fractional_trailing_zero_digits: 2,
+ num_fractional_input_digits: 2,
+ num_fractional_normal_digits: 2,
+ num_fractional_trailing_zero_digits: 2,
is_currency_name_leading: true,
alt_unit_names: {},
},
@@ -1600,7 +1605,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
return getVersion(ws);
}
case WalletApiOperation.TestingWaitTransactionsFinal:
- return await waitUntilDone(ws);
+ return await waitUntilTransactionsFinal(ws);
case WalletApiOperation.TestingWaitRefreshesFinal:
return await waitUntilRefreshesDone(ws);
case WalletApiOperation.TestingSetTimetravel: {