/*
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
*/
/**
* 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/environments.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 {
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-password";
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: "admin-password",
},
});
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"];