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 { openTalerDatabase } from "../db";
|
||||
import { HttpRequestLibrary } from "../util/http";
|
||||
import { Bank } from "./bank";
|
||||
import fs from "fs";
|
||||
import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorker";
|
||||
import { WalletNotification, NotificationType } from "../types/notifications";
|
||||
import { WalletNotification } from "../types/notifications";
|
||||
import { Database } from "../util/query";
|
||||
import { NodeHttpLib } from "./NodeHttpLib";
|
||||
import { Logger } from "../util/logging";
|
||||
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 {
|
||||
/**
|
||||
@ -135,46 +132,3 @@ export async function getDefaultNodeWallet(
|
||||
}
|
||||
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 fs from "fs";
|
||||
import { getDefaultNodeWallet, withdrawTestBalance } from "./helpers";
|
||||
import { MerchantBackendConnection } from "./merchant";
|
||||
import { runIntegrationTest, runIntegrationTestBasic } from "./integrationtest";
|
||||
import { getDefaultNodeWallet } from "./helpers";
|
||||
import { Wallet } from "../wallet";
|
||||
import qrcodeGenerator from "qrcode-generator";
|
||||
import * as clk from "./clk";
|
||||
@ -34,7 +32,6 @@ import {
|
||||
OperationFailedAndReportedError,
|
||||
OperationFailedError,
|
||||
} from "../operations/errors";
|
||||
import { Bank } from "./bank";
|
||||
import { classifyTalerUri, TalerUriType } from "../util/taleruri";
|
||||
import { Configuration } from "../util/talerconfig";
|
||||
import { setDangerousTimetravel } from "../util/time";
|
||||
@ -658,285 +655,4 @@ testCli.subcommand("vectors", "vectors").action(async (args) => {
|
||||
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();
|
||||
|
@ -19,5 +19,4 @@
|
||||
*/
|
||||
|
||||
export { Wallet } from "./wallet";
|
||||
export { runIntegrationTest } from "./headless/integrationtest";
|
||||
export { installAndroidWalletListener } from "./android";
|
||||
|
@ -300,6 +300,7 @@ export async function readSuccessResponseJsonOrThrow<T>(
|
||||
throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
|
||||
}
|
||||
|
||||
|
||||
export async function readSuccessResponseTextOrErrorCode<T>(
|
||||
httpResponse: HttpResponse,
|
||||
): 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>(
|
||||
httpResponse: HttpResponse,
|
||||
): Promise<string> {
|
||||
|
@ -113,6 +113,7 @@ import {
|
||||
TransactionsResponse,
|
||||
} from "./types/transactions";
|
||||
import { getTransactions } from "./operations/transactions";
|
||||
import { withdrawTestBalance } from "./operations/testing";
|
||||
|
||||
const builtinCurrencies: CurrencyRecord[] = [
|
||||
{
|
||||
@ -868,4 +869,13 @@ export class Wallet {
|
||||
): Promise<TransactionsResponse> {
|
||||
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,
|
||||
} from "./operations/errors";
|
||||
import { TalerErrorCode } from "./TalerErrorCode";
|
||||
import { withdrawTestBalance } from "./headless/helpers";
|
||||
import { codecForTransactionsRequest } from "./types/transactions";
|
||||
import {
|
||||
makeCodecForObject,
|
||||
@ -160,7 +159,7 @@ async function dispatchRequestInternal(
|
||||
): Promise<Record<string, any>> {
|
||||
switch (operation) {
|
||||
case "withdrawTestkudos":
|
||||
await withdrawTestBalance(wallet);
|
||||
await wallet.withdrawTestBalance();
|
||||
return {};
|
||||
case "getTransactions": {
|
||||
const req = codecForTransactionsRequest().decode(payload);
|
||||
|
Loading…
Reference in New Issue
Block a user