From 1d1a2ccef4fd1c3cbbb7c91f682d956694aa6156 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:33:22 +0300 Subject: [PATCH] Fix #5186: share ReceiptStore across Purchase instances Display.getInAppPurchase() returns a fresh Purchase instance on every call on every port (iOS ZoozPurchase, Android ZoozPurchase, the JavaSE anonymous subclass), but receiptStore was a per-instance field. The native receipt path on iOS enters through the static Purchase.postReceipt(...) (invoked from StoreKit's paymentQueue:updatedTransactions:) which calls getInAppPurchase().postReceipt(r) on a brand-new instance whose receiptStore is null. As a result synchronizeReceipts() fell through to loadReceipts(), logged "No receipt store is currently registered" and the app's ReceiptStore.submitReceipt/fetchReceipts were never invoked for auto-submitted receipts -- only an explicit synchronizeReceipts call on the app's own configured instance worked. Make receiptStore static so the store configured via setReceiptStore is visible to the native auto-submit path regardless of which Purchase instance the call arrives on. There is logically one IAP store per app, so static is the correct scope. Access stays EDT-only by construction. Co-Authored-By: Claude Opus 4.8 (1M context) --- CodenameOne/src/com/codename1/payment/Purchase.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CodenameOne/src/com/codename1/payment/Purchase.java b/CodenameOne/src/com/codename1/payment/Purchase.java index f0589e5bc0..604648ca2c 100644 --- a/CodenameOne/src/com/codename1/payment/Purchase.java +++ b/CodenameOne/src/com/codename1/payment/Purchase.java @@ -71,7 +71,18 @@ public abstract class Purchase { /// race nor the field assignment need synchronization. private static final List> synchronizeReceiptsCallbacks = new ArrayList>(); - private ReceiptStore receiptStore; + /// Static (shared across all `Purchase` instances) because + /// `Display#getInAppPurchase()` returns a fresh `Purchase` on every + /// call on every port (iOS `ZoozPurchase`, Android `ZoozPurchase`, + /// the JavaSE anonymous subclass). A per-instance store would be + /// invisible to the native receipt path: StoreKit's + /// `paymentQueue:updatedTransactions:` enters via the static + /// `#postReceipt(...)` which calls `getInAppPurchase().postReceipt(r)` + /// on a brand-new instance whose store would be null, so the app's + /// `ReceiptStore` would never be invoked for auto-submitted receipts. + /// There is logically one IAP store per app, so a static field is the + /// correct scope. Access is EDT-only by construction. + private static ReceiptStore receiptStore; private List receipts; private Date receiptsRefreshTime;