eleven / messenger Public

docs: managed payments — payout model design (BYO Stripe → Business tier) (#1285)

* docs: managed-payments design — payout model default, BYO Stripe as Business feature

Records Paul's 2026-07-22 decisions: managed payments (Stripe Express +
destination charges, 11% all-in structural) as the default provider for paid
access levels; bring-your-own-Stripe kept as a Business-tier feature at a
flat 5.5%; level-price floors €5.50/m / €11/y; MoR consequences (OSS VAT,
disputes, payout holds), the privacy-centralization and E2EE-MoR trust notes,
the phased rollout, and the superseded fee-reconciliation/prediction designs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: managed-payments — pay-what-you-want commissions (final split)

Managed: PWYW 5.5-11%, suggested 11, all-in (floor ≈ our cost line, small
international tickets dip under — priced in). BYO: open to every space, no
tier gate, PWYW down to zero suggested 5.5% on top of the admin's own fees.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: managed-payments — money-mechanics lines follow the PWYW range

* docs: managed-payments — § Lifetime access (single payment)

---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 71ba02db219e6822f4f00e1bf871a4f1abf289b3 parent 3ebfcc1
2 files changed, +163 −0
  • docs/managed-payments.md +160 −0
  • subscriptions.go +3 −0
diff --git a/docs/managed-payments.md b/docs/managed-payments.md
new file mode 100644
index 0000000..6b9b036
--- /dev/null
+++ b/docs/managed-payments.md
@@ -0,0 +1,160 @@
+# 🤖 Managed payments — the payout model for paid access levels
+
+Design doc, 2026-07-22. Status: **designed, not built** (umbrella issue links
+the phases). Companion docs: `docs/community-and-tiers.md` (layer 3 — the tier
+machinery money assigns into), `docs/tier-economics.md` (the hosting price
+book; a different product, same Stripe account), `docs/pricing-bands.md`.
+Decisions here are Paul's (2026-07-22); the VAT/OSS findings are research
+summaries, not legal advice — a human review gates the go-live phase.
+
+## The design in one sentence
+
+> **Members pay the platform and the admin's share transfers structurally —
+> no Stripe account required, commission pay-what-you-want between 5.5% and
+> 11% all-in — while "bring your own Stripe" stays open to every space, with
+> a pay-what-you-want commission (suggested 5.5%) on top of the admin's own
+> fees.**
+
+## Two providers, priced by who bears the risk
+
+| | Managed (default) | BYO Stripe (open to every space) |
+|---|---|---|
+| Merchant of record | the platform | the space admin |
+| Stripe account | ours; admin gets an **Express** account (payouts + KYC only) | admin's own (OAuth-linked Standard, today's live path) |
+| Commission | **pay-what-you-want, 5.5–11%, suggested 11 — all-in** (card processing fees come out of our points, whatever the choice) | **pay-what-you-want, suggested 5.5%** + the admin's own Stripe fees |
+| VAT on member subscriptions | ours (OSS from Ireland; Stripe Tax) | admin's |
+| Disputes/refunds | ours (payout holds; transfer reversals) | admin's |
+| Admin onboarding | minutes (Express account links: name, bank, KYC) | needs a real Stripe account |
+
+Both commissions are the honor mechanism from `pricing-bands.md` applied to
+our own cut: a suggestion, a one-tap choice, no eligibility test. The two
+rails bound it differently because the costs differ:
+
+- **Managed clamps to [5.5, 11]** because we bear real per-charge costs
+ (processing, VAT handling, disputes). The 5.5 floor is roughly our cost
+ line — on the €5.50 minimum ticket with an international card it dips a
+ couple of points under, and that known loss is priced in rather than
+ guarded against.
+- **BYO is unbounded down to zero** because our marginal cost there is nil —
+ the admin is the merchant, pays their own fees, runs their own books. Any
+ commission they choose is honest margin; zero is a legitimate choice.
+
+Why this split, in order of weight:
+
+1. **Activation.** BYO filters monetization to admins who already run a
+ business. A Family or Community admin has no Stripe account and never
+ will; Express onboarding is a bank account and a KYC form. Managed is the
+ 95% case.
+2. **"All-in, including payment fees" becomes structural.** On a
+ destination charge we own the charge and pay Stripe's processing fee from
+ our own take; the admin's share is set by `transfer_data` from their
+ chosen commission, not reconciled after the fact. The
+ refund-reconciliation and fee-prediction designs this supersedes are
+ recorded below (§ Superseded) so they aren't reinvented.
+3. **The cuts price the risk transfer — and both are the admin's call.**
+ Managed bears processing + VAT + disputes, so its pay-what-you-want range
+ floors at 5.5 (≈ our cost line); BYO bears all three itself, keeps its
+ own books and negotiated rates, so its commission is open all the way to
+ zero, suggested 5.5 (market-honest: Memberful charges 4.9% flat for the
+ same shape; Substack takes 10 *plus* processing).
+
+## Money mechanics (managed)
+
+- Checkout Sessions on the **platform account** (no `Stripe-Account` header),
+ `mode=subscription`, `subscription_data[transfer_data][destination]` = the
+ space's Express account, `[amount_percent]` = 100 − the chosen commission
+ (89–94.5). Stripe splits every invoice; processing fees land on the
+ platform side by construction.
+- **Level price floors: €5.50/month, €11/year** (enforced at level pricing
+ time, both providers). Stripe's fixed €0.25/charge makes all-in economics
+ negative below ~€2.60/charge and thin below €5; the floors keep the
+ worst-case (international card) take ≥ ~3 points. Same fee logic that made
+ Business and Family annual-only (`tier-economics.md`).
+- Payouts ride Stripe Express schedules; a rolling payout delay (Stripe's
+ default) is the fraud/dispute buffer. Disputes: transfer reversal claws
+ back the transferred share; the €15 dispute fee is ours — priced into the
+ commission.
+- VAT: the platform is seller of record for an electronically supplied
+ service. Consumer stickers are VAT-inclusive (the pricing-bands posture),
+ so our net per subscription varies by buyer country; OSS (one Irish
+ registration) covers the EU, Stripe Tax computes per-invoice. Non-EU
+ registrations (UK first) ratchet on thresholds. **A human tax review gates
+ the live flip** — same bar as pricing-bands Phase 1.
+
+## Trust model (the two honest costs)
+
+- **Privacy centralization.** Today each admin's Stripe sees only their own
+ members' payment identity; managed payments concentrates every paying
+ member of every space into one Stripe dashboard. Our own stores still hold
+ only opaque ids (the billing.go rule), but the dashboard exposure is real
+ and grows with the fleet. Named here deliberately; BYO remains the
+ decentralized escape hatch, and that is part of why it stays.
+- **Merchant of record for content we cannot see.** E2EE means we underwrite
+ payments for communities whose content is structurally opaque to us.
+ Mitigations: the existing report-to-admins/moderation surface, payout
+ holds, and the standing right to off-board a space from *payments*
+ (payouts stop; the space itself, being E2EE, was never ours to read).
+ This asymmetry — we can stop paying out, we cannot inspect — is the
+ design, not a gap in it.
+
+## Implementation shape
+
+- `subscriptions.go` already has the seam: `PayProvider` is an interface and
+ "adding a provider never touches the state machine." `stripePlatform`
+ (managed) lands beside `stripeConnect` (BYO). Normalized `SubEvent`s feed
+ the same `applySubEvent`.
+- Provider selection per space: managed is the default wherever platform
+ creds exist; `stripe_connect_account` (BYO) stays available to every
+ space — no tier gate. A space that links its own Stripe chooses the BYO
+ rail by that act.
+- Commission choices live in the space config: `managed_commission_pct`
+ (clamped [5.5, 11], default 11) and, phase 3, the BYO percent (suggested
+ 5.5, floor 0). Changing a choice applies to new checkouts; existing
+ subscriptions keep the split they were created with.
+- Express account id lives in the space config (`stripe_express_account`),
+ set by the onboarding flow (Account Links API; `account.updated` webhooks
+ flip the payouts-ready bit). Until payouts-ready, levels can be drafted
+ but not sold — never sell against an account that can't be paid.
+- Level prices: managed levels create their Stripe Price on the platform
+ account (BYO keeps creating them on the admin's). The floors apply in the
+ level-pricing path, before any Stripe call.
+- Existing BYO spaces (paulca) are grandfathered: linked accounts keep
+ working; the 10% → pay-what-you-want change (suggested 5.5) applies from
+ its deploy — a cut *reduction* at the suggestion, so no notice period.
+
+## Lifetime access (single payment)
+
+A level can price as **`interval: "once"`** — pay once, in for as long as the
+space exists (PR #1290; both rails). The copy says "as long as the space
+exists" deliberately: an admin can close a space, and we don't sell promises
+admins can't keep. Mechanics: `mode=payment` checkouts with the commission as
+a fixed amount (payment mode has no percent splits) — BYO takes its flat fee,
+managed applies the chosen PWYW commission to the transfer. The grant has no
+lifecycle — exempt from every renewal-shaped sweep — and the only exits are a
+refund/chargeback (`charge.refunded` / `charge.dispute.created` revoke
+immediately, with a payment-intent tombstone so redelivered grants can't
+re-buy a refunded level) or an admin removal. **Floor: 11.00 one-time** —
+the elevens' third coin (€5.50/month, €11/year, €11 once).
+
+## Rollout phases (mirrored in the umbrella issue)
+
+1. **Provider core (dark):** `stripePlatform` + destination-charge checkout
+ + webhook parse + tests; config-gated off.
+2. **Express onboarding:** account links flow, payouts-ready gating, admin
+ UI (web + native surfaces per parity rule).
+3. **Floors + BYO repricing:** €5.50/€11 level-price floors both
+ providers; BYO's application fee becomes the pay-what-you-want config
+ (suggested 5.5, floor 0) with its admin picker; changelog.
+4. **Tax + go-live:** Stripe Tax on, OSS registration confirmed, human tax
+ review, live platform creds, docs/status updates.
+
+## Superseded designs (recorded so they stay dead)
+
+- **Fee-refund reconciliation** (charge 11%, refund the actual
+ `balance_transaction.fee` after `invoice.paid`): correct but only needed
+ when the admin is MoR and we promise all-in — i.e. never, under this
+ split. BYO makes no all-in promise; managed doesn't need reconciliation.
+- **Fee prediction from the payment intent**: impossible before the first
+ charge (no card yet), asymmetric on renewals (application fees refund
+ down, never up). Reconciliation was prediction with the error bar
+ collapsed; destination charges remove the need for either.
diff --git a/subscriptions.go b/subscriptions.go
index ae6991d..de6900b 100644
--- a/subscriptions.go
+++ b/subscriptions.go
@@ -10,6 +10,9 @@ package main
// Money flows via Stripe CONNECT to the SPACE ADMIN's connected account (the
// admin is merchant of record); the platform takes a 10% application fee. The
// payment backend is a seam (PayProvider); stripeConnect is one implementation.
+// NB: docs/managed-payments.md (2026-07-22) redesigns this surface — managed
+// payouts become the default provider and this BYO path becomes a
+// Business-tier feature at 5.5%; the seam below is where it lands.
// With no provider configured the feature is dormant — a level carries no
// price, so members see no subscribe path. The entitlement STATE MACHINE
// (applySubEvent) is provider-independent and unit-tested on its own; providers