## Summary - The Capacitor shell buys through StoreKit (iOS 15) and Play Billing instead of RevenueCat. - After the store sheet succeeds, the app POSTs the signed transaction or Play purchase token to `/monetization/store/confirm/`. - Restore reads current store entitlements and confirms each one. Web checkout stays on Stripe. - The store account token from `GET /monetization/subscription/` is sent as `appAccountToken` / `obfuscatedAccountId`. Depends on the `chat_backend` PR for issue #78 (`feat/78-native-store-purchases`). ## Test plan - [x] `storeBilling`, `revenueCat`, `finance`, and `BillingSection` unit tests - [ ] iOS sandbox purchase and restore against a backend with `APPLE_IAP_BUNDLE_ID` set - [ ] Play license-test purchase and restore against a backend with `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` set - [ ] Web billing still opens Stripe Checkout Reviewed-on: #122
1.5 KiB
Monetization (web Stripe + native store billing)
Channels
| Runtime | Checkout / manage | Ledger |
|---|---|---|
Web (!isNativePlatform) |
Stripe Checkout + Customer Portal via /monetization/... |
Stripe webhooks |
| Native Capacitor | StoreKit (iOS) and Play Billing (Android) | POST /monetization/store/confirm/ verifies the store transaction and writes the same invoice/payment tables |
There is no App Store, Play, or RevenueCat webhook. After the store sheet succeeds, the app sends the signed StoreKit transaction or the Play purchase token. The backend checks it before granting access. Renewals and restores send the current store entitlement again. Access ends when the verified period end passes.
FE displays whatever /monetization/invoices/ (and subscription) returns after refresh. Provider badge uses provider (app_store, play_store, or legacy revenuecat) plus revenuecat_store.
Store account token
GET /monetization/subscription/ returns store_account_token. The native purchase passes that value as StoreKit appAccountToken and Play Billing obfuscatedAccountId. The backend rejects a token that belongs to a different user, and rejects a transaction already linked to another account.
Plan rows expose store_product_id (the store product identifier). Configure it on SubscriptionPlan.revenuecat_product_id, or set REVENUECAT_PRODUCT_PLAN_MAP so a product id maps to a plan slug.
Related
- Backend issue #78
ANDROID.md/IOS.mdfor store builds