Read-time fallbacks
Across the SDKs,getCustomerInfo() aims for authoritative server state first. On transient failures, the SDKs can still surface offline-derived entitlement state when they have enough local evidence.
That means your UI should pay attention to:
- whether the snapshot is offline-derived
- whether verification is server-backed or device-backed
- whether you are reading a cached surface or forcing a fresh fetch
Cached reads are intentional
Use cached reads when you need fast UI decisions:- cached offerings for paywall paint
- cached remote configs for local behavior
- cached customer info when you want local continuity during app startup
Common recovery mistakes
- using restore as a silent background sync
- using queue drain as a normal restore button
- treating wrapper parity names like
quietSyncPurchases()as if they were a different business flow from native sync - overloading custom attributes with reserved profile or attribution fields
Debug signals to watch
The most useful signals are:- receipt pipeline events
- deferred purchase resolution events
- SDK log events where wrappers expose them
- explicit error objects such as
AppActorErroror wrapper equivalents with code, message, detail, request ID, and retry metadata
Purchase error taxonomy
Empty offering decision tree
- Is the app configured with the expected public app SDK key?
- Does the project have a current offering?
- Does the offering contain packages for the running platform?
- Do the packages map to store products available to this tester?
- Are the products attached to the entitlement your app checks?
- Are you rendering
offering.packages, not hardcoded product IDs?
Purchase succeeded but access did not unlock
Check:- product is attached to the
premiumentitlement - customer info was refreshed after purchase
CustomerInfo.hasActiveEntitlement("premium")is true- status is
activeor validgrace, notbilling_retry,expired, orrevoked - verification is not
failed - backend resources are checking AppActor server-side state
Practical rule of thumb
- user taps restore: run restore
- app wants quiet reconciliation: run sync
- you know receipts are stuck locally: run queue drain
- app is temporarily offline: use offline entitlement helpers as a temporary access decision, then resync when possible