commit bc382d4674b17e9c91e10287002d40603b9d1ab7
parent 9713967b3f0d4e1d0a17f86978cc024528738877
Author: Florian Dold <florian@dold.me>
Date: Wed, 24 Jun 2026 01:28:15 +0200
harness: advanced token playground test
Diffstat:
3 files changed, 286 insertions(+), 88 deletions(-)
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts
@@ -18,7 +18,6 @@
* Imports.
*/
import {
- AbsoluteTime,
AccessToken,
AmountJson,
AmountString,
@@ -33,9 +32,6 @@ import {
Logger,
LoginTokenScope,
MerchantAuthMethod,
- OrderInputType,
- OrderOutputType,
- OrderVersion,
PaytoString,
TalerBankConversionHttpClient,
TalerCoreBankHttpClient,
@@ -110,6 +106,10 @@ import { AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT } from "./integrationtests/test-
import { AML_PROGRAM_NEXT_MEASURE_FORM } from "./integrationtests/test-kyc-two-forms.js";
import { getTestInfo, runTests } from "./integrationtests/testrunner.js";
import { lintExchangeDeployment, lintExchangeUrl } from "./lint.js";
+import {
+ runPlaygroundAdvancedTokens1,
+ runPlaygroundBlog,
+} from "./playground.js";
const logger = new Logger("taler-harness:index.ts");
@@ -1787,89 +1787,22 @@ playgroundCli
.requiredOption("merchantPw", ["--merchant-pw"], clk.STRING)
.maybeOption("payDelay", ["--pay-delay"], clk.STRING)
.action(async (args) => {
- const merchantClient = new TalerMerchantInstanceHttpClient(
- args.exp.merchantUrl,
- );
- const config = succeedOrThrow(await merchantClient.getConfig());
- const tok = succeedOrThrow(
- await merchantClient.createAccessToken(
- merchantClient.guessInstanceName(),
- args.exp.merchantPw,
- {
- scope: LoginTokenScope.All,
- duration: Duration.toTalerProtocolDuration(
- Duration.fromSpec({ hours: 1 }),
- ),
- },
- ),
- );
-
- const subscriptionAmount = `${config.currency}:5` as AmountString;
- const articleAmount = `${config.currency}:1` as AmountString;
-
- const payDeadline =
- args.exp.payDelay == null
- ? undefined
- : AbsoluteTime.toProtocolTimestamp(
- AbsoluteTime.addDuration(
- AbsoluteTime.now(),
- Duration.fromPrettyString(args.exp.payDelay),
- ),
- );
-
- const createResp = succeedOrThrow(
- await merchantClient.createOrder(tok.access_token, {
- order: {
- pay_deadline: payDeadline,
- version: OrderVersion.V1,
- summary: "Test Blog Payment",
- choices: [
- {
- amount: articleAmount,
- description: "Buy an individual article",
- },
- {
- amount: subscriptionAmount as AmountString,
- description: "Buy one month of unlimited access",
- outputs: [
- {
- type: OrderOutputType.Token,
- token_family_slug: "blog_abo_en",
- },
- ],
- },
- {
- amount: Amounts.stringify(
- Amounts.zeroOfCurrency(config.currency),
- ),
- inputs: [
- {
- type: OrderInputType.Token,
- token_family_slug: "blog_abo_en",
- },
- ],
- outputs: [
- {
- type: OrderOutputType.Token,
- token_family_slug: "blog_abo_en",
- },
- ],
- },
- ],
- },
- }),
- );
-
- const st = succeedOrThrow(
- await merchantClient.getOrderDetails(
- tok.access_token,
- createResp.order_id,
- ),
- );
+ await runPlaygroundBlog({
+ merchantPw: args.exp.merchantPw,
+ merchantUrl: args.exp.merchantUrl,
+ payDelay: args.exp.payDelay,
+ });
+ });
- if (st.order_status === "unpaid") {
- console.log(st.taler_pay_uri);
- }
+playgroundCli
+ .subcommand("exp", "advanced-tokens-1", {})
+ .requiredOption("merchantUrl", ["--merchant-url"], clk.STRING)
+ .requiredOption("merchantPw", ["--merchant-pw"], clk.STRING)
+ .action(async (args) => {
+ await runPlaygroundAdvancedTokens1({
+ merchantPw: args.exp.merchantPw,
+ merchantUrl: args.exp.merchantUrl,
+ });
});
export const helpersCli = talerHarnessCli.subcommand(
diff --git a/packages/taler-harness/src/playground.ts b/packages/taler-harness/src/playground.ts
@@ -0,0 +1,267 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 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/>
+ */
+
+import {
+ AbsoluteTime,
+ AmountString,
+ Amounts,
+ Duration,
+ LoginTokenScope,
+ OrderInputType,
+ OrderOutputType,
+ OrderVersion,
+ TalerMerchantInstanceHttpClient,
+ TokenFamilyKind,
+ encodeCrock,
+ getRandomBytes,
+ succeedOrThrow,
+} from "@gnu-taler/taler-util";
+
+export interface PlaygroundBlogArgs {
+ merchantUrl: string;
+ merchantPw: string;
+ payDelay?: string;
+}
+
+export async function runPlaygroundBlog(
+ args: PlaygroundBlogArgs,
+): Promise<void> {
+ const merchantClient = new TalerMerchantInstanceHttpClient(args.merchantUrl);
+ const config = succeedOrThrow(await merchantClient.getConfig());
+ const tok = succeedOrThrow(
+ await merchantClient.createAccessToken(
+ merchantClient.guessInstanceName(),
+ args.merchantPw,
+ {
+ scope: LoginTokenScope.All,
+ duration: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ },
+ ),
+ );
+
+ const subscriptionAmount = `${config.currency}:5` as AmountString;
+ const articleAmount = `${config.currency}:1` as AmountString;
+
+ const payDeadline =
+ args.payDelay == null
+ ? undefined
+ : AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromPrettyString(args.payDelay),
+ ),
+ );
+
+ const createResp = succeedOrThrow(
+ await merchantClient.createOrder(tok.access_token, {
+ order: {
+ pay_deadline: payDeadline,
+ version: OrderVersion.V1,
+ summary: "Test Blog Payment",
+ choices: [
+ {
+ amount: articleAmount,
+ description: "Buy an individual article",
+ },
+ {
+ amount: subscriptionAmount as AmountString,
+ description: "Buy one month of unlimited access",
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: "blog_abo_en",
+ },
+ ],
+ },
+ {
+ amount: Amounts.stringify(Amounts.zeroOfCurrency(config.currency)),
+ inputs: [
+ {
+ type: OrderInputType.Token,
+ token_family_slug: "blog_abo_en",
+ },
+ ],
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: "blog_abo_en",
+ },
+ ],
+ },
+ ],
+ },
+ }),
+ );
+
+ const st = succeedOrThrow(
+ await merchantClient.getOrderDetails(tok.access_token, createResp.order_id),
+ );
+
+ if (st.order_status === "unpaid") {
+ console.log(st.taler_pay_uri);
+ }
+}
+
+export interface PlaygroundAdvancedTokensArgs {
+ merchantUrl: string;
+ merchantPw: string;
+ payDelay?: string;
+}
+
+export async function runPlaygroundAdvancedTokens1(
+ args: PlaygroundAdvancedTokensArgs,
+): Promise<void> {
+ const merchantClient = new TalerMerchantInstanceHttpClient(args.merchantUrl);
+ const config = succeedOrThrow(await merchantClient.getConfig());
+ const tok = succeedOrThrow(
+ await merchantClient.createAccessToken(
+ merchantClient.guessInstanceName(),
+ args.merchantPw,
+ {
+ scope: LoginTokenScope.All,
+ duration: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ },
+ ),
+ );
+
+ const rnd = encodeCrock(getRandomBytes(16));
+
+ const slugDiscount1 = `slugdiscount1-${rnd}`;
+ const slugSubscription1 = `slugsubscription1-${rnd}`;
+
+ await merchantClient.createTokenFamily(tok.access_token, {
+ name: "discount1",
+ slug: slugDiscount1,
+ description: "My Discount 1 (valid two minutes)",
+ duration: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 2 }),
+ ),
+ valid_before: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ ),
+ kind: TokenFamilyKind.Discount,
+ validity_granularity: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ await merchantClient.createTokenFamily(tok.access_token, {
+ name: "subscription1",
+ slug: slugSubscription1,
+ description: "My Subscription 1 (valid one minute)",
+ duration: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 2 }),
+ ),
+ valid_before: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ hours: 1 }),
+ ),
+ ),
+ kind: TokenFamilyKind.Subscription,
+ validity_granularity: Duration.toTalerProtocolDuration(
+ Duration.fromSpec({ minutes: 1 }),
+ ),
+ });
+
+ const payDeadline = AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ minutes: 10 }),
+ ),
+ );
+
+ const subscriptionAmount = `${config.currency}:5` as AmountString;
+ const articleAmount = `${config.currency}:1` as AmountString;
+ const articleDiscountedAmount = `${config.currency}:0.5` as AmountString;
+
+ const createResp = succeedOrThrow(
+ await merchantClient.createOrder(tok.access_token, {
+ order: {
+ pay_deadline: payDeadline,
+ version: OrderVersion.V1,
+ summary: "Test Payment",
+ choices: [
+ {
+ amount: articleAmount,
+ description: "Buy an individual article",
+ },
+ {
+ amount: subscriptionAmount as AmountString,
+ description: "Article and discount",
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: slugDiscount1,
+ },
+ ],
+ },
+ {
+ amount: subscriptionAmount as AmountString,
+ description: "Article and subscription",
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: slugSubscription1,
+ },
+ ],
+ },
+ {
+ amount: articleDiscountedAmount,
+ inputs: [
+ {
+ type: OrderInputType.Token,
+ token_family_slug: slugDiscount1,
+ },
+ ],
+ outputs: [],
+ },
+ {
+ amount: articleAmount,
+ description: "Article via subscription",
+ inputs: [
+ {
+ type: OrderInputType.Token,
+ token_family_slug: slugSubscription1,
+ },
+ ],
+ outputs: [
+ {
+ type: OrderOutputType.Token,
+ token_family_slug: slugSubscription1,
+ },
+ ],
+ },
+ ],
+ },
+ }),
+ );
+
+ const st = succeedOrThrow(
+ await merchantClient.getOrderDetails(tok.access_token, createResp.order_id),
+ );
+
+ if (st.order_status === "unpaid") {
+ console.log(st.taler_pay_uri);
+ }
+}
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -961,8 +961,6 @@ async function processDownloadProposal(
return TaskRunResult.finished();
}
- const transactionId = ctx.transactionId;
-
const orderClaimUrl = new URL(
`orders/${proposal.orderId}/claim`,
proposal.merchantBaseUrl,