Merge branch 'master' into age-withdraw
This commit is contained in:
commit
d42a06607b
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc --build && ./build-node.mjs",
|
"compile": "tsc && ./build-node.mjs",
|
||||||
"test": "tsc",
|
"test": "tsc",
|
||||||
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
||||||
"pretty": "prettier --write src"
|
"pretty": "prettier --write src"
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "tsc && ava",
|
"test": "tsc && ava",
|
||||||
"compile": "tsc --build",
|
"compile": "tsc",
|
||||||
"clean": "rimraf dist lib tsconfig.tsbuildinfo",
|
"clean": "rimraf dist lib tsconfig.tsbuildinfo",
|
||||||
"pretty": "prettier --write src"
|
"pretty": "prettier --write src"
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"author": "Florian Dold",
|
"author": "Florian Dold",
|
||||||
"license": "GPL-2.0+",
|
"license": "GPL-2.0+",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc --build"
|
"compile": "tsc"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"po2json": "^0.4.5",
|
"po2json": "^0.4.5",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc --build && ./build.mjs",
|
"compile": "tsc && ./build.mjs",
|
||||||
"check": "tsc",
|
"check": "tsc",
|
||||||
"test": "tsc",
|
"test": "tsc",
|
||||||
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
||||||
@ -40,7 +40,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gnu-taler/taler-util": "workspace:*",
|
"@gnu-taler/taler-util": "workspace:*",
|
||||||
"@gnu-taler/taler-wallet-core": "workspace:*",
|
"@gnu-taler/taler-wallet-core": "workspace:*",
|
||||||
"axios": "^0.27.2",
|
|
||||||
"tslib": "^2.5.3"
|
"tslib": "^2.5.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,13 +26,13 @@ import {
|
|||||||
j2s,
|
j2s,
|
||||||
Logger,
|
Logger,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
|
||||||
import {
|
import {
|
||||||
AccessStats,
|
AccessStats,
|
||||||
createNativeWalletHost2,
|
createNativeWalletHost2,
|
||||||
Wallet,
|
Wallet,
|
||||||
WalletApiOperation,
|
WalletApiOperation,
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
import { harnessHttpLib } from "./harness/harness.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point for the benchmark.
|
* Entry point for the benchmark.
|
||||||
@ -46,11 +46,6 @@ export async function runBench1(configJson: any): Promise<void> {
|
|||||||
// Validate the configuration file for this benchmark.
|
// Validate the configuration file for this benchmark.
|
||||||
const b1conf = codecForBench1Config().decode(configJson);
|
const b1conf = codecForBench1Config().decode(configJson);
|
||||||
|
|
||||||
const myHttpLib = createPlatformHttpLib({
|
|
||||||
enableThrottling: false,
|
|
||||||
allowHttp: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const numIter = b1conf.iterations ?? 1;
|
const numIter = b1conf.iterations ?? 1;
|
||||||
const numDeposits = b1conf.deposits ?? 5;
|
const numDeposits = b1conf.deposits ?? 5;
|
||||||
const restartWallet = b1conf.restartAfter ?? 20;
|
const restartWallet = b1conf.restartAfter ?? 20;
|
||||||
@ -85,7 +80,7 @@ export async function runBench1(configJson: any): Promise<void> {
|
|||||||
const res = await createNativeWalletHost2({
|
const res = await createNativeWalletHost2({
|
||||||
// No persistent DB storage.
|
// No persistent DB storage.
|
||||||
persistentStoragePath: undefined,
|
persistentStoragePath: undefined,
|
||||||
httpLib: myHttpLib,
|
httpLib: harnessHttpLib,
|
||||||
config: {
|
config: {
|
||||||
testing: {
|
testing: {
|
||||||
insecureTrustExchange: trustExchange,
|
insecureTrustExchange: trustExchange,
|
||||||
|
@ -55,9 +55,11 @@ import {
|
|||||||
RewardCreateRequest,
|
RewardCreateRequest,
|
||||||
TippingReserveStatus,
|
TippingReserveStatus,
|
||||||
WalletNotification,
|
WalletNotification,
|
||||||
|
codecForAny,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
createPlatformHttpLib,
|
createPlatformHttpLib,
|
||||||
|
expectSuccessResponseOrThrow,
|
||||||
readSuccessResponseJsonOrThrow,
|
readSuccessResponseJsonOrThrow,
|
||||||
} from "@gnu-taler/taler-util/http";
|
} from "@gnu-taler/taler-util/http";
|
||||||
import {
|
import {
|
||||||
@ -78,7 +80,6 @@ import {
|
|||||||
WalletNotificationWaiter,
|
WalletNotificationWaiter,
|
||||||
} from "@gnu-taler/taler-wallet-core/remote";
|
} from "@gnu-taler/taler-wallet-core/remote";
|
||||||
import { deepStrictEqual } from "assert";
|
import { deepStrictEqual } from "assert";
|
||||||
import axiosImp, { AxiosError } from "axios";
|
|
||||||
import { ChildProcess, spawn } from "child_process";
|
import { ChildProcess, spawn } from "child_process";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
@ -87,12 +88,9 @@ import * as path from "path";
|
|||||||
import * as readline from "readline";
|
import * as readline from "readline";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import { CoinConfig } from "./denomStructures.js";
|
import { CoinConfig } from "./denomStructures.js";
|
||||||
import { LibeufinNexusApi, LibeufinSandboxApi } from "./libeufin-apis.js";
|
|
||||||
|
|
||||||
const logger = new Logger("harness.ts");
|
const logger = new Logger("harness.ts");
|
||||||
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
|
|
||||||
export async function delayMs(ms: number): Promise<void> {
|
export async function delayMs(ms: number): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
setTimeout(() => resolve(), ms);
|
setTimeout(() => resolve(), ms);
|
||||||
@ -322,12 +320,6 @@ export class GlobalTestState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
assertAxiosError(e: any): asserts e is AxiosError {
|
|
||||||
if (!e.isAxiosError) {
|
|
||||||
throw Error("expected axios error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(b: boolean): asserts b {
|
assertTrue(b: boolean): asserts b {
|
||||||
if (!b) {
|
if (!b) {
|
||||||
throw Error("test assertion failed");
|
throw Error("test assertion failed");
|
||||||
@ -558,7 +550,10 @@ export async function pingProc(
|
|||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
logger.trace(`pinging ${serviceName} at ${url}`);
|
logger.trace(`pinging ${serviceName} at ${url}`);
|
||||||
const resp = await axios.get(url);
|
const resp = await harnessHttpLib.fetch(url);
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
throw Error("non-200 status code");
|
||||||
|
}
|
||||||
logger.trace(`service ${serviceName} available`);
|
logger.trace(`service ${serviceName} available`);
|
||||||
return;
|
return;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -583,289 +578,6 @@ class BankServiceBase {
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Work in progress. The key point is that both Sandbox and Nexus
|
|
||||||
* will be configured and started by this class.
|
|
||||||
*/
|
|
||||||
class LibEuFinBankService extends BankServiceBase implements BankServiceHandle {
|
|
||||||
sandboxProc: ProcessWrapper | undefined;
|
|
||||||
nexusProc: ProcessWrapper | undefined;
|
|
||||||
|
|
||||||
http = createPlatformHttpLib({
|
|
||||||
allowHttp: true,
|
|
||||||
enableThrottling: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
static async create(
|
|
||||||
gc: GlobalTestState,
|
|
||||||
bc: BankConfig,
|
|
||||||
): Promise<LibEuFinBankService> {
|
|
||||||
return new LibEuFinBankService(gc, bc, "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
get port() {
|
|
||||||
return this.bankConfig.httpPort;
|
|
||||||
}
|
|
||||||
get nexusPort() {
|
|
||||||
return this.bankConfig.httpPort + 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nexusDbConn(): string {
|
|
||||||
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-nexus.sqlite3`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get sandboxDbConn(): string {
|
|
||||||
return `jdbc:sqlite:${this.globalTestState.testDir}/libeufin-sandbox.sqlite3`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get nexusBaseUrl(): string {
|
|
||||||
return `http://localhost:${this.nexusPort}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseUrlDemobank(): string {
|
|
||||||
let url = new URL("demobanks/default/", this.baseUrlNetloc);
|
|
||||||
return url.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
get bankAccessApiBaseUrl(): string {
|
|
||||||
let url = new URL("access-api/", this.baseUrlDemobank);
|
|
||||||
return url.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseUrlNetloc(): string {
|
|
||||||
return `http://localhost:${this.bankConfig.httpPort}/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get baseUrl(): string {
|
|
||||||
return this.bankAccessApiBaseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setSuggestedExchange(
|
|
||||||
e: ExchangeServiceInterface,
|
|
||||||
exchangePayto: string,
|
|
||||||
) {
|
|
||||||
await sh(
|
|
||||||
this.globalTestState,
|
|
||||||
"libeufin-sandbox-set-default-exchange",
|
|
||||||
`libeufin-sandbox default-exchange ${e.baseUrl} ${exchangePayto}`,
|
|
||||||
{
|
|
||||||
...process.env,
|
|
||||||
LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create one at both sides: Sandbox and Nexus.
|
|
||||||
async createExchangeAccount(
|
|
||||||
accountName: string,
|
|
||||||
password: string,
|
|
||||||
): Promise<HarnessExchangeBankAccount> {
|
|
||||||
logger.info("Create Exchange account(s)!");
|
|
||||||
/**
|
|
||||||
* Many test cases try to create a Exchange account before
|
|
||||||
* starting the bank; that's because the Pybank did it entirely
|
|
||||||
* via the configuration file.
|
|
||||||
*/
|
|
||||||
await this.start();
|
|
||||||
await this.pingUntilAvailable();
|
|
||||||
await LibeufinSandboxApi.createDemobankAccount(accountName, password, {
|
|
||||||
baseUrl: this.bankAccessApiBaseUrl,
|
|
||||||
});
|
|
||||||
let bankAccountLabel = accountName;
|
|
||||||
await LibeufinSandboxApi.createDemobankEbicsSubscriber(
|
|
||||||
{
|
|
||||||
hostID: "talertestEbicsHost",
|
|
||||||
userID: "exchangeEbicsUser",
|
|
||||||
partnerID: "exchangeEbicsPartner",
|
|
||||||
},
|
|
||||||
bankAccountLabel,
|
|
||||||
{ baseUrl: this.baseUrlDemobank },
|
|
||||||
);
|
|
||||||
|
|
||||||
await LibeufinNexusApi.createUser(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
{
|
|
||||||
username: accountName,
|
|
||||||
password: password,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.createEbicsBankConnection(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
{
|
|
||||||
name: "ebics-connection", // connection name.
|
|
||||||
ebicsURL: new URL("ebicsweb", this.baseUrlNetloc).href,
|
|
||||||
hostID: "talertestEbicsHost",
|
|
||||||
userID: "exchangeEbicsUser",
|
|
||||||
partnerID: "exchangeEbicsPartner",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.connectBankConnection(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
"ebics-connection",
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.fetchAccounts(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
"ebics-connection",
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.importConnectionAccount(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
"ebics-connection", // connection name
|
|
||||||
accountName, // offered account label
|
|
||||||
`${accountName}-nexus-label`, // bank account label at Nexus
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.createTwgFacade(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
{
|
|
||||||
name: "exchange-facade",
|
|
||||||
connectionName: "ebics-connection",
|
|
||||||
accountName: `${accountName}-nexus-label`,
|
|
||||||
currency: "EUR",
|
|
||||||
reserveTransferLevel: "report",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.postPermission(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
{
|
|
||||||
action: "grant",
|
|
||||||
permission: {
|
|
||||||
subjectId: accountName,
|
|
||||||
subjectType: "user",
|
|
||||||
resourceType: "facade",
|
|
||||||
resourceId: "exchange-facade", // facade name
|
|
||||||
permissionName: "facade.talerWireGateway.transfer",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.postPermission(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
{
|
|
||||||
action: "grant",
|
|
||||||
permission: {
|
|
||||||
subjectId: accountName,
|
|
||||||
subjectType: "user",
|
|
||||||
resourceType: "facade",
|
|
||||||
resourceId: "exchange-facade", // facade name
|
|
||||||
permissionName: "facade.talerWireGateway.history",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// Set fetch task.
|
|
||||||
await LibeufinNexusApi.postTask(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
`${accountName}-nexus-label`,
|
|
||||||
{
|
|
||||||
name: "wirewatch-task",
|
|
||||||
cronspec: "* * *",
|
|
||||||
type: "fetch",
|
|
||||||
params: {
|
|
||||||
level: "all",
|
|
||||||
rangeType: "all",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await LibeufinNexusApi.postTask(
|
|
||||||
{ baseUrl: this.nexusBaseUrl },
|
|
||||||
`${accountName}-nexus-label`,
|
|
||||||
{
|
|
||||||
name: "aggregator-task",
|
|
||||||
cronspec: "* * *",
|
|
||||||
type: "submit",
|
|
||||||
params: {},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
let facadesResp = await LibeufinNexusApi.getAllFacades({
|
|
||||||
baseUrl: this.nexusBaseUrl,
|
|
||||||
});
|
|
||||||
let accountInfoResp = await LibeufinSandboxApi.demobankAccountInfo(
|
|
||||||
"admin",
|
|
||||||
"secret",
|
|
||||||
{ baseUrl: this.bankAccessApiBaseUrl },
|
|
||||||
accountName, // bank account label.
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
accountName: accountName,
|
|
||||||
accountPassword: password,
|
|
||||||
accountPaytoUri: accountInfoResp.data.paytoUri,
|
|
||||||
wireGatewayApiBaseUrl: facadesResp.data.facades[0].baseUrl,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async start(): Promise<void> {
|
|
||||||
/**
|
|
||||||
* Because many test cases try to create a Exchange bank
|
|
||||||
* account _before_ starting the bank (Pybank did it only via
|
|
||||||
* the config), it is possible that at this point Sandbox and
|
|
||||||
* Nexus are already running. Hence, this method only launches
|
|
||||||
* them if they weren't launched earlier.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Only go ahead if BOTH aren't running.
|
|
||||||
if (this.sandboxProc || this.nexusProc) {
|
|
||||||
logger.info("Nexus or Sandbox already running, not taking any action.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await sh(
|
|
||||||
this.globalTestState,
|
|
||||||
"libeufin-sandbox-config-demobank",
|
|
||||||
`libeufin-sandbox config --currency=${this.bankConfig.currency} default`,
|
|
||||||
{
|
|
||||||
...process.env,
|
|
||||||
LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
|
|
||||||
LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
this.sandboxProc = this.globalTestState.spawnService(
|
|
||||||
"libeufin-sandbox",
|
|
||||||
["serve", "--port", `${this.port}`],
|
|
||||||
"libeufin-sandbox",
|
|
||||||
{
|
|
||||||
...process.env,
|
|
||||||
LIBEUFIN_SANDBOX_DB_CONNECTION: this.sandboxDbConn,
|
|
||||||
LIBEUFIN_SANDBOX_ADMIN_PASSWORD: "secret",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await runCommand(
|
|
||||||
this.globalTestState,
|
|
||||||
"libeufin-nexus-superuser",
|
|
||||||
"libeufin-nexus",
|
|
||||||
["superuser", "admin", "--password", "test"],
|
|
||||||
{
|
|
||||||
...process.env,
|
|
||||||
LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusDbConn,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
this.nexusProc = this.globalTestState.spawnService(
|
|
||||||
"libeufin-nexus",
|
|
||||||
["serve", "--port", `${this.nexusPort}`],
|
|
||||||
"libeufin-nexus",
|
|
||||||
{
|
|
||||||
...process.env,
|
|
||||||
LIBEUFIN_NEXUS_DB_CONNECTION: this.nexusDbConn,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
// need to wait here, because at this point
|
|
||||||
// a Ebics host needs to be created (RESTfully)
|
|
||||||
await this.pingUntilAvailable();
|
|
||||||
LibeufinSandboxApi.createEbicsHost(
|
|
||||||
{ baseUrl: this.baseUrlNetloc },
|
|
||||||
"talertestEbicsHost",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async pingUntilAvailable(): Promise<void> {
|
|
||||||
await pingProc(
|
|
||||||
this.sandboxProc,
|
|
||||||
`http://localhost:${this.bankConfig.httpPort}`,
|
|
||||||
"libeufin-sandbox",
|
|
||||||
);
|
|
||||||
await pingProc(
|
|
||||||
this.nexusProc,
|
|
||||||
`${this.nexusBaseUrl}/config`,
|
|
||||||
"libeufin-nexus",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the bank service using the "taler-fakebank-run" tool.
|
* Implementation of the bank service using the "taler-fakebank-run" tool.
|
||||||
*/
|
*/
|
||||||
@ -1152,6 +864,9 @@ export class ExchangeService implements ExchangeServiceInterface {
|
|||||||
"currency_round_unit",
|
"currency_round_unit",
|
||||||
e.roundUnit ?? `${e.currency}:0.01`,
|
e.roundUnit ?? `${e.currency}:0.01`,
|
||||||
);
|
);
|
||||||
|
// Set to a high value to not break existing test cases where the merchant
|
||||||
|
// would cover all fees.
|
||||||
|
config.setString("exchange", "STEFAN_ABS", `${e.currency}:1`);
|
||||||
config.setString(
|
config.setString(
|
||||||
"exchange",
|
"exchange",
|
||||||
"revocation_dir",
|
"revocation_dir",
|
||||||
@ -1636,20 +1351,30 @@ export interface DeleteTippingReserveArgs {
|
|||||||
purge?: boolean;
|
purge?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default HTTP client handle for the integration test harness.
|
||||||
|
*/
|
||||||
|
export const harnessHttpLib = createPlatformHttpLib({
|
||||||
|
allowHttp: true,
|
||||||
|
enableThrottling: false,
|
||||||
|
});
|
||||||
|
|
||||||
export class MerchantApiClient {
|
export class MerchantApiClient {
|
||||||
constructor(
|
constructor(
|
||||||
private baseUrl: string,
|
private baseUrl: string,
|
||||||
public readonly auth: MerchantAuthConfiguration,
|
public readonly auth: MerchantAuthConfiguration,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// FIXME: Migrate everything to this in favor of axios
|
httpClient = createPlatformHttpLib({ allowHttp: true, enableThrottling: false });
|
||||||
http = createPlatformHttpLib({ allowHttp: true, enableThrottling: false });
|
|
||||||
|
|
||||||
async changeAuth(auth: MerchantAuthConfiguration): Promise<void> {
|
async changeAuth(auth: MerchantAuthConfiguration): Promise<void> {
|
||||||
const url = new URL("private/auth", this.baseUrl);
|
const url = new URL("private/auth", this.baseUrl);
|
||||||
await axios.post(url.href, auth, {
|
const res = await this.httpClient.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: auth,
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
|
await expectSuccessResponseOrThrow(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> {
|
async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> {
|
||||||
@ -1657,7 +1382,8 @@ export class MerchantApiClient {
|
|||||||
if (req.purge) {
|
if (req.purge) {
|
||||||
url.searchParams.set("purge", "YES");
|
url.searchParams.set("purge", "YES");
|
||||||
}
|
}
|
||||||
const resp = await axios.delete(url.href, {
|
const resp = await this.httpClient.fetch(url.href, {
|
||||||
|
method: "DELETE",
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
logger.info(`delete status: ${resp.status}`);
|
logger.info(`delete status: ${resp.status}`);
|
||||||
@ -1668,7 +1394,7 @@ export class MerchantApiClient {
|
|||||||
req: CreateMerchantTippingReserveRequest,
|
req: CreateMerchantTippingReserveRequest,
|
||||||
): Promise<MerchantReserveCreateConfirmation> {
|
): Promise<MerchantReserveCreateConfirmation> {
|
||||||
const url = new URL("private/reserves", this.baseUrl);
|
const url = new URL("private/reserves", this.baseUrl);
|
||||||
const resp = await this.http.fetch(url.href, {
|
const resp = await this.httpClient.fetch(url.href, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: req,
|
body: req,
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
@ -1684,7 +1410,7 @@ export class MerchantApiClient {
|
|||||||
console.log(this.makeAuthHeader());
|
console.log(this.makeAuthHeader());
|
||||||
const url = new URL("private", this.baseUrl);
|
const url = new URL("private", this.baseUrl);
|
||||||
logger.info(`request url ${url.href}`);
|
logger.info(`request url ${url.href}`);
|
||||||
const resp = await this.http.fetch(url.href, {
|
const resp = await this.httpClient.fetch(url.href, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
@ -1694,7 +1420,7 @@ export class MerchantApiClient {
|
|||||||
async getPrivateTipReserves(): Promise<TippingReserveStatus> {
|
async getPrivateTipReserves(): Promise<TippingReserveStatus> {
|
||||||
console.log(this.makeAuthHeader());
|
console.log(this.makeAuthHeader());
|
||||||
const url = new URL("private/reserves", this.baseUrl);
|
const url = new URL("private/reserves", this.baseUrl);
|
||||||
const resp = await this.http.fetch(url.href, {
|
const resp = await this.httpClient.fetch(url.href, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
@ -1704,33 +1430,37 @@ export class MerchantApiClient {
|
|||||||
|
|
||||||
async deleteInstance(instanceId: string) {
|
async deleteInstance(instanceId: string) {
|
||||||
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
|
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
|
||||||
await axios.delete(url.href, {
|
const resp = await this.httpClient.fetch(url.href, {
|
||||||
|
method: "DELETE",
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
|
await expectSuccessResponseOrThrow(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createInstance(req: MerchantInstanceConfig): Promise<void> {
|
async createInstance(req: MerchantInstanceConfig): Promise<void> {
|
||||||
const url = new URL("management/instances", this.baseUrl);
|
const url = new URL("management/instances", this.baseUrl);
|
||||||
await axios.post(url.href, req, {
|
await this.httpClient.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: req,
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getInstances(): Promise<MerchantInstancesResponse> {
|
async getInstances(): Promise<MerchantInstancesResponse> {
|
||||||
const url = new URL("management/instances", this.baseUrl);
|
const url = new URL("management/instances", this.baseUrl);
|
||||||
const resp = await axios.get(url.href, {
|
const resp = await this.httpClient.fetch(url.href, {
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
return resp.data;
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getInstanceFullDetails(instanceId: string): Promise<any> {
|
async getInstanceFullDetails(instanceId: string): Promise<any> {
|
||||||
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
|
const url = new URL(`management/instances/${instanceId}`, this.baseUrl);
|
||||||
try {
|
try {
|
||||||
const resp = await axios.get(url.href, {
|
const resp = await this.httpClient.fetch(url.href, {
|
||||||
headers: this.makeAuthHeader(),
|
headers: this.makeAuthHeader(),
|
||||||
});
|
});
|
||||||
return resp.data;
|
return resp.json();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -1750,6 +1480,8 @@ export class MerchantApiClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* FIXME: This should be deprecated in favor of MerchantApiClient
|
* FIXME: This should be deprecated in favor of MerchantApiClient
|
||||||
|
*
|
||||||
|
* @deprecated use MerchantApiClient instead
|
||||||
*/
|
*/
|
||||||
export namespace MerchantPrivateApi {
|
export namespace MerchantPrivateApi {
|
||||||
export async function createOrder(
|
export async function createOrder(
|
||||||
@ -1760,10 +1492,15 @@ export namespace MerchantPrivateApi {
|
|||||||
): Promise<MerchantPostOrderResponse> {
|
): Promise<MerchantPostOrderResponse> {
|
||||||
const baseUrl = merchantService.makeInstanceBaseUrl(instanceName);
|
const baseUrl = merchantService.makeInstanceBaseUrl(instanceName);
|
||||||
let url = new URL("private/orders", baseUrl);
|
let url = new URL("private/orders", baseUrl);
|
||||||
const resp = await axios.post(url.href, req, {
|
const resp = await harnessHttpLib.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: req,
|
||||||
headers: withAuthorization as Record<string, string>,
|
headers: withAuthorization as Record<string, string>,
|
||||||
});
|
});
|
||||||
return codecForMerchantPostOrderResponse().decode(resp.data);
|
return readSuccessResponseJsonOrThrow(
|
||||||
|
resp,
|
||||||
|
codecForMerchantPostOrderResponse(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTemplate(
|
export async function createTemplate(
|
||||||
@ -1774,7 +1511,9 @@ export namespace MerchantPrivateApi {
|
|||||||
) {
|
) {
|
||||||
const baseUrl = merchantService.makeInstanceBaseUrl(instanceName);
|
const baseUrl = merchantService.makeInstanceBaseUrl(instanceName);
|
||||||
let url = new URL("private/templates", baseUrl);
|
let url = new URL("private/templates", baseUrl);
|
||||||
const resp = await axios.post(url.href, req, {
|
const resp = await harnessHttpLib.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: req,
|
||||||
headers: withAuthorization as Record<string, string>,
|
headers: withAuthorization as Record<string, string>,
|
||||||
});
|
});
|
||||||
if (resp.status !== 204) {
|
if (resp.status !== 204) {
|
||||||
@ -1794,10 +1533,13 @@ export namespace MerchantPrivateApi {
|
|||||||
if (query.sessionId) {
|
if (query.sessionId) {
|
||||||
reqUrl.searchParams.set("session_id", query.sessionId);
|
reqUrl.searchParams.set("session_id", query.sessionId);
|
||||||
}
|
}
|
||||||
const resp = await axios.get(reqUrl.href, {
|
const resp = await harnessHttpLib.fetch(reqUrl.href, {
|
||||||
headers: withAuthorization as Record<string, string>,
|
headers: withAuthorization as Record<string, string>,
|
||||||
});
|
});
|
||||||
return codecForMerchantOrderPrivateStatusResponse().decode(resp.data);
|
return readSuccessResponseJsonOrThrow(
|
||||||
|
resp,
|
||||||
|
codecForMerchantOrderPrivateStatusResponse(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function giveRefund(
|
export async function giveRefund(
|
||||||
@ -1813,12 +1555,16 @@ export namespace MerchantPrivateApi {
|
|||||||
`private/orders/${r.orderId}/refund`,
|
`private/orders/${r.orderId}/refund`,
|
||||||
merchantService.makeInstanceBaseUrl(r.instance),
|
merchantService.makeInstanceBaseUrl(r.instance),
|
||||||
);
|
);
|
||||||
const resp = await axios.post(reqUrl.href, {
|
const resp = await harnessHttpLib.fetch(reqUrl.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
refund: r.amount,
|
refund: r.amount,
|
||||||
reason: r.justification,
|
reason: r.justification,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
const respBody = await resp.json();
|
||||||
return {
|
return {
|
||||||
talerRefundUri: resp.data.taler_refund_uri,
|
talerRefundUri: respBody.taler_refund_uri,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1830,9 +1576,9 @@ export namespace MerchantPrivateApi {
|
|||||||
`private/reserves`,
|
`private/reserves`,
|
||||||
merchantService.makeInstanceBaseUrl(instance),
|
merchantService.makeInstanceBaseUrl(instance),
|
||||||
);
|
);
|
||||||
const resp = await axios.get(reqUrl.href);
|
const resp = await harnessHttpLib.fetch(reqUrl.href);
|
||||||
// FIXME: validate
|
// FIXME: validate
|
||||||
return resp.data;
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function giveTip(
|
export async function giveTip(
|
||||||
@ -1844,9 +1590,12 @@ export namespace MerchantPrivateApi {
|
|||||||
`private/tips`,
|
`private/tips`,
|
||||||
merchantService.makeInstanceBaseUrl(instance),
|
merchantService.makeInstanceBaseUrl(instance),
|
||||||
);
|
);
|
||||||
const resp = await axios.post(reqUrl.href, req);
|
const resp = await harnessHttpLib.fetch(reqUrl.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: req,
|
||||||
|
});
|
||||||
// FIXME: validate
|
// FIXME: validate
|
||||||
return resp.data;
|
return resp.json();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2052,7 +1801,12 @@ export class MerchantService implements MerchantServiceInterface {
|
|||||||
instanceConfig.defaultPayDelay ??
|
instanceConfig.defaultPayDelay ??
|
||||||
Duration.toTalerProtocolDuration(Duration.getForever()),
|
Duration.toTalerProtocolDuration(Duration.getForever()),
|
||||||
};
|
};
|
||||||
await axios.post(url, body);
|
const httpLib = createPlatformHttpLib({
|
||||||
|
allowHttp: true,
|
||||||
|
enableThrottling: false,
|
||||||
|
});
|
||||||
|
const resp = await httpLib.fetch(url, { method: "POST", body });
|
||||||
|
await expectSuccessResponseOrThrow(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeInstanceBaseUrl(instanceName?: string): string {
|
makeInstanceBaseUrl(instanceName?: string): string {
|
||||||
|
@ -215,10 +215,14 @@ export async function createSimpleTestkudosEnvironment(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSharedTestDir(): string {
|
||||||
|
return `/tmp/taler-harness@${process.env.USER}`;
|
||||||
|
}
|
||||||
|
|
||||||
export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
||||||
const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS"));
|
const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS"));
|
||||||
|
|
||||||
const sharedDir = `/tmp/taler-harness@${process.env.USER}`;
|
const sharedDir = getSharedTestDir();
|
||||||
|
|
||||||
fs.mkdirSync(sharedDir, { recursive: true });
|
fs.mkdirSync(sharedDir, { recursive: true });
|
||||||
|
|
||||||
@ -230,10 +234,9 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
|||||||
|
|
||||||
logger.info(`previous setup done: ${prevSetupDone}`);
|
logger.info(`previous setup done: ${prevSetupDone}`);
|
||||||
|
|
||||||
|
|
||||||
// Wallet has longer startup-time and no dependencies,
|
// Wallet has longer startup-time and no dependencies,
|
||||||
// so we start it rather early.
|
// so we start it rather early.
|
||||||
const walletStartProm = createWalletDaemonWithClient(t, { name: "wallet" })
|
const walletStartProm = createWalletDaemonWithClient(t, { name: "wallet" });
|
||||||
|
|
||||||
if (fs.existsSync(sharedDir + "/bank.conf")) {
|
if (fs.existsSync(sharedDir + "/bank.conf")) {
|
||||||
logger.info("reusing existing bank");
|
logger.info("reusing existing bank");
|
||||||
@ -361,7 +364,7 @@ export async function useSharedTestkudosEnvironment(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await bankStart()
|
await bankStart();
|
||||||
|
|
||||||
const res = await Promise.all([
|
const res = await Promise.all([
|
||||||
exchangeStart(),
|
exchangeStart(),
|
||||||
|
@ -6,8 +6,21 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { URL } from "@gnu-taler/taler-util";
|
import { URL } from "@gnu-taler/taler-util";
|
||||||
import axiosImp from "axios";
|
import {
|
||||||
const axios = axiosImp.default;
|
createPlatformHttpLib,
|
||||||
|
makeBasicAuthHeader,
|
||||||
|
} from "@gnu-taler/taler-util/http";
|
||||||
|
import {
|
||||||
|
LibeufinNexusTransactions,
|
||||||
|
LibeufinSandboxAdminBankAccountBalance,
|
||||||
|
NexusBankConnections,
|
||||||
|
NexusFacadeListResponse,
|
||||||
|
NexusGetPermissionsResponse,
|
||||||
|
NexusNewTransactionsInfo,
|
||||||
|
NexusTask,
|
||||||
|
NexusTaskCollection,
|
||||||
|
NexusUserResponse,
|
||||||
|
} from "./libeufin.js";
|
||||||
|
|
||||||
export interface LibeufinSandboxServiceInterface {
|
export interface LibeufinSandboxServiceInterface {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@ -163,30 +176,13 @@ export interface LibeufinSandboxAddIncomingRequest {
|
|||||||
direction: string;
|
direction: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const libeufinHttpLib = createPlatformHttpLib();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APIs spread across Legacy and Access, it is therefore
|
* APIs spread across Legacy and Access, it is therefore
|
||||||
* the "base URL" relative to which API every call addresses.
|
* the "base URL" relative to which API every call addresses.
|
||||||
*/
|
*/
|
||||||
export namespace LibeufinSandboxApi {
|
export namespace LibeufinSandboxApi {
|
||||||
// Need Access API base URL.
|
|
||||||
export async function demobankAccountInfo(
|
|
||||||
username: string,
|
|
||||||
password: string,
|
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
|
||||||
accountLabel: string,
|
|
||||||
) {
|
|
||||||
let url = new URL(
|
|
||||||
`accounts/${accountLabel}`,
|
|
||||||
libeufinSandboxService.baseUrl,
|
|
||||||
);
|
|
||||||
return await axios.get(url.href, {
|
|
||||||
auth: {
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates one bank account via the Access API.
|
// Creates one bank account via the Access API.
|
||||||
// Need the /demobanks/$id/access-api as the base URL
|
// Need the /demobanks/$id/access-api as the base URL
|
||||||
export async function createDemobankAccount(
|
export async function createDemobankAccount(
|
||||||
@ -194,12 +190,15 @@ export namespace LibeufinSandboxApi {
|
|||||||
password: string,
|
password: string,
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
iban: string | null = null,
|
iban: string | null = null,
|
||||||
) {
|
): Promise<void> {
|
||||||
let url = new URL("testing/register", libeufinSandboxService.baseUrl);
|
let url = new URL("testing/register", libeufinSandboxService.baseUrl);
|
||||||
await axios.post(url.href, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
username: username,
|
username: username,
|
||||||
password: password,
|
password: password,
|
||||||
iban: iban,
|
iban: iban,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Need /demobanks/$id as the base URL
|
// Need /demobanks/$id as the base URL
|
||||||
@ -209,75 +208,57 @@ export namespace LibeufinSandboxApi {
|
|||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
username: string = "admin",
|
username: string = "admin",
|
||||||
password: string = "secret",
|
password: string = "secret",
|
||||||
) {
|
): Promise<void> {
|
||||||
// baseUrl should already be pointed to one demobank.
|
// baseUrl should already be pointed to one demobank.
|
||||||
let url = new URL("ebics/subscribers", libeufinSandboxService.baseUrl);
|
let url = new URL("ebics/subscribers", libeufinSandboxService.baseUrl);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
body: {
|
||||||
userID: req.userID,
|
userID: req.userID,
|
||||||
hostID: req.hostID,
|
hostID: req.hostID,
|
||||||
partnerID: req.partnerID,
|
partnerID: req.partnerID,
|
||||||
demobankAccountLabel: demobankAccountLabel,
|
demobankAccountLabel: demobankAccountLabel,
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "secret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function rotateKeys(
|
export async function rotateKeys(
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
hostID: string,
|
hostID: string,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL(`admin/ebics/hosts/${hostID}/rotate-keys`, baseUrl);
|
let url = new URL(`admin/ebics/hosts/${hostID}/rotate-keys`, baseUrl);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{},
|
body: {},
|
||||||
{
|
});
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "secret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
export async function createEbicsHost(
|
export async function createEbicsHost(
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
hostID: string,
|
hostID: string,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL("admin/ebics/hosts", baseUrl);
|
let url = new URL("admin/ebics/hosts", baseUrl);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
body: {
|
||||||
hostID,
|
hostID,
|
||||||
ebicsVersion: "2.5",
|
ebicsVersion: "2.5",
|
||||||
},
|
},
|
||||||
{
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
auth: {
|
});
|
||||||
username: "admin",
|
|
||||||
password: "secret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createBankAccount(
|
export async function createBankAccount(
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
req: BankAccountInfo,
|
req: BankAccountInfo,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL(`admin/bank-accounts/${req.label}`, baseUrl);
|
let url = new URL(`admin/bank-accounts/${req.label}`, baseUrl);
|
||||||
await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
body: req,
|
||||||
password: "secret",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,14 +269,13 @@ export namespace LibeufinSandboxApi {
|
|||||||
export async function createEbicsSubscriber(
|
export async function createEbicsSubscriber(
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
req: CreateEbicsSubscriberRequest,
|
req: CreateEbicsSubscriberRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL("admin/ebics/subscribers", baseUrl);
|
let url = new URL("admin/ebics/subscribers", baseUrl);
|
||||||
await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
body: req,
|
||||||
password: "secret",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,14 +286,13 @@ export namespace LibeufinSandboxApi {
|
|||||||
export async function createEbicsBankAccount(
|
export async function createEbicsBankAccount(
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
req: CreateEbicsBankAccountRequest,
|
req: CreateEbicsBankAccountRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL("admin/ebics/bank-accounts", baseUrl);
|
let url = new URL("admin/ebics/bank-accounts", baseUrl);
|
||||||
await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
body: req,
|
||||||
password: "secret",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,17 +300,16 @@ export namespace LibeufinSandboxApi {
|
|||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
accountLabel: string,
|
accountLabel: string,
|
||||||
req: SimulateIncomingTransactionRequest,
|
req: SimulateIncomingTransactionRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL(
|
let url = new URL(
|
||||||
`admin/bank-accounts/${accountLabel}/simulate-incoming-transaction`,
|
`admin/bank-accounts/${accountLabel}/simulate-incoming-transaction`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
body: req,
|
||||||
password: "secret",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,13 +322,10 @@ export namespace LibeufinSandboxApi {
|
|||||||
`admin/bank-accounts/${accountLabel}/transactions`,
|
`admin/bank-accounts/${accountLabel}/transactions`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
const res = await axios.get(url.href, {
|
const res = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: "admin",
|
|
||||||
password: "secret",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return res.data as SandboxAccountTransactions;
|
return (await res.json()) as SandboxAccountTransactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCamt053(
|
export async function getCamt053(
|
||||||
@ -359,61 +334,50 @@ export namespace LibeufinSandboxApi {
|
|||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL("admin/payments/camt", baseUrl);
|
let url = new URL("admin/payments/camt", baseUrl);
|
||||||
return await axios.post(
|
return await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
body: {
|
||||||
bankaccount: accountLabel,
|
bankaccount: accountLabel,
|
||||||
type: 53,
|
type: 53,
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "secret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAccountInfoWithBalance(
|
export async function getAccountInfoWithBalance(
|
||||||
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
libeufinSandboxService: LibeufinSandboxServiceInterface,
|
||||||
accountLabel: string,
|
accountLabel: string,
|
||||||
): Promise<any> {
|
): Promise<LibeufinSandboxAdminBankAccountBalance> {
|
||||||
const baseUrl = libeufinSandboxService.baseUrl;
|
const baseUrl = libeufinSandboxService.baseUrl;
|
||||||
let url = new URL(`admin/bank-accounts/${accountLabel}`, baseUrl);
|
let url = new URL(`admin/bank-accounts/${accountLabel}`, baseUrl);
|
||||||
return await axios.get(url.href, {
|
const res = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: "admin",
|
|
||||||
password: "secret",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
return res.json();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace LibeufinNexusApi {
|
export namespace LibeufinNexusApi {
|
||||||
export async function getAllConnections(
|
export async function getAllConnections(
|
||||||
nexus: LibeufinNexusServiceInterface,
|
nexus: LibeufinNexusServiceInterface,
|
||||||
): Promise<any> {
|
): Promise<NexusBankConnections> {
|
||||||
let url = new URL("bank-connections", nexus.baseUrl);
|
let url = new URL("bank-connections", nexus.baseUrl);
|
||||||
const res = await axios.get(url.href, {
|
const res = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return res;
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBankConnection(
|
export async function deleteBankConnection(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
req: DeleteBankConnectionRequest,
|
req: DeleteBankConnectionRequest,
|
||||||
): Promise<any> {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL("bank-connections/delete-connection", baseUrl);
|
let url = new URL("bank-connections/delete-connection", baseUrl);
|
||||||
return await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
password: "test",
|
body: req,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -423,9 +387,10 @@ export namespace LibeufinNexusApi {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL("bank-connections", baseUrl);
|
let url = new URL("bank-connections", baseUrl);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
body: {
|
||||||
source: "new",
|
source: "new",
|
||||||
type: "ebics",
|
type: "ebics",
|
||||||
name: req.name,
|
name: req.name,
|
||||||
@ -437,13 +402,7 @@ export namespace LibeufinNexusApi {
|
|||||||
systemID: req.systemID,
|
systemID: req.systemID,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBankAccount(
|
export async function getBankAccount(
|
||||||
@ -452,12 +411,10 @@ export namespace LibeufinNexusApi {
|
|||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`bank-accounts/${accountName}`, baseUrl);
|
let url = new URL(`bank-accounts/${accountName}`, baseUrl);
|
||||||
return await axios.get(url.href, {
|
const resp = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function submitInitiatedPayment(
|
export async function submitInitiatedPayment(
|
||||||
@ -470,16 +427,11 @@ export namespace LibeufinNexusApi {
|
|||||||
`bank-accounts/${accountName}/payment-initiations/${paymentId}/submit`,
|
`bank-accounts/${accountName}/payment-initiations/${paymentId}/submit`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{},
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
{
|
body: {},
|
||||||
auth: {
|
});
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAccounts(
|
export async function fetchAccounts(
|
||||||
@ -491,16 +443,11 @@ export namespace LibeufinNexusApi {
|
|||||||
`bank-connections/${connectionName}/fetch-accounts`,
|
`bank-connections/${connectionName}/fetch-accounts`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{},
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
{
|
body: {},
|
||||||
auth: {
|
});
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function importConnectionAccount(
|
export async function importConnectionAccount(
|
||||||
@ -514,37 +461,27 @@ export namespace LibeufinNexusApi {
|
|||||||
`bank-connections/${connectionName}/import-account`,
|
`bank-connections/${connectionName}/import-account`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
body: {
|
||||||
offeredAccountId,
|
offeredAccountId,
|
||||||
nexusBankAccountId,
|
nexusBankAccountId,
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function connectBankConnection(
|
export async function connectBankConnection(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
connectionName: string,
|
connectionName: string,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`bank-connections/${connectionName}/connect`, baseUrl);
|
let url = new URL(`bank-connections/${connectionName}/connect`, baseUrl);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{},
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
{
|
body: {},
|
||||||
auth: {
|
});
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPaymentInitiations(
|
export async function getPaymentInitiations(
|
||||||
@ -558,43 +495,33 @@ export namespace LibeufinNexusApi {
|
|||||||
`/bank-accounts/${accountName}/payment-initiations`,
|
`/bank-accounts/${accountName}/payment-initiations`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
let response = await axios.get(url.href, {
|
let response = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
const respJson = await response.json();
|
||||||
console.log(
|
console.log(
|
||||||
`Payment initiations of: ${accountName}`,
|
`Payment initiations of: ${accountName}`,
|
||||||
JSON.stringify(response.data, null, 2),
|
JSON.stringify(respJson, null, 2),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConfig(
|
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
|
||||||
): Promise<void> {
|
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
|
||||||
let url = new URL(`/config`, baseUrl);
|
|
||||||
let response = await axios.get(url.href);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uses the Anastasis API to get a list of transactions.
|
// Uses the Anastasis API to get a list of transactions.
|
||||||
export async function getAnastasisTransactions(
|
export async function getAnastasisTransactions(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
anastasisBaseUrl: string,
|
anastasisBaseUrl: string,
|
||||||
|
// FIXME: Nail down type!
|
||||||
params: {}, // of the request: {delta: 5, ..}
|
params: {}, // of the request: {delta: 5, ..}
|
||||||
username: string = "admin",
|
username: string = "admin",
|
||||||
password: string = "test",
|
password: string = "test",
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
let url = new URL("history/incoming", anastasisBaseUrl);
|
let url = new URL("history/incoming", anastasisBaseUrl);
|
||||||
let response = await axios.get(url.href, {
|
for (const [k, v] of Object.entries(params)) {
|
||||||
params: params,
|
url.searchParams.set(k, String(v));
|
||||||
auth: {
|
}
|
||||||
username: username,
|
let response = await libeufinHttpLib.fetch(url.href, {
|
||||||
password: password,
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return response;
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this function should return some structured
|
// FIXME: this function should return some structured
|
||||||
@ -604,16 +531,13 @@ export namespace LibeufinNexusApi {
|
|||||||
accountName: string,
|
accountName: string,
|
||||||
username: string = "admin",
|
username: string = "admin",
|
||||||
password: string = "test",
|
password: string = "test",
|
||||||
): Promise<any> {
|
): Promise<LibeufinNexusTransactions> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/bank-accounts/${accountName}/transactions`, baseUrl);
|
let url = new URL(`/bank-accounts/${accountName}/transactions`, baseUrl);
|
||||||
let response = await axios.get(url.href, {
|
let response = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
return response;
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchTransactions(
|
export async function fetchTransactions(
|
||||||
@ -623,25 +547,21 @@ export namespace LibeufinNexusApi {
|
|||||||
level: string = "report",
|
level: string = "report",
|
||||||
username: string = "admin",
|
username: string = "admin",
|
||||||
password: string = "test",
|
password: string = "test",
|
||||||
): Promise<any> {
|
): Promise<NexusNewTransactionsInfo> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(
|
let url = new URL(
|
||||||
`/bank-accounts/${accountName}/fetch-transactions`,
|
`/bank-accounts/${accountName}/fetch-transactions`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
return await axios.post(
|
const resp = await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
body: {
|
||||||
rangeType: rangeType,
|
rangeType: rangeType,
|
||||||
level: level,
|
level: level,
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
auth: {
|
return resp.json();
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function changePassword(
|
export async function changePassword(
|
||||||
@ -649,97 +569,109 @@ export namespace LibeufinNexusApi {
|
|||||||
username: string,
|
username: string,
|
||||||
req: UpdateNexusUserRequest,
|
req: UpdateNexusUserRequest,
|
||||||
auth: NexusAuth,
|
auth: NexusAuth,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/users/${username}/password`, baseUrl);
|
let url = new URL(`/users/${username}/password`, baseUrl);
|
||||||
await axios.post(url.href, req, auth);
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
body: req,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUser(
|
export async function getUser(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
auth: NexusAuth,
|
auth: NexusAuth,
|
||||||
): Promise<any> {
|
): Promise<NexusUserResponse> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/user`, baseUrl);
|
let url = new URL(`/user`, baseUrl);
|
||||||
return await axios.get(url.href, auth);
|
const resp = await libeufinHttpLib.fetch(url.href, {
|
||||||
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
});
|
||||||
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUser(
|
export async function createUser(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
req: CreateNexusUserRequest,
|
req: CreateNexusUserRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/users`, baseUrl);
|
let url = new URL(`/users`, baseUrl);
|
||||||
await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
password: "test",
|
body: req,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllPermissions(
|
export async function getAllPermissions(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
): Promise<any> {
|
): Promise<NexusGetPermissionsResponse> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/permissions`, baseUrl);
|
let url = new URL(`/permissions`, baseUrl);
|
||||||
return await axios.get(url.href, {
|
const resp = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postPermission(
|
export async function postPermission(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
req: PostNexusPermissionRequest,
|
req: PostNexusPermissionRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/permissions`, baseUrl);
|
let url = new URL(`/permissions`, baseUrl);
|
||||||
await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
password: "test",
|
body: req,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTasks(
|
export async function getAllTasks(
|
||||||
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
|
bankAccountName: string,
|
||||||
|
): Promise<NexusTaskCollection> {
|
||||||
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
|
let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl);
|
||||||
|
const resp = await libeufinHttpLib.fetch(url.href, {
|
||||||
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
});
|
||||||
|
return resp.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTask(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
bankAccountName: string,
|
bankAccountName: string,
|
||||||
// When void, the request returns the list of all the
|
// When void, the request returns the list of all the
|
||||||
// tasks under this bank account.
|
// tasks under this bank account.
|
||||||
taskName: string | void,
|
taskName: string,
|
||||||
): Promise<any> {
|
): Promise<NexusTask> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl);
|
let url = new URL(
|
||||||
|
`/bank-accounts/${bankAccountName}/schedule/${taskName}`,
|
||||||
|
baseUrl,
|
||||||
|
);
|
||||||
if (taskName) url = new URL(taskName, `${url.href}/`);
|
if (taskName) url = new URL(taskName, `${url.href}/`);
|
||||||
|
const resp = await libeufinHttpLib.fetch(url.href, {
|
||||||
// It's caller's responsibility to interpret the response.
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
return await axios.get(url.href, {
|
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTask(
|
export async function deleteTask(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
bankAccountName: string,
|
bankAccountName: string,
|
||||||
taskName: string,
|
taskName: string,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(
|
let url = new URL(
|
||||||
`/bank-accounts/${bankAccountName}/schedule/${taskName}`,
|
`/bank-accounts/${bankAccountName}/schedule/${taskName}`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
await axios.delete(url.href, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "DELETE",
|
||||||
username: "admin",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,53 +679,50 @@ export namespace LibeufinNexusApi {
|
|||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
bankAccountName: string,
|
bankAccountName: string,
|
||||||
req: PostNexusTaskRequest,
|
req: PostNexusTaskRequest,
|
||||||
): Promise<any> {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl);
|
let url = new URL(`/bank-accounts/${bankAccountName}/schedule`, baseUrl);
|
||||||
return await axios.post(url.href, req, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "POST",
|
||||||
username: "admin",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
password: "test",
|
body: req,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteFacade(
|
export async function deleteFacade(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
facadeName: string,
|
facadeName: string,
|
||||||
): Promise<any> {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(`facades/${facadeName}`, baseUrl);
|
let url = new URL(`facades/${facadeName}`, baseUrl);
|
||||||
return await axios.delete(url.href, {
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
method: "DELETE",
|
||||||
username: "admin",
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllFacades(
|
export async function getAllFacades(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
): Promise<any> {
|
): Promise<NexusFacadeListResponse> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL("facades", baseUrl);
|
let url = new URL("facades", baseUrl);
|
||||||
return await axios.get(url.href, {
|
const resp = await libeufinHttpLib.fetch(url.href, {
|
||||||
auth: {
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
// FIXME: Just return validated, typed response here!
|
||||||
|
return resp.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAnastasisFacade(
|
export async function createAnastasisFacade(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
req: CreateAnastasisFacadeRequest,
|
req: CreateAnastasisFacadeRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL("facades", baseUrl);
|
let url = new URL("facades", baseUrl);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
body: {
|
||||||
name: req.name,
|
name: req.name,
|
||||||
type: "anastasis",
|
type: "anastasis",
|
||||||
config: {
|
config: {
|
||||||
@ -803,24 +732,19 @@ export namespace LibeufinNexusApi {
|
|||||||
reserveTransferLevel: req.reserveTransferLevel,
|
reserveTransferLevel: req.reserveTransferLevel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTwgFacade(
|
export async function createTwgFacade(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
req: CreateTalerWireGatewayFacadeRequest,
|
req: CreateTalerWireGatewayFacadeRequest,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL("facades", baseUrl);
|
let url = new URL("facades", baseUrl);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
|
body: {
|
||||||
name: req.name,
|
name: req.name,
|
||||||
type: "taler-wire-gateway",
|
type: "taler-wire-gateway",
|
||||||
config: {
|
config: {
|
||||||
@ -830,33 +754,22 @@ export namespace LibeufinNexusApi {
|
|||||||
reserveTransferLevel: req.reserveTransferLevel,
|
reserveTransferLevel: req.reserveTransferLevel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
auth: {
|
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function submitAllPaymentInitiations(
|
export async function submitAllPaymentInitiations(
|
||||||
libeufinNexusService: LibeufinNexusServiceInterface,
|
libeufinNexusService: LibeufinNexusServiceInterface,
|
||||||
accountId: string,
|
accountId: string,
|
||||||
) {
|
): Promise<void> {
|
||||||
const baseUrl = libeufinNexusService.baseUrl;
|
const baseUrl = libeufinNexusService.baseUrl;
|
||||||
let url = new URL(
|
let url = new URL(
|
||||||
`/bank-accounts/${accountId}/submit-all-payment-initiations`,
|
`/bank-accounts/${accountId}/submit-all-payment-initiations`,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
);
|
);
|
||||||
await axios.post(
|
await libeufinHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{},
|
headers: { Authorization: makeBasicAuthHeader("admin", "secret") },
|
||||||
{
|
body: {},
|
||||||
auth: {
|
});
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,39 +26,32 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import axios from "axios";
|
import { AmountString, Logger } from "@gnu-taler/taler-util";
|
||||||
import { URL, Logger } from "@gnu-taler/taler-util";
|
|
||||||
import {
|
import {
|
||||||
GlobalTestState,
|
|
||||||
DbInfo,
|
DbInfo,
|
||||||
pingProc,
|
GlobalTestState,
|
||||||
ProcessWrapper,
|
ProcessWrapper,
|
||||||
|
getRandomIban,
|
||||||
|
pingProc,
|
||||||
runCommand,
|
runCommand,
|
||||||
setupDb,
|
setupDb,
|
||||||
sh,
|
sh,
|
||||||
getRandomIban,
|
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
LibeufinSandboxApi,
|
|
||||||
LibeufinNexusApi,
|
|
||||||
CreateEbicsBankAccountRequest,
|
|
||||||
LibeufinSandboxServiceInterface,
|
|
||||||
CreateTalerWireGatewayFacadeRequest,
|
|
||||||
SimulateIncomingTransactionRequest,
|
|
||||||
SandboxAccountTransactions,
|
|
||||||
DeleteBankConnectionRequest,
|
|
||||||
CreateEbicsBankConnectionRequest,
|
|
||||||
UpdateNexusUserRequest,
|
|
||||||
NexusAuth,
|
|
||||||
CreateAnastasisFacadeRequest,
|
CreateAnastasisFacadeRequest,
|
||||||
PostNexusTaskRequest,
|
CreateEbicsBankAccountRequest,
|
||||||
PostNexusPermissionRequest,
|
CreateEbicsBankConnectionRequest,
|
||||||
CreateNexusUserRequest,
|
CreateNexusUserRequest,
|
||||||
|
CreateTalerWireGatewayFacadeRequest,
|
||||||
|
LibeufinNexusApi,
|
||||||
|
LibeufinSandboxApi,
|
||||||
|
LibeufinSandboxServiceInterface,
|
||||||
|
PostNexusPermissionRequest,
|
||||||
} from "../harness/libeufin-apis.js";
|
} from "../harness/libeufin-apis.js";
|
||||||
|
|
||||||
const logger = new Logger("libeufin.ts");
|
const logger = new Logger("libeufin.ts");
|
||||||
|
|
||||||
export { LibeufinSandboxApi, LibeufinNexusApi };
|
export { LibeufinNexusApi, LibeufinSandboxApi };
|
||||||
|
|
||||||
export interface LibeufinServices {
|
export interface LibeufinServices {
|
||||||
libeufinSandbox: LibeufinSandboxService;
|
libeufinSandbox: LibeufinSandboxService;
|
||||||
@ -76,7 +69,7 @@ export interface LibeufinNexusConfig {
|
|||||||
databaseJdbcUri: string;
|
databaseJdbcUri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LibeufinNexusMoneyMovement {
|
export interface LibeufinNexusMoneyMovement {
|
||||||
amount: string;
|
amount: string;
|
||||||
creditDebitIndicator: string;
|
creditDebitIndicator: string;
|
||||||
details: {
|
details: {
|
||||||
@ -103,11 +96,11 @@ interface LibeufinNexusMoneyMovement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LibeufinNexusBatches {
|
export interface LibeufinNexusBatches {
|
||||||
batchTransactions: Array<LibeufinNexusMoneyMovement>;
|
batchTransactions: Array<LibeufinNexusMoneyMovement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LibeufinNexusTransaction {
|
export interface LibeufinNexusTransaction {
|
||||||
amount: string;
|
amount: string;
|
||||||
creditDebitIndicator: string;
|
creditDebitIndicator: string;
|
||||||
status: string;
|
status: string;
|
||||||
@ -118,7 +111,7 @@ interface LibeufinNexusTransaction {
|
|||||||
batches: Array<LibeufinNexusBatches>;
|
batches: Array<LibeufinNexusBatches>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LibeufinNexusTransactions {
|
export interface LibeufinNexusTransactions {
|
||||||
transactions: Array<LibeufinNexusTransaction>;
|
transactions: Array<LibeufinNexusTransaction>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +175,146 @@ export interface LibeufinPreparedPaymentDetails {
|
|||||||
nexusBankAccountName: string;
|
nexusBankAccountName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NexusBankConnection {
|
||||||
|
// connection type. For example "ebics".
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
// connection name as given by the user at
|
||||||
|
// the moment of creation.
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NexusBankConnections {
|
||||||
|
bankConnections: NexusBankConnection[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacadeShowInfo {
|
||||||
|
// Name of the facade, same as the "fcid" parameter.
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
// Type of the facade.
|
||||||
|
// For example, "taler-wire-gateway".
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
// Bas URL of the facade.
|
||||||
|
baseUrl: string;
|
||||||
|
|
||||||
|
// details depending on the facade type.
|
||||||
|
config: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FetchParams {
|
||||||
|
// Because transactions are delivered by banks in "batches",
|
||||||
|
// then every batch can have different qualities. This value
|
||||||
|
// lets the request specify which type of batch ought to be
|
||||||
|
// returned. Currently, the following two type are supported:
|
||||||
|
//
|
||||||
|
// 'report': typically includes only non booked transactions.
|
||||||
|
// 'statement': typically includes only booked transactions.
|
||||||
|
level: "report" | "statement" | "all";
|
||||||
|
|
||||||
|
// This type indicates the time range of the query.
|
||||||
|
// It allows the following values:
|
||||||
|
//
|
||||||
|
// 'latest': retrieves the last transactions from the bank.
|
||||||
|
// If there are older unread transactions, those will *not*
|
||||||
|
// be downloaded.
|
||||||
|
//
|
||||||
|
// 'all': retrieves all the transactions from the bank,
|
||||||
|
// until the oldest.
|
||||||
|
//
|
||||||
|
// 'previous-days': currently *not* implemented, it will allow
|
||||||
|
// the request to download transactions from
|
||||||
|
// today until N days before.
|
||||||
|
//
|
||||||
|
// 'since-last': retrieves all the transactions since the last
|
||||||
|
// time one was downloaded.
|
||||||
|
//
|
||||||
|
rangeType: "latest" | "all" | "previous-days" | "since-last";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NexusTask {
|
||||||
|
// The resource being impacted by this operation.
|
||||||
|
// Typically a (Nexus) bank account being fetched
|
||||||
|
// or whose payments are submitted. In this cases,
|
||||||
|
// this value is the "bank-account" constant.
|
||||||
|
resourceType: string;
|
||||||
|
// Name of the resource. In case of "bank-account", that
|
||||||
|
// is the name under which the bank account was imported
|
||||||
|
// from the bank.
|
||||||
|
resourceId: string;
|
||||||
|
// Task name, equals 'taskId'
|
||||||
|
taskName: string;
|
||||||
|
// Values allowed are "fetch" or "submit".
|
||||||
|
taskType: string;
|
||||||
|
// FIXME: describe.
|
||||||
|
taskCronSpec: string;
|
||||||
|
// Only meaningful for "fetch" types.
|
||||||
|
taskParams: FetchParams;
|
||||||
|
// Timestamp in secons when the next iteration will run.
|
||||||
|
nextScheduledExecutionSec: number;
|
||||||
|
// Timestamp in seconds when the previous iteration ran.
|
||||||
|
prevScheduledExecutionSec: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NexusNewTransactionsInfo {
|
||||||
|
// How many transactions are new to Nexus.
|
||||||
|
newTransactions: number;
|
||||||
|
// How many transactions got downloaded by the request.
|
||||||
|
// Note that a transaction can be downloaded multiple
|
||||||
|
// times but only counts as new once.
|
||||||
|
downloadedTransactions: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface NexusUserResponse {
|
||||||
|
// User name
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
// Is this a super user?
|
||||||
|
superuser: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NexusTaskShortInfo {
|
||||||
|
cronspec: string;
|
||||||
|
type: "fetch" | "submit";
|
||||||
|
params: FetchParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NexusTaskCollection {
|
||||||
|
// This field can contain *multiple* objects of the type sampled below.
|
||||||
|
schedule: {
|
||||||
|
[taskName: string]: NexusTaskShortInfo;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NexusFacadeListResponse {
|
||||||
|
facades: FacadeShowInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LibeufinSandboxAdminBankAccountBalance {
|
||||||
|
// Balance in the $currency:$amount format.
|
||||||
|
balance: AmountString;
|
||||||
|
// IBAN of the bank account identified by $accountLabel
|
||||||
|
iban: string;
|
||||||
|
// BIC of the bank account identified by $accountLabel
|
||||||
|
bic: string;
|
||||||
|
// Mentions $accountLabel
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LibeufinPermission {
|
||||||
|
subjectType: string;
|
||||||
|
subjectId: string;
|
||||||
|
resourceType: string;
|
||||||
|
resourceId: string;
|
||||||
|
permissionName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NexusGetPermissionsResponse {
|
||||||
|
permissions: LibeufinPermission[];
|
||||||
|
}
|
||||||
|
|
||||||
export class LibeufinSandboxService implements LibeufinSandboxServiceInterface {
|
export class LibeufinSandboxService implements LibeufinSandboxServiceInterface {
|
||||||
static async create(
|
static async create(
|
||||||
gc: GlobalTestState,
|
gc: GlobalTestState,
|
||||||
|
@ -191,12 +191,12 @@ configCli
|
|||||||
const config = Configuration.load();
|
const config = Configuration.load();
|
||||||
let res;
|
let res;
|
||||||
if (args.get.file) {
|
if (args.get.file) {
|
||||||
res = config.getString(args.get.section, args.get.option);
|
|
||||||
} else {
|
|
||||||
res = config.getPath(args.get.section, args.get.option);
|
res = config.getPath(args.get.section, args.get.option);
|
||||||
|
} else {
|
||||||
|
res = config.getString(args.get.section, args.get.option);
|
||||||
}
|
}
|
||||||
if (res.isDefined()) {
|
if (res.isDefined()) {
|
||||||
console.log(res.getValue());
|
console.log(res.required());
|
||||||
} else {
|
} else {
|
||||||
console.warn("not found");
|
console.warn("not found");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2023 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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
AbsoluteTime,
|
||||||
|
ContractTermsUtil,
|
||||||
|
decodeCrock,
|
||||||
|
Duration,
|
||||||
|
encodeCrock,
|
||||||
|
getRandomBytes,
|
||||||
|
hash,
|
||||||
|
j2s,
|
||||||
|
PeerContractTerms,
|
||||||
|
TalerError,
|
||||||
|
TalerPreciseTimestamp,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import {
|
||||||
|
checkReserve,
|
||||||
|
CryptoDispatcher,
|
||||||
|
downloadExchangeInfo,
|
||||||
|
EncryptContractRequest,
|
||||||
|
findDenomOrThrow,
|
||||||
|
SpendCoinDetails,
|
||||||
|
SynchronousCryptoWorkerFactoryPlain,
|
||||||
|
topupReserveWithDemobank,
|
||||||
|
Wallet,
|
||||||
|
withdrawCoin,
|
||||||
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
|
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
|
||||||
|
import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the exchange's purse API.
|
||||||
|
*/
|
||||||
|
export async function runExchangePurseTest(t: GlobalTestState) {
|
||||||
|
// Set up test environment
|
||||||
|
|
||||||
|
const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
|
||||||
|
|
||||||
|
const http = harnessHttpLib;
|
||||||
|
const cryptoDisp = new CryptoDispatcher(
|
||||||
|
new SynchronousCryptoWorkerFactoryPlain(),
|
||||||
|
);
|
||||||
|
const cryptoApi = cryptoDisp.cryptoApi;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
|
const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http);
|
||||||
|
|
||||||
|
const reserveKeyPair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
|
||||||
|
let reserveUrl = new URL(
|
||||||
|
`reserves/${reserveKeyPair.pub}`,
|
||||||
|
exchange.baseUrl,
|
||||||
|
);
|
||||||
|
reserveUrl.searchParams.set("timeout_ms", "30000");
|
||||||
|
const longpollReq = http.fetch(reserveUrl.href, {
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
await topupReserveWithDemobank({
|
||||||
|
amount: "TESTKUDOS:10",
|
||||||
|
http,
|
||||||
|
reservePub: reserveKeyPair.pub,
|
||||||
|
bankAccessApiBaseUrl: bank.bankAccessApiBaseUrl,
|
||||||
|
exchangeInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("waiting for longpoll request");
|
||||||
|
const resp = await longpollReq;
|
||||||
|
console.log(`got response, status ${resp.status}`);
|
||||||
|
|
||||||
|
console.log(exchangeInfo);
|
||||||
|
|
||||||
|
await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);
|
||||||
|
|
||||||
|
const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8", {
|
||||||
|
denomselAllowLate: Wallet.defaultConfig.testing.denomselAllowLate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const coin = await withdrawCoin({
|
||||||
|
http,
|
||||||
|
cryptoApi,
|
||||||
|
reserveKeyPair: {
|
||||||
|
reservePriv: reserveKeyPair.priv,
|
||||||
|
reservePub: reserveKeyPair.pub,
|
||||||
|
},
|
||||||
|
denom: d1,
|
||||||
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
const amount = "TESTKUDOS:5";
|
||||||
|
const purseFee = "TESTKUDOS:0";
|
||||||
|
|
||||||
|
const mergeTimestamp = TalerPreciseTimestamp.now();
|
||||||
|
|
||||||
|
const contractTerms: PeerContractTerms = {
|
||||||
|
amount,
|
||||||
|
summary: "Hello",
|
||||||
|
purse_expiration: AbsoluteTime.toProtocolTimestamp(
|
||||||
|
AbsoluteTime.addDuration(
|
||||||
|
AbsoluteTime.now(),
|
||||||
|
Duration.fromSpec({ minutes: 1 }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mergeReservePair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const pursePair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const mergePair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const contractPair = await cryptoApi.createEddsaKeypair({});
|
||||||
|
const contractEncNonce = encodeCrock(getRandomBytes(24));
|
||||||
|
|
||||||
|
const pursePub = pursePair.pub;
|
||||||
|
|
||||||
|
const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);
|
||||||
|
|
||||||
|
const purseSigResp = await cryptoApi.signPurseCreation({
|
||||||
|
hContractTerms,
|
||||||
|
mergePub: mergePair.pub,
|
||||||
|
minAge: 0,
|
||||||
|
purseAmount: amount,
|
||||||
|
purseExpiration: contractTerms.purse_expiration,
|
||||||
|
pursePriv: pursePair.priv,
|
||||||
|
});
|
||||||
|
|
||||||
|
const coinSpend: SpendCoinDetails = {
|
||||||
|
ageCommitmentProof: undefined,
|
||||||
|
coinPriv: coin.coinPriv,
|
||||||
|
coinPub: coin.coinPub,
|
||||||
|
contribution: amount,
|
||||||
|
denomPubHash: coin.denomPubHash,
|
||||||
|
denomSig: coin.denomSig,
|
||||||
|
};
|
||||||
|
|
||||||
|
const depositSigsResp = await cryptoApi.signPurseDeposits({
|
||||||
|
exchangeBaseUrl: exchange.baseUrl,
|
||||||
|
pursePub: pursePair.pub,
|
||||||
|
coins: [coinSpend],
|
||||||
|
});
|
||||||
|
|
||||||
|
const encryptContractRequest: EncryptContractRequest = {
|
||||||
|
contractTerms: contractTerms,
|
||||||
|
mergePriv: mergePair.priv,
|
||||||
|
pursePriv: pursePair.priv,
|
||||||
|
pursePub: pursePair.pub,
|
||||||
|
contractPriv: contractPair.priv,
|
||||||
|
contractPub: contractPair.pub,
|
||||||
|
nonce: contractEncNonce,
|
||||||
|
};
|
||||||
|
|
||||||
|
const econtractResp = await cryptoApi.encryptContractForMerge(
|
||||||
|
encryptContractRequest,
|
||||||
|
);
|
||||||
|
|
||||||
|
const econtractHash = encodeCrock(
|
||||||
|
hash(decodeCrock(econtractResp.econtract.econtract)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const createPurseUrl = new URL(
|
||||||
|
`purses/${pursePair.pub}/create`,
|
||||||
|
exchange.baseUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reqBody = {
|
||||||
|
amount: amount,
|
||||||
|
merge_pub: mergePair.pub,
|
||||||
|
purse_sig: purseSigResp.sig,
|
||||||
|
h_contract_terms: hContractTerms,
|
||||||
|
purse_expiration: contractTerms.purse_expiration,
|
||||||
|
deposits: depositSigsResp.deposits,
|
||||||
|
min_age: 0,
|
||||||
|
econtract: econtractResp.econtract,
|
||||||
|
};
|
||||||
|
|
||||||
|
const httpResp = await http.fetch(createPurseUrl.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: reqBody,
|
||||||
|
});
|
||||||
|
|
||||||
|
const respBody = await httpResp.json();
|
||||||
|
|
||||||
|
console.log("status", httpResp.status);
|
||||||
|
|
||||||
|
console.log(j2s(respBody));
|
||||||
|
|
||||||
|
const mergeUrl = new URL(`purses/${pursePub}/merge`, exchange.baseUrl);
|
||||||
|
mergeUrl.searchParams.set("timeout_ms", "300");
|
||||||
|
const statusResp = await http.fetch(mergeUrl.href, {});
|
||||||
|
|
||||||
|
const statusRespBody = await statusResp.json();
|
||||||
|
|
||||||
|
console.log(j2s(statusRespBody));
|
||||||
|
|
||||||
|
t.assertTrue(statusRespBody.merge_timestamp === undefined);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TalerError) {
|
||||||
|
console.log(e);
|
||||||
|
console.log(j2s(e.errorDetail));
|
||||||
|
} else {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runExchangePurseTest.suites = ["wallet"];
|
@ -21,8 +21,11 @@ import {
|
|||||||
AbsoluteTime,
|
AbsoluteTime,
|
||||||
codecForExchangeKeysJson,
|
codecForExchangeKeysJson,
|
||||||
DenominationPubKey,
|
DenominationPubKey,
|
||||||
|
DenomKeyType,
|
||||||
Duration,
|
Duration,
|
||||||
durationFromSpec,
|
durationFromSpec,
|
||||||
|
ExchangeKeysJson,
|
||||||
|
Logger,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
createPlatformHttpLib,
|
createPlatformHttpLib,
|
||||||
@ -32,14 +35,60 @@ import { makeNoFeeCoinConfig } from "../harness/denomStructures.js";
|
|||||||
import {
|
import {
|
||||||
BankService,
|
BankService,
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
|
getPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
getPayto,
|
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import { withdrawViaBank } from "../harness/helpers.js";
|
import { withdrawViaBank } from "../harness/helpers.js";
|
||||||
|
|
||||||
|
const logger = new Logger("test-exchange-timetravel.ts");
|
||||||
|
|
||||||
|
interface DenomInfo {
|
||||||
|
denomPub: DenominationPubKey;
|
||||||
|
expireDeposit: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDenomInfoFromKeys(ek: ExchangeKeysJson): DenomInfo[] {
|
||||||
|
const denomInfos: DenomInfo[] = [];
|
||||||
|
for (const denomGroup of ek.denominations) {
|
||||||
|
switch (denomGroup.cipher) {
|
||||||
|
case "RSA":
|
||||||
|
case "RSA+age_restricted": {
|
||||||
|
let ageMask = 0;
|
||||||
|
if (denomGroup.cipher === "RSA+age_restricted") {
|
||||||
|
ageMask = denomGroup.age_mask;
|
||||||
|
}
|
||||||
|
for (const denomIn of denomGroup.denoms) {
|
||||||
|
const denomPub: DenominationPubKey = {
|
||||||
|
age_mask: ageMask,
|
||||||
|
cipher: DenomKeyType.Rsa,
|
||||||
|
rsa_public_key: denomIn.rsa_pub,
|
||||||
|
};
|
||||||
|
denomInfos.push({
|
||||||
|
denomPub,
|
||||||
|
expireDeposit: AbsoluteTime.stringify(
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(denomIn.stamp_expire_deposit),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CS+age_restricted":
|
||||||
|
case "CS":
|
||||||
|
logger.warn("Clause-Schnorr denominations not supported");
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
logger.warn(
|
||||||
|
`denomination type ${(denomGroup as any).cipher} not supported`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return denomInfos;
|
||||||
|
}
|
||||||
|
|
||||||
async function applyTimeTravel(
|
async function applyTimeTravel(
|
||||||
timetravelDuration: Duration,
|
timetravelDuration: Duration,
|
||||||
s: {
|
s: {
|
||||||
@ -144,7 +193,7 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:15" });
|
await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:15" });
|
||||||
|
|
||||||
const keysResp1 = await http.get(exchange.baseUrl + "keys");
|
const keysResp1 = await http.fetch(exchange.baseUrl + "keys");
|
||||||
const keys1 = await readSuccessResponseJsonOrThrow(
|
const keys1 = await readSuccessResponseJsonOrThrow(
|
||||||
keysResp1,
|
keysResp1,
|
||||||
codecForExchangeKeysJson(),
|
codecForExchangeKeysJson(),
|
||||||
@ -163,7 +212,7 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
|
|||||||
merchant,
|
merchant,
|
||||||
});
|
});
|
||||||
|
|
||||||
const keysResp2 = await http.get(exchange.baseUrl + "keys");
|
const keysResp2 = await http.fetch(exchange.baseUrl + "keys");
|
||||||
const keys2 = await readSuccessResponseJsonOrThrow(
|
const keys2 = await readSuccessResponseJsonOrThrow(
|
||||||
keysResp2,
|
keysResp2,
|
||||||
codecForExchangeKeysJson(),
|
codecForExchangeKeysJson(),
|
||||||
@ -173,41 +222,31 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
|
|||||||
JSON.stringify(keys2, undefined, 2),
|
JSON.stringify(keys2, undefined, 2),
|
||||||
);
|
);
|
||||||
|
|
||||||
const denomPubs1 = keys1.denoms.map((x) => {
|
const denomPubs1 = getDenomInfoFromKeys(keys1);
|
||||||
return {
|
const denomPubs2 = getDenomInfoFromKeys(keys2);
|
||||||
denomPub: x.denom_pub,
|
|
||||||
expireDeposit: AbsoluteTime.stringify(
|
|
||||||
AbsoluteTime.fromProtocolTimestamp(x.stamp_expire_deposit),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const denomPubs2 = keys2.denoms.map((x) => {
|
|
||||||
return {
|
|
||||||
denomPub: x.denom_pub,
|
|
||||||
expireDeposit: AbsoluteTime.stringify(
|
|
||||||
AbsoluteTime.fromProtocolTimestamp(x.stamp_expire_deposit),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const dps2 = new Set(denomPubs2.map((x) => x.denomPub));
|
const dps2 = new Set(denomPubs2.map((x) => x.denomPub));
|
||||||
|
|
||||||
console.log("=== KEYS RESPONSE 1 ===");
|
console.log("=== KEYS RESPONSE 1 ===");
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"list issue date",
|
"list issue date",
|
||||||
AbsoluteTime.stringify(AbsoluteTime.fromProtocolTimestamp(keys1.list_issue_date)),
|
AbsoluteTime.stringify(
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(keys1.list_issue_date),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
console.log("num denoms", keys1.denoms.length);
|
console.log("num denoms", denomPubs1.length);
|
||||||
console.log("denoms", JSON.stringify(denomPubs1, undefined, 2));
|
console.log("denoms", JSON.stringify(denomPubs1, undefined, 2));
|
||||||
|
|
||||||
console.log("=== KEYS RESPONSE 2 ===");
|
console.log("=== KEYS RESPONSE 2 ===");
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"list issue date",
|
"list issue date",
|
||||||
AbsoluteTime.stringify(AbsoluteTime.fromProtocolTimestamp(keys2.list_issue_date)),
|
AbsoluteTime.stringify(
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(keys2.list_issue_date),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
console.log("num denoms", keys2.denoms.length);
|
console.log("num denoms", denomPubs2.length);
|
||||||
console.log("denoms", JSON.stringify(denomPubs2, undefined, 2));
|
console.log("denoms", JSON.stringify(denomPubs2, undefined, 2));
|
||||||
|
|
||||||
for (const da of denomPubs1) {
|
for (const da of denomPubs1) {
|
||||||
|
@ -101,7 +101,7 @@ export async function runLibeufinApiBankaccountTest(t: GlobalTestState) {
|
|||||||
nexus,
|
nexus,
|
||||||
"local-mock",
|
"local-mock",
|
||||||
);
|
);
|
||||||
let el = findNexusPayment("mock subject", transactions.data);
|
let el = findNexusPayment("mock subject", transactions);
|
||||||
t.assertTrue(el instanceof Object);
|
t.assertTrue(el instanceof Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,12 +45,12 @@ export async function runLibeufinApiBankconnectionTest(t: GlobalTestState) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let connections = await LibeufinNexusApi.getAllConnections(nexus);
|
let connections = await LibeufinNexusApi.getAllConnections(nexus);
|
||||||
t.assertTrue(connections.data["bankConnections"].length == 1);
|
t.assertTrue(connections.bankConnections.length == 1);
|
||||||
|
|
||||||
await LibeufinNexusApi.deleteBankConnection(nexus, {
|
await LibeufinNexusApi.deleteBankConnection(nexus, {
|
||||||
bankConnectionId: "bankconnection-api-test-connection",
|
bankConnectionId: "bankconnection-api-test-connection",
|
||||||
});
|
});
|
||||||
connections = await LibeufinNexusApi.getAllConnections(nexus);
|
connections = await LibeufinNexusApi.getAllConnections(nexus);
|
||||||
t.assertTrue(connections.data["bankConnections"].length == 0);
|
t.assertTrue(connections.bankConnections.length == 0);
|
||||||
}
|
}
|
||||||
runLibeufinApiBankconnectionTest.suites = ["libeufin"];
|
runLibeufinApiBankconnectionTest.suites = ["libeufin"];
|
||||||
|
@ -18,15 +18,16 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { URL } from "@gnu-taler/taler-util";
|
import { URL } from "@gnu-taler/taler-util";
|
||||||
import axiosImp from "axios";
|
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
|
||||||
import { GlobalTestState } from "../harness/harness.js";
|
|
||||||
import {
|
import {
|
||||||
launchLibeufinServices,
|
launchLibeufinServices,
|
||||||
NexusUserBundle,
|
NexusUserBundle,
|
||||||
SandboxUserBundle,
|
SandboxUserBundle,
|
||||||
} from "../harness/libeufin.js";
|
} from "../harness/libeufin.js";
|
||||||
|
import {
|
||||||
const axios = axiosImp.default;
|
createPlatformHttpLib,
|
||||||
|
makeBasicAuthHeader,
|
||||||
|
} from "@gnu-taler/taler-util/http";
|
||||||
|
|
||||||
export async function runLibeufinApiFacadeBadRequestTest(t: GlobalTestState) {
|
export async function runLibeufinApiFacadeBadRequestTest(t: GlobalTestState) {
|
||||||
/**
|
/**
|
||||||
@ -50,21 +51,17 @@ export async function runLibeufinApiFacadeBadRequestTest(t: GlobalTestState) {
|
|||||||
console.log("malformed facade");
|
console.log("malformed facade");
|
||||||
const baseUrl = libeufinServices.libeufinNexus.baseUrl;
|
const baseUrl = libeufinServices.libeufinNexus.baseUrl;
|
||||||
let url = new URL("facades", baseUrl);
|
let url = new URL("facades", baseUrl);
|
||||||
let resp = await axios.post(
|
let resp = await harnessHttpLib.fetch(url.href, {
|
||||||
url.href,
|
method: "POST",
|
||||||
{
|
body: {
|
||||||
name: "malformed-facade",
|
name: "malformed-facade",
|
||||||
type: "taler-wire-gateway",
|
type: "taler-wire-gateway",
|
||||||
config: {}, // malformation here.
|
config: {}, // malformation here.
|
||||||
},
|
},
|
||||||
{
|
headers: {
|
||||||
auth: {
|
Authorization: makeBasicAuthHeader("admin", "test"),
|
||||||
username: "admin",
|
|
||||||
password: "test",
|
|
||||||
},
|
},
|
||||||
validateStatus: () => true,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
t.assertTrue(resp.status == 400);
|
t.assertTrue(resp.status == 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,20 +51,20 @@ export async function runLibeufinApiFacadeTest(t: GlobalTestState) {
|
|||||||
libeufinServices.libeufinNexus,
|
libeufinServices.libeufinNexus,
|
||||||
);
|
);
|
||||||
// check that original facade shows up.
|
// check that original facade shows up.
|
||||||
t.assertTrue(resp.data["facades"][0]["name"] == user01nexus.twgReq["name"]);
|
t.assertTrue(resp.facades[0].name == user01nexus.twgReq["name"]);
|
||||||
|
|
||||||
const twgBaseUrl: string = resp.data["facades"][0]["baseUrl"];
|
const twgBaseUrl: string = resp.facades[0]["baseUrl"];
|
||||||
t.assertTrue(typeof twgBaseUrl === "string");
|
t.assertTrue(typeof twgBaseUrl === "string");
|
||||||
t.assertTrue(twgBaseUrl.startsWith("http://"));
|
t.assertTrue(twgBaseUrl.startsWith("http://"));
|
||||||
t.assertTrue(twgBaseUrl.endsWith("/"));
|
t.assertTrue(twgBaseUrl.endsWith("/"));
|
||||||
|
|
||||||
// delete it.
|
// delete it.
|
||||||
resp = await LibeufinNexusApi.deleteFacade(
|
await LibeufinNexusApi.deleteFacade(
|
||||||
libeufinServices.libeufinNexus,
|
libeufinServices.libeufinNexus,
|
||||||
user01nexus.twgReq["name"],
|
user01nexus.twgReq["name"],
|
||||||
);
|
);
|
||||||
// check that no facades show up.
|
resp = await LibeufinNexusApi.getAllFacades(libeufinServices.libeufinNexus);
|
||||||
t.assertTrue(!resp.data.hasOwnProperty("facades"));
|
t.assertTrue(!resp.hasOwnProperty("facades"));
|
||||||
}
|
}
|
||||||
|
|
||||||
runLibeufinApiFacadeTest.suites = ["libeufin"];
|
runLibeufinApiFacadeTest.suites = ["libeufin"];
|
||||||
|
@ -46,7 +46,8 @@ export async function runLibeufinApiPermissionsTest(t: GlobalTestState) {
|
|||||||
user01nexus.twgTransferPermission,
|
user01nexus.twgTransferPermission,
|
||||||
);
|
);
|
||||||
let transferPermission = await LibeufinNexusApi.getAllPermissions(nexus);
|
let transferPermission = await LibeufinNexusApi.getAllPermissions(nexus);
|
||||||
let element = transferPermission.data["permissions"].pop();
|
let element = transferPermission["permissions"].pop();
|
||||||
|
t.assertTrue(!!element);
|
||||||
t.assertTrue(
|
t.assertTrue(
|
||||||
element["permissionName"] == "facade.talerwiregateway.transfer" &&
|
element["permissionName"] == "facade.talerwiregateway.transfer" &&
|
||||||
element["subjectId"] == "username-01",
|
element["subjectId"] == "username-01",
|
||||||
@ -58,7 +59,7 @@ export async function runLibeufinApiPermissionsTest(t: GlobalTestState) {
|
|||||||
await LibeufinNexusApi.postPermission(nexus, denyTransfer);
|
await LibeufinNexusApi.postPermission(nexus, denyTransfer);
|
||||||
|
|
||||||
transferPermission = await LibeufinNexusApi.getAllPermissions(nexus);
|
transferPermission = await LibeufinNexusApi.getAllPermissions(nexus);
|
||||||
t.assertTrue(transferPermission.data["permissions"].length == 0);
|
t.assertTrue(transferPermission["permissions"].length == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
runLibeufinApiPermissionsTest.suites = ["libeufin"];
|
runLibeufinApiPermissionsTest.suites = ["libeufin"];
|
||||||
|
@ -64,6 +64,6 @@ export async function runLibeufinApiSandboxTransactionsTest(
|
|||||||
sandbox,
|
sandbox,
|
||||||
"mock-account",
|
"mock-account",
|
||||||
);
|
);
|
||||||
t.assertAmountEquals(ret.data.balance, "EUR:2.1");
|
t.assertAmountEquals(ret.balance, "EUR:2.1");
|
||||||
}
|
}
|
||||||
runLibeufinApiSandboxTransactionsTest.suites = ["libeufin"];
|
runLibeufinApiSandboxTransactionsTest.suites = ["libeufin"];
|
||||||
|
@ -54,19 +54,19 @@ export async function runLibeufinApiSchedulingTest(t: GlobalTestState) {
|
|||||||
rangeType: "all",
|
rangeType: "all",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let resp = await LibeufinNexusApi.getTasks(
|
let resp = await LibeufinNexusApi.getTask(
|
||||||
nexus,
|
nexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
"test-task",
|
"test-task",
|
||||||
);
|
);
|
||||||
t.assertTrue(resp.data["taskName"] == "test-task");
|
t.assertTrue(resp.taskName == "test-task");
|
||||||
await LibeufinNexusApi.deleteTask(
|
await LibeufinNexusApi.deleteTask(
|
||||||
nexus,
|
nexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
"test-task",
|
"test-task",
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await LibeufinNexusApi.getTasks(
|
await LibeufinNexusApi.getTask(
|
||||||
nexus,
|
nexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
"test-task",
|
"test-task",
|
||||||
@ -82,19 +82,19 @@ export async function runLibeufinApiSchedulingTest(t: GlobalTestState) {
|
|||||||
type: "submit",
|
type: "submit",
|
||||||
params: {},
|
params: {},
|
||||||
});
|
});
|
||||||
resp = await LibeufinNexusApi.getTasks(
|
resp = await LibeufinNexusApi.getTask(
|
||||||
nexus,
|
nexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
"test-task",
|
"test-task",
|
||||||
);
|
);
|
||||||
t.assertTrue(resp.data["taskName"] == "test-task");
|
t.assertTrue(resp.taskName == "test-task");
|
||||||
await LibeufinNexusApi.deleteTask(
|
await LibeufinNexusApi.deleteTask(
|
||||||
nexus,
|
nexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
"test-task",
|
"test-task",
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
await LibeufinNexusApi.getTasks(
|
await LibeufinNexusApi.getTask(
|
||||||
nexus,
|
nexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
"test-task",
|
"test-task",
|
||||||
|
@ -56,8 +56,8 @@ export async function runLibeufinApiUsersTest(t: GlobalTestState) {
|
|||||||
password: "got-changed",
|
password: "got-changed",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(resp.data);
|
console.log(resp);
|
||||||
t.assertTrue(resp.data["username"] == "one" && !resp.data["superuser"]);
|
t.assertTrue(resp["username"] == "one" && !resp["superuser"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
runLibeufinApiUsersTest.suites = ["libeufin"];
|
runLibeufinApiUsersTest.suites = ["libeufin"];
|
||||||
|
@ -79,7 +79,7 @@ export async function runLibeufinC5xTest(t: GlobalTestState) {
|
|||||||
libeufinServices.libeufinNexus,
|
libeufinServices.libeufinNexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
);
|
);
|
||||||
t.assertTrue(nexusTxs.data["transactions"].length == 0);
|
t.assertTrue(nexusTxs["transactions"].length == 0);
|
||||||
|
|
||||||
// Addressing one payment to user 01
|
// Addressing one payment to user 01
|
||||||
await libeufinServices.libeufinSandbox.makeTransaction(
|
await libeufinServices.libeufinSandbox.makeTransaction(
|
||||||
@ -95,8 +95,8 @@ export async function runLibeufinC5xTest(t: GlobalTestState) {
|
|||||||
"all", // range
|
"all", // range
|
||||||
"report", // C52
|
"report", // C52
|
||||||
);
|
);
|
||||||
t.assertTrue(expectOne.data.newTransactions == 1);
|
t.assertTrue(expectOne.newTransactions == 1);
|
||||||
t.assertTrue(expectOne.data.downloadedTransactions == 1);
|
t.assertTrue(expectOne.downloadedTransactions == 1);
|
||||||
|
|
||||||
/* Expect zero payments being downloaded because the
|
/* Expect zero payments being downloaded because the
|
||||||
* previous request consumed already the one pending
|
* previous request consumed already the one pending
|
||||||
@ -108,8 +108,8 @@ export async function runLibeufinC5xTest(t: GlobalTestState) {
|
|||||||
"all", // range
|
"all", // range
|
||||||
"report", // C52
|
"report", // C52
|
||||||
);
|
);
|
||||||
t.assertTrue(expectZero.data.newTransactions == 0);
|
t.assertTrue(expectZero.newTransactions == 0);
|
||||||
t.assertTrue(expectZero.data.downloadedTransactions == 0);
|
t.assertTrue(expectZero.downloadedTransactions == 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A statement should still account zero payments because
|
* A statement should still account zero payments because
|
||||||
@ -121,8 +121,8 @@ export async function runLibeufinC5xTest(t: GlobalTestState) {
|
|||||||
"all", // range
|
"all", // range
|
||||||
"statement", // C53
|
"statement", // C53
|
||||||
);
|
);
|
||||||
t.assertTrue(expectZero.data.newTransactions == 0);
|
t.assertTrue(expectZero.newTransactions == 0);
|
||||||
t.assertTrue(expectZero.data.downloadedTransactions == 0);
|
t.assertTrue(expectZero.downloadedTransactions == 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ticking now. That books any pending transaction.
|
* Ticking now. That books any pending transaction.
|
||||||
@ -141,7 +141,7 @@ export async function runLibeufinC5xTest(t: GlobalTestState) {
|
|||||||
"all", // range
|
"all", // range
|
||||||
"statement", // C53
|
"statement", // C53
|
||||||
);
|
);
|
||||||
t.assertTrue(expectOne.data.downloadedTransactions == 1);
|
t.assertTrue(expectOne.downloadedTransactions == 1);
|
||||||
t.assertTrue(expectOne.data.newTransactions == 0);
|
t.assertTrue(expectOne.newTransactions == 0);
|
||||||
}
|
}
|
||||||
runLibeufinC5xTest.suites = ["libeufin"];
|
runLibeufinC5xTest.suites = ["libeufin"];
|
||||||
|
@ -53,9 +53,9 @@ export async function runLibeufinAnastasisFacadeTest(t: GlobalTestState) {
|
|||||||
);
|
);
|
||||||
// check that original facade shows up.
|
// check that original facade shows up.
|
||||||
t.assertTrue(
|
t.assertTrue(
|
||||||
resp.data["facades"][0]["name"] == user01nexus.anastasisReq["name"],
|
resp["facades"][0]["name"] == user01nexus.anastasisReq["name"],
|
||||||
);
|
);
|
||||||
const anastasisBaseUrl: string = resp.data["facades"][0]["baseUrl"];
|
const anastasisBaseUrl: string = resp["facades"][0]["baseUrl"];
|
||||||
t.assertTrue(typeof anastasisBaseUrl === "string");
|
t.assertTrue(typeof anastasisBaseUrl === "string");
|
||||||
t.assertTrue(anastasisBaseUrl.startsWith("http://"));
|
t.assertTrue(anastasisBaseUrl.startsWith("http://"));
|
||||||
t.assertTrue(anastasisBaseUrl.endsWith("/"));
|
t.assertTrue(anastasisBaseUrl.endsWith("/"));
|
||||||
|
@ -77,7 +77,7 @@ export async function runLibeufinRefundTest(t: GlobalTestState) {
|
|||||||
libeufinServices.libeufinNexus,
|
libeufinServices.libeufinNexus,
|
||||||
user01nexus.localAccountName,
|
user01nexus.localAccountName,
|
||||||
);
|
);
|
||||||
t.assertTrue(nexusTxs.data["transactions"].length == 1);
|
t.assertTrue(nexusTxs["transactions"].length == 1);
|
||||||
|
|
||||||
// Submit the reimbursement
|
// Submit the reimbursement
|
||||||
await LibeufinNexusApi.submitInitiatedPayment(
|
await LibeufinNexusApi.submitInitiatedPayment(
|
||||||
|
@ -79,7 +79,7 @@ export async function runLibeufinSandboxWireTransferCliTest(
|
|||||||
sandbox,
|
sandbox,
|
||||||
"mock-account-2",
|
"mock-account-2",
|
||||||
);
|
);
|
||||||
console.log(ret.data.balance);
|
console.log(ret.balance);
|
||||||
t.assertTrue(ret.data.balance == "EUR:1.89");
|
t.assertTrue(ret.balance == "EUR:1.89");
|
||||||
}
|
}
|
||||||
runLibeufinSandboxWireTransferCliTest.suites = ["libeufin"];
|
runLibeufinSandboxWireTransferCliTest.suites = ["libeufin"];
|
||||||
|
@ -23,8 +23,6 @@ import {
|
|||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import axiosImp from "axios";
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import { defaultCoinConfig } from "../harness/denomStructures.js";
|
import { defaultCoinConfig } from "../harness/denomStructures.js";
|
||||||
import {
|
import {
|
||||||
@ -36,6 +34,7 @@ import {
|
|||||||
ExchangeService,
|
ExchangeService,
|
||||||
getPayto,
|
getPayto,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
|
harnessHttpLib,
|
||||||
MerchantPrivateApi,
|
MerchantPrivateApi,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
@ -45,6 +44,7 @@ import {
|
|||||||
FaultyMerchantTestEnvironment,
|
FaultyMerchantTestEnvironment,
|
||||||
withdrawViaBank,
|
withdrawViaBank,
|
||||||
} from "../harness/helpers.js";
|
} from "../harness/helpers.js";
|
||||||
|
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a test case with a simple TESTKUDOS Taler environment, consisting
|
* Run a test case with a simple TESTKUDOS Taler environment, consisting
|
||||||
@ -186,9 +186,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) {
|
|||||||
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
||||||
let publicOrderStatusUrl = orderStatus.order_status_url;
|
let publicOrderStatusUrl = orderStatus.order_status_url;
|
||||||
|
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -197,7 +195,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
await publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(pubUnpaidStatus);
|
console.log(pubUnpaidStatus);
|
||||||
@ -221,9 +219,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
console.log("requesting", orderUrlWithHash.href);
|
console.log("requesting", orderUrlWithHash.href);
|
||||||
|
|
||||||
publicOrderStatusResp = await axios.get(orderUrlWithHash.href, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(orderUrlWithHash.href);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -232,7 +228,7 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
await publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const confirmPayRes = await wallet.client.call(
|
const confirmPayRes = await wallet.client.call(
|
||||||
|
@ -17,9 +17,7 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { URL } from "@gnu-taler/taler-util";
|
import { TalerError, URL } from "@gnu-taler/taler-util";
|
||||||
import axiosImp from "axios";
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
import {
|
import {
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
@ -27,7 +25,9 @@ import {
|
|||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
getPayto,
|
getPayto,
|
||||||
|
harnessHttpLib,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test instance deletion and authentication for it
|
* Test instance deletion and authentication for it
|
||||||
@ -61,15 +61,17 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
|
|||||||
const baseUrl = merchant.makeInstanceBaseUrl();
|
const baseUrl = merchant.makeInstanceBaseUrl();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = await axios.get(new URL("config", baseUrl).href);
|
const r = await harnessHttpLib.fetch(new URL("config", baseUrl).href);
|
||||||
console.log(r.data);
|
const data = await r.json();
|
||||||
t.assertDeepEqual(r.data.currency, "TESTKUDOS");
|
console.log(data);
|
||||||
|
t.assertDeepEqual(data.currency, "TESTKUDOS");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instances should initially be empty
|
// Instances should initially be empty
|
||||||
{
|
{
|
||||||
const r = await axios.get(new URL("management/instances", baseUrl).href);
|
const r = await harnessHttpLib.fetch(new URL("management/instances", baseUrl).href);
|
||||||
t.assertDeepEqual(r.data.instances, []);
|
const data = await r.json();
|
||||||
|
t.assertDeepEqual(data.instances, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an instance, no auth!
|
// Add an instance, no auth!
|
||||||
@ -121,8 +123,8 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
|
|||||||
await unauthMerchantClient.deleteInstance("myinst");
|
await unauthMerchantClient.deleteInstance("myinst");
|
||||||
});
|
});
|
||||||
console.log("Got expected exception", exc);
|
console.log("Got expected exception", exc);
|
||||||
t.assertAxiosError(exc);
|
t.assertTrue(exc instanceof TalerError);
|
||||||
t.assertDeepEqual(exc.response?.status, 401);
|
t.assertDeepEqual(exc.errorDetail.httpStatusCode, 401);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,6 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { Duration } from "@gnu-taler/taler-util";
|
import { Duration } from "@gnu-taler/taler-util";
|
||||||
import axiosImp from "axios";
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
import {
|
import {
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
@ -27,14 +25,14 @@ import {
|
|||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
getPayto,
|
getPayto,
|
||||||
|
harnessHttpLib,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do basic checks on instance management and authentication.
|
* Do basic checks on instance management and authentication.
|
||||||
*/
|
*/
|
||||||
export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {
|
export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {
|
||||||
// Set up test environment
|
|
||||||
|
|
||||||
const db = await setupDb(t);
|
const db = await setupDb(t);
|
||||||
|
|
||||||
const exchange = ExchangeService.create(t, {
|
const exchange = ExchangeService.create(t, {
|
||||||
@ -111,11 +109,10 @@ export async function runMerchantInstancesUrlsTest(t: GlobalTestState) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function check(url: string, token: string, expectedStatus: number) {
|
async function check(url: string, token: string, expectedStatus: number) {
|
||||||
const resp = await axios.get(url, {
|
const resp = await harnessHttpLib.fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
});
|
||||||
console.log(
|
console.log(
|
||||||
`checking ${url}, expected ${expectedStatus}, got ${resp.status}`,
|
`checking ${url}, expected ${expectedStatus}, got ${resp.status}`,
|
||||||
|
@ -18,8 +18,6 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { URL } from "@gnu-taler/taler-util";
|
import { URL } from "@gnu-taler/taler-util";
|
||||||
import axiosImp from "axios";
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
import {
|
import {
|
||||||
ExchangeService,
|
ExchangeService,
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
@ -27,7 +25,9 @@ import {
|
|||||||
MerchantService,
|
MerchantService,
|
||||||
setupDb,
|
setupDb,
|
||||||
getPayto,
|
getPayto,
|
||||||
|
harnessHttpLib,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do basic checks on instance management and authentication.
|
* Do basic checks on instance management and authentication.
|
||||||
@ -61,15 +61,19 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
const baseUrl = merchant.makeInstanceBaseUrl();
|
const baseUrl = merchant.makeInstanceBaseUrl();
|
||||||
|
|
||||||
{
|
{
|
||||||
const r = await axios.get(new URL("config", baseUrl).href);
|
const r = await harnessHttpLib.fetch(new URL("config", baseUrl).href);
|
||||||
console.log(r.data);
|
const data = await r.json();
|
||||||
t.assertDeepEqual(r.data.currency, "TESTKUDOS");
|
console.log(data);
|
||||||
|
t.assertDeepEqual(data.currency, "TESTKUDOS");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instances should initially be empty
|
// Instances should initially be empty
|
||||||
{
|
{
|
||||||
const r = await axios.get(new URL("management/instances", baseUrl).href);
|
const r = await harnessHttpLib.fetch(
|
||||||
t.assertDeepEqual(r.data.instances, []);
|
new URL("management/instances", baseUrl).href,
|
||||||
|
);
|
||||||
|
const data = await r.json();
|
||||||
|
t.assertDeepEqual(data.instances, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an instance, no auth!
|
// Add an instance, no auth!
|
||||||
@ -104,11 +108,14 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
// Check that a "malformed" bearer Authorization header gets ignored
|
// Check that a "malformed" bearer Authorization header gets ignored
|
||||||
{
|
{
|
||||||
const url = merchant.makeInstanceBaseUrl();
|
const url = merchant.makeInstanceBaseUrl();
|
||||||
const resp = await axios.get(new URL("management/instances", url).href, {
|
const resp = await harnessHttpLib.fetch(
|
||||||
|
new URL("management/instances", url).href,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "foo bar-baz",
|
Authorization: "foo bar-baz",
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
t.assertDeepEqual(resp.status, 200);
|
t.assertDeepEqual(resp.status, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,9 +137,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
console.log(exc);
|
console.log(exc);
|
||||||
|
t.assertTrue(exc.errorDetail.httpStatusCode === 401);
|
||||||
t.assertAxiosError(exc);
|
|
||||||
t.assertTrue(exc.response?.status === 401);
|
|
||||||
|
|
||||||
merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl(), {
|
merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl(), {
|
||||||
method: "token",
|
method: "token",
|
||||||
@ -145,12 +150,15 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
// Now, try some variations.
|
// Now, try some variations.
|
||||||
{
|
{
|
||||||
const url = merchant.makeInstanceBaseUrl();
|
const url = merchant.makeInstanceBaseUrl();
|
||||||
const resp = await axios.get(new URL("management/instances", url).href, {
|
const resp = await harnessHttpLib.fetch(
|
||||||
|
new URL("management/instances", url).href,
|
||||||
|
{
|
||||||
headers: {
|
headers: {
|
||||||
// Note the spaces
|
// Note the spaces
|
||||||
Authorization: "Bearer secret-token:foobar",
|
Authorization: "Bearer secret-token:foobar",
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
|
);
|
||||||
t.assertDeepEqual(resp.status, 200);
|
t.assertDeepEqual(resp.status, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +184,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
|
|||||||
await unauthMerchantClient.deleteInstance("myinst");
|
await unauthMerchantClient.deleteInstance("myinst");
|
||||||
});
|
});
|
||||||
console.log(exc);
|
console.log(exc);
|
||||||
t.assertAxiosError(exc);
|
t.assertTrue(exc.errorDetail.httpStatusCode === 401);
|
||||||
t.assertDeepEqual(exc.response?.status, 401);
|
t.assertDeepEqual(exc.response?.status, 401);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,20 +24,18 @@ import {
|
|||||||
codecForMerchantOrderStatusUnpaid,
|
codecForMerchantOrderStatusUnpaid,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import axiosImp from "axios";
|
import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js";
|
||||||
import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
|
|
||||||
import {
|
import {
|
||||||
createSimpleTestkudosEnvironmentV2,
|
createSimpleTestkudosEnvironmentV2,
|
||||||
withdrawViaBankV2
|
withdrawViaBankV2,
|
||||||
} from "../harness/helpers.js";
|
} from "../harness/helpers.js";
|
||||||
const axios = axiosImp.default;
|
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for basic, bank-integrated withdrawal.
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
*/
|
*/
|
||||||
export async function runMerchantLongpollingTest(t: GlobalTestState) {
|
export async function runMerchantLongpollingTest(t: GlobalTestState) {
|
||||||
// Set up test environment
|
// Set up test environment
|
||||||
|
|
||||||
const { walletClient, bank, exchange, merchant } =
|
const { walletClient, bank, exchange, merchant } =
|
||||||
await createSimpleTestkudosEnvironmentV2(t);
|
await createSimpleTestkudosEnvironmentV2(t);
|
||||||
|
|
||||||
@ -83,9 +81,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) {
|
|||||||
// First, request order status without longpolling
|
// First, request order status without longpolling
|
||||||
{
|
{
|
||||||
console.log("requesting", publicOrderStatusUrl.href);
|
console.log("requesting", publicOrderStatusUrl.href);
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -98,9 +94,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) {
|
|||||||
publicOrderStatusUrl.searchParams.set("timeout_ms", "500");
|
publicOrderStatusUrl.searchParams.set("timeout_ms", "500");
|
||||||
|
|
||||||
console.log("requesting", publicOrderStatusUrl.href);
|
console.log("requesting", publicOrderStatusUrl.href);
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -109,7 +103,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
await publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(pubUnpaidStatus);
|
console.log(pubUnpaidStatus);
|
||||||
@ -135,9 +129,7 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) {
|
|||||||
preparePayResp.contractTermsHash,
|
preparePayResp.contractTermsHash,
|
||||||
);
|
);
|
||||||
|
|
||||||
let publicOrderStatusPromise = axios.get(publicOrderStatusUrl.href, {
|
let publicOrderStatusPromise = harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
t.assertTrue(preparePayResp.status === PreparePayResultType.PaymentPossible);
|
t.assertTrue(preparePayResp.status === PreparePayResultType.PaymentPossible);
|
||||||
|
|
||||||
@ -152,15 +144,12 @@ export async function runMerchantLongpollingTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
await publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const confirmPayRes = await walletClient.call(
|
const confirmPayRes = await walletClient.call(WalletApiOperation.ConfirmPay, {
|
||||||
WalletApiOperation.ConfirmPay,
|
|
||||||
{
|
|
||||||
proposalId: proposalId,
|
proposalId: proposalId,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
|
t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,14 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
|
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
||||||
import {
|
import {
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
MerchantPrivateApi,
|
MerchantPrivateApi,
|
||||||
MerchantServiceInterface,
|
MerchantServiceInterface,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
ExchangeServiceInterface,
|
ExchangeServiceInterface,
|
||||||
|
harnessHttpLib,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
createSimpleTestkudosEnvironment,
|
createSimpleTestkudosEnvironment,
|
||||||
@ -34,8 +36,6 @@ import {
|
|||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
Duration,
|
Duration,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import axiosImp from "axios";
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
import {
|
import {
|
||||||
WalletApiOperation,
|
WalletApiOperation,
|
||||||
BankServiceHandle,
|
BankServiceHandle,
|
||||||
@ -136,23 +136,19 @@ async function testRefundApiWithFulfillmentUrl(
|
|||||||
preparePayResult.contractTermsHash,
|
preparePayResult.contractTermsHash,
|
||||||
);
|
);
|
||||||
|
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
validateStatus: () => true,
|
const respData = await publicOrderStatusResp.json();
|
||||||
});
|
|
||||||
console.log(publicOrderStatusResp.data);
|
|
||||||
t.assertTrue(publicOrderStatusResp.status === 200);
|
t.assertTrue(publicOrderStatusResp.status === 200);
|
||||||
t.assertAmountEquals(publicOrderStatusResp.data.refund_amount, "TESTKUDOS:5");
|
t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5");
|
||||||
|
|
||||||
publicOrderStatusUrl = new URL(
|
publicOrderStatusUrl = new URL(
|
||||||
`orders/${orderId}`,
|
`orders/${orderId}`,
|
||||||
merchant.makeInstanceBaseUrl(),
|
merchant.makeInstanceBaseUrl(),
|
||||||
);
|
);
|
||||||
console.log(`requesting order status via '${publicOrderStatusUrl.href}'`);
|
console.log(`requesting order status via '${publicOrderStatusUrl.href}'`);
|
||||||
publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
console.log(publicOrderStatusResp.status);
|
console.log(publicOrderStatusResp.status);
|
||||||
console.log(publicOrderStatusResp.data);
|
console.log(await publicOrderStatusResp.json());
|
||||||
// We didn't give any authentication, so we should get a fulfillment URL back
|
// We didn't give any authentication, so we should get a fulfillment URL back
|
||||||
t.assertTrue(publicOrderStatusResp.status === 403);
|
t.assertTrue(publicOrderStatusResp.status === 403);
|
||||||
}
|
}
|
||||||
@ -252,22 +248,20 @@ async function testRefundApiWithFulfillmentMessage(
|
|||||||
preparePayResult.contractTermsHash,
|
preparePayResult.contractTermsHash,
|
||||||
);
|
);
|
||||||
|
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
validateStatus: () => true,
|
let respData = await publicOrderStatusResp.json();
|
||||||
});
|
console.log(respData);
|
||||||
console.log(publicOrderStatusResp.data);
|
|
||||||
t.assertTrue(publicOrderStatusResp.status === 200);
|
t.assertTrue(publicOrderStatusResp.status === 200);
|
||||||
t.assertAmountEquals(publicOrderStatusResp.data.refund_amount, "TESTKUDOS:5");
|
t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5");
|
||||||
|
|
||||||
publicOrderStatusUrl = new URL(
|
publicOrderStatusUrl = new URL(
|
||||||
`orders/${orderId}`,
|
`orders/${orderId}`,
|
||||||
merchant.makeInstanceBaseUrl(),
|
merchant.makeInstanceBaseUrl(),
|
||||||
);
|
);
|
||||||
|
|
||||||
publicOrderStatusResp = await axios.get(publicOrderStatusUrl.href, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
validateStatus: () => true,
|
respData = await publicOrderStatusResp.json();
|
||||||
});
|
console.log(respData);
|
||||||
console.log(publicOrderStatusResp.data);
|
|
||||||
// We didn't give any authentication, so we should get a fulfillment URL back
|
// We didn't give any authentication, so we should get a fulfillment URL back
|
||||||
t.assertTrue(publicOrderStatusResp.status === 403);
|
t.assertTrue(publicOrderStatusResp.status === 403);
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import {
|
|||||||
encodeCrock,
|
encodeCrock,
|
||||||
getRandomBytes,
|
getRandomBytes,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import {
|
import {
|
||||||
BankService,
|
BankService,
|
||||||
@ -33,17 +32,13 @@ import {
|
|||||||
MerchantPrivateApi,
|
MerchantPrivateApi,
|
||||||
MerchantService,
|
MerchantService,
|
||||||
WalletCli,
|
WalletCli,
|
||||||
|
harnessHttpLib,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
createSimpleTestkudosEnvironment,
|
createSimpleTestkudosEnvironment,
|
||||||
withdrawViaBank,
|
withdrawViaBank,
|
||||||
} from "../harness/helpers.js";
|
} from "../harness/helpers.js";
|
||||||
|
|
||||||
const httpLib = createPlatformHttpLib({
|
|
||||||
allowHttp: true,
|
|
||||||
enableThrottling: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
merchant: MerchantService;
|
merchant: MerchantService;
|
||||||
merchantBaseUrl: string;
|
merchantBaseUrl: string;
|
||||||
@ -51,6 +46,8 @@ interface Context {
|
|||||||
exchange: ExchangeService;
|
exchange: ExchangeService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const httpLib = harnessHttpLib;
|
||||||
|
|
||||||
async function testWithClaimToken(
|
async function testWithClaimToken(
|
||||||
t: GlobalTestState,
|
t: GlobalTestState,
|
||||||
c: Context,
|
c: Context,
|
||||||
@ -75,7 +72,7 @@ async function testWithClaimToken(
|
|||||||
let talerPayUri: string;
|
let talerPayUri: string;
|
||||||
|
|
||||||
{
|
{
|
||||||
const httpResp = await httpLib.get(
|
const httpResp = await httpLib.fetch(
|
||||||
new URL(`orders/${orderId}`, merchantBaseUrl).href,
|
new URL(`orders/${orderId}`, merchantBaseUrl).href,
|
||||||
);
|
);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
@ -86,7 +83,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", claimToken);
|
url.searchParams.set("token", claimToken);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
console.log(r);
|
console.log(r);
|
||||||
@ -97,7 +94,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", claimToken);
|
url.searchParams.set("token", claimToken);
|
||||||
const httpResp = await httpLib.get(url.href, {
|
const httpResp = await httpLib.fetch(url.href, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "text/html",
|
Accept: "text/html",
|
||||||
},
|
},
|
||||||
@ -123,7 +120,7 @@ async function testWithClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const hcWrong = encodeCrock(getRandomBytes(64));
|
const hcWrong = encodeCrock(getRandomBytes(64));
|
||||||
url.searchParams.set("h_contract", hcWrong);
|
url.searchParams.set("h_contract", hcWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -134,7 +131,7 @@ async function testWithClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const ctWrong = encodeCrock(getRandomBytes(16));
|
const ctWrong = encodeCrock(getRandomBytes(16));
|
||||||
url.searchParams.set("token", ctWrong);
|
url.searchParams.set("token", ctWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -144,7 +141,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", claimToken);
|
url.searchParams.set("token", claimToken);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -154,7 +151,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("h_contract", contractTermsHash);
|
url.searchParams.set("h_contract", contractTermsHash);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -163,7 +160,7 @@ async function testWithClaimToken(
|
|||||||
// claimed, unpaid, access without credentials
|
// claimed, unpaid, access without credentials
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 202);
|
t.assertDeepEqual(httpResp.status, 202);
|
||||||
@ -181,7 +178,7 @@ async function testWithClaimToken(
|
|||||||
// paid, access without credentials
|
// paid, access without credentials
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 202);
|
t.assertDeepEqual(httpResp.status, 202);
|
||||||
@ -192,7 +189,7 @@ async function testWithClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const hcWrong = encodeCrock(getRandomBytes(64));
|
const hcWrong = encodeCrock(getRandomBytes(64));
|
||||||
url.searchParams.set("h_contract", hcWrong);
|
url.searchParams.set("h_contract", hcWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -203,7 +200,7 @@ async function testWithClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const ctWrong = encodeCrock(getRandomBytes(16));
|
const ctWrong = encodeCrock(getRandomBytes(16));
|
||||||
url.searchParams.set("token", ctWrong);
|
url.searchParams.set("token", ctWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -213,7 +210,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("h_contract", contractTermsHash);
|
url.searchParams.set("h_contract", contractTermsHash);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 200);
|
t.assertDeepEqual(httpResp.status, 200);
|
||||||
@ -223,7 +220,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", claimToken);
|
url.searchParams.set("token", claimToken);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 200);
|
t.assertDeepEqual(httpResp.status, 200);
|
||||||
@ -235,7 +232,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", claimToken);
|
url.searchParams.set("token", claimToken);
|
||||||
const httpResp = await httpLib.get(url.href, {
|
const httpResp = await httpLib.fetch(url.href, {
|
||||||
headers: { Accept: "text/html" },
|
headers: { Accept: "text/html" },
|
||||||
});
|
});
|
||||||
t.assertDeepEqual(httpResp.status, 200);
|
t.assertDeepEqual(httpResp.status, 200);
|
||||||
@ -272,7 +269,7 @@ async function testWithClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", apToken);
|
url.searchParams.set("token", apToken);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -283,7 +280,7 @@ async function testWithClaimToken(
|
|||||||
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", apToken);
|
url.searchParams.set("token", apToken);
|
||||||
url.searchParams.set("session_id", sessionId);
|
url.searchParams.set("session_id", sessionId);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -296,7 +293,7 @@ async function testWithClaimToken(
|
|||||||
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", apToken);
|
url.searchParams.set("token", apToken);
|
||||||
url.searchParams.set("session_id", sessionId);
|
url.searchParams.set("session_id", sessionId);
|
||||||
const httpResp = await httpLib.get(url.href, {
|
const httpResp = await httpLib.fetch(url.href, {
|
||||||
headers: { Accept: "text/html" },
|
headers: { Accept: "text/html" },
|
||||||
});
|
});
|
||||||
t.assertDeepEqual(httpResp.status, 302);
|
t.assertDeepEqual(httpResp.status, 302);
|
||||||
@ -329,7 +326,7 @@ async function testWithoutClaimToken(
|
|||||||
let talerPayUri: string;
|
let talerPayUri: string;
|
||||||
|
|
||||||
{
|
{
|
||||||
const httpResp = await httpLib.get(
|
const httpResp = await httpLib.fetch(
|
||||||
new URL(`orders/${orderId}`, merchantBaseUrl).href,
|
new URL(`orders/${orderId}`, merchantBaseUrl).href,
|
||||||
);
|
);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
@ -339,7 +336,7 @@ async function testWithoutClaimToken(
|
|||||||
|
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
console.log(r);
|
console.log(r);
|
||||||
@ -349,7 +346,7 @@ async function testWithoutClaimToken(
|
|||||||
|
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href, {
|
const httpResp = await httpLib.fetch(url.href, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "text/html",
|
Accept: "text/html",
|
||||||
},
|
},
|
||||||
@ -377,7 +374,7 @@ async function testWithoutClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const hcWrong = encodeCrock(getRandomBytes(64));
|
const hcWrong = encodeCrock(getRandomBytes(64));
|
||||||
url.searchParams.set("h_contract", hcWrong);
|
url.searchParams.set("h_contract", hcWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -388,7 +385,7 @@ async function testWithoutClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const ctWrong = encodeCrock(getRandomBytes(16));
|
const ctWrong = encodeCrock(getRandomBytes(16));
|
||||||
url.searchParams.set("token", ctWrong);
|
url.searchParams.set("token", ctWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -397,7 +394,7 @@ async function testWithoutClaimToken(
|
|||||||
// claimed, unpaid, no claim token
|
// claimed, unpaid, no claim token
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -407,7 +404,7 @@ async function testWithoutClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("h_contract", contractTermsHash);
|
url.searchParams.set("h_contract", contractTermsHash);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -416,7 +413,7 @@ async function testWithoutClaimToken(
|
|||||||
// claimed, unpaid, access without credentials
|
// claimed, unpaid, access without credentials
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
// No credentials, but the order doesn't require a claim token.
|
// No credentials, but the order doesn't require a claim token.
|
||||||
@ -437,7 +434,7 @@ async function testWithoutClaimToken(
|
|||||||
// paid, access without credentials
|
// paid, access without credentials
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 200);
|
t.assertDeepEqual(httpResp.status, 200);
|
||||||
@ -448,7 +445,7 @@ async function testWithoutClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const hcWrong = encodeCrock(getRandomBytes(64));
|
const hcWrong = encodeCrock(getRandomBytes(64));
|
||||||
url.searchParams.set("h_contract", hcWrong);
|
url.searchParams.set("h_contract", hcWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -459,7 +456,7 @@ async function testWithoutClaimToken(
|
|||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const ctWrong = encodeCrock(getRandomBytes(16));
|
const ctWrong = encodeCrock(getRandomBytes(16));
|
||||||
url.searchParams.set("token", ctWrong);
|
url.searchParams.set("token", ctWrong);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 403);
|
t.assertDeepEqual(httpResp.status, 403);
|
||||||
@ -469,7 +466,7 @@ async function testWithoutClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("h_contract", contractTermsHash);
|
url.searchParams.set("h_contract", contractTermsHash);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 200);
|
t.assertDeepEqual(httpResp.status, 200);
|
||||||
@ -478,7 +475,7 @@ async function testWithoutClaimToken(
|
|||||||
// paid, JSON
|
// paid, JSON
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 200);
|
t.assertDeepEqual(httpResp.status, 200);
|
||||||
@ -489,7 +486,7 @@ async function testWithoutClaimToken(
|
|||||||
// paid, HTML
|
// paid, HTML
|
||||||
{
|
{
|
||||||
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${orderId}`, merchantBaseUrl);
|
||||||
const httpResp = await httpLib.get(url.href, {
|
const httpResp = await httpLib.fetch(url.href, {
|
||||||
headers: { Accept: "text/html" },
|
headers: { Accept: "text/html" },
|
||||||
});
|
});
|
||||||
t.assertDeepEqual(httpResp.status, 200);
|
t.assertDeepEqual(httpResp.status, 200);
|
||||||
@ -526,7 +523,7 @@ async function testWithoutClaimToken(
|
|||||||
{
|
{
|
||||||
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", apToken);
|
url.searchParams.set("token", apToken);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -537,7 +534,7 @@ async function testWithoutClaimToken(
|
|||||||
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", apToken);
|
url.searchParams.set("token", apToken);
|
||||||
url.searchParams.set("session_id", sessionId);
|
url.searchParams.set("session_id", sessionId);
|
||||||
const httpResp = await httpLib.get(url.href);
|
const httpResp = await httpLib.fetch(url.href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(httpResp.status, 402);
|
t.assertDeepEqual(httpResp.status, 402);
|
||||||
@ -550,7 +547,7 @@ async function testWithoutClaimToken(
|
|||||||
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
const url = new URL(`orders/${apOrderId}`, merchantBaseUrl);
|
||||||
url.searchParams.set("token", apToken);
|
url.searchParams.set("token", apToken);
|
||||||
url.searchParams.set("session_id", sessionId);
|
url.searchParams.set("session_id", sessionId);
|
||||||
const httpResp = await httpLib.get(url.href, {
|
const httpResp = await httpLib.fetch(url.href, {
|
||||||
headers: { Accept: "text/html" },
|
headers: { Accept: "text/html" },
|
||||||
});
|
});
|
||||||
t.assertDeepEqual(httpResp.status, 302);
|
t.assertDeepEqual(httpResp.status, 302);
|
||||||
@ -575,14 +572,14 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) {
|
|||||||
const merchantBaseUrl = merchant.makeInstanceBaseUrl();
|
const merchantBaseUrl = merchant.makeInstanceBaseUrl();
|
||||||
|
|
||||||
{
|
{
|
||||||
const httpResp = await httpLib.get(new URL("config", merchantBaseUrl).href);
|
const httpResp = await httpLib.fetch(new URL("config", merchantBaseUrl).href);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
console.log(r);
|
console.log(r);
|
||||||
t.assertDeepEqual(r.currency, "TESTKUDOS");
|
t.assertDeepEqual(r.currency, "TESTKUDOS");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const httpResp = await httpLib.get(
|
const httpResp = await httpLib.fetch(
|
||||||
new URL("orders/foo", merchantBaseUrl).href,
|
new URL("orders/foo", merchantBaseUrl).href,
|
||||||
);
|
);
|
||||||
const r = await httpResp.json();
|
const r = await httpResp.json();
|
||||||
@ -592,7 +589,7 @@ export async function runMerchantSpecPublicOrdersTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const httpResp = await httpLib.get(
|
const httpResp = await httpLib.fetch(
|
||||||
new URL("orders/foo", merchantBaseUrl).href,
|
new URL("orders/foo", merchantBaseUrl).href,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
|
import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
withdrawViaBank,
|
withdrawViaBank,
|
||||||
createFaultInjectedMerchantTestkudosEnvironment,
|
createFaultInjectedMerchantTestkudosEnvironment,
|
||||||
@ -28,8 +28,6 @@ import {
|
|||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
URL,
|
URL,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import axiosImp from "axios";
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
import { FaultInjectionRequestContext } from "../harness/faultInjection.js";
|
import { FaultInjectionRequestContext } from "../harness/faultInjection.js";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
|
|
||||||
@ -86,9 +84,7 @@ export async function runPayPaidTest(t: GlobalTestState) {
|
|||||||
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
||||||
let publicOrderStatusUrl = orderStatus.order_status_url;
|
let publicOrderStatusUrl = orderStatus.order_status_url;
|
||||||
|
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -97,7 +93,7 @@ export async function runPayPaidTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(pubUnpaidStatus);
|
console.log(pubUnpaidStatus);
|
||||||
@ -113,9 +109,7 @@ export async function runPayPaidTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
const proposalId = preparePayResp.proposalId;
|
const proposalId = preparePayResp.proposalId;
|
||||||
|
|
||||||
publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -124,7 +118,7 @@ export async function runPayPaidTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const confirmPayRes = await wallet.client.call(
|
const confirmPayRes = await wallet.client.call(
|
||||||
@ -136,14 +130,12 @@ export async function runPayPaidTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
|
t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
|
||||||
|
|
||||||
publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(publicOrderStatusResp.data);
|
console.log(publicOrderStatusResp.json());
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 200) {
|
if (publicOrderStatusResp.status != 200) {
|
||||||
console.log(publicOrderStatusResp.data);
|
console.log(publicOrderStatusResp.json());
|
||||||
throw Error(
|
throw Error(
|
||||||
`expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,
|
`expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,
|
||||||
);
|
);
|
||||||
|
@ -17,14 +17,12 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
|
import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
withdrawViaBank,
|
withdrawViaBank,
|
||||||
createFaultInjectedMerchantTestkudosEnvironment,
|
createFaultInjectedMerchantTestkudosEnvironment,
|
||||||
} from "../harness/helpers.js";
|
} from "../harness/helpers.js";
|
||||||
import {
|
import { FaultInjectionRequestContext } from "../harness/faultInjection.js";
|
||||||
FaultInjectionRequestContext,
|
|
||||||
} from "../harness/faultInjection.js";
|
|
||||||
import {
|
import {
|
||||||
codecForMerchantOrderStatusUnpaid,
|
codecForMerchantOrderStatusUnpaid,
|
||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
@ -35,9 +33,6 @@ import {
|
|||||||
URL,
|
URL,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import axiosImp from "axios";
|
|
||||||
|
|
||||||
const axios = axiosImp.default;
|
|
||||||
|
|
||||||
export async function runPaymentAbortTest(t: GlobalTestState) {
|
export async function runPaymentAbortTest(t: GlobalTestState) {
|
||||||
// Set up test environment
|
// Set up test environment
|
||||||
@ -75,9 +70,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) {
|
|||||||
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
||||||
let publicOrderStatusUrl = orderStatus.order_status_url;
|
let publicOrderStatusUrl = orderStatus.order_status_url;
|
||||||
|
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -86,7 +79,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(pubUnpaidStatus);
|
console.log(pubUnpaidStatus);
|
||||||
@ -102,9 +95,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
const proposalId = preparePayResp.proposalId;
|
const proposalId = preparePayResp.proposalId;
|
||||||
|
|
||||||
publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -113,7 +104,7 @@ export async function runPaymentAbortTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
faultyMerchant.faultProxy.addFault({
|
faultyMerchant.faultProxy.addFault({
|
||||||
|
@ -42,13 +42,15 @@ export async function runPaymentClaimTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
await withdrawViaBankV2(t, {
|
const wres = await withdrawViaBankV2(t, {
|
||||||
walletClient,
|
walletClient,
|
||||||
bank,
|
bank,
|
||||||
exchange,
|
exchange,
|
||||||
amount: "TESTKUDOS:20",
|
amount: "TESTKUDOS:20",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await wres.withdrawalFinishedCond;
|
||||||
|
|
||||||
// Set up order.
|
// Set up order.
|
||||||
|
|
||||||
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
||||||
|
@ -37,13 +37,15 @@ export async function runPaymentIdempotencyTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
await withdrawViaBankV2(t, {
|
const wres = await withdrawViaBankV2(t, {
|
||||||
walletClient,
|
walletClient,
|
||||||
bank,
|
bank,
|
||||||
exchange,
|
exchange,
|
||||||
amount: "TESTKUDOS:20",
|
amount: "TESTKUDOS:20",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await wres.withdrawalFinishedCond;
|
||||||
|
|
||||||
// Set up order.
|
// Set up order.
|
||||||
|
|
||||||
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
||||||
|
@ -50,7 +50,8 @@ export async function runPaymentTemplateTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20" });
|
const wres = await withdrawViaBankV2(t, { walletClient, bank, exchange, amount: "TESTKUDOS:20" });
|
||||||
|
await wres.withdrawalFinishedCond;
|
||||||
|
|
||||||
// Request a template payment
|
// Request a template payment
|
||||||
|
|
||||||
|
@ -17,23 +17,22 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
|
|
||||||
import {
|
import {
|
||||||
withdrawViaBank,
|
|
||||||
createFaultInjectedMerchantTestkudosEnvironment,
|
|
||||||
} from "../harness/helpers.js";
|
|
||||||
import { FaultInjectionResponseContext } from "../harness/faultInjection.js";
|
|
||||||
import {
|
|
||||||
codecForMerchantOrderStatusUnpaid,
|
|
||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
TalerErrorCode,
|
TalerErrorCode,
|
||||||
TalerErrorDetail,
|
TalerErrorDetail,
|
||||||
URL,
|
URL,
|
||||||
|
codecForMerchantOrderStatusUnpaid,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import axiosImp from "axios";
|
import { FaultInjectionResponseContext } from "../harness/faultInjection.js";
|
||||||
const axios = axiosImp.default;
|
import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js";
|
||||||
|
import {
|
||||||
|
createFaultInjectedMerchantTestkudosEnvironment,
|
||||||
|
withdrawViaBank,
|
||||||
|
} from "../harness/helpers.js";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for a payment where the merchant has a transient
|
* Run test for a payment where the merchant has a transient
|
||||||
@ -75,9 +74,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) {
|
|||||||
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
||||||
let publicOrderStatusUrl = orderStatus.order_status_url;
|
let publicOrderStatusUrl = orderStatus.order_status_url;
|
||||||
|
|
||||||
let publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -86,7 +83,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(pubUnpaidStatus);
|
console.log(pubUnpaidStatus);
|
||||||
@ -102,9 +99,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
const proposalId = preparePayResp.proposalId;
|
const proposalId = preparePayResp.proposalId;
|
||||||
|
|
||||||
publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -113,7 +108,7 @@ export async function runPaymentTransientTest(t: GlobalTestState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
|
||||||
publicOrderStatusResp.data,
|
publicOrderStatusResp.json(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let faultInjected = false;
|
let faultInjected = false;
|
||||||
@ -165,14 +160,12 @@ export async function runPaymentTransientTest(t: GlobalTestState) {
|
|||||||
// Now ask the merchant if paid
|
// Now ask the merchant if paid
|
||||||
|
|
||||||
console.log("requesting", publicOrderStatusUrl);
|
console.log("requesting", publicOrderStatusUrl);
|
||||||
publicOrderStatusResp = await axios.get(publicOrderStatusUrl, {
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);
|
||||||
validateStatus: () => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(publicOrderStatusResp.data);
|
console.log(publicOrderStatusResp.json());
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 200) {
|
if (publicOrderStatusResp.status != 200) {
|
||||||
console.log(publicOrderStatusResp.data);
|
console.log(publicOrderStatusResp.json());
|
||||||
throw Error(
|
throw Error(
|
||||||
`expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,
|
`expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,
|
||||||
);
|
);
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
|
import { GlobalTestState, MerchantPrivateApi, harnessHttpLib } from "../harness/harness.js";
|
||||||
import {
|
import {
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
codecForMerchantOrderStatusUnpaid,
|
codecForMerchantOrderStatusUnpaid,
|
||||||
@ -29,12 +29,6 @@ import {
|
|||||||
createSimpleTestkudosEnvironmentV2,
|
createSimpleTestkudosEnvironmentV2,
|
||||||
withdrawViaBankV2,
|
withdrawViaBankV2,
|
||||||
} from "../harness/helpers.js";
|
} from "../harness/helpers.js";
|
||||||
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
|
||||||
|
|
||||||
const httpLib = createPlatformHttpLib({
|
|
||||||
allowHttp: true,
|
|
||||||
enableThrottling: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run test for basic, bank-integrated withdrawal.
|
* Run test for basic, bank-integrated withdrawal.
|
||||||
@ -47,13 +41,15 @@ export async function runPaywallFlowTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
await withdrawViaBankV2(t, {
|
const wres = await withdrawViaBankV2(t, {
|
||||||
walletClient,
|
walletClient,
|
||||||
bank,
|
bank,
|
||||||
exchange,
|
exchange,
|
||||||
amount: "TESTKUDOS:20",
|
amount: "TESTKUDOS:20",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await wres.withdrawalFinishedCond;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* =========================================================================
|
* =========================================================================
|
||||||
* Create an order and let the wallet pay under a session ID
|
* Create an order and let the wallet pay under a session ID
|
||||||
@ -86,7 +82,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) {
|
|||||||
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
t.assertTrue(orderStatus.already_paid_order_id === undefined);
|
||||||
let publicOrderStatusUrl = new URL(orderStatus.order_status_url);
|
let publicOrderStatusUrl = new URL(orderStatus.order_status_url);
|
||||||
|
|
||||||
let publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href);
|
let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -112,7 +108,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) {
|
|||||||
const proposalId = preparePayResp.proposalId;
|
const proposalId = preparePayResp.proposalId;
|
||||||
|
|
||||||
console.log("requesting", publicOrderStatusUrl.href);
|
console.log("requesting", publicOrderStatusUrl.href);
|
||||||
publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href);
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
console.log("response body", publicOrderStatusResp.json());
|
console.log("response body", publicOrderStatusResp.json());
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(
|
throw Error(
|
||||||
@ -129,7 +125,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
|
t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
|
||||||
publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href);
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
|
|
||||||
console.log(publicOrderStatusResp.json());
|
console.log(publicOrderStatusResp.json());
|
||||||
|
|
||||||
@ -231,7 +227,7 @@ export async function runPaywallFlowTest(t: GlobalTestState) {
|
|||||||
console.log("requesting public status", publicOrderStatusUrl);
|
console.log("requesting public status", publicOrderStatusUrl);
|
||||||
|
|
||||||
// Ask the order status of the claimed-but-unpaid order
|
// Ask the order status of the claimed-but-unpaid order
|
||||||
publicOrderStatusResp = await httpLib.fetch(publicOrderStatusUrl.href);
|
publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
|
||||||
|
|
||||||
if (publicOrderStatusResp.status != 402) {
|
if (publicOrderStatusResp.status != 402) {
|
||||||
throw Error(`expected status 402, but got ${publicOrderStatusResp.status}`);
|
throw Error(`expected status 402, but got ${publicOrderStatusResp.status}`);
|
||||||
|
@ -36,13 +36,15 @@ export async function runRefundAutoTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
await withdrawViaBankV2(t, {
|
const wres = await withdrawViaBankV2(t, {
|
||||||
walletClient,
|
walletClient,
|
||||||
bank,
|
bank,
|
||||||
exchange,
|
exchange,
|
||||||
amount: "TESTKUDOS:20",
|
amount: "TESTKUDOS:20",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await wres.withdrawalFinishedCond;
|
||||||
|
|
||||||
// Set up order.
|
// Set up order.
|
||||||
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
||||||
order: {
|
order: {
|
||||||
|
@ -45,13 +45,15 @@ export async function runRefundIncrementalTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
await withdrawViaBankV2(t, {
|
const wres = await withdrawViaBankV2(t, {
|
||||||
walletClient,
|
walletClient,
|
||||||
bank,
|
bank,
|
||||||
exchange,
|
exchange,
|
||||||
amount: "TESTKUDOS:20",
|
amount: "TESTKUDOS:20",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await wres.withdrawalFinishedCond;
|
||||||
|
|
||||||
// Set up order.
|
// Set up order.
|
||||||
|
|
||||||
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
|
||||||
|
@ -17,22 +17,11 @@
|
|||||||
/**
|
/**
|
||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
import { Amounts, Duration, PreparePayResultType } from "@gnu-taler/taler-util";
|
import { Amounts, PreparePayResultType } from "@gnu-taler/taler-util";
|
||||||
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
|
||||||
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
|
import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js";
|
||||||
import {
|
|
||||||
ExchangeService,
|
|
||||||
FakebankService,
|
|
||||||
getRandomIban,
|
|
||||||
GlobalTestState,
|
|
||||||
MerchantPrivateApi,
|
|
||||||
MerchantService,
|
|
||||||
setupDb,
|
|
||||||
WalletCli,
|
|
||||||
} from "../harness/harness.js";
|
|
||||||
import {
|
import {
|
||||||
createSimpleTestkudosEnvironmentV2,
|
createSimpleTestkudosEnvironmentV2,
|
||||||
withdrawViaBank,
|
|
||||||
withdrawViaBankV2,
|
withdrawViaBankV2,
|
||||||
} from "../harness/helpers.js";
|
} from "../harness/helpers.js";
|
||||||
|
|
||||||
@ -50,13 +39,15 @@ export async function runWalletBalanceTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
// Withdraw digital cash into the wallet.
|
// Withdraw digital cash into the wallet.
|
||||||
|
|
||||||
await withdrawViaBankV2(t, {
|
const wres = await withdrawViaBankV2(t, {
|
||||||
walletClient,
|
walletClient,
|
||||||
bank,
|
bank,
|
||||||
exchange,
|
exchange,
|
||||||
amount: "TESTKUDOS:20",
|
amount: "TESTKUDOS:20",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await wres.withdrawalFinishedCond;
|
||||||
|
|
||||||
const order = {
|
const order = {
|
||||||
summary: "Buy me!",
|
summary: "Buy me!",
|
||||||
amount: "TESTKUDOS:5",
|
amount: "TESTKUDOS:5",
|
||||||
|
@ -23,7 +23,6 @@ import {
|
|||||||
j2s,
|
j2s,
|
||||||
TalerError,
|
TalerError,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
|
|
||||||
import {
|
import {
|
||||||
checkReserve,
|
checkReserve,
|
||||||
CryptoDispatcher,
|
CryptoDispatcher,
|
||||||
@ -36,7 +35,7 @@ import {
|
|||||||
Wallet,
|
Wallet,
|
||||||
withdrawCoin,
|
withdrawCoin,
|
||||||
} from "@gnu-taler/taler-wallet-core";
|
} from "@gnu-taler/taler-wallet-core";
|
||||||
import { GlobalTestState } from "../harness/harness.js";
|
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
|
||||||
import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
|
import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,10 +46,7 @@ export async function runWalletDblessTest(t: GlobalTestState) {
|
|||||||
|
|
||||||
const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
|
const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
|
||||||
|
|
||||||
const http = createPlatformHttpLib({
|
const http = harnessHttpLib;
|
||||||
allowHttp: true,
|
|
||||||
enableThrottling: false,
|
|
||||||
});
|
|
||||||
const cryptiDisp = new CryptoDispatcher(
|
const cryptiDisp = new CryptoDispatcher(
|
||||||
new SynchronousCryptoWorkerFactoryPlain(),
|
new SynchronousCryptoWorkerFactoryPlain(),
|
||||||
);
|
);
|
||||||
|
@ -14,7 +14,12 @@
|
|||||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CancellationToken, Logger, minimatch, setGlobalLogLevelFromString } from "@gnu-taler/taler-util";
|
import {
|
||||||
|
CancellationToken,
|
||||||
|
Logger,
|
||||||
|
minimatch,
|
||||||
|
setGlobalLogLevelFromString,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
@ -22,10 +27,12 @@ import * as path from "path";
|
|||||||
import url from "url";
|
import url from "url";
|
||||||
import {
|
import {
|
||||||
GlobalTestState,
|
GlobalTestState,
|
||||||
|
runCommand,
|
||||||
runTestWithState,
|
runTestWithState,
|
||||||
shouldLingerInTest,
|
shouldLingerInTest,
|
||||||
TestRunResult,
|
TestRunResult,
|
||||||
} from "../harness/harness.js";
|
} from "../harness/harness.js";
|
||||||
|
import { spawnSync } from "child_process";
|
||||||
import { runAgeRestrictionsMerchantTest } from "./test-age-restrictions-merchant.js";
|
import { runAgeRestrictionsMerchantTest } from "./test-age-restrictions-merchant.js";
|
||||||
import { runBankApiTest } from "./test-bank-api.js";
|
import { runBankApiTest } from "./test-bank-api.js";
|
||||||
import { runClaimLoopTest } from "./test-claim-loop.js";
|
import { runClaimLoopTest } from "./test-claim-loop.js";
|
||||||
@ -105,6 +112,8 @@ import { runPeerRepairTest } from "./test-peer-repair.js";
|
|||||||
import { runPaymentShareTest } from "./test-payment-share.js";
|
import { runPaymentShareTest } from "./test-payment-share.js";
|
||||||
import { runSimplePaymentTest } from "./test-simple-payment.js";
|
import { runSimplePaymentTest } from "./test-simple-payment.js";
|
||||||
import { runTermOfServiceFormatTest } from "./test-tos-format.js";
|
import { runTermOfServiceFormatTest } from "./test-tos-format.js";
|
||||||
|
import { runExchangePurseTest } from "./test-exchange-purse.js";
|
||||||
|
import { getSharedTestDir } from "../harness/helpers.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test runner.
|
* Test runner.
|
||||||
@ -137,6 +146,7 @@ const allTests: TestMainFunction[] = [
|
|||||||
runFeeRegressionTest,
|
runFeeRegressionTest,
|
||||||
runForcedSelectionTest,
|
runForcedSelectionTest,
|
||||||
runKycTest,
|
runKycTest,
|
||||||
|
runExchangePurseTest,
|
||||||
runExchangeDepositTest,
|
runExchangeDepositTest,
|
||||||
runLibeufinAnastasisFacadeTest,
|
runLibeufinAnastasisFacadeTest,
|
||||||
runLibeufinApiBankaccountTest,
|
runLibeufinApiBankaccountTest,
|
||||||
@ -257,7 +267,37 @@ interface RunTestChildInstruction {
|
|||||||
testRootDir: string;
|
testRootDir: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function purgeSharedTestEnvironment() {
|
||||||
|
const rmRes = spawnSync("rm", ["-rf", `${getSharedTestDir()}`]);
|
||||||
|
if (rmRes.status != 0) {
|
||||||
|
logger.warn("can't delete shared test directory");
|
||||||
|
}
|
||||||
|
const psqlRes = spawnSync("psql", ["-Aqtl"], {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
if (psqlRes.status != 0) {
|
||||||
|
logger.warn("could not list available postgres databases");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (psqlRes.output[1]!!.indexOf("taler-integrationtest-shared") >= 0) {
|
||||||
|
const dropRes = spawnSync("dropdb", ["taler-integrationtest-shared"], {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
if (dropRes.status != 0) {
|
||||||
|
logger.warn("could not drop taler-integrationtest-shared database");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function runTests(spec: TestRunSpec) {
|
export async function runTests(spec: TestRunSpec) {
|
||||||
|
if (!process.env.TALER_HARNESS_KEEP) {
|
||||||
|
logger.info("purging shared test environment");
|
||||||
|
purgeSharedTestEnvironment();
|
||||||
|
} else {
|
||||||
|
logger.info("keeping shared test environment");
|
||||||
|
}
|
||||||
|
|
||||||
const testRootDir = fs.mkdtempSync(
|
const testRootDir = fs.mkdtempSync(
|
||||||
path.join(os.tmpdir(), "taler-integrationtests-"),
|
path.join(os.tmpdir(), "taler-integrationtests-"),
|
||||||
);
|
);
|
||||||
|
@ -407,7 +407,7 @@ export async function checkExchangeHttpd(
|
|||||||
|
|
||||||
{
|
{
|
||||||
const mgmtUrl = new URL("management/keys", baseUrl);
|
const mgmtUrl = new URL("management/keys", baseUrl);
|
||||||
const resp = await httpLib.get(mgmtUrl.href);
|
const resp = await httpLib.fetch(mgmtUrl.href);
|
||||||
|
|
||||||
const futureKeys = await readSuccessResponseJsonOrThrow(
|
const futureKeys = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
@ -431,7 +431,7 @@ export async function checkExchangeHttpd(
|
|||||||
{
|
{
|
||||||
const keysUrl = new URL("keys", baseUrl);
|
const keysUrl = new URL("keys", baseUrl);
|
||||||
|
|
||||||
const resp = await Promise.race([httpLib.get(keysUrl.href), delayMs(2000)]);
|
const resp = await Promise.race([httpLib.fetch(keysUrl.href), delayMs(2000)]);
|
||||||
|
|
||||||
if (!resp) {
|
if (!resp) {
|
||||||
context.numErr++;
|
context.numErr++;
|
||||||
@ -467,7 +467,7 @@ export async function checkExchangeHttpd(
|
|||||||
{
|
{
|
||||||
const keysUrl = new URL("wire", baseUrl);
|
const keysUrl = new URL("wire", baseUrl);
|
||||||
|
|
||||||
const resp = await Promise.race([httpLib.get(keysUrl.href), delayMs(2000)]);
|
const resp = await Promise.race([httpLib.fetch(keysUrl.href), delayMs(2000)]);
|
||||||
|
|
||||||
if (!resp) {
|
if (!resp) {
|
||||||
context.numErr++;
|
context.numErr++;
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc --build",
|
"compile": "tsc",
|
||||||
"test": "tsc && ava",
|
"test": "tsc && ava",
|
||||||
"clean": "rimraf dist lib tsconfig.tsbuildinfo",
|
"clean": "rimraf dist lib tsconfig.tsbuildinfo",
|
||||||
"pretty": "prettier --write src"
|
"pretty": "prettier --write src"
|
||||||
|
@ -19,7 +19,12 @@
|
|||||||
import { CancellationToken } from "./CancellationToken.js";
|
import { CancellationToken } from "./CancellationToken.js";
|
||||||
import { Codec } from "./codec.js";
|
import { Codec } from "./codec.js";
|
||||||
import { j2s } from "./helpers.js";
|
import { j2s } from "./helpers.js";
|
||||||
import { TalerError, makeErrorDetail } from "./index.js";
|
import {
|
||||||
|
TalerError,
|
||||||
|
base64FromArrayBuffer,
|
||||||
|
makeErrorDetail,
|
||||||
|
stringToBytes,
|
||||||
|
} from "./index.js";
|
||||||
import { Logger } from "./logging.js";
|
import { Logger } from "./logging.js";
|
||||||
import { TalerErrorCode } from "./taler-error-codes.js";
|
import { TalerErrorCode } from "./taler-error-codes.js";
|
||||||
import { Duration, AbsoluteTime } from "./time.js";
|
import { Duration, AbsoluteTime } from "./time.js";
|
||||||
@ -306,6 +311,16 @@ export async function readSuccessResponseJsonOrThrow<T>(
|
|||||||
throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
|
throwUnexpectedRequestError(httpResponse, r.talerErrorResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectSuccessResponseOrThrow<T>(
|
||||||
|
httpResponse: HttpResponse,
|
||||||
|
): Promise<void> {
|
||||||
|
if (httpResponse.status >= 200 && httpResponse.status <= 299) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const errResp = await readTalerErrorResponse(httpResponse);
|
||||||
|
throwUnexpectedRequestError(httpResponse, errResp);
|
||||||
|
}
|
||||||
|
|
||||||
export async function readSuccessResponseTextOrErrorCode<T>(
|
export async function readSuccessResponseTextOrErrorCode<T>(
|
||||||
httpResponse: HttpResponse,
|
httpResponse: HttpResponse,
|
||||||
): Promise<ResponseOrError<string>> {
|
): Promise<ResponseOrError<string>> {
|
||||||
@ -452,3 +467,15 @@ export function getDefaultHeaders(method: string): Record<string, string> {
|
|||||||
|
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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}`;
|
||||||
|
}
|
||||||
|
@ -239,3 +239,25 @@ export function parsePaytoUri(s: string): PaytoUri | undefined {
|
|||||||
isKnown: false,
|
isKnown: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function talerPaytoFromExchangeReserve(
|
||||||
|
exchangeBaseUrl: string,
|
||||||
|
reservePub: string,
|
||||||
|
): string {
|
||||||
|
const url = new URL(exchangeBaseUrl);
|
||||||
|
let proto: string;
|
||||||
|
if (url.protocol === "http:") {
|
||||||
|
proto = "taler-reserve-http";
|
||||||
|
} else if (url.protocol === "https:") {
|
||||||
|
proto = "taler-reserve";
|
||||||
|
} else {
|
||||||
|
throw Error(`unsupported exchange base URL protocol (${url.protocol})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = url.pathname;
|
||||||
|
if (!path.endsWith("/")) {
|
||||||
|
path = path + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
|
||||||
|
}
|
||||||
|
@ -1004,7 +1004,7 @@ export enum TalerSignaturePurpose {
|
|||||||
SYNC_BACKUP_UPLOAD = 1450,
|
SYNC_BACKUP_UPLOAD = 1450,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum WalletAccountMergeFlags {
|
export enum WalletAccountMergeFlags {
|
||||||
/**
|
/**
|
||||||
* Not a legal mode!
|
* Not a legal mode!
|
||||||
*/
|
*/
|
||||||
@ -1260,7 +1260,8 @@ export namespace AgeRestriction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PublishedAgeRestrictionBaseKey: Edx25519PublicKey = decodeCrock(
|
const PublishedAgeRestrictionBaseKey: Edx25519PublicKey = decodeCrock(
|
||||||
"CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG");
|
"CH0VKFDZ2GWRWHQBBGEK9MWV5YDQVJ0RXEE0KYT3NMB69F0R96TG",
|
||||||
|
);
|
||||||
|
|
||||||
export async function restrictionCommitSeeded(
|
export async function restrictionCommitSeeded(
|
||||||
ageMask: number,
|
ageMask: number,
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
* Imports.
|
* Imports.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { codecForAmountString } from "./amounts.js";
|
import { Amounts, codecForAmountString } from "./amounts.js";
|
||||||
import {
|
import {
|
||||||
buildCodecForObject,
|
buildCodecForObject,
|
||||||
buildCodecForUnion,
|
buildCodecForUnion,
|
||||||
@ -719,16 +719,12 @@ export class ExchangeSignKeyJson {
|
|||||||
* Structure that the exchange gives us in /keys.
|
* Structure that the exchange gives us in /keys.
|
||||||
*/
|
*/
|
||||||
export class ExchangeKeysJson {
|
export class ExchangeKeysJson {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Canonical, public base URL of the exchange.
|
* Canonical, public base URL of the exchange.
|
||||||
*/
|
*/
|
||||||
base_url: string;
|
base_url: string;
|
||||||
|
|
||||||
/**
|
currency: string;
|
||||||
* List of offered denominations.
|
|
||||||
*/
|
|
||||||
denoms: ExchangeDenomination[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The exchange's master public key.
|
* The exchange's master public key.
|
||||||
@ -764,6 +760,111 @@ export class ExchangeKeysJson {
|
|||||||
reserve_closing_delay: TalerProtocolDuration;
|
reserve_closing_delay: TalerProtocolDuration;
|
||||||
|
|
||||||
global_fees: GlobalFees[];
|
global_fees: GlobalFees[];
|
||||||
|
|
||||||
|
accounts: AccountInfo[];
|
||||||
|
|
||||||
|
wire_fees: { [methodName: string]: WireFeesJson[] };
|
||||||
|
|
||||||
|
denominations: DenomGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DenomGroup =
|
||||||
|
| DenomGroupRsa
|
||||||
|
| DenomGroupCs
|
||||||
|
| DenomGroupRsaAgeRestricted
|
||||||
|
| DenomGroupCsAgeRestricted;
|
||||||
|
|
||||||
|
export interface DenomGroupCommon {
|
||||||
|
// How much are coins of this denomination worth?
|
||||||
|
value: AmountString;
|
||||||
|
|
||||||
|
// Fee charged by the exchange for withdrawing a coin of this denomination.
|
||||||
|
fee_withdraw: AmountString;
|
||||||
|
|
||||||
|
// Fee charged by the exchange for depositing a coin of this denomination.
|
||||||
|
fee_deposit: AmountString;
|
||||||
|
|
||||||
|
// Fee charged by the exchange for refreshing a coin of this denomination.
|
||||||
|
fee_refresh: AmountString;
|
||||||
|
|
||||||
|
// Fee charged by the exchange for refunding a coin of this denomination.
|
||||||
|
fee_refund: AmountString;
|
||||||
|
|
||||||
|
// XOR of all the SHA-512 hash values of the denominations' public keys
|
||||||
|
// in this group. Note that for hashing, the binary format of the
|
||||||
|
// public keys is used, and not their base32 encoding.
|
||||||
|
hash: HashCodeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DenomCommon {
|
||||||
|
// Signature of TALER_DenominationKeyValidityPS.
|
||||||
|
master_sig: EddsaSignatureString;
|
||||||
|
|
||||||
|
// When does the denomination key become valid?
|
||||||
|
stamp_start: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
// When is it no longer possible to deposit coins
|
||||||
|
// of this denomination?
|
||||||
|
stamp_expire_withdraw: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
// Timestamp indicating by when legal disputes relating to these coins must
|
||||||
|
// be settled, as the exchange will afterwards destroy its evidence relating to
|
||||||
|
// transactions involving this coin.
|
||||||
|
stamp_expire_legal: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
stamp_expire_deposit: TalerProtocolTimestamp;
|
||||||
|
|
||||||
|
// Set to 'true' if the exchange somehow "lost"
|
||||||
|
// the private key. The denomination was not
|
||||||
|
// necessarily revoked, but still cannot be used
|
||||||
|
// to withdraw coins at this time (theoretically,
|
||||||
|
// the private key could be recovered in the
|
||||||
|
// future; coins signed with the private key
|
||||||
|
// remain valid).
|
||||||
|
lost?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RsaPublicKeySring = string;
|
||||||
|
export type AgeMask = number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 32-byte value representing a point on Curve25519.
|
||||||
|
*/
|
||||||
|
export type Cs25519Point = string;
|
||||||
|
|
||||||
|
export interface DenomGroupRsa extends DenomGroupCommon {
|
||||||
|
cipher: "RSA";
|
||||||
|
|
||||||
|
denoms: ({
|
||||||
|
rsa_pub: RsaPublicKeySring;
|
||||||
|
} & DenomCommon)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DenomGroupRsaAgeRestricted extends DenomGroupCommon {
|
||||||
|
cipher: "RSA+age_restricted";
|
||||||
|
age_mask: AgeMask;
|
||||||
|
|
||||||
|
denoms: ({
|
||||||
|
rsa_pub: RsaPublicKeySring;
|
||||||
|
} & DenomCommon)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DenomGroupCs extends DenomGroupCommon {
|
||||||
|
cipher: "CS";
|
||||||
|
age_mask: AgeMask;
|
||||||
|
|
||||||
|
denoms: ({
|
||||||
|
cs_pub: Cs25519Point;
|
||||||
|
} & DenomCommon)[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DenomGroupCsAgeRestricted extends DenomGroupCommon {
|
||||||
|
cipher: "CS+age_restricted";
|
||||||
|
age_mask: AgeMask;
|
||||||
|
|
||||||
|
denoms: ({
|
||||||
|
cs_pub: Cs25519Point;
|
||||||
|
} & DenomCommon)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobalFees {
|
export interface GlobalFees {
|
||||||
@ -847,10 +948,10 @@ export interface AccountInfo {
|
|||||||
debit_restrictions?: any;
|
debit_restrictions?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeWireJson {
|
/**
|
||||||
accounts: AccountInfo[];
|
* @deprecated
|
||||||
fees: { [methodName: string]: WireFeesJson[] };
|
*/
|
||||||
}
|
export interface ExchangeWireJson {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proposal returned from the contract URL.
|
* Proposal returned from the contract URL.
|
||||||
@ -1404,10 +1505,13 @@ export const codecForGlobalFees = (): Codec<GlobalFees> =>
|
|||||||
.property("master_sig", codecForString())
|
.property("master_sig", codecForString())
|
||||||
.build("GlobalFees");
|
.build("GlobalFees");
|
||||||
|
|
||||||
|
// FIXME: Validate properly!
|
||||||
|
export const codecForNgDenominations: Codec<DenomGroup> = codecForAny();
|
||||||
|
|
||||||
export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
|
export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
|
||||||
buildCodecForObject<ExchangeKeysJson>()
|
buildCodecForObject<ExchangeKeysJson>()
|
||||||
.property("denoms", codecForList(codecForDenomination()))
|
|
||||||
.property("base_url", codecForString())
|
.property("base_url", codecForString())
|
||||||
|
.property("currency", codecForString())
|
||||||
.property("master_public_key", codecForString())
|
.property("master_public_key", codecForString())
|
||||||
.property("auditors", codecForList(codecForAuditor()))
|
.property("auditors", codecForList(codecForAuditor()))
|
||||||
.property("list_issue_date", codecForTimestamp)
|
.property("list_issue_date", codecForTimestamp)
|
||||||
@ -1416,6 +1520,9 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
|
|||||||
.property("version", codecForString())
|
.property("version", codecForString())
|
||||||
.property("reserve_closing_delay", codecForDuration)
|
.property("reserve_closing_delay", codecForDuration)
|
||||||
.property("global_fees", codecForList(codecForGlobalFees()))
|
.property("global_fees", codecForList(codecForGlobalFees()))
|
||||||
|
.property("accounts", codecForList(codecForAccountInfo()))
|
||||||
|
.property("wire_fees", codecForMap(codecForList(codecForWireFeesJson())))
|
||||||
|
.property("denominations", codecForList(codecForNgDenominations))
|
||||||
.build("ExchangeKeysJson");
|
.build("ExchangeKeysJson");
|
||||||
|
|
||||||
export const codecForWireFeesJson = (): Codec<WireFeesJson> =>
|
export const codecForWireFeesJson = (): Codec<WireFeesJson> =>
|
||||||
@ -1436,12 +1543,6 @@ export const codecForAccountInfo = (): Codec<AccountInfo> =>
|
|||||||
.property("debit_restrictions", codecForAny())
|
.property("debit_restrictions", codecForAny())
|
||||||
.build("AccountInfo");
|
.build("AccountInfo");
|
||||||
|
|
||||||
export const codecForExchangeWireJson = (): Codec<ExchangeWireJson> =>
|
|
||||||
buildCodecForObject<ExchangeWireJson>()
|
|
||||||
.property("accounts", codecForList(codecForAccountInfo()))
|
|
||||||
.property("fees", codecForMap(codecForList(codecForWireFeesJson())))
|
|
||||||
.build("ExchangeWireJson");
|
|
||||||
|
|
||||||
export const codecForProposal = (): Codec<Proposal> =>
|
export const codecForProposal = (): Codec<Proposal> =>
|
||||||
buildCodecForObject<Proposal>()
|
buildCodecForObject<Proposal>()
|
||||||
.property("contract_terms", codecForAny())
|
.property("contract_terms", codecForAny())
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc --build && ./build-node.mjs",
|
"compile": "tsc && ./build-node.mjs",
|
||||||
"test": "tsc",
|
"test": "tsc",
|
||||||
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
"clean": "rimraf lib dist tsconfig.tsbuildinfo",
|
||||||
"pretty": "prettier --write src"
|
"pretty": "prettier --write src"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"author": "Florian Dold",
|
"author": "Florian Dold",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"compile": "tsc --build",
|
"compile": "tsc",
|
||||||
"pretty": "prettier --write src",
|
"pretty": "prettier --write src",
|
||||||
"test": "tsc && ava",
|
"test": "tsc && ava",
|
||||||
"coverage": "tsc && c8 --src src --all ava",
|
"coverage": "tsc && c8 --src src --all ava",
|
||||||
@ -72,7 +72,6 @@
|
|||||||
"@gnu-taler/idb-bridge": "workspace:*",
|
"@gnu-taler/idb-bridge": "workspace:*",
|
||||||
"@gnu-taler/taler-util": "workspace:*",
|
"@gnu-taler/taler-util": "workspace:*",
|
||||||
"@types/node": "^18.11.17",
|
"@types/node": "^18.11.17",
|
||||||
"axios": "^0.27.2",
|
|
||||||
"big-integer": "^1.6.51",
|
"big-integer": "^1.6.51",
|
||||||
"fflate": "^0.7.4",
|
"fflate": "^0.7.4",
|
||||||
"tslib": "^2.5.3"
|
"tslib": "^2.5.3"
|
||||||
|
@ -224,7 +224,7 @@ export namespace BankAccessApi {
|
|||||||
`accounts/${bankUser.username}`,
|
`accounts/${bankUser.username}`,
|
||||||
bank.bankAccessApiBaseUrl,
|
bank.bankAccessApiBaseUrl,
|
||||||
);
|
);
|
||||||
const resp = await bank.http.get(url.href, {
|
const resp = await bank.http.fetch(url.href, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: makeBasicAuthHeader(
|
Authorization: makeBasicAuthHeader(
|
||||||
bankUser.username,
|
bankUser.username,
|
||||||
|
@ -723,7 +723,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
|||||||
maybeAcp = await AgeRestriction.restrictionCommitSeeded(
|
maybeAcp = await AgeRestriction.restrictionCommitSeeded(
|
||||||
denomPub.age_mask,
|
denomPub.age_mask,
|
||||||
age,
|
age,
|
||||||
stringToBytes(req.secretSeed)
|
stringToBytes(req.secretSeed),
|
||||||
);
|
);
|
||||||
maybeAgeCommitmentHash = AgeRestriction.hashCommitment(
|
maybeAgeCommitmentHash = AgeRestriction.hashCommitment(
|
||||||
maybeAcp.commitment,
|
maybeAcp.commitment,
|
||||||
@ -1127,6 +1127,8 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
|||||||
// All zeros.
|
// All zeros.
|
||||||
hAgeCommitment = new Uint8Array(32);
|
hAgeCommitment = new Uint8Array(32);
|
||||||
}
|
}
|
||||||
|
// FIXME: Actually allow passing user data here!
|
||||||
|
const walletDataHash = new Uint8Array(64);
|
||||||
let d: Uint8Array;
|
let d: Uint8Array;
|
||||||
if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
|
if (depositInfo.denomKeyType === DenomKeyType.Rsa) {
|
||||||
d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
|
d = buildSigPS(TalerSignaturePurpose.WALLET_COIN_DEPOSIT)
|
||||||
@ -1140,6 +1142,7 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
|
|||||||
.put(amountToBuffer(depositInfo.spendAmount))
|
.put(amountToBuffer(depositInfo.spendAmount))
|
||||||
.put(amountToBuffer(depositInfo.feeDeposit))
|
.put(amountToBuffer(depositInfo.feeDeposit))
|
||||||
.put(decodeCrock(depositInfo.merchantPub))
|
.put(decodeCrock(depositInfo.merchantPub))
|
||||||
|
.put(walletDataHash)
|
||||||
.build();
|
.build();
|
||||||
} else {
|
} else {
|
||||||
throw Error("unsupported exchange protocol version");
|
throw Error("unsupported exchange protocol version");
|
||||||
|
@ -352,6 +352,7 @@ export interface DenomFees {
|
|||||||
export interface DenominationRecord {
|
export interface DenominationRecord {
|
||||||
currency: string;
|
currency: string;
|
||||||
|
|
||||||
|
// FIXME: Use binary encoding of amount instead?
|
||||||
amountVal: number;
|
amountVal: number;
|
||||||
|
|
||||||
amountFrac: number;
|
amountFrac: number;
|
||||||
|
@ -137,7 +137,7 @@ export async function topupReserveWithDemobank(
|
|||||||
throw Error("no suggested exchange");
|
throw Error("no suggested exchange");
|
||||||
}
|
}
|
||||||
const plainPaytoUris =
|
const plainPaytoUris =
|
||||||
exchangeInfo.wire.accounts.map((x) => x.payto_uri) ?? [];
|
exchangeInfo.keys.accounts.map((x) => x.payto_uri) ?? [];
|
||||||
if (plainPaytoUris.length <= 0) {
|
if (plainPaytoUris.length <= 0) {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
@ -338,7 +338,10 @@ export async function refreshCoin(req: {
|
|||||||
|
|
||||||
logger.info("requesting melt done");
|
logger.info("requesting melt done");
|
||||||
|
|
||||||
const meltHttpResp = await http.postJson(meltReqUrl.href, meltReqBody);
|
const meltHttpResp = await http.fetch(meltReqUrl.href, {
|
||||||
|
method: "POST",
|
||||||
|
body: meltReqBody,
|
||||||
|
});
|
||||||
|
|
||||||
const meltResponse = await readSuccessResponseJsonOrThrow(
|
const meltResponse = await readSuccessResponseJsonOrThrow(
|
||||||
meltHttpResp,
|
meltHttpResp,
|
||||||
@ -386,7 +389,7 @@ export async function createFakebankReserve(args: {
|
|||||||
exchangeInfo: ExchangeInfo;
|
exchangeInfo: ExchangeInfo;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { http, fakebankBaseUrl, amount, reservePub } = args;
|
const { http, fakebankBaseUrl, amount, reservePub } = args;
|
||||||
const paytoUri = args.exchangeInfo.wire.accounts[0].payto_uri;
|
const paytoUri = args.exchangeInfo.keys.accounts[0].payto_uri;
|
||||||
const pt = parsePaytoUri(paytoUri);
|
const pt = parsePaytoUri(paytoUri);
|
||||||
if (!pt) {
|
if (!pt) {
|
||||||
throw Error("failed to parse payto URI");
|
throw Error("failed to parse payto URI");
|
||||||
|
@ -70,7 +70,7 @@ export class DevExperimentHttpLib implements HttpRequestLibrary {
|
|||||||
opt?: HttpRequestOptions | undefined,
|
opt?: HttpRequestOptions | undefined,
|
||||||
): Promise<HttpResponse> {
|
): Promise<HttpResponse> {
|
||||||
logger.trace(`devexperiment httplib ${url}`);
|
logger.trace(`devexperiment httplib ${url}`);
|
||||||
return this.underlyingLib.get(url, opt);
|
return this.underlyingLib.fetch(url, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
postJson(
|
postJson(
|
||||||
|
@ -51,12 +51,8 @@ export * from "./operations/refresh.js";
|
|||||||
|
|
||||||
export * from "./dbless.js";
|
export * from "./dbless.js";
|
||||||
|
|
||||||
export {
|
export * from "./crypto/cryptoTypes.js";
|
||||||
nativeCryptoR,
|
export * from "./crypto/cryptoImplementation.js";
|
||||||
nativeCrypto,
|
|
||||||
nullCrypto,
|
|
||||||
TalerCryptoInterface,
|
|
||||||
} from "./crypto/cryptoImplementation.js";
|
|
||||||
|
|
||||||
export * from "./util/timer.js";
|
export * from "./util/timer.js";
|
||||||
export * from "./util/denominations.js";
|
export * from "./util/denominations.js";
|
||||||
|
@ -661,7 +661,7 @@ export async function addBackupProvider(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const termsUrl = new URL("config", canonUrl);
|
const termsUrl = new URL("config", canonUrl);
|
||||||
const resp = await ws.http.get(termsUrl.href);
|
const resp = await ws.http.fetch(termsUrl.href);
|
||||||
const terms = await readSuccessResponseJsonOrThrow(
|
const terms = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
codecForSyncTermsOfServiceResponse(),
|
codecForSyncTermsOfServiceResponse(),
|
||||||
|
@ -19,12 +19,14 @@
|
|||||||
*/
|
*/
|
||||||
import {
|
import {
|
||||||
AbsoluteTime,
|
AbsoluteTime,
|
||||||
|
AccountInfo,
|
||||||
Amounts,
|
Amounts,
|
||||||
CancellationToken,
|
CancellationToken,
|
||||||
canonicalizeBaseUrl,
|
canonicalizeBaseUrl,
|
||||||
codecForExchangeKeysJson,
|
codecForExchangeKeysJson,
|
||||||
codecForExchangeWireJson,
|
DenomGroup,
|
||||||
DenominationPubKey,
|
DenominationPubKey,
|
||||||
|
DenomKeyType,
|
||||||
Duration,
|
Duration,
|
||||||
durationFromSpec,
|
durationFromSpec,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
@ -51,6 +53,7 @@ import {
|
|||||||
URL,
|
URL,
|
||||||
WireFee,
|
WireFee,
|
||||||
WireFeeMap,
|
WireFeeMap,
|
||||||
|
WireFeesJson,
|
||||||
WireInfo,
|
WireInfo,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
@ -84,43 +87,6 @@ import {
|
|||||||
|
|
||||||
const logger = new Logger("exchanges.ts");
|
const logger = new Logger("exchanges.ts");
|
||||||
|
|
||||||
function denominationRecordFromKeys(
|
|
||||||
exchangeBaseUrl: string,
|
|
||||||
exchangeMasterPub: string,
|
|
||||||
listIssueDate: TalerProtocolTimestamp,
|
|
||||||
denomIn: ExchangeDenomination,
|
|
||||||
): DenominationRecord {
|
|
||||||
let denomPub: DenominationPubKey;
|
|
||||||
denomPub = denomIn.denom_pub;
|
|
||||||
const denomPubHash = encodeCrock(hashDenomPub(denomPub));
|
|
||||||
const value = Amounts.parseOrThrow(denomIn.value);
|
|
||||||
const d: DenominationRecord = {
|
|
||||||
denomPub,
|
|
||||||
denomPubHash,
|
|
||||||
exchangeBaseUrl,
|
|
||||||
exchangeMasterPub,
|
|
||||||
fees: {
|
|
||||||
feeDeposit: Amounts.stringify(denomIn.fee_deposit),
|
|
||||||
feeRefresh: Amounts.stringify(denomIn.fee_refresh),
|
|
||||||
feeRefund: Amounts.stringify(denomIn.fee_refund),
|
|
||||||
feeWithdraw: Amounts.stringify(denomIn.fee_withdraw),
|
|
||||||
},
|
|
||||||
isOffered: true,
|
|
||||||
isRevoked: false,
|
|
||||||
masterSig: denomIn.master_sig,
|
|
||||||
stampExpireDeposit: denomIn.stamp_expire_deposit,
|
|
||||||
stampExpireLegal: denomIn.stamp_expire_legal,
|
|
||||||
stampExpireWithdraw: denomIn.stamp_expire_withdraw,
|
|
||||||
stampStart: denomIn.stamp_start,
|
|
||||||
verificationStatus: DenominationVerificationStatus.Unverified,
|
|
||||||
amountFrac: value.fraction,
|
|
||||||
amountVal: value.value,
|
|
||||||
currency: value.currency,
|
|
||||||
listIssueDate,
|
|
||||||
};
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getExchangeRequestTimeout(): Duration {
|
export function getExchangeRequestTimeout(): Duration {
|
||||||
return Duration.fromSpec({
|
return Duration.fromSpec({
|
||||||
seconds: 5,
|
seconds: 5,
|
||||||
@ -145,7 +111,7 @@ export async function downloadExchangeWithTermsOfService(
|
|||||||
Accept: contentType,
|
Accept: contentType,
|
||||||
};
|
};
|
||||||
|
|
||||||
const resp = await http.get(reqUrl.href, {
|
const resp = await http.fetch(reqUrl.href, {
|
||||||
headers,
|
headers,
|
||||||
timeout,
|
timeout,
|
||||||
});
|
});
|
||||||
@ -241,7 +207,7 @@ export async function acceptExchangeTermsOfService(
|
|||||||
async function validateWireInfo(
|
async function validateWireInfo(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
versionCurrent: number,
|
versionCurrent: number,
|
||||||
wireInfo: ExchangeWireJson,
|
wireInfo: ExchangeKeysDownloadResult,
|
||||||
masterPublicKey: string,
|
masterPublicKey: string,
|
||||||
): Promise<WireInfo> {
|
): Promise<WireInfo> {
|
||||||
for (const a of wireInfo.accounts) {
|
for (const a of wireInfo.accounts) {
|
||||||
@ -267,9 +233,9 @@ async function validateWireInfo(
|
|||||||
}
|
}
|
||||||
logger.trace("account validation done");
|
logger.trace("account validation done");
|
||||||
const feesForType: WireFeeMap = {};
|
const feesForType: WireFeeMap = {};
|
||||||
for (const wireMethod of Object.keys(wireInfo.fees)) {
|
for (const wireMethod of Object.keys(wireInfo.wireFees)) {
|
||||||
const feeList: WireFee[] = [];
|
const feeList: WireFee[] = [];
|
||||||
for (const x of wireInfo.fees[wireMethod]) {
|
for (const x of wireInfo.wireFees[wireMethod]) {
|
||||||
const startStamp = x.start_date;
|
const startStamp = x.start_date;
|
||||||
const endStamp = x.end_date;
|
const endStamp = x.end_date;
|
||||||
const fee: WireFee = {
|
const fee: WireFee = {
|
||||||
@ -343,7 +309,6 @@ async function validateGlobalFees(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeInfo {
|
export interface ExchangeInfo {
|
||||||
wire: ExchangeWireJson;
|
|
||||||
keys: ExchangeKeysDownloadResult;
|
keys: ExchangeKeysDownloadResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,11 +316,6 @@ export async function downloadExchangeInfo(
|
|||||||
exchangeBaseUrl: string,
|
exchangeBaseUrl: string,
|
||||||
http: HttpRequestLibrary,
|
http: HttpRequestLibrary,
|
||||||
): Promise<ExchangeInfo> {
|
): Promise<ExchangeInfo> {
|
||||||
const wireInfo = await downloadExchangeWireInfo(
|
|
||||||
exchangeBaseUrl,
|
|
||||||
http,
|
|
||||||
Duration.getForever(),
|
|
||||||
);
|
|
||||||
const keysInfo = await downloadExchangeKeysInfo(
|
const keysInfo = await downloadExchangeKeysInfo(
|
||||||
exchangeBaseUrl,
|
exchangeBaseUrl,
|
||||||
http,
|
http,
|
||||||
@ -363,33 +323,9 @@ export async function downloadExchangeInfo(
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
keys: keysInfo,
|
keys: keysInfo,
|
||||||
wire: wireInfo,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch wire information for an exchange.
|
|
||||||
*
|
|
||||||
* @param exchangeBaseUrl Exchange base URL, assumed to be already normalized.
|
|
||||||
*/
|
|
||||||
async function downloadExchangeWireInfo(
|
|
||||||
exchangeBaseUrl: string,
|
|
||||||
http: HttpRequestLibrary,
|
|
||||||
timeout: Duration,
|
|
||||||
): Promise<ExchangeWireJson> {
|
|
||||||
const reqUrl = new URL("wire", exchangeBaseUrl);
|
|
||||||
|
|
||||||
const resp = await http.get(reqUrl.href, {
|
|
||||||
timeout,
|
|
||||||
});
|
|
||||||
const wireInfo = await readSuccessResponseJsonOrThrow(
|
|
||||||
resp,
|
|
||||||
codecForExchangeWireJson(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return wireInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function provideExchangeRecordInTx(
|
export async function provideExchangeRecordInTx(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
tx: GetReadWriteAccess<{
|
tx: GetReadWriteAccess<{
|
||||||
@ -434,6 +370,8 @@ interface ExchangeKeysDownloadResult {
|
|||||||
recoup: Recoup[];
|
recoup: Recoup[];
|
||||||
listIssueDate: TalerProtocolTimestamp;
|
listIssueDate: TalerProtocolTimestamp;
|
||||||
globalFees: GlobalFees[];
|
globalFees: GlobalFees[];
|
||||||
|
accounts: AccountInfo[];
|
||||||
|
wireFees: { [methodName: string]: WireFeesJson[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -446,7 +384,7 @@ async function downloadExchangeKeysInfo(
|
|||||||
): Promise<ExchangeKeysDownloadResult> {
|
): Promise<ExchangeKeysDownloadResult> {
|
||||||
const keysUrl = new URL("keys", baseUrl);
|
const keysUrl = new URL("keys", baseUrl);
|
||||||
|
|
||||||
const resp = await http.get(keysUrl.href, {
|
const resp = await http.fetch(keysUrl.href, {
|
||||||
timeout,
|
timeout,
|
||||||
});
|
});
|
||||||
const exchangeKeysJsonUnchecked = await readSuccessResponseJsonOrThrow(
|
const exchangeKeysJsonUnchecked = await readSuccessResponseJsonOrThrow(
|
||||||
@ -454,7 +392,7 @@ async function downloadExchangeKeysInfo(
|
|||||||
codecForExchangeKeysJson(),
|
codecForExchangeKeysJson(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (exchangeKeysJsonUnchecked.denoms.length === 0) {
|
if (exchangeKeysJsonUnchecked.denominations.length === 0) {
|
||||||
throw TalerError.fromDetail(
|
throw TalerError.fromDetail(
|
||||||
TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT,
|
TalerErrorCode.WALLET_EXCHANGE_DENOMINATIONS_INSUFFICIENT,
|
||||||
{
|
{
|
||||||
@ -481,23 +419,72 @@ async function downloadExchangeKeysInfo(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currency = Amounts.parseOrThrow(
|
const currency = exchangeKeysJsonUnchecked.currency;
|
||||||
exchangeKeysJsonUnchecked.denoms[0].value,
|
|
||||||
).currency.toUpperCase();
|
const currentDenominations: DenominationRecord[] = [];
|
||||||
|
|
||||||
|
for (const denomGroup of exchangeKeysJsonUnchecked.denominations) {
|
||||||
|
switch (denomGroup.cipher) {
|
||||||
|
case "RSA":
|
||||||
|
case "RSA+age_restricted": {
|
||||||
|
let ageMask = 0;
|
||||||
|
if (denomGroup.cipher === "RSA+age_restricted") {
|
||||||
|
ageMask = denomGroup.age_mask;
|
||||||
|
}
|
||||||
|
for (const denomIn of denomGroup.denoms) {
|
||||||
|
const denomPub: DenominationPubKey = {
|
||||||
|
age_mask: ageMask,
|
||||||
|
cipher: DenomKeyType.Rsa,
|
||||||
|
rsa_public_key: denomIn.rsa_pub,
|
||||||
|
};
|
||||||
|
const denomPubHash = encodeCrock(hashDenomPub(denomPub));
|
||||||
|
const value = Amounts.parseOrThrow(denomGroup.value);
|
||||||
|
const rec: DenominationRecord = {
|
||||||
|
denomPub,
|
||||||
|
denomPubHash,
|
||||||
|
exchangeBaseUrl: baseUrl,
|
||||||
|
exchangeMasterPub: exchangeKeysJsonUnchecked.master_public_key,
|
||||||
|
isOffered: true,
|
||||||
|
isRevoked: false,
|
||||||
|
amountFrac: value.fraction,
|
||||||
|
amountVal: value.value,
|
||||||
|
currency: value.currency,
|
||||||
|
stampExpireDeposit: denomIn.stamp_expire_deposit,
|
||||||
|
stampExpireLegal: denomIn.stamp_expire_legal,
|
||||||
|
stampExpireWithdraw: denomIn.stamp_expire_withdraw,
|
||||||
|
stampStart: denomIn.stamp_start,
|
||||||
|
verificationStatus: DenominationVerificationStatus.Unverified,
|
||||||
|
masterSig: denomIn.master_sig,
|
||||||
|
listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
|
||||||
|
fees: {
|
||||||
|
feeDeposit: Amounts.stringify(denomGroup.fee_deposit),
|
||||||
|
feeRefresh: Amounts.stringify(denomGroup.fee_refresh),
|
||||||
|
feeRefund: Amounts.stringify(denomGroup.fee_refund),
|
||||||
|
feeWithdraw: Amounts.stringify(denomGroup.fee_withdraw),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
currentDenominations.push(rec);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "CS+age_restricted":
|
||||||
|
case "CS":
|
||||||
|
logger.warn("Clause-Schnorr denominations not supported");
|
||||||
|
continue;
|
||||||
|
default:
|
||||||
|
logger.warn(
|
||||||
|
`denomination type ${(denomGroup as any).cipher} not supported`,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
masterPublicKey: exchangeKeysJsonUnchecked.master_public_key,
|
masterPublicKey: exchangeKeysJsonUnchecked.master_public_key,
|
||||||
currency,
|
currency,
|
||||||
baseUrl: exchangeKeysJsonUnchecked.base_url,
|
baseUrl: exchangeKeysJsonUnchecked.base_url,
|
||||||
auditors: exchangeKeysJsonUnchecked.auditors,
|
auditors: exchangeKeysJsonUnchecked.auditors,
|
||||||
currentDenominations: exchangeKeysJsonUnchecked.denoms.map((d) =>
|
currentDenominations,
|
||||||
denominationRecordFromKeys(
|
|
||||||
baseUrl,
|
|
||||||
exchangeKeysJsonUnchecked.master_public_key,
|
|
||||||
exchangeKeysJsonUnchecked.list_issue_date,
|
|
||||||
d,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
protocolVersion: exchangeKeysJsonUnchecked.version,
|
protocolVersion: exchangeKeysJsonUnchecked.version,
|
||||||
signingKeys: exchangeKeysJsonUnchecked.signkeys,
|
signingKeys: exchangeKeysJsonUnchecked.signkeys,
|
||||||
reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay,
|
reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay,
|
||||||
@ -509,6 +496,8 @@ async function downloadExchangeKeysInfo(
|
|||||||
recoup: exchangeKeysJsonUnchecked.recoup ?? [],
|
recoup: exchangeKeysJsonUnchecked.recoup ?? [],
|
||||||
listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
|
listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
|
||||||
globalFees: exchangeKeysJsonUnchecked.global_fees,
|
globalFees: exchangeKeysJsonUnchecked.global_fees,
|
||||||
|
accounts: exchangeKeysJsonUnchecked.accounts,
|
||||||
|
wireFees: exchangeKeysJsonUnchecked.wire_fees,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,14 +643,7 @@ export async function updateExchangeFromUrlHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.trace("updating exchange /wire info");
|
logger.trace("validating exchange wire info");
|
||||||
const wireInfoDownload = await downloadExchangeWireInfo(
|
|
||||||
exchangeBaseUrl,
|
|
||||||
ws.http,
|
|
||||||
timeout,
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.trace("validating exchange /wire info");
|
|
||||||
|
|
||||||
const version = LibtoolVersion.parseVersion(keysInfo.protocolVersion);
|
const version = LibtoolVersion.parseVersion(keysInfo.protocolVersion);
|
||||||
if (!version) {
|
if (!version) {
|
||||||
@ -672,7 +654,7 @@ export async function updateExchangeFromUrlHandler(
|
|||||||
const wireInfo = await validateWireInfo(
|
const wireInfo = await validateWireInfo(
|
||||||
ws,
|
ws,
|
||||||
version.current,
|
version.current,
|
||||||
wireInfoDownload,
|
keysInfo,
|
||||||
keysInfo.masterPublicKey,
|
keysInfo.masterPublicKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ export async function getMerchantInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const configUrl = new URL("config", canonBaseUrl);
|
const configUrl = new URL("config", canonBaseUrl);
|
||||||
const resp = await ws.http.get(configUrl.href);
|
const resp = await ws.http.fetch(configUrl.href);
|
||||||
|
|
||||||
const configResp = await readSuccessResponseJsonOrThrow(
|
const configResp = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
|
@ -43,8 +43,6 @@ import {
|
|||||||
import { SpendCoinDetails } from "../crypto/cryptoImplementation.js";
|
import { SpendCoinDetails } from "../crypto/cryptoImplementation.js";
|
||||||
import {
|
import {
|
||||||
DenominationRecord,
|
DenominationRecord,
|
||||||
KycPendingInfo,
|
|
||||||
KycUserType,
|
|
||||||
PeerPushPaymentCoinSelection,
|
PeerPushPaymentCoinSelection,
|
||||||
ReserveRecord,
|
ReserveRecord,
|
||||||
} from "../db.js";
|
} from "../db.js";
|
||||||
@ -52,68 +50,13 @@ import { InternalWalletState } from "../internal-wallet-state.js";
|
|||||||
import { checkDbInvariant } from "../util/invariants.js";
|
import { checkDbInvariant } from "../util/invariants.js";
|
||||||
import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
|
import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
|
||||||
import { getTotalRefreshCost } from "./refresh.js";
|
import { getTotalRefreshCost } from "./refresh.js";
|
||||||
|
import type { PeerCoinInfo, PeerCoinSelectionRequest, SelectPeerCoinsResult, SelectedPeerCoin } from "../util/coinSelection.js";
|
||||||
|
|
||||||
const logger = new Logger("operations/peer-to-peer.ts");
|
const logger = new Logger("operations/peer-to-peer.ts");
|
||||||
|
|
||||||
interface SelectedPeerCoin {
|
|
||||||
coinPub: string;
|
|
||||||
coinPriv: string;
|
|
||||||
contribution: AmountString;
|
|
||||||
denomPubHash: string;
|
|
||||||
denomSig: UnblindedSignature;
|
|
||||||
ageCommitmentProof: AgeCommitmentProof | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface PeerCoinSelectionDetails {
|
|
||||||
exchangeBaseUrl: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Info of Coins that were selected.
|
|
||||||
*/
|
|
||||||
coins: SelectedPeerCoin[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* How much of the deposit fees is the customer paying?
|
|
||||||
*/
|
|
||||||
depositFees: AmountJson;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about a selected coin for peer to peer payments.
|
|
||||||
*/
|
|
||||||
interface CoinInfo {
|
|
||||||
/**
|
|
||||||
* Public key of the coin.
|
|
||||||
*/
|
|
||||||
coinPub: string;
|
|
||||||
|
|
||||||
coinPriv: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deposit fee for the coin.
|
|
||||||
*/
|
|
||||||
feeDeposit: AmountJson;
|
|
||||||
|
|
||||||
value: AmountJson;
|
|
||||||
|
|
||||||
denomPubHash: string;
|
|
||||||
|
|
||||||
denomSig: UnblindedSignature;
|
|
||||||
|
|
||||||
maxAge: number;
|
|
||||||
|
|
||||||
ageCommitmentProof?: AgeCommitmentProof;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SelectPeerCoinsResult =
|
|
||||||
| { type: "success"; result: PeerCoinSelectionDetails }
|
|
||||||
| {
|
|
||||||
type: "failure";
|
|
||||||
insufficientBalanceDetails: PayPeerInsufficientBalanceDetails;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about the coin selected for signatures
|
* Get information about the coin selected for signatures
|
||||||
|
*
|
||||||
* @param ws
|
* @param ws
|
||||||
* @param csel
|
* @param csel
|
||||||
* @returns
|
* @returns
|
||||||
@ -153,211 +96,7 @@ export async function queryCoinInfosForSelection(
|
|||||||
return infos;
|
return infos;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PeerCoinRepair {
|
|
||||||
exchangeBaseUrl: string;
|
|
||||||
coinPubs: CoinPublicKeyString[];
|
|
||||||
contribs: AmountJson[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PeerCoinSelectionRequest {
|
|
||||||
instructedAmount: AmountJson;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instruct the coin selection to repair this coin
|
|
||||||
* selection instead of selecting completely new coins.
|
|
||||||
*/
|
|
||||||
repair?: PeerCoinRepair;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function selectPeerCoins(
|
|
||||||
ws: InternalWalletState,
|
|
||||||
req: PeerCoinSelectionRequest,
|
|
||||||
): Promise<SelectPeerCoinsResult> {
|
|
||||||
const instructedAmount = req.instructedAmount;
|
|
||||||
if (Amounts.isZero(instructedAmount)) {
|
|
||||||
// Other parts of the code assume that we have at least
|
|
||||||
// one coin to spend.
|
|
||||||
throw new Error("amount of zero not allowed");
|
|
||||||
}
|
|
||||||
return await ws.db
|
|
||||||
.mktx((x) => [
|
|
||||||
x.exchanges,
|
|
||||||
x.contractTerms,
|
|
||||||
x.coins,
|
|
||||||
x.coinAvailability,
|
|
||||||
x.denominations,
|
|
||||||
x.refreshGroups,
|
|
||||||
x.peerPushPaymentInitiations,
|
|
||||||
])
|
|
||||||
.runReadWrite(async (tx) => {
|
|
||||||
const exchanges = await tx.exchanges.iter().toArray();
|
|
||||||
const exchangeFeeGap: { [url: string]: AmountJson } = {};
|
|
||||||
const currency = Amounts.currencyOf(instructedAmount);
|
|
||||||
for (const exch of exchanges) {
|
|
||||||
if (exch.detailsPointer?.currency !== currency) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// FIXME: Can't we do this faster by using coinAvailability?
|
|
||||||
const coins = (
|
|
||||||
await tx.coins.indexes.byBaseUrl.getAll(exch.baseUrl)
|
|
||||||
).filter((x) => x.status === CoinStatus.Fresh);
|
|
||||||
const coinInfos: CoinInfo[] = [];
|
|
||||||
for (const coin of coins) {
|
|
||||||
const denom = await ws.getDenomInfo(
|
|
||||||
ws,
|
|
||||||
tx,
|
|
||||||
coin.exchangeBaseUrl,
|
|
||||||
coin.denomPubHash,
|
|
||||||
);
|
|
||||||
if (!denom) {
|
|
||||||
throw Error("denom not found");
|
|
||||||
}
|
|
||||||
coinInfos.push({
|
|
||||||
coinPub: coin.coinPub,
|
|
||||||
feeDeposit: Amounts.parseOrThrow(denom.feeDeposit),
|
|
||||||
value: Amounts.parseOrThrow(denom.value),
|
|
||||||
denomPubHash: denom.denomPubHash,
|
|
||||||
coinPriv: coin.coinPriv,
|
|
||||||
denomSig: coin.denomSig,
|
|
||||||
maxAge: coin.maxAge,
|
|
||||||
ageCommitmentProof: coin.ageCommitmentProof,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (coinInfos.length === 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
coinInfos.sort(
|
|
||||||
(o1, o2) =>
|
|
||||||
-Amounts.cmp(o1.value, o2.value) ||
|
|
||||||
strcmp(o1.denomPubHash, o2.denomPubHash),
|
|
||||||
);
|
|
||||||
let amountAcc = Amounts.zeroOfCurrency(currency);
|
|
||||||
let depositFeesAcc = Amounts.zeroOfCurrency(currency);
|
|
||||||
const resCoins: {
|
|
||||||
coinPub: string;
|
|
||||||
coinPriv: string;
|
|
||||||
contribution: AmountString;
|
|
||||||
denomPubHash: string;
|
|
||||||
denomSig: UnblindedSignature;
|
|
||||||
ageCommitmentProof: AgeCommitmentProof | undefined;
|
|
||||||
}[] = [];
|
|
||||||
let lastDepositFee = Amounts.zeroOfCurrency(currency);
|
|
||||||
|
|
||||||
if (req.repair) {
|
|
||||||
for (let i = 0; i < req.repair.coinPubs.length; i++) {
|
|
||||||
const contrib = req.repair.contribs[i];
|
|
||||||
const coin = await tx.coins.get(req.repair.coinPubs[i]);
|
|
||||||
if (!coin) {
|
|
||||||
throw Error("repair not possible, coin not found");
|
|
||||||
}
|
|
||||||
const denom = await ws.getDenomInfo(
|
|
||||||
ws,
|
|
||||||
tx,
|
|
||||||
coin.exchangeBaseUrl,
|
|
||||||
coin.denomPubHash,
|
|
||||||
);
|
|
||||||
checkDbInvariant(!!denom);
|
|
||||||
resCoins.push({
|
|
||||||
coinPriv: coin.coinPriv,
|
|
||||||
coinPub: coin.coinPub,
|
|
||||||
contribution: Amounts.stringify(contrib),
|
|
||||||
denomPubHash: coin.denomPubHash,
|
|
||||||
denomSig: coin.denomSig,
|
|
||||||
ageCommitmentProof: coin.ageCommitmentProof,
|
|
||||||
});
|
|
||||||
const depositFee = Amounts.parseOrThrow(denom.feeDeposit);
|
|
||||||
lastDepositFee = depositFee;
|
|
||||||
amountAcc = Amounts.add(
|
|
||||||
amountAcc,
|
|
||||||
Amounts.sub(contrib, depositFee).amount,
|
|
||||||
).amount;
|
|
||||||
depositFeesAcc = Amounts.add(depositFeesAcc, depositFee).amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const coin of coinInfos) {
|
|
||||||
if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const gap = Amounts.add(
|
|
||||||
coin.feeDeposit,
|
|
||||||
Amounts.sub(instructedAmount, amountAcc).amount,
|
|
||||||
).amount;
|
|
||||||
const contrib = Amounts.min(gap, coin.value);
|
|
||||||
amountAcc = Amounts.add(
|
|
||||||
amountAcc,
|
|
||||||
Amounts.sub(contrib, coin.feeDeposit).amount,
|
|
||||||
).amount;
|
|
||||||
depositFeesAcc = Amounts.add(depositFeesAcc, coin.feeDeposit).amount;
|
|
||||||
resCoins.push({
|
|
||||||
coinPriv: coin.coinPriv,
|
|
||||||
coinPub: coin.coinPub,
|
|
||||||
contribution: Amounts.stringify(contrib),
|
|
||||||
denomPubHash: coin.denomPubHash,
|
|
||||||
denomSig: coin.denomSig,
|
|
||||||
ageCommitmentProof: coin.ageCommitmentProof,
|
|
||||||
});
|
|
||||||
lastDepositFee = coin.feeDeposit;
|
|
||||||
}
|
|
||||||
if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
|
|
||||||
const res: PeerCoinSelectionDetails = {
|
|
||||||
exchangeBaseUrl: exch.baseUrl,
|
|
||||||
coins: resCoins,
|
|
||||||
depositFees: depositFeesAcc,
|
|
||||||
};
|
|
||||||
return { type: "success", result: res };
|
|
||||||
}
|
|
||||||
const diff = Amounts.sub(instructedAmount, amountAcc).amount;
|
|
||||||
exchangeFeeGap[exch.baseUrl] = Amounts.add(lastDepositFee, diff).amount;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We were unable to select coins.
|
|
||||||
// Now we need to produce error details.
|
|
||||||
|
|
||||||
const infoGeneral = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
|
|
||||||
currency,
|
|
||||||
});
|
|
||||||
|
|
||||||
const perExchange: PayPeerInsufficientBalanceDetails["perExchange"] = {};
|
|
||||||
|
|
||||||
let maxFeeGapEstimate = Amounts.zeroOfCurrency(currency);
|
|
||||||
|
|
||||||
for (const exch of exchanges) {
|
|
||||||
if (exch.detailsPointer?.currency !== currency) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const infoExchange = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
|
|
||||||
currency,
|
|
||||||
restrictExchangeTo: exch.baseUrl,
|
|
||||||
});
|
|
||||||
let gap =
|
|
||||||
exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency);
|
|
||||||
if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) {
|
|
||||||
// Show fee gap only if we should've been able to pay with the material amount
|
|
||||||
gap = Amounts.zeroOfCurrency(currency);
|
|
||||||
}
|
|
||||||
perExchange[exch.baseUrl] = {
|
|
||||||
balanceAvailable: Amounts.stringify(infoExchange.balanceAvailable),
|
|
||||||
balanceMaterial: Amounts.stringify(infoExchange.balanceMaterial),
|
|
||||||
feeGapEstimate: Amounts.stringify(gap),
|
|
||||||
};
|
|
||||||
|
|
||||||
maxFeeGapEstimate = Amounts.max(maxFeeGapEstimate, gap);
|
|
||||||
}
|
|
||||||
|
|
||||||
const errDetails: PayPeerInsufficientBalanceDetails = {
|
|
||||||
amountRequested: Amounts.stringify(instructedAmount),
|
|
||||||
balanceAvailable: Amounts.stringify(infoGeneral.balanceAvailable),
|
|
||||||
balanceMaterial: Amounts.stringify(infoGeneral.balanceMaterial),
|
|
||||||
feeGapEstimate: Amounts.stringify(maxFeeGapEstimate),
|
|
||||||
perExchange,
|
|
||||||
};
|
|
||||||
|
|
||||||
return { type: "failure", insufficientBalanceDetails: errDetails };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getTotalPeerPaymentCost(
|
export async function getTotalPeerPaymentCost(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
@ -420,28 +159,6 @@ export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> =>
|
|||||||
.property("merge_timestamp", codecOptional(codecForTimestamp))
|
.property("merge_timestamp", codecOptional(codecForTimestamp))
|
||||||
.build("ExchangePurseStatus");
|
.build("ExchangePurseStatus");
|
||||||
|
|
||||||
export function talerPaytoFromExchangeReserve(
|
|
||||||
exchangeBaseUrl: string,
|
|
||||||
reservePub: string,
|
|
||||||
): string {
|
|
||||||
const url = new URL(exchangeBaseUrl);
|
|
||||||
let proto: string;
|
|
||||||
if (url.protocol === "http:") {
|
|
||||||
proto = "taler-reserve-http";
|
|
||||||
} else if (url.protocol === "https:") {
|
|
||||||
proto = "taler-reserve";
|
|
||||||
} else {
|
|
||||||
throw Error(`unsupported exchange base URL protocol (${url.protocol})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = url.pathname;
|
|
||||||
if (!path.endsWith("/")) {
|
|
||||||
path = path + "/";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMergeReserveInfo(
|
export async function getMergeReserveInfo(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
req: {
|
req: {
|
||||||
|
@ -45,6 +45,7 @@ import {
|
|||||||
j2s,
|
j2s,
|
||||||
makeErrorDetail,
|
makeErrorDetail,
|
||||||
stringifyTalerUri,
|
stringifyTalerUri,
|
||||||
|
talerPaytoFromExchangeReserve,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import {
|
import {
|
||||||
readSuccessResponseJsonOrErrorCode,
|
readSuccessResponseJsonOrErrorCode,
|
||||||
@ -74,7 +75,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
codecForExchangePurseStatus,
|
codecForExchangePurseStatus,
|
||||||
getMergeReserveInfo,
|
getMergeReserveInfo,
|
||||||
talerPaytoFromExchangeReserve,
|
|
||||||
} from "./pay-peer-common.js";
|
} from "./pay-peer-common.js";
|
||||||
import {
|
import {
|
||||||
constructTransactionIdentifier,
|
constructTransactionIdentifier,
|
||||||
|
@ -68,11 +68,9 @@ import {
|
|||||||
spendCoins,
|
spendCoins,
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
import {
|
import {
|
||||||
PeerCoinRepair,
|
|
||||||
codecForExchangePurseStatus,
|
codecForExchangePurseStatus,
|
||||||
getTotalPeerPaymentCost,
|
getTotalPeerPaymentCost,
|
||||||
queryCoinInfosForSelection,
|
queryCoinInfosForSelection,
|
||||||
selectPeerCoins,
|
|
||||||
} from "./pay-peer-common.js";
|
} from "./pay-peer-common.js";
|
||||||
import {
|
import {
|
||||||
constructTransactionIdentifier,
|
constructTransactionIdentifier,
|
||||||
@ -80,6 +78,7 @@ import {
|
|||||||
parseTransactionIdentifier,
|
parseTransactionIdentifier,
|
||||||
stopLongpolling,
|
stopLongpolling,
|
||||||
} from "./transactions.js";
|
} from "./transactions.js";
|
||||||
|
import { PeerCoinRepair, selectPeerCoins } from "../util/coinSelection.js";
|
||||||
|
|
||||||
const logger = new Logger("pay-peer-pull-debit.ts");
|
const logger = new Logger("pay-peer-pull-debit.ts");
|
||||||
|
|
||||||
@ -530,7 +529,7 @@ export async function preparePeerPullDebit(
|
|||||||
|
|
||||||
const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
|
const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl);
|
||||||
|
|
||||||
const contractHttpResp = await ws.http.get(getContractUrl.href);
|
const contractHttpResp = await ws.http.fetch(getContractUrl.href);
|
||||||
|
|
||||||
const contractResp = await readSuccessResponseJsonOrThrow(
|
const contractResp = await readSuccessResponseJsonOrThrow(
|
||||||
contractHttpResp,
|
contractHttpResp,
|
||||||
|
@ -47,6 +47,7 @@ import {
|
|||||||
j2s,
|
j2s,
|
||||||
makeErrorDetail,
|
makeErrorDetail,
|
||||||
parsePayPushUri,
|
parsePayPushUri,
|
||||||
|
talerPaytoFromExchangeReserve,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
|
||||||
import {
|
import {
|
||||||
@ -71,7 +72,6 @@ import { updateExchangeFromUrl } from "./exchanges.js";
|
|||||||
import {
|
import {
|
||||||
codecForExchangePurseStatus,
|
codecForExchangePurseStatus,
|
||||||
getMergeReserveInfo,
|
getMergeReserveInfo,
|
||||||
talerPaytoFromExchangeReserve,
|
|
||||||
} from "./pay-peer-common.js";
|
} from "./pay-peer-common.js";
|
||||||
import {
|
import {
|
||||||
TransitionInfo,
|
TransitionInfo,
|
||||||
@ -165,7 +165,7 @@ export async function preparePeerPushCredit(
|
|||||||
|
|
||||||
const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl);
|
const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl);
|
||||||
|
|
||||||
const purseHttpResp = await ws.http.get(getPurseUrl.href);
|
const purseHttpResp = await ws.http.fetch(getPurseUrl.href);
|
||||||
|
|
||||||
const purseStatus = await readSuccessResponseJsonOrThrow(
|
const purseStatus = await readSuccessResponseJsonOrThrow(
|
||||||
purseHttpResp,
|
purseHttpResp,
|
||||||
|
@ -68,17 +68,16 @@ import {
|
|||||||
spendCoins,
|
spendCoins,
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
import {
|
import {
|
||||||
PeerCoinRepair,
|
|
||||||
codecForExchangePurseStatus,
|
codecForExchangePurseStatus,
|
||||||
getTotalPeerPaymentCost,
|
getTotalPeerPaymentCost,
|
||||||
queryCoinInfosForSelection,
|
queryCoinInfosForSelection,
|
||||||
selectPeerCoins,
|
|
||||||
} from "./pay-peer-common.js";
|
} from "./pay-peer-common.js";
|
||||||
import {
|
import {
|
||||||
constructTransactionIdentifier,
|
constructTransactionIdentifier,
|
||||||
notifyTransition,
|
notifyTransition,
|
||||||
stopLongpolling,
|
stopLongpolling,
|
||||||
} from "./transactions.js";
|
} from "./transactions.js";
|
||||||
|
import { PeerCoinRepair, selectPeerCoins } from "../util/coinSelection.js";
|
||||||
|
|
||||||
const logger = new Logger("pay-peer-push-debit.ts");
|
const logger = new Logger("pay-peer-push-debit.ts");
|
||||||
|
|
||||||
|
@ -358,7 +358,7 @@ export async function processRecoupGroup(
|
|||||||
);
|
);
|
||||||
logger.info(`querying reserve status for recoup via ${reserveUrl}`);
|
logger.info(`querying reserve status for recoup via ${reserveUrl}`);
|
||||||
|
|
||||||
const resp = await ws.http.get(reserveUrl.href);
|
const resp = await ws.http.fetch(reserveUrl.href);
|
||||||
|
|
||||||
const result = await readSuccessResponseJsonOrThrow(
|
const result = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
|
@ -161,7 +161,7 @@ export async function prepareTip(
|
|||||||
res.merchantBaseUrl,
|
res.merchantBaseUrl,
|
||||||
);
|
);
|
||||||
logger.trace("checking tip status from", tipStatusUrl.href);
|
logger.trace("checking tip status from", tipStatusUrl.href);
|
||||||
const merchantResp = await ws.http.get(tipStatusUrl.href);
|
const merchantResp = await ws.http.fetch(tipStatusUrl.href);
|
||||||
const tipPickupStatus = await readSuccessResponseJsonOrThrow(
|
const tipPickupStatus = await readSuccessResponseJsonOrThrow(
|
||||||
merchantResp,
|
merchantResp,
|
||||||
codecForTipPickupGetResponse(),
|
codecForTipPickupGetResponse(),
|
||||||
|
@ -293,7 +293,7 @@ async function checkPayment(
|
|||||||
): Promise<CheckPaymentResponse> {
|
): Promise<CheckPaymentResponse> {
|
||||||
const reqUrl = new URL(`private/orders/${orderId}`, merchantBackend.baseUrl);
|
const reqUrl = new URL(`private/orders/${orderId}`, merchantBackend.baseUrl);
|
||||||
reqUrl.searchParams.set("order_id", orderId);
|
reqUrl.searchParams.set("order_id", orderId);
|
||||||
const resp = await http.get(reqUrl.href, {
|
const resp = await http.fetch(reqUrl.href, {
|
||||||
headers: getMerchantAuthHeader(merchantBackend),
|
headers: getMerchantAuthHeader(merchantBackend),
|
||||||
});
|
});
|
||||||
return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse());
|
return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse());
|
||||||
|
@ -558,7 +558,7 @@ export async function getBankWithdrawalInfo(
|
|||||||
|
|
||||||
const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl);
|
const configReqUrl = new URL("config", uriResult.bankIntegrationApiBaseUrl);
|
||||||
|
|
||||||
const configResp = await http.get(configReqUrl.href);
|
const configResp = await http.fetch(configReqUrl.href);
|
||||||
const config = await readSuccessResponseJsonOrThrow(
|
const config = await readSuccessResponseJsonOrThrow(
|
||||||
configResp,
|
configResp,
|
||||||
codecForTalerConfigResponse(),
|
codecForTalerConfigResponse(),
|
||||||
@ -586,7 +586,7 @@ export async function getBankWithdrawalInfo(
|
|||||||
|
|
||||||
logger.info(`bank withdrawal status URL: ${reqUrl.href}}`);
|
logger.info(`bank withdrawal status URL: ${reqUrl.href}}`);
|
||||||
|
|
||||||
const resp = await http.get(reqUrl.href);
|
const resp = await http.fetch(reqUrl.href);
|
||||||
const status = await readSuccessResponseJsonOrThrow(
|
const status = await readSuccessResponseJsonOrThrow(
|
||||||
resp,
|
resp,
|
||||||
codecForWithdrawOperationStatusResponse(),
|
codecForWithdrawOperationStatusResponse(),
|
||||||
@ -2103,7 +2103,7 @@ async function processReserveBankStatus(
|
|||||||
|
|
||||||
const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri);
|
const bankStatusUrl = getBankStatusUrl(bankInfo.talerWithdrawUri);
|
||||||
|
|
||||||
const statusResp = await ws.http.get(bankStatusUrl, {
|
const statusResp = await ws.http.fetch(bankStatusUrl, {
|
||||||
timeout: getReserveRequestTimeout(withdrawalGroup),
|
timeout: getReserveRequestTimeout(withdrawalGroup),
|
||||||
});
|
});
|
||||||
const status = await readSuccessResponseJsonOrThrow(
|
const status = await readSuccessResponseJsonOrThrow(
|
||||||
|
@ -22,746 +22,4 @@ import {
|
|||||||
TransactionAmountMode,
|
TransactionAmountMode,
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import test, { ExecutionContext } from "ava";
|
import test, { ExecutionContext } from "ava";
|
||||||
import {
|
|
||||||
CoinInfo,
|
|
||||||
convertDepositAmountForAvailableCoins,
|
|
||||||
convertWithdrawalAmountFromAvailableCoins,
|
|
||||||
getMaxDepositAmountForAvailableCoins,
|
|
||||||
} from "./coinSelection.js";
|
|
||||||
|
|
||||||
function makeCurrencyHelper(currency: string) {
|
|
||||||
return (sx: TemplateStringsArray, ...vx: any[]) => {
|
|
||||||
const s = String.raw({ raw: sx }, ...vx);
|
|
||||||
return Amounts.parseOrThrow(`${currency}:${s}`);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const kudos = makeCurrencyHelper("kudos");
|
|
||||||
|
|
||||||
function defaultFeeConfig(value: AmountJson, totalAvailable: number): CoinInfo {
|
|
||||||
return {
|
|
||||||
id: Amounts.stringify(value),
|
|
||||||
denomDeposit: kudos`0.01`,
|
|
||||||
denomRefresh: kudos`0.01`,
|
|
||||||
denomWithdraw: kudos`0.01`,
|
|
||||||
exchangeBaseUrl: "1",
|
|
||||||
duration: Duration.getForever(),
|
|
||||||
exchangePurse: undefined,
|
|
||||||
exchangeWire: undefined,
|
|
||||||
maxAge: AgeRestriction.AGE_UNRESTRICTED,
|
|
||||||
totalAvailable,
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
type Coin = [AmountJson, number];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making a deposit with effective amount
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
test("deposit effective 2", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`2`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "2");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "1.99");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit effective 10", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`10`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "10");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "9.98");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit effective 24", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`24`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "23.94");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit effective 40", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`40`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "35");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "34.9");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit with wire fee effective 2", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {
|
|
||||||
one: {
|
|
||||||
wireFee: kudos`0.1`,
|
|
||||||
purseFee: kudos`0.00`,
|
|
||||||
creditDeadline: AbsoluteTime.never(),
|
|
||||||
debitDeadline: AbsoluteTime.never(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
kudos`2`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "2");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "1.89");
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making a deposit with raw amount, using the result from effective
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
test("deposit raw 1.99 (effective 2)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`1.99`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "2");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "1.99");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit raw 9.98 (effective 10)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`9.98`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "10");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "9.98");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit raw 23.94 (effective 24)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`23.94`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "23.94");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit raw 34.9 (effective 40)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`34.9`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "35");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "34.9");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit with wire fee raw 2", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {
|
|
||||||
one: {
|
|
||||||
wireFee: kudos`0.1`,
|
|
||||||
purseFee: kudos`0.00`,
|
|
||||||
creditDeadline: AbsoluteTime.never(),
|
|
||||||
debitDeadline: AbsoluteTime.never(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
kudos`2`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "2");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "1.89");
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculating the max amount possible to deposit
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
test("deposit max 35", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = getMaxDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {
|
|
||||||
"2": {
|
|
||||||
wireFee: kudos`0.00`,
|
|
||||||
purseFee: kudos`0.00`,
|
|
||||||
creditDeadline: AbsoluteTime.never(),
|
|
||||||
debitDeadline: AbsoluteTime.never(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"KUDOS",
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "34.9");
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "35");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit max 35 with wirefee", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = getMaxDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {
|
|
||||||
"2": {
|
|
||||||
wireFee: kudos`1`,
|
|
||||||
purseFee: kudos`0.00`,
|
|
||||||
creditDeadline: AbsoluteTime.never(),
|
|
||||||
debitDeadline: AbsoluteTime.never(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"KUDOS",
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "33.9");
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "35");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit max repeated denom", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 1],
|
|
||||||
[kudos`2`, 1],
|
|
||||||
[kudos`5`, 1],
|
|
||||||
];
|
|
||||||
const result = getMaxDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {
|
|
||||||
"2": {
|
|
||||||
wireFee: kudos`0.00`,
|
|
||||||
purseFee: kudos`0.00`,
|
|
||||||
creditDeadline: AbsoluteTime.never(),
|
|
||||||
debitDeadline: AbsoluteTime.never(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"KUDOS",
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "8.97");
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "9");
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making a withdrawal with effective amount
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
test("withdraw effective 2", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`2`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "2");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "2.01");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw effective 10", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`10`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "10");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "10.02");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw effective 24", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`24`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "24.06");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw effective 40", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`40`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "40");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "40.08");
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making a deposit with raw amount, using the result from effective
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
test("withdraw raw 2.01 (effective 2)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`2.01`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "2");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "2.01");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw raw 10.02 (effective 10)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`10.02`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "10");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "10.02");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw raw 24.06 (effective 24)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`24.06`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "24.06");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw raw 40.08 (effective 40)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`40.08`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "40");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "40.08");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw raw 25", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 0],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 0],
|
|
||||||
[kudos`5`, 0],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`25`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "24.94");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("withdraw effective 24.8 (raw 25)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 0],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 0],
|
|
||||||
[kudos`5`, 0],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`24.8`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "24.94");
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making a deposit with refresh
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
test("deposit with refresh: effective 3", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 0],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`3`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "3.1");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "2.98");
|
|
||||||
expectDefined(t, result.refresh);
|
|
||||||
//FEES
|
|
||||||
//deposit 2 x 0.01
|
|
||||||
//refresh 1 x 0.01
|
|
||||||
//withdraw 9 x 0.01
|
|
||||||
//-----------------
|
|
||||||
//op 0.12
|
|
||||||
|
|
||||||
//coins sent 2 x 2.0
|
|
||||||
//coins recv 9 x 0.1
|
|
||||||
//-------------------
|
|
||||||
//effective 3.10
|
|
||||||
//raw 2.98
|
|
||||||
t.is(Amounts.stringifyValue(result.refresh.selected.id), "2");
|
|
||||||
t.deepEqual(asCoinList(result.refresh.coins), [[kudos`0.1`, 9]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit with refresh: raw 2.98 (effective 3)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 0],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`2.98`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "3.2");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "3.09");
|
|
||||||
expectDefined(t, result.refresh);
|
|
||||||
//FEES
|
|
||||||
//deposit 1 x 0.01
|
|
||||||
//refresh 1 x 0.01
|
|
||||||
//withdraw 8 x 0.01
|
|
||||||
//-----------------
|
|
||||||
//op 0.10
|
|
||||||
|
|
||||||
//coins sent 1 x 2.0
|
|
||||||
//coins recv 8 x 0.1
|
|
||||||
//-------------------
|
|
||||||
//effective 3.20
|
|
||||||
//raw 3.09
|
|
||||||
t.is(Amounts.stringifyValue(result.refresh.selected.id), "2");
|
|
||||||
t.deepEqual(asCoinList(result.refresh.coins), [[kudos`0.1`, 8]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("deposit with refresh: effective 3.2 (raw 2.98)", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 0],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 5],
|
|
||||||
[kudos`5`, 5],
|
|
||||||
];
|
|
||||||
const result = convertDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`3.2`,
|
|
||||||
TransactionAmountMode.Effective,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "3.3");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "3.2");
|
|
||||||
expectDefined(t, result.refresh);
|
|
||||||
//FEES
|
|
||||||
//deposit 2 x 0.01
|
|
||||||
//refresh 1 x 0.01
|
|
||||||
//withdraw 7 x 0.01
|
|
||||||
//-----------------
|
|
||||||
//op 0.10
|
|
||||||
|
|
||||||
//coins sent 2 x 2.0
|
|
||||||
//coins recv 7 x 0.1
|
|
||||||
//-------------------
|
|
||||||
//effective 3.30
|
|
||||||
//raw 3.20
|
|
||||||
t.is(Amounts.stringifyValue(result.refresh.selected.id), "2");
|
|
||||||
t.deepEqual(asCoinList(result.refresh.coins), [[kudos`0.1`, 7]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
function expectDefined<T>(
|
|
||||||
t: ExecutionContext,
|
|
||||||
v: T | undefined,
|
|
||||||
): asserts v is T {
|
|
||||||
t.assert(v !== undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
function asCoinList(v: { info: CoinInfo; size: number }[]): any {
|
|
||||||
return v.map((c) => {
|
|
||||||
return [c.info.value, c.size];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* regression tests
|
|
||||||
*/
|
|
||||||
|
|
||||||
test("demo: withdraw raw 25", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 0],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 0],
|
|
||||||
[kudos`5`, 0],
|
|
||||||
[kudos`10`, 0],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`25`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "24.92");
|
|
||||||
// coins received
|
|
||||||
// 8 x 0.1
|
|
||||||
// 2 x 0.2
|
|
||||||
// 2 x 10.0
|
|
||||||
// total effective 24.8
|
|
||||||
// fee 12 x 0.01 = 0.12
|
|
||||||
// total raw 24.92
|
|
||||||
// left in reserve 25 - 24.92 == 0.08
|
|
||||||
|
|
||||||
//current wallet impl: hides the left in reserve fee
|
|
||||||
//shows fee = 0.2
|
|
||||||
});
|
|
||||||
|
|
||||||
test("demo: deposit max after withdraw raw 25", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 8],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 2],
|
|
||||||
[kudos`5`, 0],
|
|
||||||
[kudos`10`, 2],
|
|
||||||
];
|
|
||||||
const result = getMaxDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {
|
|
||||||
one: {
|
|
||||||
wireFee: kudos`0.01`,
|
|
||||||
purseFee: kudos`0.00`,
|
|
||||||
creditDeadline: AbsoluteTime.never(),
|
|
||||||
debitDeadline: AbsoluteTime.never(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"KUDOS",
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "24.67");
|
|
||||||
|
|
||||||
// 8 x 0.1
|
|
||||||
// 2 x 0.2
|
|
||||||
// 2 x 10.0
|
|
||||||
// total effective 24.8
|
|
||||||
// deposit fee 12 x 0.01 = 0.12
|
|
||||||
// wire fee 0.01
|
|
||||||
// total raw: 24.8 - 0.13 = 24.67
|
|
||||||
|
|
||||||
// current wallet impl fee 0.14
|
|
||||||
});
|
|
||||||
|
|
||||||
test("demo: withdraw raw 13", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 0],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 0],
|
|
||||||
[kudos`5`, 0],
|
|
||||||
[kudos`10`, 0],
|
|
||||||
];
|
|
||||||
const result = convertWithdrawalAmountFromAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {},
|
|
||||||
},
|
|
||||||
kudos`13`,
|
|
||||||
TransactionAmountMode.Raw,
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "12.8");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "12.9");
|
|
||||||
// coins received
|
|
||||||
// 8 x 0.1
|
|
||||||
// 1 x 0.2
|
|
||||||
// 1 x 10.0
|
|
||||||
// total effective 12.8
|
|
||||||
// fee 10 x 0.01 = 0.10
|
|
||||||
// total raw 12.9
|
|
||||||
// left in reserve 13 - 12.9 == 0.1
|
|
||||||
|
|
||||||
//current wallet impl: hides the left in reserve fee
|
|
||||||
//shows fee = 0.2
|
|
||||||
});
|
|
||||||
|
|
||||||
test("demo: deposit max after withdraw raw 13", (t) => {
|
|
||||||
const coinList: Coin[] = [
|
|
||||||
[kudos`0.1`, 8],
|
|
||||||
[kudos`1`, 0],
|
|
||||||
[kudos`2`, 1],
|
|
||||||
[kudos`5`, 0],
|
|
||||||
[kudos`10`, 1],
|
|
||||||
];
|
|
||||||
const result = getMaxDepositAmountForAvailableCoins(
|
|
||||||
{
|
|
||||||
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
|
||||||
exchanges: {
|
|
||||||
one: {
|
|
||||||
wireFee: kudos`0.01`,
|
|
||||||
purseFee: kudos`0.00`,
|
|
||||||
creditDeadline: AbsoluteTime.never(),
|
|
||||||
debitDeadline: AbsoluteTime.never(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"KUDOS",
|
|
||||||
);
|
|
||||||
t.is(Amounts.stringifyValue(result.effective), "12.8");
|
|
||||||
t.is(Amounts.stringifyValue(result.raw), "12.69");
|
|
||||||
|
|
||||||
// 8 x 0.1
|
|
||||||
// 1 x 0.2
|
|
||||||
// 1 x 10.0
|
|
||||||
// total effective 12.8
|
|
||||||
// deposit fee 10 x 0.01 = 0.10
|
|
||||||
// wire fee 0.01
|
|
||||||
// total raw: 12.8 - 0.11 = 12.69
|
|
||||||
|
|
||||||
// current wallet impl fee 0.14
|
|
||||||
});
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,763 @@
|
|||||||
|
/*
|
||||||
|
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 {
|
||||||
|
AbsoluteTime,
|
||||||
|
AgeRestriction,
|
||||||
|
AmountJson,
|
||||||
|
Amounts,
|
||||||
|
Duration,
|
||||||
|
TransactionAmountMode,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import test, { ExecutionContext } from "ava";
|
||||||
|
import { CoinInfo } from "./coinSelection.js";
|
||||||
|
import { convertDepositAmountForAvailableCoins, getMaxDepositAmountForAvailableCoins, convertWithdrawalAmountFromAvailableCoins } from "./instructedAmountConversion.js";
|
||||||
|
|
||||||
|
function makeCurrencyHelper(currency: string) {
|
||||||
|
return (sx: TemplateStringsArray, ...vx: any[]) => {
|
||||||
|
const s = String.raw({ raw: sx }, ...vx);
|
||||||
|
return Amounts.parseOrThrow(`${currency}:${s}`);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const kudos = makeCurrencyHelper("kudos");
|
||||||
|
|
||||||
|
function defaultFeeConfig(value: AmountJson, totalAvailable: number): CoinInfo {
|
||||||
|
return {
|
||||||
|
id: Amounts.stringify(value),
|
||||||
|
denomDeposit: kudos`0.01`,
|
||||||
|
denomRefresh: kudos`0.01`,
|
||||||
|
denomWithdraw: kudos`0.01`,
|
||||||
|
exchangeBaseUrl: "1",
|
||||||
|
duration: Duration.getForever(),
|
||||||
|
exchangePurse: undefined,
|
||||||
|
exchangeWire: undefined,
|
||||||
|
maxAge: AgeRestriction.AGE_UNRESTRICTED,
|
||||||
|
totalAvailable,
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
type Coin = [AmountJson, number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Making a deposit with effective amount
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
test("deposit effective 2", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`2`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "2");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "1.99");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit effective 10", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`10`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "10");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "9.98");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit effective 24", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`24`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "23.94");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit effective 40", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`40`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "35");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "34.9");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit with wire fee effective 2", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {
|
||||||
|
one: {
|
||||||
|
wireFee: kudos`0.1`,
|
||||||
|
purseFee: kudos`0.00`,
|
||||||
|
creditDeadline: AbsoluteTime.never(),
|
||||||
|
debitDeadline: AbsoluteTime.never(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kudos`2`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "2");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "1.89");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Making a deposit with raw amount, using the result from effective
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
test("deposit raw 1.99 (effective 2)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`1.99`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "2");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "1.99");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit raw 9.98 (effective 10)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`9.98`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "10");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "9.98");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit raw 23.94 (effective 24)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`23.94`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "23.94");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit raw 34.9 (effective 40)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`34.9`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "35");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "34.9");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit with wire fee raw 2", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {
|
||||||
|
one: {
|
||||||
|
wireFee: kudos`0.1`,
|
||||||
|
purseFee: kudos`0.00`,
|
||||||
|
creditDeadline: AbsoluteTime.never(),
|
||||||
|
debitDeadline: AbsoluteTime.never(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
kudos`2`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "2");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "1.89");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculating the max amount possible to deposit
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
test("deposit max 35", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = getMaxDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {
|
||||||
|
"2": {
|
||||||
|
wireFee: kudos`0.00`,
|
||||||
|
purseFee: kudos`0.00`,
|
||||||
|
creditDeadline: AbsoluteTime.never(),
|
||||||
|
debitDeadline: AbsoluteTime.never(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"KUDOS",
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "34.9");
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "35");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit max 35 with wirefee", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = getMaxDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {
|
||||||
|
"2": {
|
||||||
|
wireFee: kudos`1`,
|
||||||
|
purseFee: kudos`0.00`,
|
||||||
|
creditDeadline: AbsoluteTime.never(),
|
||||||
|
debitDeadline: AbsoluteTime.never(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"KUDOS",
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "33.9");
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "35");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit max repeated denom", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 1],
|
||||||
|
[kudos`2`, 1],
|
||||||
|
[kudos`5`, 1],
|
||||||
|
];
|
||||||
|
const result = getMaxDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {
|
||||||
|
"2": {
|
||||||
|
wireFee: kudos`0.00`,
|
||||||
|
purseFee: kudos`0.00`,
|
||||||
|
creditDeadline: AbsoluteTime.never(),
|
||||||
|
debitDeadline: AbsoluteTime.never(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"KUDOS",
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "8.97");
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "9");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Making a withdrawal with effective amount
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
test("withdraw effective 2", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`2`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "2");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "2.01");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw effective 10", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`10`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "10");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "10.02");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw effective 24", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`24`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "24.06");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw effective 40", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`40`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "40");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "40.08");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Making a deposit with raw amount, using the result from effective
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
test("withdraw raw 2.01 (effective 2)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`2.01`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "2");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "2.01");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw raw 10.02 (effective 10)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`10.02`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "10");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "10.02");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw raw 24.06 (effective 24)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`24.06`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "24.06");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw raw 40.08 (effective 40)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`40.08`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "40");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "40.08");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw raw 25", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 0],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 0],
|
||||||
|
[kudos`5`, 0],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`25`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "24.94");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("withdraw effective 24.8 (raw 25)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 0],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 0],
|
||||||
|
[kudos`5`, 0],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`24.8`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "24.94");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Making a deposit with refresh
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
test("deposit with refresh: effective 3", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 0],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`3`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "3.1");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "2.98");
|
||||||
|
expectDefined(t, result.refresh);
|
||||||
|
//FEES
|
||||||
|
//deposit 2 x 0.01
|
||||||
|
//refresh 1 x 0.01
|
||||||
|
//withdraw 9 x 0.01
|
||||||
|
//-----------------
|
||||||
|
//op 0.12
|
||||||
|
|
||||||
|
//coins sent 2 x 2.0
|
||||||
|
//coins recv 9 x 0.1
|
||||||
|
//-------------------
|
||||||
|
//effective 3.10
|
||||||
|
//raw 2.98
|
||||||
|
t.is(Amounts.stringifyValue(result.refresh.selected.id), "2");
|
||||||
|
t.deepEqual(asCoinList(result.refresh.coins), [[kudos`0.1`, 9]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit with refresh: raw 2.98 (effective 3)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 0],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`2.98`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "3.2");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "3.09");
|
||||||
|
expectDefined(t, result.refresh);
|
||||||
|
//FEES
|
||||||
|
//deposit 1 x 0.01
|
||||||
|
//refresh 1 x 0.01
|
||||||
|
//withdraw 8 x 0.01
|
||||||
|
//-----------------
|
||||||
|
//op 0.10
|
||||||
|
|
||||||
|
//coins sent 1 x 2.0
|
||||||
|
//coins recv 8 x 0.1
|
||||||
|
//-------------------
|
||||||
|
//effective 3.20
|
||||||
|
//raw 3.09
|
||||||
|
t.is(Amounts.stringifyValue(result.refresh.selected.id), "2");
|
||||||
|
t.deepEqual(asCoinList(result.refresh.coins), [[kudos`0.1`, 8]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deposit with refresh: effective 3.2 (raw 2.98)", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 0],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 5],
|
||||||
|
[kudos`5`, 5],
|
||||||
|
];
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`3.2`,
|
||||||
|
TransactionAmountMode.Effective,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "3.3");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "3.2");
|
||||||
|
expectDefined(t, result.refresh);
|
||||||
|
//FEES
|
||||||
|
//deposit 2 x 0.01
|
||||||
|
//refresh 1 x 0.01
|
||||||
|
//withdraw 7 x 0.01
|
||||||
|
//-----------------
|
||||||
|
//op 0.10
|
||||||
|
|
||||||
|
//coins sent 2 x 2.0
|
||||||
|
//coins recv 7 x 0.1
|
||||||
|
//-------------------
|
||||||
|
//effective 3.30
|
||||||
|
//raw 3.20
|
||||||
|
t.is(Amounts.stringifyValue(result.refresh.selected.id), "2");
|
||||||
|
t.deepEqual(asCoinList(result.refresh.coins), [[kudos`0.1`, 7]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
function expectDefined<T>(
|
||||||
|
t: ExecutionContext,
|
||||||
|
v: T | undefined,
|
||||||
|
): asserts v is T {
|
||||||
|
t.assert(v !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function asCoinList(v: { info: CoinInfo; size: number }[]): any {
|
||||||
|
return v.map((c) => {
|
||||||
|
return [c.info.value, c.size];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* regression tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
test("demo: withdraw raw 25", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 0],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 0],
|
||||||
|
[kudos`5`, 0],
|
||||||
|
[kudos`10`, 0],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`25`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "24.92");
|
||||||
|
// coins received
|
||||||
|
// 8 x 0.1
|
||||||
|
// 2 x 0.2
|
||||||
|
// 2 x 10.0
|
||||||
|
// total effective 24.8
|
||||||
|
// fee 12 x 0.01 = 0.12
|
||||||
|
// total raw 24.92
|
||||||
|
// left in reserve 25 - 24.92 == 0.08
|
||||||
|
|
||||||
|
//current wallet impl: hides the left in reserve fee
|
||||||
|
//shows fee = 0.2
|
||||||
|
});
|
||||||
|
|
||||||
|
test("demo: deposit max after withdraw raw 25", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 8],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 2],
|
||||||
|
[kudos`5`, 0],
|
||||||
|
[kudos`10`, 2],
|
||||||
|
];
|
||||||
|
const result = getMaxDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {
|
||||||
|
one: {
|
||||||
|
wireFee: kudos`0.01`,
|
||||||
|
purseFee: kudos`0.00`,
|
||||||
|
creditDeadline: AbsoluteTime.never(),
|
||||||
|
debitDeadline: AbsoluteTime.never(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"KUDOS",
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "24.8");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "24.67");
|
||||||
|
|
||||||
|
// 8 x 0.1
|
||||||
|
// 2 x 0.2
|
||||||
|
// 2 x 10.0
|
||||||
|
// total effective 24.8
|
||||||
|
// deposit fee 12 x 0.01 = 0.12
|
||||||
|
// wire fee 0.01
|
||||||
|
// total raw: 24.8 - 0.13 = 24.67
|
||||||
|
|
||||||
|
// current wallet impl fee 0.14
|
||||||
|
});
|
||||||
|
|
||||||
|
test("demo: withdraw raw 13", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 0],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 0],
|
||||||
|
[kudos`5`, 0],
|
||||||
|
[kudos`10`, 0],
|
||||||
|
];
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {},
|
||||||
|
},
|
||||||
|
kudos`13`,
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "12.8");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "12.9");
|
||||||
|
// coins received
|
||||||
|
// 8 x 0.1
|
||||||
|
// 1 x 0.2
|
||||||
|
// 1 x 10.0
|
||||||
|
// total effective 12.8
|
||||||
|
// fee 10 x 0.01 = 0.10
|
||||||
|
// total raw 12.9
|
||||||
|
// left in reserve 13 - 12.9 == 0.1
|
||||||
|
|
||||||
|
//current wallet impl: hides the left in reserve fee
|
||||||
|
//shows fee = 0.2
|
||||||
|
});
|
||||||
|
|
||||||
|
test("demo: deposit max after withdraw raw 13", (t) => {
|
||||||
|
const coinList: Coin[] = [
|
||||||
|
[kudos`0.1`, 8],
|
||||||
|
[kudos`1`, 0],
|
||||||
|
[kudos`2`, 1],
|
||||||
|
[kudos`5`, 0],
|
||||||
|
[kudos`10`, 1],
|
||||||
|
];
|
||||||
|
const result = getMaxDepositAmountForAvailableCoins(
|
||||||
|
{
|
||||||
|
list: coinList.map(([v, t]) => defaultFeeConfig(v, t)),
|
||||||
|
exchanges: {
|
||||||
|
one: {
|
||||||
|
wireFee: kudos`0.01`,
|
||||||
|
purseFee: kudos`0.00`,
|
||||||
|
creditDeadline: AbsoluteTime.never(),
|
||||||
|
debitDeadline: AbsoluteTime.never(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"KUDOS",
|
||||||
|
);
|
||||||
|
t.is(Amounts.stringifyValue(result.effective), "12.8");
|
||||||
|
t.is(Amounts.stringifyValue(result.raw), "12.69");
|
||||||
|
|
||||||
|
// 8 x 0.1
|
||||||
|
// 1 x 0.2
|
||||||
|
// 1 x 10.0
|
||||||
|
// total effective 12.8
|
||||||
|
// deposit fee 10 x 0.01 = 0.10
|
||||||
|
// wire fee 0.01
|
||||||
|
// total raw: 12.8 - 0.11 = 12.69
|
||||||
|
|
||||||
|
// current wallet impl fee 0.14
|
||||||
|
});
|
@ -0,0 +1,849 @@
|
|||||||
|
/*
|
||||||
|
This file is part of GNU Taler
|
||||||
|
(C) 2023 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 {
|
||||||
|
AbsoluteTime,
|
||||||
|
AgeRestriction,
|
||||||
|
AmountJson,
|
||||||
|
AmountResponse,
|
||||||
|
Amounts,
|
||||||
|
ConvertAmountRequest,
|
||||||
|
Duration,
|
||||||
|
GetAmountRequest,
|
||||||
|
GetPlanForOperationRequest,
|
||||||
|
TransactionAmountMode,
|
||||||
|
TransactionType,
|
||||||
|
parsePaytoUri,
|
||||||
|
strcmp,
|
||||||
|
} from "@gnu-taler/taler-util";
|
||||||
|
import { checkDbInvariant } from "./invariants.js";
|
||||||
|
import {
|
||||||
|
DenominationRecord,
|
||||||
|
InternalWalletState,
|
||||||
|
getExchangeDetails,
|
||||||
|
} from "../index.js";
|
||||||
|
import { CoinInfo } from "./coinSelection.js";
|
||||||
|
import { GlobalIDB } from "@gnu-taler/idb-bridge";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the operation going to be plan subtracts
|
||||||
|
* or adds amount in the wallet db
|
||||||
|
*/
|
||||||
|
export enum OperationType {
|
||||||
|
Credit = "credit",
|
||||||
|
Debit = "debit",
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Name conflict ...
|
||||||
|
interface ExchangeInfo {
|
||||||
|
wireFee: AmountJson | undefined;
|
||||||
|
purseFee: AmountJson | undefined;
|
||||||
|
creditDeadline: AbsoluteTime;
|
||||||
|
debitDeadline: AbsoluteTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOperationType(txType: TransactionType): OperationType {
|
||||||
|
const operationType =
|
||||||
|
txType === TransactionType.Withdrawal
|
||||||
|
? OperationType.Credit
|
||||||
|
: txType === TransactionType.Deposit
|
||||||
|
? OperationType.Debit
|
||||||
|
: undefined;
|
||||||
|
if (!operationType) {
|
||||||
|
throw Error(`operation type ${txType} not yet supported`);
|
||||||
|
}
|
||||||
|
return operationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectedCoins {
|
||||||
|
totalValue: AmountJson;
|
||||||
|
coins: { info: CoinInfo; size: number }[];
|
||||||
|
refresh?: RefreshChoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCoinsFilter(req: GetPlanForOperationRequest): CoinsFilter {
|
||||||
|
switch (req.type) {
|
||||||
|
case TransactionType.Withdrawal: {
|
||||||
|
return {
|
||||||
|
exchanges:
|
||||||
|
req.exchangeUrl === undefined ? undefined : [req.exchangeUrl],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case TransactionType.Deposit: {
|
||||||
|
const payto = parsePaytoUri(req.account);
|
||||||
|
if (!payto) {
|
||||||
|
throw Error(`wrong payto ${req.account}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
wireMethod: payto.targetType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RefreshChoice {
|
||||||
|
/**
|
||||||
|
* Amount that need to be covered
|
||||||
|
*/
|
||||||
|
gap: AmountJson;
|
||||||
|
totalFee: AmountJson;
|
||||||
|
selected: CoinInfo;
|
||||||
|
totalChangeValue: AmountJson;
|
||||||
|
refreshEffective: AmountJson;
|
||||||
|
coins: { info: CoinInfo; size: number }[];
|
||||||
|
|
||||||
|
// totalValue: AmountJson;
|
||||||
|
// totalDepositFee: AmountJson;
|
||||||
|
// totalRefreshFee: AmountJson;
|
||||||
|
// totalChangeContribution: AmountJson;
|
||||||
|
// totalChangeWithdrawalFee: AmountJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CoinsFilter {
|
||||||
|
shouldCalculatePurseFee?: boolean;
|
||||||
|
exchanges?: string[];
|
||||||
|
wireMethod?: string;
|
||||||
|
ageRestricted?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AvailableCoins {
|
||||||
|
list: CoinInfo[];
|
||||||
|
exchanges: Record<string, ExchangeInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the denoms that can be used for a operation that is limited
|
||||||
|
* by the following restrictions.
|
||||||
|
* This function is costly (by the database access) but with high chances
|
||||||
|
* of being cached
|
||||||
|
*/
|
||||||
|
async function getAvailableDenoms(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
op: TransactionType,
|
||||||
|
currency: string,
|
||||||
|
filters: CoinsFilter = {},
|
||||||
|
): Promise<AvailableCoins> {
|
||||||
|
const operationType = getOperationType(TransactionType.Deposit);
|
||||||
|
|
||||||
|
return await ws.db
|
||||||
|
.mktx((x) => [
|
||||||
|
x.exchanges,
|
||||||
|
x.exchangeDetails,
|
||||||
|
x.denominations,
|
||||||
|
x.coinAvailability,
|
||||||
|
])
|
||||||
|
.runReadOnly(async (tx) => {
|
||||||
|
const list: CoinInfo[] = [];
|
||||||
|
const exchanges: Record<string, ExchangeInfo> = {};
|
||||||
|
|
||||||
|
const databaseExchanges = await tx.exchanges.iter().toArray();
|
||||||
|
const filteredExchanges =
|
||||||
|
filters.exchanges ?? databaseExchanges.map((e) => e.baseUrl);
|
||||||
|
|
||||||
|
for (const exchangeBaseUrl of filteredExchanges) {
|
||||||
|
const exchangeDetails = await getExchangeDetails(tx, exchangeBaseUrl);
|
||||||
|
// 1.- exchange has same currency
|
||||||
|
if (exchangeDetails?.currency !== currency) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let deadline = AbsoluteTime.never();
|
||||||
|
// 2.- exchange supports wire method
|
||||||
|
let wireFee: AmountJson | undefined;
|
||||||
|
if (filters.wireMethod) {
|
||||||
|
const wireMethodWithDates =
|
||||||
|
exchangeDetails.wireInfo.feesForType[filters.wireMethod];
|
||||||
|
|
||||||
|
if (!wireMethodWithDates) {
|
||||||
|
throw Error(
|
||||||
|
`exchange ${exchangeBaseUrl} doesn't have wire method ${filters.wireMethod}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const wireMethodFee = wireMethodWithDates.find((x) => {
|
||||||
|
return AbsoluteTime.isBetween(
|
||||||
|
AbsoluteTime.now(),
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(x.startStamp),
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(x.endStamp),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!wireMethodFee) {
|
||||||
|
throw Error(
|
||||||
|
`exchange ${exchangeBaseUrl} doesn't have wire fee defined for this period`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
wireFee = Amounts.parseOrThrow(wireMethodFee.wireFee);
|
||||||
|
deadline = AbsoluteTime.min(
|
||||||
|
deadline,
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(wireMethodFee.endStamp),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// exchanges[exchangeBaseUrl].wireFee = wireMethodFee;
|
||||||
|
|
||||||
|
// 3.- exchange supports wire method
|
||||||
|
let purseFee: AmountJson | undefined;
|
||||||
|
if (filters.shouldCalculatePurseFee) {
|
||||||
|
const purseFeeFound = exchangeDetails.globalFees.find((x) => {
|
||||||
|
return AbsoluteTime.isBetween(
|
||||||
|
AbsoluteTime.now(),
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(x.startDate),
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(x.endDate),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (!purseFeeFound) {
|
||||||
|
throw Error(
|
||||||
|
`exchange ${exchangeBaseUrl} doesn't have purse fee defined for this period`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
purseFee = Amounts.parseOrThrow(purseFeeFound.purseFee);
|
||||||
|
deadline = AbsoluteTime.min(
|
||||||
|
deadline,
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(purseFeeFound.endDate),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let creditDeadline = AbsoluteTime.never();
|
||||||
|
let debitDeadline = AbsoluteTime.never();
|
||||||
|
//4.- filter coins restricted by age
|
||||||
|
if (operationType === OperationType.Credit) {
|
||||||
|
const ds = await tx.denominations.indexes.byExchangeBaseUrl.getAll(
|
||||||
|
exchangeBaseUrl,
|
||||||
|
);
|
||||||
|
for (const denom of ds) {
|
||||||
|
const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
|
||||||
|
denom.stampExpireWithdraw,
|
||||||
|
);
|
||||||
|
const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
|
||||||
|
denom.stampExpireDeposit,
|
||||||
|
);
|
||||||
|
creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
|
||||||
|
debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
|
||||||
|
list.push(
|
||||||
|
buildCoinInfoFromDenom(
|
||||||
|
denom,
|
||||||
|
purseFee,
|
||||||
|
wireFee,
|
||||||
|
AgeRestriction.AGE_UNRESTRICTED,
|
||||||
|
Number.MAX_SAFE_INTEGER, // Max withdrawable from single denom
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const ageLower = filters.ageRestricted ?? 0;
|
||||||
|
const ageUpper = AgeRestriction.AGE_UNRESTRICTED;
|
||||||
|
|
||||||
|
const myExchangeCoins =
|
||||||
|
await tx.coinAvailability.indexes.byExchangeAgeAvailability.getAll(
|
||||||
|
GlobalIDB.KeyRange.bound(
|
||||||
|
[exchangeDetails.exchangeBaseUrl, ageLower, 1],
|
||||||
|
[
|
||||||
|
exchangeDetails.exchangeBaseUrl,
|
||||||
|
ageUpper,
|
||||||
|
Number.MAX_SAFE_INTEGER,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
//5.- save denoms with how many coins are available
|
||||||
|
// FIXME: Check that the individual denomination is audited!
|
||||||
|
// FIXME: Should we exclude denominations that are
|
||||||
|
// not spendable anymore?
|
||||||
|
for (const coinAvail of myExchangeCoins) {
|
||||||
|
const denom = await tx.denominations.get([
|
||||||
|
coinAvail.exchangeBaseUrl,
|
||||||
|
coinAvail.denomPubHash,
|
||||||
|
]);
|
||||||
|
checkDbInvariant(!!denom);
|
||||||
|
if (denom.isRevoked || !denom.isOffered) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
|
||||||
|
denom.stampExpireWithdraw,
|
||||||
|
);
|
||||||
|
const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
|
||||||
|
denom.stampExpireDeposit,
|
||||||
|
);
|
||||||
|
creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
|
||||||
|
debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
|
||||||
|
list.push(
|
||||||
|
buildCoinInfoFromDenom(
|
||||||
|
denom,
|
||||||
|
purseFee,
|
||||||
|
wireFee,
|
||||||
|
coinAvail.maxAge,
|
||||||
|
coinAvail.freshCoinCount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exchanges[exchangeBaseUrl] = {
|
||||||
|
purseFee,
|
||||||
|
wireFee,
|
||||||
|
debitDeadline,
|
||||||
|
creditDeadline,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list, exchanges };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildCoinInfoFromDenom(
|
||||||
|
denom: DenominationRecord,
|
||||||
|
purseFee: AmountJson | undefined,
|
||||||
|
wireFee: AmountJson | undefined,
|
||||||
|
maxAge: number,
|
||||||
|
total: number,
|
||||||
|
): CoinInfo {
|
||||||
|
return {
|
||||||
|
id: denom.denomPubHash,
|
||||||
|
denomWithdraw: Amounts.parseOrThrow(denom.fees.feeWithdraw),
|
||||||
|
denomDeposit: Amounts.parseOrThrow(denom.fees.feeDeposit),
|
||||||
|
denomRefresh: Amounts.parseOrThrow(denom.fees.feeRefresh),
|
||||||
|
exchangePurse: purseFee,
|
||||||
|
exchangeWire: wireFee,
|
||||||
|
exchangeBaseUrl: denom.exchangeBaseUrl,
|
||||||
|
duration: AbsoluteTime.difference(
|
||||||
|
AbsoluteTime.now(),
|
||||||
|
AbsoluteTime.fromProtocolTimestamp(denom.stampExpireDeposit),
|
||||||
|
),
|
||||||
|
totalAvailable: total,
|
||||||
|
value: DenominationRecord.getValue(denom),
|
||||||
|
maxAge,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function convertDepositAmount(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: ConvertAmountRequest,
|
||||||
|
): Promise<AmountResponse> {
|
||||||
|
const amount = Amounts.parseOrThrow(req.amount);
|
||||||
|
// const filter = getCoinsFilter(req);
|
||||||
|
|
||||||
|
const denoms = await getAvailableDenoms(
|
||||||
|
ws,
|
||||||
|
TransactionType.Deposit,
|
||||||
|
amount.currency,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
const result = convertDepositAmountForAvailableCoins(
|
||||||
|
denoms,
|
||||||
|
amount,
|
||||||
|
req.type,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
effectiveAmount: Amounts.stringify(result.effective),
|
||||||
|
rawAmount: Amounts.stringify(result.raw),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const LOG_REFRESH = false;
|
||||||
|
const LOG_DEPOSIT = false;
|
||||||
|
export function convertDepositAmountForAvailableCoins(
|
||||||
|
denoms: AvailableCoins,
|
||||||
|
amount: AmountJson,
|
||||||
|
mode: TransactionAmountMode,
|
||||||
|
): AmountAndRefresh {
|
||||||
|
const zero = Amounts.zeroOfCurrency(amount.currency);
|
||||||
|
if (!denoms.list.length) {
|
||||||
|
// no coins in the database
|
||||||
|
return { effective: zero, raw: zero };
|
||||||
|
}
|
||||||
|
const depositDenoms = rankDenominationForDeposit(denoms.list, mode);
|
||||||
|
|
||||||
|
//FIXME: we are not taking into account
|
||||||
|
// * exchanges with multiple accounts
|
||||||
|
// * wallet with multiple exchanges
|
||||||
|
const wireFee = Object.values(denoms.exchanges)[0]?.wireFee ?? zero;
|
||||||
|
const adjustedAmount = Amounts.add(amount, wireFee).amount;
|
||||||
|
|
||||||
|
const selected = selectGreedyCoins(depositDenoms, adjustedAmount);
|
||||||
|
|
||||||
|
const gap = Amounts.sub(amount, selected.totalValue).amount;
|
||||||
|
|
||||||
|
const result = getTotalEffectiveAndRawForDeposit(
|
||||||
|
selected.coins,
|
||||||
|
amount.currency,
|
||||||
|
);
|
||||||
|
result.raw = Amounts.sub(result.raw, wireFee).amount;
|
||||||
|
|
||||||
|
if (Amounts.isZero(gap)) {
|
||||||
|
// exact amount founds
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LOG_DEPOSIT) {
|
||||||
|
const logInfo = selected.coins.map((c) => {
|
||||||
|
return `${Amounts.stringifyValue(c.info.id)} x ${c.size}`;
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
"deposit used:",
|
||||||
|
logInfo.join(", "),
|
||||||
|
"gap:",
|
||||||
|
Amounts.stringifyValue(gap),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshDenoms = rankDenominationForRefresh(denoms.list);
|
||||||
|
/**
|
||||||
|
* FIXME: looking for refresh AFTER selecting greedy is not optimal
|
||||||
|
*/
|
||||||
|
const refreshCoin = searchBestRefreshCoin(
|
||||||
|
depositDenoms,
|
||||||
|
refreshDenoms,
|
||||||
|
gap,
|
||||||
|
mode,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (refreshCoin) {
|
||||||
|
const fee = Amounts.sub(result.effective, result.raw).amount;
|
||||||
|
const effective = Amounts.add(
|
||||||
|
result.effective,
|
||||||
|
refreshCoin.refreshEffective,
|
||||||
|
).amount;
|
||||||
|
const raw = Amounts.sub(effective, fee, refreshCoin.totalFee).amount;
|
||||||
|
//found with change
|
||||||
|
return {
|
||||||
|
effective,
|
||||||
|
raw,
|
||||||
|
refresh: refreshCoin,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is a gap, but no refresh coin was found
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMaxDepositAmount(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: GetAmountRequest,
|
||||||
|
): Promise<AmountResponse> {
|
||||||
|
// const filter = getCoinsFilter(req);
|
||||||
|
|
||||||
|
const denoms = await getAvailableDenoms(
|
||||||
|
ws,
|
||||||
|
TransactionType.Deposit,
|
||||||
|
req.currency,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = getMaxDepositAmountForAvailableCoins(denoms, req.currency);
|
||||||
|
return {
|
||||||
|
effectiveAmount: Amounts.stringify(result.effective),
|
||||||
|
rawAmount: Amounts.stringify(result.raw),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMaxDepositAmountForAvailableCoins(
|
||||||
|
denoms: AvailableCoins,
|
||||||
|
currency: string,
|
||||||
|
) {
|
||||||
|
const zero = Amounts.zeroOfCurrency(currency);
|
||||||
|
if (!denoms.list.length) {
|
||||||
|
// no coins in the database
|
||||||
|
return { effective: zero, raw: zero };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = getTotalEffectiveAndRawForDeposit(
|
||||||
|
denoms.list.map((info) => {
|
||||||
|
return { info, size: info.totalAvailable ?? 0 };
|
||||||
|
}),
|
||||||
|
currency,
|
||||||
|
);
|
||||||
|
|
||||||
|
const wireFee = Object.values(denoms.exchanges)[0]?.wireFee ?? zero;
|
||||||
|
result.raw = Amounts.sub(result.raw, wireFee).amount;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function convertPeerPushAmount(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: ConvertAmountRequest,
|
||||||
|
): Promise<AmountResponse> {
|
||||||
|
throw Error("to be implemented after 1.0");
|
||||||
|
}
|
||||||
|
export async function getMaxPeerPushAmount(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: GetAmountRequest,
|
||||||
|
): Promise<AmountResponse> {
|
||||||
|
throw Error("to be implemented after 1.0");
|
||||||
|
}
|
||||||
|
export async function convertWithdrawalAmount(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
req: ConvertAmountRequest,
|
||||||
|
): Promise<AmountResponse> {
|
||||||
|
const amount = Amounts.parseOrThrow(req.amount);
|
||||||
|
|
||||||
|
const denoms = await getAvailableDenoms(
|
||||||
|
ws,
|
||||||
|
TransactionType.Withdrawal,
|
||||||
|
amount.currency,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
denoms,
|
||||||
|
amount,
|
||||||
|
req.type,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
effectiveAmount: Amounts.stringify(result.effective),
|
||||||
|
rawAmount: Amounts.stringify(result.raw),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertWithdrawalAmountFromAvailableCoins(
|
||||||
|
denoms: AvailableCoins,
|
||||||
|
amount: AmountJson,
|
||||||
|
mode: TransactionAmountMode,
|
||||||
|
) {
|
||||||
|
const zero = Amounts.zeroOfCurrency(amount.currency);
|
||||||
|
if (!denoms.list.length) {
|
||||||
|
// no coins in the database
|
||||||
|
return { effective: zero, raw: zero };
|
||||||
|
}
|
||||||
|
const withdrawDenoms = rankDenominationForWithdrawals(denoms.list, mode);
|
||||||
|
|
||||||
|
const selected = selectGreedyCoins(withdrawDenoms, amount);
|
||||||
|
|
||||||
|
return getTotalEffectiveAndRawForWithdrawal(selected.coins, amount.currency);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** *****************************************************
|
||||||
|
* HELPERS
|
||||||
|
* *****************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param depositDenoms
|
||||||
|
* @param refreshDenoms
|
||||||
|
* @param amount
|
||||||
|
* @param mode
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function searchBestRefreshCoin(
|
||||||
|
depositDenoms: SelectableElement[],
|
||||||
|
refreshDenoms: Record<string, SelectableElement[]>,
|
||||||
|
amount: AmountJson,
|
||||||
|
mode: TransactionAmountMode,
|
||||||
|
): RefreshChoice | undefined {
|
||||||
|
let choice: RefreshChoice | undefined = undefined;
|
||||||
|
let refreshIdx = 0;
|
||||||
|
refreshIteration: while (refreshIdx < depositDenoms.length) {
|
||||||
|
const d = depositDenoms[refreshIdx];
|
||||||
|
|
||||||
|
const denomContribution =
|
||||||
|
mode === TransactionAmountMode.Effective
|
||||||
|
? d.value
|
||||||
|
: Amounts.sub(d.value, d.info.denomRefresh, d.info.denomDeposit).amount;
|
||||||
|
|
||||||
|
const changeAfterDeposit = Amounts.sub(denomContribution, amount).amount;
|
||||||
|
if (Amounts.isZero(changeAfterDeposit)) {
|
||||||
|
//this coin is not big enough to use for refresh
|
||||||
|
//since the list is sorted, we can break here
|
||||||
|
break refreshIteration;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withdrawDenoms = refreshDenoms[d.info.exchangeBaseUrl];
|
||||||
|
const change = selectGreedyCoins(withdrawDenoms, changeAfterDeposit);
|
||||||
|
|
||||||
|
const zero = Amounts.zeroOfCurrency(amount.currency);
|
||||||
|
const withdrawChangeFee = change.coins.reduce((cur, prev) => {
|
||||||
|
return Amounts.add(
|
||||||
|
cur,
|
||||||
|
Amounts.mult(prev.info.denomWithdraw, prev.size).amount,
|
||||||
|
).amount;
|
||||||
|
}, zero);
|
||||||
|
|
||||||
|
const withdrawChangeValue = change.coins.reduce((cur, prev) => {
|
||||||
|
return Amounts.add(cur, Amounts.mult(prev.info.value, prev.size).amount)
|
||||||
|
.amount;
|
||||||
|
}, zero);
|
||||||
|
|
||||||
|
const totalFee = Amounts.add(
|
||||||
|
d.info.denomDeposit,
|
||||||
|
d.info.denomRefresh,
|
||||||
|
withdrawChangeFee,
|
||||||
|
).amount;
|
||||||
|
|
||||||
|
if (!choice || Amounts.cmp(totalFee, choice.totalFee) === -1) {
|
||||||
|
//found cheaper change
|
||||||
|
choice = {
|
||||||
|
gap: amount,
|
||||||
|
totalFee: totalFee,
|
||||||
|
totalChangeValue: change.totalValue, //change after refresh
|
||||||
|
refreshEffective: Amounts.sub(d.info.value, withdrawChangeValue).amount, // what of the denom used is not recovered
|
||||||
|
selected: d.info,
|
||||||
|
coins: change.coins,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
refreshIdx++;
|
||||||
|
}
|
||||||
|
if (choice) {
|
||||||
|
if (LOG_REFRESH) {
|
||||||
|
const logInfo = choice.coins.map((c) => {
|
||||||
|
return `${Amounts.stringifyValue(c.info.id)} x ${c.size}`;
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
"refresh used:",
|
||||||
|
Amounts.stringifyValue(choice.selected.value),
|
||||||
|
"change:",
|
||||||
|
logInfo.join(", "),
|
||||||
|
"fee:",
|
||||||
|
Amounts.stringifyValue(choice.totalFee),
|
||||||
|
"refreshEffective:",
|
||||||
|
Amounts.stringifyValue(choice.refreshEffective),
|
||||||
|
"totalChangeValue:",
|
||||||
|
Amounts.stringifyValue(choice.totalChangeValue),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return choice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the list sorted for the best denom to withdraw first
|
||||||
|
*
|
||||||
|
* @param denoms
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function rankDenominationForWithdrawals(
|
||||||
|
denoms: CoinInfo[],
|
||||||
|
mode: TransactionAmountMode,
|
||||||
|
): SelectableElement[] {
|
||||||
|
const copyList = [...denoms];
|
||||||
|
/**
|
||||||
|
* Rank coins
|
||||||
|
*/
|
||||||
|
copyList.sort((d1, d2) => {
|
||||||
|
// the best coin to use is
|
||||||
|
// 1.- the one that contrib more and pay less fee
|
||||||
|
// 2.- it takes more time before expires
|
||||||
|
|
||||||
|
//different exchanges may have different wireFee
|
||||||
|
//ranking should take the relative contribution in the exchange
|
||||||
|
//which is (value - denomFee / fixedFee)
|
||||||
|
const rate1 = Amounts.divmod(d1.value, d1.denomWithdraw).quotient;
|
||||||
|
const rate2 = Amounts.divmod(d2.value, d2.denomWithdraw).quotient;
|
||||||
|
const contribCmp = rate1 === rate2 ? 0 : rate1 < rate2 ? 1 : -1;
|
||||||
|
return (
|
||||||
|
contribCmp ||
|
||||||
|
Duration.cmp(d1.duration, d2.duration) ||
|
||||||
|
strcmp(d1.id, d2.id)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return copyList.map((info) => {
|
||||||
|
switch (mode) {
|
||||||
|
case TransactionAmountMode.Effective: {
|
||||||
|
//if the user instructed "effective" then we need to selected
|
||||||
|
//greedy total coin value
|
||||||
|
return {
|
||||||
|
info,
|
||||||
|
value: info.value,
|
||||||
|
total: Number.MAX_SAFE_INTEGER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case TransactionAmountMode.Raw: {
|
||||||
|
//if the user instructed "raw" then we need to selected
|
||||||
|
//greedy total coin raw amount (without fee)
|
||||||
|
return {
|
||||||
|
info,
|
||||||
|
value: Amounts.add(info.value, info.denomWithdraw).amount,
|
||||||
|
total: Number.MAX_SAFE_INTEGER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the list sorted for the best denom to deposit first
|
||||||
|
*
|
||||||
|
* @param denoms
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function rankDenominationForDeposit(
|
||||||
|
denoms: CoinInfo[],
|
||||||
|
mode: TransactionAmountMode,
|
||||||
|
): SelectableElement[] {
|
||||||
|
const copyList = [...denoms];
|
||||||
|
/**
|
||||||
|
* Rank coins
|
||||||
|
*/
|
||||||
|
copyList.sort((d1, d2) => {
|
||||||
|
// the best coin to use is
|
||||||
|
// 1.- the one that contrib more and pay less fee
|
||||||
|
// 2.- it takes more time before expires
|
||||||
|
|
||||||
|
//different exchanges may have different wireFee
|
||||||
|
//ranking should take the relative contribution in the exchange
|
||||||
|
//which is (value - denomFee / fixedFee)
|
||||||
|
const rate1 = Amounts.divmod(d1.value, d1.denomDeposit).quotient;
|
||||||
|
const rate2 = Amounts.divmod(d2.value, d2.denomDeposit).quotient;
|
||||||
|
const contribCmp = rate1 === rate2 ? 0 : rate1 < rate2 ? 1 : -1;
|
||||||
|
return (
|
||||||
|
contribCmp ||
|
||||||
|
Duration.cmp(d1.duration, d2.duration) ||
|
||||||
|
strcmp(d1.id, d2.id)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return copyList.map((info) => {
|
||||||
|
switch (mode) {
|
||||||
|
case TransactionAmountMode.Effective: {
|
||||||
|
//if the user instructed "effective" then we need to selected
|
||||||
|
//greedy total coin value
|
||||||
|
return {
|
||||||
|
info,
|
||||||
|
value: info.value,
|
||||||
|
total: info.totalAvailable ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case TransactionAmountMode.Raw: {
|
||||||
|
//if the user instructed "raw" then we need to selected
|
||||||
|
//greedy total coin raw amount (without fee)
|
||||||
|
return {
|
||||||
|
info,
|
||||||
|
value: Amounts.sub(info.value, info.denomDeposit).amount,
|
||||||
|
total: info.totalAvailable ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the list sorted for the best denom to withdraw first
|
||||||
|
*
|
||||||
|
* @param denoms
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function rankDenominationForRefresh(
|
||||||
|
denoms: CoinInfo[],
|
||||||
|
): Record<string, SelectableElement[]> {
|
||||||
|
const groupByExchange: Record<string, CoinInfo[]> = {};
|
||||||
|
for (const d of denoms) {
|
||||||
|
if (!groupByExchange[d.exchangeBaseUrl]) {
|
||||||
|
groupByExchange[d.exchangeBaseUrl] = [];
|
||||||
|
}
|
||||||
|
groupByExchange[d.exchangeBaseUrl].push(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Record<string, SelectableElement[]> = {};
|
||||||
|
for (const d of denoms) {
|
||||||
|
result[d.exchangeBaseUrl] = rankDenominationForWithdrawals(
|
||||||
|
groupByExchange[d.exchangeBaseUrl],
|
||||||
|
TransactionAmountMode.Raw,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SelectableElement {
|
||||||
|
total: number;
|
||||||
|
value: AmountJson;
|
||||||
|
info: CoinInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectGreedyCoins(
|
||||||
|
coins: SelectableElement[],
|
||||||
|
limit: AmountJson,
|
||||||
|
): SelectedCoins {
|
||||||
|
const result: SelectedCoins = {
|
||||||
|
totalValue: Amounts.zeroOfCurrency(limit.currency),
|
||||||
|
coins: [],
|
||||||
|
};
|
||||||
|
if (!coins.length) return result;
|
||||||
|
|
||||||
|
let denomIdx = 0;
|
||||||
|
iterateDenoms: while (denomIdx < coins.length) {
|
||||||
|
const denom = coins[denomIdx];
|
||||||
|
// let total = denom.total;
|
||||||
|
const left = Amounts.sub(limit, result.totalValue).amount;
|
||||||
|
|
||||||
|
if (Amounts.isZero(denom.value)) {
|
||||||
|
// 0 contribution denoms should be the last
|
||||||
|
break iterateDenoms;
|
||||||
|
}
|
||||||
|
|
||||||
|
//use Amounts.divmod instead of iterate
|
||||||
|
const div = Amounts.divmod(left, denom.value);
|
||||||
|
const size = Math.min(div.quotient, denom.total);
|
||||||
|
if (size > 0) {
|
||||||
|
const mul = Amounts.mult(denom.value, size).amount;
|
||||||
|
const progress = Amounts.add(result.totalValue, mul).amount;
|
||||||
|
|
||||||
|
result.totalValue = progress;
|
||||||
|
result.coins.push({ info: denom.info, size });
|
||||||
|
denom.total = denom.total - size;
|
||||||
|
}
|
||||||
|
|
||||||
|
//go next denom
|
||||||
|
denomIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AmountWithFee = { raw: AmountJson; effective: AmountJson };
|
||||||
|
type AmountAndRefresh = AmountWithFee & { refresh?: RefreshChoice };
|
||||||
|
|
||||||
|
export function getTotalEffectiveAndRawForDeposit(
|
||||||
|
list: { info: CoinInfo; size: number }[],
|
||||||
|
currency: string,
|
||||||
|
): AmountWithFee {
|
||||||
|
const init = {
|
||||||
|
raw: Amounts.zeroOfCurrency(currency),
|
||||||
|
effective: Amounts.zeroOfCurrency(currency),
|
||||||
|
};
|
||||||
|
return list.reduce((prev, cur) => {
|
||||||
|
const ef = Amounts.mult(cur.info.value, cur.size).amount;
|
||||||
|
const rw = Amounts.mult(
|
||||||
|
Amounts.sub(cur.info.value, cur.info.denomDeposit).amount,
|
||||||
|
cur.size,
|
||||||
|
).amount;
|
||||||
|
|
||||||
|
prev.effective = Amounts.add(prev.effective, ef).amount;
|
||||||
|
prev.raw = Amounts.add(prev.raw, rw).amount;
|
||||||
|
return prev;
|
||||||
|
}, init);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalEffectiveAndRawForWithdrawal(
|
||||||
|
list: { info: CoinInfo; size: number }[],
|
||||||
|
currency: string,
|
||||||
|
): AmountWithFee {
|
||||||
|
const init = {
|
||||||
|
raw: Amounts.zeroOfCurrency(currency),
|
||||||
|
effective: Amounts.zeroOfCurrency(currency),
|
||||||
|
};
|
||||||
|
return list.reduce((prev, cur) => {
|
||||||
|
const ef = Amounts.mult(cur.info.value, cur.size).amount;
|
||||||
|
const rw = Amounts.mult(
|
||||||
|
Amounts.add(cur.info.value, cur.info.denomWithdraw).amount,
|
||||||
|
cur.size,
|
||||||
|
).amount;
|
||||||
|
|
||||||
|
prev.effective = Amounts.add(prev.effective, ef).amount;
|
||||||
|
prev.raw = Amounts.add(prev.raw, rw).amount;
|
||||||
|
return prev;
|
||||||
|
}, init);
|
||||||
|
}
|
@ -19,7 +19,7 @@
|
|||||||
*
|
*
|
||||||
* Uses libtool's current:revision:age versioning.
|
* Uses libtool's current:revision:age versioning.
|
||||||
*/
|
*/
|
||||||
export const WALLET_EXCHANGE_PROTOCOL_VERSION = "15:0:2";
|
export const WALLET_EXCHANGE_PROTOCOL_VERSION = "17:0:0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protocol version spoken with the merchant.
|
* Protocol version spoken with the merchant.
|
||||||
|
@ -276,13 +276,6 @@ import {
|
|||||||
} from "./operations/withdraw.js";
|
} from "./operations/withdraw.js";
|
||||||
import { PendingTaskInfo, PendingTaskType } from "./pending-types.js";
|
import { PendingTaskInfo, PendingTaskType } from "./pending-types.js";
|
||||||
import { assertUnreachable } from "./util/assertUnreachable.js";
|
import { assertUnreachable } from "./util/assertUnreachable.js";
|
||||||
import {
|
|
||||||
convertDepositAmount,
|
|
||||||
convertPeerPushAmount,
|
|
||||||
convertWithdrawalAmount,
|
|
||||||
getMaxDepositAmount,
|
|
||||||
getMaxPeerPushAmount,
|
|
||||||
} from "./util/coinSelection.js";
|
|
||||||
import {
|
import {
|
||||||
createTimeline,
|
createTimeline,
|
||||||
selectBestForOverlappingDenominations,
|
selectBestForOverlappingDenominations,
|
||||||
@ -313,6 +306,13 @@ import {
|
|||||||
WalletCoreApiClient,
|
WalletCoreApiClient,
|
||||||
WalletCoreResponseType,
|
WalletCoreResponseType,
|
||||||
} from "./wallet-api-types.js";
|
} from "./wallet-api-types.js";
|
||||||
|
import {
|
||||||
|
convertDepositAmount,
|
||||||
|
getMaxDepositAmount,
|
||||||
|
convertPeerPushAmount,
|
||||||
|
getMaxPeerPushAmount,
|
||||||
|
convertWithdrawalAmount,
|
||||||
|
} from "./util/instructedAmountConversion.js";
|
||||||
|
|
||||||
const logger = new Logger("wallet.ts");
|
const logger = new Logger("wallet.ts");
|
||||||
|
|
||||||
|
@ -44,7 +44,6 @@
|
|||||||
"@types/web": "^0.0.82",
|
"@types/web": "^0.0.82",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/ws": "^8.5.3",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"axios": "^1.2.2",
|
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"esbuild": "^0.17.7",
|
"esbuild": "^0.17.7",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
@ -579,9 +579,6 @@ importers:
|
|||||||
'@gnu-taler/taler-wallet-core':
|
'@gnu-taler/taler-wallet-core':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../taler-wallet-core
|
version: link:../taler-wallet-core
|
||||||
axios:
|
|
||||||
specifier: ^0.27.2
|
|
||||||
version: 0.27.2
|
|
||||||
tslib:
|
tslib:
|
||||||
specifier: ^2.5.3
|
specifier: ^2.5.3
|
||||||
version: 2.5.3
|
version: 2.5.3
|
||||||
@ -678,9 +675,6 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^18.11.17
|
specifier: ^18.11.17
|
||||||
version: 18.11.17
|
version: 18.11.17
|
||||||
axios:
|
|
||||||
specifier: ^0.27.2
|
|
||||||
version: 0.27.2
|
|
||||||
big-integer:
|
big-integer:
|
||||||
specifier: ^1.6.51
|
specifier: ^1.6.51
|
||||||
version: 1.6.51
|
version: 1.6.51
|
||||||
@ -925,9 +919,6 @@ importers:
|
|||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.14
|
specifier: ^10.4.14
|
||||||
version: 10.4.14(postcss@8.4.23)
|
version: 10.4.14(postcss@8.4.23)
|
||||||
axios:
|
|
||||||
specifier: ^1.2.2
|
|
||||||
version: 1.2.2
|
|
||||||
chokidar:
|
chokidar:
|
||||||
specifier: ^3.5.3
|
specifier: ^3.5.3
|
||||||
version: 3.5.3
|
version: 3.5.3
|
||||||
@ -6804,6 +6795,7 @@ packages:
|
|||||||
|
|
||||||
/asynckit@0.4.0:
|
/asynckit@0.4.0:
|
||||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/at-least-node@1.0.0:
|
/at-least-node@1.0.0:
|
||||||
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
||||||
@ -6969,15 +6961,6 @@ packages:
|
|||||||
- debug
|
- debug
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/axios@0.27.2:
|
|
||||||
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: 1.15.2
|
|
||||||
form-data: 4.0.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/axios@1.1.3:
|
/axios@1.1.3:
|
||||||
resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
|
resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -6988,16 +6971,6 @@ packages:
|
|||||||
- debug
|
- debug
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/axios@1.2.2:
|
|
||||||
resolution: {integrity: sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==}
|
|
||||||
dependencies:
|
|
||||||
follow-redirects: 1.15.2
|
|
||||||
form-data: 4.0.0
|
|
||||||
proxy-from-env: 1.1.0
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- debug
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/axobject-query@2.2.0:
|
/axobject-query@2.2.0:
|
||||||
resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==}
|
resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -8069,6 +8042,7 @@ packages:
|
|||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream: 1.0.0
|
delayed-stream: 1.0.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/commander@2.17.1:
|
/commander@2.17.1:
|
||||||
resolution: {integrity: sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==}
|
resolution: {integrity: sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==}
|
||||||
@ -8918,6 +8892,7 @@ packages:
|
|||||||
/delayed-stream@1.0.0:
|
/delayed-stream@1.0.0:
|
||||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/depd@1.1.2:
|
/depd@1.1.2:
|
||||||
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
|
resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
|
||||||
@ -10528,6 +10503,7 @@ packages:
|
|||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
debug:
|
debug:
|
||||||
optional: true
|
optional: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/for-each@0.3.3:
|
/for-each@0.3.3:
|
||||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||||
@ -10604,6 +10580,7 @@ packages:
|
|||||||
asynckit: 0.4.0
|
asynckit: 0.4.0
|
||||||
combined-stream: 1.0.8
|
combined-stream: 1.0.8
|
||||||
mime-types: 2.1.35
|
mime-types: 2.1.35
|
||||||
|
dev: true
|
||||||
|
|
||||||
/forwarded@0.2.0:
|
/forwarded@0.2.0:
|
||||||
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
|
||||||
@ -12669,12 +12646,14 @@ packages:
|
|||||||
/mime-db@1.52.0:
|
/mime-db@1.52.0:
|
||||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/mime-types@2.1.35:
|
/mime-types@2.1.35:
|
||||||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-db: 1.52.0
|
mime-db: 1.52.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/mime@1.6.0:
|
/mime@1.6.0:
|
||||||
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
|
Loading…
Reference in New Issue
Block a user