Subscription lifecycle
The interactive simulator on the homepage walks through these transitions. This page is the reference behind it.
The two flags that matter
Section titled “The two flags that matter”Your code should gate on whether the entitlement is active, and separately show UI based on whether it will renew. Confusing the two is the most common lifecycle bug: a customer who turns off auto-renew is still entitled until the period ends.
Transitions
Section titled “Transitions”| Event | App Store signal | Google Play (RTDN) | Stripe | Entitlement after | Your app |
|---|---|---|---|---|---|
| Purchase | SUBSCRIBED |
SUBSCRIPTION_PURCHASED |
invoice.paid |
Active | Refresh entitlements; server records it |
| Renewal | DID_RENEW |
SUBSCRIPTION_RENEWED |
invoice.paid |
Active, new expiry | Nothing |
| Auto-renew off | DID_CHANGE_RENEWAL_STATUS |
SUBSCRIPTION_CANCELED |
customer.subscription.updated |
Active until period end | Keep access; optional win-back |
| Payment failed | DID_FAIL_TO_RENEW |
SUBSCRIPTION_IN_GRACE_PERIOD |
invoice.payment_failed |
Active during grace | Keep access; prompt to fix payment |
| Recovered | DID_RENEW (billing recovery) |
SUBSCRIPTION_RECOVERED |
invoice.paid |
Active | Nothing |
| Period ends | EXPIRED |
SUBSCRIPTION_EXPIRED |
customer.subscription.deleted |
Inactive | Remove access on next check |
| Refund / revoke | REFUND, REVOKE |
SUBSCRIPTION_REVOKED |
charge.refunded |
Inactive immediately | Remove access now |
| Restore (new device) | StoreKit current entitlements | queryPurchases |
, | Unchanged | identify(email) then read entitlements |
The store signal names are the platforms’ own. Orca’s webhook does not forward them; it sends the updated entitlement, and the server SDKs’ getActiveEntitlements reflects the current state.
Grace periods and billing retry
Section titled “Grace periods and billing retry”Grace-period length is configured in App Store Connect, Play Console or Stripe’s retry settings, not in Orca. Apple’s billing retry and Google’s account hold can continue after grace ends; during those windows the entitlement is no longer active even though the store may still recover the payment later.
Plan changes
Section titled “Plan changes”App Store and Google Play prorate automatically once an Offering is configured in the dashboard. For Stripe and GoCardless, pass what the customer is switching from:
await orca.purchase(newEntitlement, externalStore: ExternalStore.stripe, redirectUrl: 'https://yourapp.com/success', failureRedirectUrl: 'https://yourapp.com/failure', proratedProduct: currentEntitlement.products.stripe, prorationMode: ProrationMode.upgrade);Restores and cross-platform access
Section titled “Restores and cross-platform access”Access follows the customer email. A purchase made on iOS by ada@example.com is active on Android and web for ada@example.com once each client calls identify. A purchase made without identifying the customer cannot later be found by email, identify before selling.
Simplifications in the homepage simulator
Section titled “Simplifications in the homepage simulator”The simulator collapses billing-retry and account-hold states into “grace” and “expired”, shows one store at a time, and omits one-time purchases and consumables.