commit 9a299f4e4e60df79eeaf96724ef79d016350a0b7
parent b048d0ea9b9378b4802611de548129fbb1b996ac
Author: Florian Dold <florian@dold.me>
Date: Fri, 22 May 2026 21:40:10 +0200
harness: add (incomplete!) test for payto reuse between wallet/merchant
Diffstat:
2 files changed, 245 insertions(+), 0 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-wallet-reuse.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-wallet-reuse.ts
@@ -0,0 +1,243 @@
+/*
+ This file is part of GNU Taler
+ (C) 2024 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,
+ AccessToken,
+ Duration,
+ j2s,
+ Logger,
+ succeedOrThrow,
+ TalerExchangeHttpClient,
+ TalerMerchantInstanceHttpClient,
+ TalerProtocolDuration,
+ TransactionMajorState,
+ TransactionMinorState,
+ TransactionType,
+} from "@gnu-taler/taler-util";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { withdrawViaBankV3 } from "../harness/environments.js";
+import { startFakeChallenger } from "../harness/fake-challenger.js";
+import { GlobalTestState, harnessHttpLib, waitMs } from "../harness/harness.js";
+import { createTopsEnvironment } from "../harness/tops.js";
+
+const logger = new Logger(`test-kyc-merchant-deposit.ts`);
+
+export async function runKycMerchantWalletReuseTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ walletClient,
+ bankClient,
+ exchange,
+ exchangeApi,
+ officerAcc,
+ merchant,
+ merchantAdminAccessToken,
+ wireGatewayApi,
+ } = await createTopsEnvironment(t);
+
+ const challenger = await startFakeChallenger({
+ port: 6001,
+ addressType: "postal-ch",
+ });
+
+ const exchangeClient = new TalerExchangeHttpClient(exchange.baseUrl, {
+ httpClient: harnessHttpLib,
+ });
+
+ t.logStep("starting withdrawal");
+
+ // Withdrawal below threshold succeeds!
+ const wres = await withdrawViaBankV3(t, {
+ amount: "CHF:20",
+ bankClient,
+ exchange,
+ walletClient,
+ });
+
+ await wres.withdrawalFinishedCond;
+
+ t.logStep("withdrawal done");
+
+ const peerResp = await walletClient.call(
+ WalletApiOperation.InitiatePeerPushDebit,
+ {
+ partialContractTerms: {
+ amount: "CHF:5",
+ purse_expiration: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ minutes: 5 }),
+ ),
+ ),
+ summary: "Test",
+ },
+ },
+ );
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: peerResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Ready,
+ },
+ });
+
+ t.logStep("p2p ready");
+
+ const pushDebitTxDet = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: peerResp.transactionId,
+ },
+ );
+ t.assertDeepEqual(pushDebitTxDet.type, TransactionType.PeerPushDebit);
+ const talerUri = pushDebitTxDet.talerUri;
+ t.assertTrue(typeof talerUri === "string");
+
+ const prepareResp = await walletClient.call(
+ WalletApiOperation.PreparePeerPushCredit,
+ {
+ talerUri,
+ },
+ );
+
+ await walletClient.call(WalletApiOperation.ConfirmPeerPushCredit, {
+ transactionId: prepareResp.transactionId,
+ });
+
+ t.logStep("p2p confirmed");
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: prepareResp.transactionId,
+ timeout: { seconds: 10 },
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.KycRequired,
+ },
+ });
+
+ t.logStep("p2p merge kyc required");
+
+ const pushCreditTxDet = await walletClient.call(
+ WalletApiOperation.GetTransactionById,
+ {
+ transactionId: prepareResp.transactionId,
+ },
+ );
+
+ const paytoHash = pushCreditTxDet.kycPaytoHash;
+ t.assertTrue(!!paytoHash);
+
+ const accessToken = pushCreditTxDet.kycAccessToken;
+
+ t.assertTrue(typeof accessToken === "string");
+
+ const infoResp = await exchangeApi.checkKycInfo(accessToken as AccessToken);
+
+ console.log(j2s(infoResp));
+
+ t.assertDeepEqual(infoResp.case, "ok");
+
+ const myId = infoResp.body.requirements.find((x) =>
+ x.description.includes("TAN letter"),
+ )?.id;
+ t.assertTrue(!!myId);
+
+ const startResp = succeedOrThrow(
+ await exchangeApi.startExternalKycProcess(myId, {}),
+ );
+ console.log(`start resp`, j2s(startResp));
+
+ let challengerRedirectUrl = startResp.redirect_url;
+
+ const resp = await harnessHttpLib.fetch(challengerRedirectUrl);
+ const respJson = await resp.json();
+ console.log(`challenger resp: ${j2s(respJson)}`);
+
+ const nonce = respJson.nonce;
+ t.assertTrue(typeof nonce === "string");
+ const proofRedirectUrl = respJson.redirect_url;
+
+ challenger.fakeVerification(nonce, {
+ CONTACT_NAME: "Richard Stallman",
+ ADDRESS_LINES: "Bundesgasse 1\n1234 Bern",
+ });
+
+ console.log("nonce", nonce);
+ console.log("proof redirect URL", proofRedirectUrl);
+
+ const proofResp = await harnessHttpLib.fetch(proofRedirectUrl, {
+ redirect: "manual",
+ });
+ console.log("proof status:", proofResp.status);
+ if (proofResp.status === 404) {
+ console.log(j2s(await proofResp.text()));
+ }
+ t.assertDeepEqual(proofResp.status, 303);
+
+ const infoResp2 = await exchangeApi.checkKycInfo(accessToken as AccessToken);
+
+ console.log(j2s(infoResp2));
+
+ if (infoResp2.case === "ok" && infoResp2.body.requirements.length != 0) {
+ t.fail("requirements may not include ToS after KYC for P2P");
+ }
+
+ await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
+ transactionId: prepareResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Done,
+ },
+ });
+
+ {
+ const { accessToken: mt1Tok } = await merchant.addInstanceWithWireAccount(
+ {
+ id: "mt1",
+ name: "MerchantTest Instance",
+ paytoUris: [wres.accountPaytoUri],
+ defaultWireTransferDelay: TalerProtocolDuration.fromSpec({
+ minutes: 1,
+ }),
+ },
+ { adminAccessToken: merchantAdminAccessToken },
+ );
+
+ const merchantApi = new TalerMerchantInstanceHttpClient(
+ merchant.makeInstanceBaseUrl("mt1"),
+ harnessHttpLib,
+ );
+
+ while (true) {
+ const st = await merchantApi.getCurrentInstanceKycStatus(mt1Tok);
+
+ console.log(j2s(st));
+
+ if (st.case != "ok") {
+ await waitMs(1000);
+ }
+
+
+ }
+ }
+}
+
+runKycMerchantWalletReuseTest.suites = ["merchant", "kyc"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -94,6 +94,7 @@ import { runKycMerchantAggregateTest } from "./test-kyc-merchant-aggregate.js";
import { runKycMerchantDepositFormTest } from "./test-kyc-merchant-deposit-form.js";
import { runKycMerchantDepositRewriteTest } from "./test-kyc-merchant-deposit-rewrite.js";
import { runKycMerchantDepositTest } from "./test-kyc-merchant-deposit.js";
+import { runKycMerchantWalletReuseTest } from "./test-kyc-merchant-wallet-reuse.js";
import { runKycNewMeasureTest } from "./test-kyc-new-measure.js";
import { runKycNewMeasuresProgTest } from "./test-kyc-new-measures-prog.js";
import { runKycPeerPullTest } from "./test-kyc-peer-pull.js";
@@ -436,6 +437,7 @@ const allTests: TestMainFunction[] = [
runPaivanaTest,
runPaivanaRepurchaseTest,
runKycFormValidationTest,
+ runKycMerchantWalletReuseTest,
];
export interface TestRunSpec {