@antelopejs/dms-saas 0.1.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +262 -13
- package/dist/auth/subscription-access-gate.d.ts +1 -1
- package/dist/billing-state/index.d.ts +1 -0
- package/dist/billing-state/index.js +1 -0
- package/dist/billing-state/index.js.map +1 -1
- package/dist/billing-state/past-due-banner.d.ts +4 -0
- package/dist/billing-state/past-due-banner.js +25 -0
- package/dist/billing-state/past-due-banner.js.map +1 -0
- package/dist/config/runtime.d.ts +6 -1
- package/dist/config/runtime.js +50 -1
- package/dist/config/runtime.js.map +1 -1
- package/dist/crons/recompute-billing-state.js +4 -1
- package/dist/crons/recompute-billing-state.js.map +1 -1
- package/dist/data-api/platformOwner/index.d.ts +1 -0
- package/dist/data-api/platformOwner/index.js +1 -0
- package/dist/data-api/platformOwner/index.js.map +1 -1
- package/dist/data-api/platformOwner/invitations.d.ts +22 -0
- package/dist/data-api/platformOwner/invitations.js +121 -0
- package/dist/data-api/platformOwner/invitations.js.map +1 -0
- package/dist/data-api/platformOwner/workspaces.d.ts +3 -0
- package/dist/data-api/platformOwner/workspaces.js +64 -12
- package/dist/data-api/platformOwner/workspaces.js.map +1 -1
- package/dist/data-api/tenant/tenant-invoices.js +2 -10
- package/dist/data-api/tenant/tenant-invoices.js.map +1 -1
- package/dist/hooks/register.js +2 -1
- package/dist/hooks/register.js.map +1 -1
- package/dist/implementations/dms-saas/billing.d.ts +2 -1
- package/dist/implementations/dms-saas/billing.js +6 -0
- package/dist/implementations/dms-saas/billing.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/invoice-line-items/injection.d.ts +10 -0
- package/dist/invoice-line-items/injection.js +7 -3
- package/dist/invoice-line-items/injection.js.map +1 -1
- package/dist/notifications/subjects.d.ts +19 -19
- package/dist/operator-actions/index.d.ts +1 -0
- package/dist/operator-actions/index.js +1 -0
- package/dist/operator-actions/index.js.map +1 -1
- package/dist/operator-actions/invitation-commands.d.ts +12 -0
- package/dist/operator-actions/invitation-commands.js +46 -0
- package/dist/operator-actions/invitation-commands.js.map +1 -0
- package/dist/operator-actions/types.d.ts +1 -1
- package/dist/pages/platform/categories.d.ts +6 -6
- package/dist/pages/platform/credit-notes.d.ts +1 -1
- package/dist/pages/platform/dashboard.d.ts +2 -2
- package/dist/pages/platform/dashboard.js +2 -4
- package/dist/pages/platform/dashboard.js.map +1 -1
- package/dist/pages/platform/features.d.ts +1 -1
- package/dist/pages/platform/invoices.d.ts +1 -1
- package/dist/pages/platform/plans/edit.d.ts +2 -2
- package/dist/pages/platform/plans/index.d.ts +1 -1
- package/dist/pages/platform/plans/migrations.d.ts +1 -1
- package/dist/pages/platform/segments/index.d.ts +1 -1
- package/dist/pages/platform/settings.d.ts +1 -1
- package/dist/pages/platform/users/detail.d.ts +1 -1
- package/dist/pages/platform/users/index.d.ts +1 -1
- package/dist/pages/platform/workspaces/detail.d.ts +1 -1
- package/dist/pages/platform/workspaces/detail.js +40 -1
- package/dist/pages/platform/workspaces/detail.js.map +1 -1
- package/dist/pages/platform/workspaces/index.d.ts +4 -10
- package/dist/pages/platform/workspaces/index.js +27 -111
- package/dist/pages/platform/workspaces/index.js.map +1 -1
- package/dist/pages/public/legal/privacy-policy.js +2 -1
- package/dist/pages/public/legal/privacy-policy.js.map +1 -1
- package/dist/pages/public/legal/terms-and-conditions.js +2 -1
- package/dist/pages/public/legal/terms-and-conditions.js.map +1 -1
- package/dist/pages/public/legal/terms-of-use.js +2 -1
- package/dist/pages/public/legal/terms-of-use.js.map +1 -1
- package/dist/pages/tenant/billing.d.ts +1 -1
- package/dist/plans/index.d.ts +1 -0
- package/dist/plans/index.js +1 -0
- package/dist/plans/index.js.map +1 -1
- package/dist/plans/platform-support.d.ts +2 -0
- package/dist/plans/platform-support.js +19 -0
- package/dist/plans/platform-support.js.map +1 -0
- package/dist/plans/seat-capacity.d.ts +9 -2
- package/dist/plans/seat-capacity.js +59 -12
- package/dist/plans/seat-capacity.js.map +1 -1
- package/dist/plans/seat-hooks.js +34 -13
- package/dist/plans/seat-hooks.js.map +1 -1
- package/dist/routes/platformOwner/billing-settings.d.ts +2 -2
- package/dist/routes/platformOwner/dashboard.d.ts +8 -6
- package/dist/routes/platformOwner/dashboard.js +31 -17
- package/dist/routes/platformOwner/dashboard.js.map +1 -1
- package/dist/routes/platformOwner/index.d.ts +1 -0
- package/dist/routes/platformOwner/index.js +1 -0
- package/dist/routes/platformOwner/index.js.map +1 -1
- package/dist/routes/platformOwner/plan-deletion.d.ts +1 -1
- package/dist/routes/platformOwner/plans.d.ts +5 -5
- package/dist/routes/platformOwner/segments.d.ts +1 -1
- package/dist/routes/platformOwner/workspace-invitations.d.ts +13 -0
- package/dist/routes/platformOwner/workspace-invitations.js +73 -0
- package/dist/routes/platformOwner/workspace-invitations.js.map +1 -0
- package/dist/routes/public/legal-documents.d.ts +2 -2
- package/dist/routes/public/register.d.ts +7 -3
- package/dist/routes/public/register.js +42 -27
- package/dist/routes/public/register.js.map +1 -1
- package/dist/routes/tenant/tenant-billing.d.ts +7 -11
- package/dist/routes/tenant/tenant-billing.js +34 -26
- package/dist/routes/tenant/tenant-billing.js.map +1 -1
- package/dist/routes/tenant/tenant-plan.d.ts +1 -0
- package/dist/routes/tenant/tenant-plan.js +27 -2
- package/dist/routes/tenant/tenant-plan.js.map +1 -1
- package/dist/routes/tenant/tenant-seats.d.ts +2 -4
- package/dist/routes/tenant/tenant-seats.js.map +1 -1
- package/dist/routes/tenant/workspaces.d.ts +3 -1
- package/dist/routes/tenant/workspaces.js +10 -4
- package/dist/routes/tenant/workspaces.js.map +1 -1
- package/dist/stripe/upcoming-invoice.d.ts +21 -0
- package/dist/stripe/upcoming-invoice.js +42 -0
- package/dist/stripe/upcoming-invoice.js.map +1 -0
- package/dist/stripe/webhook-dispatch.js +2 -0
- package/dist/stripe/webhook-dispatch.js.map +1 -1
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -1
- package/dist/upcoming-invoice/db/preview-cache.model.d.ts +38 -0
- package/dist/upcoming-invoice/db/preview-cache.model.js +57 -0
- package/dist/upcoming-invoice/db/preview-cache.model.js.map +1 -0
- package/dist/upcoming-invoice/db/preview-cache.table.d.ts +10 -0
- package/dist/upcoming-invoice/db/preview-cache.table.js +42 -0
- package/dist/upcoming-invoice/db/preview-cache.table.js.map +1 -0
- package/dist/upcoming-invoice/eligibility.d.ts +17 -0
- package/dist/upcoming-invoice/eligibility.js +52 -0
- package/dist/upcoming-invoice/eligibility.js.map +1 -0
- package/dist/upcoming-invoice/invalidation.d.ts +2 -0
- package/dist/upcoming-invoice/invalidation.js +39 -0
- package/dist/upcoming-invoice/invalidation.js.map +1 -0
- package/dist/upcoming-invoice/mapping.d.ts +8 -0
- package/dist/upcoming-invoice/mapping.js +82 -0
- package/dist/upcoming-invoice/mapping.js.map +1 -0
- package/dist/upcoming-invoice/outcomes.d.ts +13 -0
- package/dist/upcoming-invoice/outcomes.js +15 -0
- package/dist/upcoming-invoice/outcomes.js.map +1 -0
- package/dist/upcoming-invoice/preview.d.ts +3 -0
- package/dist/upcoming-invoice/preview.js +80 -0
- package/dist/upcoming-invoice/preview.js.map +1 -0
- package/dist/upcoming-invoice/pricing.d.ts +3 -0
- package/dist/upcoming-invoice/pricing.js +84 -0
- package/dist/upcoming-invoice/pricing.js.map +1 -0
- package/dist/upcoming-invoice/usage.d.ts +14 -0
- package/dist/upcoming-invoice/usage.js +53 -0
- package/dist/upcoming-invoice/usage.js.map +1 -0
- package/dist/utils/invoice-lines-type.d.ts +1 -1
- package/dist/workspaces/billing-identity.d.ts +19 -0
- package/dist/workspaces/billing-identity.js +51 -0
- package/dist/workspaces/billing-identity.js.map +1 -0
- package/dist/workspaces/default-plan.d.ts +5 -0
- package/dist/workspaces/default-plan.js +109 -0
- package/dist/workspaces/default-plan.js.map +1 -0
- package/dist/workspaces/index.d.ts +2 -0
- package/dist/workspaces/index.js +2 -0
- package/dist/workspaces/index.js.map +1 -1
- package/dist/workspaces/invitations.d.ts +26 -0
- package/dist/workspaces/invitations.js +112 -0
- package/dist/workspaces/invitations.js.map +1 -0
- package/dist/workspaces/provisioning.d.ts +3 -3
- package/dist/workspaces/provisioning.js +39 -11
- package/dist/workspaces/provisioning.js.map +1 -1
- package/frontend-vue/app/components/BillingIdentityFields.vue +179 -0
- package/frontend-vue/app/components/PastDueBanner.vue +81 -0
- package/frontend-vue/app/components/PaymentMethodCard.vue +94 -38
- package/frontend-vue/app/components/PlanComparisonModal.vue +62 -26
- package/frontend-vue/app/components/PlanUpgradeModal.vue +181 -0
- package/frontend-vue/app/components/PlatformNotes.vue +4 -0
- package/frontend-vue/app/components/RefundButton.vue +10 -1
- package/frontend-vue/app/components/Register.vue +49 -92
- package/frontend-vue/app/components/SeatQuotaBanner.vue +28 -0
- package/frontend-vue/app/components/TenantBillingForm.vue +52 -128
- package/frontend-vue/app/components/TenantPlanCard.vue +68 -10
- package/frontend-vue/app/components/UserSummaryCard.vue +12 -10
- package/frontend-vue/app/components/WorkspaceAdminCreateModal.vue +202 -0
- package/frontend-vue/app/components/WorkspaceCreateModal.vue +46 -9
- package/frontend-vue/app/components/WorkspaceInvitationLink.vue +106 -0
- package/frontend-vue/app/components/WorkspaceSummaryCard.vue +39 -15
- package/frontend-vue/app/components/WorkspaceSuspended.vue +3 -5
- package/frontend-vue/app/components/WorkspaceSwitcherWidget.vue +8 -5
- package/frontend-vue/app/composables/useBillingCountries.ts +25 -5
- package/frontend-vue/app/composables/useBillingIdentity.ts +141 -0
- package/frontend-vue/app/composables/useInvitationLink.ts +69 -0
- package/frontend-vue/app/composables/usePastDueBannerAction.ts +20 -0
- package/frontend-vue/app/composables/usePlanComparison.ts +15 -0
- package/frontend-vue/app/composables/usePlanFeatureFormat.ts +312 -20
- package/frontend-vue/app/composables/usePlanFeatureLabel.ts +24 -0
- package/frontend-vue/app/composables/useSaasRegistration.ts +110 -223
- package/frontend-vue/app/composables/useSeatQuota.ts +8 -0
- package/frontend-vue/app/composables/useStripePaymentElement.ts +3 -3
- package/frontend-vue/app/composables/useWorkspaceAccessCache.ts +29 -2
- package/frontend-vue/app/composables/useWorkspaceFinalization.ts +7 -32
- package/frontend-vue/app/custom-pages/auth/no-workspace.vue +49 -194
- package/frontend-vue/app/plugins/auth-links.ts +0 -4
- package/frontend-vue/app/types/runtime-config.ts +13 -0
- package/frontend-vue/dms.frontend.ts +10 -4
- package/frontend-vue/i18n/locales/saas-en-GB.json +161 -36
- package/frontend-vue/i18n/locales/saas-fr-FR.json +162 -37
- package/frontend-vue/package.json +0 -1
- package/frontend-vue/pnpm-lock.yaml +1 -125
- package/package.json +15 -15
- package/frontend-vue/app/middleware/workspace-suspended.global.ts +0 -59
package/README.md
CHANGED
|
@@ -32,14 +32,17 @@ workspace. From that directory run `pnpm install`,
|
|
|
32
32
|
`pnpm test:frontend`. The frontend check generates a temporary workspace, then
|
|
33
33
|
builds client, SSR, and email bundles and runs Vue typechecking. To run the
|
|
34
34
|
playground frontend during backend development, use `pnpm frontend:dev`; this
|
|
35
|
-
dispatches through the core CLI as `ajs dms dev`.
|
|
35
|
+
dispatches through the core CLI as `ajs dms dev`. The playground connects to
|
|
36
|
+
MongoDB at `mongodb://localhost:27017` unless `MONGO_URL` is set, either in the
|
|
37
|
+
environment or in `playground/.env`.
|
|
36
38
|
|
|
37
39
|
## Completing a registration that entered without a workspace
|
|
38
40
|
|
|
39
41
|
An account can reach the product before it owns anything to log into: an OAuth
|
|
40
42
|
sign-in for an unknown e-mail, or any entry point the core answers with
|
|
41
43
|
`requires_tenant_assignment`. The `auth/no-workspace` page is where that
|
|
42
|
-
account
|
|
44
|
+
account opens its first workspace — on the free plan, with a card when
|
|
45
|
+
`registration.paymentMethod` asks for one — and
|
|
43
46
|
`POST /api/saas/register/finalize` provisions it and answers with a token pair
|
|
44
47
|
for the workspace it just created, so the visitor lands signed in instead of
|
|
45
48
|
back on the login screen.
|
|
@@ -57,8 +60,8 @@ declare the finalize endpoint in the frontend server's environment:
|
|
|
57
60
|
DMS_AUTH_ESTABLISH_ENDPOINTS=/api/saas/register/finalize
|
|
58
61
|
```
|
|
59
62
|
|
|
60
|
-
Without it the loader answers `403` and a visitor who has
|
|
61
|
-
out. A replacement completion screen keeps the same requirement, and can build
|
|
63
|
+
Without it the loader answers `403` and a visitor who has just registered stays
|
|
64
|
+
signed out. A replacement completion screen keeps the same requirement, and can build
|
|
62
65
|
its request with the auto-imported `buildSessionEstablishRequest()` helper
|
|
63
66
|
rather than restating the endpoint and the payload shape.
|
|
64
67
|
|
|
@@ -71,6 +74,11 @@ permissions, regardless of assigned roles. Permissions outside the plan are
|
|
|
71
74
|
removed even when a role grants them. Other members retain only the intersection
|
|
72
75
|
of their incoming permissions and the resolved plan.
|
|
73
76
|
|
|
77
|
+
A plan grants exactly the ids it lists, nothing nested under them: the plan
|
|
78
|
+
editor's permission tree adds a node's descendants and ancestors when it is
|
|
79
|
+
ticked, so plans built from the UI list every component id, and plans seeded by
|
|
80
|
+
code must list them too.
|
|
81
|
+
|
|
74
82
|
Tenant ownership never grants `*`, even if a plan contains it. An incoming `*`
|
|
75
83
|
(including the DMS platform owner's wildcard) retains the existing behavior:
|
|
76
84
|
the wildcard survives, and other incoming permissions are intersected with the
|
|
@@ -129,6 +137,164 @@ accepted, each is promoted before the tenant message is persisted, and failed
|
|
|
129
137
|
writes move promoted files back to staging. Ticket-specific metadata routes
|
|
130
138
|
verify that a key belongs to the requested thread before issuing its read URL.
|
|
131
139
|
|
|
140
|
+
## Registration
|
|
141
|
+
|
|
142
|
+
Public registration is short: an account (name, e-mail, password) and the
|
|
143
|
+
acceptance of the terms, plus a card when the deployment asks for one. The
|
|
144
|
+
workspace it opens lands on the catalogue's first free plan — the lowest
|
|
145
|
+
`order` among active plans priced 0 and open to individuals — under a default
|
|
146
|
+
name its owner renames from the workspace settings. Customer type, billing
|
|
147
|
+
address, VAT number and plan choice belong to the upgrade flow. Registration
|
|
148
|
+
answers `409 saas.errors.plan.no_free_plan` while the catalogue has no such
|
|
149
|
+
plan.
|
|
150
|
+
|
|
151
|
+
`POST /api/saas/register` holds the password to the DMS password policy
|
|
152
|
+
(`isPasswordCompliant` from `@antelopejs/interface-dms/auth/password`) before
|
|
153
|
+
it looks the e-mail up or creates anything, and answers
|
|
154
|
+
`400 saas.errors.registration.password_policy` otherwise. The OAuth completion
|
|
155
|
+
carries no password.
|
|
156
|
+
|
|
157
|
+
Two module options shape it:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"modules": {
|
|
162
|
+
"dms-saas": {
|
|
163
|
+
"config": {
|
|
164
|
+
"admissionMode": "open",
|
|
165
|
+
"registration": { "paymentMethod": "required" }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`registration.paymentMethod` decides whether registration asks for a card:
|
|
173
|
+
|
|
174
|
+
| Value | Behaviour |
|
|
175
|
+
| --- | --- |
|
|
176
|
+
| `required` (default) | The card step is shown and must be completed. The workspace gets a Stripe customer and a free subscription on that card, and the free-workspace-per-card cap applies. |
|
|
177
|
+
| `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`. |
|
|
178
|
+
| `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. |
|
|
179
|
+
|
|
180
|
+
Under `optional` and `none`, a card-less workspace has no card fingerprint, so
|
|
181
|
+
the free-workspace-per-card cap does not apply to it: the only remaining limit
|
|
182
|
+
on free workspaces is one account per e-mail address.
|
|
183
|
+
|
|
184
|
+
The option covers both public entry points — `POST /api/saas/register` and the
|
|
185
|
+
`auth/no-workspace` completion — and nothing else: invitation sign-up never
|
|
186
|
+
asks for a plan or a card, whatever `admissionMode` and
|
|
187
|
+
`registration.paymentMethod` say. Tenant-side workspace creation keeps
|
|
188
|
+
requiring a card.
|
|
189
|
+
|
|
190
|
+
`admissionMode: "invitation-only"` closes public registration: every
|
|
191
|
+
`/api/saas/register` route answers `403 saas.errors.registration_closed` before
|
|
192
|
+
looking at the card policy, the registration screens render a "registration by
|
|
193
|
+
invitation only" state instead of the form, and the login page drops its
|
|
194
|
+
sign-up link. Invitations keep working.
|
|
195
|
+
|
|
196
|
+
Both values reach the browser through the frontend module options
|
|
197
|
+
(`useDmsRuntimeConfig().public.dmsSaas.admissionMode` and
|
|
198
|
+
`.registrationPaymentMethod`); `useSaasRegistration()` already reads them.
|
|
199
|
+
|
|
200
|
+
The Stripe SetupIntent behind the card step is restricted to cards on the
|
|
201
|
+
server (`payment_method_types: ["card"]`); the Payment Element is created from
|
|
202
|
+
its client secret alone, since Stripe refuses `paymentMethodTypes` next to a
|
|
203
|
+
`clientSecret`.
|
|
204
|
+
|
|
205
|
+
## Default plan
|
|
206
|
+
|
|
207
|
+
A workspace is never without a plan. The default plan is the catalogue's
|
|
208
|
+
first free plan — the lowest `order` among active plans priced 0 and open to
|
|
209
|
+
individuals — unless `defaultPlanSlug` names another such plan:
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{ "modules": { "dms-saas": { "config": { "defaultPlanSlug": "free" } } } }
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
It is what registration opens, and it is attached, as an active card-less
|
|
216
|
+
subscription, to every workspace that has no subscription at all — the
|
|
217
|
+
platform's `default` tenant and workspaces created outside dms-saas included.
|
|
218
|
+
The backfill runs at startup and with the billing-state recompute every 15
|
|
219
|
+
minutes, and the billing page covers a workspace on first read; each pass is
|
|
220
|
+
idempotent and never touches a workspace that holds any subscription. While
|
|
221
|
+
the catalogue has no free plan, nothing is attached and a warning is logged.
|
|
222
|
+
|
|
223
|
+
Once attached, the plan's permissions cap the workspace's members like any
|
|
224
|
+
other plan (see [Tenant owner permissions](#tenant-owner-permissions)).
|
|
225
|
+
|
|
226
|
+
## Plan feature labels and values
|
|
227
|
+
|
|
228
|
+
A feature row stores one display name and one tooltip. The tenant plan pages
|
|
229
|
+
(the plan card summary and the plan comparison table) render both through the
|
|
230
|
+
DMS display string convention: a value starting with `$` is an i18n key,
|
|
231
|
+
anything else is shown as written. A module declaring features either stores
|
|
232
|
+
plain text or stores `$<key>` and ships that key in its own frontend locale
|
|
233
|
+
files (`frontend-vue/i18n/locales/<name>-<locale>.json`):
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
{
|
|
237
|
+
displayName: "$cloud.plan_features.egress.label",
|
|
238
|
+
tooltip: "$cloud.plan_features.egress.tooltip",
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
A key missing in the viewer's locale falls back to the fallback locale; a
|
|
243
|
+
tooltip key missing everywhere hides the tooltip.
|
|
244
|
+
|
|
245
|
+
Values are formatted from the feature's `valueType` and `unit`: `-1` reads as
|
|
246
|
+
unlimited, booleans as ✓/—, numbers are grouped in the viewer's locale. A
|
|
247
|
+
`per <unit>` unit makes the value a price in the plan's currency, and
|
|
248
|
+
`currency units` an amount of it. Known units are scaled to something a person
|
|
249
|
+
reads at a glance; any other unit is shown verbatim after the grouped number.
|
|
250
|
+
A text value follows the same convention as labels: a `$<key>` value is
|
|
251
|
+
translated in the declaring module's locales, anything else is shown as stored.
|
|
252
|
+
|
|
253
|
+
| Stored unit | Quantity reads as | `per <unit>` price reads as |
|
|
254
|
+
|-------------|-------------------|-----------------------------|
|
|
255
|
+
| `byte(s)` | bytes, KB, MB, GB or TB (powers of 1000) | per GB |
|
|
256
|
+
| `minute(s)` | minutes | per minute |
|
|
257
|
+
|
|
258
|
+
A module declaring features in its own units teaches the plan pages how they
|
|
259
|
+
read with `registerPlanFeatureUnit`, auto-imported like the other dms-saas
|
|
260
|
+
composables. Call it from a frontend plugin that runs on the server and in the
|
|
261
|
+
browser, so server-rendered and hydrated values agree. Each scale says how
|
|
262
|
+
many stored units it holds; a quantity takes the largest scale it fills, and a
|
|
263
|
+
`per <unit>` price reads per `priceScale`. Labels are full i18n keys the module
|
|
264
|
+
ships in its own locales, each a node holding a `quantity` message
|
|
265
|
+
(`{value}`, pluralised on the scaled value) and a `price` message (`{price}`):
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
// frontend-vue/app/plugins/plan-feature-units.ts
|
|
269
|
+
const MINUTES_PER_HOUR = 60;
|
|
270
|
+
const vcpuHour = { label: "cloud.plan.units.vcpu_hour", size: MINUTES_PER_HOUR };
|
|
271
|
+
|
|
272
|
+
export default defineDmsPlugin(() => {
|
|
273
|
+
registerPlanFeatureUnit(["vCPU-minute", "vCPU-minutes"], {
|
|
274
|
+
scales: [vcpuHour],
|
|
275
|
+
priceScale: vcpuHour,
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"cloud": {
|
|
283
|
+
"plan": {
|
|
284
|
+
"units": {
|
|
285
|
+
"vcpu_hour": {
|
|
286
|
+
"quantity": "{value} vCPU-hour | {value} vCPU-hours",
|
|
287
|
+
"price": "{price} / vCPU-hour"
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Units match case-insensitively, and a registered unit takes precedence over a
|
|
296
|
+
built-in one of the same name.
|
|
297
|
+
|
|
132
298
|
## Extension points
|
|
133
299
|
|
|
134
300
|
### Consuming this module
|
|
@@ -142,7 +308,7 @@ boots the registration graph:
|
|
|
142
308
|
| Subpath | Surface |
|
|
143
309
|
| --- | --- |
|
|
144
310
|
| `@antelopejs/interface-dms-saas` | every extension point except `db`: billing, data API, pages, plans, provisioning and registration flat, plus the `invoiceLineItems` and `workspaceLifecycle` namespaces |
|
|
145
|
-
| `@antelopejs/interface-dms-saas/billing` | customer balance and complimentary subscription state |
|
|
311
|
+
| `@antelopejs/interface-dms-saas/billing` | customer balance, upcoming invoice preview and complimentary subscription state |
|
|
146
312
|
| `@antelopejs/interface-dms-saas/data-api` | hidden-value data API filters |
|
|
147
313
|
| `@antelopejs/interface-dms-saas/db` | canonical SaaS tables and data models |
|
|
148
314
|
| `@antelopejs/interface-dms-saas/hidden-filter` | the `HiddenStringFilter` decorator on its own |
|
|
@@ -197,19 +363,31 @@ races the bundled one instead of overriding it:
|
|
|
197
363
|
|
|
198
364
|
Then register your own page on the `register` slug and build it on the
|
|
199
365
|
`useSaasRegistration()` composable the Vue adapter auto-imports. It holds the
|
|
200
|
-
whole flow —
|
|
201
|
-
|
|
202
|
-
|
|
366
|
+
whole flow — the card policy, the Stripe setup intent and card confirmation,
|
|
367
|
+
the order the requirements are validated in, the provisioning call and the
|
|
368
|
+
landing route — and renders nothing, so a re-themed screen keeps the same
|
|
369
|
+
steps:
|
|
203
370
|
|
|
204
371
|
```vue
|
|
205
372
|
<script setup lang="ts">
|
|
206
|
-
const {
|
|
207
|
-
|
|
373
|
+
const {
|
|
374
|
+
form, // name, email, password, hasAcceptedLegal, skipsPaymentMethod
|
|
375
|
+
paymentElementId,
|
|
376
|
+
canSkipPaymentMethod, // true under `optional`
|
|
377
|
+
isPaymentStepVisible, // bind with v-show so the Stripe element stays mounted
|
|
378
|
+
passwordStrength, // { strength, score, color } for <DmsPasswordStrength>
|
|
379
|
+
isRegistrationClosed, // render the invitation-only state instead of the form
|
|
380
|
+
errorMessage,
|
|
381
|
+
isSubmitting,
|
|
382
|
+
submit,
|
|
383
|
+
} = useSaasRegistration({ redirectTo: "/welcome" });
|
|
208
384
|
</script>
|
|
209
385
|
```
|
|
210
386
|
|
|
211
387
|
Nothing it returns throws: a failure surfaces as a translated `errorMessage`,
|
|
212
|
-
and `submit()` resolves to the new tenant id or `null`.
|
|
388
|
+
and `submit()` resolves to the new tenant id or `null`. A password outside the
|
|
389
|
+
DMS policy stops `submit()` before the card is confirmed. The workspace name is
|
|
390
|
+
the localised `saas.register.default_workspace_name`. Every screen stays
|
|
213
391
|
enabled by default, so an existing project needs no configuration change.
|
|
214
392
|
|
|
215
393
|
### Registration extras and the provisioning hook
|
|
@@ -352,6 +530,77 @@ export async function stop(): Promise<void> {
|
|
|
352
530
|
why it is only reported at error level once dms-saas is attached. Two modules
|
|
353
531
|
registering the same `id` before that collapse into one silently, so pick an
|
|
354
532
|
`id` no other module could plausibly choose.
|
|
533
|
+
- Resolvers are also called to price the [upcoming invoice
|
|
534
|
+
preview](#upcoming-invoice-preview). Such a call carries `isPreview: true`,
|
|
535
|
+
a synthetic `upcoming_<subscription id>` `invoiceId`, and a `periodEnd` set
|
|
536
|
+
to the time of the request: the running cycle's usage so far. Its lines are
|
|
537
|
+
only quoted to Stripe, never invoiced. A resolver that throws or returns an
|
|
538
|
+
invalid line then makes the preview `unavailable` rather than short.
|
|
539
|
+
|
|
540
|
+
### Upcoming invoice preview
|
|
541
|
+
|
|
542
|
+
`GetUpcomingInvoicePreview` returns what Stripe will bill the current
|
|
543
|
+
workspace next, tax included, so a page can show the exact VAT and total before
|
|
544
|
+
the invoice exists. Stripe computes every figure (Stripe Tax: country, VAT
|
|
545
|
+
number, reverse charge); dms-saas copies them without recomputing any.
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
import { GetUpcomingInvoicePreview } from "@antelopejs/interface-dms-saas/billing";
|
|
549
|
+
|
|
550
|
+
const preview = await GetUpcomingInvoicePreview({ tenantId, userId });
|
|
551
|
+
if (preview.status === "available") {
|
|
552
|
+
const { taxMinorUnits, totalMinorUnits, currency } = preview;
|
|
553
|
+
render({ tax: taxMinorUnits, total: totalMinorUnits, currency });
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
The scope must come from a trusted authentication boundary: the call rejects
|
|
558
|
+
with `403` a user who is not a member of the tenant or is refused by the tenant
|
|
559
|
+
access gate (a workspace awaiting its first payment, or suspended).
|
|
560
|
+
|
|
561
|
+
The result is always one of three states, and Stripe errors never throw:
|
|
562
|
+
|
|
563
|
+
| `status` | Meaning | `reason` |
|
|
564
|
+
| --- | --- | --- |
|
|
565
|
+
| `available` | Stripe priced the next invoice | — |
|
|
566
|
+
| `absent` | Nothing to bill next | `free_plan`, `complimentary`, `customer_not_configured`, `subscription_not_configured` (no plan, or no Stripe subscription yet), `no_upcoming_invoice` (the subscription ends with the current cycle) |
|
|
567
|
+
| `unavailable` | An invoice is due but cannot be priced exactly right now; the cause is logged | `stripe_not_configured` (placeholder keys), `tax_not_configured` (Stripe Tax inactive), `tax_location_invalid`, `tax_location_required`, `tax_calculation_failed`, `usage_unavailable` (a line items provider failed), `provider_error` (any other Stripe failure) |
|
|
568
|
+
|
|
569
|
+
A missing payment method does not prevent a preview: Stripe prices the invoice
|
|
570
|
+
all the same.
|
|
571
|
+
|
|
572
|
+
An `available` preview carries:
|
|
573
|
+
|
|
574
|
+
- `currency` (uppercase ISO 4217) and integer amounts in its minor units:
|
|
575
|
+
`subtotalMinorUnits` (lines before discounts and tax),
|
|
576
|
+
`totalExcludingTaxMinorUnits`, `taxMinorUnits`, `totalMinorUnits` (tax
|
|
577
|
+
included) and `amountDueMinorUnits` (after the customer balance).
|
|
578
|
+
- `taxes`: one entry per tax Stripe applied, with its amount, taxable amount,
|
|
579
|
+
`ratePercentage`, `country`, `taxType` (`vat`…), `taxabilityReason` and
|
|
580
|
+
`isReverseCharge`. `taxCountry` and `isReverseCharge` summarize them; a
|
|
581
|
+
reverse-charged invoice has a zero tax amount and `isReverseCharge: true`.
|
|
582
|
+
- `lines`: `subscription` for the plan, `usage` for the lines quoted by the
|
|
583
|
+
[invoice line items](#invoice-line-items) providers (with their
|
|
584
|
+
`usageLineKey`), `invoice_item` for any other pending Stripe item. Each has
|
|
585
|
+
its pre-tax amount, its tax and its period. `hasMoreLines` flags a list
|
|
586
|
+
Stripe truncated; the totals are always complete.
|
|
587
|
+
- `periodStart` / `periodEnd` (the cycle the invoice closes), `billingDate`
|
|
588
|
+
(when Stripe issues it), `usageThrough` (how far the quoted usage runs, null
|
|
589
|
+
without usage lines) and `computedAt`. Dates are ISO 8601 strings.
|
|
590
|
+
|
|
591
|
+
Usage is quoted up to the time of the request with the same providers, paid
|
|
592
|
+
coverage window and line validation as the renewal invoice, so the preview's
|
|
593
|
+
tax covers plan and usage together. It is an estimate of a cycle still running:
|
|
594
|
+
the renewal invoice bills the whole cycle.
|
|
595
|
+
|
|
596
|
+
Previews are cached per workspace for an hour, shared by every instance through
|
|
597
|
+
the database. Set `upcomingInvoicePreviewCacheTtlSeconds` to change that
|
|
598
|
+
lifetime, or to `0` to price every request. A cached preview is dropped as soon
|
|
599
|
+
as the workspace's subscription (plan change, status) or billing identity
|
|
600
|
+
(address, VAT number) changes locally, and when Stripe reports an invoice
|
|
601
|
+
created, finalized, paid or voided, a subscription updated or deleted, or a
|
|
602
|
+
customer updated. `unavailable` results are never cached. Display
|
|
603
|
+
`computedAt` to tell the reader how current the figures are.
|
|
355
604
|
|
|
356
605
|
### Workspace settings pages
|
|
357
606
|
|
|
@@ -506,7 +755,7 @@ remain indefinitely. The existing retention policy still removes old terminal
|
|
|
506
755
|
`success`/`skipped` receipts, so deduplication is not guaranteed after that window.
|
|
507
756
|
|
|
508
757
|
Revision mutations require the published database interface `0.1.6` or later
|
|
509
|
-
and an adapter implementing that contract. The MongoDB test adapter uses `1.3.
|
|
758
|
+
and an adapter implementing that contract. The MongoDB test adapter uses `1.3.1`,
|
|
510
759
|
and any DMS release carrying this module satisfies the requirement. New records
|
|
511
760
|
initialize revisions; legacy PostgreSQL rows with a declared revision column
|
|
512
761
|
require an explicit revision backfill
|
|
@@ -663,7 +912,7 @@ records positive promotion confirmation before validating final metadata, then
|
|
|
663
912
|
uses the existing database operation receipt to authorize publication. Published
|
|
664
913
|
prerequisites are the database interface `0.1.6`, the storage interface `0.1.3`,
|
|
665
914
|
and a compatible provider: local `0.1.4` or S3 `0.1.3`, all of which any DMS
|
|
666
|
-
release carrying this module provides. Tests use MongoDB `1.3.
|
|
915
|
+
release carrying this module provides. Tests use MongoDB `1.3.1`.
|
|
667
916
|
There is no legacy move/existence fallback.
|
|
668
917
|
|
|
669
918
|
Source admissions remain insert-only and permanent. Rejected work cannot publish,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TenantSubscriptionStatus } from "../db";
|
|
2
|
-
export declare const BLOCKING_STATUSES: Set<"active" | "
|
|
2
|
+
export declare const BLOCKING_STATUSES: Set<"active" | "cancelled" | "suspended" | "trialing" | "past_due" | "pending_payment">;
|
|
3
3
|
export declare const WORKSPACE_ACCESS_BLOCKED_CODE = "saas.errors.workspace.access_blocked";
|
|
4
4
|
export declare function isTenantSubscriptionBlocked(tenantId: string): Promise<{
|
|
5
5
|
blocked: boolean;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type BillingState, type TenantSubscription } from "../db";
|
|
2
|
+
export * from "./past-due-banner";
|
|
2
3
|
export * from "./recovery";
|
|
3
4
|
export declare function deriveBillingState(subscription: TenantSubscription | undefined): BillingState;
|
|
4
5
|
export declare function recomputeTenantBillingState(tenantId: string): Promise<void>;
|
|
@@ -20,6 +20,7 @@ exports.recomputeAllTenantBillingStates = recomputeAllTenantBillingStates;
|
|
|
20
20
|
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
21
21
|
const db_1 = require("@antelopejs/interface-dms/db");
|
|
22
22
|
const db_2 = require("../db");
|
|
23
|
+
__exportStar(require("./past-due-banner"), exports);
|
|
23
24
|
__exportStar(require("./recovery"), exports);
|
|
24
25
|
const ACTIVE_STATUS = "active";
|
|
25
26
|
const FREE_STATE = "free";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/billing-state/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/billing-state/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAgBA,gDAWC;AAED,kEAwBC;AAED,0EAMC;AA7DD,6FAAqE;AACrE,qDAA2D;AAC3D,8BAKe;AAEf,oDAAkC;AAClC,6CAA2B;AAE3B,MAAM,aAAa,GAAG,QAAQ,CAAC;AAC/B,MAAM,UAAU,GAAiB,MAAM,CAAC;AACxC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAE/B,SAAgB,kBAAkB,CAChC,YAA4C;IAE5C,IAAI,CAAC,YAAY;QAAE,OAAO,UAAU,CAAC;IACrC,IACE,YAAY,CAAC,MAAM,KAAK,aAAa;QACrC,CAAC,YAAY,CAAC,oBAAoB,EAClC,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,OAAO,YAAY,CAAC,MAAM,CAAC;AAC7B,CAAC;AAEM,KAAK,UAAU,2BAA2B,CAC/C,QAAgB;IAEhB,MAAM,KAAK,GAAG,IAAA,wCAAQ,EAAC,4BAAuB,CAAC,CAAC;IAChD,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,oBAAoB,EAAE,OAAO,EAAE,EAAE,CAAC;QAChE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QACpD,IAAI,QAAQ,EAAE,SAAS;YAAE,OAAO;QAChC,MAAM,YAAY,GAAG,MAAM,IAAA,wCAAQ,EACjC,4BAAuB,EACvB,QAAQ,CACT,CAAC,OAAO,EAAE,CAAC;QACZ,IAAI,YAAY,EAAE,iBAAiB;YAAE,OAAO;QAC5C,IACE,MAAM,KAAK,CAAC,eAAe,CACzB,QAAQ,EACR,kBAAkB,CAAC,YAAY,CAAC,EAChC,QAAQ,CACT;YAED,OAAO;IACX,CAAC;IACD,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,+BAA+B;IACnD,MAAM,OAAO,GAAG,MAAM,IAAA,wCAAQ,EAAC,gBAAW,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,CAAC;IACrE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,GAAG;YAAE,SAAS;QAC1B,MAAM,2BAA2B,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAChD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type LayoutBannerComponentInfo, type LayoutBannerContext } from "@antelopejs/interface-dms/layout-banners";
|
|
2
|
+
export declare function isPastDueBannerVisible({ user, tenantId, }: LayoutBannerContext): Promise<boolean>;
|
|
3
|
+
export declare const PAST_DUE_BANNER: LayoutBannerComponentInfo;
|
|
4
|
+
export declare function registerPastDueBanner(): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PAST_DUE_BANNER = void 0;
|
|
4
|
+
exports.isPastDueBannerVisible = isPastDueBannerVisible;
|
|
5
|
+
exports.registerPastDueBanner = registerPastDueBanner;
|
|
6
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
7
|
+
const layout_banners_1 = require("@antelopejs/interface-dms/layout-banners");
|
|
8
|
+
const db_1 = require("../db");
|
|
9
|
+
const PAST_DUE_STATE = "past_due";
|
|
10
|
+
async function isPastDueBannerVisible({ user, tenantId, }) {
|
|
11
|
+
if (!user)
|
|
12
|
+
return false;
|
|
13
|
+
const state = await (0, interface_database_decorators_1.GetModel)(db_1.TenantBillingStateModel).findByTenant(tenantId);
|
|
14
|
+
return !state?.deletedAt && state?.billingState === PAST_DUE_STATE;
|
|
15
|
+
}
|
|
16
|
+
exports.PAST_DUE_BANNER = {
|
|
17
|
+
key: "dms-saas:past-due",
|
|
18
|
+
variant: layout_banners_1.LayoutBannerVariant.ERROR,
|
|
19
|
+
component: "DmsSaasPastDueBanner",
|
|
20
|
+
visible: isPastDueBannerVisible,
|
|
21
|
+
};
|
|
22
|
+
function registerPastDueBanner() {
|
|
23
|
+
(0, layout_banners_1.RegisterLayoutBanner)(exports.PAST_DUE_BANNER);
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=past-due-banner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"past-due-banner.js","sourceRoot":"","sources":["../../src/billing-state/past-due-banner.ts"],"names":[],"mappings":";;;AAgBA,wDAOC;AASD,sDAEC;AAlCD,6FAAqE;AACrE,6EAKkD;AAClD,8BAAmE;AAEnE,MAAM,cAAc,GAAiB,UAAU,CAAC;AAOzC,KAAK,UAAU,sBAAsB,CAAC,EAC3C,IAAI,EACJ,QAAQ,GACY;IACpB,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,KAAK,GAAG,MAAM,IAAA,wCAAQ,EAAC,4BAAuB,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC7E,OAAO,CAAC,KAAK,EAAE,SAAS,IAAI,KAAK,EAAE,YAAY,KAAK,cAAc,CAAC;AACrE,CAAC;AAEY,QAAA,eAAe,GAA8B;IACxD,GAAG,EAAE,mBAAmB;IACxB,OAAO,EAAE,oCAAmB,CAAC,KAAK;IAClC,SAAS,EAAE,sBAAsB;IACjC,OAAO,EAAE,sBAAsB;CAChC,CAAC;AAEF,SAAgB,qBAAqB;IACnC,IAAA,qCAAoB,EAAC,uBAAe,CAAC,CAAC;AACxC,CAAC"}
|
package/dist/config/runtime.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DmsSaasConfig, type RegistrationPaymentMethodPolicy } from "../types";
|
|
2
2
|
export declare function setRuntimeConfig(config: DmsSaasConfig): void;
|
|
3
|
+
export declare function getRegistrationPaymentMethodPolicy(): RegistrationPaymentMethodPolicy;
|
|
4
|
+
export declare function assertRegistrationCardAccepted(): void;
|
|
5
|
+
export declare function resolveRegistrationPaymentMethodId(paymentMethodId: string | undefined): string | undefined;
|
|
6
|
+
export declare function getDefaultPlanSlug(): string | undefined;
|
|
7
|
+
export declare function getUpcomingInvoicePreviewCacheTtlMs(): number;
|
|
3
8
|
export declare function getAllowedRedirectHosts(): string[];
|
|
4
9
|
export declare function getSupportStorage(): string;
|
|
5
10
|
export declare function assertAdmissionOpen(isPlatformOwner?: boolean): void;
|
package/dist/config/runtime.js
CHANGED
|
@@ -1,24 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.setRuntimeConfig = setRuntimeConfig;
|
|
4
|
+
exports.getRegistrationPaymentMethodPolicy = getRegistrationPaymentMethodPolicy;
|
|
5
|
+
exports.assertRegistrationCardAccepted = assertRegistrationCardAccepted;
|
|
6
|
+
exports.resolveRegistrationPaymentMethodId = resolveRegistrationPaymentMethodId;
|
|
7
|
+
exports.getDefaultPlanSlug = getDefaultPlanSlug;
|
|
8
|
+
exports.getUpcomingInvoicePreviewCacheTtlMs = getUpcomingInvoicePreviewCacheTtlMs;
|
|
4
9
|
exports.getAllowedRedirectHosts = getAllowedRedirectHosts;
|
|
5
10
|
exports.getSupportStorage = getSupportStorage;
|
|
6
11
|
exports.assertAdmissionOpen = assertAdmissionOpen;
|
|
7
12
|
exports.isAllowedRedirectUrl = isAllowedRedirectUrl;
|
|
8
13
|
const interface_api_util_1 = require("@antelopejs/interface-api-util");
|
|
14
|
+
const types_1 = require("../types");
|
|
9
15
|
const dev_mode_1 = require("./dev-mode");
|
|
10
16
|
const REDIRECT_PROTOCOLS = ["http:", "https:"];
|
|
11
17
|
const LOOPBACK_HOSTNAMES = ["localhost", "127.0.0.1", "::1", "[::1]"];
|
|
18
|
+
const HTTP_BAD_REQUEST = 400;
|
|
12
19
|
const HTTP_UNAVAILABLE = 503;
|
|
13
20
|
const ADMISSION_MODES = ["open", "invitation-only"];
|
|
21
|
+
const DEFAULT_UPCOMING_INVOICE_PREVIEW_CACHE_TTL_SECONDS = 3600;
|
|
22
|
+
const MS_PER_SECOND = 1000;
|
|
14
23
|
let runtimeConfig = null;
|
|
15
|
-
function
|
|
24
|
+
function assertValidAdmissionMode(config) {
|
|
16
25
|
if (config.admissionMode !== undefined &&
|
|
17
26
|
!ADMISSION_MODES.includes(config.admissionMode)) {
|
|
18
27
|
throw new Error("Invalid dms-saas admissionMode: expected open or invitation-only");
|
|
19
28
|
}
|
|
29
|
+
}
|
|
30
|
+
function assertValidRegistrationPaymentMethod(config) {
|
|
31
|
+
const policy = config.registration?.paymentMethod;
|
|
32
|
+
if (policy !== undefined &&
|
|
33
|
+
!types_1.REGISTRATION_PAYMENT_METHOD_POLICIES.includes(policy)) {
|
|
34
|
+
throw new Error("Invalid dms-saas registration.paymentMethod: expected required, optional or none");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function assertValidUpcomingInvoicePreviewCacheTtl(config) {
|
|
38
|
+
const ttl = config.upcomingInvoicePreviewCacheTtlSeconds;
|
|
39
|
+
if (ttl === undefined || (Number.isFinite(ttl) && ttl >= 0))
|
|
40
|
+
return;
|
|
41
|
+
throw new Error("Invalid dms-saas upcomingInvoicePreviewCacheTtlSeconds: expected a non-negative number");
|
|
42
|
+
}
|
|
43
|
+
function setRuntimeConfig(config) {
|
|
44
|
+
assertValidAdmissionMode(config);
|
|
45
|
+
assertValidRegistrationPaymentMethod(config);
|
|
46
|
+
assertValidUpcomingInvoicePreviewCacheTtl(config);
|
|
20
47
|
runtimeConfig = config;
|
|
21
48
|
}
|
|
49
|
+
function getRegistrationPaymentMethodPolicy() {
|
|
50
|
+
return (runtimeConfig?.registration?.paymentMethod ??
|
|
51
|
+
types_1.DEFAULT_REGISTRATION_PAYMENT_METHOD_POLICY);
|
|
52
|
+
}
|
|
53
|
+
function assertRegistrationCardAccepted() {
|
|
54
|
+
(0, interface_api_util_1.assert)(getRegistrationPaymentMethodPolicy() !== "none", HTTP_BAD_REQUEST, "saas.errors.registration.payment_method_disabled");
|
|
55
|
+
}
|
|
56
|
+
function resolveRegistrationPaymentMethodId(paymentMethodId) {
|
|
57
|
+
const policy = getRegistrationPaymentMethodPolicy();
|
|
58
|
+
if (policy === "none")
|
|
59
|
+
return undefined;
|
|
60
|
+
(0, interface_api_util_1.assert)(policy === "optional" || paymentMethodId, HTTP_BAD_REQUEST, "saas.errors.registration.payment_method_required");
|
|
61
|
+
return paymentMethodId || undefined;
|
|
62
|
+
}
|
|
63
|
+
function getDefaultPlanSlug() {
|
|
64
|
+
return runtimeConfig?.defaultPlanSlug;
|
|
65
|
+
}
|
|
66
|
+
function getUpcomingInvoicePreviewCacheTtlMs() {
|
|
67
|
+
const seconds = runtimeConfig?.upcomingInvoicePreviewCacheTtlSeconds ??
|
|
68
|
+
DEFAULT_UPCOMING_INVOICE_PREVIEW_CACHE_TTL_SECONDS;
|
|
69
|
+
return seconds * MS_PER_SECOND;
|
|
70
|
+
}
|
|
22
71
|
function getAllowedRedirectHosts() {
|
|
23
72
|
return runtimeConfig?.allowedRedirectHosts ?? [];
|
|
24
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/config/runtime.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/config/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,11 +7,14 @@ exports.scheduleRecomputeBillingState = scheduleRecomputeBillingState;
|
|
|
7
7
|
const logging_1 = require("@antelopejs/interface-core/logging");
|
|
8
8
|
const node_cron_1 = __importDefault(require("node-cron"));
|
|
9
9
|
const billing_state_1 = require("../billing-state");
|
|
10
|
+
const default_plan_1 = require("../workspaces/default-plan");
|
|
10
11
|
const CRON_NAME = "recompute-billing-state";
|
|
11
12
|
const CRON_SCHEDULE = "*/15 * * * *";
|
|
12
13
|
function scheduleRecomputeBillingState() {
|
|
13
14
|
return node_cron_1.default.schedule(CRON_SCHEDULE, () => {
|
|
14
|
-
void (0,
|
|
15
|
+
void (0, default_plan_1.backfillDefaultSubscriptions)()
|
|
16
|
+
.then(billing_state_1.recomputeAllTenantBillingStates)
|
|
17
|
+
.catch((error) => {
|
|
15
18
|
logging_1.Logging.Error(`[dms-saas:cron:${CRON_NAME}] failed`, error);
|
|
16
19
|
});
|
|
17
20
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recompute-billing-state.js","sourceRoot":"","sources":["../../src/crons/recompute-billing-state.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"recompute-billing-state.js","sourceRoot":"","sources":["../../src/crons/recompute-billing-state.ts"],"names":[],"mappings":";;;;;AAQA,sEAUC;AAlBD,gEAA6D;AAC7D,0DAAqD;AACrD,oDAAmE;AACnE,6DAA0E;AAE1E,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,aAAa,GAAG,cAAc,CAAC;AAErC,SAAgB,6BAA6B;IAC3C,OAAO,mBAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAGvC,KAAK,IAAA,2CAA4B,GAAE;aAChC,IAAI,CAAC,+CAA+B,CAAC;aACrC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACxB,iBAAO,CAAC,KAAK,CAAC,kBAAkB,SAAS,UAAU,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -2,6 +2,7 @@ export * from "./admin-user-invoices";
|
|
|
2
2
|
export * from "./admin-user-workspaces";
|
|
3
3
|
export * from "./credit-notes";
|
|
4
4
|
export * from "./features";
|
|
5
|
+
export * from "./invitations";
|
|
5
6
|
export * from "./invoices";
|
|
6
7
|
export * from "./members";
|
|
7
8
|
export * from "./plan-migrations";
|
|
@@ -18,6 +18,7 @@ __exportStar(require("./admin-user-invoices"), exports);
|
|
|
18
18
|
__exportStar(require("./admin-user-workspaces"), exports);
|
|
19
19
|
__exportStar(require("./credit-notes"), exports);
|
|
20
20
|
__exportStar(require("./features"), exports);
|
|
21
|
+
__exportStar(require("./invitations"), exports);
|
|
21
22
|
__exportStar(require("./invoices"), exports);
|
|
22
23
|
__exportStar(require("./members"), exports);
|
|
23
24
|
__exportStar(require("./plan-migrations"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/data-api/platformOwner/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wDAAsC;AACtC,0DAAwC;AACxC,iDAA+B;AAC/B,6CAA2B;AAC3B,6CAA2B;AAC3B,4CAA0B;AAC1B,oDAAkC;AAClC,0CAAwB;AACxB,6CAA2B;AAC3B,4CAA0B;AAC1B,0CAAwB;AACxB,+CAA6B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/data-api/platformOwner/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wDAAsC;AACtC,0DAAwC;AACxC,iDAA+B;AAC/B,6CAA2B;AAC3B,gDAA8B;AAC9B,6CAA2B;AAC3B,4CAA0B;AAC1B,oDAAkC;AAClC,0CAAwB;AACxB,6CAA2B;AAC3B,4CAA0B;AAC1B,0CAAwB;AACxB,+CAA6B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { UserInvite, UserInviteModel } from "@antelopejs/interface-dms/db";
|
|
2
|
+
declare const invitationsDataAPI_base: import("@antelopejs/interface-core/decorators").Class<import("@antelopejs/interface-data-api").ExtractDefCallbacks<{
|
|
3
|
+
get: import("@antelopejs/interface-data-api").DataControllerCallback;
|
|
4
|
+
list: import("@antelopejs/interface-data-api").DataControllerCallback;
|
|
5
|
+
count: import("@antelopejs/interface-data-api").DataControllerCallback;
|
|
6
|
+
}> & {
|
|
7
|
+
table: UserInvite;
|
|
8
|
+
}> & import("@antelopejs/interface-core/decorators").Class<object> & {
|
|
9
|
+
extend: <T extends import("@antelopejs/interface-api").ControllerClass>(this: T, location: string) => T;
|
|
10
|
+
location: string;
|
|
11
|
+
};
|
|
12
|
+
export declare class invitationsDataAPI extends invitationsDataAPI_base {
|
|
13
|
+
model: UserInviteModel;
|
|
14
|
+
_id: string;
|
|
15
|
+
_instance: string;
|
|
16
|
+
email: string;
|
|
17
|
+
asTenantOwner: boolean;
|
|
18
|
+
get status(): boolean;
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
expiresAt: Date;
|
|
21
|
+
}
|
|
22
|
+
export {};
|