Skip to content

MyRecipes 1.0 purchase, restore, and synchronization contract

Issue: #267

Baseline: 2026-08-02

Source of truth

StoreKit reports transaction events, but it does not directly grant MyRecipes cloud access. The app sends the store verification payload to the App Check and Firebase Authentication-protected record_purchase_call Function. Only the canonical schema-v1 entitlement subsequently written under users/{uid}.subscription determines access.

The app listens to that user document after sign-in. A backend purchase, restore, notification, or scheduled reconciliation therefore updates the subscription screen without a reinstall or locally manufactured boolean. Signing out cancels both the StoreKit and entitlement listeners, clears the in-memory entitlement, and clears persistent private Firestore cache before a different account initializes.

Transaction states

StoreKit state Client behavior Complete transaction? Access decision
Purchased Send to trusted verifier Only after backend returns verified=true Canonical entitlement
Restored Send to the same trusted verifier Only after backend returns verified=true Canonical entitlement and original-transaction binding
Pending / Ask to Buy Display pending; wait for a later StoreKit event No No new access
Cancelled Display cancellation and clear pending UI No No new access
Error Display bounded retry guidance No No new access
Verification/network/App Check failure Retain transaction for StoreKit replay; offer restore/retry No Existing bounded canonical entitlement only

Duplicate purchase-stream deliveries are coalesced while verification is in flight. The backend remains idempotent by verified transaction identity, so repeated restore and interrupted verification cannot duplicate entitlement or move one original transaction to another UID.

Startup and outage behavior

The app binds the purchase stream before product lookup. This permits an unfinished verified transaction to replay after termination or restart. It also starts a Firestore entitlement listener for the current UID. Product loading, store availability, and restore feedback are bounded and expose retry messages.

When backend refresh is temporarily unavailable, the client uses only the server-written state and deadline already present in Firestore. It does not extend expiry. The backend may issue the approved bounded outage state under

269; otherwise writes fail closed under #303.

Products and disclosures

Only the explicitly configured monthly and annual product identifiers are classified as subscriptions. The purchase screen shows the store-provided full price, monthly/annual duration, included cloud service, eligible 14-day introductory trial, automatic-renewal disclosure, restore action, Apple standard Terms of Use, Privacy Policy, and platform-appropriate subscription management.

App Store Connect pricing, localization, introductory-offer eligibility, URLs, and the privacy text remain subject to the final compliance review in #271.

Evidence still required

Repository and emulator tests cannot generate authoritative StoreKit sandbox evidence. Before #267 closes, record monthly and annual sandbox purchase, cancellation, pending/deferred, failed verification and retry, interrupted verification, repeated restore, reinstall/Device B restore, account switching, and live paywall refresh against the designated TestFlight build. Final deployed authorization is also part of #305.