Settings → Billing
Purpose
Manage your CS Logbook subscription.
Your subscription lives on your personal account, and ONE subscription covers every clinic you own. You are billed as a person, not as a clinic. If you own three clinics, you do not have three subscriptions — you have one, and it covers all of them.
You are billed per DEA-registered veterinarian, and seats are identified by DEA number. That last part is the rule that surprises people most: the same veterinarian working at two of your clinics is ONE seat, not two. Clinics, locations, and non-vet staff (technicians, practice managers, assistants) are unlimited and free — adding a clinic or a technician costs nothing. Only DEA-registered veterinarians add cost.
Older versions of this page described "clinic billing" as the primary path and personal billing as a legacy fallback. That is backwards and has been corrected (2026-07-16). Personal-account billing is the only live path — see ADR-026 § Amendment — Model D.
CFR tie: none (operational concern, not a regulatory requirement). Subscription state does, however, gate functional access via RLS — every domain table's INSERT / UPDATE policy includes has_active_subscription(account_id) (or grace-period variant).
When to use it
- First subscription: start your subscription after the trial / onboarding wizard
- Change veterinarian count: add or remove DEA-registered veterinarian seats as your practice changes. Seats are counted by distinct DEA number across all your clinics — a vet at two of your clinics is one seat. When an action would add a seat, CS Logbook shows you the cost first and asks you to confirm — you are never billed for a seat you did not agree to.
- Update payment method: card on file expired or changed
- Cancel: pause or end subscription (records become read-only after grace period)
Adding a veterinarian seat — what you'll see
You don't add seats on this page — a seat is added when you add or edit a DEA-registered veterinarian on Settings → Staff. Because that changes what you pay, CS Logbook asks you to confirm the cost before it saves anything. Non-veterinarian staff (technicians, practice managers, assistants) never trigger this — they're free and unlimited.
When adding a veterinarian would add a billed seat, a confirmation dialog appears that tells you:
- the per-seat cost and billing interval (your $50/month or $500/year per veterinarian — see the plan table below);
- your current seat count and total, and the projected count and total after this vet;
- that the new seat is prorated for the rest of the current billing period.
Nothing is saved until you confirm. Choose Add veterinarian & seat to proceed, or cancel to write nothing. This is a billing confirmation, not a compliance block — like the soft warnings elsewhere in CS Logbook, it never prevents you from proceeding; it just makes sure you agreed to the charge first. Remember seats dedupe by DEA number, so adding a vet who already holds a seat at another of your clinics does not add a second seat and won't raise this dialog.
Walkthrough
Step 1 — Open the billing page
Click your avatar → Billing (/home/billing). This is your subscription — it covers every clinic
you own.
(The per-clinic route /home/<slug>/billing still exists in the app but is feature-flagged off under
Model D: enableTeamAccountBilling=false. There is no separate per-clinic subscription to manage.)
The page shows your current plan (if subscribed) or the plan selector (if not).

Step 2 — Select a plan (initial subscription)
Click a plan card (data-test-plan attribute on each). Click Proceed to checkout (data-test="checkout-submit-button").
You'll be redirected to the Stripe Checkout flow — a Stripe-hosted (or embedded-iframe) page where you enter your credit card and billing details.
(Stripe checkout boundary screenshot intentionally not captured by 15g: the iframe is JS-protected by Stripe and Playwright cannot drive it. Existing canonical coverage is apps/e2e/tests/team-billing/team-billing.spec.ts. The Sprint A verification records record this as the OPEN / UNVERIFIED close-out per .claude/rules/verification-discipline.md § 4.)
⚠️ Stripe Elements are JavaScript-protected iframes. The CS Logbook UI hands off to Stripe at this point; everything inside the Stripe checkout iframe is Stripe's UX. We can't drive it from automated tests — the verification-area-15g.md § Evidence of Attempt section explains how Sprint A's e2e spec captures evidence-of-arrival at the Stripe boundary without filling the iframe.
After successful payment, Stripe redirects back to /home/<slug>/billing/return. The CS Logbook return page polls for the webhook to confirm the subscription is active.
Step 3 — Manage subscription (after subscribing)
Once subscribed, the page shows a Manage Billing button (data-test="manage-billing-redirect-button"). Click it to be redirected to the Stripe customer portal — the portal is where you change payment method, download invoices, change plan, or cancel.
(Manage Billing screenshot covered by the existing apps/e2e/tests/team-billing/team-billing.spec.ts which exercises the full subscribe → return → manage portal flow.)
Step 4 — Plan change
Inside the Stripe customer portal, select Change plan. The portal handles proration and updates the CS Logbook subscription via webhook on confirmation.
Step 5 — Cancel
Inside the portal, select Cancel subscription. CS Logbook receives the cancellation webhook and marks the subscription as canceled at the period end. Read access continues during grace period (has_active_or_grace_subscription); write access ends at period end.
Field reference
Plan
CS Logbook Online is a single, pure per-seat plan (ADR-026):
| Plan | Price | Billed by | Trial |
|---|---|---|---|
| CS Logbook Online | $50/month or $500/year per DEA-registered veterinarian (annual = 2 months free) | Count of distinct DEA-registered (prescribing) veterinarians — app-user prescribing vets + no-login clinic-staff prescribers. Minimum 1. | 14-day free trial (card required; auto-converts) |
CS Logbook Online is the only plan. CS Logbook Sheets Edition was discontinued on 2026-08-06 and cannot be purchased; if you are reading an older guide that lists it, that guide is out of date.
There are no user tiers, no maxUsers caps, and no metered storage or overage. Clinics/locations and staff/members are unlimited and are not billed separately — only the count of DEA-registered veterinarians drives the price.
Non-refundable on both intervals; no mid-cycle credits. Adding a veterinarian prorates an immediate top-up; cancelling, switching annual → monthly, or reducing veterinarians takes effect at your next renewal (no proration or refund).
(Source: ADR-026 as amended by Model D — docs/cslogbook/architecture/decisions/026-pure-per-seat-billing.md, see its Amendment — Model D section. Cite it as amended: the base ADR predates the entity change and reading it bare invites the wrong conclusion that clinics are billed.)
Subscription state
subscriptions table columns (MakerKit standard, not user-facing):
status:trialing/active/past_due/canceled/unpaidperiod_starts_at/period_ends_atcancel_at_period_end(boolean)
has_active_subscription(account_id) returns true for status IN ('active', 'trialing').
has_active_or_grace_subscription(account_id) returns true for status IN ('active', 'trialing', 'past_due') AND period_ends_at > now().
Edge cases
Subscription state gates RLS
Most domain tables' INSERT / UPDATE policies require has_active_subscription(account_id). If your subscription lapses, you can still READ records (per audit-trail retention) but you cannot CREATE new ones until you re-subscribe.
Trial-to-paid transition
The first checkout converts trial → paid via the Stripe webhook. The clinic should not see write-blocking during the transition — trialing status satisfies has_active_subscription.
Webhook timing
Subscription state updates are webhook-driven. If the webhook is slow, the user may see "no active subscription" for a few seconds after returning from Stripe. The return page polls for ~10 seconds; refreshing usually resolves any UI lag.
Stripe customer portal vs. CS Logbook UI
Plan changes, payment method updates, invoice downloads, and cancellation all happen in the Stripe customer portal, not in CS Logbook. The portal is consistent with Stripe's UX globally; CS Logbook is the entry point.
Multiple subscription items per subscription
A subscription is a single per-seat line item whose quantity is the DEA-registered veterinarian count. The subscription_items[0].variant_id drives feature flag lookups; the item quantity drives the price. Any additional items are stored but not user-visible in the CS Logbook UI.
Sandbox vs. production keys
In dev (apps/web/.env.development + .env.local), Stripe runs against the sandbox account with publishable key pk_test_* + secret sk_test_*. In production, real keys. The webhook URL must match the environment.
Common mistakes
- Cancelling without exporting first. Cancellation triggers grace-period; records become read-only quickly. Run Settings → Data Export BEFORE cancellation.
- Assuming the same vet at two clinics is two seats. They're not — seats are deduped by DEA number across every clinic you own, so one veterinarian is one seat no matter how many of your clinics they work at. (This is also why recording a vet's DEA number matters: without it, CS Logbook can't tell that two roster entries are the same person, and will bill them twice.)
- Expecting a separate subscription per clinic. There isn't one. Your subscription lives on your personal account and covers every clinic you own.
- Assuming staff or clinics cost extra. They don't — clinics/locations and staff/members are unlimited and never billed. Only the count of DEA-registered veterinarians drives the price. Adding a prescribing veterinarian increases your seat count (and prorates a top-up); adding non-prescribing staff does not.
- Confusing test mode with prod. Stripe test cards (the canonical
4242…Visa test number documented at https://stripe.com/docs/testing) work in dev but never in prod. If you see "test card" errors, you're in production with a test card by mistake. - Treating the Manage Billing redirect as a UX bug. The redirect to the Stripe portal is intentional — Stripe is the payment / invoicing source of truth; CS Logbook does not duplicate that surface.
Related
- Existing e2e coverage —
apps/e2e/tests/team-billing/team-billing.spec.ts+user-billing.spec.tsexercise the Stripe checkout flow against the test harness - Settings → Data Export — export records before cancellation
- Settings → Members — members are unlimited and not billed; only DEA-registered veterinarians drive the price
- Glossary § Subscription gating