Whop runs alongside Stripe in production — no rip-and-replace. Checkout, subscriptions, coach payouts, and the earnings dashboard switch over per-coach. Stripe stays live as the fallback until you’re ready to cut over.
This file gives Cursor and Claude Code full context about your Whop migration. Every file path, schema change, and code example — ready to implement.
Save as CLAUDE.md in your project root. Open Cursor. Say “implement the Whop migration.”
Whop runs alongside Stripe in production. No staging environment needed, no new dependencies. Routing is controlled by an env var with per-coach override using a Firestore field.
PAYMENT_PROVIDER env var defaults to "stripe". Per-coach override: coach.payment_provider == "whop" in their Firestore document. All routing lives in back_end/services/payment_router.py — one check, not scattered across files.Whop client module, webhook controller, Firestore field additions, both flow paths. Stripe stays default. Deploy with zero behavior change.
Set payment_provider: "whop" on your own test coach in Firestore. Book a session, complete a lesson, verify the payout — real money.
Enable Whop for 5–10 trusted coaches. Verify checkout, subscription renewals, per-lesson payouts, and notification parity.
Flip PAYMENT_PROVIDER=whop globally. Stripe path stays in code as fallback until you’re confident.
Four distinct money flows get a Whop path. Each runs behind the provider routing check.
How your existing Firestore collections and Python models map to Whop entities. These are the fields that get added.
| Your Schema | File | Whop Entity | New Fields |
|---|---|---|---|
Coach |
back_end/models/coach.py |
Company (child) | whop_company_id, whop_verified, whop_product_id, whop_plan_ids, payment_provider |
Athlete |
back_end/models/athlete.py |
User | whop_user_id |
Connection |
back_end/models/connection.py |
Membership | whop_membership_id, whop_product_id |
Transaction |
server.py:1088 / stripe_service.py:475 |
Payment | whop_payment_id, whop_checkout_id, provider |
| (new) | Firestore collection | Webhook Event | whop_webhook_events collection for idempotency |
Side-by-side: the Stripe calls you have today vs. the Whop equivalents.
PaymentIntent.create for checkoutSubscription.create for recurringAccount.create(express) for coachesAccountLink.create for onboardingTransfer.create per-lesson payoutBalance.retrieve for dashboardbilling_portal.Session.createcheckout_sessions.create for checkoutrenewal_period for recurringcompanies.create (child) for coachestransfers.create per-lesson payout<BalanceElement><PaymentElement> in BookASession.jsx<CardElement> in AthletePaymentSettings.jsx<Elements> wrapper in StripeProvider.jsx<PayoutElement> + <BalanceElement>Your existing Stripe event handlers map to these Whop events. Both webhook endpoints run simultaneously.
| Stripe Event | Whop Event | What Happens |
|---|---|---|
payment_intent.succeeded |
payment.succeeded |
Mark transaction paid, credit sessions, send confirmation SMS + email |
payment_intent.payment_failed |
payment.failed |
Mark transaction failed, send failure notification |
invoice.payment_succeeded (renewal) |
membership.went_valid |
Create renewal transaction, credit sessions for the period |
invoice.payment_failed |
membership.went_invalid |
Mark subscription canceled, notify athlete |
customer.subscription.deleted |
membership.went_invalid |
Move to past subscriptions, clean up connection |
customer.subscription.updated |
membership.went_invalid |
Handle cancel-at-period-end state changes |
customer.subscription.created |
membership.went_valid |
Log + track (no DB changes, same as today) |
/api/v1/stripe/webhook handles in-flight Stripe transactions. /api/v1/whop/webhook handles Whop events. Both coexist until full cutover. Whop uses event (not type) for the event name.Where Whop fits in your stack. Orange is new, green stays unchanged. Both providers coexist.
Next.js Frontend Flask Backend
(Pages Router) (server.py)
| |
BookASession.jsx ───── POST ───── /api/v1/create/payment/intent
payments/index.jsx |
┌───────────┴───────────┐
│ payment_router.py │
│ get_provider(coach) │
└───────┬───────┬───────┘
│ │
┌─────────────┘ └─────────────┐
│ │
Stripe (legacy) Whop (new)
stripe.PaymentIntent.* whop.create_checkout_session()
stripe.Transfer.create whop.create_transfer()
stripe.Account.create whop.create_company()
│ │
/api/v1/stripe/webhook /api/v1/whop/webhook
│ │
└─────────┬─────────────────────────┘
│
Firestore (transactions, connections, users)
Brevo emails | Twilio SMS | PostHog events
Each coach transitions through these states as they onboard onto Whop payouts.
| State | Meaning | Can Do | Blocked |
|---|---|---|---|
| No Whop Account | Coach hasn't started Whop setup | Everything via Stripe | Whop checkout, Whop payouts |
| Company Created | whop_company_id set, verification pending |
Products/plans created | Whop checkout (until verified) |
| Verified | whop_verified: true, ready for payouts |
Full Whop flow: checkout, payouts, dashboard | Nothing |
| Active on Whop | payment_provider: "whop" |
All payments route through Whop | Stripe (intentionally bypassed) |
One week to first live dollar. Stripe stays live the entire time.
Most of your stack is unchanged. Whop replaces only the payment provider layer.