Stripe setup (operators)
Configure a Stripe account with the Maketools script (products, Plus and Pro prices, Stripe Tax, customer portal, webhook), the remaining manual steps, billing locally and Enterprise activation.
This page is for the team that runs Maketools. The billing service uses the official Stripe SDK:
Checkout, the customer portal and webhooks. A Stripe account is configured with the
scripts/billing/stripe-setup.ts script, then a few settings are made by hand in the Dashboard.
Use the test mode for staging, the live mode for production.
The setup script
# Dry run: lists what would be created or updated, writes nothing
STRIPE_SECRET_KEY=sk_test_… bun scripts/billing/stripe-setup.ts \
--base-url https://staging.example.com --dry-run
# Test mode (staging)
STRIPE_SECRET_KEY=sk_test_… bun scripts/billing/stripe-setup.ts --base-url https://staging.example.com
# Live mode (production): --live is required with a live key, and refused with a test key
STRIPE_SECRET_KEY=sk_live_… bun scripts/billing/stripe-setup.ts --base-url https://example.com --live
--base-url is the origin of the site (https, or http://localhost:<port> in test mode). The
script is idempotent: it compares each resource with what it should be and only writes the
difference; run again on an up-to-date account, it writes nothing. It ends with a summary (one
line per resource: created, updated, unchanged or to do by hand) and the list of the manual steps
it found. Detailed usage: scripts/billing/README.md.
It configures:
Two products,
maketools_plusandmaketools_pro(IDs set at creation,planmetadata), Stripe Tax codetxcd_10103001(software as a service, business use), unit "siège" (seat).Four prices, in EUR, excluding tax (exclusive tax behaviour), per unit (licensed, not metered): the quantity of the subscription is the number of seats, adjusted every day by the
billing-seat-syncjob (see Seats and active members).Lookup key Plan Amount per seat, excl. VAT maketools_plus_monthlyPlus €6 per month maketools_plus_yearlyPlus €60 per year maketools_pro_monthlyPro €10 per month maketools_pro_yearlyPro €100 per year Stripe Tax: head office (149 avenue du Maine, 75014 Paris) as the origin address, default tax code and tax-exclusive behaviour, and the French VAT registration. If Stripe Tax is not activated on the account yet, the script carries on and reports it as a manual step.
Customer portal: invoice history, payment method, billing details (address, email, name, phone, VAT number), cancellation at the end of the period without prorated refund, with the cancellation survey (too expensive, missing features, switched service, unused, customer service, too complex, low quality, other), and switching between the four prices, with the difference invoiced immediately. Changing the quantity is not offered: seats are managed by Maketools. The links shown are the French terms of sale and privacy policy (French is the authoritative version); the return link leads to
<site>/admin.Webhook
<site>/api/billing/stripe/webhook(through CloudFront: the API refuses calls that do not go through it), subscribed to exactly the events the backend handles and pinned to the same API version. Stripe only returns its signing secret (whsec_…) at creation: the script prints it once, with the command that stores it in the environment's secrets.
Amounts live only in Stripe and in the script's catalog (scripts/billing/stripe-catalog.ts, the
source the script pushes); a test checks that the prices shown by the site match this catalog.
The backend knows no Price ID: it finds the plan of a subscription from the lookup key of its
price, the same in test and live mode. A subscription on a price with neither one of these four keys
nor the maketools_plus or maketools_pro product grants nothing (billing_unknown_price alert).
Changing a price
A Stripe price cannot be edited. After an amount changes in the catalog, the script creates a new
price that takes over the lookup key, then deactivates the old one. Current subscriptions stay
on the old price: it no longer has a key, but the backend still recognises their plan from the
Stripe product (maketools_plus or maketools_pro); they pay the old amount until they are
moved. The script's summary lists them; move them to the new price (Stripe Dashboard,
subscription, "Update subscription", without proration: the new amount applies at renewal). An
increase is notified to customers at least 30 days before the renewal it applies to
(terms of sale, section 5.4): run the script once that notice has
been given, and update the prices shown by the site at the same time.
Manual steps
To do once per account, in the Stripe Dashboard:
- Activate the account: identity verification (KYC) and payout bank account.
- Public business details: company name MAKETOOLS SAS, SIREN 990 247 603 (RCS Paris), VAT number FR63 990 247 603, head office address, support email.
- Branding of Checkout, the portal and invoices: logo, icon, colours.
- Invoices: numbering prefix, and a default invoice footer carrying the mandatory business-to-business terms in France: late payment penalties at the ECB rate plus 10 points, a fixed €40 recovery fee (articles L. 441-10 and D. 441-5 of the French Commercial Code), no discount for early payment. The backend also sets this footer on every Stripe customer it creates; the account setting covers the other cases.
- Stripe Tax, if the script reported it: activate it, then run the script again.
- Unpaid invoices: Smart Retries on, and "mark the subscription as unpaid" after the last retry.
- Backend key: a restricted key rather than the secret key (permissions below).
Environment secrets
| Secret | Value |
|---|---|
StripeSecretKey |
backend key (sk_… or, recommended, a restricted rk_… key) |
StripeWebhookSecret |
signing secret of the webhook (whsec_…), printed by the script when it creates it |
In staging and production they are kept in AWS Secrets Manager (self-hosting on AWS), never in
the code or in the images. Set the values: bun run infra:secrets <env> --set StripeSecretKey,
then --set StripeWebhookSecret (value read from standard input; deploy/secrets.md). There is
no Price ID to store any more.
Permissions of a restricted key:
- backend: write on Customers, Checkout Sessions, Customer portal, Subscriptions and Invoices, read on Prices and Refunds;
- setup script (a separate key, kept out of the backend secrets): write on Products, Prices, Tax settings, Tax registrations, Customer portal (configurations) and Webhook endpoints.
Checkout and customer portal sessions are limited to 10 per hour per organization, re-syncs from Stripe to 20 per hour, and next-invoice previews to 60 per hour (cached for one minute).
Local development
Without StripeSecretKey (the default of deploy/env/local.env.example), billing is disabled
cleanly: every organization is Free, the Checkout buttons explain that Stripe is not configured,
and the webhook answers ERR_BILLING_NOT_CONFIGURED.
To force an organization out of Free locally, activate Enterprise with the admin script (below).
To try the real flow in test mode:
- configure a test account with
--base-url http://localhost:4200(the script creates no webhook, as Stripe cannot reach localhost); - run
stripe listen --forward-to localhost:4000/api/billing/stripe/webhook, which prints awhsec_…secret; - put the test key and this secret in
deploy/env/local.env(never committed), then run./deploy/set-local-secrets.sh.
Activate Enterprise
Enterprise is activated by a platform administrator through the internal API
billing.setEnterprisePlan, with the script scripts/admin/enterprise.ts (explicit confirmation:
retype the organization slug; named operator; audited as billing.enterprise.granted or
billing.enterprise.revoked). Locally, from backend/:
bun ../scripts/encore.ts exec -- \
bun ../scripts/admin/enterprise.ts grant acme --operator "Jane Ops" --reason "Quote 2026-042"
In staging and production, activate it from the organization's page in the operations console
(bun run console), which calls the same internal API through a signed request, under the name
of the operator key. If the organization had a Stripe subscription, cancel it in the Stripe
dashboard.
Data kept by Maketools
Stripe customer and subscription IDs, status, period, seats, unit amount (excluding tax) and currency of the price, start, cancellation request and end dates, the reason picked in the cancellation survey (never the free-text comment), and the IDs of processed webhook events (idempotency). For Maketools' internal statistics, a payment register: for each subscription invoice (paid or failed) and each refund, its Stripe ID, the organization, the amount excluding tax, the tax, the currency, the plan and the date. No card number, bank detail or billing address: Stripe is a subprocessor and keeps them. Everything is erased with the organization.