Display rule
Use packages as your UI source of truth:offering.packagesfor the full list and dashboard orderoffering.monthly,offering.annual, orpackageFor(...)for known slotslocalizedPriceStringfor the primary price labeldisplayName,productName,productDescription, andmetadatafor copybasePlanIdandofferIdfor Android debugging or upgrade flows
Keep product UI simple
Your paywall view model is app code, not an AppActor requirement. Keep the docs-level rule simple:- fetch the current offering
- render the packages that exist
- show the store-localized price where available
- provide a recoverable fallback when there are no packages
- keep the original package object attached to the CTA
Preserve package identity on tap
The selected UI card should keep the originalAppActorPackage object. That package carries the backend package ID, offering ID, store product metadata, Android base plan, and offer information the purchase API needs.
Paywall QA checklist
- The app can render the current offering.
- Monthly and annual package slots exist if your UI references them directly.
- Package display order matches the dashboard order.
- Price labels use store-localized data, not hardcoded strings.
- Metadata-driven badges have fallbacks.
- Empty offerings produce a recoverable UI, not a crash or premium unlock.
- The purchase CTA receives the original package object, not just a product ID.