taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit ac60a4467b5eaa8f8c431ee9c32b29a4bb290db2
parent 17b1149a1a781018491cecd000ce15a669ce9a67
Author: Florian Dold <florian@dold.me>
Date:   Fri, 26 Jun 2026 01:50:24 +0200

harness: split up TOPS / short wire transfer subject tests, add merchant test

Diffstat:
Mpackages/taler-harness/src/harness/harness.ts | 12++++++++++++
Mpackages/taler-harness/src/harness/tops.ts | 2+-
Apackages/taler-harness/src/integrationtests/test-tops-merchant-kycauths.ts | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/test-tops-nexus-basic.ts | 5++++-
Apackages/taler-harness/src/integrationtests/test-tops-nexus-swt.ts | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 4++++
6 files changed, 346 insertions(+), 2 deletions(-)

diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts @@ -899,6 +899,11 @@ export interface NexusBankAccountInfo { iban: string; name: string; bic: string; + /** + * QR IBAN of a QR virtual bank account linked to the configured + * bank account that can be used for QR BILL. + */ + qrIban?: string; } export class LibeufinNexusService { @@ -950,6 +955,9 @@ export class LibeufinNexusService { config.setString("nexus-ebics", "iban", bi.iban); config.setString("nexus-ebics", "bic", bi.bic); config.setString("nexus-ebics", "name", bi.name); + if (bi.qrIban) { + config.setString("nexus-ebics", "qr_iban", bi.qrIban); + } } const cfgFilename = testDir + "/nexus.conf"; config.writeTo(cfgFilename, { excludeDefaults: true }); @@ -1016,6 +1024,10 @@ export class LibeufinNexusService { get wireGatewayApiBaseUrl(): string { return `http://localhost:${this.bc.httpPort}/taler-wire-gateway/`; } + + get preparedTransferApiBaseUrl(): string { + return `http://localhost:${this.bc.httpPort}/taler-prepared-transfer/`; + } } /** diff --git a/packages/taler-harness/src/harness/tops.ts b/packages/taler-harness/src/harness/tops.ts @@ -507,7 +507,7 @@ FALLBACK = freeze-investigate `; -const topsProvidersTestConf = ` +export const topsProvidersTestConf = ` [kyc-provider-postal-challenger] LOGIC = oauth2 KYC_OAUTH2_VALIDITY = 2 years diff --git a/packages/taler-harness/src/integrationtests/test-tops-merchant-kycauths.ts b/packages/taler-harness/src/integrationtests/test-tops-merchant-kycauths.ts @@ -0,0 +1,165 @@ +/* + This file is part of GNU Taler + (C) 2025 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 { + IbanString, + j2s, + Logger, + MerchantAccountKycStatus, + Paytos, + TalerMerchantInstanceHttpClient, + TalerProtocolDuration, +} from "@gnu-taler/taler-util"; +import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; +import { startFakeChallenger } from "../harness/fake-challenger.js"; +import { + ExchangeService, + GlobalTestState, + LibeufinNexusService, + MerchantService, + NexusBankAccountInfo, + setupDb, +} from "../harness/harness.js"; +import { topsKycRulesConf, topsProvidersTestConf } from "../harness/tops.js"; + +const logger = new Logger("test-tops-merchant-kycauths.ts"); + +/** + * Test short wire transfers in an exchange setup that simulates + * the Taler Operations CH exchange, using libeufin-nexus + * as the taler wire gateway API. + */ +export async function runTopsMerchantKycauthsTest(t: GlobalTestState) { + const db = await setupDb(t); + + const challenger = await startFakeChallenger({ + port: 6001, + addressType: "postal-ch", + }); + + let coinConfig: CoinConfig[]; + coinConfig = defaultCoinConfig.map((x) => x("CHF")); + + const bankAccountInfo: NexusBankAccountInfo = { + // Random IBAN generated via "libeufin-nexus testing iban gen --country CH" + iban: "CH7347363QVFHHFR8BWWB", + // PostFinance BIC + bic: "POFICHBEXXX", + name: "Harness Test Exchange", + qrIban: "CH1130000001166556117", + }; + + const nexus = await LibeufinNexusService.create(t, { + currency: "CHF", + database: db.connStr, + httpPort: 8085, + bankAccountInfo, + }); + + const exchangePayto = Paytos.toFullString( + Paytos.createIban(bankAccountInfo.qrIban as IbanString, undefined, { + "receiver-name": bankAccountInfo.name, + }), + ); + + await nexus.dbinit(); + + await nexus.start(); + + const exchange = ExchangeService.create(t, { + name: "testexchange-1", + currency: "CHF", + httpPort: 8081, + database: db.connStr, + // FIXME: This is a terrible way to configure the exchange, should be moved into config. + extraProcEnv: { + EXCHANGE_AML_PROGRAM_TOPS_ENABLE_DEPOSITS_TOS_NAME: "v1", + EXCHANGE_AML_PROGRAM_TOPS_ENABLE_DEPOSITS_THRESHOLD: "CHF:0", + EXCHANGE_AML_PROGRAM_TOPS_POSTAL_CHECK_COUNTRY_REGEX: "ch|CH|Ch", + }, + }); + + exchange.addBankAccount("nexusacct", { + accountPaytoUri: exchangePayto, + wireGatewayApiBaseUrl: nexus.wireGatewayApiBaseUrl, + wireGatewayAuth: { + type: "basic", + username: "exchange-test", + password: "exchange-test", + }, + preparedTransferUrl: nexus.preparedTransferApiBaseUrl, + }); + + exchange.addCoinConfigList(coinConfig); + + await exchange.modifyConfig(async (config) => { + config.loadFromString(topsKycRulesConf); + config.loadFromString(topsProvidersTestConf); + }); + + await exchange.start(); + + const merchantIban = "CH4810303KL83V1QZ05T7"; + + const merchant = await MerchantService.create(t, { + name: "testmerchant-1", + httpPort: 8083, + database: db.connStr, + }); + + merchant.addExchange(exchange); + + await merchant.start(); + + const merchantPayto = Paytos.toFullString( + Paytos.createIban(merchantIban as IbanString, undefined, { + "receiver-name": "Merchant Test", + }), + ); + + const { accessToken: merchantAdminAccessToken } = + await merchant.addInstanceWithWireAccount({ + id: "admin", + name: "Default Instance", + paytoUris: [merchantPayto], + defaultWireTransferDelay: TalerProtocolDuration.fromSpec({ minutes: 1 }), + }); + + const merchantClient = new TalerMerchantInstanceHttpClient( + merchant.makeInstanceBaseUrl(), + ); + // Do KYC auth transfer + + const kycStatus = await merchantClient.getCurrentInstanceKycStatus( + merchantAdminAccessToken, + { + longpoll: { + type: "state-enter", + status: MerchantAccountKycStatus.KYC_WIRE_REQUIRED, + timeout: 10000, + }, + }, + ); + t.assertTrue(kycStatus.type === "ok"); + console.log(`kyc status: ${j2s(kycStatus)}`); + + t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-wire-required"); +} + +runTopsMerchantKycauthsTest.suites = ["tops", "libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/test-tops-nexus-basic.ts b/packages/taler-harness/src/integrationtests/test-tops-nexus-basic.ts @@ -41,9 +41,12 @@ import { const logger = new Logger("test-tops-nexus-swt.ts"); /** - * Test short wire transfers in an exchange setup that simulates + * Test normal wire transfers in an exchange setup that simulates * the Taler Operations CH exchange, using libeufin-nexus * as the taler wire gateway API. + * + * In this test, the TOPS environment is simplified and does + * not configure any KYC checks. */ export async function runTopsNexusBasicTest(t: GlobalTestState) { const db = await setupDb(t); diff --git a/packages/taler-harness/src/integrationtests/test-tops-nexus-swt.ts b/packages/taler-harness/src/integrationtests/test-tops-nexus-swt.ts @@ -0,0 +1,160 @@ +/* + This file is part of GNU Taler + (C) 2025 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, + IbanString, + j2s, + Logger, + Paytos, + TalerProtocolDuration, + TransactionMajorState, + TransactionType, + WithdrawalType, +} from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; +import { createWalletDaemonWithClient } from "../harness/environments.js"; +import { startFakeChallenger } from "../harness/fake-challenger.js"; +import { + ExchangeService, + GlobalTestState, + LibeufinNexusService, + MerchantService, + NexusBankAccountInfo, + setupDb, +} from "../harness/harness.js"; +import { topsKycRulesConf, topsProvidersTestConf } from "../harness/tops.js"; + +const logger = new Logger("test-tops-nexus-swt.ts"); + +/** + * Test short wire transfers in an exchange setup that simulates + * the Taler Operations CH exchange, using libeufin-nexus + * as the taler wire gateway API. + */ +export async function runTopsNexusSwtTest(t: GlobalTestState) { + const db = await setupDb(t); + + const challenger = await startFakeChallenger({ + port: 6001, + addressType: "postal-ch", + }); + + let coinConfig: CoinConfig[]; + coinConfig = defaultCoinConfig.map((x) => x("CHF")); + + const bankAccountInfo: NexusBankAccountInfo = { + // Random IBAN generated via "libeufin-nexus testing iban gen --country CH" + iban: "CH7347363QVFHHFR8BWWB", + // PostFinance BIC + bic: "POFICHBEXXX", + name: "Harness Test Exchange", + qrIban: "CH1130000001166556117", + }; + + const nexus = await LibeufinNexusService.create(t, { + currency: "CHF", + database: db.connStr, + httpPort: 8085, + bankAccountInfo, + }); + + const exchangePayto = Paytos.toFullString( + Paytos.createIban(bankAccountInfo.qrIban as IbanString, undefined, { + "receiver-name": bankAccountInfo.name, + }), + ); + + await nexus.dbinit(); + + await nexus.start(); + + const exchange = ExchangeService.create(t, { + name: "testexchange-1", + currency: "CHF", + httpPort: 8081, + database: db.connStr, + // FIXME: This is a terrible way to configure the exchange, should be moved into config. + extraProcEnv: { + EXCHANGE_AML_PROGRAM_TOPS_ENABLE_DEPOSITS_TOS_NAME: "v1", + EXCHANGE_AML_PROGRAM_TOPS_ENABLE_DEPOSITS_THRESHOLD: "CHF:0", + EXCHANGE_AML_PROGRAM_TOPS_POSTAL_CHECK_COUNTRY_REGEX: "ch|CH|Ch", + }, + }); + + exchange.addBankAccount("nexusacct", { + accountPaytoUri: exchangePayto, + wireGatewayApiBaseUrl: nexus.wireGatewayApiBaseUrl, + wireGatewayAuth: { + type: "basic", + username: "exchange-test", + password: "exchange-test", + }, + preparedTransferUrl: nexus.preparedTransferApiBaseUrl, + }); + + exchange.addCoinConfigList(coinConfig); + + await exchange.modifyConfig(async (config) => { + config.loadFromString(topsKycRulesConf); + config.loadFromString(topsProvidersTestConf); + }); + + await exchange.start(); + + const { walletClient } = await createWalletDaemonWithClient(t, { + name: "wallet", + }); + + const acceptRes = await walletClient.call( + WalletApiOperation.AcceptManualWithdrawal, + { + amount: "CHF:10" as AmountString, + exchangeBaseUrl: exchange.baseUrl, + }, + ); + + const wtx = await walletClient.call(WalletApiOperation.GetTransactionById, { + transactionId: acceptRes.transactionId, + }); + + t.assertDeepEqual(wtx.type, TransactionType.Withdrawal); + t.assertDeepEqual(wtx.withdrawalDetails.type, WithdrawalType.ManualTransfer); + + console.log(j2s(wtx.withdrawalDetails.exchangeCreditAccountDetails)); + const transferOpt = + wtx.withdrawalDetails.exchangeCreditAccountDetails?.[0].transferOptions?.find( + (x) => x.type === "ch-qr-bill", + ); + t.assertTrue(!!transferOpt); + + await nexus.fakeIncoming({ + creditPayto: transferOpt.paytoUri, + }); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: acceptRes.transactionId, + txState: { + major: TransactionMajorState.Done, + }, + }); +} + +runTopsNexusSwtTest.suites = ["tops", "libeufin"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -173,8 +173,10 @@ import { runTopsAmlLegiTest } from "./test-tops-aml-legi.js"; import { runTopsAmlMeasuresTest } from "./test-tops-aml-measures.js"; import { runTopsAmlPdfTest } from "./test-tops-aml-pdf.js"; import { runTopsChallengerTwiceTest } from "./test-tops-challenger-twice.js"; +import { runTopsMerchantKycauthsTest } from "./test-tops-merchant-kycauths.js"; import { runTopsMerchantTosTest } from "./test-tops-merchant-tos.js"; import { runTopsNexusBasicTest } from "./test-tops-nexus-basic.js"; +import { runTopsNexusSwtTest } from "./test-tops-nexus-swt.js"; import { runTopsPeerTest } from "./test-tops-peer.js"; import { runTermOfServiceFormatTest } from "./test-tos-format.js"; import { runUtilMerchantClientTest } from "./test-util-merchant-client.js"; @@ -440,6 +442,8 @@ const allTests: TestMainFunction[] = [ runBalanceProspectiveTest, runMerchantTemplatesTest, runTopsNexusBasicTest, + runTopsNexusSwtTest, + runTopsMerchantKycauthsTest, ]; export interface TestRunSpec {