DreamPro Coaching — Stripe checkout + split webhook
The money-in path that turns the (already-built) split engine into real revenue. Code is shipped but inert until you create a Stripe price + enable the webhook event + (for payouts) onboard Connect accounts. This doc is the wiring + test runbook. Companion to DREAMPRO_COACHING_CHANNEL_SCHEMA.md.
What's shipped (inert)
POST /api/coaching/checkout(payment.ts) — creates a Stripe Checkout subscription session for the caller's coaching membership. Stampsplan_key=coaching_subscription+membership_idon the subscription metadata (subscription_data.metadata) so every recurring invoice can resolve the membership. Returns 503 untilSTRIPE_PRICE_COACHINGis set.priceToPlanKeymapsSTRIPE_PRICE_COACHING→coaching_subscription.- Webhook
invoice.payment_succeededcase — isolated (own try/catch), no-ops for any non-coaching subscription. For a coaching sub it callsrecordCoachingInvoiceSplit({ membershipId, grossCents: invoice.amount_paid, invoiceId })→ twocreator_earningsrows (coach + override),pending. The money guard skipsis_seed/ AI-persona memberships.
Now shipped (since this doc was first written)
- Subscribe surface —
/coaching/subscribePOSTs/api/coaching/checkoutand redirects tosession.url; 503s with a friendly message untilSTRIPE_PRICE_COACHINGis set. - Connect transfers (payout-out) —
payoutPendingCoachingEarnings()(coaching-payouts.ts) transferspendingearnings to each payee's Connect account, stampingstripe_transfer_id+ flippingpending → transferred. It runs on the dailycoaching_payoutscron (index.ts), doubly gated: the cron toggle (/dashboard/admin/crons) AND envCOACHING_PAYOUTS_ENABLED(the fn is a no-op when unset). - Coach payout onboarding — the "Get paid" card on
/dashboard/coachingruns Connect onboarding (/api/creator/payouts/onboard-link) + shows the coach's own earnings (/api/coaching/my-earnings). Coaches without an onboarded account are skipped by the cron (their earnings staypending). - Partner dashboard —
/dashboard/coaching/partnershows the network-wide override balance (pending vs paid) + coach roster.
Go-live order + env/cron checklist: DREAMPRO_COACHING_GO_LIVE.md (Parts D + E).
Wiring (one-time)
- Stripe Dashboard → create a recurring Price for DreamPro Coaching (e.g. $29/mo). Copy the price id.
- Env (backend / Railway):
STRIPE_PRICE_COACHING=price_… - Stripe Dashboard → Webhooks: on the existing endpoint (
/api/payment/webhook), enable theinvoice.payment_succeededevent (andcheckout.session.completedcustomer.subscription.*if not already). The signing secret is unchanged.
- (Payouts, later) coaches + the channel partner each finish Stripe Connect
onboarding via the existing
/api/creator/payouts/onboard-link.
Test plan (Stripe test mode)
- Set
STRIPE_PRICE_COACHINGto a test-mode price; use test webhook secret. - Enroll a real (non-seed) client via a
/start/<slug>?ref=<coach-code>link (creates thecoaching_membershipsrow the checkout needs). - Hit
POST /api/coaching/checkout(or the future button) → complete Stripe Checkout with test card4242 4242 4242 4242. - Confirm
invoice.payment_succeededfires → checkcreator_earningshas acoaching_subscriptionrow (coach share + platform share) and acoaching_overriderow (partner share), bothpending, samestripe_invoice_id. - Verify the money guard: repeat against a seed/persona membership → no
earnings rows (the webhook logs
recorded: false, reason: 'seed membership…'). - Verify idempotency: replay the webhook → no duplicate rows (dedup on
stripe_invoice_id+coaching_subscription). - The split totals appear in the super-admin earnings panel on
/dashboard/coaching.
Safety notes
- The webhook case is additive + isolated — it only acts on subscriptions
whose metadata says
coaching_subscription; everything else (Graphene+, etc.) is untouched. A coaching-split error is caught locally and never fails the webhook for other events. - Nothing moves real money yet: earnings are recorded
pending; transfers are the explicit follow-on above.