Add Whop as a payment provider alongside Stripe — no rip-and-replace. Both run side-by-side in production 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-org override using your existing Organization.external_metadata field.
PAYMENT_PROVIDER env var defaults to "stripe". Per-org override: Organization.external_metadata["payment_provider"] == "whop". All routing lives in Agree.Payments — one check, not scattered across 52 files.Whop client, webhook controller, Ecto migration, both flow paths. Stripe stays default. Deploy with zero behavior change.
Set external_metadata["payment_provider"] to "whop" on your own Agree org. Test with real money internally.
Enable Whop for 5–10 trusted merchants. Verify webhook parity, accounting sync, and payout timing.
Flip PAYMENT_PROVIDER=whop globally. Stripe path stays in code as fallback until you’re confident.
Your integration has two independent tracks. Each routes through Agree.Payments based on the provider check.
How your existing Ecto schemas and modules map to Whop. These are the columns that get added.
| Your Schema | File | Whop Entity | New Columns |
|---|---|---|---|
Organization |
lib/agree/accounts/organization.ex |
Company (submerchant) | whop_company_id, whop_verification_status |
Invoice |
lib/agree/payments/invoice.ex |
Checkout Session + Payment | whop_checkout_session_id, whop_payment_id |
InvoiceTemplate |
lib/agree/payments/invoice_template.ex |
Plan | whop_plan_id |
Transaction |
lib/agree/payments/transaction.ex |
Payment / Payout | whop_payment_id, whop_payout_id |
PaymentMethod |
lib/agree/payments/payment_method.ex |
Checkout method selection | Methods become checkout-config driven |
Side-by-side: the Stripe calls you have today vs. the Whop equivalents.
Stripe.Customer.createStripe.PaymentIntent.createStripe.SetupIntent for saved methodspayment_intent.succeededInvoiceTemplatepayment.succeeded + membership.went_validStripe.Account.createStripe.AccountLink.create for KYCsubmit_kyc / finish_kycaccount.updatedOrganizationcompany.updatedYour existing 13 Stripe event handlers map to these Whop events. Both webhook endpoints run simultaneously.
| Stripe Event | Whop Event | What Happens |
|---|---|---|
payment_intent.succeeded | payment.succeeded | Invoice marked paid, QBO/Xero sync |
payment_intent.payment_failed | payment.failed | Failed status, retry alert |
charge.refunded | payment.refunded | Refund transaction, fee update |
account.updated | company.updated | Merchant KYC status refreshed |
payout.* | payout.* | Payout timeline in merchant dashboard |
setup_intent.succeeded | payment_method.attached | Payment method readiness updated |
Agree.Workers.Webhook still fires invoice.paid, invoice.failed, etc. to customer endpoints with the same payload shapes.Where Whop fits. Orange is new, green stays unchanged. Both providers coexist.
CUSTOMERS CONTRACT SIGNERS | | Agree Dashboard Agree Sign Flow (React 19 + Vite) (AcceptView.jsx) | | v v +-------------------------------------------+ | Agree Backend (Phoenix) | | lib/agree/payments.ex | | provider = external_metadata || env | +-------------------------------------------+ | | | | Ecto/PG Stripe API Whop REST API Oban (stays) (default) (per-org) (stays)
Flow 2 merchants progress through these states. Stored as organization.whop_verification_status.
| State | Meaning | Can Do | Blocked |
|---|---|---|---|
| invited | Not started | View checklist | Checkout, payouts |
| kyc_pending | KYC in progress | Continue verification | Checkout, payouts |
| restricted | Missing docs | Upload documents | Checkout, payouts |
| active | Fully verified | Checkout, payments, payouts | None |
| suspended | Compliance hold | Read-only | All money movement |
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.