View Issue Details

IDProjectCategoryView StatusLast Update
0012099Talerwallet (WebExtension)public2026-11-11 01:58
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 
Summary0012099: WebExtension: an expired or already used peer link says only "This peer payment is no longer available." -- wallet-core's purseExpired is ignored (patch attached)
DescriptionOpening a peer payment link that can no longer be used -- a taler://pay-push whose money expired or was taken back, or a taler://pay-pull request that expired or was already paid -- the web wallet says only "This peer payment is no longer available." for every case. wallet-core tells the UI which case it is: both purse-gone errors (WALLET_PEER_PUSH_CREDIT_PURSE_GONE, WALLET_PEER_PULL_DEBIT_PURSE_GONE) carry purseExpired: true/false (see 0011654). For an expired request the screen additionally offers "Withdraw funds", which cannot help. Android shows wallet-core's reason ("this payment expired", "this invoice expired"; untranslated); iOS shows the same text under "Internal core error" (0011934).

Screenshots (demo backend, links created with a 2-minute expiry and opened afterwards):
- before-web-1-push-expired.png, before-web-2-pull-expired.png: web, "This peer payment is no longer available." (+ "Withdraw funds" for the request)
- before-android-1-push-expired.png, before-android-3-pull-expired.png (and -2 behind the sheet): Android "this payment expired" / "this invoice expired"
- before-ios-1-push-expired.png: iOS "Internal core error" / "this payment expired"
- after-web-1-push-expired.png, after-web-2-pull-expired.png: with the patch, "This payment link has expired." / "This payment request has expired.", no "Withdraw funds"

Code (taler-typescript-core origin/master bf2e571a2, packages/wallet-webui/src/):
- ui/error.ts:73-75: both PURSE_GONE codes -> one sentence, purseExpired unused
- screens/PeerReceiveScreen.tsx:67-74: "Withdraw funds" for every error of a pay request
wallet-core: pay-peer-push-credit.ts:759-782 and 1541-1548 ({ purseExpired }, "this payment expired" / "the sender aborted this payment"), pay-peer-pull-debit.ts:2010-2040 ({ purseExpired }, "this invoice expired" / "this invoice was already paid or withdrawn by the payee").

Patch (attached, web-peer-link-gone-reason.patch): errorMessage() uses purseExpired to say "This payment link has expired." / "This payment request has expired." or, when not expired, "This payment was cancelled by the sender or has already been received." / "This payment request was already paid or withdrawn."; without the flag the old sentence stays. PeerReceiveScreen hides "Withdraw funds" for a request that is gone. Four new source strings.

Verification: Chrome extension build; after-web-1/2 for the expired cases (the not-expired variants could not be produced with the test backend). pnpm typecheck: no errors in the touched files (29 pre-existing elsewhere).

Assisted by LLM
Steps To Reproduce1. Let another wallet create a pay-push and a pay-pull link with a short expiry (e.g. 2 minutes) and wait until they expire.
2. Open them in the web wallet: both say only "This peer payment is no longer available."; the request also offers "Withdraw funds".
TagsNo tags attached.
Attached Files
before-web-1-push-expired.png (52,024 bytes)   
before-web-1-push-expired.png (52,024 bytes)   
before-web-2-pull-expired.png (58,573 bytes)   
before-web-2-pull-expired.png (58,573 bytes)   
before-ios-1-push-expired.png (118,127 bytes)   
before-ios-1-push-expired.png (118,127 bytes)   
after-web-1-push-expired.png (51,390 bytes)   
after-web-1-push-expired.png (51,390 bytes)   
after-web-2-pull-expired.png (52,050 bytes)   
after-web-2-pull-expired.png (52,050 bytes)   
web-peer-link-gone-reason.patch (3,064 bytes)   
diff --git a/packages/wallet-webui/src/screens/PeerReceiveScreen.tsx b/packages/wallet-webui/src/screens/PeerReceiveScreen.tsx
index bb4cde691..d1638ea82 100644
--- a/packages/wallet-webui/src/screens/PeerReceiveScreen.tsx
+++ b/packages/wallet-webui/src/screens/PeerReceiveScreen.tsx
@@ -1,3 +1,4 @@
+import { TalerErrorCode } from "@gnu-taler/taler-util";
 import { Button } from "../ui/Button.js";
 import { Card } from "../ui/Card.js";
 import { ErrorCard } from "../ui/ErrorCard.js";
@@ -25,6 +26,10 @@ export function PeerReceiveScreen(props: {
   onWithdraw: () => void;
 }) {
   const receive = props.kind === "receive";
+  // more money does not help with a request that expired or was paid
+  const requestGone =
+    props.error?.detail?.code ===
+    TalerErrorCode.WALLET_PEER_PULL_DEBIT_PURSE_GONE;
   return (
     <div class="mx-auto w-full max-w-2xl space-y-6">
       <div class="flex items-center gap-4">
@@ -64,7 +69,7 @@ export function PeerReceiveScreen(props: {
               onClick={props.onRetryObservation}
             >{i18n.str`Retry updates`}</Button>
           )}
-          {!receive && (
+          {!receive && !requestGone && (
             <div class="mt-4">
               <Button
                 tone="secondary"
diff --git a/packages/wallet-webui/src/ui/error.ts b/packages/wallet-webui/src/ui/error.ts
index 1450c2531..90cc8edab 100644
--- a/packages/wallet-webui/src/ui/error.ts
+++ b/packages/wallet-webui/src/ui/error.ts
@@ -71,8 +71,21 @@ function errorMessage(detail: TalerErrorDetail, fallback: string): string {
     case TalerErrorCode.WALLET_NO_SUITABLE_EXCHANGE:
       return /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`No compatible exchange is available for this operation.`;
     case TalerErrorCode.WALLET_PEER_PULL_DEBIT_PURSE_GONE:
-    case TalerErrorCode.WALLET_PEER_PUSH_CREDIT_PURSE_GONE:
+    case TalerErrorCode.WALLET_PEER_PUSH_CREDIT_PURSE_GONE: {
+      // wallet-core says whether the link expired or was used/withdrawn
+      const purseExpired = (detail as { purseExpired?: boolean }).purseExpired;
+      const push =
+        detail.code === TalerErrorCode.WALLET_PEER_PUSH_CREDIT_PURSE_GONE;
+      if (purseExpired === true)
+        return push
+          ? i18n.str`This payment link has expired.`
+          : i18n.str`This payment request has expired.`;
+      if (purseExpired === false)
+        return push
+          ? /* Translators: The money of a peer payment link is gone because the sender took it back or another wallet received it. */ i18n.str`This payment was cancelled by the sender or has already been received.`
+          : i18n.str`This payment request was already paid or withdrawn.`;
       return /* Translators: Peer payments transfer digital cash between people using links. A shared link can offer money to receive or request money to be paid. */ i18n.str`This peer payment is no longer available.`;
+    }
     default:
       return fallback;
   }

Required notifications

Address or linkRemarkAdded byNotifiedActions

Relationships

related to 0011654 closedFlorian Dold wallet-core should use local time to distinguish purse gone reasons 

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-11 01:58bot-cgNew Issue
2026-11-11 01:58bot-cgRelationship addedrelated to 0011654
2026-11-11 01:58bot-cgFile Added: before-web-1-push-expired.png
2026-11-11 01:58bot-cgFile Added: before-web-2-pull-expired.png
2026-11-11 01:58bot-cgFile Added: before-android-1-push-expired.png
2026-11-11 01:58bot-cgFile Added: before-android-2-push-expired-behind.png
2026-11-11 01:58bot-cgFile Added: before-android-3-pull-expired.png
2026-11-11 01:58bot-cgFile Added: before-ios-1-push-expired.png
2026-11-11 01:58bot-cgFile Added: after-web-1-push-expired.png
2026-11-11 01:58bot-cgFile Added: after-web-2-pull-expired.png
2026-11-11 01:58bot-cgFile Added: web-peer-link-gone-reason.patch