Configuration
The module reads its options from its entry in antelope.config.ts. Only stripe is required.
Options
| Option | Type | Default | Description |
|---|---|---|---|
stripe.secretKey | string | required | Stripe secret key. A placeholder key (sk_test_placeholder) disables every Stripe call. |
stripe.publishableKey | string | required | Stripe publishable key, sent to the browser for the card element. |
stripe.webhookSecret | string | required | Signing secret of the webhook endpoint targeting /api/saas/webhooks/stripe. |
stripe.readTimeoutMs | number | 4000 | How long a Stripe read a page renders (billing status, next invoice preview, VAT check) waits before the page renders without it. Writes keep the Stripe SDK's own timeout and retries. |
stripe.readMaxNetworkRetries | number | 0 | How many times such a read is retried on a network failure. |
admissionMode | "open" | "invitation-only" | "open" | invitation-only closes public registration; invitations keep working. |
registration.paymentMethod | "required" | "optional" | "none" | "required" | Whether public registration asks for a card. See Registration. |
publicScreens.register | boolean | true | false frees the register slug for your own page. |
publicScreens.pricing | boolean | true | false frees the pricing slug for your own page. |
allowedRedirectHosts | string[] | [] | Hosts accepted as Stripe Checkout and billing portal return URLs, matched on URL.host. Loopback hosts are accepted in development. |
defaultPlanSlug | string | first free plan | The free plan every workspace falls back to. See Default plan. |
upcomingInvoicePreviewCacheTtlSeconds | number | 3600 | How long the upcoming invoice preview is cached per workspace; 0 prices every request. |
reportingCurrency | string | "EUR" | ISO 4217 code the back office reports recurring revenue in. Other currencies are shown apart, never converted. |
The public options reach the browser through the frontend module options, under useDmsRuntimeConfig().public.dmsSaas (admissionMode, registrationPaymentMethod). Stripe secrets never do.
The frontend server needs DMS_AUTH_ESTABLISH_ENDPOINTS=/api/saas/register/finalize; see Installation.
Registration
Public registration is short: an account (name, e-mail, password) and the
acceptance of the terms, plus a card when the deployment asks for one. The
workspace it opens lands on the catalogue's first free plan — the lowest
order among active plans priced 0 and open to individuals — under a default
name its owner renames from the workspace settings. Customer type, billing
address, VAT number and plan choice belong to the upgrade flow. Registration
answers 409 saas.errors.plan.no_free_plan while the catalogue has no such
plan.
POST /api/saas/register holds the password to the DMS password policy
(isPasswordCompliant from @antelopejs/interface-dms/auth/password) before
it looks the e-mail up or creates anything, and answers
400 saas.errors.registration.password_policy otherwise. The OAuth completion
carries no password.
Two module options shape it:
{
"modules": {
"dms-saas": {
"config": {
"admissionMode": "open",
"registration": { "paymentMethod": "required" }
}
}
}
}
registration.paymentMethod decides whether registration asks for a card:
| Value | Behaviour |
|---|---|
required (default) | The card step is shown and must be completed. The workspace gets a Stripe customer holding that card and a free subscription — a local one while the free plan is off Stripe, which the upgrade checkout replaces on that customer — and the free-workspace-per-card cap applies when it is on. |
optional | The card step is shown, and the visitor may choose to add a card later. Without a card the workspace is card-less, as under none. |
none | The card step is never shown, GET /api/saas/register/setup-intent answers 400 saas.errors.registration.payment_method_disabled, and Stripe is never called: the workspace gets a local free subscription with no Stripe customer, which the upgrade checkout creates when the owner first pays. A card sent anyway is ignored. |
The free-workspace-per-card cap ("Free workspaces per card" in the operator
billing rules) is off by default (0): limiting free workspaces per person is
usually the consuming SaaS's own business rule. An operator may turn it on;
it then asks a card of every free workspace created in the app.
Under optional and none, a card-less workspace has no card fingerprint, so
the free-workspace-per-card cap does not apply to it: the only remaining limit
on free workspaces is one account per e-mail address.
The option covers both public entry points — POST /api/saas/register and the
auth/no-workspace completion — and nothing else: invitation sign-up never
asks for a plan or a card, whatever admissionMode and
registration.paymentMethod say. Tenant-side workspace creation keeps
requiring a card.
admissionMode: "invitation-only" closes public registration: every
/api/saas/register route answers 403 saas.errors.registration_closed before
looking at the card policy, the registration screens render a "registration by
invitation only" state instead of the form, and the login page drops its
sign-up link. Invitations keep working.
Both values reach the browser through the frontend module options
(useDmsRuntimeConfig().public.dmsSaas.admissionMode and
.registrationPaymentMethod); useSaasRegistration() already reads them.
The Stripe SetupIntent behind the card step is restricted to cards on the
server (allowed_payment_method_types: ["card"], since Stripe is retiring
payment_method_types on SetupIntent creation); the Payment Element is
created from its client secret alone, since Stripe refuses
paymentMethodTypes next to a clientSecret.
Default plan
A workspace is never without a plan. The default plan is the catalogue's
first free plan — the lowest order among active plans priced 0 and open to
individuals — unless defaultPlanSlug names another such plan:
{ "modules": { "dms-saas": { "config": { "defaultPlanSlug": "free" } } } }
It is what registration opens, and it is attached, as an active card-less
subscription, to every workspace that has no subscription at all — the
platform's default tenant and workspaces created outside dms-saas included.
The backfill runs at startup and with the billing-state recompute every 15
minutes, and the billing page covers a workspace on first read; each pass is
idempotent and never touches a workspace that holds any subscription. While
the catalogue has no free plan, nothing is attached and a warning is logged.
Once attached, the plan's permissions cap the workspace's members like any other plan (see Tenant owner permissions).