View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0012079 | Taler | wallet (iOS App) | public | 2026-11-11 01:36 | 2026-11-11 01:37 |
| Reporter | bot-cg [agent] | Assigned To | Resolution deadline | ||
| Implementer | Reviewer | ||||
| Release signed by | Deployed by | ||||
| Verifier | Communicator | CM_7 | |||
| Data protection authority notification required | No | ||||
| CSIRT notification required | No | ||||
| Estimated effort (hours) | Actual effort (hours) | 0 | |||
| Fast-track | No | ||||
| Work package | none | ||||
| Priority | normal | Severity | minor | Reproducibility | have not tried |
| Status | new | Resolution | open | ||
| Summary | 0012079: iOS: an order already claimed by another wallet (or deleted by the merchant) shows only "Abandoned" -- the reason is never displayed (patch attached) | ||||
| Description | Opening the pay link of an order that another wallet has already claimed (a QR code scanned with a second phone, a link opened twice), or of an order the merchant has deleted or that never existed, ends in a failed claim. wallet-core keeps it as a FailedClaim transaction (state failed/claim-proposal, actions [delete]) with failReason = WALLET_ORDER_ALREADY_CLAIMED (7014) or WALLET_MERCHANT_ORDER_NOT_FOUND (7049) (pay-merchant.ts:1576-1606, failProposalClaimPermanently :1236-1266). The WebExtension explains it; Android and iOS only say that the transaction failed. What the user sees (origin/master builds of 2026-11-11; the order is claimed first by a taler-wallet-cli wallet, resp. deleted at the merchant before the link is opened): - WebExtension: "Payment could not continue" -- "This order was already claimed by another wallet." -- "Return to the merchant and request a new order." (before-web-1.png); deleted order: "The merchant order no longer exists or has expired." -- "Return to the merchant and create a new order." (before-web-2.png). - Android: "This transaction has failed or been abandoned", date, "Total UNKNOWN 0.00", "Paid UNKNOWN 0.00", Delete (before-android-1.png, before-android-2.png). The placeholder amounts come from wallet-core's contract-less transaction. - iOS: "Payment", date, "Abandoned", Done -- nothing else (before-ios-1.png, before-ios-2.png). On iOS (origin/master 674b7915): - TalerWallet1/Views/Transactions/PaymentTransactionView.swift:150-152, 172, 242: the view renders the dialog branch or, if !isClaiming, the finished payment. A failed claim keeps minor state claim-proposal, so neither branch runs and nothing is shown. - TalerWallet1/Views/Transactions/PaidTransactionDetailV.swift:96-101 would print failReason.hint, but is unreachable here. - TalerWallet1/Views/Transactions/TransactionSummaryList.swift:170-187: only a *deleted* transition raises an error. Reference: web routes/App.tsx:4416-4432, routes/transaction-model.ts:383-394, ui/error.ts:24-29, 83-88. Patch (ios-claimed-or-deleted-order-no-reason.patch): PaymentTransactionView gets a branch for a failed claim (ClaimFailureView): 7014 -> "This order was already claimed by another wallet." / "Return to the merchant and request a new order."; 7049 -> "The merchant order no longer exists or has expired." / "Return to the merchant and create a new order."; otherwise failReason.hint. Four new English entries in Localizable.xcstrings. Verified: GNU_Taler Release builds for the simulator; after-ios-1.png and after-ios-2.png are the same two scenarios with the patch. Related: 0012068 (generic: transaction error never shown), 0012078 (Android counterpart). Assisted by LLM | ||||
| Steps To Reproduce | 1. Create an order (any merchant) and open its taler://pay/ link in one wallet (or claim it with taler-wallet-cli), without paying. 2. Open the same link in the wallet under test: failed/Abandoned without a reason. 3. Variant: delete the order at the merchant (or change the order id in the link) and open it. (Automated: U=$(wc-stack pay-uri --amount KUDOS:1); wc-stack claim-elsewhere "$U"; wc-android uri "$U".) | ||||
| Tags | No tags attached. | ||||
| Attached Files | ios-claimed-or-deleted-order-no-reason.patch (3,505 bytes)
diff --git a/TalerWallet1/Resources/Localizable.xcstrings b/TalerWallet1/Resources/Localizable.xcstrings
index e283ca45..1aba8779 100755
--- a/TalerWallet1/Resources/Localizable.xcstrings
+++ b/TalerWallet1/Resources/Localizable.xcstrings
@@ -16830,6 +16830,12 @@
}
}
},
+ "Return to the merchant and create a new order." : {
+ "comment" : "below 'The merchant order no longer exists or has expired.'"
+ },
+ "Return to the merchant and request a new order." : {
+ "comment" : "below 'This order was already claimed by another wallet.'"
+ },
"Right now" : {
"localizations" : {
"de" : {
@@ -19138,6 +19144,9 @@
}
}
},
+ "The merchant order no longer exists or has expired." : {
+ "comment" : "payment: the merchant does not know the order (anymore)"
+ },
"The payment service is waiting for your wire-transfer." : {
"localizations" : {
"de" : {
@@ -19986,6 +19995,9 @@
}
}
},
+ "This order was already claimed by another wallet." : {
+ "comment" : "payment: the merchant order was claimed by another wallet first"
+ },
"This payment service has not published any terms of service." : {
"comment" : "A message displayed when a payment service has not published any terms of service.",
"isCommentAutoGenerated" : true,
diff --git a/TalerWallet1/Views/Transactions/PaymentTransactionView.swift b/TalerWallet1/Views/Transactions/PaymentTransactionView.swift
index 1f23d124..c1520836 100755
--- a/TalerWallet1/Views/Transactions/PaymentTransactionView.swift
+++ b/TalerWallet1/Views/Transactions/PaymentTransactionView.swift
@@ -90,6 +90,34 @@ struct MerchantHeader: View {
}
}
// MARK: -
+/// Why the wallet could not claim an order, e.g. because another wallet already claimed it
+struct ClaimFailureView: View {
+ let failReason: TalerErrorDetail?
+
+ var body: some View {
+ let (message, hint): (String?, String?) = switch failReason?.code {
+ case 7014: // WALLET_ORDER_ALREADY_CLAIMED
+ (String(localized: "This order was already claimed by another wallet."),
+ String(localized: "Return to the merchant and request a new order."))
+ case 7049: // WALLET_MERCHANT_ORDER_NOT_FOUND
+ (String(localized: "The merchant order no longer exists or has expired."),
+ String(localized: "Return to the merchant and create a new order."))
+ default:
+ (failReason?.hint, nil)
+ }
+ if let message {
+ Section {
+ Text(message)
+ .talerFont(.title3)
+ if let hint {
+ Text(hint)
+ .talerFont(.body)
+ }
+ }
+ }
+ }
+}
+// MARK: -
struct PaymentTransactionView: View {
private let symLog = SymLogV(0)
let stack: CallStack
@@ -239,6 +267,8 @@ struct PaymentTransactionView: View {
// TODO: purge and reload
}
}
+ } else if common.isFailed { // the claim failed permanently: no contract, but a reason
+ ClaimFailureView(failReason: common.failReason)
} else if isNotClaiming { // show finished payment
PaidTransactionDetailV(stack: stack.push(),
paymentTx: paymentTransaction) // TODO: details.info.merchant.name
| ||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-11 01:36 | bot-cg | New Issue | |
| 2026-11-11 01:36 | bot-cg | Relationship added | related to 0012078 |
| 2026-11-11 01:37 | bot-cg | File Added: before-android-1.png | |
| 2026-11-11 01:37 | bot-cg | File Added: before-ios-1.png | |
| 2026-11-11 01:37 | bot-cg | File Added: before-web-1.png | |
| 2026-11-11 01:37 | bot-cg | File Added: before-android-2.png | |
| 2026-11-11 01:37 | bot-cg | File Added: before-ios-2.png | |
| 2026-11-11 01:37 | bot-cg | File Added: before-web-2.png | |
| 2026-11-11 01:37 | bot-cg | File Added: after-ios-1.png | |
| 2026-11-11 01:37 | bot-cg | File Added: after-ios-2.png | |
| 2026-11-11 01:37 | bot-cg | File Added: ios-claimed-or-deleted-order-no-reason.patch |