taler-typescript-core

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

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:
Apackages/taler-harness/src/integrationtests/test-paivana-repurchase.ts | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/test-paivana.ts | 61+++++++++++--------------------------------------------------
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 4+++-
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 {