@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.
Files changed (199) hide show
  1. package/README.md +262 -13
  2. package/dist/auth/subscription-access-gate.d.ts +1 -1
  3. package/dist/billing-state/index.d.ts +1 -0
  4. package/dist/billing-state/index.js +1 -0
  5. package/dist/billing-state/index.js.map +1 -1
  6. package/dist/billing-state/past-due-banner.d.ts +4 -0
  7. package/dist/billing-state/past-due-banner.js +25 -0
  8. package/dist/billing-state/past-due-banner.js.map +1 -0
  9. package/dist/config/runtime.d.ts +6 -1
  10. package/dist/config/runtime.js +50 -1
  11. package/dist/config/runtime.js.map +1 -1
  12. package/dist/crons/recompute-billing-state.js +4 -1
  13. package/dist/crons/recompute-billing-state.js.map +1 -1
  14. package/dist/data-api/platformOwner/index.d.ts +1 -0
  15. package/dist/data-api/platformOwner/index.js +1 -0
  16. package/dist/data-api/platformOwner/index.js.map +1 -1
  17. package/dist/data-api/platformOwner/invitations.d.ts +22 -0
  18. package/dist/data-api/platformOwner/invitations.js +121 -0
  19. package/dist/data-api/platformOwner/invitations.js.map +1 -0
  20. package/dist/data-api/platformOwner/workspaces.d.ts +3 -0
  21. package/dist/data-api/platformOwner/workspaces.js +64 -12
  22. package/dist/data-api/platformOwner/workspaces.js.map +1 -1
  23. package/dist/data-api/tenant/tenant-invoices.js +2 -10
  24. package/dist/data-api/tenant/tenant-invoices.js.map +1 -1
  25. package/dist/hooks/register.js +2 -1
  26. package/dist/hooks/register.js.map +1 -1
  27. package/dist/implementations/dms-saas/billing.d.ts +2 -1
  28. package/dist/implementations/dms-saas/billing.js +6 -0
  29. package/dist/implementations/dms-saas/billing.js.map +1 -1
  30. package/dist/index.js +5 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/invoice-line-items/injection.d.ts +10 -0
  33. package/dist/invoice-line-items/injection.js +7 -3
  34. package/dist/invoice-line-items/injection.js.map +1 -1
  35. package/dist/notifications/subjects.d.ts +19 -19
  36. package/dist/operator-actions/index.d.ts +1 -0
  37. package/dist/operator-actions/index.js +1 -0
  38. package/dist/operator-actions/index.js.map +1 -1
  39. package/dist/operator-actions/invitation-commands.d.ts +12 -0
  40. package/dist/operator-actions/invitation-commands.js +46 -0
  41. package/dist/operator-actions/invitation-commands.js.map +1 -0
  42. package/dist/operator-actions/types.d.ts +1 -1
  43. package/dist/pages/platform/categories.d.ts +6 -6
  44. package/dist/pages/platform/credit-notes.d.ts +1 -1
  45. package/dist/pages/platform/dashboard.d.ts +2 -2
  46. package/dist/pages/platform/dashboard.js +2 -4
  47. package/dist/pages/platform/dashboard.js.map +1 -1
  48. package/dist/pages/platform/features.d.ts +1 -1
  49. package/dist/pages/platform/invoices.d.ts +1 -1
  50. package/dist/pages/platform/plans/edit.d.ts +2 -2
  51. package/dist/pages/platform/plans/index.d.ts +1 -1
  52. package/dist/pages/platform/plans/migrations.d.ts +1 -1
  53. package/dist/pages/platform/segments/index.d.ts +1 -1
  54. package/dist/pages/platform/settings.d.ts +1 -1
  55. package/dist/pages/platform/users/detail.d.ts +1 -1
  56. package/dist/pages/platform/users/index.d.ts +1 -1
  57. package/dist/pages/platform/workspaces/detail.d.ts +1 -1
  58. package/dist/pages/platform/workspaces/detail.js +40 -1
  59. package/dist/pages/platform/workspaces/detail.js.map +1 -1
  60. package/dist/pages/platform/workspaces/index.d.ts +4 -10
  61. package/dist/pages/platform/workspaces/index.js +27 -111
  62. package/dist/pages/platform/workspaces/index.js.map +1 -1
  63. package/dist/pages/public/legal/privacy-policy.js +2 -1
  64. package/dist/pages/public/legal/privacy-policy.js.map +1 -1
  65. package/dist/pages/public/legal/terms-and-conditions.js +2 -1
  66. package/dist/pages/public/legal/terms-and-conditions.js.map +1 -1
  67. package/dist/pages/public/legal/terms-of-use.js +2 -1
  68. package/dist/pages/public/legal/terms-of-use.js.map +1 -1
  69. package/dist/pages/tenant/billing.d.ts +1 -1
  70. package/dist/plans/index.d.ts +1 -0
  71. package/dist/plans/index.js +1 -0
  72. package/dist/plans/index.js.map +1 -1
  73. package/dist/plans/platform-support.d.ts +2 -0
  74. package/dist/plans/platform-support.js +19 -0
  75. package/dist/plans/platform-support.js.map +1 -0
  76. package/dist/plans/seat-capacity.d.ts +9 -2
  77. package/dist/plans/seat-capacity.js +59 -12
  78. package/dist/plans/seat-capacity.js.map +1 -1
  79. package/dist/plans/seat-hooks.js +34 -13
  80. package/dist/plans/seat-hooks.js.map +1 -1
  81. package/dist/routes/platformOwner/billing-settings.d.ts +2 -2
  82. package/dist/routes/platformOwner/dashboard.d.ts +8 -6
  83. package/dist/routes/platformOwner/dashboard.js +31 -17
  84. package/dist/routes/platformOwner/dashboard.js.map +1 -1
  85. package/dist/routes/platformOwner/index.d.ts +1 -0
  86. package/dist/routes/platformOwner/index.js +1 -0
  87. package/dist/routes/platformOwner/index.js.map +1 -1
  88. package/dist/routes/platformOwner/plan-deletion.d.ts +1 -1
  89. package/dist/routes/platformOwner/plans.d.ts +5 -5
  90. package/dist/routes/platformOwner/segments.d.ts +1 -1
  91. package/dist/routes/platformOwner/workspace-invitations.d.ts +13 -0
  92. package/dist/routes/platformOwner/workspace-invitations.js +73 -0
  93. package/dist/routes/platformOwner/workspace-invitations.js.map +1 -0
  94. package/dist/routes/public/legal-documents.d.ts +2 -2
  95. package/dist/routes/public/register.d.ts +7 -3
  96. package/dist/routes/public/register.js +42 -27
  97. package/dist/routes/public/register.js.map +1 -1
  98. package/dist/routes/tenant/tenant-billing.d.ts +7 -11
  99. package/dist/routes/tenant/tenant-billing.js +34 -26
  100. package/dist/routes/tenant/tenant-billing.js.map +1 -1
  101. package/dist/routes/tenant/tenant-plan.d.ts +1 -0
  102. package/dist/routes/tenant/tenant-plan.js +27 -2
  103. package/dist/routes/tenant/tenant-plan.js.map +1 -1
  104. package/dist/routes/tenant/tenant-seats.d.ts +2 -4
  105. package/dist/routes/tenant/tenant-seats.js.map +1 -1
  106. package/dist/routes/tenant/workspaces.d.ts +3 -1
  107. package/dist/routes/tenant/workspaces.js +10 -4
  108. package/dist/routes/tenant/workspaces.js.map +1 -1
  109. package/dist/stripe/upcoming-invoice.d.ts +21 -0
  110. package/dist/stripe/upcoming-invoice.js +42 -0
  111. package/dist/stripe/upcoming-invoice.js.map +1 -0
  112. package/dist/stripe/webhook-dispatch.js +2 -0
  113. package/dist/stripe/webhook-dispatch.js.map +1 -1
  114. package/dist/types/config.d.ts +9 -0
  115. package/dist/types/config.js +7 -0
  116. package/dist/types/config.js.map +1 -1
  117. package/dist/upcoming-invoice/db/preview-cache.model.d.ts +38 -0
  118. package/dist/upcoming-invoice/db/preview-cache.model.js +57 -0
  119. package/dist/upcoming-invoice/db/preview-cache.model.js.map +1 -0
  120. package/dist/upcoming-invoice/db/preview-cache.table.d.ts +10 -0
  121. package/dist/upcoming-invoice/db/preview-cache.table.js +42 -0
  122. package/dist/upcoming-invoice/db/preview-cache.table.js.map +1 -0
  123. package/dist/upcoming-invoice/eligibility.d.ts +17 -0
  124. package/dist/upcoming-invoice/eligibility.js +52 -0
  125. package/dist/upcoming-invoice/eligibility.js.map +1 -0
  126. package/dist/upcoming-invoice/invalidation.d.ts +2 -0
  127. package/dist/upcoming-invoice/invalidation.js +39 -0
  128. package/dist/upcoming-invoice/invalidation.js.map +1 -0
  129. package/dist/upcoming-invoice/mapping.d.ts +8 -0
  130. package/dist/upcoming-invoice/mapping.js +82 -0
  131. package/dist/upcoming-invoice/mapping.js.map +1 -0
  132. package/dist/upcoming-invoice/outcomes.d.ts +13 -0
  133. package/dist/upcoming-invoice/outcomes.js +15 -0
  134. package/dist/upcoming-invoice/outcomes.js.map +1 -0
  135. package/dist/upcoming-invoice/preview.d.ts +3 -0
  136. package/dist/upcoming-invoice/preview.js +80 -0
  137. package/dist/upcoming-invoice/preview.js.map +1 -0
  138. package/dist/upcoming-invoice/pricing.d.ts +3 -0
  139. package/dist/upcoming-invoice/pricing.js +84 -0
  140. package/dist/upcoming-invoice/pricing.js.map +1 -0
  141. package/dist/upcoming-invoice/usage.d.ts +14 -0
  142. package/dist/upcoming-invoice/usage.js +53 -0
  143. package/dist/upcoming-invoice/usage.js.map +1 -0
  144. package/dist/utils/invoice-lines-type.d.ts +1 -1
  145. package/dist/workspaces/billing-identity.d.ts +19 -0
  146. package/dist/workspaces/billing-identity.js +51 -0
  147. package/dist/workspaces/billing-identity.js.map +1 -0
  148. package/dist/workspaces/default-plan.d.ts +5 -0
  149. package/dist/workspaces/default-plan.js +109 -0
  150. package/dist/workspaces/default-plan.js.map +1 -0
  151. package/dist/workspaces/index.d.ts +2 -0
  152. package/dist/workspaces/index.js +2 -0
  153. package/dist/workspaces/index.js.map +1 -1
  154. package/dist/workspaces/invitations.d.ts +26 -0
  155. package/dist/workspaces/invitations.js +112 -0
  156. package/dist/workspaces/invitations.js.map +1 -0
  157. package/dist/workspaces/provisioning.d.ts +3 -3
  158. package/dist/workspaces/provisioning.js +39 -11
  159. package/dist/workspaces/provisioning.js.map +1 -1
  160. package/frontend-vue/app/components/BillingIdentityFields.vue +179 -0
  161. package/frontend-vue/app/components/PastDueBanner.vue +81 -0
  162. package/frontend-vue/app/components/PaymentMethodCard.vue +94 -38
  163. package/frontend-vue/app/components/PlanComparisonModal.vue +62 -26
  164. package/frontend-vue/app/components/PlanUpgradeModal.vue +181 -0
  165. package/frontend-vue/app/components/PlatformNotes.vue +4 -0
  166. package/frontend-vue/app/components/RefundButton.vue +10 -1
  167. package/frontend-vue/app/components/Register.vue +49 -92
  168. package/frontend-vue/app/components/SeatQuotaBanner.vue +28 -0
  169. package/frontend-vue/app/components/TenantBillingForm.vue +52 -128
  170. package/frontend-vue/app/components/TenantPlanCard.vue +68 -10
  171. package/frontend-vue/app/components/UserSummaryCard.vue +12 -10
  172. package/frontend-vue/app/components/WorkspaceAdminCreateModal.vue +202 -0
  173. package/frontend-vue/app/components/WorkspaceCreateModal.vue +46 -9
  174. package/frontend-vue/app/components/WorkspaceInvitationLink.vue +106 -0
  175. package/frontend-vue/app/components/WorkspaceSummaryCard.vue +39 -15
  176. package/frontend-vue/app/components/WorkspaceSuspended.vue +3 -5
  177. package/frontend-vue/app/components/WorkspaceSwitcherWidget.vue +8 -5
  178. package/frontend-vue/app/composables/useBillingCountries.ts +25 -5
  179. package/frontend-vue/app/composables/useBillingIdentity.ts +141 -0
  180. package/frontend-vue/app/composables/useInvitationLink.ts +69 -0
  181. package/frontend-vue/app/composables/usePastDueBannerAction.ts +20 -0
  182. package/frontend-vue/app/composables/usePlanComparison.ts +15 -0
  183. package/frontend-vue/app/composables/usePlanFeatureFormat.ts +312 -20
  184. package/frontend-vue/app/composables/usePlanFeatureLabel.ts +24 -0
  185. package/frontend-vue/app/composables/useSaasRegistration.ts +110 -223
  186. package/frontend-vue/app/composables/useSeatQuota.ts +8 -0
  187. package/frontend-vue/app/composables/useStripePaymentElement.ts +3 -3
  188. package/frontend-vue/app/composables/useWorkspaceAccessCache.ts +29 -2
  189. package/frontend-vue/app/composables/useWorkspaceFinalization.ts +7 -32
  190. package/frontend-vue/app/custom-pages/auth/no-workspace.vue +49 -194
  191. package/frontend-vue/app/plugins/auth-links.ts +0 -4
  192. package/frontend-vue/app/types/runtime-config.ts +13 -0
  193. package/frontend-vue/dms.frontend.ts +10 -4
  194. package/frontend-vue/i18n/locales/saas-en-GB.json +161 -36
  195. package/frontend-vue/i18n/locales/saas-fr-FR.json +162 -37
  196. package/frontend-vue/package.json +0 -1
  197. package/frontend-vue/pnpm-lock.yaml +1 -125
  198. package/package.json +15 -15
  199. 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 buys its way in — a workspace, a plan, a card — and
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 paid stays signed
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 — plan loading and filtering by customer type, the Stripe setup
201
- intent and card confirmation, the order the requirements are validated in, the
202
- provisioning call and the landing route — and renders nothing:
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 { form, plans, errorMessage, isSubmitting, formatPlanPrice, submit } =
207
- useSaasRegistration({ redirectTo: "/welcome" });
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`. Every screen stays
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.0`,
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.0`.
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" | "suspended" | "cancelled" | "trialing" | "past_due" | "pending_payment">;
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":";;;;;;;;;;;;;;;;AAeA,gDAWC;AAED,kEAwBC;AAED,0EAMC;AA5DD,6FAAqE;AACrE,qDAA2D;AAC3D,8BAKe;AAEf,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"}
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"}
@@ -1,5 +1,10 @@
1
- import type { DmsSaasConfig } from "../types";
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;
@@ -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 setRuntimeConfig(config) {
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":";;AAWA,4CAUC;AAED,0DAEC;AAGD,8CAQC;AAGD,kDAOC;AAuBD,oDAKC;AA1ED,uEAAwD;AAExD,yCAAuC;AAEvC,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC/C,MAAM,kBAAkB,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AACtE,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAEpD,IAAI,aAAa,GAAyB,IAAI,CAAC;AAE/C,SAAgB,gBAAgB,CAAC,MAAqB;IACpD,IACE,MAAM,CAAC,aAAa,KAAK,SAAS;QAClC,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,EAC/C,CAAC;QACD,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,aAAa,GAAG,MAAM,CAAC;AACzB,CAAC;AAED,SAAgB,uBAAuB;IACrC,OAAO,aAAa,EAAE,oBAAoB,IAAI,EAAE,CAAC;AACnD,CAAC;AAGD,SAAgB,iBAAiB;IAC/B,MAAM,OAAO,GAAG,aAAa,EAAE,cAAc,CAAC;IAC9C,IAAA,2BAAM,EACJ,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EACxD,gBAAgB,EAChB,yCAAyC,CAC1C,CAAC;IACF,OAAO,OAAO,CAAC;AACjB,CAAC;AAGD,SAAgB,mBAAmB,CAAC,eAAe,GAAG,KAAK;IACzD,MAAM,cAAc,GAAG,GAAG,CAAC;IAC3B,IAAA,2BAAM,EACJ,eAAe,IAAI,aAAa,EAAE,aAAa,KAAK,iBAAiB,EACrE,cAAc,EACd,iCAAiC,CAClC,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAc;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,kBAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,GAAQ;IAC7B,OAAO,kBAAkB,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACnD,CAAC;AAUD,SAAgB,oBAAoB,CAAC,MAAc;IACjD,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,IAAI,IAAA,oBAAS,GAAE,IAAI,aAAa,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,OAAO,uBAAuB,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACzD,CAAC"}
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, billing_state_1.recomputeAllTenantBillingStates)().catch((error) => {
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":";;;;;AAOA,sEAMC;AAbD,gEAA6D;AAC7D,0DAAqD;AACrD,oDAAmE;AAEnE,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,aAAa,GAAG,cAAc,CAAC;AAErC,SAAgB,6BAA6B;IAC3C,OAAO,mBAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QACvC,KAAK,IAAA,+CAA+B,GAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC9D,iBAAO,CAAC,KAAK,CAAC,kBAAkB,SAAS,UAAU,EAAE,KAAK,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
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 {};