aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-harness/src/integrationtests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-harness/src/integrationtests')
-rw-r--r--packages/taler-harness/src/integrationtests/test-account-restrictions.ts179
-rw-r--r--packages/taler-harness/src/integrationtests/test-age-restrictions-deposit.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-bank-api.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-currency-scope.ts10
-rw-r--r--packages/taler-harness/src/integrationtests/test-deposit.ts21
-rw-r--r--packages/taler-harness/src/integrationtests/test-exchange-management-fault.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-exchange-purse.ts1
-rw-r--r--packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-fee-regression.ts7
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts266
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts291
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts367
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts297
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts248
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts311
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-merchant-aggregate.ts292
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts413
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts410
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts356
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts347
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts322
-rw-r--r--packages/taler-harness/src/integrationtests/test-kyc.ts139
-rw-r--r--packages/taler-harness/src/integrationtests/test-libeufin-bank.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-merchant-categories.ts106
-rw-r--r--packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts38
-rw-r--r--packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts6
-rw-r--r--packages/taler-harness/src/integrationtests/test-merchant-instances.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-multiexchange.ts10
-rw-r--r--packages/taler-harness/src/integrationtests/test-payment-fault.ts6
-rw-r--r--packages/taler-harness/src/integrationtests/test-payment-multiple.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts14
-rw-r--r--packages/taler-harness/src/integrationtests/test-refund-auto.ts20
-rw-r--r--packages/taler-harness/src/integrationtests/test-repurchase.ts164
-rw-r--r--packages/taler-harness/src/integrationtests/test-revocation.ts9
-rw-r--r--packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-timetravel-withdraw.ts2
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-balance.ts1
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts6
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-dbless.ts10
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-dd48.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts160
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-network-availability.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-notifications.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-observability.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-refresh.ts6
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-wirefees.ts8
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallettesting.ts11
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts20
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-external.ts101
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts4
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-idempotent.ts172
-rw-r--r--packages/taler-harness/src/integrationtests/test-withdrawal-prepare.ts78
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts36
54 files changed, 5133 insertions, 212 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-account-restrictions.ts b/packages/taler-harness/src/integrationtests/test-account-restrictions.ts
new file mode 100644
index 000000000..c0c85642c
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-account-restrictions.ts
@@ -0,0 +1,179 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ AmountString,
+ j2s,
+ Logger,
+ NotificationType,
+ TalerCorebankApiClient,
+ TransactionMajorState,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import {
+ ExchangeServiceInterface,
+ GlobalTestState,
+ WalletClient,
+} from "../harness/harness.js";
+import {
+ createSimpleTestkudosEnvironmentV3,
+ WithdrawViaBankResult,
+} from "../harness/helpers.js";
+
+const logger = new Logger("test-account-restrictions.ts");
+
+/**
+ * Test for credit/debit account restrictions.
+ */
+export async function runAccountRestrictionsTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange } =
+ await createSimpleTestkudosEnvironmentV3(t, undefined, {
+ accountRestrictions: [
+ [
+ "debit-restriction",
+ "regex",
+ "payto://x-taler-bank/.*/foo-.*",
+ "bla",
+ "{}",
+ ],
+ [
+ "credit-restriction",
+ "regex",
+ "payto://x-taler-bank/.*/foo-.*",
+ "bla",
+ "{}",
+ ],
+ ],
+ });
+
+ // Withdraw digital cash into the wallet.
+
+ const withdrawalResult = await myWithdrawViaBank(t, {
+ walletClient,
+ bankClient,
+ exchange,
+ amount: "TESTKUDOS:20",
+ acctname: "foo-123",
+ });
+
+ await withdrawalResult.withdrawalFinishedCond;
+
+ // When withdrawing from an account that doesn't begin with "foo-",
+ // it fails.
+ await t.assertThrowsAsync(async () => {
+ await myWithdrawViaBank(t, {
+ walletClient,
+ bankClient,
+ exchange,
+ amount: "TESTKUDOS:20",
+ acctname: "bar-123",
+ });
+ });
+
+ // Invalid account, does not start with "foo-"
+ const err = await t.assertThrowsTalerErrorAsync(async () => {
+ await walletClient.call(WalletApiOperation.CheckDeposit, {
+ amount: "TESTKUDOS:5",
+ depositPaytoUri: "payto://x-taler-bank/localhost/bar-42",
+ });
+ });
+
+ logger.info(`checkResp ${j2s(err)}`);
+
+ // Valid account
+ await walletClient.call(WalletApiOperation.CheckDeposit, {
+ amount: "TESTKUDOS:5",
+ depositPaytoUri: "payto://x-taler-bank/localhost/foo-42",
+ });
+}
+
+export async function myWithdrawViaBank(
+ t: GlobalTestState,
+ p: {
+ walletClient: WalletClient;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeServiceInterface;
+ amount: AmountString | string;
+ restrictAge?: number;
+ acctname: string;
+ },
+): Promise<WithdrawViaBankResult> {
+ const { walletClient: wallet, bankClient, exchange, amount } = p;
+ await bankClient.registerAccountExtended({
+ name: p.acctname,
+ password: "test",
+ username: p.acctname,
+ });
+ const user = {
+ password: "test",
+ username: p.acctname,
+ };
+ const accountPaytoUri = `payto://x-taler-bank/localhost/${p.acctname}?receiver-name=${p.acctname}`;
+ const bankClient2 = new TalerCorebankApiClient(bankClient.baseUrl);
+ bankClient2.setAuth({
+ username: user.username,
+ password: user.password,
+ });
+
+ const wop = await bankClient2.createWithdrawalOperation(
+ user.username,
+ amount,
+ );
+
+ // Hand it to the wallet
+
+ await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ restrictAge: p.restrictAge,
+ });
+
+ // Withdraw (AKA select)
+
+ const acceptRes = await wallet.client.call(
+ WalletApiOperation.AcceptBankIntegratedWithdrawal,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ restrictAge: p.restrictAge,
+ },
+ );
+
+ const withdrawalFinishedCond = wallet.waitForNotificationCond(
+ (x) =>
+ x.type === NotificationType.TransactionStateTransition &&
+ x.newTxState.major === TransactionMajorState.Done &&
+ x.transactionId === acceptRes.transactionId,
+ );
+
+ // Confirm it
+
+ await bankClient2.confirmWithdrawalOperation(user.username, {
+ withdrawalOperationId: wop.withdrawal_id,
+ });
+
+ return {
+ accountPaytoUri,
+ withdrawalFinishedCond,
+ transactionId: acceptRes.transactionId,
+ };
+}
+
+runAccountRestrictionsTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-age-restrictions-deposit.ts b/packages/taler-harness/src/integrationtests/test-age-restrictions-deposit.ts
index a0e97c218..aa107696c 100644
--- a/packages/taler-harness/src/integrationtests/test-age-restrictions-deposit.ts
+++ b/packages/taler-harness/src/integrationtests/test-age-restrictions-deposit.ts
@@ -24,7 +24,7 @@ import {
TransactionMinorState,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState, generateRandomPayto } from "../harness/harness.js";
+import { GlobalTestState, getTestHarnessPaytoForLabel } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironmentV3,
withdrawViaBankV3,
@@ -83,7 +83,7 @@ export async function runAgeRestrictionsDepositTest(t: GlobalTestState) {
WalletApiOperation.CreateDepositGroup,
{
amount: "TESTKUDOS:10" as AmountString,
- depositPaytoUri: generateRandomPayto("foo"),
+ depositPaytoUri: getTestHarnessPaytoForLabel("foo"),
transactionId: depositTxId,
},
);
diff --git a/packages/taler-harness/src/integrationtests/test-bank-api.ts b/packages/taler-harness/src/integrationtests/test-bank-api.ts
index 58f8bb106..544957185 100644
--- a/packages/taler-harness/src/integrationtests/test-bank-api.ts
+++ b/packages/taler-harness/src/integrationtests/test-bank-api.ts
@@ -30,7 +30,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
@@ -66,7 +66,7 @@ export async function runBankApiTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
let wireGatewayApiBaseUrl = new URL("accounts/exchange/taler-wire-gateway/", bank.baseUrl).href;
await exchange.addBankAccount("1", {
@@ -95,13 +95,13 @@ export async function runBankApiTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
diff --git a/packages/taler-harness/src/integrationtests/test-currency-scope.ts b/packages/taler-harness/src/integrationtests/test-currency-scope.ts
index 34d18d87d..48502f6b7 100644
--- a/packages/taler-harness/src/integrationtests/test-currency-scope.ts
+++ b/packages/taler-harness/src/integrationtests/test-currency-scope.ts
@@ -26,7 +26,7 @@ import {
GlobalTestState,
HarnessExchangeBankAccount,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -81,7 +81,7 @@ export async function runCurrencyScopeTest(t: GlobalTestState) {
).href,
accountName: "myexchange",
accountPassword: "x",
- accountPaytoUri: generateRandomPayto("myexchange"),
+ accountPaytoUri: getTestHarnessPaytoForLabel("myexchange"),
};
let exchangeTwoBankAccount: HarnessExchangeBankAccount = {
@@ -91,7 +91,7 @@ export async function runCurrencyScopeTest(t: GlobalTestState) {
).href,
accountName: "myexchange2",
accountPassword: "x",
- accountPaytoUri: generateRandomPayto("myexchange2"),
+ accountPaytoUri: getTestHarnessPaytoForLabel("myexchange2"),
};
bank.setSuggestedExchange(
@@ -151,7 +151,7 @@ export async function runCurrencyScopeTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -160,7 +160,7 @@ export async function runCurrencyScopeTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
diff --git a/packages/taler-harness/src/integrationtests/test-deposit.ts b/packages/taler-harness/src/integrationtests/test-deposit.ts
index 0879c9e9f..654829c91 100644
--- a/packages/taler-harness/src/integrationtests/test-deposit.ts
+++ b/packages/taler-harness/src/integrationtests/test-deposit.ts
@@ -25,7 +25,7 @@ import {
j2s,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState, generateRandomPayto } from "../harness/harness.js";
+import { GlobalTestState, getTestHarnessPaytoForLabel } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironmentV3,
withdrawViaBankV3,
@@ -51,6 +51,23 @@ export async function runDepositTest(t: GlobalTestState) {
await withdrawalResult.withdrawalFinishedCond;
+ const depositPaytoUri = getTestHarnessPaytoForLabel("foo");
+
+ const bal = await walletClient.call(WalletApiOperation.GetBalances, {});
+
+ t.assertAmountEquals(bal.balances[0].available, "TESTKUDOS:19.53");
+
+ const maxDepositResp = await walletClient.call(
+ WalletApiOperation.GetMaxDepositAmount,
+ {
+ currency: "TESTKUDOS",
+ depositPaytoUri,
+ },
+ );
+
+ t.assertAmountEquals(maxDepositResp.rawAmount, "TESTKUDOS:19.09");
+ t.assertAmountEquals(maxDepositResp.effectiveAmount, "TESTKUDOS:19.53");
+
const dgIdResp = await walletClient.client.call(
WalletApiOperation.GenerateDepositGroupTxId,
{},
@@ -77,7 +94,7 @@ export async function runDepositTest(t: GlobalTestState) {
WalletApiOperation.CreateDepositGroup,
{
amount: "TESTKUDOS:10" as AmountString,
- depositPaytoUri: generateRandomPayto("foo"),
+ depositPaytoUri,
transactionId: depositTxId,
},
);
diff --git a/packages/taler-harness/src/integrationtests/test-exchange-management-fault.ts b/packages/taler-harness/src/integrationtests/test-exchange-management-fault.ts
index 801162ac8..9bdb0d93b 100644
--- a/packages/taler-harness/src/integrationtests/test-exchange-management-fault.ts
+++ b/packages/taler-harness/src/integrationtests/test-exchange-management-fault.ts
@@ -36,7 +36,7 @@ import {
GlobalTestState,
MerchantService,
WalletCli,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
@@ -74,7 +74,7 @@ export async function runExchangeManagementFaultTest(
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -111,13 +111,13 @@ export async function runExchangeManagementFaultTest(
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
diff --git a/packages/taler-harness/src/integrationtests/test-exchange-purse.ts b/packages/taler-harness/src/integrationtests/test-exchange-purse.ts
index 6666e2d0b..68dd58d3e 100644
--- a/packages/taler-harness/src/integrationtests/test-exchange-purse.ts
+++ b/packages/taler-harness/src/integrationtests/test-exchange-purse.ts
@@ -148,6 +148,7 @@ export async function runExchangePurseTest(t: GlobalTestState) {
contribution: amount,
denomPubHash: coin.denomPubHash,
denomSig: coin.denomSig,
+ feeDeposit: d1.fees.feeDeposit,
};
const depositSigsResp = await cryptoApi.signPurseDeposits({
diff --git a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
index 4f2fb1ee4..828289373 100644
--- a/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
+++ b/packages/taler-harness/src/integrationtests/test-exchange-timetravel.ts
@@ -35,7 +35,7 @@ import { makeNoFeeCoinConfig } from "../harness/denomStructures.js";
import {
BankService,
ExchangeService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
GlobalTestState,
MerchantService,
setupDb,
@@ -128,7 +128,7 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -171,13 +171,13 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
diff --git a/packages/taler-harness/src/integrationtests/test-fee-regression.ts b/packages/taler-harness/src/integrationtests/test-fee-regression.ts
index 6ae7b5de8..b08fce4cb 100644
--- a/packages/taler-harness/src/integrationtests/test-fee-regression.ts
+++ b/packages/taler-harness/src/integrationtests/test-fee-regression.ts
@@ -27,7 +27,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -70,7 +70,7 @@ export async function createMyTestkudosEnvironment(
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -170,7 +170,7 @@ export async function createMyTestkudosEnvironment(
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
@@ -189,6 +189,7 @@ export async function createMyTestkudosEnvironment(
walletClient,
walletService,
bankClient,
+ bank,
exchangeBankAccount: {
accountName: "",
accountPassword: "",
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts
new file mode 100644
index 000000000..5e7a4756e
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-balance-withdrawal.ts
@@ -0,0 +1,266 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ encodeCrock,
+ ExchangeWalletKycStatus,
+ hashPaytoUri,
+ j2s,
+ TalerCorebankApiClient,
+ TransactionIdStr,
+ TransactionMajorState,
+ TransactionMinorState,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ EnvOptions,
+ postAmlDecisionNoRules,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ exchange.addCoinConfigList(coinConfig);
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "balance");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:10");
+ config.setString("KYC-RULE-R1", "timeframe", "forever");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycBalanceWithdrawalTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ amount: "TESTKUDOS:20",
+ bankClient,
+ exchange,
+ walletClient,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: wres.transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.BalanceKycRequired,
+ },
+ });
+
+ {
+ const exchangeEntry = await walletClient.call(
+ WalletApiOperation.GetExchangeEntryByUrl,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ },
+ );
+ console.log(j2s(exchangeEntry));
+ }
+
+ await walletClient.call(WalletApiOperation.TestingWaitExchangeState, {
+ exchangeBaseUrl: exchange.baseUrl,
+ walletKycStatus: ExchangeWalletKycStatus.Legi,
+ });
+
+ {
+ const exchangeEntry = await walletClient.call(
+ WalletApiOperation.GetExchangeEntryByUrl,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ },
+ );
+ console.log(j2s(exchangeEntry));
+ t.assertDeepEqual(
+ exchangeEntry.walletKycStatus,
+ ExchangeWalletKycStatus.Legi,
+ );
+
+ const kycReservePub = exchangeEntry.walletKycReservePub;
+ t.assertTrue(!!kycReservePub);
+
+ // FIXME: Create/user helper function for this!
+ const hPayto = hashPaytoUri(
+ `payto://taler-reserve-http/localhost:${exchange.port}/${kycReservePub}`,
+ );
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: encodeCrock(hPayto),
+ });
+ }
+
+ // Now after KYC is done for the balance, the withdrawal should finish
+ await wres.withdrawalFinishedCond;
+}
+
+runKycBalanceWithdrawalTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts
new file mode 100644
index 000000000..fb0e7d79e
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-deposit-aggregate.ts
@@ -0,0 +1,291 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ TalerCorebankApiClient,
+ TransactionMajorState,
+ TransactionMinorState,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ EnvOptions,
+ postAmlDecisionNoRules,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "aggregate");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycDepositAggregateTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:50",
+ exchange: exchange,
+ walletClient: walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ const depositResp = await walletClient.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:10",
+ depositPaytoUri: wres.accountPaytoUri,
+ },
+ );
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Track,
+ },
+ });
+
+ await exchange.runAggregatorOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60 * 3,
+ });
+
+ console.log("waiting for kyc-required");
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: depositResp.transactionId,
+ },
+ );
+
+ const kycPaytoHash = txDetails.kycPaytoHash;
+
+ t.assertTrue(!!kycPaytoHash);
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: kycPaytoHash,
+ });
+
+ await exchange.runAggregatorOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60 * 3,
+ });
+
+ await exchange.runTransferOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60 * 3,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+}
+
+runKycDepositAggregateTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts
new file mode 100644
index 000000000..b91c32a71
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit-kyctransfer.ts
@@ -0,0 +1,367 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ Logger,
+ parsePaytoUri,
+ TalerCorebankApiClient,
+ TransactionMajorState,
+ TransactionMinorState,
+ TransactionType,
+ WireGatewayApiClient,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ EnvOptions,
+ postAmlDecisionNoRules,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+const logger = new Logger("test-kyc-deposit-deposit.ts");
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ const wireGatewayApiBaseUrl = new URL(
+ `accounts/${exchangeBankUsername}/taler-wire-gateway/`,
+ bank.corebankApiBaseUrl,
+ ).href;
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "deposit");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: exchangePaytoUri,
+ wireGatewayApiBaseUrl,
+ },
+ };
+}
+
+export async function runKycDepositDepositKyctransferTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ walletClient,
+ bankClient,
+ exchange,
+ amlKeypair,
+ exchangeBankAccount,
+ } = await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:50",
+ exchange: exchange,
+ walletClient: walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ const depositPaytoUri = getTestHarnessPaytoForLabel("deposit-test");
+
+ await bankClient.registerAccountExtended({
+ name: "deposit-test",
+ password: "test",
+ username: "deposit-test",
+ is_taler_exchange: false,
+ payto_uri: depositPaytoUri,
+ });
+
+ const depositResp = await walletClient.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:10",
+ depositPaytoUri,
+ },
+ );
+
+ console.log("waiting for kyc-required");
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycAuthRequired,
+ },
+ });
+
+ t.logStep("kyc-auth-requested");
+
+ const wireGatewayApiClient = new WireGatewayApiClient(
+ exchangeBankAccount.wireGatewayApiBaseUrl,
+ {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ },
+ );
+
+ {
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: depositResp.transactionId,
+ },
+ );
+
+ t.assertDeepEqual(txDetails.type, TransactionType.Deposit);
+ const kycTx = txDetails.kycAuthTransferInfo;
+ t.assertTrue(!!kycTx);
+
+ logger.info(`account pub: ${kycTx.accountPub}`);
+
+ await wireGatewayApiClient.adminAddKycauth({
+ amount: "TESTKUDOS:0.1",
+ debitAccountPayto: depositPaytoUri,
+ accountPub: kycTx.accountPub,
+ });
+ }
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: depositResp.transactionId,
+ },
+ );
+
+ {
+ const kycAuthCreditPayto =
+ txDetails.kycAuthTransferInfo?.creditPaytoUris[0];
+ t.assertTrue(!!kycAuthCreditPayto);
+ const p = parsePaytoUri(kycAuthCreditPayto);
+ t.assertTrue(!!p);
+ t.assertAmountEquals(p.params["amount"], "TESTKUDOS:0.01");
+ }
+
+ const kycPaytoHash = txDetails.kycPaytoHash;
+
+ t.assertTrue(!!kycPaytoHash);
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: kycPaytoHash,
+ });
+
+ logger.info(`made decision to have no rules on ${kycPaytoHash}`);
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: [
+ {
+ major: TransactionMajorState.Done,
+ },
+ {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Track,
+ },
+ ],
+ });
+
+ await exchange.runAggregatorOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60,
+ });
+
+ await exchange.runTransferOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+}
+
+runKycDepositDepositKyctransferTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts
new file mode 100644
index 000000000..10f34a56a
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts
@@ -0,0 +1,297 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ Logger,
+ TalerCorebankApiClient,
+ TransactionMajorState,
+ TransactionMinorState,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ EnvOptions,
+ postAmlDecisionNoRules,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+const logger = new Logger("test-kyc-deposit-deposit.ts");
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "deposit");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycDepositDepositTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:50",
+ exchange: exchange,
+ walletClient: walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ const depositResp = await walletClient.call(
+ WalletApiOperation.CreateDepositGroup,
+ {
+ amount: "TESTKUDOS:10",
+ depositPaytoUri: wres.accountPaytoUri,
+ },
+ );
+
+ console.log("waiting for kyc-required");
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: depositResp.transactionId,
+ },
+ );
+
+ const kycPaytoHash = txDetails.kycPaytoHash;
+
+ t.assertTrue(!!kycPaytoHash);
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: kycPaytoHash,
+ });
+
+ logger.info(`made decision to have no rules on ${kycPaytoHash}`);
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: [
+ {
+ major: TransactionMajorState.Done,
+ },
+ {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Track,
+ },
+ ],
+ });
+
+ await exchange.runAggregatorOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60,
+ });
+
+ await exchange.runTransferOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: depositResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+}
+
+runKycDepositDepositTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts b/packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts
new file mode 100644
index 000000000..fbbaf382d
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-exchange-wallet.ts
@@ -0,0 +1,248 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ encodeCrock,
+ ExchangeWalletKycStatus,
+ hashPaytoUri,
+ j2s,
+ TalerCorebankApiClient,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import { EnvOptions, postAmlDecisionNoRules } from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ exchange.addCoinConfigList(coinConfig);
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "balance");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "forever");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ amlKeypair,
+ };
+}
+
+export async function runKycExchangeWalletTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ await walletClient.call(WalletApiOperation.AddExchange, {
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+
+ await walletClient.call(WalletApiOperation.StartExchangeWalletKyc, {
+ amount: "TESTKUDOS:20",
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitExchangeWalletKyc, {
+ amount: "TESTKUDOS:20",
+ exchangeBaseUrl: exchange.baseUrl,
+ passed: false,
+ });
+
+ const exchangeEntry = await walletClient.call(
+ WalletApiOperation.GetExchangeEntryByUrl,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ },
+ );
+
+ console.log(j2s(exchangeEntry));
+
+ t.assertDeepEqual(
+ exchangeEntry.walletKycStatus,
+ ExchangeWalletKycStatus.Legi,
+ );
+
+ const kycReservePub = exchangeEntry.walletKycReservePub;
+
+ t.assertTrue(!!kycReservePub);
+
+ // FIXME: Create/user helper function for this!
+ const hPayto = hashPaytoUri(
+ `payto://taler-reserve-http/localhost:${exchange.port}/${kycReservePub}`,
+ );
+
+ console.log(`hPayto: ${hPayto}`);
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: encodeCrock(hPayto),
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitExchangeWalletKyc, {
+ amount: "TESTKUDOS:20",
+ exchangeBaseUrl: exchange.baseUrl,
+ passed: true,
+ });
+}
+
+runKycExchangeWalletTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts
new file mode 100644
index 000000000..6a8a13ab9
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-form-withdrawal.ts
@@ -0,0 +1,311 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ codecForAny,
+ codecForKycProcessClientInformation,
+ decodeCrock,
+ encodeCrock,
+ j2s,
+ signAmlQuery,
+ TalerCorebankApiClient,
+ TransactionIdStr,
+ TransactionMajorState,
+ TransactionMinorState,
+} from "@gnu-taler/taler-util";
+import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ harnessHttpLib,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import { EnvOptions, withdrawViaBankV3 } from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ exchange.addCoinConfigList(coinConfig);
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1 M2");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("KYC-MEASURE-M2", "check_name", "C2");
+ config.setString("KYC-MEASURE-M2", "context", "{}");
+ config.setString("KYC-MEASURE-M2", "program", "P2");
+
+ config.setString(
+ "AML-PROGRAM-P1",
+ "command",
+ "taler-exchange-helper-measure-test-form",
+ );
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString(
+ "AML-PROGRAM-P1",
+ "description",
+ "test for full_name and birthdate",
+ );
+ config.setString("AML-PROGRAM-P1", "description_i18n", "{}");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("AML-PROGRAM-P2", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P2", "enabled", "true");
+ config.setString("AML-PROGRAM-P2", "description", "does nothing");
+ config.setString("AML-PROGRAM-P2", "description_i18n", "{}");
+ config.setString("AML-PROGRAM-P2", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "FORM");
+ config.setString("KYC-CHECK-C1", "form_name", "myform");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "description_i18n", "{}");
+ config.setString("KYC-CHECK-C1", "outputs", "full_name birthdate");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C2", "type", "INFO");
+ config.setString("KYC-CHECK-C2", "description", "my check info!");
+ config.setString("KYC-CHECK-C2", "description_i18n", "{}");
+ config.setString("KYC-CHECK-C2", "fallback", "M2");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycFormWithdrawalTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ amount: "TESTKUDOS:20",
+ bankClient,
+ exchange,
+ walletClient,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: wres.transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: wres.transactionId,
+ },
+ );
+
+ console.log(j2s(txDetails));
+ const accessToken = txDetails.kycAccessToken;
+ t.assertTrue(!!accessToken);
+
+ const infoResp = await harnessHttpLib.fetch(
+ new URL(`kyc-info/${txDetails.kycAccessToken}`, exchange.baseUrl).href,
+ );
+
+ const clientInfo = await readResponseJsonOrThrow(
+ infoResp,
+ codecForKycProcessClientInformation(),
+ );
+
+ console.log(j2s(clientInfo));
+
+ const kycId = clientInfo.requirements.find((x) => x.id != null)?.id;
+ t.assertTrue(!!kycId);
+
+ const uploadResp = await harnessHttpLib.fetch(
+ new URL(`kyc-upload/${kycId}`, exchange.baseUrl).href,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: "full_name=Alice+Abc&birthdate=2000-01-01",
+ },
+ );
+
+ console.log("resp status", uploadResp.status);
+
+ t.assertDeepEqual(uploadResp.status, 204);
+
+ const sig = signAmlQuery(decodeCrock(amlKeypair.priv));
+
+ const decisionsResp = await harnessHttpLib.fetch(
+ new URL(`aml/${amlKeypair.pub}/decisions`, exchange.baseUrl).href,
+ {
+ headers: {
+ "Taler-AML-Officer-Signature": encodeCrock(sig),
+ },
+ },
+ );
+
+ const decisions = await readResponseJsonOrThrow(decisionsResp, codecForAny());
+ console.log(j2s(decisions));
+
+ t.assertDeepEqual(decisionsResp.status, 200);
+
+ // KYC should pass now
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: wres.transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+}
+
+runKycFormWithdrawalTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-aggregate.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-aggregate.ts
new file mode 100644
index 000000000..3b32656ae
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-merchant-aggregate.ts
@@ -0,0 +1,292 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 { Duration, j2s, TalerCorebankApiClient } from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ harnessHttpLib,
+ MerchantService,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ EnvOptions,
+ makeTestPaymentV2,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+ merchant: MerchantService;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "aggregate");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ const merchant = await MerchantService.create(t, {
+ name: "testmerchant-1",
+ currency: "TESTKUDOS",
+ httpPort: 8083,
+ database: db.connStr,
+ });
+
+ merchant.addExchange(exchange);
+
+ if (opts.additionalMerchantConfig) {
+ opts.additionalMerchantConfig(merchant);
+ }
+ await merchant.start();
+ await merchant.pingUntilAvailable();
+
+ await merchant.addInstanceWithWireAccount({
+ id: "default",
+ name: "Default Instance",
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ await merchant.addInstanceWithWireAccount({
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ merchant,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycMerchantAggregateTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { merchant, walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:50",
+ exchange: exchange,
+ walletClient: walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ await makeTestPaymentV2(t, {
+ merchant,
+ walletClient,
+ order: {
+ amount: "TESTKUDOS:20",
+ summary: "hello",
+ },
+ });
+
+ await exchange.runAggregatorOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60,
+ });
+
+ await exchange.runTransferOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60,
+ });
+
+ t.logStep("start-run-kyccheck");
+
+ await merchant.runReconciliationOnceWithTimetravel({
+ timetravelMicroseconds: 1000 * 1000 * 60 * 60,
+ });
+
+ t.logStep("start-request-kyc");
+ const kycStatusUrl = new URL("private/kyc", merchant.makeInstanceBaseUrl());
+ const resp = await harnessHttpLib.fetch(kycStatusUrl.href);
+
+ console.log(`mechant kyc status: ${resp.status}`);
+
+ t.assertDeepEqual(resp.status, 200);
+
+ console.log(j2s(await resp.json()));
+}
+
+runKycMerchantAggregateTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts
new file mode 100644
index 000000000..73449ecb7
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts
@@ -0,0 +1,413 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ codecForAccountKycRedirects,
+ codecForKycProcessClientInformation,
+ codecForQueryInstancesResponse,
+ Duration,
+ encodeCrock,
+ hashPaytoUri,
+ j2s,
+ Logger,
+ MerchantAccountKycRedirectsResponse,
+ TalerCorebankApiClient,
+ WireGatewayApiClient,
+} from "@gnu-taler/taler-util";
+import {
+ readResponseJsonOrThrow,
+ readSuccessResponseJsonOrThrow,
+} from "@gnu-taler/taler-util/http";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ harnessHttpLib,
+ MerchantService,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ EnvOptions,
+ postAmlDecisionNoRules,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+const logger = new Logger(`test-kyc-merchant-deposit.ts`);
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+ merchant: MerchantService;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ const wireGatewayApiBaseUrl = new URL(
+ `accounts/${exchangeBankUsername}/taler-wire-gateway/`,
+ bank.corebankApiBaseUrl,
+ ).href;
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "deposit");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:0");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ const merchant = await MerchantService.create(t, {
+ name: "testmerchant-1",
+ currency: "TESTKUDOS",
+ httpPort: 8083,
+ database: db.connStr,
+ });
+
+ merchant.addExchange(exchange);
+
+ if (opts.additionalMerchantConfig) {
+ opts.additionalMerchantConfig(merchant);
+ }
+ await merchant.start();
+ await merchant.pingUntilAvailable();
+
+ await merchant.addInstanceWithWireAccount({
+ id: "default",
+ name: "Default Instance",
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ await merchant.addInstanceWithWireAccount({
+ id: "minst1",
+ name: "minst1",
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
+ defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ merchant,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl,
+ },
+ };
+}
+
+export async function runKycMerchantDepositTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ merchant,
+ walletClient,
+ bankClient,
+ exchange,
+ exchangeBankAccount,
+ amlKeypair,
+ } = await createKycTestkudosEnvironment(t);
+
+ let accountPub: string;
+
+ {
+ const instanceUrl = new URL("private", merchant.makeInstanceBaseUrl());
+ const resp = await harnessHttpLib.fetch(instanceUrl.href);
+ const parsedResp = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForQueryInstancesResponse(),
+ );
+ accountPub = parsedResp.merchant_pub;
+ }
+
+ const wireGatewayApiClient = new WireGatewayApiClient(
+ exchangeBankAccount.wireGatewayApiBaseUrl,
+ {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ },
+ );
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:50",
+ exchange: exchange,
+ walletClient: walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ let kycRespOne: MerchantAccountKycRedirectsResponse | undefined = undefined;
+
+ while (1) {
+ const kycStatusUrl = new URL("private/kyc", merchant.makeInstanceBaseUrl())
+ .href;
+ logger.info(`requesting GET ${kycStatusUrl}`);
+ const resp = await harnessHttpLib.fetch(kycStatusUrl);
+ if (resp.status === 200) {
+ kycRespOne = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForAccountKycRedirects(),
+ );
+ break;
+ }
+ // Wait 500ms
+ await new Promise<void>((resolve) => {
+ setTimeout(() => resolve(), 500);
+ });
+ }
+
+ t.assertTrue(!!kycRespOne);
+
+ logger.info(`mechant kyc status: ${j2s(kycRespOne)}`);
+
+ await wireGatewayApiClient.adminAddKycauth({
+ amount: "TESTKUDOS:0.1",
+ debitAccountPayto: kycRespOne.kyc_data[0].payto_uri,
+ accountPub,
+ });
+
+ let kycRespTwo: MerchantAccountKycRedirectsResponse | undefined = undefined;
+
+ // We do this in a loop as a work-around.
+ // Not exactly the correct behavior from the merchant right now.
+ while (true) {
+ const kycStatusLongpollUrl = new URL(
+ "private/kyc",
+ merchant.makeInstanceBaseUrl(),
+ );
+ kycStatusLongpollUrl.searchParams.set("lpt", "1");
+ const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href);
+ t.assertDeepEqual(resp.status, 200);
+ const parsedResp = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForAccountKycRedirects(),
+ );
+ logger.info(`kyc resp 2: ${j2s(parsedResp)}`);
+ if (parsedResp.kyc_data[0].payto_kycauths == null) {
+ kycRespTwo = parsedResp;
+ break;
+ }
+ // Wait 500ms
+ await new Promise<void>((resolve) => {
+ setTimeout(() => resolve(), 500);
+ });
+ }
+
+ t.assertTrue(!!kycRespTwo);
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: encodeCrock(hashPaytoUri(kycRespTwo.kyc_data[0].payto_uri)),
+ });
+
+ // We do this in a loop as a work-around.
+ // Not exactly the correct behavior from the merchant right now.
+ while (true) {
+ const kycStatusLongpollUrl = new URL(
+ "private/kyc",
+ merchant.makeInstanceBaseUrl(),
+ );
+ kycStatusLongpollUrl.searchParams.set("lpt", "3");
+ const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href);
+ t.assertDeepEqual(resp.status, 200);
+ const parsedResp = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForAccountKycRedirects(),
+ );
+ logger.info(`kyc resp 3: ${j2s(parsedResp)}`);
+ if ((parsedResp.kyc_data[0].limits?.length ?? 0) == 0) {
+ break;
+ }
+
+ const accessToken = parsedResp.kyc_data[0].access_token;
+
+ t.assertTrue(!!accessToken);
+
+ const infoResp = await harnessHttpLib.fetch(
+ new URL(`kyc-info/${accessToken}`, exchange.baseUrl).href,
+ );
+
+ const clientInfo = await readResponseJsonOrThrow(
+ infoResp,
+ codecForKycProcessClientInformation(),
+ );
+
+ logger.info(`kyc-info: ${j2s(clientInfo)}`);
+
+ // Wait 500ms
+ await new Promise<void>((resolve) => {
+ setTimeout(() => resolve(), 500);
+ });
+ }
+}
+
+runKycMerchantDepositTest.suites = ["wallet", "merchant", "kyc"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts b/packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts
new file mode 100644
index 000000000..c0d3881a0
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-new-measure.ts
@@ -0,0 +1,410 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ codecForAny,
+ codecForKycProcessClientInformation,
+ decodeCrock,
+ encodeCrock,
+ j2s,
+ signAmlQuery,
+ TalerCorebankApiClient,
+ TalerProtocolTimestamp,
+ TransactionIdStr,
+ TransactionMajorState,
+ TransactionMinorState,
+} from "@gnu-taler/taler-util";
+import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ harnessHttpLib,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ EnvOptions,
+ postAmlDecision,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ exchange.addCoinConfigList(coinConfig);
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1 M2");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("KYC-MEASURE-M2", "check_name", "C2");
+ config.setString("KYC-MEASURE-M2", "context", "{}");
+ config.setString("KYC-MEASURE-M2", "program", "P2");
+
+ config.setString("KYC-MEASURE-M3", "check_name", "C3");
+ config.setString("KYC-MEASURE-M3", "context", "{}");
+ config.setString("KYC-MEASURE-M3", "program", "P2");
+
+ config.setString(
+ "AML-PROGRAM-P1",
+ "command",
+ "taler-exchange-helper-measure-test-form",
+ );
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString(
+ "AML-PROGRAM-P1",
+ "description",
+ "test for full_name and birthdate",
+ );
+ config.setString("AML-PROGRAM-P1", "description_i18n", "{}");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("AML-PROGRAM-P2", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P2", "enabled", "true");
+ config.setString("AML-PROGRAM-P2", "description", "does nothing");
+ config.setString("AML-PROGRAM-P2", "description_i18n", "{}");
+ config.setString("AML-PROGRAM-P2", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "FORM");
+ config.setString("KYC-CHECK-C1", "form_name", "myform");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "description_i18n", "{}");
+ config.setString("KYC-CHECK-C1", "outputs", "full_name birthdate");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C2", "type", "INFO");
+ config.setString("KYC-CHECK-C2", "description", "my check info!");
+ config.setString("KYC-CHECK-C2", "description_i18n", "{}");
+ config.setString("KYC-CHECK-C2", "fallback", "M2");
+
+ config.setString("KYC-CHECK-C3", "type", "INFO");
+ config.setString("KYC-CHECK-C3", "description", "this is info c3");
+ config.setString("KYC-CHECK-C3", "description_i18n", "{}");
+ config.setString("KYC-CHECK-C3", "fallback", "M2");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+/**
+ * Test setting a `new_measure` as the AML officer.
+ */
+export async function runKycNewMeasureTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+ let kycPaytoHash: string | undefined;
+ let accessToken: string | undefined;
+ let firstTransaction: string | undefined;
+
+ {
+ const wres = await withdrawViaBankV3(t, {
+ amount: "TESTKUDOS:20",
+ bankClient,
+ exchange,
+ walletClient,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: wres.transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: wres.transactionId,
+ },
+ );
+
+ console.log(j2s(txDetails));
+
+ accessToken = txDetails.kycAccessToken;
+ kycPaytoHash = txDetails.kycPaytoHash;
+ firstTransaction = wres.transactionId;
+ }
+
+ t.assertTrue(!!accessToken);
+ const infoResp = await harnessHttpLib.fetch(
+ new URL(`kyc-info/${accessToken}`, exchange.baseUrl).href,
+ );
+
+ const clientInfo = await readResponseJsonOrThrow(
+ infoResp,
+ codecForKycProcessClientInformation(),
+ );
+
+ console.log(j2s(clientInfo));
+
+ const kycId = clientInfo.requirements.find((x) => x.id != null)?.id;
+ t.assertTrue(!!kycId);
+
+ const uploadResp = await harnessHttpLib.fetch(
+ new URL(`kyc-upload/${kycId}`, exchange.baseUrl).href,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ body: "full_name=Alice+Abc&birthdate=2000-01-01",
+ },
+ );
+
+ console.log("resp status", uploadResp.status);
+
+ t.assertDeepEqual(uploadResp.status, 204);
+
+ const sig = signAmlQuery(decodeCrock(amlKeypair.priv));
+ {
+ const decisionsResp = await harnessHttpLib.fetch(
+ new URL(`aml/${amlKeypair.pub}/decisions`, exchange.baseUrl).href,
+ {
+ headers: {
+ "Taler-AML-Officer-Signature": encodeCrock(sig),
+ },
+ },
+ );
+
+ const decisions = await readResponseJsonOrThrow(decisionsResp, codecForAny());
+ console.log(j2s(decisions));
+
+ t.assertDeepEqual(decisionsResp.status, 200);
+ }
+ // KYC should pass now
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: firstTransaction as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+
+ // Now, AML officer takes action and freezes the account, requiring a new_measure
+ t.assertTrue(!!kycPaytoHash);
+
+ await postAmlDecision(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: kycPaytoHash,
+ newMeasure: "m3",
+ newRules: {
+ expiration_time: TalerProtocolTimestamp.never(),
+ custom_measures: {},
+ rules: [
+ // No rules!
+ ],
+ },
+ });
+
+
+ {
+ const decisionsResp = await harnessHttpLib.fetch(
+ new URL(`aml/${amlKeypair.pub}/decisions`, exchange.baseUrl).href,
+ {
+ headers: {
+ "Taler-AML-Officer-Signature": encodeCrock(sig),
+ },
+ },
+ );
+
+ const decisions = await readResponseJsonOrThrow(decisionsResp, codecForAny());
+ console.log(j2s(decisions));
+
+ t.assertDeepEqual(decisionsResp.status, 200);
+ }
+
+ {
+ const wres = await withdrawViaBankV3(t, {
+ amount: "TESTKUDOS:21",
+ bankClient,
+ exchange,
+ walletClient,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: wres.transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: wres.transactionId,
+ },
+ );
+ console.log(j2s(txDetails));
+
+ const accessToken = txDetails.kycAccessToken;
+ t.assertTrue(!!accessToken);
+
+ const infoResp = await harnessHttpLib.fetch(
+ new URL(`kyc-info/${txDetails.kycAccessToken}`, exchange.baseUrl).href,
+ );
+
+ const clientInfo = await readResponseJsonOrThrow(
+ infoResp,
+ codecForKycProcessClientInformation(),
+ );
+
+ console.log("second withdrawal, clientInfo:");
+ console.log(j2s(clientInfo));
+ }
+}
+
+runKycNewMeasureTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts b/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts
new file mode 100644
index 000000000..0919f6550
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-peer-pull.ts
@@ -0,0 +1,356 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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,
+ AmountString,
+ Duration,
+ j2s,
+ TalerCorebankApiClient,
+ TransactionIdStr,
+ TransactionMajorState,
+ TransactionMinorState,
+ TransactionType,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ createWalletDaemonWithClient,
+ EnvOptions,
+ postAmlDecisionNoRules,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "merge");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycPeerPullTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Origin wallet for the p2p transaction,
+ // will pay for the invoice.
+ const w0 = await createWalletDaemonWithClient(t, {
+ name: "w0",
+ });
+
+ // Withdraw digital cash into the wallet.
+
+ const wres1 = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:20",
+ exchange: exchange,
+ walletClient: w0.walletClient,
+ });
+
+ await wres1.withdrawalFinishedCond;
+
+ const wres2 = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:1",
+ exchange: exchange,
+ walletClient: walletClient,
+ });
+
+ await wres2.withdrawalFinishedCond;
+
+ const pullRes = await doPeerPullCredit(t, {
+ walletClient,
+ amount: "TESTKUDOS:10",
+ summary: "test123",
+ });
+
+ const txDet = await walletClient.call(WalletApiOperation.GetTransactionById, {
+ transactionId: pullRes.transactionId,
+ });
+
+ console.log("tx details", j2s(txDet));
+
+ const kycPaytoHash = txDet.kycPaytoHash;
+
+ t.assertTrue(!!kycPaytoHash);
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: kycPaytoHash,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: pullRes.transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Ready,
+ },
+ });
+
+ const prepRes = await w0.walletClient.call(
+ WalletApiOperation.PreparePeerPullDebit,
+ {
+ talerUri: pullRes.talerUri,
+ },
+ );
+
+ await w0.walletClient.call(WalletApiOperation.ConfirmPeerPullDebit, {
+ transactionId: prepRes.transactionId,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: pullRes.transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+}
+
+/**
+ * Initiate a pull credit transaction, wait until the transaction
+ * is ready.
+ */
+async function doPeerPullCredit(
+ t: GlobalTestState,
+ args: {
+ walletClient: WalletClient;
+ amount: AmountString;
+ summary?: string;
+ },
+): Promise<{
+ transactionId: string;
+ talerUri: string;
+}> {
+ const purse_expiration = AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ days: 2 }),
+ ),
+ );
+ const initRet = await args.walletClient.call(
+ WalletApiOperation.InitiatePeerPullCredit,
+ {
+ partialContractTerms: {
+ amount: args.amount,
+ summary: args.summary ?? "Test P2P Payment",
+ purse_expiration,
+ },
+ },
+ );
+
+ await args.walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: initRet.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.MergeKycRequired,
+ },
+ });
+
+ const txDet = await args.walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: initRet.transactionId,
+ },
+ );
+
+ t.assertTrue(txDet.type === TransactionType.PeerPullCredit);
+ const talerUri = txDet.talerUri;
+ t.assertTrue(!!talerUri);
+
+ return {
+ transactionId: initRet.transactionId,
+ talerUri,
+ };
+}
+
+runKycPeerPullTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts b/packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts
new file mode 100644
index 000000000..6e1a78a26
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-peer-push.ts
@@ -0,0 +1,347 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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,
+ AmountString,
+ Duration,
+ j2s,
+ TalerCorebankApiClient,
+ TransactionMajorState,
+ TransactionMinorState,
+ TransactionType,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import {
+ createWalletDaemonWithClient,
+ EnvOptions,
+ postAmlDecisionNoRules,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "merge");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycPeerPushTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Origin wallet for the p2p transaction.
+ const w0 = await createWalletDaemonWithClient(t, {
+ name: "w0",
+ });
+
+ // Withdraw digital cash into the wallet.
+
+ const wres = await withdrawViaBankV3(t, {
+ bankClient,
+ amount: "TESTKUDOS:20",
+ exchange: exchange,
+ walletClient: w0.walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ const pushDebitRes = await doPeerPushDebit(t, {
+ walletClient: w0.walletClient,
+ amount: "TESTKUDOS:10",
+ summary: "Test1",
+ });
+
+ const prepRes = await walletClient.call(
+ WalletApiOperation.PreparePeerPushCredit,
+ {
+ talerUri: pushDebitRes.talerUri,
+ },
+ );
+
+ console.log("prepRes", j2s(prepRes));
+
+ await walletClient.call(WalletApiOperation.ConfirmPeerPushCredit, {
+ transactionId: prepRes.transactionId,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: prepRes.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.MergeKycRequired,
+ },
+ });
+
+ const txDet = await walletClient.call(WalletApiOperation.GetTransactionById, {
+ transactionId: prepRes.transactionId,
+ });
+
+ console.log("tx details", j2s(txDet));
+
+ const kycPaytoHash = txDet.kycPaytoHash;
+
+ t.assertTrue(!!kycPaytoHash);
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: kycPaytoHash,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: prepRes.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+}
+
+/**
+ * Initiate a push debit transaction, wait until the transaction
+ * is ready.
+ */
+async function doPeerPushDebit(
+ t: GlobalTestState,
+ args: {
+ walletClient: WalletClient;
+ amount: AmountString;
+ summary?: string;
+ },
+): Promise<{
+ transactionId: string;
+ talerUri: string;
+}> {
+ const purse_expiration = AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ days: 2 }),
+ ),
+ );
+ const initRet = await args.walletClient.call(
+ WalletApiOperation.InitiatePeerPushDebit,
+ {
+ partialContractTerms: {
+ amount: args.amount,
+ summary: args.summary ?? "Test P2P Payment",
+ purse_expiration,
+ },
+ },
+ );
+
+ await args.walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: initRet.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Ready,
+ },
+ });
+
+ const txDet = await args.walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: initRet.transactionId,
+ },
+ );
+
+ t.assertTrue(txDet.type === TransactionType.PeerPushDebit);
+ const talerUri = txDet.talerUri;
+ t.assertTrue(!!talerUri);
+
+ return {
+ transactionId: initRet.transactionId,
+ talerUri,
+ };
+}
+
+runKycPeerPushTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts
new file mode 100644
index 000000000..6aa65992a
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc-threshold-withdrawal.ts
@@ -0,0 +1,322 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ NotificationType,
+ TalerCorebankApiClient,
+ TransactionMajorState,
+ TransactionMinorState,
+ TransactionType,
+} from "@gnu-taler/taler-util";
+import {
+ createSyncCryptoApi,
+ EddsaKeyPairStrings,
+ WalletApiOperation,
+} from "@gnu-taler/taler-wallet-core";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+ BankService,
+ DbInfo,
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ HarnessExchangeBankAccount,
+ setupDb,
+ WalletClient,
+ WalletService,
+} from "../harness/harness.js";
+import { EnvOptions, postAmlDecisionNoRules } from "../harness/helpers.js";
+
+interface KycTestEnv {
+ commonDb: DbInfo;
+ bankClient: TalerCorebankApiClient;
+ exchange: ExchangeService;
+ exchangeBankAccount: HarnessExchangeBankAccount;
+ walletClient: WalletClient;
+ walletService: WalletService;
+ amlKeypair: EddsaKeyPairStrings;
+}
+
+async function createKycTestkudosEnvironment(
+ t: GlobalTestState,
+ coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+ opts: EnvOptions = {},
+): Promise<KycTestEnv> {
+ const db = await setupDb(t);
+
+ const bank = await BankService.create(t, {
+ allowRegistrations: true,
+ currency: "TESTKUDOS",
+ database: db.connStr,
+ httpPort: 8082,
+ });
+
+ const exchange = ExchangeService.create(t, {
+ name: "testexchange-1",
+ currency: "TESTKUDOS",
+ httpPort: 8081,
+ database: db.connStr,
+ });
+
+ let receiverName = "Exchange";
+ let exchangeBankUsername = "exchange";
+ let exchangeBankPassword = "mypw";
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+
+ await exchange.addBankAccount("1", {
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
+ accountPaytoUri: exchangePaytoUri,
+ });
+
+ bank.setSuggestedExchange(exchange, exchangePaytoUri);
+
+ await bank.start();
+
+ await bank.pingUntilAvailable();
+
+ const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
+ auth: {
+ username: "admin",
+ password: "adminpw",
+ },
+ });
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ const ageMaskSpec = opts.ageMaskSpec;
+
+ if (ageMaskSpec) {
+ exchange.enableAgeRestrictions(ageMaskSpec);
+ // Enable age restriction for all coins.
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({
+ ...x,
+ name: `${x.name}-age`,
+ ageRestricted: true,
+ })),
+ );
+ // For mixed age restrictions, we also offer coins without age restrictions
+ if (opts.mixedAgeRestriction) {
+ exchange.addCoinConfigList(
+ coinConfig.map((x) => ({ ...x, ageRestricted: false })),
+ );
+ }
+ } else {
+ exchange.addCoinConfigList(coinConfig);
+ }
+
+ await exchange.modifyConfig(async (config) => {
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:300");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "verboten");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("AML-PROGRAM-P1", "command", "/bin/true");
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString("AML-PROGRAM-P1", "description", "this does nothing");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ config.setString("KYC-CHECK-C1", "type", "INFO");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+ });
+
+ await exchange.start();
+
+ const cryptoApi = createSyncCryptoApi();
+ const amlKeypair = await cryptoApi.createEddsaKeypair({});
+
+ await exchange.enableAmlAccount(amlKeypair.pub, "Alice");
+
+ const walletService = new WalletService(t, {
+ name: "wallet",
+ useInMemoryDb: true,
+ });
+ await walletService.start();
+ await walletService.pingUntilAvailable();
+
+ const walletClient = new WalletClient({
+ name: "wallet",
+ unixPath: walletService.socketPath,
+ onNotification(n) {
+ console.log("got notification", n);
+ },
+ });
+ await walletClient.connect();
+ await walletClient.client.call(WalletApiOperation.InitWallet, {
+ config: {
+ testing: {
+ skipDefaults: true,
+ },
+ },
+ });
+
+ console.log("setup done!");
+
+ return {
+ commonDb: db,
+ exchange,
+ amlKeypair,
+ walletClient,
+ walletService,
+ bankClient,
+ exchangeBankAccount: {
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
+ },
+ };
+}
+
+export async function runKycThresholdWithdrawalTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange, amlKeypair } =
+ await createKycTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ const amount = "TESTKUDOS:20";
+ const user = await bankClient.createRandomBankUser();
+ bankClient.setAuth({
+ username: user.username,
+ password: user.password,
+ });
+
+ const wop = await bankClient.createWithdrawalOperation(user.username, amount);
+
+ // Hand it to the wallet
+
+ const withdrawalUrlInfo = await walletClient.client.call(
+ WalletApiOperation.GetWithdrawalDetailsForUri,
+ {
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ },
+ );
+
+ const withdrawalAmountInfo = await walletClient.call(
+ WalletApiOperation.GetWithdrawalDetailsForAmount,
+ {
+ amount: withdrawalUrlInfo.amount!,
+ exchangeBaseUrl: withdrawalUrlInfo.possibleExchanges[0].exchangeBaseUrl,
+ },
+ );
+
+ // t.assertTrue(!!withdrawalAmountInfo.kycHardLimit);
+ // t.assertAmountEquals(withdrawalAmountInfo.kycHardLimit, "TESTKUDOS:300");
+
+ // Withdraw
+
+ const acceptResp = await walletClient.client.call(
+ WalletApiOperation.AcceptBankIntegratedWithdrawal,
+ {
+ exchangeBaseUrl: exchange.baseUrl,
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ },
+ );
+
+ const withdrawalTxId = acceptResp.transactionId;
+
+ // Confirm it
+
+ await bankClient.confirmWithdrawalOperation(user.username, {
+ withdrawalOperationId: wop.withdrawal_id,
+ });
+
+
+ t.logStep("waiting for pending(kyc-required)");
+
+ const kycNotificationCond = walletClient.waitForNotificationCond((x) => {
+ if (
+ x.type === NotificationType.TransactionStateTransition &&
+ x.transactionId === withdrawalTxId &&
+ x.newTxState.major === TransactionMajorState.Pending &&
+ x.newTxState.minor === TransactionMinorState.KycRequired
+ ) {
+ return x;
+ }
+ return false;
+ });
+
+ await kycNotificationCond;
+
+ const txDet = await walletClient.call(WalletApiOperation.GetTransactionById, {
+ transactionId: withdrawalTxId,
+ });
+
+ t.assertDeepEqual(txDet.type, TransactionType.Withdrawal);
+
+ const kycPaytoHash = txDet.kycPaytoHash;
+ t.assertTrue(!!kycPaytoHash);
+
+ t.logStep("posting aml decision");
+
+ await postAmlDecisionNoRules(t, {
+ amlPriv: amlKeypair.priv,
+ amlPub: amlKeypair.pub,
+ exchangeBaseUrl: exchange.baseUrl,
+ paytoHash: kycPaytoHash,
+ });
+
+ t.logStep("waiting for withdrawal to be done");
+
+ const doneNotificationCond = walletClient.waitForNotificationCond((x) => {
+ if (
+ x.type === NotificationType.TransactionStateTransition &&
+ x.transactionId === withdrawalTxId &&
+ x.newTxState.major === TransactionMajorState.Done
+ ) {
+ return x;
+ }
+ return false;
+ });
+
+ await doneNotificationCond;
+}
+
+runKycThresholdWithdrawalTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-kyc.ts b/packages/taler-harness/src/integrationtests/test-kyc.ts
index 213dd9df4..3a68fcbce 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc.ts
@@ -25,27 +25,29 @@ import {
TransactionMajorState,
TransactionMinorState,
TransactionType,
+ codecForKycProcessClientInformation,
j2s,
} from "@gnu-taler/taler-util";
-import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import * as http from "node:http";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
import {
- BankService,
+ BankService,
ExchangeService,
GlobalTestState,
MerchantService,
WalletClient,
WalletService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
+ harnessHttpLib,
setupDb,
} from "../harness/harness.js";
import { EnvOptions, SimpleTestEnvironmentNg3 } from "../harness/helpers.js";
const logger = new Logger("test-kyc.ts");
-export async function createKycTestkudosEnvironment(
+async function createKycTestkudosEnvironment(
t: GlobalTestState,
coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
opts: EnvOptions = {},
@@ -76,12 +78,15 @@ export async function createKycTestkudosEnvironment(
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
accountPassword: exchangeBankPassword,
- wireGatewayApiBaseUrl: new URL("accounts/exchange/taler-wire-gateway/", bank.baseUrl).href,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
accountPaytoUri: exchangePaytoUri,
});
@@ -129,11 +134,48 @@ export async function createKycTestkudosEnvironment(
}
await exchange.modifyConfig(async (config) => {
- const myprov = "kyc-provider-myprov";
- config.setString(myprov, "cost", "0");
+ config.setString("exchange", "enable_kyc", "yes");
+
+ config.setString("KYC-RULE-R1", "operation_type", "withdraw");
+ config.setString("KYC-RULE-R1", "enabled", "yes");
+ config.setString("KYC-RULE-R1", "exposed", "yes");
+ config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
+ config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
+ config.setString("KYC-RULE-R1", "timeframe", "1d");
+ config.setString("KYC-RULE-R1", "next_measures", "M1");
+
+ config.setString("KYC-MEASURE-M1", "check_name", "C1");
+ config.setString("KYC-MEASURE-M1", "context", "{}");
+ config.setString("KYC-MEASURE-M1", "program", "P1");
+
+ config.setString("KYC-CHECK-C1", "type", "LINK");
+ config.setString("KYC-CHECK-C1", "provider_id", "MYPROV");
+ config.setString("KYC-CHECK-C1", "description", "my check!");
+ config.setString("KYC-CHECK-C1", "description_i18n", "{}");
+ config.setString("KYC-CHECK-C1", "outputs", "full_name birthdate");
+ config.setString("KYC-CHECK-C1", "fallback", "M1");
+
+ config.setString(
+ "AML-PROGRAM-P1",
+ "command",
+ "taler-exchange-helper-measure-test-form",
+ );
+ config.setString("AML-PROGRAM-P1", "enabled", "true");
+ config.setString(
+ "AML-PROGRAM-P1",
+ "description",
+ "test for full_name and birthdate",
+ );
+ config.setString("AML-PROGRAM-P1", "description_i18n", "{}");
+ config.setString("AML-PROGRAM-P1", "fallback", "M1");
+
+ const myprov = "KYC-PROVIDER-MYPROV";
config.setString(myprov, "logic", "oauth2");
- config.setString(myprov, "provided_checks", "dummy1");
- config.setString(myprov, "user_type", "individual");
+ config.setString(
+ myprov,
+ "converter",
+ "taler-exchange-kyc-oauth2-test-converter.sh",
+ );
config.setString(myprov, "kyc_oauth2_validity", "forever");
config.setString(
myprov,
@@ -164,17 +206,6 @@ export async function createKycTestkudosEnvironment(
"operation_type",
"withdraw",
);
- config.setString(
- "kyc-legitimization-withdraw1",
- "required_checks",
- "dummy1",
- );
- config.setString("kyc-legitimization-withdraw1", "timeframe", "1d");
- config.setString(
- "kyc-legitimization-withdraw1",
- "threshold",
- "TESTKUDOS:5",
- );
});
await exchange.start();
@@ -188,7 +219,7 @@ export async function createKycTestkudosEnvironment(
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -197,7 +228,7 @@ export async function createKycTestkudosEnvironment(
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -234,12 +265,13 @@ export async function createKycTestkudosEnvironment(
merchant,
walletClient,
walletService,
+ bank,
bankClient,
exchangeBankAccount: {
- accountName: '',
- accountPassword: '',
- accountPaytoUri: '',
- wireGatewayApiBaseUrl: '',
+ accountName: "",
+ accountPassword: "",
+ accountPaytoUri: "",
+ wireGatewayApiBaseUrl: "",
},
};
}
@@ -314,7 +346,10 @@ async function runTestfakeKycService(): Promise<TestfakeKycService> {
JSON.stringify({
status: "success",
data: {
- id: "foobar",
+ id: "Foobar",
+ first_name: "Alice",
+ last_name: "Abc",
+ birthdate: "2000-01-01",
},
}),
);
@@ -410,26 +445,48 @@ export async function runKycTest(t: GlobalTestState) {
);
t.assertDeepEqual(txState.type, TransactionType.Withdrawal);
+ const paytoHash = txState.kycPaytoHash;
- const kycUrl = txState.kycUrl;
-
- t.assertTrue(!!kycUrl);
-
- logger.info(`kyc URL is ${kycUrl}`);
+ t.assertTrue(!!txState.kycUrl);
+ t.assertTrue(!!paytoHash);
// We now simulate the user interacting with the KYC service,
// which would usually done in the browser.
- const httpLib = createPlatformHttpLib({
- enableThrottling: false,
- });
- const kycServerResp = await httpLib.fetch(kycUrl);
- const kycLoginResp = await kycServerResp.json();
- logger.info(`kyc server resp: ${j2s(kycLoginResp)}`);
- const kycProofUrl = kycLoginResp.redirect_uri;
+ const accessToken = txState.kycAccessToken;
+ t.assertTrue(!!accessToken);
+
+ const infoResp = await harnessHttpLib.fetch(
+ new URL(`kyc-info/${txState.kycAccessToken}`, exchange.baseUrl).href,
+ );
+
+ const clientInfo = await readResponseJsonOrThrow(
+ infoResp,
+ codecForKycProcessClientInformation(),
+ );
+
+ console.log(j2s(clientInfo));
+
+ const kycId = clientInfo.requirements.find((x) => x.id != null)?.id;
+ t.assertTrue(!!kycId);
+
+ const startResp = await harnessHttpLib.fetch(
+ new URL(`kyc-start/${kycId}`, exchange.baseUrl).href,
+ {
+ method: "POST",
+ body: {},
+ },
+ );
+
+ logger.info(`kyc-start resp status: ${startResp.status}`);
+ logger.info(j2s(startResp.json()));
+
// We need to "visit" the KYC proof URL at least once to trigger the exchange
// asking for the KYC status.
- const proofHttpResp = await httpLib.fetch(kycProofUrl);
+ const proofUrl = new URL(`kyc-proof/MYPROV`, exchange.baseUrl);
+ proofUrl.searchParams.set("state", paytoHash);
+ proofUrl.searchParams.set("code", "code_is_ok");
+ const proofHttpResp = await harnessHttpLib.fetch(proofUrl.href);
logger.info(`proof resp status ${proofHttpResp.status}`);
logger.info(`resp headers ${j2s(proofHttpResp.headers.toJSON())}`);
if (
diff --git a/packages/taler-harness/src/integrationtests/test-libeufin-bank.ts b/packages/taler-harness/src/integrationtests/test-libeufin-bank.ts
index 01b20ddbf..1b67332d8 100644
--- a/packages/taler-harness/src/integrationtests/test-libeufin-bank.ts
+++ b/packages/taler-harness/src/integrationtests/test-libeufin-bank.ts
@@ -35,7 +35,7 @@ import {
GlobalTestState,
LibeufinBankService,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
generateRandomTestIban,
setupDb,
} from "../harness/harness.js";
@@ -74,7 +74,7 @@ export async function runLibeufinBankTest(t: GlobalTestState) {
const exchangeBankUsername = "exchange";
const exchangeBankPw = "mypw";
- const exchangePayto = generateRandomPayto(exchangeBankUsername);
+ const exchangePayto = getTestHarnessPaytoForLabel(exchangeBankUsername);
const wireGatewayApiBaseUrl = new URL(
"accounts/exchange/taler-wire-gateway/",
bank.baseUrl,
@@ -108,13 +108,13 @@ export async function runLibeufinBankTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
const { walletClient } = await createWalletDaemonWithClient(t, {
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-categories.ts b/packages/taler-harness/src/integrationtests/test-merchant-categories.ts
index a6ddc31e2..21ccfef3a 100644
--- a/packages/taler-harness/src/integrationtests/test-merchant-categories.ts
+++ b/packages/taler-harness/src/integrationtests/test-merchant-categories.ts
@@ -17,12 +17,12 @@
/**
* Imports.
*/
-import { URL } from "@gnu-taler/taler-util";
+import { URL, j2s } from "@gnu-taler/taler-util";
import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
harnessHttpLib,
setupDb,
} from "../harness/harness.js";
@@ -78,25 +78,101 @@ export async function runMerchantCategoriesTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
auth: {
method: "external",
},
});
- const url = new URL("private/categories", merchant.makeInstanceBaseUrl());
- const res = await harnessHttpLib.fetch(url.href, {
- method: "POST",
- body: {
- name: "Snacks",
- name_i18n: {},
- },
- });
+ let myNewCategoryId: number;
+
+ {
+ const url = new URL("private/categories", merchant.makeInstanceBaseUrl());
+ const res = await harnessHttpLib.fetch(url.href, {
+ method: "POST",
+ body: {
+ name: "Snacks",
+ name_i18n: {},
+ },
+ });
+
+ console.log(res.requestUrl);
+ console.log("status", res.status);
+ const categoryJson = await res.json();
+ console.log(categoryJson);
+ t.assertTrue(res.status >= 200 && res.status < 300);
+ myNewCategoryId = categoryJson.category_id;
+ }
+
+ {
+ const url = new URL("private/products", merchant.makeInstanceBaseUrl());
+ const res = await harnessHttpLib.fetch(url.href, {
+ method: "POST",
+ body: {
+ product_id: "foo",
+ description: "Bla Bla",
+ unit: "item",
+ price: "TESTKUDOS:6",
+ total_stock: -1,
+ },
+ });
+ t.assertTrue(res.status >= 200 && res.status < 300);
+ }
- console.log(res.requestUrl);
- console.log("status", res.status);
- console.log(await res.json());
- t.assertTrue(res.status >= 200 && res.status < 300);
+ {
+ const url = new URL("private/products", merchant.makeInstanceBaseUrl());
+ const res = await harnessHttpLib.fetch(url.href, {
+ method: "POST",
+ body: {
+ product_id: "bar",
+ description: "Bla Bla",
+ unit: "item",
+ price: "TESTKUDOS:2",
+ total_stock: -1,
+ categories: [myNewCategoryId],
+ },
+ });
+ t.assertTrue(res.status >= 200 && res.status < 300);
+ }
+
+ {
+ const url = new URL("private/products", merchant.makeInstanceBaseUrl());
+ const res = await harnessHttpLib.fetch(url.href, {
+ method: "POST",
+ body: {
+ product_id: "baz",
+ description: "Eggs",
+ unit: "item",
+ price: "TESTKUDOS:42",
+ total_stock: -1,
+ },
+ });
+ t.assertTrue(res.status >= 200 && res.status < 300);
+ }
+
+ {
+ const posUrl = new URL("private/pos", merchant.makeInstanceBaseUrl());
+ const res = await harnessHttpLib.fetch(posUrl.href, {
+ method: "GET",
+ });
+ const posJson = await res.json();
+ console.log(j2s(posJson));
+ t.assertTrue(res.status >= 200 && res.status < 300);
+
+ t.assertDeepEqual(posJson.products.length, 3);
+
+ const prodFoo = posJson.products.find((x: any) => x.product_id == "foo");
+ console.log(`prod foo`, prodFoo);
+ t.assertTrue(!!prodFoo);
+ // Only default category
+ t.assertDeepEqual(prodFoo.categories, [0]);
+
+ const prodBar = posJson.products.find((x: any) => x.product_id == "bar");
+ console.log(`prod bar`, prodBar);
+ t.assertTrue(!!prodBar);
+ // This should have the one we assigned to it.
+ t.assertDeepEqual(prodBar.categories, [myNewCategoryId]);
+ }
}
runMerchantCategoriesTest.suites = ["merchant"];
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts b/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts
index 19f89ae2c..8f47eda1b 100644
--- a/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts
+++ b/packages/taler-harness/src/integrationtests/test-merchant-exchange-confusion.ts
@@ -20,9 +20,12 @@
import {
codecForMerchantOrderStatusUnpaid,
ConfirmPayResultType,
+ j2s,
MerchantApiClient,
PreparePayResultType,
TalerCorebankApiClient,
+ TalerErrorCode,
+ TypedTalerErrorDetail,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { URL } from "url";
@@ -32,9 +35,9 @@ import {
FaultInjectedMerchantService,
} from "../harness/faultInjection.js";
import {
- BankService,
+ BankService,
ExchangeService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
GlobalTestState,
harnessHttpLib,
MerchantService,
@@ -87,12 +90,15 @@ export async function createConfusedMerchantTestkudosEnvironment(
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
accountPassword: exchangeBankPassword,
- wireGatewayApiBaseUrl: new URL("accounts/exchange/taler-wire-gateway/", bank.baseUrl).href,
+ wireGatewayApiBaseUrl: new URL(
+ "accounts/exchange/taler-wire-gateway/",
+ bank.baseUrl,
+ ).href,
accountPaytoUri: exchangePaytoUri,
});
@@ -131,13 +137,13 @@ export async function createConfusedMerchantTestkudosEnvironment(
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
@@ -258,7 +264,25 @@ export async function runMerchantExchangeConfusionTest(t: GlobalTestState) {
proposalId: proposalId,
});
- t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Done);
+ t.assertTrue(confirmPayRes.type === ConfirmPayResultType.Pending);
+
+ console.log(j2s(confirmPayRes.lastError));
+
+ // Merchant should not accept the payment!
+ // Something is clearly wrong, as the exchange now announces
+ // its own base URL and something is wrong.
+
+ // FIXME: This error code should probably be refined in the future.
+
+ t.assertDeepEqual(
+ confirmPayRes.lastError?.code,
+ TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR,
+ );
+
+ const err =
+ confirmPayRes.lastError as TypedTalerErrorDetail<TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR>;
+
+ t.assertDeepEqual(err.httpStatusCode, 400);
}
runMerchantExchangeConfusionTest.suites = ["merchant"];
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts
index c0c9353e4..51beded8d 100644
--- a/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts
+++ b/packages/taler-harness/src/integrationtests/test-merchant-instances-delete.ts
@@ -22,7 +22,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
harnessHttpLib,
setupDb,
} from "../harness/harness.js";
@@ -78,7 +78,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
auth: {
method: "external",
},
@@ -88,7 +88,7 @@ export async function runMerchantInstancesDeleteTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "myinst",
name: "Second Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
auth: {
method: "external",
},
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts
index 188451e15..f41b028ae 100644
--- a/packages/taler-harness/src/integrationtests/test-merchant-instances.ts
+++ b/packages/taler-harness/src/integrationtests/test-merchant-instances.ts
@@ -22,7 +22,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
harnessHttpLib,
setupDb,
} from "../harness/harness.js";
@@ -78,7 +78,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
auth: {
method: "external",
},
@@ -88,7 +88,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
auth: {
method: "external",
},
@@ -98,7 +98,7 @@ export async function runMerchantInstancesTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "myinst",
name: "Second Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
auth: {
method: "external",
},
diff --git a/packages/taler-harness/src/integrationtests/test-multiexchange.ts b/packages/taler-harness/src/integrationtests/test-multiexchange.ts
index 26e843073..8c1424437 100644
--- a/packages/taler-harness/src/integrationtests/test-multiexchange.ts
+++ b/packages/taler-harness/src/integrationtests/test-multiexchange.ts
@@ -26,7 +26,7 @@ import {
GlobalTestState,
HarnessExchangeBankAccount,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -82,7 +82,7 @@ export async function runMultiExchangeTest(t: GlobalTestState) {
).href,
accountName: "myexchange",
accountPassword: "x",
- accountPaytoUri: generateRandomPayto("myexchange"),
+ accountPaytoUri: getTestHarnessPaytoForLabel("myexchange"),
};
let exchangeTwoBankAccount: HarnessExchangeBankAccount = {
@@ -92,7 +92,7 @@ export async function runMultiExchangeTest(t: GlobalTestState) {
).href,
accountName: "myexchange2",
accountPassword: "x",
- accountPaytoUri: generateRandomPayto("myexchange2"),
+ accountPaytoUri: getTestHarnessPaytoForLabel("myexchange2"),
};
bank.setSuggestedExchange(
@@ -152,7 +152,7 @@ export async function runMultiExchangeTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -161,7 +161,7 @@ export async function runMultiExchangeTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
diff --git a/packages/taler-harness/src/integrationtests/test-payment-fault.ts b/packages/taler-harness/src/integrationtests/test-payment-fault.ts
index dabe42a6b..a6836953e 100644
--- a/packages/taler-harness/src/integrationtests/test-payment-fault.ts
+++ b/packages/taler-harness/src/integrationtests/test-payment-fault.ts
@@ -38,7 +38,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -71,7 +71,7 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -140,7 +140,7 @@ export async function runPaymentFaultTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl());
diff --git a/packages/taler-harness/src/integrationtests/test-payment-multiple.ts b/packages/taler-harness/src/integrationtests/test-payment-multiple.ts
index 3c902ee17..e3f3e18e3 100644
--- a/packages/taler-harness/src/integrationtests/test-payment-multiple.ts
+++ b/packages/taler-harness/src/integrationtests/test-payment-multiple.ts
@@ -25,7 +25,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -59,7 +59,7 @@ async function setupTest(t: GlobalTestState): Promise<{
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -107,13 +107,13 @@ async function setupTest(t: GlobalTestState): Promise<{
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
diff --git a/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts b/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts
index e38b690ab..05a0d2790 100644
--- a/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts
+++ b/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts
@@ -69,6 +69,19 @@ export async function runPeerToPeerPushTest(t: GlobalTestState) {
await withdrawRes.withdrawalFinishedCond;
+ {
+ const maxResp1 = await w1.walletClient.call(
+ WalletApiOperation.GetMaxPeerPushDebitAmount,
+ {
+ currency: "TESTKUDOS",
+ },
+ );
+
+ t.assertDeepEqual(maxResp1.exchangeBaseUrl, exchange.baseUrl);
+ t.assertAmountEquals(maxResp1.rawAmount, "TESTKUDOS:19.1");
+ t.assertAmountEquals(maxResp1.effectiveAmount, "TESTKUDOS:19.53");
+ }
+
const purse_expiration = AbsoluteTime.toProtocolTimestamp(
AbsoluteTime.addDuration(
AbsoluteTime.now(),
@@ -83,6 +96,7 @@ export async function runPeerToPeerPushTest(t: GlobalTestState) {
},
);
+ t.assertAmountEquals(checkResp0.amountRaw, "TESTKUDOS:5");
t.assertAmountEquals(checkResp0.amountEffective, "TESTKUDOS:5.49");
{
diff --git a/packages/taler-harness/src/integrationtests/test-refund-auto.ts b/packages/taler-harness/src/integrationtests/test-refund-auto.ts
index 582f30299..822e465e0 100644
--- a/packages/taler-harness/src/integrationtests/test-refund-auto.ts
+++ b/packages/taler-harness/src/integrationtests/test-refund-auto.ts
@@ -62,12 +62,12 @@ export async function runRefundAutoTest(t: GlobalTestState) {
summary: "Buy me!",
amount: "TESTKUDOS:5",
fulfillment_url: "taler://fulfillment-success/thx",
- auto_refund: {
- d_us: 3000 * 1000,
- },
+ auto_refund: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 5 }),
+ ),
},
refund_delay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 5 }),
+ Duration.fromSpec({ minutes: 10 }),
),
});
@@ -104,6 +104,8 @@ export async function runRefundAutoTest(t: GlobalTestState) {
console.log(ref);
+ t.logStep("gave-refund");
+
// The wallet should now automatically pick up the refund.
await walletClient.call(
WalletApiOperation.TestingWaitTransactionsFinal,
@@ -133,12 +135,12 @@ export async function runRefundAutoTest(t: GlobalTestState) {
summary: "Buy me!",
amount: "TESTKUDOS:5",
fulfillment_url: "taler://fulfillment-success/thx",
- auto_refund: {
- d_us: 3000 * 1000,
- },
+ auto_refund: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 5 }),
+ ),
},
refund_delay: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ minutes: 5 }),
+ Duration.fromSpec({ minutes: 10 }),
),
});
@@ -175,7 +177,7 @@ export async function runRefundAutoTest(t: GlobalTestState) {
});
// Only time-travel the wallet
await walletClient.call(WalletApiOperation.TestingSetTimetravel, {
- offsetMs: 5000,
+ offsetMs: 10 * 60 * 1000,
});
await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
transactionId: r1.transactionId,
diff --git a/packages/taler-harness/src/integrationtests/test-repurchase.ts b/packages/taler-harness/src/integrationtests/test-repurchase.ts
new file mode 100644
index 000000000..e2dece8b7
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-repurchase.ts
@@ -0,0 +1,164 @@
+/*
+ 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 {
+ ConfirmPayResultType,
+ MerchantApiClient,
+ PreparePayResultType,
+ TalerMerchantApi,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
+import {
+ useSharedTestkudosEnvironment,
+ withdrawViaBankV2,
+} from "../harness/helpers.js";
+
+export async function runRepurchaseTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bank, exchange, merchant } =
+ await useSharedTestkudosEnvironment(t);
+
+ // Withdraw digital cash into the wallet.
+
+ await withdrawViaBankV2(t, {
+ walletClient,
+ bank,
+ exchange,
+ amount: "TESTKUDOS:20",
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+
+ const order = {
+ summary: "Buy me!",
+ amount: "TESTKUDOS:5",
+ fulfillment_url: "taler://fulfillment-success/thx",
+ } satisfies TalerMerchantApi.Order;
+
+ const merchantClient = new MerchantApiClient(merchant.makeInstanceBaseUrl());
+
+ const orderOneResp = await merchantClient.createOrder({
+ order: {
+ summary: "Buy me",
+ amount: "TESTKUDOS:5",
+ fulfillment_url: "https://example.com/test",
+ },
+ });
+
+ let orderOneStatus = await merchantClient.queryPrivateOrderStatus({
+ orderId: orderOneResp.order_id,
+ sessionId: "session1",
+ });
+
+ t.assertTrue(orderOneStatus.order_status === "unpaid");
+
+ const preparePayOneResult = await walletClient.call(
+ WalletApiOperation.PreparePayForUri,
+ {
+ talerPayUri: orderOneStatus.taler_pay_uri,
+ },
+ );
+
+ t.assertTrue(
+ preparePayOneResult.status === PreparePayResultType.PaymentPossible,
+ );
+
+ const r2 = await walletClient.call(WalletApiOperation.ConfirmPay, {
+ transactionId: preparePayOneResult.transactionId,
+ });
+
+ t.assertTrue(r2.type === ConfirmPayResultType.Done);
+
+ const orderTwoResp = await merchantClient.createOrder({
+ order: {
+ summary: "Buy me",
+ amount: "TESTKUDOS:5",
+ fulfillment_url: "https://example.com/test",
+ },
+ });
+
+ let orderTwoStatus = await merchantClient.queryPrivateOrderStatus({
+ orderId: orderTwoResp.order_id,
+ sessionId: "session2",
+ });
+
+ t.assertTrue(orderTwoStatus.order_status === "unpaid");
+
+ const orderLongpollUrl = new URL(
+ `orders/${orderTwoResp.order_id}`,
+ merchant.makeInstanceBaseUrl(),
+ );
+ if (orderTwoResp.token) {
+ orderLongpollUrl.searchParams.set("token", orderTwoResp.token);
+ }
+ orderLongpollUrl.searchParams.set("timeout_ms", "60000");
+ orderLongpollUrl.searchParams.set("session_id", "session2");
+
+ const longpollPromise = harnessHttpLib.fetch(orderLongpollUrl.href);
+
+ const preparePayTwoResult = await walletClient.call(
+ WalletApiOperation.PreparePayForUri,
+ {
+ talerPayUri: orderTwoStatus.taler_pay_uri,
+ },
+ );
+
+ // Repurchase should be detected
+ t.assertTrue(
+ preparePayTwoResult.status === PreparePayResultType.AlreadyConfirmed,
+ );
+
+ t.logStep("start-wait-longpoll-promise");
+ await longpollPromise;
+ t.logStep("done-wait-longpoll-promise");
+
+ // Order three
+
+ const orderThreeResp = await merchantClient.createOrder({
+ order: {
+ summary: "Buy me",
+ amount: "TESTKUDOS:5",
+ fulfillment_url: "https://example.com/test",
+ },
+ });
+
+ let orderThreeStatus = await merchantClient.queryPrivateOrderStatus({
+ orderId: orderThreeResp.order_id,
+ // Go back to session1
+ sessionId: "session1",
+ });
+
+ t.assertTrue(orderThreeStatus.order_status === "unpaid");
+
+ const preparePayThreeResult = await walletClient.call(
+ WalletApiOperation.PreparePayForUri,
+ {
+ talerPayUri: orderThreeStatus.taler_pay_uri,
+ },
+ );
+
+ // Repurchase should be detected
+ t.assertTrue(
+ preparePayThreeResult.status === PreparePayResultType.AlreadyConfirmed,
+ );
+}
+
+runRepurchaseTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-revocation.ts b/packages/taler-harness/src/integrationtests/test-revocation.ts
index 8714a3769..d88a74150 100644
--- a/packages/taler-harness/src/integrationtests/test-revocation.ts
+++ b/packages/taler-harness/src/integrationtests/test-revocation.ts
@@ -31,7 +31,7 @@ import {
WalletCli,
WalletClient,
delayMs,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -93,7 +93,7 @@ async function createTestEnvironment(
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -153,13 +153,13 @@ async function createTestEnvironment(
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
@@ -179,6 +179,7 @@ async function createTestEnvironment(
merchant,
walletClient,
walletService,
+ bank,
bankClient,
exchangeBankAccount: {
accountName: "",
diff --git a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
index 046bd5aed..27bf7bdd6 100644
--- a/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
+++ b/packages/taler-harness/src/integrationtests/test-timetravel-autorefresh.ts
@@ -33,7 +33,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -74,7 +74,7 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -120,13 +120,13 @@ export async function runTimetravelAutorefreshTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
});
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
});
console.log("setup done!");
diff --git a/packages/taler-harness/src/integrationtests/test-timetravel-withdraw.ts b/packages/taler-harness/src/integrationtests/test-timetravel-withdraw.ts
index 4ee3a86e9..1ba7dc2ad 100644
--- a/packages/taler-harness/src/integrationtests/test-timetravel-withdraw.ts
+++ b/packages/taler-harness/src/integrationtests/test-timetravel-withdraw.ts
@@ -97,7 +97,7 @@ export async function runTimetravelWithdrawTest(t: GlobalTestState) {
// Now we also let the wallet time travel
- walletClient.call(WalletApiOperation.TestingSetTimetravel, {
+ await walletClient.call(WalletApiOperation.TestingSetTimetravel, {
offsetMs: Duration.toMilliseconds(timetravelDuration),
});
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-balance.ts b/packages/taler-harness/src/integrationtests/test-wallet-balance.ts
index c37a6e482..a8e62aee8 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-balance.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-balance.ts
@@ -21,7 +21,6 @@ import {
Amounts,
Duration,
MerchantApiClient,
- MerchantContractTerms,
PreparePayResultType,
TalerMerchantApi,
} from "@gnu-taler/taler-util";
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts b/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
index 66f985114..70f8989c8 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
@@ -26,7 +26,7 @@ import {
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { CoinConfig } from "../harness/denomStructures.js";
-import { GlobalTestState, generateRandomPayto } from "../harness/harness.js";
+import { GlobalTestState, getTestHarnessPaytoForLabel } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironmentV3,
createWalletDaemonWithClient,
@@ -104,7 +104,7 @@ export async function runWalletBlockedDepositTest(t: GlobalTestState) {
},
});
- const userPayto = generateRandomPayto("foo");
+ const userPayto = getTestHarnessPaytoForLabel("foo");
const bal = await w1.call(WalletApiOperation.GetBalances, {});
console.log(`balance: ${j2s(bal)}`);
@@ -114,7 +114,7 @@ export async function runWalletBlockedDepositTest(t: GlobalTestState) {
});
console.log(`balance details: ${j2s(balDet)}`);
- const depositCheckResp = await w1.call(WalletApiOperation.PrepareDeposit, {
+ const depositCheckResp = await w1.call(WalletApiOperation.CheckDeposit, {
amount: "TESTKUDOS:18" as AmountString,
depositPaytoUri: userPayto,
});
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
index a089d99b5..1d57e3458 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-dbless.ts
@@ -25,7 +25,6 @@ import {
TalerError,
} from "@gnu-taler/taler-util";
import {
- applyRunConfigDefaults,
CryptoDispatcher,
SynchronousCryptoWorkerFactoryPlain,
} from "@gnu-taler/taler-wallet-core";
@@ -86,11 +85,10 @@ export async function runWalletDblessTest(t: GlobalTestState) {
console.log(exchangeInfo);
await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);
-
- const defaultConfig = applyRunConfigDefaults();
+ const denomselAllowLate = false;
const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString, {
- denomselAllowLate: defaultConfig.testing.denomselAllowLate,
+ denomselAllowLate,
});
const coin = await withdrawCoin({
@@ -133,10 +131,10 @@ export async function runWalletDblessTest(t: GlobalTestState) {
const refreshDenoms = [
findDenomOrThrow(exchangeInfo, "TESTKUDOS:1" as AmountString, {
- denomselAllowLate: defaultConfig.testing.denomselAllowLate,
+ denomselAllowLate,
}),
findDenomOrThrow(exchangeInfo, "TESTKUDOS:1" as AmountString, {
- denomselAllowLate: defaultConfig.testing.denomselAllowLate,
+ denomselAllowLate,
}),
];
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts b/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts
index ba2b2670c..7378e272a 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts
@@ -34,7 +34,7 @@ import {
GlobalTestState,
WalletClient,
WalletService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import { withdrawViaBankV3 } from "../harness/helpers.js";
@@ -64,7 +64,7 @@ export async function runWalletDd48Test(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts b/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts
index 3a1b467c3..3d7ea32e6 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-exchange-update.ts
@@ -32,7 +32,7 @@ import {
FakebankService,
GlobalTestState,
HarnessExchangeBankAccount,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -98,7 +98,7 @@ export async function runWalletExchangeUpdateTest(
).href,
accountName: "myexchange",
accountPassword: "x",
- accountPaytoUri: generateRandomPayto("myexchange"),
+ accountPaytoUri: getTestHarnessPaytoForLabel("myexchange"),
};
await exchangeOne.addBankAccount("1", exchangeBankAccount);
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts b/packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts
index 4062e186d..3b1f4bf27 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-insufficient-balance.ts
@@ -20,39 +20,82 @@
import {
AmountString,
Duration,
+ j2s,
PaymentInsufficientBalanceDetails,
TalerErrorCode,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
import {
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
GlobalTestState,
- generateRandomPayto,
+ HarnessExchangeBankAccount,
setupDb,
} from "../harness/harness.js";
-import { createSimpleTestkudosEnvironmentV3, withdrawViaBankV3 } from "../harness/helpers.js";
+import {
+ createSimpleTestkudosEnvironmentV3,
+ withdrawViaBankV3,
+} from "../harness/helpers.js";
export async function runWalletInsufficientBalanceTest(t: GlobalTestState) {
// Set up test environment
- const db = await setupDb(t);
-
const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS"));
- let {
- bankClient,
- exchange,
- merchant,
- walletService,
- walletClient,
- } = await createSimpleTestkudosEnvironmentV3(t, coinConfig, {
- skipWireFeeCreation: true,
+ let { bankClient, bank, exchange, merchant, walletClient } =
+ await createSimpleTestkudosEnvironmentV3(t, coinConfig, {
+ skipWireFeeCreation: true,
+ });
+
+ const dbTwo = await setupDb(t, {
+ nameSuffix: "two",
});
+ const exchangeTwo = ExchangeService.create(t, {
+ name: "testexchange-2",
+ currency: "TESTKUDOS",
+ httpPort: 9081,
+ database: dbTwo.connStr,
+ });
+
+ {
+ const receiverName = "Exchange2";
+ const exchangeBankUsername = "exchange2";
+ const exchangeBankPassword = "mypw";
+ const exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
+ const wireGatewayApiBaseUrl = new URL(
+ `accounts/${exchangeBankUsername}/taler-wire-gateway/`,
+ bank.corebankApiBaseUrl,
+ ).href;
+
+ const exchangeBankAccount: HarnessExchangeBankAccount = {
+ wireGatewayApiBaseUrl,
+ accountName: exchangeBankUsername,
+ accountPassword: exchangeBankPassword,
+ accountPaytoUri: exchangePaytoUri,
+ skipWireFeeCreation: true,
+ };
+
+ await exchangeTwo.addBankAccount("1", exchangeBankAccount);
+
+ await bankClient.registerAccountExtended({
+ name: receiverName,
+ password: exchangeBankPassword,
+ username: exchangeBankUsername,
+ is_taler_exchange: true,
+ payto_uri: exchangePaytoUri,
+ });
+
+ exchangeTwo.addCoinConfigList(coinConfig);
+
+ await exchangeTwo.start();
+ }
+
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -61,7 +104,7 @@ export async function runWalletInsufficientBalanceTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -75,6 +118,8 @@ export async function runWalletInsufficientBalanceTest(t: GlobalTestState) {
},
});
+ t.logStep("setup-done");
+
const wres = await withdrawViaBankV3(t, {
amount: "TESTKUDOS:10",
bankClient,
@@ -83,29 +128,74 @@ export async function runWalletInsufficientBalanceTest(t: GlobalTestState) {
});
await wres.withdrawalFinishedCond;
- const exc = await t.assertThrowsTalerErrorAsync(async () => {
- await walletClient.call(WalletApiOperation.PrepareDeposit, {
- amount: "TESTKUDOS:5" as AmountString,
- depositPaytoUri: "payto://x-taler-bank/localhost/foobar",
+ {
+ const exc = await t.assertThrowsTalerErrorAsync(async () => {
+ await walletClient.call(WalletApiOperation.CheckDeposit, {
+ amount: "TESTKUDOS:5" as AmountString,
+ depositPaytoUri: "payto://x-taler-bank/localhost/foobar",
+ });
+ });
+
+ t.assertDeepEqual(
+ exc.errorDetail.code,
+ TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+ );
+
+ const insufficientBalanceDetails: PaymentInsufficientBalanceDetails =
+ exc.errorDetail.insufficientBalanceDetails;
+
+ t.assertAmountEquals(
+ insufficientBalanceDetails.balanceAvailable,
+ "TESTKUDOS:9.72",
+ );
+ t.assertAmountEquals(
+ insufficientBalanceDetails.balanceExchangeDepositable,
+ "TESTKUDOS:0",
+ );
+ }
+
+ t.logStep("start-p2p-push-test");
+
+ // Now check for p2p-push
+
+ {
+ const wres2 = await withdrawViaBankV3(t, {
+ amount: "TESTKUDOS:5",
+ bankClient,
+ exchange: exchangeTwo,
+ walletClient,
+ });
+ await wres2.withdrawalFinishedCond;
+
+ const exc = await t.assertThrowsTalerErrorAsync(async () => {
+ await walletClient.call(WalletApiOperation.CheckPeerPushDebit, {
+ amount: "TESTKUDOS:20" as AmountString,
+ });
});
- });
- t.assertDeepEqual(
- exc.errorDetail.code,
- TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
- );
-
- const insufficientBalanceDetails: PaymentInsufficientBalanceDetails =
- exc.errorDetail.insufficientBalanceDetails;
-
- t.assertAmountEquals(
- insufficientBalanceDetails.balanceAvailable,
- "TESTKUDOS:9.72",
- );
- t.assertAmountEquals(
- insufficientBalanceDetails.balanceExchangeDepositable,
- "TESTKUDOS:0",
- );
+ const insufficientBalanceDetails: PaymentInsufficientBalanceDetails =
+ exc.errorDetail.insufficientBalanceDetails;
+
+ const perMyExchange =
+ insufficientBalanceDetails.perExchange[exchange.baseUrl];
+
+ t.assertTrue(!!perMyExchange);
+
+ console.log(j2s(exc.errorDetail));
+
+ t.assertAmountEquals(
+ insufficientBalanceDetails.amountRequested,
+ "TESTKUDOS:20",
+ );
+ t.assertAmountEquals(
+ insufficientBalanceDetails.maxEffectiveSpendAmount,
+ "TESTKUDOS:14.22",
+ );
+ t.assertAmountEquals(
+ perMyExchange.maxEffectiveSpendAmount,
+ "TESTKUDOS:9.47",
+ );
+ }
}
runWalletInsufficientBalanceTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-network-availability.ts b/packages/taler-harness/src/integrationtests/test-wallet-network-availability.ts
index d97737e25..ba0038608 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-network-availability.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-network-availability.ts
@@ -25,7 +25,7 @@ import {
TransactionType,
} from "@gnu-taler/taler-util";
import { WalletApiOperation, parseTransactionIdentifier } from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState, generateRandomPayto } from "harness/harness.js";
+import { GlobalTestState, getTestHarnessPaytoForLabel } from "harness/harness.js";
import { createSimpleTestkudosEnvironmentV3, withdrawViaBankV3 } from "harness/helpers.js";
import { TaskRunResultType } from "../../../taler-wallet-core/src/common.js";
@@ -78,7 +78,7 @@ export async function runWalletNetworkAvailabilityTest(t: GlobalTestState) {
WalletApiOperation.CreateDepositGroup,
{
amount: "TESTKUDOS:10.5" as AmountString,
- depositPaytoUri: generateRandomPayto("foo"),
+ depositPaytoUri: getTestHarnessPaytoForLabel("foo"),
},
);
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts
index 5088c8228..27280103b 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-notifications.ts
@@ -32,7 +32,7 @@ import {
MerchantService,
WalletClient,
WalletService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
generateRandomTestIban,
setupDb,
} from "../harness/harness.js";
@@ -62,7 +62,7 @@ export async function runWalletNotificationsTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
const merchant = await MerchantService.create(t, {
name: "testmerchant-1",
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-observability.ts b/packages/taler-harness/src/integrationtests/test-wallet-observability.ts
index 55a60cb76..cf924f649 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-observability.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-observability.ts
@@ -26,7 +26,7 @@ import {
GlobalTestState,
WalletClient,
WalletService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import { withdrawViaBankV3 } from "../harness/helpers.js";
@@ -53,7 +53,7 @@ export async function runWalletObservabilityTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts b/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts
index 93fe94270..6197e6c36 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-refresh.ts
@@ -30,7 +30,7 @@ import {
WalletApiOperation,
parseTransactionIdentifier,
} from "@gnu-taler/taler-wallet-core";
-import { GlobalTestState, generateRandomPayto } from "../harness/harness.js";
+import { GlobalTestState, getTestHarnessPaytoForLabel } from "../harness/harness.js";
import {
createSimpleTestkudosEnvironmentV3,
makeTestPaymentV2,
@@ -120,7 +120,7 @@ export async function runWalletRefreshTest(t: GlobalTestState) {
WalletApiOperation.CreateDepositGroup,
{
amount: "TESTKUDOS:10.5" as AmountString,
- depositPaytoUri: generateRandomPayto("foo"),
+ depositPaytoUri: getTestHarnessPaytoForLabel("foo"),
},
);
@@ -175,7 +175,7 @@ export async function runWalletRefreshTest(t: GlobalTestState) {
WalletApiOperation.CreateDepositGroup,
{
amount: "TESTKUDOS:10.5" as AmountString,
- depositPaytoUri: generateRandomPayto("foo"),
+ depositPaytoUri: getTestHarnessPaytoForLabel("foo"),
},
);
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-wirefees.ts b/packages/taler-harness/src/integrationtests/test-wallet-wirefees.ts
index c5a0fd363..359adc7e3 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-wirefees.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-wirefees.ts
@@ -32,7 +32,7 @@ import {
ExchangeService,
GlobalTestState,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import {
@@ -74,7 +74,7 @@ export async function runWalletWirefeesTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
@@ -121,7 +121,7 @@ export async function runWalletWirefeesTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -130,7 +130,7 @@ export async function runWalletWirefeesTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
diff --git a/packages/taler-harness/src/integrationtests/test-wallettesting.ts b/packages/taler-harness/src/integrationtests/test-wallettesting.ts
index ef9bb9073..42b73654c 100644
--- a/packages/taler-harness/src/integrationtests/test-wallettesting.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallettesting.ts
@@ -25,10 +25,7 @@
import { AmountString, Amounts, CoinStatus } from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
-import {
- GlobalTestState,
- setupDb,
-} from "../harness/harness.js";
+import { GlobalTestState, setupDb } from "../harness/harness.js";
import {
SimpleTestEnvironmentNg3,
createSimpleTestkudosEnvironmentV3,
@@ -47,10 +44,9 @@ export async function createMyEnvironment(
): Promise<SimpleTestEnvironmentNg3> {
const db = await setupDb(t);
- const {bankClient, exchange, merchant, exchangeBankAccount} =
+ const { bankClient, bank, exchange, merchant, exchangeBankAccount } =
await createSimpleTestkudosEnvironmentV3(t, coinConfig, {});
-
console.log("setup done!");
const { walletClient, walletService } = await createWalletDaemonWithClient(
@@ -66,6 +62,7 @@ export async function createMyEnvironment(
merchant,
walletClient,
walletService,
+ bank,
bankClient,
exchangeBankAccount,
};
@@ -187,8 +184,6 @@ export async function runWallettestingTest(t: GlobalTestState) {
await walletClient.call(WalletApiOperation.ClearDb, {});
await walletClient.call(WalletApiOperation.RunIntegrationTestV2, {
- amountToSpend: "TESTKUDOS:5" as AmountString,
- amountToWithdraw: "TESTKUDOS:10" as AmountString,
corebankApiBaseUrl: bankClient.baseUrl,
exchangeBaseUrl: exchange.baseUrl,
merchantAuthToken: merchantAuthToken,
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
index c55e1faf0..2f6d2ff5d 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-conversion.ts
@@ -33,13 +33,12 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import * as http from "node:http";
import { defaultCoinConfig } from "../harness/denomStructures.js";
import {
- BankService,
+ BankService,
ExchangeService,
- FakebankService,
GlobalTestState,
HarnessExchangeBankAccount,
MerchantService,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
import { createWalletDaemonWithClient } from "../harness/helpers.js";
@@ -104,7 +103,7 @@ async function runTestfakeConversionService(): Promise<TestfakeConversionService
cashout_rounding_mode: "zero",
cashout_tiny_amount: "A:1" as AmountString,
},
- } satisfies TalerBankConversionApi.IntegrationConfig),
+ } satisfies TalerBankConversionApi.TalerConversionInfoConfig),
);
} else if (path === "/cashin-rate") {
res.writeHead(200, { "Content-Type": "application/json" });
@@ -165,7 +164,7 @@ export async function runWithdrawalConversionTest(t: GlobalTestState) {
).href,
accountName: "myexchange",
accountPassword: "x",
- accountPaytoUri: generateRandomPayto("myexchange"),
+ accountPaytoUri: getTestHarnessPaytoForLabel("myexchange"),
conversionUrl: "http://localhost:8071/",
};
@@ -204,7 +203,7 @@ export async function runWithdrawalConversionTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "default",
name: "Default Instance",
- paytoUris: [generateRandomPayto("merchant-default")],
+ paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
@@ -213,16 +212,15 @@ export async function runWithdrawalConversionTest(t: GlobalTestState) {
await merchant.addInstanceWithWireAccount({
id: "minst1",
name: "minst1",
- paytoUris: [generateRandomPayto("minst1")],
+ paytoUris: [getTestHarnessPaytoForLabel("minst1")],
defaultWireTransferDelay: Duration.toTalerProtocolDuration(
Duration.fromSpec({ minutes: 1 }),
),
});
- const { walletClient } = await createWalletDaemonWithClient(
- t,
- { name: "wallet" },
- );
+ const { walletClient } = await createWalletDaemonWithClient(t, {
+ name: "wallet",
+ });
await runTestfakeConversionService();
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts
new file mode 100644
index 000000000..3cd02882b
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-external.ts
@@ -0,0 +1,101 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ TransactionMajorState,
+ TransactionMinorState,
+ TransactionType,
+ WithdrawalType,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
+import { createSimpleTestkudosEnvironmentV3 } from "../harness/helpers.js";
+
+/**
+ * Test for a withdrawal that is externally confirmed.
+ */
+export async function runWithdrawalExternalTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange } =
+ await createSimpleTestkudosEnvironmentV3(t);
+
+ // Create a withdrawal operation
+
+ const bankUser = await bankClient.createRandomBankUser();
+ bankClient.setAuth(bankUser);
+ const wop = await bankClient.createWithdrawalOperation(
+ bankUser.username,
+ "TESTKUDOS:10",
+ );
+
+ const talerWithdrawUri = wop.taler_withdraw_uri + "?external-confirmation=1";
+
+ // Hand it to the wallet
+
+ const detResp = await walletClient.call(
+ WalletApiOperation.GetWithdrawalDetailsForUri,
+ {
+ talerWithdrawUri: talerWithdrawUri,
+ },
+ );
+
+ const acceptResp = await walletClient.call(
+ WalletApiOperation.AcceptBankIntegratedWithdrawal,
+ {
+ exchangeBaseUrl: detResp.defaultExchangeBaseUrl!!,
+ talerWithdrawUri,
+ },
+ );
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: acceptResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.BankConfirmTransfer,
+ },
+ });
+
+ const txDetails = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: acceptResp.transactionId,
+ },
+ );
+
+ // Now we check that the external-confirmation=1 flag actually did something!
+
+ t.assertDeepEqual(txDetails.type, TransactionType.Withdrawal);
+ t.assertDeepEqual(
+ txDetails.withdrawalDetails.type,
+ WithdrawalType.TalerBankIntegrationApi,
+ );
+ t.assertDeepEqual(txDetails.withdrawalDetails.externalConfirmation, true);
+ t.assertDeepEqual(txDetails.withdrawalDetails.bankConfirmationUrl, undefined);
+
+ t.logStep("confirming withdrawal operation");
+
+ await bankClient.confirmWithdrawalOperation(bankUser.username, {
+ withdrawalOperationId: wop.withdrawal_id,
+ });
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
+}
+
+runWithdrawalExternalTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
index 0657d2da7..c7bf0b938 100644
--- a/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-fees.ts
@@ -25,7 +25,7 @@ import {
ExchangeService,
GlobalTestState,
WalletCli,
- generateRandomPayto,
+ getTestHarnessPaytoForLabel,
setupDb,
} from "../harness/harness.js";
@@ -85,7 +85,7 @@ export async function runWithdrawalFeesTest(t: GlobalTestState) {
let receiverName = "Exchange";
let exchangeBankUsername = "exchange";
let exchangeBankPassword = "mypw";
- let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);
+ let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
await exchange.addBankAccount("1", {
accountName: exchangeBankUsername,
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-idempotent.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-idempotent.ts
new file mode 100644
index 000000000..0daa53f64
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-idempotent.ts
@@ -0,0 +1,172 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 {
+ AgeRestriction,
+ Amounts,
+ AmountString,
+ codecForExchangeWithdrawBatchResponse,
+ encodeCrock,
+ ExchangeBatchWithdrawRequest,
+ getRandomBytes,
+} from "@gnu-taler/taler-util";
+import {
+ HttpRequestLibrary,
+ readSuccessResponseJsonOrThrow,
+} from "@gnu-taler/taler-util/http";
+import {
+ CryptoDispatcher,
+ SynchronousCryptoWorkerFactoryPlain,
+ TalerCryptoInterface,
+} from "@gnu-taler/taler-wallet-core";
+import {
+ checkReserve,
+ CoinInfo,
+ downloadExchangeInfo,
+ findDenomOrThrow,
+ ReserveKeypair,
+ topupReserveWithBank,
+} from "@gnu-taler/taler-wallet-core/dbless";
+import { DenominationRecord } from "../../../taler-wallet-core/src/db.js";
+import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
+import { createSimpleTestkudosEnvironmentV2 } from "../harness/helpers.js";
+
+/**
+ * Run test for basic, bank-integrated withdrawal and payment.
+ */
+export async function runWithdrawalIdempotentTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);
+
+ const http = harnessHttpLib;
+ const cryptiDisp = new CryptoDispatcher(
+ new SynchronousCryptoWorkerFactoryPlain(),
+ );
+ const cryptoApi = cryptiDisp.cryptoApi;
+
+ 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 topupReserveWithBank({
+ amount: "TESTKUDOS:10" as AmountString,
+ http,
+ reservePub: reserveKeyPair.pub,
+ corebankApiBaseUrl: bank.corebankApiBaseUrl,
+ 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 denomselAllowLate = false;
+
+ const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString, {
+ denomselAllowLate,
+ });
+
+ await myWithdrawCoin({
+ http,
+ cryptoApi,
+ reserveKeyPair: {
+ reservePriv: reserveKeyPair.priv,
+ reservePub: reserveKeyPair.pub,
+ },
+ denom: d1,
+ exchangeBaseUrl: exchange.baseUrl,
+ });
+}
+
+async function myWithdrawCoin(args: {
+ http: HttpRequestLibrary;
+ cryptoApi: TalerCryptoInterface;
+ reserveKeyPair: ReserveKeypair;
+ denom: DenominationRecord;
+ exchangeBaseUrl: string;
+}): Promise<CoinInfo> {
+ const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args;
+ const planchet = await cryptoApi.createPlanchet({
+ coinIndex: 0,
+ denomPub: denom.denomPub,
+ feeWithdraw: Amounts.parseOrThrow(denom.fees.feeWithdraw),
+ reservePriv: reserveKeyPair.reservePriv,
+ reservePub: reserveKeyPair.reservePub,
+ secretSeed: encodeCrock(getRandomBytes(32)),
+ value: Amounts.parseOrThrow(denom.value),
+ });
+
+ const reqBody: ExchangeBatchWithdrawRequest = {
+ planchets: [
+ {
+ denom_pub_hash: planchet.denomPubHash,
+ reserve_sig: planchet.withdrawSig,
+ coin_ev: planchet.coinEv,
+ },
+ ],
+ };
+ const reqUrl = new URL(
+ `reserves/${planchet.reservePub}/batch-withdraw`,
+ exchangeBaseUrl,
+ ).href;
+
+ const resp = await http.fetch(reqUrl, { method: "POST", body: reqBody });
+ const rBatch = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForExchangeWithdrawBatchResponse(),
+ );
+
+ {
+ // Check for idempotency!
+ const resp2 = await http.fetch(reqUrl, { method: "POST", body: reqBody });
+ await readSuccessResponseJsonOrThrow(
+ resp2,
+ codecForExchangeWithdrawBatchResponse(),
+ );
+ }
+
+ const ubSig = await cryptoApi.unblindDenominationSignature({
+ planchet,
+ evSig: rBatch.ev_sigs[0].ev_sig,
+ });
+
+ return {
+ coinPriv: planchet.coinPriv,
+ coinPub: planchet.coinPub,
+ denomSig: ubSig,
+ denomPub: denom.denomPub,
+ denomPubHash: denom.denomPubHash,
+ feeDeposit: Amounts.stringify(denom.fees.feeDeposit),
+ feeRefresh: Amounts.stringify(denom.fees.feeRefresh),
+ exchangeBaseUrl: args.exchangeBaseUrl,
+ maxAge: AgeRestriction.AGE_UNRESTRICTED,
+ };
+}
+
+runWithdrawalIdempotentTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-withdrawal-prepare.ts b/packages/taler-harness/src/integrationtests/test-withdrawal-prepare.ts
new file mode 100644
index 000000000..b5c6cc769
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-withdrawal-prepare.ts
@@ -0,0 +1,78 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020 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 { j2s, Logger } from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { GlobalTestState } from "../harness/harness.js";
+import { createSimpleTestkudosEnvironmentV3 } from "../harness/helpers.js";
+
+const logger = new Logger("test-withdrawal-prepare.ts");
+
+/**
+ * Test the separate prepare step of a bank-integrated withdrawal.
+ */
+export async function runWithdrawalPrepareTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const { walletClient, bankClient, exchange } =
+ await createSimpleTestkudosEnvironmentV3(t);
+
+ // Create a withdrawal operation
+ const user = await bankClient.createRandomBankUser();
+ bankClient.setAuth(user);
+ const wop = await bankClient.createWithdrawalOperation(
+ user.username,
+ "TESTKUDOS:20",
+ );
+
+ const r1 = await walletClient.call(
+ WalletApiOperation.GetWithdrawalDetailsForUri,
+ {
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ },
+ );
+
+ console.log(j2s(r1));
+
+ //t.assertTrue(!r1.amount);
+
+ // Withdraw
+
+ const prepRes = await walletClient.call(
+ WalletApiOperation.PrepareBankIntegratedWithdrawal,
+ {
+ talerWithdrawUri: wop.taler_withdraw_uri,
+ },
+ );
+
+ logger.info(`Prep res: ${j2s(prepRes)}`);
+
+ // Make sure that we can get the transaction details for the prepared transaction
+
+ const txDetail = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: prepRes.transactionId,
+ },
+ );
+
+ logger.info(`Transaction details: ${j2s(txDetail)}`);
+}
+
+runWithdrawalPrepareTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
index 238bf3b98..5b36d6145 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -28,6 +28,7 @@ import {
shouldLingerInTest,
} from "../harness/harness.js";
import { getSharedTestDir } from "../harness/helpers.js";
+import { runAccountRestrictionsTest } from "./test-account-restrictions.js";
import { runAgeRestrictionsDepositTest } from "./test-age-restrictions-deposit.js";
import { runAgeRestrictionsMerchantTest } from "./test-age-restrictions-merchant.js";
import { runAgeRestrictionsMixedMerchantTest } from "./test-age-restrictions-mixed-merchant.js";
@@ -47,6 +48,18 @@ import { runExchangePurseTest } from "./test-exchange-purse.js";
import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js";
import { runFeeRegressionTest } from "./test-fee-regression.js";
import { runForcedSelectionTest } from "./test-forced-selection.js";
+import { runKycBalanceWithdrawalTest } from "./test-kyc-balance-withdrawal.js";
+import { runKycDepositAggregateTest } from "./test-kyc-deposit-aggregate.js";
+import { runKycDepositDepositKyctransferTest } from "./test-kyc-deposit-deposit-kyctransfer.js";
+import { runKycDepositDepositTest } from "./test-kyc-deposit-deposit.js";
+import { runKycExchangeWalletTest } from "./test-kyc-exchange-wallet.js";
+import { runKycFormWithdrawalTest } from "./test-kyc-form-withdrawal.js";
+import { runKycMerchantAggregateTest } from "./test-kyc-merchant-aggregate.js";
+import { runKycMerchantDepositTest } from "./test-kyc-merchant-deposit.js";
+import { runKycNewMeasureTest } from "./test-kyc-new-measure.js";
+import { runKycPeerPullTest } from "./test-kyc-peer-pull.js";
+import { runKycPeerPushTest } from "./test-kyc-peer-push.js";
+import { runKycThresholdWithdrawalTest } from "./test-kyc-threshold-withdrawal.js";
import { runKycTest } from "./test-kyc.js";
import { runLibeufinBankTest } from "./test-libeufin-bank.js";
import { runMerchantCategoriesTest } from "./test-merchant-categories.js";
@@ -83,6 +96,7 @@ import { runRefundAutoTest } from "./test-refund-auto.js";
import { runRefundGoneTest } from "./test-refund-gone.js";
import { runRefundIncrementalTest } from "./test-refund-incremental.js";
import { runRefundTest } from "./test-refund.js";
+import { runRepurchaseTest } from "./test-repurchase.js";
import { runRevocationTest } from "./test-revocation.js";
import { runSimplePaymentTest } from "./test-simple-payment.js";
import { runStoredBackupsTest } from "./test-stored-backups.js";
@@ -108,6 +122,7 @@ import { runWalletDevExperimentsTest } from "./test-wallet-dev-experiments.js";
import { runWalletExchangeUpdateTest } from "./test-wallet-exchange-update.js";
import { runWalletGenDbTest } from "./test-wallet-gendb.js";
import { runWalletInsufficientBalanceTest } from "./test-wallet-insufficient-balance.js";
+import { runWalletNetworkAvailabilityTest } from "./test-wallet-network-availability.js";
import { runWalletNotificationsTest } from "./test-wallet-notifications.js";
import { runWalletObservabilityTest } from "./test-wallet-observability.js";
import { runWalletRefreshErrorsTest } from "./test-wallet-refresh-errors.js";
@@ -118,13 +133,15 @@ import { runWithdrawalAbortBankTest } from "./test-withdrawal-abort-bank.js";
import { runWithdrawalAmountTest } from "./test-withdrawal-amount.js";
import { runWithdrawalBankIntegratedTest } from "./test-withdrawal-bank-integrated.js";
import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js";
+import { runWithdrawalExternalTest } from "./test-withdrawal-external.js";
import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js";
import { runWithdrawalFeesTest } from "./test-withdrawal-fees.js";
import { runWithdrawalFlexTest } from "./test-withdrawal-flex.js";
import { runWithdrawalHandoverTest } from "./test-withdrawal-handover.js";
import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
+import { runWithdrawalIdempotentTest } from "./test-withdrawal-idempotent.js";
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
-import { runWalletNetworkAvailabilityTest } from "./test-wallet-network-availability.js";
+import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js";
/**
* Test runner.
@@ -240,6 +257,23 @@ const allTests: TestMainFunction[] = [
runWithdrawalFlexTest,
runExchangeMasterPubChangeTest,
runMerchantCategoriesTest,
+ runWithdrawalExternalTest,
+ runWithdrawalIdempotentTest,
+ runKycThresholdWithdrawalTest,
+ runKycExchangeWalletTest,
+ runKycPeerPushTest,
+ runKycPeerPullTest,
+ runKycDepositAggregateTest,
+ runKycFormWithdrawalTest,
+ runKycBalanceWithdrawalTest,
+ runKycNewMeasureTest,
+ runKycDepositDepositTest,
+ runKycMerchantDepositTest,
+ runKycMerchantAggregateTest,
+ runKycDepositDepositKyctransferTest,
+ runWithdrawalPrepareTest,
+ runAccountRestrictionsTest,
+ runRepurchaseTest,
];
export interface TestRunSpec {