Replace Stripe Connect payouts with Whop — 180+ countries, instant cashout, built-in KYC and 1099 compliance. Stripe stays for pay-ins. Side-by-side, no rip-and-replace.
This file gives Cursor and Claude Code full context on the payout migration. Every file path, schema change, and TypeScript code example — scoped to payouts only.
Save as CLAUDE.md in your project root. Open Cursor. Say “implement the Whop payout migration.”
Whop runs alongside Stripe Connect in production. Stripe handles ALL pay-ins, Whop handles payouts for opted-in users.
PAYOUT_PROVIDER env var defaults to "stripe". Per-user override: accounts.payout_provider column. All routing lives in src/lib/payout-provider.ts — one function, not scattered across files.Whop client, webhook handler, migration, both payout paths. Stripe stays default. Deploy with zero behavior change.
Set accounts.payout_provider to "whop" on your own test accounts. Transfer real money.
July 1st: new assignors/referees default to Whop. Existing accounts stay on Stripe Connect.
Transition existing accounts over 3–6 months. Stripe Connect path stays as fallback.
How funds move from organizations to assignors and referees via Whop.
How your existing schemas and modules map to Whop. Only payout-related tables change.
| Your Schema | File | Whop Entity | New Columns |
|---|---|---|---|
accounts | supabase/migrations/schema_init.sql | Company (connected account) | whop_company_id TEXT, payout_provider TEXT |
transactions | supabase/migrations/schema_init.sql | Transfer (ctt_xxx) | provider TEXT, status TEXT |
profiles | supabase/migrations/schema_init.sql | — | No changes |
customers | supabase/migrations/schema_init.sql | — | No changes (Stripe pay-in) |
games | supabase/migrations/schema_init.sql | — | No changes (fee logic stays) |
| New table | migration | Webhook idempotency | whop_webhook_events |
Side-by-side: the Stripe Connect calls you have today vs. the Whop equivalents. Only payout routes change.
accounts.create — onboard refereeaccountLinks.create — KYC linktransfers.create — send payoutaccounts.retrieve — check statusPOST /companies — create accountPOST /account-links — KYC linkPOST /transfers — send payoutGET /companies/{id} — check statusStripe pay-in webhooks stay unchanged. Only Connect payout events get a Whop equivalent.
| Stripe Connect Event | Whop Event | What Happens |
|---|---|---|
account.updated | company.created | Set accounts.completed = true after verification |
| None (batch function) | transfer.completed | Transfer to referee confirmed |
| None | withdrawal.completed | Referee withdrew to bank/crypto |
| None | withdrawal.failed | Withdrawal failed — alert user |
| None | payment.succeeded | Platform top-up confirmed |
/api/stripe/webhook (pay-ins — unchanged), /api/stripe/webhook/connect (Stripe Connect accounts), and /api/whop/webhook (Whop payout events).Where Whop fits. Orange is new, green stays unchanged. Both payout providers coexist.
┌────────────────────┐
│ Organizations │ PAY-INS (Stripe — unchanged)
│ (invoices, charges) │
└─────────┬──────────┘
│
┌─────────┴──────────┐
│ Stripe Checkout │
│ Invoices, Subs │
└─────────┬──────────┘
│
┌─────────┴──────────┐
│ Refr Platform │ PAYOUTS (provider routing)
│ getPayoutProvider() │
└────┬─────────────┬────┘
│ │
provider= │ │ provider=
"stripe" │ │ "whop"
│ │
┌───────┴───┐ ┌────┴───────┐
│ Stripe │ │ Whop API │
│ Connect │ │ /transfers │
└─────┬─────┘ └────┬───────┘
│ │
│ ┌───┴───────┐
│ │ Ref Wallet │
│ │ (user-init) │
│ └────┬───────┘
│ │
┌─────┴────────────┴────┐
│ Assignor / Referee │
│ Bank / Crypto / Venmo │
└────────────────────────┘
Payouts-only scope keeps this tight. Stripe stays live for pay-ins the entire time.
Most of your stack is unchanged. Whop replaces only the payout and onboarding layer.
fetch — no new dependencies.