tenseleyflow/shithub / 7d28c38

Browse files

Document Stripe billing operations

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
7d28c38b893383d448d76e5e66eae3931f237906
Parents
f1aee2b
Tree
d0d7c92

3 changed files

StatusFile+-
M docs/internal/billing.md 3 0
M docs/internal/index.md 2 0
A docs/internal/runbooks/stripe-billing.md 136 0
docs/internal/billing.mdmodified
@@ -166,6 +166,9 @@ PAYMENTS SP03 adds the first Stripe operator contract:
166166
   When billing is disabled, that action stays visibly unavailable
167167
   instead of linking to an unmounted route.
168168
 
169
+The operator enablement flow is documented in
170
+[`runbooks/stripe-billing.md`](./runbooks/stripe-billing.md).
171
+
169172
 ## Entitlement architecture
170173
 
171174
 Paid feature checks must live behind a central entitlement package, not
docs/internal/index.mdmodified
@@ -71,6 +71,8 @@ site.
7171
 - [deploy.md](./deploy.md) — Ansible playbook + topology.
7272
 - [runbooks/runner-deploy.md](./runbooks/runner-deploy.md) —
7373
   Actions runner host deployment.
74
+- [runbooks/stripe-billing.md](./runbooks/stripe-billing.md) —
75
+  Stripe Billing setup, smoke test, go-live, and rollback.
7476
 - [runbooks/](./runbooks/) — incident, backup, restore, upgrade,
7577
   rollback, plus rotation procedures.
7678
 
docs/internal/runbooks/stripe-billing.mdadded
@@ -0,0 +1,136 @@
1
+# Stripe Billing
2
+
3
+Operator runbook for turning on shithub's paid organization flow.
4
+Pair this with [`../billing.md`](../billing.md) for the product
5
+contract and entitlement matrix.
6
+
7
+## Preconditions
8
+
9
+- A verified Stripe account with payouts enabled to a bank account the
10
+  operator controls.
11
+- A recurring per-seat Product/Price for Team organizations. The v1
12
+  price is `$4` USD per active organization member per month.
13
+- Production `auth.base_url` points at the public HTTPS origin.
14
+- Web and worker processes share the same billing env vars.
15
+- Database migrations are current.
16
+
17
+Do not enable live billing before Stripe account identity, tax, payout,
18
+and statement descriptor setup are complete. shithub stores only Stripe
19
+IDs and derived payment summaries; card data stays in Stripe.
20
+
21
+## Stripe setup
22
+
23
+1. Create a Product named `shithub Team`.
24
+2. Create a recurring monthly Price:
25
+   - Billing scheme: per unit.
26
+   - Currency: USD.
27
+   - Unit amount: `400`.
28
+   - Usage: licensed quantity.
29
+3. Copy the Price ID, for example `price_...`.
30
+4. Create a restricted secret key if possible. It must be able to:
31
+   - create/read Customers,
32
+   - create Checkout Sessions,
33
+   - create Billing Portal Sessions,
34
+   - update Subscription Items,
35
+   - read invoice/subscription objects delivered by webhooks.
36
+5. Create a webhook endpoint for:
37
+   - `checkout.session.completed`
38
+   - `customer.subscription.created`
39
+   - `customer.subscription.updated`
40
+   - `customer.subscription.deleted`
41
+   - `invoice.finalized`
42
+   - `invoice.payment_succeeded`
43
+   - `invoice.payment_failed`
44
+   - `invoice.voided`
45
+6. Point the endpoint at:
46
+   `https://<host>/stripe/webhook`
47
+7. Copy the webhook signing secret, for example `whsec_...`.
48
+
49
+Enable Stripe Tax only after the Stripe account is configured for it.
50
+If it is enabled in shithub while Stripe is not ready, Checkout can fail
51
+before the user reaches payment.
52
+
53
+## Enable test mode
54
+
55
+Use Stripe test-mode keys first:
56
+
57
+```sh
58
+SHITHUB_BILLING__ENABLED=true
59
+SHITHUB_BILLING__GRACE_PERIOD=336h
60
+SHITHUB_BILLING__STRIPE__SECRET_KEY=sk_test_...
61
+SHITHUB_BILLING__STRIPE__WEBHOOK_SECRET=whsec_...
62
+SHITHUB_BILLING__STRIPE__TEAM_PRICE_ID=price_...
63
+SHITHUB_BILLING__STRIPE__AUTOMATIC_TAX=false
64
+```
65
+
66
+Then validate and restart:
67
+
68
+```sh
69
+shithubd config validate
70
+systemctl restart shithubd-web shithubd-worker
71
+```
72
+
73
+If `config validate` fails, fix the named key before restarting. The
74
+web process mounts billing routes only when billing is configured; the
75
+worker uses the same Stripe credentials for seat quantity sync.
76
+
77
+## Smoke test
78
+
79
+1. Sign in as an owner.
80
+2. Visit `/organizations/new`.
81
+3. Confirm the plan picker appears.
82
+4. Choose Team, create a test organization, and confirm redirect to
83
+   `/organizations/<org>/settings/billing?notice=team-created`.
84
+5. Click `Upgrade to Team`.
85
+6. Complete Stripe Checkout with a test card.
86
+7. Confirm Stripe redirects back to shithub.
87
+8. Confirm `/organizations/<org>/settings/billing` eventually shows:
88
+   - current plan: Team,
89
+   - subscription: Active,
90
+   - payment source: Stripe customer configured,
91
+   - a billable member count matching the org membership.
92
+9. Invite or remove a member and verify the worker updates the Stripe
93
+   subscription item quantity.
94
+
95
+If the UI remains Free after checkout, inspect webhook receipts and the
96
+web journal. The most likely causes are a wrong webhook secret, missing
97
+event subscription, or Stripe delivering events to the wrong host.
98
+
99
+## Go live
100
+
101
+1. Replace test-mode values with live-mode Stripe keys and Price ID.
102
+2. Re-run `shithubd config validate`.
103
+3. Restart web and worker.
104
+4. Create a low-risk real organization and complete checkout.
105
+5. Confirm a live invoice appears in Stripe and shithub.
106
+6. Confirm payouts are enabled in Stripe and the first payout schedule
107
+   is visible.
108
+
109
+Do not promise Enterprise, SAML, SCIM, compliance, or custom support
110
+from the billing UI until the matching product and operational support
111
+exist.
112
+
113
+## Incident checks
114
+
115
+| Symptom | First check |
116
+| --- | --- |
117
+| Plan picker missing | `billing.enabled` false or web not restarted. |
118
+| Checkout button 404s | billing routes were not mounted; validate Stripe config and restart web. |
119
+| Checkout creation fails | secret key, Team Price ID, Stripe Tax, or network reachability. |
120
+| Webhook returns 400 | wrong `billing.stripe.webhook_secret` or non-Stripe request. |
121
+| Subscription stays Free | missing subscription webhook events or unmapped customer/subscription metadata. |
122
+| Seat count stale | worker not running, seat-sync jobs failing, or Stripe key lacks Subscription Item update permission. |
123
+| Billing portal unavailable | the organization has no Stripe customer yet; start Checkout first. |
124
+
125
+## Rollback
126
+
127
+To pause paid onboarding without changing stored subscription state:
128
+
129
+1. Set `SHITHUB_BILLING__ENABLED=false`.
130
+2. Restart web and worker.
131
+
132
+Existing local billing rows remain in the database. Billing routes
133
+unmount, plan comparison links become disabled, and entitlement state
134
+continues to derive from the latest local billing projection. Handle any
135
+Stripe-side cancellations or refunds in the Stripe dashboard until the
136
+admin billing tools exist.