Admin Stripe invoices + recurring subscriptions (customers pay us) (#24)
Unit Tests / test (push) Successful in 11s

## Summary
- Closes #23
- Admin-only invoice dashboard under `/financial/invoices` (superuser)
- One-off Stripe hosted invoices + email pay link (customers pay us)
- Monthly (or weekly/yearly) Stripe Checkout subscriptions + email checkout link
- Webhook at `/financial/stripe/webhook/` syncs invoice/subscription status
- Env docs: `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`, `STRIPE_WEBHOOK_SECRET`

## Test plan
- [ ] Set Stripe **test** keys in local `.env` and restart app
- [ ] As superuser: Dashboard → Invoices → create one-off invoice; confirm Stripe invoice + email
- [ ] As superuser: create monthly subscription; open Checkout link; complete with test card `4242…`
- [ ] Configure Stripe CLI or Dashboard webhook → confirm status updates to paid/active
- [ ] Non-admin user cannot open `/financial/invoices`
- [ ] Prod: add live keys + webhook secret to server `.env`, migrate, restart; point webhook to `https://aimloperations.com/financial/stripe/webhook/`

## Notes
Uses stdlib HTTP to Stripe API (no `stripe` PyPI package) so deploys stay dependency-light.Reviewed-on: #24
This commit was merged in pull request #24.
This commit is contained in:
2026-07-31 12:02:40 -07:00
parent 05aa0b96b1
commit b98f504afc
19 changed files with 1403 additions and 2 deletions
+12
View File
@@ -34,6 +34,18 @@ EMAIL_HOST_USER=replace-with-smtp-user
EMAIL_HOST_PASSWORD=replace-with-smtp-password
EMAIL_PORT=2525
EMAIL_USE_TLS=true
# DEFAULT_FROM_EMAIL=AI ML Operations, LLC <info@aimloperations.com>
# Stripe (live keys from https://dashboard.stripe.com/apikeys)
# Secret key starts with sk_live_; publishable with pk_live_
STRIPE_SECRET_KEY=replace-with-stripe-secret-key
STRIPE_PUBLISHABLE_KEY=replace-with-stripe-publishable-key
# Dashboard → Developers → Webhooks → endpoint
# URL: https://aimloperations.com/financial/stripe/webhook/
# Events: invoice.paid, invoice.payment_failed, invoice.finalized,
# customer.subscription.updated, customer.subscription.deleted,
# checkout.session.completed
STRIPE_WEBHOOK_SECRET=replace-with-stripe-webhook-signing-secret
# Gunicorn
GUNICORN_WORKERS=2