@antelopejs/dms-saas 0.0.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/LICENSE +190 -0
- package/README.md +658 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.js +20 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-entry.d.ts +5 -0
- package/dist/auth/oauth-entry.js +10 -0
- package/dist/auth/oauth-entry.js.map +1 -0
- package/dist/auth/plan-permissions-resolver.d.ts +1 -0
- package/dist/auth/plan-permissions-resolver.js +44 -0
- package/dist/auth/plan-permissions-resolver.js.map +1 -0
- package/dist/auth/subscription-access-gate.d.ts +8 -0
- package/dist/auth/subscription-access-gate.js +38 -0
- package/dist/auth/subscription-access-gate.js.map +1 -0
- package/dist/automation/index.d.ts +44 -0
- package/dist/automation/index.js +209 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/billing-state/index.d.ts +5 -0
- package/dist/billing-state/index.js +58 -0
- package/dist/billing-state/index.js.map +1 -0
- package/dist/billing-state/recovery.d.ts +22 -0
- package/dist/billing-state/recovery.js +81 -0
- package/dist/billing-state/recovery.js.map +1 -0
- package/dist/config/dev-mode.d.ts +3 -0
- package/dist/config/dev-mode.js +27 -0
- package/dist/config/dev-mode.js.map +1 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/runtime.d.ts +6 -0
- package/dist/config/runtime.js +54 -0
- package/dist/config/runtime.js.map +1 -0
- package/dist/crons/auto-suspend-past-due.d.ts +2 -0
- package/dist/crons/auto-suspend-past-due.js +43 -0
- package/dist/crons/auto-suspend-past-due.js.map +1 -0
- package/dist/crons/cleanup-stripe-webhook-events.d.ts +2 -0
- package/dist/crons/cleanup-stripe-webhook-events.js +27 -0
- package/dist/crons/cleanup-stripe-webhook-events.js.map +1 -0
- package/dist/crons/expire-free-workspaces.d.ts +2 -0
- package/dist/crons/expire-free-workspaces.js +36 -0
- package/dist/crons/expire-free-workspaces.js.map +1 -0
- package/dist/crons/hard-delete-cancelled.d.ts +2 -0
- package/dist/crons/hard-delete-cancelled.js +55 -0
- package/dist/crons/hard-delete-cancelled.js.map +1 -0
- package/dist/crons/index.d.ts +8 -0
- package/dist/crons/index.js +25 -0
- package/dist/crons/index.js.map +1 -0
- package/dist/crons/notify-free-workspaces-ending-soon.d.ts +2 -0
- package/dist/crons/notify-free-workspaces-ending-soon.js +69 -0
- package/dist/crons/notify-free-workspaces-ending-soon.js.map +1 -0
- package/dist/crons/recompute-billing-state.d.ts +2 -0
- package/dist/crons/recompute-billing-state.js +19 -0
- package/dist/crons/recompute-billing-state.js.map +1 -0
- package/dist/crons/recompute-segments.d.ts +2 -0
- package/dist/crons/recompute-segments.js +19 -0
- package/dist/crons/recompute-segments.js.map +1 -0
- package/dist/crons/reconcile-workspace-lifecycle.d.ts +2 -0
- package/dist/crons/reconcile-workspace-lifecycle.js +19 -0
- package/dist/crons/reconcile-workspace-lifecycle.js.map +1 -0
- package/dist/crons/register.d.ts +2 -0
- package/dist/crons/register.js +24 -0
- package/dist/crons/register.js.map +1 -0
- package/dist/crons/subscription-notifications.d.ts +2 -0
- package/dist/crons/subscription-notifications.js +55 -0
- package/dist/crons/subscription-notifications.js.map +1 -0
- package/dist/data-api/index.d.ts +2 -0
- package/dist/data-api/index.js +19 -0
- package/dist/data-api/index.js.map +1 -0
- package/dist/data-api/platformOwner/admin-user-invoices.d.ts +27 -0
- package/dist/data-api/platformOwner/admin-user-invoices.js +158 -0
- package/dist/data-api/platformOwner/admin-user-invoices.js.map +1 -0
- package/dist/data-api/platformOwner/admin-user-workspaces.d.ts +22 -0
- package/dist/data-api/platformOwner/admin-user-workspaces.js +97 -0
- package/dist/data-api/platformOwner/admin-user-workspaces.js.map +1 -0
- package/dist/data-api/platformOwner/credit-notes.d.ts +24 -0
- package/dist/data-api/platformOwner/credit-notes.js +133 -0
- package/dist/data-api/platformOwner/credit-notes.js.map +1 -0
- package/dist/data-api/platformOwner/features.d.ts +30 -0
- package/dist/data-api/platformOwner/features.js +141 -0
- package/dist/data-api/platformOwner/features.js.map +1 -0
- package/dist/data-api/platformOwner/hidden-filter.d.ts +1 -0
- package/dist/data-api/platformOwner/hidden-filter.js +18 -0
- package/dist/data-api/platformOwner/hidden-filter.js.map +1 -0
- package/dist/data-api/platformOwner/index.d.ts +12 -0
- package/dist/data-api/platformOwner/index.js +29 -0
- package/dist/data-api/platformOwner/index.js.map +1 -0
- package/dist/data-api/platformOwner/invoice-options.d.ts +7 -0
- package/dist/data-api/platformOwner/invoice-options.js +14 -0
- package/dist/data-api/platformOwner/invoice-options.js.map +1 -0
- package/dist/data-api/platformOwner/invoices.d.ts +26 -0
- package/dist/data-api/platformOwner/invoices.js +147 -0
- package/dist/data-api/platformOwner/invoices.js.map +1 -0
- package/dist/data-api/platformOwner/members.d.ts +24 -0
- package/dist/data-api/platformOwner/members.js +125 -0
- package/dist/data-api/platformOwner/members.js.map +1 -0
- package/dist/data-api/platformOwner/plan-migrations.d.ts +24 -0
- package/dist/data-api/platformOwner/plan-migrations.js +138 -0
- package/dist/data-api/platformOwner/plan-migrations.js.map +1 -0
- package/dist/data-api/platformOwner/plans.d.ts +30 -0
- package/dist/data-api/platformOwner/plans.js +228 -0
- package/dist/data-api/platformOwner/plans.js.map +1 -0
- package/dist/data-api/platformOwner/segments.d.ts +29 -0
- package/dist/data-api/platformOwner/segments.js +223 -0
- package/dist/data-api/platformOwner/segments.js.map +1 -0
- package/dist/data-api/platformOwner/support.d.ts +58 -0
- package/dist/data-api/platformOwner/support.js +111 -0
- package/dist/data-api/platformOwner/support.js.map +1 -0
- package/dist/data-api/platformOwner/users.d.ts +22 -0
- package/dist/data-api/platformOwner/users.js +128 -0
- package/dist/data-api/platformOwner/users.js.map +1 -0
- package/dist/data-api/platformOwner/workspaces.d.ts +22 -0
- package/dist/data-api/platformOwner/workspaces.js +153 -0
- package/dist/data-api/platformOwner/workspaces.js.map +1 -0
- package/dist/data-api/support-fields.d.ts +49 -0
- package/dist/data-api/support-fields.js +166 -0
- package/dist/data-api/support-fields.js.map +1 -0
- package/dist/data-api/support-routes.d.ts +10 -0
- package/dist/data-api/support-routes.js +71 -0
- package/dist/data-api/support-routes.js.map +1 -0
- package/dist/data-api/tenant/index.d.ts +2 -0
- package/dist/data-api/tenant/index.js +19 -0
- package/dist/data-api/tenant/index.js.map +1 -0
- package/dist/data-api/tenant/support.d.ts +39 -0
- package/dist/data-api/tenant/support.js +53 -0
- package/dist/data-api/tenant/support.js.map +1 -0
- package/dist/data-api/tenant/tenant-invoices.d.ts +37 -0
- package/dist/data-api/tenant/tenant-invoices.js +242 -0
- package/dist/data-api/tenant/tenant-invoices.js.map +1 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.js +18 -0
- package/dist/db/index.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +20 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/register.d.ts +1 -0
- package/dist/hooks/register.js +57 -0
- package/dist/hooks/register.js.map +1 -0
- package/dist/hooks/tenant-export.d.ts +2 -0
- package/dist/hooks/tenant-export.js +64 -0
- package/dist/hooks/tenant-export.js.map +1 -0
- package/dist/hooks/tenant-provisioning.d.ts +3 -0
- package/dist/hooks/tenant-provisioning.js +12 -0
- package/dist/hooks/tenant-provisioning.js.map +1 -0
- package/dist/implementations/dms-saas/billing.d.ts +2 -0
- package/dist/implementations/dms-saas/billing.js +49 -0
- package/dist/implementations/dms-saas/billing.js.map +1 -0
- package/dist/implementations/dms-saas/invoice-line-items.d.ts +11 -0
- package/dist/implementations/dms-saas/invoice-line-items.js +24 -0
- package/dist/implementations/dms-saas/invoice-line-items.js.map +1 -0
- package/dist/implementations/dms-saas/pages.d.ts +3 -0
- package/dist/implementations/dms-saas/pages.js +12 -0
- package/dist/implementations/dms-saas/pages.js.map +1 -0
- package/dist/implementations/dms-saas/workspace-lifecycle.d.ts +2 -0
- package/dist/implementations/dms-saas/workspace-lifecycle.js +23 -0
- package/dist/implementations/dms-saas/workspace-lifecycle.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/invoice-line-items/billing-window.d.ts +3 -0
- package/dist/invoice-line-items/billing-window.js +19 -0
- package/dist/invoice-line-items/billing-window.js.map +1 -0
- package/dist/invoice-line-items/index.d.ts +1 -0
- package/dist/invoice-line-items/index.js +18 -0
- package/dist/invoice-line-items/index.js.map +1 -0
- package/dist/invoice-line-items/injection.d.ts +2 -0
- package/dist/invoice-line-items/injection.js +171 -0
- package/dist/invoice-line-items/injection.js.map +1 -0
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.js +19 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/recipients.d.ts +14 -0
- package/dist/notifications/recipients.js +87 -0
- package/dist/notifications/recipients.js.map +1 -0
- package/dist/notifications/subjects.d.ts +21 -0
- package/dist/notifications/subjects.js +121 -0
- package/dist/notifications/subjects.js.map +1 -0
- package/dist/operator-actions/commands.d.ts +41 -0
- package/dist/operator-actions/commands.js +233 -0
- package/dist/operator-actions/commands.js.map +1 -0
- package/dist/operator-actions/db/index.d.ts +4 -0
- package/dist/operator-actions/db/index.js +21 -0
- package/dist/operator-actions/db/index.js.map +1 -0
- package/dist/operator-actions/db/lifecycle-delivery.model.d.ts +29 -0
- package/dist/operator-actions/db/lifecycle-delivery.model.js +85 -0
- package/dist/operator-actions/db/lifecycle-delivery.model.js.map +1 -0
- package/dist/operator-actions/db/lifecycle-delivery.table.d.ts +26 -0
- package/dist/operator-actions/db/lifecycle-delivery.table.js +107 -0
- package/dist/operator-actions/db/lifecycle-delivery.table.js.map +1 -0
- package/dist/operator-actions/db/operator-action.model.d.ts +33 -0
- package/dist/operator-actions/db/operator-action.model.js +63 -0
- package/dist/operator-actions/db/operator-action.model.js.map +1 -0
- package/dist/operator-actions/db/operator-action.table.d.ts +21 -0
- package/dist/operator-actions/db/operator-action.table.js +95 -0
- package/dist/operator-actions/db/operator-action.table.js.map +1 -0
- package/dist/operator-actions/index.d.ts +3 -0
- package/dist/operator-actions/index.js +20 -0
- package/dist/operator-actions/index.js.map +1 -0
- package/dist/operator-actions/journal.d.ts +22 -0
- package/dist/operator-actions/journal.js +183 -0
- package/dist/operator-actions/journal.js.map +1 -0
- package/dist/operator-actions/lifecycle-consumers.d.ts +12 -0
- package/dist/operator-actions/lifecycle-consumers.js +28 -0
- package/dist/operator-actions/lifecycle-consumers.js.map +1 -0
- package/dist/operator-actions/lifecycle-outbox.d.ts +19 -0
- package/dist/operator-actions/lifecycle-outbox.js +229 -0
- package/dist/operator-actions/lifecycle-outbox.js.map +1 -0
- package/dist/operator-actions/types.d.ts +11 -0
- package/dist/operator-actions/types.js +3 -0
- package/dist/operator-actions/types.js.map +1 -0
- package/dist/pages/definitions.d.ts +1 -0
- package/dist/pages/definitions.js +18 -0
- package/dist/pages/definitions.js.map +1 -0
- package/dist/pages/index.d.ts +4 -0
- package/dist/pages/index.js +21 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/module.d.ts +5 -0
- package/dist/pages/module.js +15 -0
- package/dist/pages/module.js.map +1 -0
- package/dist/pages/platform/categories.d.ts +6 -0
- package/dist/pages/platform/categories.js +48 -0
- package/dist/pages/platform/categories.js.map +1 -0
- package/dist/pages/platform/credit-notes.d.ts +9 -0
- package/dist/pages/platform/credit-notes.js +40 -0
- package/dist/pages/platform/credit-notes.js.map +1 -0
- package/dist/pages/platform/dashboard.d.ts +10 -0
- package/dist/pages/platform/dashboard.js +95 -0
- package/dist/pages/platform/dashboard.js.map +1 -0
- package/dist/pages/platform/features.d.ts +9 -0
- package/dist/pages/platform/features.js +42 -0
- package/dist/pages/platform/features.js.map +1 -0
- package/dist/pages/platform/index.d.ts +15 -0
- package/dist/pages/platform/index.js +32 -0
- package/dist/pages/platform/index.js.map +1 -0
- package/dist/pages/platform/invoices.d.ts +9 -0
- package/dist/pages/platform/invoices.js +63 -0
- package/dist/pages/platform/invoices.js.map +1 -0
- package/dist/pages/platform/plans/edit.d.ts +17 -0
- package/dist/pages/platform/plans/edit.js +213 -0
- package/dist/pages/platform/plans/edit.js.map +1 -0
- package/dist/pages/platform/plans/index.d.ts +9 -0
- package/dist/pages/platform/plans/index.js +99 -0
- package/dist/pages/platform/plans/index.js.map +1 -0
- package/dist/pages/platform/plans/migrations.d.ts +9 -0
- package/dist/pages/platform/plans/migrations.js +38 -0
- package/dist/pages/platform/plans/migrations.js.map +1 -0
- package/dist/pages/platform/segments/index.d.ts +9 -0
- package/dist/pages/platform/segments/index.js +59 -0
- package/dist/pages/platform/segments/index.js.map +1 -0
- package/dist/pages/platform/settings.d.ts +9 -0
- package/dist/pages/platform/settings.js +147 -0
- package/dist/pages/platform/settings.js.map +1 -0
- package/dist/pages/platform/support.d.ts +9 -0
- package/dist/pages/platform/support.js +29 -0
- package/dist/pages/platform/support.js.map +1 -0
- package/dist/pages/platform/users/detail.d.ts +9 -0
- package/dist/pages/platform/users/detail.js +78 -0
- package/dist/pages/platform/users/detail.js.map +1 -0
- package/dist/pages/platform/users/index.d.ts +9 -0
- package/dist/pages/platform/users/index.js +77 -0
- package/dist/pages/platform/users/index.js.map +1 -0
- package/dist/pages/platform/workspaces/detail.d.ts +9 -0
- package/dist/pages/platform/workspaces/detail.js +120 -0
- package/dist/pages/platform/workspaces/detail.js.map +1 -0
- package/dist/pages/platform/workspaces/index.d.ts +35 -0
- package/dist/pages/platform/workspaces/index.js +300 -0
- package/dist/pages/platform/workspaces/index.js.map +1 -0
- package/dist/pages/public/auth/index.d.ts +1 -0
- package/dist/pages/public/auth/index.js +18 -0
- package/dist/pages/public/auth/index.js.map +1 -0
- package/dist/pages/public/auth/no-workspace.d.ts +9 -0
- package/dist/pages/public/auth/no-workspace.js +26 -0
- package/dist/pages/public/auth/no-workspace.js.map +1 -0
- package/dist/pages/public/index.d.ts +3 -0
- package/dist/pages/public/index.js +20 -0
- package/dist/pages/public/index.js.map +1 -0
- package/dist/pages/public/legal/index.d.ts +3 -0
- package/dist/pages/public/legal/index.js +20 -0
- package/dist/pages/public/legal/index.js.map +1 -0
- package/dist/pages/public/legal/privacy-policy.d.ts +9 -0
- package/dist/pages/public/legal/privacy-policy.js +29 -0
- package/dist/pages/public/legal/privacy-policy.js.map +1 -0
- package/dist/pages/public/legal/terms-and-conditions.d.ts +9 -0
- package/dist/pages/public/legal/terms-and-conditions.js +29 -0
- package/dist/pages/public/legal/terms-and-conditions.js.map +1 -0
- package/dist/pages/public/legal/terms-of-use.d.ts +9 -0
- package/dist/pages/public/legal/terms-of-use.js +29 -0
- package/dist/pages/public/legal/terms-of-use.js.map +1 -0
- package/dist/pages/public/register.d.ts +9 -0
- package/dist/pages/public/register.js +29 -0
- package/dist/pages/public/register.js.map +1 -0
- package/dist/pages/public/screens.d.ts +3 -0
- package/dist/pages/public/screens.js +50 -0
- package/dist/pages/public/screens.js.map +1 -0
- package/dist/pages/tenant/billing.d.ts +15 -0
- package/dist/pages/tenant/billing.js +87 -0
- package/dist/pages/tenant/billing.js.map +1 -0
- package/dist/pages/tenant/data-export.d.ts +10 -0
- package/dist/pages/tenant/data-export.js +32 -0
- package/dist/pages/tenant/data-export.js.map +1 -0
- package/dist/pages/tenant/general.d.ts +10 -0
- package/dist/pages/tenant/general.js +33 -0
- package/dist/pages/tenant/general.js.map +1 -0
- package/dist/pages/tenant/index.d.ts +6 -0
- package/dist/pages/tenant/index.js +23 -0
- package/dist/pages/tenant/index.js.map +1 -0
- package/dist/pages/tenant/members-seat-quota.d.ts +3 -0
- package/dist/pages/tenant/members-seat-quota.js +25 -0
- package/dist/pages/tenant/members-seat-quota.js.map +1 -0
- package/dist/pages/tenant/support.d.ts +9 -0
- package/dist/pages/tenant/support.js +27 -0
- package/dist/pages/tenant/support.js.map +1 -0
- package/dist/pages/tenant/workspace-suspended.d.ts +9 -0
- package/dist/pages/tenant/workspace-suspended.js +26 -0
- package/dist/pages/tenant/workspace-suspended.js.map +1 -0
- package/dist/plan-changes/deferred.d.ts +18 -0
- package/dist/plan-changes/deferred.js +225 -0
- package/dist/plan-changes/deferred.js.map +1 -0
- package/dist/plan-changes/index.d.ts +1 -0
- package/dist/plan-changes/index.js +18 -0
- package/dist/plan-changes/index.js.map +1 -0
- package/dist/plans/index.d.ts +4 -0
- package/dist/plans/index.js +21 -0
- package/dist/plans/index.js.map +1 -0
- package/dist/plans/seat-capacity.d.ts +9 -0
- package/dist/plans/seat-capacity.js +38 -0
- package/dist/plans/seat-capacity.js.map +1 -0
- package/dist/plans/seat-hooks.d.ts +1 -0
- package/dist/plans/seat-hooks.js +80 -0
- package/dist/plans/seat-hooks.js.map +1 -0
- package/dist/plans/seat-sync.d.ts +10 -0
- package/dist/plans/seat-sync.js +23 -0
- package/dist/plans/seat-sync.js.map +1 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.js +20 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/platformOwner/billing-settings.d.ts +20 -0
- package/dist/routes/platformOwner/billing-settings.js +98 -0
- package/dist/routes/platformOwner/billing-settings.js.map +1 -0
- package/dist/routes/platformOwner/credit-notes-issue.d.ts +16 -0
- package/dist/routes/platformOwner/credit-notes-issue.js +56 -0
- package/dist/routes/platformOwner/credit-notes-issue.js.map +1 -0
- package/dist/routes/platformOwner/dashboard.d.ts +62 -0
- package/dist/routes/platformOwner/dashboard.js +310 -0
- package/dist/routes/platformOwner/dashboard.js.map +1 -0
- package/dist/routes/platformOwner/index.d.ts +12 -0
- package/dist/routes/platformOwner/index.js +29 -0
- package/dist/routes/platformOwner/index.js.map +1 -0
- package/dist/routes/platformOwner/plan-deletion.d.ts +30 -0
- package/dist/routes/platformOwner/plan-deletion.js +156 -0
- package/dist/routes/platformOwner/plan-deletion.js.map +1 -0
- package/dist/routes/platformOwner/plans.d.ts +99 -0
- package/dist/routes/platformOwner/plans.js +376 -0
- package/dist/routes/platformOwner/plans.js.map +1 -0
- package/dist/routes/platformOwner/platform-notes.d.ts +36 -0
- package/dist/routes/platformOwner/platform-notes.js +149 -0
- package/dist/routes/platformOwner/platform-notes.js.map +1 -0
- package/dist/routes/platformOwner/platform-owners.d.ts +12 -0
- package/dist/routes/platformOwner/platform-owners.js +77 -0
- package/dist/routes/platformOwner/platform-owners.js.map +1 -0
- package/dist/routes/platformOwner/segments.d.ts +24 -0
- package/dist/routes/platformOwner/segments.js +174 -0
- package/dist/routes/platformOwner/segments.js.map +1 -0
- package/dist/routes/platformOwner/support.d.ts +21 -0
- package/dist/routes/platformOwner/support.js +180 -0
- package/dist/routes/platformOwner/support.js.map +1 -0
- package/dist/routes/platformOwner/users-detail.d.ts +26 -0
- package/dist/routes/platformOwner/users-detail.js +82 -0
- package/dist/routes/platformOwner/users-detail.js.map +1 -0
- package/dist/routes/platformOwner/workspace-operator-actions.d.ts +34 -0
- package/dist/routes/platformOwner/workspace-operator-actions.js +183 -0
- package/dist/routes/platformOwner/workspace-operator-actions.js.map +1 -0
- package/dist/routes/platformOwner/workspaces-admin.d.ts +27 -0
- package/dist/routes/platformOwner/workspaces-admin.js +140 -0
- package/dist/routes/platformOwner/workspaces-admin.js.map +1 -0
- package/dist/routes/public/index.d.ts +3 -0
- package/dist/routes/public/index.js +20 -0
- package/dist/routes/public/index.js.map +1 -0
- package/dist/routes/public/legal-documents.d.ts +15 -0
- package/dist/routes/public/legal-documents.js +72 -0
- package/dist/routes/public/legal-documents.js.map +1 -0
- package/dist/routes/public/register.d.ts +48 -0
- package/dist/routes/public/register.js +193 -0
- package/dist/routes/public/register.js.map +1 -0
- package/dist/routes/public/webhooks/index.d.ts +1 -0
- package/dist/routes/public/webhooks/index.js +18 -0
- package/dist/routes/public/webhooks/index.js.map +1 -0
- package/dist/routes/public/webhooks/stripe.d.ts +7 -0
- package/dist/routes/public/webhooks/stripe.js +41 -0
- package/dist/routes/public/webhooks/stripe.js.map +1 -0
- package/dist/routes/tenant/billing-portal.d.ts +31 -0
- package/dist/routes/tenant/billing-portal.js +117 -0
- package/dist/routes/tenant/billing-portal.js.map +1 -0
- package/dist/routes/tenant/billing-self-refund.d.ts +18 -0
- package/dist/routes/tenant/billing-self-refund.js +199 -0
- package/dist/routes/tenant/billing-self-refund.js.map +1 -0
- package/dist/routes/tenant/data-export.d.ts +16 -0
- package/dist/routes/tenant/data-export.js +102 -0
- package/dist/routes/tenant/data-export.js.map +1 -0
- package/dist/routes/tenant/index.d.ts +9 -0
- package/dist/routes/tenant/index.js +26 -0
- package/dist/routes/tenant/index.js.map +1 -0
- package/dist/routes/tenant/support.d.ts +27 -0
- package/dist/routes/tenant/support.js +185 -0
- package/dist/routes/tenant/support.js.map +1 -0
- package/dist/routes/tenant/tenant-billing.d.ts +26 -0
- package/dist/routes/tenant/tenant-billing.js +148 -0
- package/dist/routes/tenant/tenant-billing.js.map +1 -0
- package/dist/routes/tenant/tenant-plan-ops.d.ts +57 -0
- package/dist/routes/tenant/tenant-plan-ops.js +244 -0
- package/dist/routes/tenant/tenant-plan-ops.js.map +1 -0
- package/dist/routes/tenant/tenant-plan.d.ts +19 -0
- package/dist/routes/tenant/tenant-plan.js +172 -0
- package/dist/routes/tenant/tenant-plan.js.map +1 -0
- package/dist/routes/tenant/tenant-seats.d.ts +17 -0
- package/dist/routes/tenant/tenant-seats.js +58 -0
- package/dist/routes/tenant/tenant-seats.js.map +1 -0
- package/dist/routes/tenant/workspaces.d.ts +78 -0
- package/dist/routes/tenant/workspaces.js +301 -0
- package/dist/routes/tenant/workspaces.js.map +1 -0
- package/dist/stripe/client.d.ts +6 -0
- package/dist/stripe/client.js +44 -0
- package/dist/stripe/client.js.map +1 -0
- package/dist/stripe/customer-balance.d.ts +12 -0
- package/dist/stripe/customer-balance.js +16 -0
- package/dist/stripe/customer-balance.js.map +1 -0
- package/dist/stripe/customer-billing.d.ts +33 -0
- package/dist/stripe/customer-billing.js +140 -0
- package/dist/stripe/customer-billing.js.map +1 -0
- package/dist/stripe/index.d.ts +11 -0
- package/dist/stripe/index.js +28 -0
- package/dist/stripe/index.js.map +1 -0
- package/dist/stripe/plan-schedule.d.ts +9 -0
- package/dist/stripe/plan-schedule.js +83 -0
- package/dist/stripe/plan-schedule.js.map +1 -0
- package/dist/stripe/proration.d.ts +2 -0
- package/dist/stripe/proration.js +19 -0
- package/dist/stripe/proration.js.map +1 -0
- package/dist/stripe/sync-plan.d.ts +2 -0
- package/dist/stripe/sync-plan.js +66 -0
- package/dist/stripe/sync-plan.js.map +1 -0
- package/dist/stripe/webhook-checkout.d.ts +3 -0
- package/dist/stripe/webhook-checkout.js +157 -0
- package/dist/stripe/webhook-checkout.js.map +1 -0
- package/dist/stripe/webhook-credit-notes.d.ts +3 -0
- package/dist/stripe/webhook-credit-notes.js +192 -0
- package/dist/stripe/webhook-credit-notes.js.map +1 -0
- package/dist/stripe/webhook-dispatch.d.ts +2 -0
- package/dist/stripe/webhook-dispatch.js +45 -0
- package/dist/stripe/webhook-dispatch.js.map +1 -0
- package/dist/stripe/webhook-handlers.d.ts +11 -0
- package/dist/stripe/webhook-handlers.js +318 -0
- package/dist/stripe/webhook-handlers.js.map +1 -0
- package/dist/stripe/webhook-shared.d.ts +28 -0
- package/dist/stripe/webhook-shared.js +126 -0
- package/dist/stripe/webhook-shared.js.map +1 -0
- package/dist/stripe/workspace-checkout.d.ts +29 -0
- package/dist/stripe/workspace-checkout.js +106 -0
- package/dist/stripe/workspace-checkout.js.map +1 -0
- package/dist/support/admissions.d.ts +36 -0
- package/dist/support/admissions.js +136 -0
- package/dist/support/admissions.js.map +1 -0
- package/dist/support/attachments.d.ts +12 -0
- package/dist/support/attachments.js +56 -0
- package/dist/support/attachments.js.map +1 -0
- package/dist/support/config.d.ts +19 -0
- package/dist/support/config.js +75 -0
- package/dist/support/config.js.map +1 -0
- package/dist/support/index.d.ts +6 -0
- package/dist/support/index.js +23 -0
- package/dist/support/index.js.map +1 -0
- package/dist/support/operations.d.ts +51 -0
- package/dist/support/operations.js +109 -0
- package/dist/support/operations.js.map +1 -0
- package/dist/support/plans.d.ts +6 -0
- package/dist/support/plans.js +130 -0
- package/dist/support/plans.js.map +1 -0
- package/dist/support/publications.d.ts +8 -0
- package/dist/support/publications.js +106 -0
- package/dist/support/publications.js.map +1 -0
- package/dist/support/recovery.d.ts +7 -0
- package/dist/support/recovery.js +43 -0
- package/dist/support/recovery.js.map +1 -0
- package/dist/support/service.d.ts +51 -0
- package/dist/support/service.js +143 -0
- package/dist/support/service.js.map +1 -0
- package/dist/support/transitions.d.ts +4 -0
- package/dist/support/transitions.js +29 -0
- package/dist/support/transitions.js.map +1 -0
- package/dist/support/uploads.d.ts +39 -0
- package/dist/support/uploads.js +115 -0
- package/dist/support/uploads.js.map +1 -0
- package/dist/support/validation.d.ts +15 -0
- package/dist/support/validation.js +54 -0
- package/dist/support/validation.js.map +1 -0
- package/dist/types/config.d.ts +16 -0
- package/dist/types/config.js +3 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +18 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/billing-document-type.d.ts +3 -0
- package/dist/utils/billing-document-type.js +18 -0
- package/dist/utils/billing-document-type.js.map +1 -0
- package/dist/utils/billing-period-type.d.ts +3 -0
- package/dist/utils/billing-period-type.js +18 -0
- package/dist/utils/billing-period-type.js.map +1 -0
- package/dist/utils/email-hash.d.ts +1 -0
- package/dist/utils/email-hash.js +8 -0
- package/dist/utils/email-hash.js.map +1 -0
- package/dist/utils/export-dataset-stream.d.ts +19 -0
- package/dist/utils/export-dataset-stream.js +40 -0
- package/dist/utils/export-dataset-stream.js.map +1 -0
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +32 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/invoice-lines-type.d.ts +27 -0
- package/dist/utils/invoice-lines-type.js +34 -0
- package/dist/utils/invoice-lines-type.js.map +1 -0
- package/dist/utils/money-cents-type.d.ts +3 -0
- package/dist/utils/money-cents-type.js +18 -0
- package/dist/utils/money-cents-type.js.map +1 -0
- package/dist/utils/pagination.d.ts +22 -0
- package/dist/utils/pagination.js +35 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/parse-future-date.d.ts +1 -0
- package/dist/utils/parse-future-date.js +15 -0
- package/dist/utils/parse-future-date.js.map +1 -0
- package/dist/utils/row-instance.d.ts +1 -0
- package/dist/utils/row-instance.js +8 -0
- package/dist/utils/row-instance.js.map +1 -0
- package/dist/utils/segment-conditions-type.d.ts +33 -0
- package/dist/utils/segment-conditions-type.js +86 -0
- package/dist/utils/segment-conditions-type.js.map +1 -0
- package/dist/utils/segment-evaluator.d.ts +3 -0
- package/dist/utils/segment-evaluator.js +117 -0
- package/dist/utils/segment-evaluator.js.map +1 -0
- package/dist/utils/segment-fields.d.ts +19 -0
- package/dist/utils/segment-fields.js +212 -0
- package/dist/utils/segment-fields.js.map +1 -0
- package/dist/utils/segment-matching.d.ts +56 -0
- package/dist/utils/segment-matching.js +200 -0
- package/dist/utils/segment-matching.js.map +1 -0
- package/dist/utils/segment-name-cache.d.ts +2 -0
- package/dist/utils/segment-name-cache.js +22 -0
- package/dist/utils/segment-name-cache.js.map +1 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.js +10 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/workers/index.d.ts +1 -0
- package/dist/workers/index.js +18 -0
- package/dist/workers/index.js.map +1 -0
- package/dist/workers/plan-migration.d.ts +4 -0
- package/dist/workers/plan-migration.js +206 -0
- package/dist/workers/plan-migration.js.map +1 -0
- package/dist/workspaces/billing-profile.d.ts +3 -0
- package/dist/workspaces/billing-profile.js +55 -0
- package/dist/workspaces/billing-profile.js.map +1 -0
- package/dist/workspaces/complimentary.d.ts +7 -0
- package/dist/workspaces/complimentary.js +47 -0
- package/dist/workspaces/complimentary.js.map +1 -0
- package/dist/workspaces/db/card-capacity.model.d.ts +30 -0
- package/dist/workspaces/db/card-capacity.model.js +98 -0
- package/dist/workspaces/db/card-capacity.model.js.map +1 -0
- package/dist/workspaces/db/card-capacity.table.d.ts +11 -0
- package/dist/workspaces/db/card-capacity.table.js +34 -0
- package/dist/workspaces/db/card-capacity.table.js.map +1 -0
- package/dist/workspaces/db/provisioning-attempt.model.d.ts +24 -0
- package/dist/workspaces/db/provisioning-attempt.model.js +33 -0
- package/dist/workspaces/db/provisioning-attempt.model.js.map +1 -0
- package/dist/workspaces/db/provisioning-attempt.table.d.ts +18 -0
- package/dist/workspaces/db/provisioning-attempt.table.js +77 -0
- package/dist/workspaces/db/provisioning-attempt.table.js.map +1 -0
- package/dist/workspaces/db/trial-identity.model.d.ts +25 -0
- package/dist/workspaces/db/trial-identity.model.js +62 -0
- package/dist/workspaces/db/trial-identity.model.js.map +1 -0
- package/dist/workspaces/db/trial-identity.table.d.ts +7 -0
- package/dist/workspaces/db/trial-identity.table.js +34 -0
- package/dist/workspaces/db/trial-identity.table.js.map +1 -0
- package/dist/workspaces/deletion.d.ts +8 -0
- package/dist/workspaces/deletion.js +75 -0
- package/dist/workspaces/deletion.js.map +1 -0
- package/dist/workspaces/free-workspace-guard.d.ts +11 -0
- package/dist/workspaces/free-workspace-guard.js +96 -0
- package/dist/workspaces/free-workspace-guard.js.map +1 -0
- package/dist/workspaces/index.d.ts +6 -0
- package/dist/workspaces/index.js +23 -0
- package/dist/workspaces/index.js.map +1 -0
- package/dist/workspaces/provisioning-rollback.d.ts +2 -0
- package/dist/workspaces/provisioning-rollback.js +78 -0
- package/dist/workspaces/provisioning-rollback.js.map +1 -0
- package/dist/workspaces/provisioning-state.d.ts +6 -0
- package/dist/workspaces/provisioning-state.js +91 -0
- package/dist/workspaces/provisioning-state.js.map +1 -0
- package/dist/workspaces/provisioning.d.ts +49 -0
- package/dist/workspaces/provisioning.js +263 -0
- package/dist/workspaces/provisioning.js.map +1 -0
- package/dist/workspaces/registration-extras.d.ts +1 -0
- package/dist/workspaces/registration-extras.js +50 -0
- package/dist/workspaces/registration-extras.js.map +1 -0
- package/dist/workspaces/suspension.d.ts +13 -0
- package/dist/workspaces/suspension.js +124 -0
- package/dist/workspaces/suspension.js.map +1 -0
- package/frontend-vue/.editorconfig +12 -0
- package/frontend-vue/.prettierrc +11 -0
- package/frontend-vue/app/build/.gitkeep +0 -0
- package/frontend-vue/app/components/CardGrid.vue +77 -0
- package/frontend-vue/app/components/CustomerPortalButton.vue +45 -0
- package/frontend-vue/app/components/DataExport.vue +300 -0
- package/frontend-vue/app/components/DeletePlanModal.vue +223 -0
- package/frontend-vue/app/components/FreeAccessBanner.vue +70 -0
- package/frontend-vue/app/components/InvoiceLines.vue +57 -0
- package/frontend-vue/app/components/IssueCreditNoteModal.vue +128 -0
- package/frontend-vue/app/components/LegalLayout.vue +63 -0
- package/frontend-vue/app/components/LoadFailure.vue +25 -0
- package/frontend-vue/app/components/PastDueAlert.vue +96 -0
- package/frontend-vue/app/components/PaymentMethodCard.vue +86 -0
- package/frontend-vue/app/components/PlanCard.vue +294 -0
- package/frontend-vue/app/components/PlanCardPublic.vue +59 -0
- package/frontend-vue/app/components/PlanCardsDisplay.vue +127 -0
- package/frontend-vue/app/components/PlanComparisonModal.vue +213 -0
- package/frontend-vue/app/components/PlanInheritancePicker.vue +329 -0
- package/frontend-vue/app/components/PlatformNotes.vue +281 -0
- package/frontend-vue/app/components/PlatformSupport.vue +461 -0
- package/frontend-vue/app/components/RecentActivity.vue +72 -0
- package/frontend-vue/app/components/RefundButton.vue +126 -0
- package/frontend-vue/app/components/Register.vue +174 -0
- package/frontend-vue/app/components/RegistrationClosed.vue +15 -0
- package/frontend-vue/app/components/SeatQuotaBanner.vue +78 -0
- package/frontend-vue/app/components/SegmentConditionsBuilder.vue +176 -0
- package/frontend-vue/app/components/SegmentConditionsGroup.vue +494 -0
- package/frontend-vue/app/components/SupportAttachmentUpload.vue +140 -0
- package/frontend-vue/app/components/SupportThreadHistory.vue +139 -0
- package/frontend-vue/app/components/TenantBillingForm.vue +208 -0
- package/frontend-vue/app/components/TenantPlanCard.vue +223 -0
- package/frontend-vue/app/components/TenantSupport.vue +415 -0
- package/frontend-vue/app/components/UserSummaryCard.vue +166 -0
- package/frontend-vue/app/components/WorkspaceBillingInfo.vue +68 -0
- package/frontend-vue/app/components/WorkspaceCompAccessButton.vue +198 -0
- package/frontend-vue/app/components/WorkspaceCreateModal.vue +256 -0
- package/frontend-vue/app/components/WorkspaceDangerZone.vue +146 -0
- package/frontend-vue/app/components/WorkspaceDetailHeader.vue +24 -0
- package/frontend-vue/app/components/WorkspaceIdentity.vue +99 -0
- package/frontend-vue/app/components/WorkspaceJoinButton.vue +50 -0
- package/frontend-vue/app/components/WorkspaceOperatorActions.vue +340 -0
- package/frontend-vue/app/components/WorkspaceSummaryCard.vue +161 -0
- package/frontend-vue/app/components/WorkspaceSuspendButton.vue +167 -0
- package/frontend-vue/app/components/WorkspaceSuspended.vue +164 -0
- package/frontend-vue/app/components/WorkspaceSwitcherWidget.vue +247 -0
- package/frontend-vue/app/composables/useApiErrorMessage.ts +35 -0
- package/frontend-vue/app/composables/useBillingCountries.ts +54 -0
- package/frontend-vue/app/composables/useBillingDocumentType.ts +7 -0
- package/frontend-vue/app/composables/useBillingStatus.ts +39 -0
- package/frontend-vue/app/composables/useCurrentWorkspace.ts +43 -0
- package/frontend-vue/app/composables/useDetailRefresh.ts +17 -0
- package/frontend-vue/app/composables/useExportHistory.ts +143 -0
- package/frontend-vue/app/composables/useInvoiceLines.ts +22 -0
- package/frontend-vue/app/composables/useMoneyFormat.ts +49 -0
- package/frontend-vue/app/composables/useMyWorkspaces.ts +47 -0
- package/frontend-vue/app/composables/usePlanFeatureFormat.ts +60 -0
- package/frontend-vue/app/composables/usePlanIntervalLabel.ts +30 -0
- package/frontend-vue/app/composables/useSaasRegistration.ts +522 -0
- package/frontend-vue/app/composables/useSeatQuota.ts +18 -0
- package/frontend-vue/app/composables/useSharedRequest.ts +54 -0
- package/frontend-vue/app/composables/useStripePaymentElement.ts +74 -0
- package/frontend-vue/app/composables/useSubscriptionAccess.ts +12 -0
- package/frontend-vue/app/composables/useSupportRequest.ts +74 -0
- package/frontend-vue/app/composables/useTenantPlan.ts +95 -0
- package/frontend-vue/app/composables/useTenantSwitch.ts +22 -0
- package/frontend-vue/app/composables/useWorkspaceAccessCache.ts +25 -0
- package/frontend-vue/app/custom-pages/auth/no-workspace.vue +495 -0
- package/frontend-vue/app/middleware/workspace-suspended.global.ts +56 -0
- package/frontend-vue/app/plugins/auth-links.ts +24 -0
- package/frontend-vue/app/plugins/billing-data-types.ts +62 -0
- package/frontend-vue/app/plugins/footer-links.ts +36 -0
- package/frontend-vue/app/plugins/plan-cards-display.client.ts +16 -0
- package/frontend-vue/app/plugins/segment-conditions-data-type.client.ts +122 -0
- package/frontend-vue/app/plugins/sidebar-widgets.ts +13 -0
- package/frontend-vue/app/types/support.ts +123 -0
- package/frontend-vue/dms.frontend.ts +68 -0
- package/frontend-vue/eslint.config.mjs +13 -0
- package/frontend-vue/i18n/locales/saas-en-GB.json +1285 -0
- package/frontend-vue/i18n/locales/saas-fr-FR.json +1285 -0
- package/frontend-vue/package.json +27 -0
- package/frontend-vue/pnpm-lock.yaml +4948 -0
- package/frontend-vue/pnpm-workspace.yaml +2 -0
- package/frontend-vue/server/routes/auth/finalize-workspace.post.ts +61 -0
- package/package.json +91 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
type MaybeRefOrGetter,
|
|
4
|
+
onMounted,
|
|
5
|
+
reactive,
|
|
6
|
+
ref,
|
|
7
|
+
toValue,
|
|
8
|
+
watch,
|
|
9
|
+
} from "vue";
|
|
10
|
+
import { formatMajorUnits } from "./useMoneyFormat";
|
|
11
|
+
import {
|
|
12
|
+
formatPlanIntervalLabel,
|
|
13
|
+
type PlanInterval,
|
|
14
|
+
} from "./usePlanIntervalLabel";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A plan as the public registration screen sees it.
|
|
18
|
+
*/
|
|
19
|
+
export interface RegistrationPlan {
|
|
20
|
+
_id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
price: number;
|
|
24
|
+
currency: string;
|
|
25
|
+
interval: PlanInterval;
|
|
26
|
+
trialDays: number;
|
|
27
|
+
audience: string;
|
|
28
|
+
borderColor: string | null;
|
|
29
|
+
borderLabel: string | null;
|
|
30
|
+
order: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RegistrationForm {
|
|
34
|
+
customerType: "individual" | "business";
|
|
35
|
+
email: string;
|
|
36
|
+
password: string;
|
|
37
|
+
name: string;
|
|
38
|
+
workspaceName: string;
|
|
39
|
+
companyName: string;
|
|
40
|
+
vatNumber: string;
|
|
41
|
+
country: string;
|
|
42
|
+
addressLine1: string;
|
|
43
|
+
postalCode: string;
|
|
44
|
+
city: string;
|
|
45
|
+
selectedPlanId: string | null;
|
|
46
|
+
hasAcceptedLegal: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Everything the submit guard looks at, independent of how it is bound. */
|
|
50
|
+
export interface RegistrationRequirements {
|
|
51
|
+
selectedPlanId: string | null;
|
|
52
|
+
isPaymentReady: boolean;
|
|
53
|
+
hasAcceptedLegal: boolean;
|
|
54
|
+
country: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type RegistrationTranslator = (
|
|
58
|
+
key: string,
|
|
59
|
+
params?: Record<string, string>,
|
|
60
|
+
) => string;
|
|
61
|
+
|
|
62
|
+
export interface UseSaasRegistrationOptions {
|
|
63
|
+
/**
|
|
64
|
+
* In-app route to land on once the workspace is provisioned. Landing on
|
|
65
|
+
* another origin is the consumer's own call to make, from the tenant id
|
|
66
|
+
* `submit()` returns.
|
|
67
|
+
*/
|
|
68
|
+
redirectTo?: string;
|
|
69
|
+
/**
|
|
70
|
+
* DOM id the Stripe payment element mounts into. Consumers that render
|
|
71
|
+
* several registration surfaces on one page must give distinct ids.
|
|
72
|
+
*/
|
|
73
|
+
paymentElementId?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Whatever the consuming SaaS captures on top of the billing fields, read
|
|
76
|
+
* when the visitor submits. It stays the consumer's data: dms-saas forwards
|
|
77
|
+
* it inside the provisioning transaction, to the consumer's own
|
|
78
|
+
* `TENANT_BEING_PROVISIONED` listener, and neither reads nor stores it. A
|
|
79
|
+
* listener that fails to write it cancels the whole registration.
|
|
80
|
+
*
|
|
81
|
+
* Keep it to what a form captures — the endpoint is public, so the API caps
|
|
82
|
+
* its serialised size, depth and number of keys and answers 400 above them.
|
|
83
|
+
*/
|
|
84
|
+
extras?: MaybeRefOrGetter<Record<string, unknown> | undefined>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface RegistrationRequirement {
|
|
88
|
+
errorKey: string;
|
|
89
|
+
isMet: (requirements: RegistrationRequirements) => boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface RegistrationAddress {
|
|
93
|
+
country: string;
|
|
94
|
+
line1?: string;
|
|
95
|
+
postalCode?: string;
|
|
96
|
+
city?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface RegistrationPayload {
|
|
100
|
+
email: string;
|
|
101
|
+
password: string;
|
|
102
|
+
name: string;
|
|
103
|
+
workspaceName: string;
|
|
104
|
+
planId: string;
|
|
105
|
+
customerType: string;
|
|
106
|
+
companyName?: string;
|
|
107
|
+
vatNumber?: string;
|
|
108
|
+
paymentMethodId: string;
|
|
109
|
+
address: RegistrationAddress;
|
|
110
|
+
extras?: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Everything but the card, which only exists once Stripe has confirmed it. */
|
|
114
|
+
type RegistrationSubmission = Omit<RegistrationPayload, "paymentMethodId">;
|
|
115
|
+
|
|
116
|
+
interface RegistrationResult {
|
|
117
|
+
tenantId: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface SetupIntentResponse {
|
|
121
|
+
clientSecret: string | null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface DmsSaasPublicRuntimeConfig {
|
|
125
|
+
stripePublishableKey?: string;
|
|
126
|
+
admissionMode?: "open" | "invitation-only";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const PLANS_ENDPOINT = "/api/saas/plans/public";
|
|
130
|
+
const SETUP_INTENT_ENDPOINT = "/api/saas/register/setup-intent";
|
|
131
|
+
const REGISTER_ENDPOINT = "/api/saas/register";
|
|
132
|
+
const DEFAULT_PAYMENT_ELEMENT_ID = "dms-saas-register-payment-element";
|
|
133
|
+
const DEFAULT_REDIRECT = "/auth/login";
|
|
134
|
+
const ANY_AUDIENCE = "any";
|
|
135
|
+
const PLAN_PRICE_KEY = "saas.register.price";
|
|
136
|
+
const NO_PLAN_ERROR_KEY = "saas.register.error.no_plan";
|
|
137
|
+
const NO_PAYMENT_ERROR_KEY = "saas.register.error.no_payment";
|
|
138
|
+
const LOAD_ERROR_KEY = "saas.register.error.load";
|
|
139
|
+
const SUBMIT_ERROR_KEY = "saas.register.error.failed";
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Checked in the order the visitor can act on them, so a missing plan is never
|
|
143
|
+
* reported as a payment failure.
|
|
144
|
+
*/
|
|
145
|
+
const REGISTRATION_REQUIREMENTS: RegistrationRequirement[] = [
|
|
146
|
+
{
|
|
147
|
+
errorKey: NO_PLAN_ERROR_KEY,
|
|
148
|
+
isMet: (requirements) => Boolean(requirements.selectedPlanId),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
errorKey: NO_PAYMENT_ERROR_KEY,
|
|
152
|
+
isMet: (requirements) => requirements.isPaymentReady,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
errorKey: "saas.register.error.legal_required",
|
|
156
|
+
isMet: (requirements) => requirements.hasAcceptedLegal,
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
errorKey: "saas.register.error.no_country",
|
|
160
|
+
isMet: (requirements) => Boolean(requirements.country),
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The one guard a consumer cannot get wrong on its own: the first requirement
|
|
166
|
+
* the visitor has not met yet, in the order they can act on them.
|
|
167
|
+
*
|
|
168
|
+
* @param requirements Current state of the registration form
|
|
169
|
+
* @returns Translation key of the first unmet requirement, or null
|
|
170
|
+
*/
|
|
171
|
+
export function firstMissingRegistrationRequirement(
|
|
172
|
+
requirements: RegistrationRequirements,
|
|
173
|
+
): string | null {
|
|
174
|
+
const missing = REGISTRATION_REQUIREMENTS.find(
|
|
175
|
+
(requirement) => !requirement.isMet(requirements),
|
|
176
|
+
);
|
|
177
|
+
return missing?.errorKey ?? null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function planIntervalLabel(
|
|
181
|
+
interval: PlanInterval,
|
|
182
|
+
translate: RegistrationTranslator,
|
|
183
|
+
): string {
|
|
184
|
+
return formatPlanIntervalLabel(interval, "saas.register.interval", translate);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Price the way a summary line reads it: amount and cadence as one string, in
|
|
189
|
+
* the visitor's locale, decimals dropped on whole amounts.
|
|
190
|
+
*
|
|
191
|
+
* Takes its locale and translator instead of reading them from the component
|
|
192
|
+
* context, so a consumer can price a plan outside a `setup()` call.
|
|
193
|
+
*
|
|
194
|
+
* @param plan Plan to price
|
|
195
|
+
* @param locale BCP 47 locale the amount is formatted in
|
|
196
|
+
* @param translate Translation function for the price pattern and the cadence
|
|
197
|
+
* @returns Localised price, e.g. `20 €/mois`
|
|
198
|
+
*/
|
|
199
|
+
export function formatRegistrationPlanPrice(
|
|
200
|
+
plan: RegistrationPlan,
|
|
201
|
+
locale: string,
|
|
202
|
+
translate: RegistrationTranslator,
|
|
203
|
+
): string {
|
|
204
|
+
const price = formatMajorUnits(plan.price, plan.currency, locale, {
|
|
205
|
+
hideWholeAmountDecimals: true,
|
|
206
|
+
});
|
|
207
|
+
return translate(PLAN_PRICE_KEY, {
|
|
208
|
+
price,
|
|
209
|
+
interval: planIntervalLabel(plan.interval, translate),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The offer a customer type is entitled to, in the order it should read.
|
|
215
|
+
*
|
|
216
|
+
* Sorted here because the endpoint returns plans in storage order, and the
|
|
217
|
+
* first plan on offer is the one that ends up pre-selected: it has to be the
|
|
218
|
+
* one the operator ranked first, not the one created first.
|
|
219
|
+
*
|
|
220
|
+
* @param all Every publicly visible plan
|
|
221
|
+
* @param customerType Customer type the visitor declared
|
|
222
|
+
* @returns Plans that customer type may subscribe to
|
|
223
|
+
*/
|
|
224
|
+
export function resolveOfferedPlans(
|
|
225
|
+
all: RegistrationPlan[],
|
|
226
|
+
customerType: string,
|
|
227
|
+
): RegistrationPlan[] {
|
|
228
|
+
return all
|
|
229
|
+
.filter(
|
|
230
|
+
(plan) => plan.audience === ANY_AUDIENCE || plan.audience === customerType,
|
|
231
|
+
)
|
|
232
|
+
.sort((left, right) => left.order - right.order);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The plan selection to hold on to now that this is the offer.
|
|
237
|
+
*
|
|
238
|
+
* Switching customer type reshuffles the offer: a selection the visitor can no
|
|
239
|
+
* longer subscribe to would travel to the API and come back rejected, so it
|
|
240
|
+
* falls back to the first plan they may actually buy.
|
|
241
|
+
*
|
|
242
|
+
* @param available Plans the current customer type may subscribe to
|
|
243
|
+
* @param current Plan id currently selected, if any
|
|
244
|
+
* @returns Plan id to select, or null when nothing is on offer
|
|
245
|
+
*/
|
|
246
|
+
export function resolveSelectedPlanId(
|
|
247
|
+
available: RegistrationPlan[],
|
|
248
|
+
current: string | null,
|
|
249
|
+
): string | null {
|
|
250
|
+
const isStillOffered = available.some((plan) => plan._id === current);
|
|
251
|
+
return isStillOffered ? current : (available[0]?._id ?? null);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function optionalField(value: string): string | undefined {
|
|
255
|
+
return value.trim() || undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* The consumer's capture, detached from the consumer.
|
|
260
|
+
*
|
|
261
|
+
* An empty capture is no capture, and a live one is not a capture at all: the
|
|
262
|
+
* object is frozen as it will be serialised, so a consumer whose own form is
|
|
263
|
+
* still editable while the card is being confirmed cannot change what was
|
|
264
|
+
* submitted — or make it too large to accept once the card has gone through.
|
|
265
|
+
*
|
|
266
|
+
* @param extras Whatever the consumer handed over, possibly reactive
|
|
267
|
+
* @returns A detached copy, or undefined when there is nothing to send
|
|
268
|
+
*/
|
|
269
|
+
export function snapshotRegistrationExtras(
|
|
270
|
+
extras: Record<string, unknown> | undefined,
|
|
271
|
+
): Record<string, unknown> | undefined {
|
|
272
|
+
if (!extras || Object.keys(extras).length === 0) return undefined;
|
|
273
|
+
return JSON.parse(JSON.stringify(extras)) as Record<string, unknown>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The public registration flow, without a single line of presentation.
|
|
278
|
+
*
|
|
279
|
+
* dms-saas owns what is dangerous to re-derive — the Stripe setup intent and
|
|
280
|
+
* card confirmation, the order the fields are validated in, the shape of the
|
|
281
|
+
* provisioning call, the mapping from API failures to a message — and hands
|
|
282
|
+
* the consumer plain state to bind and one action to call. Each SaaS then
|
|
283
|
+
* writes the markup its conversion funnel needs: the fields it groups, the way
|
|
284
|
+
* it presents plans, whether it is one page or three steps, its own copy.
|
|
285
|
+
*
|
|
286
|
+
* The screen shipped in this package is one consumer among others: replace it
|
|
287
|
+
* by turning it off (`publicScreens.register: false` in the module config) and
|
|
288
|
+
* registering your own page on the `register` slug, then build the markup you
|
|
289
|
+
* want on this composable rather than forking its logic.
|
|
290
|
+
*
|
|
291
|
+
* @param options Landing route and payment element id
|
|
292
|
+
* @returns Bindable form state, plan data, submission action and status
|
|
293
|
+
*/
|
|
294
|
+
export function useSaasRegistration(options: UseSaasRegistrationOptions = {}) {
|
|
295
|
+
const { $authFetch } = useAuthFetch();
|
|
296
|
+
const { resolveApiError } = useApiErrorMessage();
|
|
297
|
+
const { countryItems } = useBillingCountries();
|
|
298
|
+
const { $i18n } = useDmsApp();
|
|
299
|
+
const config = useDmsRuntimeConfig();
|
|
300
|
+
|
|
301
|
+
const translate: RegistrationTranslator = (key, params) =>
|
|
302
|
+
params ? $i18n.t(key, params) : $i18n.t(key);
|
|
303
|
+
|
|
304
|
+
const paymentElementId =
|
|
305
|
+
options.paymentElementId ?? DEFAULT_PAYMENT_ELEMENT_ID;
|
|
306
|
+
const redirectTo = options.redirectTo ?? DEFAULT_REDIRECT;
|
|
307
|
+
|
|
308
|
+
const form = reactive<RegistrationForm>({
|
|
309
|
+
customerType: "individual",
|
|
310
|
+
email: "",
|
|
311
|
+
password: "",
|
|
312
|
+
name: "",
|
|
313
|
+
workspaceName: "",
|
|
314
|
+
companyName: "",
|
|
315
|
+
vatNumber: "",
|
|
316
|
+
country: "",
|
|
317
|
+
addressLine1: "",
|
|
318
|
+
postalCode: "",
|
|
319
|
+
city: "",
|
|
320
|
+
selectedPlanId: null,
|
|
321
|
+
hasAcceptedLegal: false,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const allPlans = ref<RegistrationPlan[]>([]);
|
|
325
|
+
const isLoading = ref(true);
|
|
326
|
+
const isSubmitting = ref(false);
|
|
327
|
+
const errorMessage = ref<string | null>(null);
|
|
328
|
+
const stripeHandle = ref<ReturnType<typeof useStripePaymentElement> | null>(
|
|
329
|
+
null,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const stripePublishableKey = computed<string>(
|
|
333
|
+
() =>
|
|
334
|
+
(config.public.dmsSaas as DmsSaasPublicRuntimeConfig | undefined)
|
|
335
|
+
?.stripePublishableKey ?? "",
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
/** Plans the current customer type may subscribe to, in the operator's order. */
|
|
339
|
+
const plans = computed(() =>
|
|
340
|
+
resolveOfferedPlans(allPlans.value, form.customerType),
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
const selectedPlan = computed(
|
|
344
|
+
() => plans.value.find((plan) => plan._id === form.selectedPlanId) ?? null,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Whether the card field has been wired to Stripe — false as long as the
|
|
349
|
+
* publishable key or the setup intent is missing.
|
|
350
|
+
*/
|
|
351
|
+
const isPaymentReady = computed(() => stripeHandle.value !== null);
|
|
352
|
+
|
|
353
|
+
const requirements = computed<RegistrationRequirements>(() => ({
|
|
354
|
+
selectedPlanId: form.selectedPlanId,
|
|
355
|
+
isPaymentReady: isPaymentReady.value,
|
|
356
|
+
hasAcceptedLegal: form.hasAcceptedLegal,
|
|
357
|
+
country: form.country,
|
|
358
|
+
}));
|
|
359
|
+
|
|
360
|
+
const missingRequirement = computed(() =>
|
|
361
|
+
firstMissingRegistrationRequirement(requirements.value),
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const canSubmit = computed(
|
|
365
|
+
() => !isSubmitting.value && missingRequirement.value === null,
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
function formatPlanPrice(plan: RegistrationPlan): string {
|
|
369
|
+
return formatRegistrationPlanPrice(plan, $i18n.locale.value, translate);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Repaired synchronously, not on the next flush: a consumer that switches
|
|
373
|
+
// customer type and submits in the same tick would otherwise send the plan
|
|
374
|
+
// the visitor can no longer buy.
|
|
375
|
+
watch(
|
|
376
|
+
plans,
|
|
377
|
+
(available) => {
|
|
378
|
+
form.selectedPlanId = resolveSelectedPlanId(
|
|
379
|
+
available,
|
|
380
|
+
form.selectedPlanId,
|
|
381
|
+
);
|
|
382
|
+
},
|
|
383
|
+
{ flush: "sync" },
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
async function loadPlans(): Promise<void> {
|
|
387
|
+
allPlans.value = await $authFetch<RegistrationPlan[]>(PLANS_ENDPOINT);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function mountPaymentElement(): Promise<void> {
|
|
391
|
+
if (!stripePublishableKey.value) return;
|
|
392
|
+
const setup = await $authFetch<SetupIntentResponse>(SETUP_INTENT_ENDPOINT);
|
|
393
|
+
if (!setup.clientSecret) return;
|
|
394
|
+
stripeHandle.value = useStripePaymentElement({
|
|
395
|
+
publishableKey: stripePublishableKey.value,
|
|
396
|
+
clientSecret: setup.clientSecret,
|
|
397
|
+
containerId: paymentElementId,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function confirmPaymentMethod(): Promise<string | null> {
|
|
402
|
+
const confirmation = await stripeHandle.value?.confirmAndGetPaymentMethod();
|
|
403
|
+
if (confirmation?.paymentMethodId) return confirmation.paymentMethodId;
|
|
404
|
+
errorMessage.value =
|
|
405
|
+
confirmation?.error?.message ?? translate(NO_PAYMENT_ERROR_KEY);
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The form as it stood when the visitor submitted.
|
|
411
|
+
*
|
|
412
|
+
* Taken whole, before the card confirmation: that step can hold a 3-D Secure
|
|
413
|
+
* challenge open for seconds while the fields stay editable, and a payload
|
|
414
|
+
* half read before and half after would pair, say, the plan chosen then with
|
|
415
|
+
* the customer type chosen since — which the API rejects, once the card has
|
|
416
|
+
* already been confirmed.
|
|
417
|
+
*/
|
|
418
|
+
function captureSubmission(planId: string): RegistrationSubmission {
|
|
419
|
+
const isBusiness = form.customerType === "business";
|
|
420
|
+
return {
|
|
421
|
+
email: form.email,
|
|
422
|
+
password: form.password,
|
|
423
|
+
name: form.name,
|
|
424
|
+
workspaceName: form.workspaceName,
|
|
425
|
+
planId,
|
|
426
|
+
customerType: form.customerType,
|
|
427
|
+
companyName: isBusiness ? form.companyName : undefined,
|
|
428
|
+
vatNumber: isBusiness ? optionalField(form.vatNumber) : undefined,
|
|
429
|
+
address: {
|
|
430
|
+
country: form.country,
|
|
431
|
+
line1: optionalField(form.addressLine1),
|
|
432
|
+
postalCode: optionalField(form.postalCode),
|
|
433
|
+
city: optionalField(form.city),
|
|
434
|
+
},
|
|
435
|
+
extras: snapshotRegistrationExtras(toValue(options.extras)),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Confirm the card, provision the workspace, land on the configured route.
|
|
441
|
+
*
|
|
442
|
+
* Everything the caller needs to know travels through `errorMessage`:
|
|
443
|
+
* nothing throws, so a consumer template can bind the action directly.
|
|
444
|
+
*
|
|
445
|
+
* @returns Tenant id when the workspace was provisioned, null otherwise
|
|
446
|
+
*/
|
|
447
|
+
async function submit(): Promise<string | null> {
|
|
448
|
+
// A card is confirmed and an account created in here: a second call while
|
|
449
|
+
// the first is in flight would run both again.
|
|
450
|
+
if (isSubmitting.value) return null;
|
|
451
|
+
|
|
452
|
+
const planId = form.selectedPlanId;
|
|
453
|
+
if (missingRequirement.value || !planId) {
|
|
454
|
+
errorMessage.value = translate(
|
|
455
|
+
missingRequirement.value ?? NO_PLAN_ERROR_KEY,
|
|
456
|
+
);
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
errorMessage.value = null;
|
|
461
|
+
isSubmitting.value = true;
|
|
462
|
+
let tenantId: string | null = null;
|
|
463
|
+
try {
|
|
464
|
+
// Inside the guard: freezing the capture reads the consumer's object,
|
|
465
|
+
// and a consumer's object is never guaranteed to be serialisable.
|
|
466
|
+
const submission = captureSubmission(planId);
|
|
467
|
+
const paymentMethodId = await confirmPaymentMethod();
|
|
468
|
+
if (!paymentMethodId) return null;
|
|
469
|
+
|
|
470
|
+
const result = await $authFetch<RegistrationResult>(REGISTER_ENDPOINT, {
|
|
471
|
+
method: "POST",
|
|
472
|
+
body: { ...submission, paymentMethodId },
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
tenantId = result.tenantId ?? null;
|
|
476
|
+
if (tenantId) await navigateDms(redirectTo);
|
|
477
|
+
return tenantId;
|
|
478
|
+
} catch (error) {
|
|
479
|
+
// Once the workspace exists, whatever failed after it is a landing
|
|
480
|
+
// problem: calling it a failed registration sends a visitor who has
|
|
481
|
+
// already paid back through a flow that would charge them again.
|
|
482
|
+
if (tenantId) return tenantId;
|
|
483
|
+
errorMessage.value = resolveApiError(error, SUBMIT_ERROR_KEY);
|
|
484
|
+
return null;
|
|
485
|
+
} finally {
|
|
486
|
+
isSubmitting.value = false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
onMounted(async () => {
|
|
491
|
+
if (
|
|
492
|
+
(config.public.dmsSaas as DmsSaasPublicRuntimeConfig | undefined)
|
|
493
|
+
?.admissionMode === "invitation-only"
|
|
494
|
+
) {
|
|
495
|
+
isLoading.value = false;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
await Promise.all([loadPlans(), mountPaymentElement()]);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
errorMessage.value = resolveApiError(error, LOAD_ERROR_KEY);
|
|
502
|
+
} finally {
|
|
503
|
+
isLoading.value = false;
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
form,
|
|
509
|
+
plans,
|
|
510
|
+
selectedPlan,
|
|
511
|
+
countryItems,
|
|
512
|
+
paymentElementId,
|
|
513
|
+
isPaymentReady,
|
|
514
|
+
isLoading,
|
|
515
|
+
isSubmitting,
|
|
516
|
+
errorMessage,
|
|
517
|
+
missingRequirement,
|
|
518
|
+
canSubmit,
|
|
519
|
+
formatPlanPrice,
|
|
520
|
+
submit,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const SEAT_QUOTA_ENDPOINT = "/api/saas/tenant/seats";
|
|
2
|
+
const SEAT_QUOTA_STATE_KEY = "saas-seat-quota";
|
|
3
|
+
|
|
4
|
+
export interface SeatQuotaResponse {
|
|
5
|
+
members: number;
|
|
6
|
+
pendingInvites: number;
|
|
7
|
+
occupied: number;
|
|
8
|
+
/** `null` when no plan is in force, i.e. no seat ceiling to enforce. */
|
|
9
|
+
maxMembers: number | null;
|
|
10
|
+
isTenantOwner: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useSeatQuota(): SharedRequest<SeatQuotaResponse> {
|
|
14
|
+
const { $authFetch } = useAuthFetch();
|
|
15
|
+
return useSharedRequest(SEAT_QUOTA_STATE_KEY, () =>
|
|
16
|
+
$authFetch<SeatQuotaResponse>(SEAT_QUOTA_ENDPOINT),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface SharedRequest<T> {
|
|
4
|
+
data: Ref<T | null>;
|
|
5
|
+
error: Ref<boolean>;
|
|
6
|
+
load: () => Promise<T | null>;
|
|
7
|
+
refresh: () => Promise<T | null>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* One payload, many blocks. Page-level surfaces are assembled from independent
|
|
12
|
+
* components that mount together, so a plain per-component fetch multiplies
|
|
13
|
+
* the same request. This keeps the response, the failure flag and the
|
|
14
|
+
* in-flight promise in shared state: `load` serves whatever is already there,
|
|
15
|
+
* `refresh` forces a re-fetch. Failures never reject — they set `error` so
|
|
16
|
+
* every consumer can render the shared failure state and offer a retry
|
|
17
|
+
* instead of silently disappearing.
|
|
18
|
+
*/
|
|
19
|
+
export function useSharedRequest<T>(
|
|
20
|
+
key: string,
|
|
21
|
+
fetcher: () => Promise<T>,
|
|
22
|
+
): SharedRequest<T> {
|
|
23
|
+
const data = useDmsState<T | null>(key, () => null);
|
|
24
|
+
const error = useDmsState(`${key}-error`, () => false);
|
|
25
|
+
const inFlight = useDmsState<Promise<T | null> | null>(
|
|
26
|
+
`${key}-request`,
|
|
27
|
+
() => null,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function refresh(): Promise<T | null> {
|
|
31
|
+
if (inFlight.value) return inFlight.value;
|
|
32
|
+
const request = fetcher()
|
|
33
|
+
.then((response) => {
|
|
34
|
+
data.value = response;
|
|
35
|
+
error.value = false;
|
|
36
|
+
return response as T | null;
|
|
37
|
+
})
|
|
38
|
+
.catch(() => {
|
|
39
|
+
error.value = true;
|
|
40
|
+
return null;
|
|
41
|
+
})
|
|
42
|
+
.finally(() => {
|
|
43
|
+
inFlight.value = null;
|
|
44
|
+
});
|
|
45
|
+
inFlight.value = request;
|
|
46
|
+
return request;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function load(): Promise<T | null> {
|
|
50
|
+
return data.value ? Promise.resolve(data.value) : refresh();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { data, error, load, refresh };
|
|
54
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { onUnmounted } from "vue";
|
|
2
|
+
|
|
3
|
+
interface UseStripePaymentElementOptions {
|
|
4
|
+
publishableKey: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
containerId: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ConfirmResult {
|
|
10
|
+
paymentMethodId?: string;
|
|
11
|
+
error?: { message: string };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface StripePaymentElementHandle {
|
|
15
|
+
confirmAndGetPaymentMethod: () => Promise<ConfirmResult>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ALLOWED_PAYMENT_METHODS = ["card"];
|
|
19
|
+
const ALLOWED_CARD_BRANDS = ["visa", "mastercard"];
|
|
20
|
+
|
|
21
|
+
export function useStripePaymentElement(
|
|
22
|
+
options: UseStripePaymentElementOptions,
|
|
23
|
+
): StripePaymentElementHandle {
|
|
24
|
+
let stripeInstance: any = null;
|
|
25
|
+
let elementsInstance: any = null;
|
|
26
|
+
|
|
27
|
+
async function init(): Promise<void> {
|
|
28
|
+
const { loadStripe } = await import("@stripe/stripe-js");
|
|
29
|
+
stripeInstance = await loadStripe(options.publishableKey);
|
|
30
|
+
if (!stripeInstance) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
elementsInstance = stripeInstance.elements({
|
|
34
|
+
clientSecret: options.clientSecret,
|
|
35
|
+
paymentMethodTypes: ALLOWED_PAYMENT_METHODS,
|
|
36
|
+
});
|
|
37
|
+
const paymentElement = elementsInstance.create("payment", {
|
|
38
|
+
paymentMethodTypes: ALLOWED_PAYMENT_METHODS,
|
|
39
|
+
wallets: { applePay: "never", googlePay: "never" },
|
|
40
|
+
fields: {
|
|
41
|
+
billingDetails: { address: "auto", name: "auto", email: "never" },
|
|
42
|
+
},
|
|
43
|
+
cardBrands: ALLOWED_CARD_BRANDS,
|
|
44
|
+
});
|
|
45
|
+
paymentElement.mount(`#${options.containerId}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function confirmAndGetPaymentMethod(): Promise<ConfirmResult> {
|
|
49
|
+
if (!stripeInstance || !elementsInstance) {
|
|
50
|
+
return { error: { message: "Stripe not initialized" } };
|
|
51
|
+
}
|
|
52
|
+
const result = await stripeInstance.confirmSetup({
|
|
53
|
+
elements: elementsInstance,
|
|
54
|
+
redirect: "if_required",
|
|
55
|
+
});
|
|
56
|
+
if (result.error) {
|
|
57
|
+
return { error: { message: result.error.message } };
|
|
58
|
+
}
|
|
59
|
+
const paymentMethodId = result.setupIntent?.payment_method;
|
|
60
|
+
if (typeof paymentMethodId !== "string") {
|
|
61
|
+
return { error: { message: "No payment method returned" } };
|
|
62
|
+
}
|
|
63
|
+
return { paymentMethodId };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
onUnmounted(() => {
|
|
67
|
+
elementsInstance = null;
|
|
68
|
+
stripeInstance = null;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
void init();
|
|
72
|
+
|
|
73
|
+
return { confirmAndGetPaymentMethod };
|
|
74
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription statuses that close a workspace, mirroring the backend gate's
|
|
3
|
+
* `BLOCKING_STATUSES`. The billing page stays reachable in those states, so its
|
|
4
|
+
* blocks use this to keep the controls the gate still refuses out of the way.
|
|
5
|
+
*/
|
|
6
|
+
const BLOCKING_STATUSES = new Set(["pending_payment", "suspended", "cancelled"]);
|
|
7
|
+
|
|
8
|
+
export function isBlockingSubscriptionStatus(
|
|
9
|
+
status: string | null | undefined,
|
|
10
|
+
): boolean {
|
|
11
|
+
return !!status && BLOCKING_STATUSES.has(status);
|
|
12
|
+
}
|