Whop × Agree.com

Integration Guide

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.

Stripe PaymentIntent → Whop Checkout Stripe Connect → Whop KYC/KYB stripity_stripe 3.2.0 → Whop REST API Phoenix + Ecto stays React 19 + Vite stays No new dependencies

Start Here — Drop This Into Your Repo

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.”

Implement a production Stripe → Whop migration for @agree/web (Phoenix 1.7 + React 19). Whop runs side-by-side with Stripe. No rip-and-replace. Flow 1 — SaaS billing: Replace Stripe.PaymentIntent / Customer / SetupIntent in lib/agree/payments.ex Flow 2 — Merchant onboarding: Replace Stripe Connect (submit_kyc, finish_kyc) + post-signature checkout Key files: lib/agree/payments.ex, lib/agree/accounts/organization.ex, lib/agree_web/controllers/webhooks/stripe/, assets/js/views/payments/, assets/js/views/settings/Banking.tsx, lib/agree/workers/stripe.ex Create: Agree.Payments.Whop.Client, AgreeWeb.Webhooks.WhopController, Agree.Workers.Whop, Ecto migration for whop_* columns + idempotency table. Rollout: PAYMENT_PROVIDER env var defaults to "stripe". Per-org override via Organization.external_metadata["payment_provider"]. No new deps needed. Internal orgs first, then small cohort, then full rollout. Webhook handlers must update both merchant and customer dashboard state. Accounting sync (QuickBooks/Xero) must produce identical entries to Stripe path.

⚙ Rollout Strategy

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.
1

Build

Whop client, webhook controller, Ecto migration, both flow paths. Stripe stays default. Deploy with zero behavior change.

2

Internal Test

Set external_metadata["payment_provider"] to "whop" on your own Agree org. Test with real money internally.

3

Small Cohort

Enable Whop for 5–10 trusted merchants. Verify webhook parity, accounting sync, and payout timing.

4

Full Rollout

Flip PAYMENT_PROVIDER=whop globally. Stripe path stays in code as fallback until you’re confident.

⚡ Two Flows

Your integration has two independent tracks. Each routes through Agree.Payments based on the provider check.

Flow 1: SaaS Billing

CustomerSaaS User
→
PaymentWhop Checkout
→
SubscriptionWhop Membership
→
AccessSoftware

Replaces Stripe.PaymentIntent, Stripe.Customer, Stripe.SetupIntent in lib/agree/payments.ex

Flow 2: Contract → Checkout → Payout

ContractSigned
→
MerchantWhop KYC/KYB
→
PaymentWhop Checkout
→
PayoutWhop Payouts

Replaces Stripe.Account.create, Payments.submit_kyc, create_or_update_stripe_connected_account

□ Your Code → Whop

How your existing Ecto schemas and modules map to Whop. These are the columns that get added.

Your SchemaFileWhop EntityNew 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

▣ What Changes

Side-by-side: the Stripe calls you have today vs. the Whop equivalents.

Flow 1: SaaS Billing

Stripe (Today)

  • Stripe.Customer.create
  • Stripe.PaymentIntent.create
  • Stripe.SetupIntent for saved methods
  • Webhook: payment_intent.succeeded

Whop (After)

  • Whop plan from InvoiceTemplate
  • Whop checkout session
  • Methods handled inline by checkout
  • Webhook: payment.succeeded + membership.went_valid

Flow 2: Merchant Onboarding

Stripe Connect (Today)

  • Stripe.Account.create
  • Stripe.AccountLink.create for KYC
  • submit_kyc / finish_kyc
  • Webhook: account.updated

Whop (After)

  • Connected company per Organization
  • Whop KYC/KYB flow
  • Status tracked via company.updated
  • All onboarding managed by Whop

⚡ Webhook Mapping

Your existing 13 Stripe event handlers map to these Whop events. Both webhook endpoints run simultaneously.

Stripe EventWhop EventWhat Happens
payment_intent.succeededpayment.succeededInvoice marked paid, QBO/Xero sync
payment_intent.payment_failedpayment.failedFailed status, retry alert
charge.refundedpayment.refundedRefund transaction, fee update
account.updatedcompany.updatedMerchant KYC status refreshed
payout.*payout.*Payout timeline in merchant dashboard
setup_intent.succeededpayment_method.attachedPayment method readiness updated
ℹ
Outbound webhooks stay identical. Your Agree.Workers.Webhook still fires invoice.paid, invoice.failed, etc. to customer endpoints with the same payload shapes.

▣ Architecture

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)

● Merchant Onboarding States

Flow 2 merchants progress through these states. Stored as organization.whop_verification_status.

StateMeaningCan DoBlocked
invitedNot startedView checklistCheckout, payouts
kyc_pendingKYC in progressContinue verificationCheckout, payouts
restrictedMissing docsUpload documentsCheckout, payouts
activeFully verifiedCheckout, payments, payoutsNone
suspendedCompliance holdRead-onlyAll money movement

⏰ Timeline

One week to first live dollar. Stripe stays live the entire time.

Day 1–2
Build
  • Whop.Client + webhook controller
  • Ecto migration
  • Both flow paths in Payments
  • Deploy — Stripe still default
Day 3–4
Internal Test
  • Enable Whop on your own org
  • Real money, real webhooks
  • Verify QBO/Xero sync parity
Day 5–6
Small Cohort
  • 5–10 trusted merchants
  • Webhook parity confirmed
  • Payout timing validated
Day 7
Full Rollout
  • Flip env var to “whop”
  • Stripe path stays as fallback
  • First live dollar

✓ What Stays the Same

Most of your stack is unchanged. Whop replaces only the payment provider layer.

✓ Phoenix 1.7 + LiveView
✓ Ecto + PostgreSQL
✓ Oban job processing
✓ React 19 + Vite
✓ Tailwind + Radix + AUI
✓ Docker + Fly.io
✓ Auth system
✓ Contract signing flow
✓ Usage metering
✓ Outbound webhook shapes
✓ No new dependencies
~ QuickBooks / Xero
~ Banking.tsx
✓
Bottom line: Auth, contracts, metering, and all integrations are untouched. Stripe keeps running as the default until you decide to flip the switch. No new dependencies, no new infra.