Merge branch 'master' into age-withdraw
This commit is contained in:
commit
fba664f9a3
@ -52,6 +52,41 @@ export function WithdrawalQRCode({
|
|||||||
if (result.loading) {
|
if (result.loading) {
|
||||||
return <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);
|
return handleNotOkResult(i18n)(result);
|
||||||
}
|
}
|
||||||
const { data } = result;
|
const { data } = result;
|
||||||
|
@ -654,6 +654,9 @@ export class FakebankService
|
|||||||
return this.baseUrl;
|
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(
|
async createExchangeAccount(
|
||||||
accountName: string,
|
accountName: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
@ -136,20 +136,22 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
|
|
||||||
|
|
||||||
let p: PendingOperationsResponse;
|
let p: PendingOperationsResponse;
|
||||||
p = await walletClient.call(WalletApiOperation.GetPendingOperations, {});
|
p = await walletClient.call(WalletApiOperation.GetPendingOperations, {});
|
||||||
|
|
||||||
console.log("pending operations after first time travel");
|
console.log("pending operations after first time travel");
|
||||||
console.log(JSON.stringify(p, undefined, 2));
|
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,
|
walletClient,
|
||||||
bank,
|
bank,
|
||||||
exchange,
|
exchange,
|
||||||
amount: "TESTKUDOS:20",
|
amount: "TESTKUDOS:20",
|
||||||
});
|
});
|
||||||
|
await wres2.withdrawalFinishedCond;
|
||||||
|
|
||||||
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
|
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.
|
// At this point, the original coins should've been refreshed.
|
||||||
// It would be too late to refresh them now, as we're past
|
// It would be too late to refresh them now, as we're past
|
||||||
// the two year deposit expiration.
|
// the two year deposit expiration.
|
||||||
|
|
||||||
await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
|
|
||||||
|
|
||||||
const orderResp = await merchantClient.createOrder({
|
const orderResp = await merchantClient.createOrder({
|
||||||
order: {
|
order: {
|
||||||
fulfillment_url: "http://example.com",
|
fulfillment_url: "http://example.com",
|
||||||
@ -195,7 +198,7 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
|
|||||||
t.assertTrue(r.status === PreparePayResultType.PaymentPossible);
|
t.assertTrue(r.status === PreparePayResultType.PaymentPossible);
|
||||||
|
|
||||||
const cpr = await walletClient.call(WalletApiOperation.ConfirmPay, {
|
const cpr = await walletClient.call(WalletApiOperation.ConfirmPay, {
|
||||||
proposalId: r.proposalId,
|
transactionId: r.transactionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
t.assertTrue(cpr.type === ConfirmPayResultType.Done);
|
t.assertTrue(cpr.type === ConfirmPayResultType.Done);
|
||||||
|
@ -55,12 +55,14 @@ export async function runWithdrawalFakebankTest(t: GlobalTestState) {
|
|||||||
accountName: "exchange",
|
accountName: "exchange",
|
||||||
accountPassword: "x",
|
accountPassword: "x",
|
||||||
wireGatewayApiBaseUrl: new URL(
|
wireGatewayApiBaseUrl: new URL(
|
||||||
"/accounts/exchange/taler-wire-gateway",
|
"/accounts/exchange/taler-wire-gateway/",
|
||||||
bank.baseUrl,
|
bank.baseUrl,
|
||||||
).href,
|
).href,
|
||||||
accountPaytoUri: "payto://x-taler-bank/localhost/exchange",
|
accountPaytoUri: "payto://x-taler-bank/localhost/exchange",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await bank.createExchangeAccount("exchange", "x");
|
||||||
|
|
||||||
await bank.start();
|
await bank.start();
|
||||||
|
|
||||||
await bank.pingUntilAvailable();
|
await bank.pingUntilAvailable();
|
||||||
@ -93,8 +95,6 @@ export async function runWithdrawalFakebankTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
const balResp = await wallet.client.call(WalletApiOperation.GetBalances, {});
|
const balResp = await wallet.client.call(WalletApiOperation.GetBalances, {});
|
||||||
t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available);
|
t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available);
|
||||||
|
|
||||||
await t.shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runWithdrawalFakebankTest.suites = ["wallet"];
|
runWithdrawalFakebankTest.suites = ["wallet"];
|
||||||
|
489
packages/taler-util/src/http-client/bank-core.ts
Normal file
489
packages/taler-util/src/http-client/bank-core.ts
Normal file
@ -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,)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
47
packages/taler-util/src/http-client/bank-integration.ts
Normal file
47
packages/taler-util/src/http-client/bank-integration.ts
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
32
packages/taler-util/src/http-client/bank-revenue.ts
Normal file
32
packages/taler-util/src/http-client/bank-revenue.ts
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
98
packages/taler-util/src/http-client/bank-wire.ts
Normal file
98
packages/taler-util/src/http-client/bank-wire.ts
Normal file
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
0
packages/taler-util/src/http-client/index.ts
Normal file
0
packages/taler-util/src/http-client/index.ts
Normal file
1183
packages/taler-util/src/http-client/types.ts
Normal file
1183
packages/taler-util/src/http-client/types.ts
Normal file
File diff suppressed because it is too large
Load Diff
68
packages/taler-util/src/http-client/utils.ts
Normal file
68
packages/taler-util/src/http-client/utils.ts
Normal file
@ -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"
|
||||||
|
}
|
@ -50,7 +50,7 @@ export interface HttpResponse {
|
|||||||
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
|
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
|
||||||
|
|
||||||
export interface HttpRequestOptions {
|
export interface HttpRequestOptions {
|
||||||
method?: "POST" | "PUT" | "GET" | "DELETE";
|
method?: "POST" | "PATCH" | "PUT" | "GET" | "DELETE";
|
||||||
headers?: { [name: string]: string };
|
headers?: { [name: string]: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,3 +42,8 @@ export * from "./transaction-test-data.js";
|
|||||||
export * from "./libeufin-api-types.js";
|
export * from "./libeufin-api-types.js";
|
||||||
export * from "./MerchantApiClient.js";
|
export * from "./MerchantApiClient.js";
|
||||||
export * from "./bank-api-client.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";
|
||||||
|
@ -123,7 +123,7 @@ function getValue(chr: string): number {
|
|||||||
switch (chr) {
|
switch (chr) {
|
||||||
case "O":
|
case "O":
|
||||||
case "o":
|
case "o":
|
||||||
a = "0;";
|
a = "0";
|
||||||
break;
|
break;
|
||||||
case "i":
|
case "i":
|
||||||
case "I":
|
case "I":
|
||||||
|
@ -951,7 +951,7 @@ export interface AccountInfo {
|
|||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export interface ExchangeWireJson {}
|
export interface ExchangeWireJson { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proposal returned from the contract URL.
|
* Proposal returned from the contract URL.
|
||||||
@ -1006,8 +1006,8 @@ export class WithdrawOperationStatusResponse {
|
|||||||
/**
|
/**
|
||||||
* Response from the merchant.
|
* Response from the merchant.
|
||||||
*/
|
*/
|
||||||
export class TipPickupGetResponse {
|
export class RewardPickupGetResponse {
|
||||||
tip_amount: string;
|
reward_amount: string;
|
||||||
|
|
||||||
exchange_url: string;
|
exchange_url: string;
|
||||||
|
|
||||||
@ -1310,12 +1310,6 @@ export const codecForDenominationPubKey = () =>
|
|||||||
.alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
|
.alternative(DenomKeyType.ClauseSchnorr, codecForCsDenominationPubKey())
|
||||||
.build("DenominationPubKey");
|
.build("DenominationPubKey");
|
||||||
|
|
||||||
export const codecForBankWithdrawalOperationPostResponse =
|
|
||||||
(): Codec<BankWithdrawalOperationPostResponse> =>
|
|
||||||
buildCodecForObject<BankWithdrawalOperationPostResponse>()
|
|
||||||
.property("transfer_done", codecForBoolean())
|
|
||||||
.build("BankWithdrawalOperationPostResponse");
|
|
||||||
|
|
||||||
export type AmountString = string;
|
export type AmountString = string;
|
||||||
export type Base32String = string;
|
export type Base32String = string;
|
||||||
export type EddsaSignatureString = string;
|
export type EddsaSignatureString = string;
|
||||||
@ -1572,9 +1566,9 @@ export const codecForWithdrawOperationStatusResponse =
|
|||||||
.property("wire_types", codecForList(codecForString()))
|
.property("wire_types", codecForList(codecForString()))
|
||||||
.build("WithdrawOperationStatusResponse");
|
.build("WithdrawOperationStatusResponse");
|
||||||
|
|
||||||
export const codecForTipPickupGetResponse = (): Codec<TipPickupGetResponse> =>
|
export const codecForRewardPickupGetResponse = (): Codec<RewardPickupGetResponse> =>
|
||||||
buildCodecForObject<TipPickupGetResponse>()
|
buildCodecForObject<RewardPickupGetResponse>()
|
||||||
.property("tip_amount", codecForString())
|
.property("reward_amount", codecForString())
|
||||||
.property("exchange_url", codecForString())
|
.property("exchange_url", codecForString())
|
||||||
.property("next_url", codecOptional(codecForString()))
|
.property("next_url", codecOptional(codecForString()))
|
||||||
.property("expiration", codecForTimestamp)
|
.property("expiration", codecForTimestamp)
|
||||||
@ -1591,7 +1585,7 @@ export const codecForWithdrawResponse = (): Codec<ExchangeWithdrawResponse> =>
|
|||||||
.property("ev_sig", codecForBlindedDenominationSignature())
|
.property("ev_sig", codecForBlindedDenominationSignature())
|
||||||
.build("WithdrawResponse");
|
.build("WithdrawResponse");
|
||||||
|
|
||||||
export const codecForWithdrawBatchResponse =
|
export const codecForExchangeWithdrawBatchResponse =
|
||||||
(): Codec<ExchangeWithdrawBatchResponse> =>
|
(): Codec<ExchangeWithdrawBatchResponse> =>
|
||||||
buildCodecForObject<ExchangeWithdrawBatchResponse>()
|
buildCodecForObject<ExchangeWithdrawBatchResponse>()
|
||||||
.property("ev_sigs", codecForList(codecForWithdrawResponse()))
|
.property("ev_sigs", codecForList(codecForWithdrawResponse()))
|
||||||
|
@ -43,6 +43,8 @@ import {
|
|||||||
codecForList,
|
codecForList,
|
||||||
codecForAny,
|
codecForAny,
|
||||||
codecForBoolean,
|
codecForBoolean,
|
||||||
|
codecForEither,
|
||||||
|
codecForConstString,
|
||||||
} from "./codec.js";
|
} from "./codec.js";
|
||||||
import {
|
import {
|
||||||
RefreshReason,
|
RefreshReason,
|
||||||
@ -62,6 +64,13 @@ export interface TransactionsRequest {
|
|||||||
*/
|
*/
|
||||||
search?: string;
|
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.
|
* If true, include all refreshes in the transactions list.
|
||||||
*/
|
*/
|
||||||
@ -690,6 +699,15 @@ export const codecForTransactionsRequest = (): Codec<TransactionsRequest> =>
|
|||||||
buildCodecForObject<TransactionsRequest>()
|
buildCodecForObject<TransactionsRequest>()
|
||||||
.property("currency", codecOptional(codecForString()))
|
.property("currency", codecOptional(codecForString()))
|
||||||
.property("search", codecOptional(codecForString()))
|
.property("search", codecOptional(codecForString()))
|
||||||
|
.property(
|
||||||
|
"sort",
|
||||||
|
codecOptional(
|
||||||
|
codecForEither(
|
||||||
|
codecForConstString("ascending"),
|
||||||
|
codecForConstString("descending"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
.property("includeRefreshes", codecOptional(codecForBoolean()))
|
.property("includeRefreshes", codecOptional(codecForBoolean()))
|
||||||
.build("TransactionsRequest");
|
.build("TransactionsRequest");
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ import {
|
|||||||
codecOptional,
|
codecOptional,
|
||||||
renderContext,
|
renderContext,
|
||||||
} from "./codec.js";
|
} from "./codec.js";
|
||||||
|
import { CurrencySpecification } from "./index.js";
|
||||||
import { VersionMatchResult } from "./libtool-version.js";
|
import { VersionMatchResult } from "./libtool-version.js";
|
||||||
import { PaytoUri } from "./payto.js";
|
import { PaytoUri } from "./payto.js";
|
||||||
import { AgeCommitmentProof } from "./taler-crypto.js";
|
import { AgeCommitmentProof } from "./taler-crypto.js";
|
||||||
@ -429,16 +430,6 @@ export interface GetCurrencySpecificationResponse {
|
|||||||
currencySpecification: CurrencySpecification;
|
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 {
|
export interface InitRequest {
|
||||||
skipDefaults?: boolean;
|
skipDefaults?: boolean;
|
||||||
}
|
}
|
||||||
@ -556,11 +547,11 @@ export interface CoinDumpJson {
|
|||||||
withdrawal_reserve_pub: string | undefined;
|
withdrawal_reserve_pub: string | undefined;
|
||||||
coin_status: CoinStatus;
|
coin_status: CoinStatus;
|
||||||
spend_allocation:
|
spend_allocation:
|
||||||
| {
|
| {
|
||||||
id: string;
|
id: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
/**
|
/**
|
||||||
* Information about the age restriction
|
* Information about the age restriction
|
||||||
*/
|
*/
|
||||||
|
@ -49,6 +49,9 @@ import {
|
|||||||
Logger,
|
Logger,
|
||||||
parsePaytoUri,
|
parsePaytoUri,
|
||||||
UnblindedSignature,
|
UnblindedSignature,
|
||||||
|
ExchangeBatchWithdrawRequest,
|
||||||
|
ExchangeWithdrawBatchResponse,
|
||||||
|
codecForExchangeWithdrawBatchResponse,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
HttpRequestLibrary,
|
HttpRequestLibrary,
|
||||||
@ -165,25 +168,29 @@ export async function withdrawCoin(args: {
|
|||||||
value: Amounts.parseOrThrow(denom.value),
|
value: Amounts.parseOrThrow(denom.value),
|
||||||
});
|
});
|
||||||
|
|
||||||
const reqBody: ExchangeWithdrawRequest = {
|
const reqBody: ExchangeBatchWithdrawRequest = {
|
||||||
denom_pub_hash: planchet.denomPubHash,
|
planchets: [
|
||||||
reserve_sig: planchet.withdrawSig,
|
{
|
||||||
coin_ev: planchet.coinEv,
|
denom_pub_hash: planchet.denomPubHash,
|
||||||
|
reserve_sig: planchet.withdrawSig,
|
||||||
|
coin_ev: planchet.coinEv,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
const reqUrl = new URL(
|
const reqUrl = new URL(
|
||||||
`reserves/${planchet.reservePub}/withdraw`,
|
`reserves/${planchet.reservePub}/batch-withdraw`,
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
).href;
|
).href;
|
||||||
|
|
||||||
const resp = await http.postJson(reqUrl, reqBody);
|
const resp = await http.fetch(reqUrl, { method: "POST", body: reqBody });
|
||||||
const r = await readSuccessResponseJsonOrThrow(
|
const rBatch = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
codecForWithdrawResponse(),
|
codecForExchangeWithdrawBatchResponse(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const ubSig = await cryptoApi.unblindDenominationSignature({
|
const ubSig = await cryptoApi.unblindDenominationSignature({
|
||||||
planchet,
|
planchet,
|
||||||
evSig: r.ev_sig,
|
evSig: rBatch.ev_sigs[0].ev_sig,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -101,14 +101,14 @@ async function gatherExchangePending(
|
|||||||
case ExchangeEntryDbUpdateStatus.Failed:
|
case ExchangeEntryDbUpdateStatus.Failed:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opTag = TaskIdentifiers.forExchangeUpdate(exch);
|
const opUpdateExchangeTag = TaskIdentifiers.forExchangeUpdate(exch);
|
||||||
let opr = await tx.operationRetries.get(opTag);
|
let opr = await tx.operationRetries.get(opUpdateExchangeTag);
|
||||||
const timestampDue = opr?.retryInfo.nextRetry ?? exch.nextRefreshCheckStamp;
|
const timestampDue = opr?.retryInfo.nextRetry ?? exch.nextRefreshCheckStamp;
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
type: PendingTaskType.ExchangeUpdate,
|
type: PendingTaskType.ExchangeUpdate,
|
||||||
...getPendingCommon(
|
...getPendingCommon(
|
||||||
ws,
|
ws,
|
||||||
opTag,
|
opUpdateExchangeTag,
|
||||||
AbsoluteTime.fromPreciseTimestamp(timestampPreciseFromDb(timestampDue)),
|
AbsoluteTime.fromPreciseTimestamp(timestampPreciseFromDb(timestampDue)),
|
||||||
),
|
),
|
||||||
givesLifeness: false,
|
givesLifeness: false,
|
||||||
@ -119,11 +119,12 @@ async function gatherExchangePending(
|
|||||||
// We only schedule a check for auto-refresh if the exchange update
|
// We only schedule a check for auto-refresh if the exchange update
|
||||||
// was successful.
|
// was successful.
|
||||||
if (!opr?.lastError) {
|
if (!opr?.lastError) {
|
||||||
|
const opCheckRefreshTag = TaskIdentifiers.forExchangeCheckRefresh(exch);
|
||||||
resp.pendingOperations.push({
|
resp.pendingOperations.push({
|
||||||
type: PendingTaskType.ExchangeCheckRefresh,
|
type: PendingTaskType.ExchangeCheckRefresh,
|
||||||
...getPendingCommon(
|
...getPendingCommon(
|
||||||
ws,
|
ws,
|
||||||
opTag,
|
opCheckRefreshTag,
|
||||||
AbsoluteTime.fromPreciseTimestamp(
|
AbsoluteTime.fromPreciseTimestamp(
|
||||||
timestampPreciseFromDb(timestampDue),
|
timestampPreciseFromDb(timestampDue),
|
||||||
),
|
),
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
Amounts,
|
Amounts,
|
||||||
BlindedDenominationSignature,
|
BlindedDenominationSignature,
|
||||||
codecForMerchantTipResponseV2,
|
codecForMerchantTipResponseV2,
|
||||||
codecForTipPickupGetResponse,
|
codecForRewardPickupGetResponse,
|
||||||
CoinStatus,
|
CoinStatus,
|
||||||
DenomKeyType,
|
DenomKeyType,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
@ -168,11 +168,11 @@ export async function prepareTip(
|
|||||||
const merchantResp = await ws.http.fetch(tipStatusUrl.href);
|
const merchantResp = await ws.http.fetch(tipStatusUrl.href);
|
||||||
const tipPickupStatus = await readSuccessResponseJsonOrThrow(
|
const tipPickupStatus = await readSuccessResponseJsonOrThrow(
|
||||||
merchantResp,
|
merchantResp,
|
||||||
codecForTipPickupGetResponse(),
|
codecForRewardPickupGetResponse(),
|
||||||
);
|
);
|
||||||
logger.trace(`status ${j2s(tipPickupStatus)}`);
|
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");
|
logger.trace("new tip, creating tip record");
|
||||||
await updateExchangeFromUrl(ws, tipPickupStatus.exchange_url);
|
await updateExchangeFromUrl(ws, tipPickupStatus.exchange_url);
|
||||||
|
@ -74,6 +74,7 @@ import {
|
|||||||
import { initiatePeerPushDebit } from "./pay-peer-push-debit.js";
|
import { initiatePeerPushDebit } from "./pay-peer-push-debit.js";
|
||||||
import { OpenedPromise, openPromise } from "../index.js";
|
import { OpenedPromise, openPromise } from "../index.js";
|
||||||
import { getTransactionById, getTransactions } from "./transactions.js";
|
import { getTransactionById, getTransactions } from "./transactions.js";
|
||||||
|
import { getPendingOperations } from "./pending.js";
|
||||||
|
|
||||||
const logger = new Logger("operations/testing.ts");
|
const logger = new Logger("operations/testing.ts");
|
||||||
|
|
||||||
@ -290,7 +291,7 @@ export async function runIntegrationTest(
|
|||||||
corebankApiBaseUrl: args.corebankApiBaseUrl,
|
corebankApiBaseUrl: args.corebankApiBaseUrl,
|
||||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||||
});
|
});
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
logger.info("done withdrawing test balance");
|
logger.info("done withdrawing test balance");
|
||||||
|
|
||||||
const balance = await getBalances(ws);
|
const balance = await getBalances(ws);
|
||||||
@ -305,7 +306,7 @@ export async function runIntegrationTest(
|
|||||||
await makePayment(ws, myMerchant, args.amountToSpend, "hello world");
|
await makePayment(ws, myMerchant, args.amountToSpend, "hello world");
|
||||||
|
|
||||||
// Wait until the refresh is done
|
// Wait until the refresh is done
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
logger.trace("withdrawing test balance for refund");
|
logger.trace("withdrawing test balance for refund");
|
||||||
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
|
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
|
||||||
@ -320,7 +321,7 @@ export async function runIntegrationTest(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Wait until the withdraw is done
|
// Wait until the withdraw is done
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
const { orderId: refundOrderId } = await makePayment(
|
const { orderId: refundOrderId } = await makePayment(
|
||||||
ws,
|
ws,
|
||||||
@ -344,7 +345,7 @@ export async function runIntegrationTest(
|
|||||||
logger.trace("integration test: applied refund");
|
logger.trace("integration test: applied refund");
|
||||||
|
|
||||||
// Wait until the refund is done
|
// Wait until the refund is done
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
logger.trace("integration test: making payment after refund");
|
logger.trace("integration test: making payment after refund");
|
||||||
|
|
||||||
@ -357,12 +358,17 @@ export async function runIntegrationTest(
|
|||||||
|
|
||||||
logger.trace("integration test: make payment done");
|
logger.trace("integration test: make payment done");
|
||||||
|
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
logger.trace("integration test: all done!");
|
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");
|
logger.info("waiting until all transactions are in a final state");
|
||||||
ws.ensureTaskLoopRunning();
|
ws.ensureTaskLoopRunning();
|
||||||
let p: OpenedPromise<void> | undefined = undefined;
|
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");
|
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(
|
export async function waitUntilRefreshesDone(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@ -463,7 +507,7 @@ export async function waitUntilRefreshesDone(
|
|||||||
logger.info("done waiting until all refreshes are in a final state");
|
logger.info("done waiting until all refreshes are in a final state");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitUntilPendingReady(
|
async function waitUntilTransactionPendingReady(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
transactionId: string,
|
transactionId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@ -560,7 +604,7 @@ export async function runIntegrationTest2(
|
|||||||
corebankApiBaseUrl: args.corebankApiBaseUrl,
|
corebankApiBaseUrl: args.corebankApiBaseUrl,
|
||||||
exchangeBaseUrl: args.exchangeBaseUrl,
|
exchangeBaseUrl: args.exchangeBaseUrl,
|
||||||
});
|
});
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
logger.info("done withdrawing test balance");
|
logger.info("done withdrawing test balance");
|
||||||
|
|
||||||
const balance = await getBalances(ws);
|
const balance = await getBalances(ws);
|
||||||
@ -580,7 +624,7 @@ export async function runIntegrationTest2(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Wait until the refresh is done
|
// Wait until the refresh is done
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
logger.trace("withdrawing test balance for refund");
|
logger.trace("withdrawing test balance for refund");
|
||||||
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
|
const withdrawAmountTwo = Amounts.parseOrThrow(`${currency}:18`);
|
||||||
@ -595,7 +639,7 @@ export async function runIntegrationTest2(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Wait until the withdraw is done
|
// Wait until the withdraw is done
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
const { orderId: refundOrderId } = await makePayment(
|
const { orderId: refundOrderId } = await makePayment(
|
||||||
ws,
|
ws,
|
||||||
@ -619,7 +663,7 @@ export async function runIntegrationTest2(
|
|||||||
logger.trace("integration test: applied refund");
|
logger.trace("integration test: applied refund");
|
||||||
|
|
||||||
// Wait until the refund is done
|
// Wait until the refund is done
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
logger.trace("integration test: making payment after refund");
|
logger.trace("integration test: making payment after refund");
|
||||||
|
|
||||||
@ -632,7 +676,7 @@ export async function runIntegrationTest2(
|
|||||||
|
|
||||||
logger.trace("integration test: make payment done");
|
logger.trace("integration test: make payment done");
|
||||||
|
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
const peerPushInit = await initiatePeerPushDebit(ws, {
|
const peerPushInit = await initiatePeerPushDebit(ws, {
|
||||||
partialContractTerms: {
|
partialContractTerms: {
|
||||||
@ -647,7 +691,7 @@ export async function runIntegrationTest2(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitUntilPendingReady(ws, peerPushInit.transactionId);
|
await waitUntilTransactionPendingReady(ws, peerPushInit.transactionId);
|
||||||
|
|
||||||
const peerPushCredit = await preparePeerPushCredit(ws, {
|
const peerPushCredit = await preparePeerPushCredit(ws, {
|
||||||
talerUri: peerPushInit.talerUri,
|
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, {
|
const peerPullInc = await preparePeerPullDebit(ws, {
|
||||||
talerUri: peerPullInit.talerUri,
|
talerUri: peerPullInit.talerUri,
|
||||||
@ -680,7 +724,7 @@ export async function runIntegrationTest2(
|
|||||||
peerPullDebitId: peerPullInc.peerPullDebitId,
|
peerPullDebitId: peerPullInc.peerPullDebitId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitUntilDone(ws);
|
await waitUntilTransactionsFinal(ws);
|
||||||
|
|
||||||
logger.trace("integration test: all done!");
|
logger.trace("integration test: all done!");
|
||||||
}
|
}
|
||||||
|
@ -1290,9 +1290,16 @@ export async function getTransactions(
|
|||||||
const txPending = transactions.filter((x) => isPending(x));
|
const txPending = transactions.filter((x) => isPending(x));
|
||||||
const txNotPending = 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) => {
|
const txCmp = (h1: Transaction, h2: Transaction) => {
|
||||||
// Order transactions by timestamp. Newest transactions come first.
|
// Order transactions by timestamp. Newest transactions come first.
|
||||||
const tsCmp = -AbsoluteTime.cmp(
|
const tsCmp = AbsoluteTime.cmp(
|
||||||
AbsoluteTime.fromPreciseTimestamp(h1.timestamp),
|
AbsoluteTime.fromPreciseTimestamp(h1.timestamp),
|
||||||
AbsoluteTime.fromPreciseTimestamp(h2.timestamp),
|
AbsoluteTime.fromPreciseTimestamp(h2.timestamp),
|
||||||
);
|
);
|
||||||
@ -1300,7 +1307,7 @@ export async function getTransactions(
|
|||||||
if (tsCmp === 0) {
|
if (tsCmp === 0) {
|
||||||
return Math.sign(txOrder[h1.type] - txOrder[h2.type]);
|
return Math.sign(txOrder[h1.type] - txOrder[h2.type]);
|
||||||
}
|
}
|
||||||
return tsCmp;
|
return sortSign * tsCmp;
|
||||||
};
|
};
|
||||||
|
|
||||||
txPending.sort(txCmp);
|
txPending.sort(txCmp);
|
||||||
|
@ -33,7 +33,7 @@ import {
|
|||||||
codecForReserveStatus,
|
codecForReserveStatus,
|
||||||
codecForTalerConfigResponse,
|
codecForTalerConfigResponse,
|
||||||
codecForWalletKycUuid,
|
codecForWalletKycUuid,
|
||||||
codecForWithdrawBatchResponse,
|
codecForExchangeWithdrawBatchResponse,
|
||||||
codecForWithdrawOperationStatusResponse,
|
codecForWithdrawOperationStatusResponse,
|
||||||
codecForWithdrawResponse,
|
codecForWithdrawResponse,
|
||||||
CoinStatus,
|
CoinStatus,
|
||||||
@ -944,7 +944,7 @@ async function processPlanchetExchangeBatchRequest(
|
|||||||
}
|
}
|
||||||
const r = await readSuccessResponseJsonOrThrow(
|
const r = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
codecForWithdrawBatchResponse(),
|
codecForExchangeWithdrawBatchResponse(),
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
coinIdxs: requestCoinIdxs,
|
coinIdxs: requestCoinIdxs,
|
||||||
|
@ -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
|
* Will be replaced with the value from package.json in a
|
||||||
* post-compilation step (inside lib/).
|
* 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.
|
||||||
|
*/
|
@ -224,6 +224,7 @@ export enum WalletApiOperation {
|
|||||||
DeleteStoredBackup = "deleteStoredBackup",
|
DeleteStoredBackup = "deleteStoredBackup",
|
||||||
RecoverStoredBackup = "recoverStoredBackup",
|
RecoverStoredBackup = "recoverStoredBackup",
|
||||||
UpdateExchangeEntry = "updateExchangeEntry",
|
UpdateExchangeEntry = "updateExchangeEntry",
|
||||||
|
TestingWaitTasksProcessed = "testingWaitTasksProcessed",
|
||||||
}
|
}
|
||||||
|
|
||||||
// group: Initialization
|
// group: Initialization
|
||||||
@ -1007,7 +1008,7 @@ export type TestingSetTimetravelOp = {
|
|||||||
/**
|
/**
|
||||||
* Wait until all transactions are in a final state.
|
* Wait until all transactions are in a final state.
|
||||||
*/
|
*/
|
||||||
export type TestingWaitTransactionsFinal = {
|
export type TestingWaitTransactionsFinalOp = {
|
||||||
op: WalletApiOperation.TestingWaitTransactionsFinal;
|
op: WalletApiOperation.TestingWaitTransactionsFinal;
|
||||||
request: EmptyObject;
|
request: EmptyObject;
|
||||||
response: EmptyObject;
|
response: EmptyObject;
|
||||||
@ -1016,12 +1017,21 @@ export type TestingWaitTransactionsFinal = {
|
|||||||
/**
|
/**
|
||||||
* Wait until all refresh transactions are in a final state.
|
* Wait until all refresh transactions are in a final state.
|
||||||
*/
|
*/
|
||||||
export type TestingWaitRefreshesFinal = {
|
export type TestingWaitRefreshesFinalOp = {
|
||||||
op: WalletApiOperation.TestingWaitRefreshesFinal;
|
op: WalletApiOperation.TestingWaitRefreshesFinal;
|
||||||
request: EmptyObject;
|
request: EmptyObject;
|
||||||
response: 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.
|
* Wait until a transaction is in a particular state.
|
||||||
*/
|
*/
|
||||||
@ -1132,8 +1142,9 @@ export type WalletOperations = {
|
|||||||
[WalletApiOperation.Recycle]: RecycleOp;
|
[WalletApiOperation.Recycle]: RecycleOp;
|
||||||
[WalletApiOperation.ApplyDevExperiment]: ApplyDevExperimentOp;
|
[WalletApiOperation.ApplyDevExperiment]: ApplyDevExperimentOp;
|
||||||
[WalletApiOperation.ValidateIban]: ValidateIbanOp;
|
[WalletApiOperation.ValidateIban]: ValidateIbanOp;
|
||||||
[WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal;
|
[WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinalOp;
|
||||||
[WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal;
|
[WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinalOp;
|
||||||
|
[WalletApiOperation.TestingWaitTasksProcessed]: TestingWaitTasksProcessedOp;
|
||||||
[WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp;
|
[WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp;
|
||||||
[WalletApiOperation.TestingWaitTransactionState]: TestingWaitTransactionStateOp;
|
[WalletApiOperation.TestingWaitTransactionState]: TestingWaitTransactionStateOp;
|
||||||
[WalletApiOperation.GetCurrencySpecification]: GetCurrencySpecificationOp;
|
[WalletApiOperation.GetCurrencySpecification]: GetCurrencySpecificationOp;
|
||||||
|
@ -252,9 +252,10 @@ import {
|
|||||||
runIntegrationTest2,
|
runIntegrationTest2,
|
||||||
testPay,
|
testPay,
|
||||||
waitTransactionState,
|
waitTransactionState,
|
||||||
waitUntilDone,
|
waitUntilTransactionsFinal,
|
||||||
waitUntilRefreshesDone,
|
waitUntilRefreshesDone,
|
||||||
withdrawTestBalance,
|
withdrawTestBalance,
|
||||||
|
waitUntilTasksProcessed,
|
||||||
} from "./operations/testing.js";
|
} from "./operations/testing.js";
|
||||||
import {
|
import {
|
||||||
acceptTip,
|
acceptTip,
|
||||||
@ -1427,6 +1428,10 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
await waitTransactionState(ws, req.transactionId, req.txState);
|
await waitTransactionState(ws, req.transactionId, req.txState);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
case WalletApiOperation.TestingWaitTasksProcessed: {
|
||||||
|
await waitUntilTasksProcessed(ws);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
case WalletApiOperation.GetCurrencySpecification: {
|
case WalletApiOperation.GetCurrencySpecification: {
|
||||||
// Ignore result, just validate in this mock implementation
|
// Ignore result, just validate in this mock implementation
|
||||||
const req = codecForGetCurrencyInfoRequest().decode(payload);
|
const req = codecForGetCurrencyInfoRequest().decode(payload);
|
||||||
@ -1436,9 +1441,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
currencySpecification: {
|
currencySpecification: {
|
||||||
decimal_separator: ",",
|
decimal_separator: ",",
|
||||||
name: "Kudos (Taler Demonstrator)",
|
name: "Kudos (Taler Demonstrator)",
|
||||||
fractional_input_digits: 2,
|
num_fractional_input_digits: 2,
|
||||||
fractional_normal_digits: 2,
|
num_fractional_normal_digits: 2,
|
||||||
fractional_trailing_zero_digits: 2,
|
num_fractional_trailing_zero_digits: 2,
|
||||||
is_currency_name_leading: true,
|
is_currency_name_leading: true,
|
||||||
alt_unit_names: {
|
alt_unit_names: {
|
||||||
"0": "ク",
|
"0": "ク",
|
||||||
@ -1451,9 +1456,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
currencySpecification: {
|
currencySpecification: {
|
||||||
decimal_separator: ",",
|
decimal_separator: ",",
|
||||||
name: "Test (Taler Unstable Demonstrator)",
|
name: "Test (Taler Unstable Demonstrator)",
|
||||||
fractional_input_digits: 0,
|
num_fractional_input_digits: 0,
|
||||||
fractional_normal_digits: 0,
|
num_fractional_normal_digits: 0,
|
||||||
fractional_trailing_zero_digits: 0,
|
num_fractional_trailing_zero_digits: 0,
|
||||||
is_currency_name_leading: false,
|
is_currency_name_leading: false,
|
||||||
alt_unit_names: {},
|
alt_unit_names: {},
|
||||||
},
|
},
|
||||||
@ -1464,9 +1469,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
currencySpecification: {
|
currencySpecification: {
|
||||||
decimal_separator: ",",
|
decimal_separator: ",",
|
||||||
name: "Unknown",
|
name: "Unknown",
|
||||||
fractional_input_digits: 2,
|
num_fractional_input_digits: 2,
|
||||||
fractional_normal_digits: 2,
|
num_fractional_normal_digits: 2,
|
||||||
fractional_trailing_zero_digits: 2,
|
num_fractional_trailing_zero_digits: 2,
|
||||||
is_currency_name_leading: true,
|
is_currency_name_leading: true,
|
||||||
alt_unit_names: {},
|
alt_unit_names: {},
|
||||||
},
|
},
|
||||||
@ -1600,7 +1605,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
|||||||
return getVersion(ws);
|
return getVersion(ws);
|
||||||
}
|
}
|
||||||
case WalletApiOperation.TestingWaitTransactionsFinal:
|
case WalletApiOperation.TestingWaitTransactionsFinal:
|
||||||
return await waitUntilDone(ws);
|
return await waitUntilTransactionsFinal(ws);
|
||||||
case WalletApiOperation.TestingWaitRefreshesFinal:
|
case WalletApiOperation.TestingWaitRefreshesFinal:
|
||||||
return await waitUntilRefreshesDone(ws);
|
return await waitUntilRefreshesDone(ws);
|
||||||
case WalletApiOperation.TestingSetTimetravel: {
|
case WalletApiOperation.TestingSetTimetravel: {
|
||||||
|
Loading…
Reference in New Issue
Block a user