commit 4c075fb41d6d13c741c879af335a53b5c3c7c29c
parent 867c6e6a76ee23bc5c2e230ae9f0a923c4a5887d
Author: Sebastian <sebasjm@taler-systems.com>
Date: Wed, 29 Apr 2026 17:07:32 -0300
fix #11366: test for template repurchase
Diffstat:
3 files changed, 241 insertions(+), 51 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-paivana-repurchase.ts b/packages/taler-harness/src/integrationtests/test-paivana-repurchase.ts
@@ -0,0 +1,227 @@
+/*
+ This file is part of GNU Taler
+ (C) 2020-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 {
+ ConfirmPayResultType,
+ encodeCrock,
+ getRandomBytes,
+ Logger,
+ PreparePayResultType,
+ Result,
+ stringToBytes,
+ succeedOrThrow,
+ TalerMerchantInstanceHttpClient,
+ TalerProtocolTimestamp,
+ TalerUriAction,
+ TalerUris,
+ timestampRoundedToBuffer,
+ WalletNotification,
+} from "@gnu-taler/taler-util";
+import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
+import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { HashSha256 } from "../../../taler-util/src/sha256.js";
+import {
+ createSimpleTestkudosEnvironmentV3,
+ withdrawViaBankV3,
+} from "../harness/environments.js";
+import { GlobalTestState, waitMs } from "../harness/harness.js";
+
+const harnessHttpLib = createPlatformHttpLib({
+ enableThrottling: false,
+});
+export const logger = new Logger("test-paivana.ts");
+
+export function createPaivanaSessionFor(website: string) {
+ const now = new Date().getTime();
+ const cur_time = Math.floor(now / 1000); // + 60*60*24;
+ const time = TalerProtocolTimestamp.fromSeconds(cur_time);
+
+ const webArr = stringToBytes(`${website}\0`);
+ const timeArr = timestampRoundedToBuffer(time);
+ const nonceArr = getRandomBytes(16);
+ const nonce = encodeCrock(nonceArr.buffer);
+
+ const binary = new HashSha256()
+ .update(nonceArr)
+ .update(webArr)
+ .update(timeArr)
+ .digest();
+
+ const hash = Buffer.from(binary).toString("base64url");
+
+ const paivanaId = `${cur_time}-${hash}`;
+
+ return { paivanaId, nonce, time };
+}
+
+export async function runPaivanaRepurchaseTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ walletClient,
+ bankClient,
+ exchange,
+ merchant,
+ paivana,
+ merchantAdminAccessToken,
+ } = await createSimpleTestkudosEnvironmentV3(t, undefined, {
+ paivanaWebsite: ".*.html", // block all html pages
+ });
+
+ const notifs: WalletNotification[] = [];
+
+ walletClient.addNotificationListener((x) => {
+ notifs.push(x);
+ });
+
+ const withdrawalRes = await withdrawViaBankV3(t, {
+ walletClient,
+ bankClient,
+ exchange,
+ amount: "TESTKUDOS:20",
+ });
+
+ await withdrawalRes.withdrawalFinishedCond;
+
+ const website = `${paivana.baseUrl}index.html`;
+
+ const firstRequest = await harnessHttpLib.fetch(website);
+ const templateURI = firstRequest.headers.get("paivana");
+ t.assertTrue(!!templateURI);
+
+ const uri = Result.unpack(TalerUris.fromString(templateURI));
+ t.assertTrue(uri.type === TalerUriAction.PayTemplate);
+
+ const merchantClient = new TalerMerchantInstanceHttpClient(
+ merchant.makeInstanceBaseUrl(),
+ );
+
+ let times = 3;
+ while (times--) {
+ const session = createPaivanaSessionFor(website);
+ logger.info("1) PAIVANA ID created", JSON.stringify(session));
+
+ logger.info("2) access denied, we need to pay");
+
+ {
+ // Pay the access to the site
+ // This is part of the wallet and it may be a
+ // thrid device so no information produced
+ // here is available
+
+ const newTemplate = TalerUris.createTalerPayTemplate(
+ uri.merchantBaseUrl,
+ uri.templateId,
+ {
+ fulfillmenURL: website,
+ sessionId: session.paivanaId,
+ },
+ );
+ const talerPayTemplateUri = TalerUris.toString(newTemplate);
+ logger.info("3) pay template", newTemplate, talerPayTemplateUri);
+
+ const templateStatus = await walletClient.call(
+ WalletApiOperation.PreparePayForTemplate,
+ { talerPayTemplateUri },
+ );
+ // t.assertTrue(
+ // templateStatus.status === PreparePayResultType.PaymentPossible,
+ // );
+ const talerPayUri = templateStatus.talerUri;
+ logger.info("4) pay order", talerPayUri);
+
+ const payStatus = await walletClient.call(
+ WalletApiOperation.PreparePayForUri,
+ { talerPayUri },
+ );
+ // t.assertTrue(payStatus.status === PreparePayResultType.PaymentPossible);
+
+ const startPayment = await walletClient.call(
+ WalletApiOperation.ConfirmPay,
+ {
+ transactionId: payStatus.transactionId,
+ },
+ );
+
+ // t.assertTrue(startPayment.type === ConfirmPayResultType.Pending);
+ await walletClient.call(
+ WalletApiOperation.TestingWaitTransactionsFinal,
+ {},
+ );
+ const payment = await walletClient.call(WalletApiOperation.ConfirmPay, {
+ transactionId: payStatus.transactionId,
+ });
+ t.assertTrue(payment.type === ConfirmPayResultType.Done);
+ logger.info("5) order paid", payment.contractTerms.fulfillment_url);
+
+ const orderStatus = succeedOrThrow(
+ await merchantClient.getOrderDetails(
+ merchantAdminAccessToken,
+ payment.contractTerms.order_id,
+ {
+ sessionId: session.paivanaId,
+ },
+ ),
+ );
+ console.log("asdasdasd",orderStatus)
+ // check that merchant also think is paid for this session
+ t.assertTrue(orderStatus.order_status === "paid");
+ }
+
+ logger.info("6) getting the order based on session and site");
+
+ const order = succeedOrThrow(
+ await merchantClient.getOrderIdForSessionAndUrl(
+ session.paivanaId,
+ website,
+ ),
+ );
+
+ logger.info(`---- STATE ${website}`, {
+ order_id: order.order_id,
+ nonce: session.nonce,
+ cur_time: session.time,
+ website,
+ });
+
+ logger.info("7) showing the info to paivana so it will return the cookie");
+ const res = await harnessHttpLib.fetch(
+ `${paivana.baseUrl}.well-known/paivana`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: {
+ order_id: order.order_id,
+ nonce: session.nonce,
+ cur_time: session.time,
+ website,
+ },
+ redirect: "manual",
+ },
+ );
+
+ t.assertTrue(res.headers.get("location") === website);
+ t.assertTrue(!!res.headers.get("Set-Cookie"));
+ t.assertTrue(res.status === 303);
+
+ await waitMs(600)
+ }
+}
+
+runPaivanaRepurchaseTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/test-paivana.ts b/packages/taler-harness/src/integrationtests/test-paivana.ts
@@ -43,6 +43,7 @@ import {
withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState } from "../harness/harness.js";
+import { createPaivanaSessionFor } from "./test-paivana-repurchase.js";
const harnessHttpLib = createPlatformHttpLib({
enableThrottling: false,
@@ -87,39 +88,15 @@ export async function runPaivanaTest(t: GlobalTestState) {
const uri = Result.unpack(TalerUris.fromString(templateURI));
t.assertTrue(uri.type === TalerUriAction.PayTemplate);
- const now = (Date.now())
- const cur_time = Math.floor(now / 1000); // + 60*60*24;
-
- const webArr = stringToBytes(`${website}\0`);
- const timeArr = timestampRoundedToBuffer(
- TalerProtocolTimestamp.fromSeconds(cur_time),
- );
- const nonceArr = getRandomBytes(16);
- const nonce = encodeCrock(nonceArr.buffer);
-
- const binary = new HashSha256()
- .update(nonceArr)
- .update(webArr)
- .update(timeArr)
- .digest();
-
- const hash = Buffer.from(binary).toString("base64url");
-
- const paivanaId = `${cur_time}-${hash}`;
const merchantClient = new TalerMerchantInstanceHttpClient(
merchant.makeInstanceBaseUrl(),
);
- logger.info("1) PAIVANA ID created", paivanaId);
+ const session = createPaivanaSessionFor(website)
+
+ logger.info("1) PAIVANA ID created", JSON.stringify(session));
- // {
- // // Check if we magically have access (guess what, no)
- // const res = await harnessHttpLib.fetch(`${PAIVANA_POLL_BASE}`);
- // // const res = await harnessHttpLib.fetch(`${PAIVANA_POLL_BASE}?timeout_ms=1000`);
- // logger.info("first paivana check", res);
- // t.assertTrue(res.status === 402);
- // }
logger.info("2) access denied, we need to pay");
{
@@ -133,7 +110,7 @@ export async function runPaivanaTest(t: GlobalTestState) {
uri.templateId,
{
fulfillmenURL: website,
- sessionId: paivanaId,
+ sessionId: session.paivanaId,
},
);
const talerPayTemplateUri = TalerUris.toString(newTemplate);
@@ -178,7 +155,7 @@ export async function runPaivanaTest(t: GlobalTestState) {
merchantAdminAccessToken,
payment.contractTerms.order_id,
{
- sessionId: paivanaId,
+ sessionId: session.paivanaId,
},
),
);
@@ -189,30 +166,14 @@ export async function runPaivanaTest(t: GlobalTestState) {
logger.info("6) getting the order based on session and site");
const order = succeedOrThrow(
- await merchantClient.getOrderIdForSessionAndUrl(paivanaId, website),
+ await merchantClient.getOrderIdForSessionAndUrl(session.paivanaId, website),
);
- // while (true) {
- // // Check if we have access
- // const res = await harnessHttpLib.fetch(
- // `${PAIVANA_POLL_BASE}?timeout_ms=3000`,
- // );
- // logger.info("paivana say", res);
- // if (res.status !== 200) {
- // continue;
- // }
- // t.assertTrue(res.status === 200);
- // try {
- // order_id = (await res.json()).order_id ?? undefined;
- // } catch (_) {}
- // break;
- // }
-
logger.info(`---- STATE ${website}`, {
order_id: order.order_id,
- nonce,
- cur_time,
+ nonce: session.nonce,
+ cur_time: session.time,
website,
});
@@ -223,8 +184,8 @@ export async function runPaivanaTest(t: GlobalTestState) {
headers: { "Content-Type": "application/json" },
body: {
order_id: order.order_id,
- nonce,
- cur_time: { t_s: cur_time },
+ nonce: session.nonce,
+ cur_time: session.time,
website,
},
redirect: "manual",
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -225,6 +225,7 @@ import { runWithdrawalIdempotentTest } from "./test-withdrawal-idempotent.js";
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js";
import { runPaivanaTest } from "./test-paivana.js";
+import { runPaivanaRepurchaseTest } from "./test-paivana-repurchase.js";
/**
* Test runner.
@@ -433,7 +434,8 @@ const allTests: TestMainFunction[] = [
runWireMetadataTest,
runMerchantPaytoReuseTest,
runPreparedTransferTest,
- runPaivanaTest
+ runPaivanaTest,
+ runPaivanaRepurchaseTest,
];
export interface TestRunSpec {