View Issue Details

IDProjectCategoryView StatusLast Update
0012084Talerwallet (iOS App)public2026-11-11 01:40
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 
Summary0012084: iOS: the payment screen never shows product images, which Android and the WebExtension display (patch attached)
DescriptionThe iOS payment screen lists the order's products (name, description, quantity, price) but never shows the product images that merchants put into the contract (ProductSold.image, a data: URL). Android and the WebExtension show them.

What the user sees (origin/master builds of 2026-11-11; an order with two products, each with a 128x128 PNG image):
- iOS: "Coffee / Large cappuccino / 2 / 2.00" and "Croissant / Butter croissant / 1 / 1.00", text only (before-ios-1.png).
- Android: each product with its thumbnail (tap enlarges it) (before-android-1.png).
- WebExtension: each product with a round thumbnail (before-web-1.png).

Code (origin/master 674b7915):
- TalerWallet1/Views/Sheets/Payment/ChoicesView.swift:178-220: ProductsView never reads product.image.
- TalerWallet1/Backend/WalletBackendRequest.swift:154: image is decoded ("product image 128x128 encoded").
- TalerCommon/Helper/Image+fallback.swift:41: Image(imageBase64:) already decodes product.image safely (its comment names product.image); PaymentTransactionView.swift:52-58,79-83 uses it for the merchant logo.
References: Android PromptPaymentComposable.kt:354-409; web routes/order-model.ts:10-14,66 and ui/OrderSummary.tsx:17-35.

Patch (ios-product-images.patch): a small ProductImageView (decodes once in .task with Image(imageBase64:), 44x44, rounded, hidden from VoiceOver) in front of each product that has an image. No new strings. (The name ProductImage is taken by an unused model struct in ThreeAmountsSection.swift:73.)
Verified: GNU_Taler Release builds for the simulator; after-ios-1.png is the same order with the patch.

Assisted by LLM
Steps To Reproduce1. Create an order with products[] that carry "image": "data:image/png;base64,...".
2. Open its taler://pay/ link on iOS: no images. Same order on Android or in the WebExtension: thumbnails.
(Automated: wc-stack pay-uri --order-json <order with products and images>.)
TagsNo tags attached.
Attached Files
before-ios-1.png (224,335 bytes)   
before-ios-1.png (224,335 bytes)   
before-android-1.png (85,959 bytes)   
before-android-1.png (85,959 bytes)   
before-web-1.png (107,293 bytes)   
before-web-1.png (107,293 bytes)   
after-ios-1.png (229,123 bytes)   
after-ios-1.png (229,123 bytes)   
ios-product-images.patch (1,692 bytes)   
diff --git a/TalerWallet1/Views/Sheets/Payment/ChoicesView.swift b/TalerWallet1/Views/Sheets/Payment/ChoicesView.swift
index dbeb0dca..680ee72c 100644
--- a/TalerWallet1/Views/Sheets/Payment/ChoicesView.swift
+++ b/TalerWallet1/Views/Sheets/Payment/ChoicesView.swift
@@ -175,6 +175,31 @@ struct ChoicesView: View, Sendable {
     }
 }
 // MARK: -
+/// The merchant-supplied product image (a data URL), decoded once
+struct ProductImageView: View {
+    let imageBase64: String
+
+    @State private var image: Image? = nil
+
+    var body: some View {
+        Group {
+            if let image {
+                image
+                    .resizable()
+                    .aspectRatio(contentMode: .fit)
+                    .clipShape(RoundedRectangle(cornerRadius: 6))
+            } else {
+                Color.clear     // keeps the view (and its .task) alive until decoded
+            }
+        }
+        .frame(width: 44, height: 44)
+        .accessibilityHidden(true)
+        .task(id: imageBase64) {
+            image = Image(imageBase64: imageBase64)
+        }
+    }
+}
+// MARK: -
 struct ProductsView: View, Sendable {
     let stack: CallStack
     let scope: ScopeInfo?
@@ -188,6 +213,9 @@ struct ProductsView: View, Sendable {
             Section {
                 ForEach(products, id: \.self) { product in
                     HStack {
+                        if let imageBase64 = product.image {
+                            ProductImageView(imageBase64: imageBase64)
+                        }
                         VStack(alignment: .leading) {
                             if let name = product.productName {
                                 Text(name)
ios-product-images.patch (1,692 bytes)   

Required notifications

Address or linkRemarkAdded byNotifiedActions

Activities

Issue History

Date ModifiedUsernameFieldChange
2026-11-11 01:40bot-cgNew Issue
2026-11-11 01:40bot-cgFile Added: before-ios-1.png
2026-11-11 01:40bot-cgFile Added: before-android-1.png
2026-11-11 01:40bot-cgFile Added: before-web-1.png
2026-11-11 01:40bot-cgFile Added: after-ios-1.png
2026-11-11 01:40bot-cgFile Added: ios-product-images.patch