Monetization app + RevenueCat webhooks (store IAP ledger) (#69)
Deploy Beta / unit-tests (push) Successful in 11s
Unit Tests / test (push) Successful in 11s
Deploy Beta / docker (push) Successful in 21s
Deploy Beta / deploy-beta (push) Successful in 50s

## Summary
- Rename `finance` → **`monetization`** Django app (keep `finance_*` tables via `label = "finance"`)
- Add `services/stripe.py` + `services/revenuecat.py`; RevenueCat webhook upserts **subscription + Invoice/Payment** (billing history parity with Stripe)
- Mount `/api/monetization/` + keep `/api/finance/` alias
- Extend `Source`/`Provider` with `revenuecat`; product→plan mapping via `revenuecat_product_id` / `REVENUECAT_PRODUCT_PLAN_MAP`

Closes #68. Companion to [chat_web_app#100](ai_ml_operations/chat_web_app#100).

## Test plan
- [x] `manage.py test monetization.tests` (54 OK)
- [x] Smoke `chat_backend.tests.test_views_documents` + `test_oauth`
- [ ] Deploy: set `REVENUECAT_WEBHOOK_SECRET`; point RC webhook at `/api/finance/webhooks/revenuecat/`
- [ ] Map store product IDs on `SubscriptionPlan.revenuecat_product_id` (or env JSON map)
- [ ] Sandbox INITIAL_PURCHASE → subscription `source=revenuecat` + invoice in `/finance/invoices/`Reviewed-on: #69
This commit was merged in pull request #69.
This commit is contained in:
2026-08-04 03:40:15 -07:00
parent 2aeb95136a
commit e1e086a474
44 changed files with 965 additions and 102 deletions
+14 -2
View File
@@ -98,7 +98,9 @@ with `COMPOSE_DATABASE_URL` if needed.
| `EMAIL_HOST_*` | empty | yes (prod/beta) | SMTP2GO |
| `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 |
| `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 |
| `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 |
| `MICROSOFT_OAUTH_CLIENT_ID` / `..._SECRET` / `..._TENANT` | empty / `common` | for SSO/Drive | Also used for Drive linking (#47), incremental scopes |
@@ -276,9 +278,19 @@ Post-delete UX: clear local tokens → redirect to sign-in. Subsequent
### Subscription change / cancel (portal + webhooks)
Plan change and cancel stay on Stripe Customer Portal
Billing lives in the **`monetization`** Django app (package rename of
`finance`; DB tables keep the `finance_*` prefix via app `label = "finance"`).
URLs: `/api/monetization/...` and alias `/api/finance/...`.
**Web:** plan change/cancel stay on Stripe Customer Portal
(`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`).
`GET /api/finance/subscription/` includes `cancel_at_period_end` and
`current_period_end` for Account UI messaging.