commit 6fb290a4e8663c7fa72a869d01c51f1ef440bfc2
parent 8e5feef9ff2cd986f9f8e403a702a82001987030
Author: Florian Dold <dold@taler.net>
Date: Thu, 13 Aug 2026 12:17:37 +0200
taler-harness: add expiring orders playground
Diffstat:
3 files changed, 92 insertions(+), 1 deletion(-)
diff --git a/packages/taler-harness/README.md b/packages/taler-harness/README.md
@@ -4,6 +4,20 @@ This package implements the `taler-harness` CLI tool. It contains integration
tests for GNU Taler and GNU anastasis, as well as various helpers for managing
deployments of GNU Taler.
+## Quickly expiring payments
+
+Create a payment that expires after one minute with:
+
+```
+taler-harness playground expiring-orders \
+ --merchant-url "$MERCHANT_URL" \
+ --merchant-pw "$MERCHANT_PASSWORD"
+```
+
+Open the printed `taler://pay/` URI in a wallet and leave the payment
+confirmation open until it expires. The deadline can be changed with
+`--pay-delay`, for example `--pay-delay 30s`.
+
## Debugging
To get more actionable stack traces, enable source maps for node:
@@ -120,4 +134,3 @@ taler-harness run-integrationtests web-merchant-login
```
The default browser is "chrome" unless the env variable DEFAULT_BROWSER is set to either "firefox" or "chrome".
-
diff --git a/packages/taler-harness/src/index.ts b/packages/taler-harness/src/index.ts
@@ -115,6 +115,7 @@ import { lintExchangeDeployment, lintExchangeUrl } from "./lint.js";
import {
runPlaygroundAdvancedTokens1,
runPlaygroundBlog,
+ runPlaygroundExpiringOrders,
} from "./playground.js";
import {
MYTOPS_STAGE_BASE_URL,
@@ -2054,6 +2055,23 @@ export const playgroundCli = talerHarnessCli.subcommand(
);
playgroundCli
+ .subcommand("exp", "expiring-orders", {
+ help: "Create a payment that expires quickly for wallet UI testing.",
+ })
+ .requiredOption("merchantUrl", ["--merchant-url"], clk.STRING)
+ .requiredOption("merchantPw", ["--merchant-pw"], clk.STRING)
+ .maybeOption("payDelay", ["--pay-delay"], clk.STRING, {
+ help: "Time until the order expires (default: 1m).",
+ })
+ .action(async (args) => {
+ await runPlaygroundExpiringOrders({
+ merchantPw: args.exp.merchantPw,
+ merchantUrl: args.exp.merchantUrl,
+ payDelay: args.exp.payDelay,
+ });
+ });
+
+playgroundCli
.subcommand("exp", "blog", {})
.requiredOption("merchantUrl", ["--merchant-url"], clk.STRING)
.requiredOption("merchantPw", ["--merchant-pw"], clk.STRING)
diff --git a/packages/taler-harness/src/playground.ts b/packages/taler-harness/src/playground.ts
@@ -38,6 +38,66 @@ export interface PlaygroundBlogArgs {
payDelay?: string;
}
+export interface PlaygroundExpiringOrdersArgs {
+ merchantUrl: string;
+ merchantPw: string;
+ payDelay?: string;
+}
+
+/**
+ * Create a plain payment order that expires soon after it is opened.
+ *
+ * The short deadline makes it possible to leave the wallet on the payment
+ * confirmation screen until the order expires and then inspect the expired
+ * transaction in the wallet's history.
+ */
+export async function runPlaygroundExpiringOrders(
+ args: PlaygroundExpiringOrdersArgs,
+): Promise<void> {
+ const payDelay = args.payDelay ?? "1m";
+ const payDuration = Duration.fromPrettyString(payDelay);
+ if (payDuration.d_ms === "forever" || payDuration.d_ms <= 0) {
+ throw Error("pay delay must be a positive, finite duration");
+ }
+
+ 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 payDeadline = AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(AbsoluteTime.now(), payDuration),
+ );
+ const createResp = succeedOrThrow(
+ await merchantClient.createOrder(tok.access_token, {
+ order: {
+ amount: `${config.currency}:1` as AmountString,
+ fulfillment_message: "The quickly expiring test payment succeeded.",
+ pay_deadline: payDeadline,
+ summary: `Test payment expiring in ${payDelay}`,
+ },
+ }),
+ );
+ const st = succeedOrThrow(
+ await merchantClient.getOrderDetails(tok.access_token, createResp.order_id),
+ );
+
+ if (st.order_status !== "unpaid") {
+ throw Error(`new order unexpectedly has status ${st.order_status}`);
+ }
+ console.log(st.taler_pay_uri);
+}
+
export async function runPlaygroundBlog(
args: PlaygroundBlogArgs,
): Promise<void> {