Skip to content

Fix #5186: share ReceiptStore across Purchase instances#5188

Merged
shai-almog merged 1 commit into
masterfrom
fix-5186-receiptstore-lost-per-instance
Jun 6, 2026
Merged

Fix #5186: share ReceiptStore across Purchase instances#5188
shai-almog merged 1 commit into
masterfrom
fix-5186-receiptstore-lost-per-instance

Conversation

@shai-almog

@shai-almog shai-almog commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Problem

Display.getInAppPurchase() returns a fresh Purchase instance on every call on every port:

  • iOS — IOSImplementation.getInAppPurchase()new ZoozPurchase(...)
  • Android — AndroidImplementation.getInAppPurchase()ZoozPurchase.class.newInstance()
  • JavaSE — JavaSEPort.getInAppPurchase()new Purchase() { ... }

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.

So 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 — which is exactly the behavior reported in #5186 (receipts only submitted when the developer called synchronizeReceiptsSync(...) manually).

Fix

Make receiptStore static so the store installed via setReceiptStore(...) is visible to the native auto-submit path regardless of which Purchase instance the callback arrives on. There is logically one IAP store per app, so static is the correct scope. Access remains EDT-only by construction, consistent with the existing syncInProgress / loadInProgress / synchronizeReceiptsCallbacks static state.

Scope

This PR addresses only the confirmed, source-visible bug: the native auto-submit path never reaching the configured ReceiptStore. The separate "app crashes right after a successful purchase" symptom in #5186 is not addressed here and its root cause is still undetermined — it needs the device crash log to diagnose.

🤖 Generated with Claude Code

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) <noreply@anthropic.com>
@shai-almog shai-almog linked an issue Jun 6, 2026 that may be closed by this pull request
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 124 screenshots: 124 matched.

Native Android coverage

  • 📊 Line coverage: 13.20% (7894/59821 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.69% (39114/365744), branch 4.56% (1570/34434), complexity 5.60% (1849/33033), method 9.81% (1517/15456), class 16.01% (347/2167)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 13.20% (7894/59821 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 10.69% (39114/365744), branch 4.56% (1570/34434), complexity 5.60% (1849/33033), method 9.81% (1517/15456), class 16.01% (347/2167)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 998.000 ms
Base64 CN1 encode 373.000 ms
Base64 encode ratio (CN1/native) 0.374x (62.6% faster)
Base64 native decode 968.000 ms
Base64 CN1 decode 260.000 ms
Base64 decode ratio (CN1/native) 0.269x (73.1% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@shai-almog

shai-almog commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 124 screenshots: 124 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 149 seconds

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 697.000 ms
Base64 CN1 encode 1198.000 ms
Base64 encode ratio (CN1/native) 1.719x (71.9% slower)
Base64 native decode 575.000 ms
Base64 CN1 decode 913.000 ms
Base64 decode ratio (CN1/native) 1.588x (58.8% slower)
Base64 SIMD encode 437.000 ms
Base64 encode ratio (SIMD/native) 0.627x (37.3% faster)
Base64 encode ratio (SIMD/CN1) 0.365x (63.5% faster)
Base64 SIMD decode 422.000 ms
Base64 decode ratio (SIMD/native) 0.734x (26.6% faster)
Base64 decode ratio (SIMD/CN1) 0.462x (53.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 65.000 ms
Image createMask (SIMD on) 12.000 ms
Image createMask ratio (SIMD on/off) 0.185x (81.5% faster)
Image applyMask (SIMD off) 139.000 ms
Image applyMask (SIMD on) 82.000 ms
Image applyMask ratio (SIMD on/off) 0.590x (41.0% faster)
Image modifyAlpha (SIMD off) 194.000 ms
Image modifyAlpha (SIMD on) 123.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.634x (36.6% faster)
Image modifyAlpha removeColor (SIMD off) 189.000 ms
Image modifyAlpha removeColor (SIMD on) 95.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.503x (49.7% faster)
Image PNG encode (SIMD off) 1077.000 ms
Image PNG encode (SIMD on) 949.000 ms
Image PNG encode ratio (SIMD on/off) 0.881x (11.9% faster)
Image JPEG encode 693.000 ms

@shai-almog

shai-almog commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 124 screenshots: 124 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 328 seconds

Build and Run Timing

Metric Duration
Simulator Boot 85000 ms
Simulator Boot (Run) 1000 ms
App Install 16000 ms
App Launch 7000 ms
Test Execution 266000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 919.000 ms
Base64 CN1 encode 1796.000 ms
Base64 encode ratio (CN1/native) 1.954x (95.4% slower)
Base64 native decode 322.000 ms
Base64 CN1 decode 1118.000 ms
Base64 decode ratio (CN1/native) 3.472x (247.2% slower)
Base64 SIMD encode 418.000 ms
Base64 encode ratio (SIMD/native) 0.455x (54.5% faster)
Base64 encode ratio (SIMD/CN1) 0.233x (76.7% faster)
Base64 SIMD decode 526.000 ms
Base64 decode ratio (SIMD/native) 1.634x (63.4% slower)
Base64 decode ratio (SIMD/CN1) 0.470x (53.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 82.000 ms
Image createMask (SIMD on) 13.000 ms
Image createMask ratio (SIMD on/off) 0.159x (84.1% faster)
Image applyMask (SIMD off) 162.000 ms
Image applyMask (SIMD on) 99.000 ms
Image applyMask ratio (SIMD on/off) 0.611x (38.9% faster)
Image modifyAlpha (SIMD off) 173.000 ms
Image modifyAlpha (SIMD on) 80.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.462x (53.8% faster)
Image modifyAlpha removeColor (SIMD off) 193.000 ms
Image modifyAlpha removeColor (SIMD on) 73.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.378x (62.2% faster)
Image PNG encode (SIMD off) 1747.000 ms
Image PNG encode (SIMD on) 1181.000 ms
Image PNG encode ratio (SIMD on/off) 0.676x (32.4% faster)
Image JPEG encode 819.000 ms

@shai-almog

shai-almog commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 124 screenshots: 124 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 199 seconds

Build and Run Timing

Metric Duration
Simulator Boot 75000 ms
Simulator Boot (Run) 2000 ms
App Install 17000 ms
App Launch 12000 ms
Test Execution 325000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 639.000 ms
Base64 CN1 encode 1523.000 ms
Base64 encode ratio (CN1/native) 2.383x (138.3% slower)
Base64 native decode 384.000 ms
Base64 CN1 decode 881.000 ms
Base64 decode ratio (CN1/native) 2.294x (129.4% slower)
Base64 SIMD encode 446.000 ms
Base64 encode ratio (SIMD/native) 0.698x (30.2% faster)
Base64 encode ratio (SIMD/CN1) 0.293x (70.7% faster)
Base64 SIMD decode 373.000 ms
Base64 decode ratio (SIMD/native) 0.971x (2.9% faster)
Base64 decode ratio (SIMD/CN1) 0.423x (57.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 85.000 ms
Image createMask (SIMD on) 12.000 ms
Image createMask ratio (SIMD on/off) 0.141x (85.9% faster)
Image applyMask (SIMD off) 193.000 ms
Image applyMask (SIMD on) 111.000 ms
Image applyMask ratio (SIMD on/off) 0.575x (42.5% faster)
Image modifyAlpha (SIMD off) 206.000 ms
Image modifyAlpha (SIMD on) 114.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.553x (44.7% faster)
Image modifyAlpha removeColor (SIMD off) 314.000 ms
Image modifyAlpha removeColor (SIMD on) 100.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.318x (68.2% faster)
Image PNG encode (SIMD off) 1234.000 ms
Image PNG encode (SIMD on) 1074.000 ms
Image PNG encode ratio (SIMD on/off) 0.870x (13.0% faster)
Image JPEG encode 800.000 ms

@shai-almog shai-almog merged commit 3f8a84f into master Jun 6, 2026
26 checks passed
@shai-almog shai-almog deleted the fix-5186-receiptstore-lost-per-instance branch June 6, 2026 12:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant