View Issue Details

IDProjectCategoryView StatusLast Update
0012095Talerwallet (WebExtension)public2026-11-11 01:50
Reporterbot-cg [agent] Assigned ToResolution deadline
ImplementerReviewer 
Release signed byDeployed by 
VerifierCommunicatorCM_7  
Data protection authority notification requiredNo  
CSIRT notification requiredNo  
Estimated effort (hours)Actual effort (hours)0 
Fast-trackNo
Work packagenone
PrioritynormalSeverityminorReproducibilityhave not tried
Status newResolutionopen 
Summary0012095: WebExtension: no "time to pay" indication when an order expires within minutes; iOS counts down (patch attached)
DescriptionWhen an order is about to expire (contract pay_deadline less than five minutes away -- typical for point-of-sale and vending orders), iOS shows "Time to pay: m:ss" counting down above the Pay button and withdraws the button when it runs out. Android and the WebExtension show nothing; the screen simply changes under the user when wallet-core expires the proposal.

What the user sees (order created with pay_deadline = now + 4 minutes; origin/master builds of 2026-11-11):
- iOS: "Time to pay:" with the remaining minutes and seconds next to "Pay KUDOS 1.00 now" (before-ios-1.png).
- Android: the normal prompt, no time shown (before-android-1.png).
- WebExtension: the normal "Review payment", no time shown (before-web-1.png).

References: iOS Views/Sheets/Payment/PaymentScan.swift:255-316 (PaySafeArea, TimeView "Time to pay:"), Model/Model+Payment.swift:286-306 (timeToPay from payDeadline), Views/Transactions/TransactionSummaryList.swift:359-372.

WebExtension (origin/master bf2e571a2): packages/wallet-webui/src has no reference to pay_deadline; screens/PaymentScreen.tsx:324-384 renders the action row without any deadline.

Patch (web-time-to-pay.patch): paymentReviewView passes the contract's pay_deadline (ms) as payDeadline; PaymentOptions shows a small TimeToPay element ("Time to pay: m:ss", role=timer) above the action buttons once the deadline is less than five minutes away, updated every second (a single timeout until the five-minute mark before that). One new i18n string.
Verified: wallet-webui builds (esbuild), and tsc --build reports no error in the changed files (the 29 errors it reports are identical on unmodified origin/master); after-web-1.png shows "Time to pay: 3:59" for the same order, ticking (3:52 a few seconds later).

Related: 0008378, 0012093 (Android counterpart).

Assisted by LLM
Steps To Reproduce1. Create an order whose pay_deadline is a few minutes from now (POST /private/orders with "pay_deadline": {"t_s": <now+240>}).
2. Open its taler://pay/ link: iOS shows a countdown, Android and the WebExtension do not.
(Automated: wc-stack pay-uri --amount KUDOS:1 --pay-deadline 240.)
TagsNo tags attached.
Attached Files
before-ios-1.png (264,738 bytes)
before-android-1.png (64,104 bytes)   
before-android-1.png (64,104 bytes)   
before-web-1.png (78,811 bytes)   
before-web-1.png (78,811 bytes)   
after-web-1.png (83,184 bytes)   
after-web-1.png (83,184 bytes)   
web-time-to-pay.patch (4,488 bytes)   
diff --git a/packages/wallet-webui/src/routes/App.tsx b/packages/wallet-webui/src/routes/App.tsx
index e649abaf4..57ca4883a 100644
--- a/packages/wallet-webui/src/routes/App.tsx
+++ b/packages/wallet-webui/src/routes/App.tsx
@@ -4889,6 +4889,7 @@ function PaymentDialog(props: {
       summary={review.summary}
       order={review.order}
       minimumAge={review.minimumAge}
+      payDeadline={review.payDeadline}
       choices={review.choices}
       selectedChoice={activeChoice}
       error={error}
diff --git a/packages/wallet-webui/src/routes/payment-model.ts b/packages/wallet-webui/src/routes/payment-model.ts
index 8629cf90f..09b8db19d 100644
--- a/packages/wallet-webui/src/routes/payment-model.ts
+++ b/packages/wallet-webui/src/routes/payment-model.ts
@@ -31,6 +31,8 @@ export interface PaymentReviewView {
   summary: string;
   order: OrderSummaryView;
   minimumAge?: number;
+  /** Payment deadline in milliseconds since the epoch, if any. */
+  payDeadline?: number;
   fulfillmentMessage?: string;
   fulfillmentUrl?: string;
   choices: PaymentChoiceView[];
@@ -270,6 +272,9 @@ export function paymentReviewView(
       Amounts.parse(choices[0]?.amountRaw ?? "")?.currency,
     ),
     ...(terms.minimum_age ? { minimumAge: terms.minimum_age } : {}),
+    ...(typeof terms.pay_deadline?.t_s === "number"
+      ? { payDeadline: terms.pay_deadline.t_s * 1000 }
+      : {}),
     ...(terms.fulfillment_message
       ? { fulfillmentMessage: terms.fulfillment_message }
       : {}),
diff --git a/packages/wallet-webui/src/screens/PaymentScreen.tsx b/packages/wallet-webui/src/screens/PaymentScreen.tsx
index da04c0f71..baaf4a407 100644
--- a/packages/wallet-webui/src/screens/PaymentScreen.tsx
+++ b/packages/wallet-webui/src/screens/PaymentScreen.tsx
@@ -7,7 +7,7 @@ import type { ErrorPresentation } from "../ui/error.js";
 import type { PaymentInsufficientBalanceDetails } from "@gnu-taler/taler-util";
 import { ConfirmationDialog } from "../ui/ConfirmationDialog.js";
 import { QrFrame } from "../ui/QrFrame.js";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
 
 export interface PaymentChoiceView {
   index: number;
@@ -163,8 +163,37 @@ export type PaymentScreenState =
   | "done"
   | "error";
 
+/** Payments due within this time show a countdown, as in the iOS wallet. */
+const TIME_TO_PAY_WARNING_MS = 5 * 60 * 1000;
+
+function TimeToPay(props: { deadline: number }) {
+  const [now, setNow] = useState(() => Date.now());
+  const remaining = props.deadline - now;
+  const visible = remaining <= TIME_TO_PAY_WARNING_MS;
+  useEffect(() => {
+    if (remaining <= 0) return;
+    const timer = window.setTimeout(
+      () => setNow(Date.now()),
+      visible ? 1000 : remaining - TIME_TO_PAY_WARNING_MS,
+    );
+    return () => window.clearTimeout(timer);
+  }, [now, remaining, visible]);
+  if (!visible) return null;
+  const seconds = Math.max(0, Math.ceil(remaining / 1000));
+  const time = `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
+  return (
+    <p role="timer" class="text-right text-sm font-medium text-onWarningContainer">
+      {
+        // Translators: Placeholder is the remaining time (minutes:seconds) before the merchant's payment deadline.
+        i18n.str`Time to pay: ${time}`
+      }
+    </p>
+  );
+}
+
 export function PaymentOptions(props: {
   state: "ready" | "paying";
+  payDeadline?: number;
   choices?: PaymentChoiceView[];
   selectedChoice?: number;
   useDonau?: boolean;
@@ -321,6 +350,9 @@ export function PaymentOptions(props: {
             : i18n.str`Preparing the payment for another wallet…`}
         </p>
       )}
+      {props.payDeadline !== undefined && (
+        <TimeToPay deadline={props.payDeadline} />
+      )}
       <div class="flex flex-wrap justify-end gap-3 border-t border-outlineVariant pt-5">
         {props.onUnclaim && (
           <Button
@@ -442,6 +474,7 @@ export function PaymentScreen(props: {
   summary?: string;
   order?: OrderSummaryView;
   minimumAge?: number;
+  payDeadline?: number;
   choices?: PaymentChoiceView[];
   selectedChoice?: number;
   error?: ErrorPresentation;
@@ -591,6 +624,7 @@ export function PaymentScreen(props: {
           />
           <PaymentOptions
             state={props.state}
+            payDeadline={props.payDeadline}
             choices={props.choices}
             selectedChoice={props.selectedChoice}
             useDonau={props.useDonau}
web-time-to-pay.patch (4,488 bytes)   

Required notifications

Address or linkRemarkAdded byNotifiedActions

Relationships

related to 0012093 new Android: no "time to pay" indication when an order expires within minutes; iOS counts down (patch attached) 

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-11 01:50bot-cgNew Issue
2026-11-11 01:50bot-cgRelationship addedrelated to 0012093
2026-11-11 01:50bot-cgFile Added: before-ios-1.png
2026-11-11 01:50bot-cgFile Added: before-android-1.png
2026-11-11 01:50bot-cgFile Added: before-web-1.png
2026-11-11 01:50bot-cgFile Added: after-web-1.png
2026-11-11 01:50bot-cgFile Added: web-time-to-pay.patch