use wallet's http lib for test balance withdrawal, remove redundant integration tests
This commit is contained in:
parent
b37c98346d
commit
aa481e4267
@ -1,143 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of GNU Taler
|
|
||||||
(C) 2019 GNUnet e.V.
|
|
||||||
|
|
||||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
||||||
terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation; either version 3, or (at your option) any later version.
|
|
||||||
|
|
||||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper functions to deal with the GNU Taler demo bank.
|
|
||||||
*
|
|
||||||
* Mostly useful for automated tests.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports.
|
|
||||||
*/
|
|
||||||
import Axios from "axios";
|
|
||||||
|
|
||||||
export interface BankUser {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a random alphanumeric ID. Does *not* use cryptographically
|
|
||||||
* secure randomness.
|
|
||||||
*/
|
|
||||||
function makeId(length: number): string {
|
|
||||||
let result = "";
|
|
||||||
const characters =
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to generate the "Authorization" HTTP header.
|
|
||||||
*/
|
|
||||||
function makeAuth(username: string, password: string): string {
|
|
||||||
const auth = `${username}:${password}`;
|
|
||||||
const authEncoded: string = Buffer.from(auth).toString("base64");
|
|
||||||
return `Basic ${authEncoded}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Client for the Taler bank access API.
|
|
||||||
*/
|
|
||||||
export class Bank {
|
|
||||||
constructor(private bankBaseUrl: string) {}
|
|
||||||
|
|
||||||
async generateWithdrawUri(
|
|
||||||
bankUser: BankUser,
|
|
||||||
amount: string,
|
|
||||||
): Promise<string> {
|
|
||||||
const body = {
|
|
||||||
amount,
|
|
||||||
};
|
|
||||||
|
|
||||||
const reqUrl = new URL("api/withdraw-headless-uri", this.bankBaseUrl).href;
|
|
||||||
|
|
||||||
const resp = await Axios({
|
|
||||||
method: "post",
|
|
||||||
url: reqUrl,
|
|
||||||
data: body,
|
|
||||||
responseType: "json",
|
|
||||||
headers: {
|
|
||||||
Authorization: makeAuth(bankUser.username, bankUser.password),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.status != 200) {
|
|
||||||
throw Error("failed to create bank reserve");
|
|
||||||
}
|
|
||||||
|
|
||||||
const withdrawUri = resp.data["taler_withdraw_uri"];
|
|
||||||
if (!withdrawUri) {
|
|
||||||
throw Error("Bank's response did not include withdraw URI");
|
|
||||||
}
|
|
||||||
return withdrawUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createReserve(
|
|
||||||
bankUser: BankUser,
|
|
||||||
amount: string,
|
|
||||||
reservePub: string,
|
|
||||||
exchangePaytoUri: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const reqUrl = new URL("testing/withdraw", this.bankBaseUrl).href;
|
|
||||||
|
|
||||||
const body = {
|
|
||||||
username: bankUser,
|
|
||||||
amount,
|
|
||||||
reserve_pub: reservePub,
|
|
||||||
exchange_payto_uri: exchangePaytoUri,
|
|
||||||
};
|
|
||||||
|
|
||||||
const resp = await Axios({
|
|
||||||
method: "post",
|
|
||||||
url: reqUrl,
|
|
||||||
data: body,
|
|
||||||
responseType: "json",
|
|
||||||
headers: {
|
|
||||||
Authorization: makeAuth(bankUser.username, bankUser.password),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.status != 200) {
|
|
||||||
throw Error("failed to create bank reserve");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async registerRandomUser(): Promise<BankUser> {
|
|
||||||
const reqUrl = new URL("testing/register", this.bankBaseUrl).href;
|
|
||||||
const randId = makeId(8);
|
|
||||||
const bankUser: BankUser = {
|
|
||||||
username: `testuser-${randId}`,
|
|
||||||
password: `testpw-${randId}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const resp = await Axios({
|
|
||||||
method: "post",
|
|
||||||
url: reqUrl,
|
|
||||||
data: bankUser,
|
|
||||||
responseType: "json",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (resp.status != 200) {
|
|
||||||
throw Error("could not register bank user");
|
|
||||||
}
|
|
||||||
return bankUser;
|
|
||||||
}
|
|
||||||
}
|
|
@ -26,18 +26,15 @@ import { Wallet } from "../wallet";
|
|||||||
import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
|
import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
|
||||||
import { openTalerDatabase } from "../db";
|
import { openTalerDatabase } from "../db";
|
||||||
import { HttpRequestLibrary } from "../util/http";
|
import { HttpRequestLibrary } from "../util/http";
|
||||||
import { Bank } from "./bank";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorker";
|
import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorker";
|
||||||
import { WalletNotification, NotificationType } from "../types/notifications";
|
import { WalletNotification } from "../types/notifications";
|
||||||
import { Database } from "../util/query";
|
import { Database } from "../util/query";
|
||||||
import { NodeHttpLib } from "./NodeHttpLib";
|
import { NodeHttpLib } from "./NodeHttpLib";
|
||||||
import { Logger } from "../util/logging";
|
import { Logger } from "../util/logging";
|
||||||
import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker";
|
import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker";
|
||||||
import { WithdrawalSourceType } from "../types/dbTypes";
|
|
||||||
import { Amounts } from "../util/amounts";
|
|
||||||
|
|
||||||
const logger = new Logger("helpers.ts");
|
const logger = new Logger("headless/helpers.ts");
|
||||||
|
|
||||||
export interface DefaultNodeWalletArgs {
|
export interface DefaultNodeWalletArgs {
|
||||||
/**
|
/**
|
||||||
@ -135,46 +132,3 @@ export async function getDefaultNodeWallet(
|
|||||||
}
|
}
|
||||||
return w;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function withdrawTestBalance(
|
|
||||||
myWallet: Wallet,
|
|
||||||
amount = "TESTKUDOS:10",
|
|
||||||
bankBaseUrl = "https://bank.test.taler.net/",
|
|
||||||
exchangeBaseUrl = "https://exchange.test.taler.net/",
|
|
||||||
): Promise<void> {
|
|
||||||
await myWallet.updateExchangeFromUrl(exchangeBaseUrl, true);
|
|
||||||
const reserveResponse = await myWallet.acceptManualWithdrawal(
|
|
||||||
exchangeBaseUrl,
|
|
||||||
Amounts.parseOrThrow(amount),
|
|
||||||
);
|
|
||||||
|
|
||||||
const reservePub = reserveResponse.reservePub;
|
|
||||||
|
|
||||||
const bank = new Bank(bankBaseUrl);
|
|
||||||
|
|
||||||
const bankUser = await bank.registerRandomUser();
|
|
||||||
|
|
||||||
logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`);
|
|
||||||
|
|
||||||
const exchangePaytoUri = await myWallet.getExchangePaytoUri(exchangeBaseUrl, [
|
|
||||||
"x-taler-bank",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const donePromise = new Promise((resolve, reject) => {
|
|
||||||
myWallet.runRetryLoop().catch((x) => {
|
|
||||||
reject(x);
|
|
||||||
});
|
|
||||||
myWallet.addNotificationListener((n) => {
|
|
||||||
if (
|
|
||||||
n.type === NotificationType.WithdrawGroupFinished &&
|
|
||||||
n.withdrawalSource.type === WithdrawalSourceType.Reserve &&
|
|
||||||
n.withdrawalSource.reservePub === reservePub
|
|
||||||
) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await bank.createReserve(bankUser, amount, reservePub, exchangePaytoUri);
|
|
||||||
await donePromise;
|
|
||||||
}
|
|
||||||
|
@ -1,327 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of GNU Taler
|
|
||||||
(C) 2019 GNUnet e.V.
|
|
||||||
|
|
||||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
||||||
terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation; either version 3, or (at your option) any later version.
|
|
||||||
|
|
||||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Integration tests against real Taler bank/exchange/merchant deployments.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers";
|
|
||||||
import { MerchantBackendConnection } from "./merchant";
|
|
||||||
import { Logger } from "../util/logging";
|
|
||||||
import { NodeHttpLib } from "./NodeHttpLib";
|
|
||||||
import { Wallet } from "../wallet";
|
|
||||||
import { Configuration } from "../util/talerconfig";
|
|
||||||
import { Amounts, AmountJson } from "../util/amounts";
|
|
||||||
|
|
||||||
const logger = new Logger("integrationtest.ts");
|
|
||||||
|
|
||||||
export interface IntegrationTestArgs {
|
|
||||||
exchangeBaseUrl: string;
|
|
||||||
bankBaseUrl: string;
|
|
||||||
merchantBaseUrl: string;
|
|
||||||
merchantApiKey: string;
|
|
||||||
amountToWithdraw: string;
|
|
||||||
amountToSpend: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makePayment(
|
|
||||||
wallet: Wallet,
|
|
||||||
merchant: MerchantBackendConnection,
|
|
||||||
amount: string,
|
|
||||||
summary: string,
|
|
||||||
): Promise<{ orderId: string }> {
|
|
||||||
const orderResp = await merchant.createOrder(
|
|
||||||
amount,
|
|
||||||
summary,
|
|
||||||
"taler://fulfillment-success/thx",
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("created order with orderId", orderResp.orderId);
|
|
||||||
|
|
||||||
let paymentStatus = await merchant.checkPayment(orderResp.orderId);
|
|
||||||
|
|
||||||
console.log("payment status", paymentStatus);
|
|
||||||
|
|
||||||
const talerPayUri = paymentStatus.taler_pay_uri;
|
|
||||||
if (!talerPayUri) {
|
|
||||||
throw Error("no taler://pay/ URI in payment response");
|
|
||||||
}
|
|
||||||
|
|
||||||
const preparePayResult = await wallet.preparePayForUri(talerPayUri);
|
|
||||||
|
|
||||||
console.log("prepare pay result", preparePayResult);
|
|
||||||
|
|
||||||
if (preparePayResult.status != "payment-possible") {
|
|
||||||
throw Error("payment not possible");
|
|
||||||
}
|
|
||||||
|
|
||||||
const confirmPayResult = await wallet.confirmPay(
|
|
||||||
preparePayResult.proposalId,
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("confirmPayResult", confirmPayResult);
|
|
||||||
|
|
||||||
paymentStatus = await merchant.checkPayment(orderResp.orderId);
|
|
||||||
|
|
||||||
if (paymentStatus.order_status !== "paid") {
|
|
||||||
console.log("payment status:", paymentStatus);
|
|
||||||
throw Error("payment did not succeed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
orderId: orderResp.orderId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runIntegrationTest(
|
|
||||||
args: IntegrationTestArgs,
|
|
||||||
): Promise<void> {
|
|
||||||
logger.info("running test with arguments", args);
|
|
||||||
|
|
||||||
const parsedSpendAmount = Amounts.parseOrThrow(args.amountToSpend);
|
|
||||||
const currency = parsedSpendAmount.currency;
|
|
||||||
|
|
||||||
const myHttpLib = new NodeHttpLib();
|
|
||||||
myHttpLib.setThrottling(false);
|
|
||||||
|
|
||||||
const myWallet = await getDefaultNodeWallet({ httpLib: myHttpLib });
|
|
||||||
|
|
||||||
myWallet.runRetryLoop().catch((e) => {
|
|
||||||
console.error("exception during retry loop:", e);
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("withdrawing test balance");
|
|
||||||
await withdrawTestBalance(
|
|
||||||
myWallet,
|
|
||||||
args.amountToWithdraw,
|
|
||||||
args.bankBaseUrl,
|
|
||||||
args.exchangeBaseUrl,
|
|
||||||
);
|
|
||||||
logger.info("done withdrawing test balance");
|
|
||||||
|
|
||||||
const myMerchant = new MerchantBackendConnection(
|
|
||||||
args.merchantBaseUrl,
|
|
||||||
args.merchantApiKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
await makePayment(myWallet, myMerchant, args.amountToSpend, "hello world");
|
|
||||||
|
|
||||||
// Wait until the refresh is done
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
|
|
||||||
console.log("withdrawing test balance for refund");
|
|
||||||
const withdrawAmountTwo: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 18,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
const spendAmountTwo: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 7,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const refundAmount: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 6,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const spendAmountThree: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 3,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
await withdrawTestBalance(
|
|
||||||
myWallet,
|
|
||||||
Amounts.stringify(withdrawAmountTwo),
|
|
||||||
args.bankBaseUrl,
|
|
||||||
args.exchangeBaseUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait until the withdraw is done
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
|
|
||||||
const { orderId: refundOrderId } = await makePayment(
|
|
||||||
myWallet,
|
|
||||||
myMerchant,
|
|
||||||
Amounts.stringify(spendAmountTwo),
|
|
||||||
"order that will be refunded",
|
|
||||||
);
|
|
||||||
|
|
||||||
const refundUri = await myMerchant.refund(
|
|
||||||
refundOrderId,
|
|
||||||
"test refund",
|
|
||||||
Amounts.stringify(refundAmount),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("refund URI", refundUri);
|
|
||||||
|
|
||||||
await myWallet.applyRefund(refundUri);
|
|
||||||
|
|
||||||
// Wait until the refund is done
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
|
|
||||||
await makePayment(
|
|
||||||
myWallet,
|
|
||||||
myMerchant,
|
|
||||||
Amounts.stringify(spendAmountThree),
|
|
||||||
"payment after refund",
|
|
||||||
);
|
|
||||||
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runIntegrationTestBasic(
|
|
||||||
cfg: Configuration,
|
|
||||||
): Promise<void> {
|
|
||||||
const walletDbPath = cfg.getString("integrationtest", "walletdb").required();
|
|
||||||
|
|
||||||
const bankBaseUrl = cfg
|
|
||||||
.getString("integrationtest", "bank_base_url")
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const exchangeBaseUrl = cfg
|
|
||||||
.getString("integrationtest", "exchange_base_url")
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const merchantBaseUrl = cfg
|
|
||||||
.getString("integrationtest", "merchant_base_url")
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const merchantApiKey = cfg
|
|
||||||
.getString("integrationtest", "merchant_api_key")
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const parsedWithdrawAmount = cfg
|
|
||||||
.getAmount("integrationtest-basic", "amount_withdraw")
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const parsedSpendAmount = cfg
|
|
||||||
.getAmount("integrationtest-basic", "amount_spend")
|
|
||||||
.required();
|
|
||||||
|
|
||||||
const currency = parsedSpendAmount.currency;
|
|
||||||
|
|
||||||
const myHttpLib = new NodeHttpLib();
|
|
||||||
myHttpLib.setThrottling(false);
|
|
||||||
|
|
||||||
const myWallet = await getDefaultNodeWallet({
|
|
||||||
httpLib: myHttpLib,
|
|
||||||
persistentStoragePath: walletDbPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
myWallet.runRetryLoop().catch((e) => {
|
|
||||||
console.error("exception during retry loop:", e);
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("withdrawing test balance");
|
|
||||||
await withdrawTestBalance(
|
|
||||||
myWallet,
|
|
||||||
Amounts.stringify(parsedWithdrawAmount),
|
|
||||||
bankBaseUrl,
|
|
||||||
exchangeBaseUrl,
|
|
||||||
);
|
|
||||||
logger.info("done withdrawing test balance");
|
|
||||||
|
|
||||||
const balance = await myWallet.getBalances();
|
|
||||||
|
|
||||||
console.log(JSON.stringify(balance, null, 2));
|
|
||||||
|
|
||||||
const myMerchant = new MerchantBackendConnection(
|
|
||||||
merchantBaseUrl,
|
|
||||||
merchantApiKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
await makePayment(
|
|
||||||
myWallet,
|
|
||||||
myMerchant,
|
|
||||||
Amounts.stringify(parsedSpendAmount),
|
|
||||||
"hello world",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait until the refresh is done
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
|
|
||||||
console.log("withdrawing test balance for refund");
|
|
||||||
const withdrawAmountTwo: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 18,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
const spendAmountTwo: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 7,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const refundAmount: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 6,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const spendAmountThree: AmountJson = {
|
|
||||||
currency,
|
|
||||||
value: 3,
|
|
||||||
fraction: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
await withdrawTestBalance(
|
|
||||||
myWallet,
|
|
||||||
Amounts.stringify(withdrawAmountTwo),
|
|
||||||
bankBaseUrl,
|
|
||||||
exchangeBaseUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Wait until the withdraw is done
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
|
|
||||||
const { orderId: refundOrderId } = await makePayment(
|
|
||||||
myWallet,
|
|
||||||
myMerchant,
|
|
||||||
Amounts.stringify(spendAmountTwo),
|
|
||||||
"order that will be refunded",
|
|
||||||
);
|
|
||||||
|
|
||||||
const refundUri = await myMerchant.refund(
|
|
||||||
refundOrderId,
|
|
||||||
"test refund",
|
|
||||||
Amounts.stringify(refundAmount),
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("refund URI", refundUri);
|
|
||||||
|
|
||||||
await myWallet.applyRefund(refundUri);
|
|
||||||
|
|
||||||
// Wait until the refund is done
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
|
|
||||||
await makePayment(
|
|
||||||
myWallet,
|
|
||||||
myMerchant,
|
|
||||||
Amounts.stringify(spendAmountThree),
|
|
||||||
"payment after refund",
|
|
||||||
);
|
|
||||||
|
|
||||||
await myWallet.runUntilDone();
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"history after integration test:",
|
|
||||||
JSON.stringify(history, undefined, 2),
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
This file is part of GNU Taler
|
|
||||||
(C) 2019 GNUnet e.V.
|
|
||||||
|
|
||||||
GNU Taler is free software; you can redistribute it and/or modify it under the
|
|
||||||
terms of the GNU General Public License as published by the Free Software
|
|
||||||
Foundation; either version 3, or (at your option) any later version.
|
|
||||||
|
|
||||||
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with
|
|
||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helpers for talking to the GNU Taler merchant backend.
|
|
||||||
* Used mostly for integration tests.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports.
|
|
||||||
*/
|
|
||||||
import axios from "axios";
|
|
||||||
import {
|
|
||||||
CheckPaymentResponse,
|
|
||||||
codecForCheckPaymentResponse,
|
|
||||||
} from "../types/talerTypes";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connection to the *internal* merchant backend.
|
|
||||||
*/
|
|
||||||
export class MerchantBackendConnection {
|
|
||||||
async refund(
|
|
||||||
orderId: string,
|
|
||||||
reason: string,
|
|
||||||
refundAmount: string,
|
|
||||||
): Promise<string> {
|
|
||||||
const reqUrl = new URL(
|
|
||||||
`private/orders/${orderId}/refund`,
|
|
||||||
this.merchantBaseUrl,
|
|
||||||
);
|
|
||||||
const refundReq = {
|
|
||||||
reason,
|
|
||||||
refund: refundAmount,
|
|
||||||
};
|
|
||||||
const resp = await axios({
|
|
||||||
method: "post",
|
|
||||||
url: reqUrl.href,
|
|
||||||
data: refundReq,
|
|
||||||
responseType: "json",
|
|
||||||
headers: {
|
|
||||||
Authorization: `ApiKey ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (resp.status != 200) {
|
|
||||||
throw Error("failed to do refund");
|
|
||||||
}
|
|
||||||
console.log("response", resp.data);
|
|
||||||
const refundUri = resp.data.taler_refund_uri;
|
|
||||||
if (!refundUri) {
|
|
||||||
throw Error("no refund URI in response");
|
|
||||||
}
|
|
||||||
return refundUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(public merchantBaseUrl: string, public apiKey: string) {}
|
|
||||||
|
|
||||||
async authorizeTip(amount: string, justification: string): Promise<string> {
|
|
||||||
const reqUrl = new URL("private/tips", this.merchantBaseUrl).href;
|
|
||||||
const tipReq = {
|
|
||||||
amount,
|
|
||||||
justification,
|
|
||||||
next_url: "about:blank",
|
|
||||||
};
|
|
||||||
const resp = await axios({
|
|
||||||
method: "post",
|
|
||||||
url: reqUrl,
|
|
||||||
data: tipReq,
|
|
||||||
responseType: "json",
|
|
||||||
headers: {
|
|
||||||
Authorization: `ApiKey ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const tipUri = resp.data.taler_tip_uri;
|
|
||||||
if (!tipUri) {
|
|
||||||
throw Error("response does not contain tip URI");
|
|
||||||
}
|
|
||||||
return tipUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createOrder(
|
|
||||||
amount: string,
|
|
||||||
summary: string,
|
|
||||||
fulfillmentUrl: string,
|
|
||||||
): Promise<{ orderId: string }> {
|
|
||||||
const t = Math.floor(new Date().getTime() / 1000) + 15 * 60;
|
|
||||||
const reqUrl = new URL("private/orders", this.merchantBaseUrl).href;
|
|
||||||
const orderReq = {
|
|
||||||
order: {
|
|
||||||
amount,
|
|
||||||
summary,
|
|
||||||
fulfillment_url: fulfillmentUrl,
|
|
||||||
refund_deadline: { t_ms: t * 1000 },
|
|
||||||
wire_transfer_deadline: { t_ms: t * 1000 },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const resp = await axios({
|
|
||||||
method: "post",
|
|
||||||
url: reqUrl,
|
|
||||||
data: orderReq,
|
|
||||||
responseType: "json",
|
|
||||||
headers: {
|
|
||||||
Authorization: `ApiKey ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (resp.status != 200) {
|
|
||||||
throw Error("failed to create bank reserve");
|
|
||||||
}
|
|
||||||
const orderId = resp.data.order_id;
|
|
||||||
if (!orderId) {
|
|
||||||
throw Error("no order id in response");
|
|
||||||
}
|
|
||||||
return { orderId };
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkPayment(orderId: string): Promise<CheckPaymentResponse> {
|
|
||||||
const reqUrl = new URL(`private/orders/${orderId}`, this.merchantBaseUrl)
|
|
||||||
.href;
|
|
||||||
const resp = await axios({
|
|
||||||
method: "get",
|
|
||||||
url: reqUrl,
|
|
||||||
responseType: "json",
|
|
||||||
headers: {
|
|
||||||
Authorization: `ApiKey ${this.apiKey}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (resp.status != 200) {
|
|
||||||
throw Error("failed to check payment");
|
|
||||||
}
|
|
||||||
|
|
||||||
return codecForCheckPaymentResponse().decode(resp.data);
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,9 +16,7 @@
|
|||||||
|
|
||||||
import os from "os";
|
import os from "os";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers";
|
import { getDefaultNodeWallet } from "./helpers";
|
||||||
import { MerchantBackendConnection } from "./merchant";
|
|
||||||
import { runIntegrationTest, runIntegrationTestBasic } from "./integrationtest";
|
|
||||||
import { Wallet } from "../wallet";
|
import { Wallet } from "../wallet";
|
||||||
import qrcodeGenerator from "qrcode-generator";
|
import qrcodeGenerator from "qrcode-generator";
|
||||||
import * as clk from "./clk";
|
import * as clk from "./clk";
|
||||||
@ -34,7 +32,6 @@ import {
|
|||||||
OperationFailedAndReportedError,
|
OperationFailedAndReportedError,
|
||||||
OperationFailedError,
|
OperationFailedError,
|
||||||
} from "../operations/errors";
|
} from "../operations/errors";
|
||||||
import { Bank } from "./bank";
|
|
||||||
import { classifyTalerUri, TalerUriType } from "../util/taleruri";
|
import { classifyTalerUri, TalerUriType } from "../util/taleruri";
|
||||||
import { Configuration } from "../util/talerconfig";
|
import { Configuration } from "../util/talerconfig";
|
||||||
import { setDangerousTimetravel } from "../util/time";
|
import { setDangerousTimetravel } from "../util/time";
|
||||||
@ -658,285 +655,4 @@ testCli.subcommand("vectors", "vectors").action(async (args) => {
|
|||||||
console.log(` (out) coin pub: ${encodeCrock(p.coinPub)}`);
|
console.log(` (out) coin pub: ${encodeCrock(p.coinPub)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("integrationtestBasic", "integrationtest-basic")
|
|
||||||
.requiredArgument("cfgfile", clk.STRING)
|
|
||||||
.action(async (args) => {
|
|
||||||
const cfgStr = fs.readFileSync(args.integrationtestBasic.cfgfile, "utf8");
|
|
||||||
const cfg = new Configuration();
|
|
||||||
cfg.loadFromString(cfgStr);
|
|
||||||
try {
|
|
||||||
await runIntegrationTestBasic(cfg);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("integration test failed");
|
|
||||||
console.log(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("testPayCmd", "test-pay", { help: "Create contract and pay." })
|
|
||||||
.requiredOption("merchant", ["-m", "--mechant-url"], clk.STRING)
|
|
||||||
.requiredOption("apikey", ["-k", "--mechant-api-key"], clk.STRING)
|
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING)
|
|
||||||
.requiredOption("summary", ["-s", "--summary"], clk.STRING, {
|
|
||||||
default: "Test Payment",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
const cmdArgs = args.testPayCmd;
|
|
||||||
console.log("creating order");
|
|
||||||
const merchantBackend = new MerchantBackendConnection(
|
|
||||||
args.testPayCmd.merchant,
|
|
||||||
args.testPayCmd.apikey,
|
|
||||||
);
|
|
||||||
const orderResp = await merchantBackend.createOrder(
|
|
||||||
cmdArgs.amount,
|
|
||||||
cmdArgs.summary,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
console.log("created new order with order ID", orderResp.orderId);
|
|
||||||
const checkPayResp = await merchantBackend.checkPayment(orderResp.orderId);
|
|
||||||
const talerPayUri = checkPayResp.taler_pay_uri;
|
|
||||||
if (!talerPayUri) {
|
|
||||||
console.error("fatal: no taler pay URI received from backend");
|
|
||||||
process.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("taler pay URI:", talerPayUri);
|
|
||||||
await withWallet(args, async (wallet) => {
|
|
||||||
await doPay(wallet, talerPayUri, { alwaysYes: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("integrationtestCmd", "integrationtest", {
|
|
||||||
help: "Run integration test with bank, exchange and merchant.",
|
|
||||||
})
|
|
||||||
.requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
|
|
||||||
default: "https://exchange.test.taler.net/",
|
|
||||||
})
|
|
||||||
.requiredOption("merchant", ["-m", "--merchant"], clk.STRING, {
|
|
||||||
default: "https://backend.test.taler.net/",
|
|
||||||
})
|
|
||||||
.requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
|
|
||||||
default: "sandbox",
|
|
||||||
})
|
|
||||||
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
|
|
||||||
default: "https://bank.test.taler.net/",
|
|
||||||
})
|
|
||||||
.requiredOption("withdrawAmount", ["-w", "--amount"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:10",
|
|
||||||
})
|
|
||||||
.requiredOption("spendAmount", ["-s", "--spend-amount"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:4",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
applyVerbose(args.wallet.verbose);
|
|
||||||
const cmdObj = args.integrationtestCmd;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await runIntegrationTest({
|
|
||||||
amountToSpend: cmdObj.spendAmount,
|
|
||||||
amountToWithdraw: cmdObj.withdrawAmount,
|
|
||||||
bankBaseUrl: cmdObj.bank,
|
|
||||||
exchangeBaseUrl: cmdObj.exchange,
|
|
||||||
merchantApiKey: cmdObj.merchantApiKey,
|
|
||||||
merchantBaseUrl: cmdObj.merchant,
|
|
||||||
}).catch((err) => {
|
|
||||||
console.error("Integration test failed with exception:");
|
|
||||||
console.error(err);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
process.exit(0);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("genTipUri", "gen-tip-uri", {
|
|
||||||
help: "Generate a taler://tip URI.",
|
|
||||||
})
|
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:10",
|
|
||||||
})
|
|
||||||
.maybeOption("merchant", ["-m", "--merchant"], clk.STRING, {
|
|
||||||
default: "https://backend.test.taler.net/",
|
|
||||||
})
|
|
||||||
.maybeOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
|
|
||||||
default: "sandbox",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
const merchantBackend = new MerchantBackendConnection(
|
|
||||||
args.genTipUri.merchant ?? "https://backend.test.taler.net/",
|
|
||||||
args.genTipUri.merchantApiKey ?? "sandbox",
|
|
||||||
);
|
|
||||||
const tipUri = await merchantBackend.authorizeTip(
|
|
||||||
args.genTipUri.amount,
|
|
||||||
"test",
|
|
||||||
);
|
|
||||||
console.log(tipUri);
|
|
||||||
});
|
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("genWithdrawUri", "gen-withdraw-uri", {
|
|
||||||
help: "Generate a taler://withdraw URI.",
|
|
||||||
})
|
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:20",
|
|
||||||
})
|
|
||||||
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
|
|
||||||
default: "https://bank.test.taler.net/",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
const b = new Bank(args.genWithdrawUri.bank);
|
|
||||||
const user = await b.registerRandomUser();
|
|
||||||
const url = await b.generateWithdrawUri(user, args.genWithdrawUri.amount);
|
|
||||||
console.log(url);
|
|
||||||
});
|
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("genRefundUri", "gen-refund-uri", {
|
|
||||||
help: "Generate a taler://refund URI.",
|
|
||||||
})
|
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:5",
|
|
||||||
})
|
|
||||||
.requiredOption("refundAmount", ["-r", "--refund"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:3",
|
|
||||||
})
|
|
||||||
.requiredOption("summary", ["-s", "--summary"], clk.STRING, {
|
|
||||||
default: "Test Payment (for refund)",
|
|
||||||
})
|
|
||||||
.maybeOption("merchant", ["-m", "--merchant"], clk.STRING, {
|
|
||||||
default: "https://backend.test.taler.net/",
|
|
||||||
})
|
|
||||||
.maybeOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
|
|
||||||
default: "sandbox",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
const cmdArgs = args.genRefundUri;
|
|
||||||
const merchantBackend = new MerchantBackendConnection(
|
|
||||||
cmdArgs.merchant ?? "https://backend.test.taler.net/",
|
|
||||||
cmdArgs.merchantApiKey ?? "sandbox",
|
|
||||||
);
|
|
||||||
const orderResp = await merchantBackend.createOrder(
|
|
||||||
cmdArgs.amount,
|
|
||||||
cmdArgs.summary,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
console.log("created new order with order ID", orderResp.orderId);
|
|
||||||
const checkPayResp = await merchantBackend.checkPayment(orderResp.orderId);
|
|
||||||
const talerPayUri = checkPayResp.taler_pay_uri;
|
|
||||||
if (!talerPayUri) {
|
|
||||||
console.error("fatal: no taler pay URI received from backend");
|
|
||||||
process.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await withWallet(args, async (wallet) => {
|
|
||||||
await doPay(wallet, talerPayUri, { alwaysYes: true });
|
|
||||||
});
|
|
||||||
const refundUri = await merchantBackend.refund(
|
|
||||||
orderResp.orderId,
|
|
||||||
"test refund",
|
|
||||||
cmdArgs.refundAmount,
|
|
||||||
);
|
|
||||||
console.log(refundUri);
|
|
||||||
});
|
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("genPayUri", "gen-pay-uri", {
|
|
||||||
help: "Generate a taler://pay URI.",
|
|
||||||
})
|
|
||||||
.flag("qrcode", ["--qr"], {
|
|
||||||
help: "Show a QR code with the taler://pay URI",
|
|
||||||
})
|
|
||||||
.flag("wait", ["--wait"], {
|
|
||||||
help: "Wait until payment has completed",
|
|
||||||
})
|
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:1",
|
|
||||||
})
|
|
||||||
.requiredOption("summary", ["-s", "--summary"], clk.STRING, {
|
|
||||||
default: "Test Payment",
|
|
||||||
})
|
|
||||||
.requiredOption("merchant", ["-m", "--merchant"], clk.STRING, {
|
|
||||||
default: "https://backend.test.taler.net/",
|
|
||||||
})
|
|
||||||
.requiredOption("merchantApiKey", ["-k", "--merchant-api-key"], clk.STRING, {
|
|
||||||
default: "sandbox",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
const cmdArgs = args.genPayUri;
|
|
||||||
console.log("creating order");
|
|
||||||
const merchantBackend = new MerchantBackendConnection(
|
|
||||||
cmdArgs.merchant,
|
|
||||||
cmdArgs.merchantApiKey,
|
|
||||||
);
|
|
||||||
const orderResp = await merchantBackend.createOrder(
|
|
||||||
cmdArgs.amount,
|
|
||||||
cmdArgs.summary,
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
console.log("created new order with order ID", orderResp.orderId);
|
|
||||||
const checkPayResp = await merchantBackend.checkPayment(orderResp.orderId);
|
|
||||||
const talerPayUri = checkPayResp.taler_pay_uri;
|
|
||||||
if (!talerPayUri) {
|
|
||||||
console.error("fatal: no taler pay URI received from backend");
|
|
||||||
process.exit(1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log("taler pay URI:", talerPayUri);
|
|
||||||
if (cmdArgs.qrcode) {
|
|
||||||
const qrcode = qrcodeGenerator(0, "M");
|
|
||||||
qrcode.addData(talerPayUri);
|
|
||||||
qrcode.make();
|
|
||||||
console.log(qrcode.createASCII());
|
|
||||||
}
|
|
||||||
if (cmdArgs.wait) {
|
|
||||||
console.log("waiting for payment ...");
|
|
||||||
while (1) {
|
|
||||||
await asyncSleep(500);
|
|
||||||
const checkPayResp2 = await merchantBackend.checkPayment(
|
|
||||||
orderResp.orderId,
|
|
||||||
);
|
|
||||||
if (checkPayResp2.order_status === "paid") {
|
|
||||||
console.log("payment successfully received!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
testCli
|
|
||||||
.subcommand("withdrawArgs", "withdraw", {
|
|
||||||
help: "Withdraw from a test bank (must support test registrations).",
|
|
||||||
})
|
|
||||||
.requiredOption("amount", ["-a", "--amount"], clk.STRING, {
|
|
||||||
default: "TESTKUDOS:10",
|
|
||||||
help: "Amount to withdraw.",
|
|
||||||
})
|
|
||||||
.requiredOption("exchange", ["-e", "--exchange"], clk.STRING, {
|
|
||||||
default: "https://exchange.test.taler.net/",
|
|
||||||
help: "Exchange base URL.",
|
|
||||||
})
|
|
||||||
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
|
|
||||||
default: "https://bank.test.taler.net/",
|
|
||||||
help: "Bank base URL",
|
|
||||||
})
|
|
||||||
.action(async (args) => {
|
|
||||||
await withWallet(args, async (wallet) => {
|
|
||||||
await wallet.updateExchangeFromUrl(args.withdrawArgs.exchange, true);
|
|
||||||
await withdrawTestBalance(
|
|
||||||
wallet,
|
|
||||||
args.withdrawArgs.amount,
|
|
||||||
args.withdrawArgs.bank,
|
|
||||||
args.withdrawArgs.exchange,
|
|
||||||
);
|
|
||||||
logger.info("Withdraw done");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
walletCli.run();
|
walletCli.run();
|
||||||
|
@ -19,5 +19,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export { Wallet } from "./wallet";
|
export { Wallet } from "./wallet";
|
||||||
export { runIntegrationTest } from "./headless/integrationtest";
|
|
||||||
export { installAndroidWalletListener } from "./android";
|
export { installAndroidWalletListener } from "./android";
|
||||||
|
@ -300,6 +300,7 @@ export async function readSuccessResponseJsonOrThrow<T>(
|
|||||||
throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
|
throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function readSuccessResponseTextOrErrorCode<T>(
|
export async function readSuccessResponseTextOrErrorCode<T>(
|
||||||
httpResponse: HttpResponse,
|
httpResponse: HttpResponse,
|
||||||
): Promise<ResponseOrError<string>> {
|
): Promise<ResponseOrError<string>> {
|
||||||
@ -329,6 +330,27 @@ export async function readSuccessResponseTextOrErrorCode<T>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checkSuccessResponseOrThrow(
|
||||||
|
httpResponse: HttpResponse,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!(httpResponse.status >= 200 && httpResponse.status < 300)) {
|
||||||
|
const errJson = await httpResponse.json();
|
||||||
|
const talerErrorCode = errJson.code;
|
||||||
|
if (typeof talerErrorCode !== "number") {
|
||||||
|
throw new OperationFailedError(
|
||||||
|
makeErrorDetails(
|
||||||
|
TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
|
||||||
|
"Error response did not contain error code",
|
||||||
|
{
|
||||||
|
requestUrl: httpResponse.requestUrl,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
throwUnexpectedRequestError(httpResponse, errJson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function readSuccessResponseTextOrThrow<T>(
|
export async function readSuccessResponseTextOrThrow<T>(
|
||||||
httpResponse: HttpResponse,
|
httpResponse: HttpResponse,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
@ -113,6 +113,7 @@ import {
|
|||||||
TransactionsResponse,
|
TransactionsResponse,
|
||||||
} from "./types/transactions";
|
} from "./types/transactions";
|
||||||
import { getTransactions } from "./operations/transactions";
|
import { getTransactions } from "./operations/transactions";
|
||||||
|
import { withdrawTestBalance } from "./operations/testing";
|
||||||
|
|
||||||
const builtinCurrencies: CurrencyRecord[] = [
|
const builtinCurrencies: CurrencyRecord[] = [
|
||||||
{
|
{
|
||||||
@ -868,4 +869,13 @@ export class Wallet {
|
|||||||
): Promise<TransactionsResponse> {
|
): Promise<TransactionsResponse> {
|
||||||
return getTransactions(this.ws, request);
|
return getTransactions(this.ws, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async withdrawTestBalance(
|
||||||
|
amount = "TESTKUDOS:10",
|
||||||
|
bankBaseUrl = "https://bank.test.taler.net/",
|
||||||
|
exchangeBaseUrl = "https://exchange.test.taler.net/",
|
||||||
|
): Promise<void> {
|
||||||
|
await withdrawTestBalance(this.ws, amount, bankBaseUrl, exchangeBaseUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
makeErrorDetails,
|
makeErrorDetails,
|
||||||
} from "./operations/errors";
|
} from "./operations/errors";
|
||||||
import { TalerErrorCode } from "./TalerErrorCode";
|
import { TalerErrorCode } from "./TalerErrorCode";
|
||||||
import { withdrawTestBalance } from "./headless/helpers";
|
|
||||||
import { codecForTransactionsRequest } from "./types/transactions";
|
import { codecForTransactionsRequest } from "./types/transactions";
|
||||||
import {
|
import {
|
||||||
makeCodecForObject,
|
makeCodecForObject,
|
||||||
@ -160,7 +159,7 @@ async function dispatchRequestInternal(
|
|||||||
): Promise<Record<string, any>> {
|
): Promise<Record<string, any>> {
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case "withdrawTestkudos":
|
case "withdrawTestkudos":
|
||||||
await withdrawTestBalance(wallet);
|
await wallet.withdrawTestBalance();
|
||||||
return {};
|
return {};
|
||||||
case "getTransactions": {
|
case "getTransactions": {
|
||||||
const req = codecForTransactionsRequest().decode(payload);
|
const req = codecForTransactionsRequest().decode(payload);
|
||||||
|
Loading…
Reference in New Issue
Block a user