Grant mobile plans from verified App Store and Play purchases (#78).
CI / test (pull_request) Successful in 10s
Unit Tests / test (pull_request) Successful in 10s

The app confirms a StoreKit signed transaction or Play purchase token. The backend checks it with Apple or Google before writing the ledger, and the RevenueCat webhook no longer grants access.
This commit is contained in:
westfarn committed 2026-10-19 06:14:27 -05:00
1 parent bb5c43a4e7
commit 74e8a4ad71
14 files changed
+1141 -119

No files matched your search

+11 -5
View File
@@ -99,7 +99,9 @@ with `COMPOSE_DATABASE_URL` if needed.
| `CAPTCHA_SECRET_KEY` | empty | recommended | |
| `ENABLE_ACCOUNT_REGISTRATION` | `false` | optional | Self-serve sign-up; keep false until ready |
| `STRIPE_SECRET_KEY` / `STRIPE_PUBLISHABLE_KEY` / `STRIPE_WEBHOOK_SECRET` | empty | yes for billing | Stripe API + webhook (`monetization` app) |
| `REVENUECAT_WEBHOOK_SECRET` | empty | yes for store IAP | RevenueCat webhook Authorization bearer |
| `APPLE_IAP_BUNDLE_ID` | `ai.hesychia.chat` | yes for iOS IAP | Must match the signed StoreKit transaction |
| `GOOGLE_PLAY_PACKAGE_NAME` | `ai.hesychia.chat` | yes for Play IAP | Android application id |
| `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` | empty | yes for Play IAP | Play Console service-account JSON (or a file path) |
| `REVENUECAT_PRODUCT_PLAN_MAP` | empty JSON | optional | `{"product_id":"plan_slug"}` fallback map |
| `STRIPE_PRICE_ID` | empty | optional | Pre-created Price; else `$10/mo` from settings |
| `GOOGLE_OAUTH_CLIENT_ID` / `..._SECRET` | empty | for SSO/Drive | Also used for Drive linking (#47), incremental scopes |
@@ -286,10 +288,14 @@ URLs: `/api/monetization/...` and alias `/api/finance/...`.
(`POST /api/finance/portal/`). Local state syncs via
`customer.subscription.updated` / `deleted` webhooks.
**Native (Play / App Store):** RevenueCat webhooks at
`POST /api/finance/webhooks/revenuecat/` (Authorization bearer =
`REVENUECAT_WEBHOOK_SECRET`) upsert Invoice/Payment ledger rows and sync
`UserSubscription` (`source=revenuecat`).
**Native (Play / App Store):** the app buys through StoreKit or Play Billing,
then `POST /api/finance/store/confirm/` with the signed App Store transaction
or the Play purchase token. The backend verifies it (Apple certificate chain,
or Play Developer API `purchases.subscriptionsv2`) and upserts the ledger.
`source` is `app_store` or `play_store`. There is no store webhook. Renewals
are another confirm from the app; access ends when `current_period_end` passes.
`GET /api/finance/subscription/` includes `store_account_token` to pass into
the store purchase as `appAccountToken` / `obfuscatedAccountId`.
`GET /api/finance/subscription/` includes `cancel_at_period_end` and
`current_period_end` for Account UI messaging.