@cosmicdrift/kumiko-bundled-features 0.386.0 → 0.388.0
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 +21 -57
- package/dist/agent-tools/tool-catalog.js +1 -1
- package/dist/agent-tools/tool-dispatch.d.ts +7 -7
- package/dist/agent-tools/tool-dispatch.js +9 -9
- package/dist/audit/handlers/audit-events.js +1 -1
- package/dist/audit/screens.js +1 -1
- package/dist/auth-email-password/feature.js +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +3 -3
- package/dist/auth-email-password/handlers/confirm-token-flow.js +5 -5
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +1 -1
- package/dist/auth-email-password/handlers/invite-accept.write.js +18 -18
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
- package/dist/auth-email-password/handlers/invite-create.write.js +10 -10
- package/dist/auth-email-password/handlers/invite-info.query.js +1 -1
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +24 -26
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.js +1 -1
- package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.js +34 -37
- package/dist/auth-email-password/handlers/signup-request.write.js +6 -7
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +1 -4
- package/dist/auth-email-password/index.js +5 -5
- package/dist/auth-email-password/lockout-store.js +1 -1
- package/dist/auth-email-password/seeding.d.ts +42 -45
- package/dist/auth-email-password/seeding.js +51 -53
- package/dist/auth-email-password/signup-token-store.js +8 -8
- package/dist/auth-email-password/testing.js +3 -3
- package/dist/auth-email-password/web/auth-client.d.ts +2 -2
- package/dist/auth-email-password/web/auth-client.js +36 -37
- package/dist/auth-email-password/web/auth-form-primitives.js +10 -10
- package/dist/auth-email-password/web/auth-gate.js +15 -16
- package/dist/auth-email-password/web/client-plugin.d.ts +7 -7
- package/dist/auth-email-password/web/client-plugin.js +4 -5
- package/dist/auth-email-password/web/confirm-account-unlock-screen.js +1 -1
- package/dist/auth-email-password/web/constants.js +3 -3
- package/dist/auth-email-password/web/default-topbar-actions.d.ts +8 -8
- package/dist/auth-email-password/web/default-topbar-actions.js +6 -6
- package/dist/auth-email-password/web/forgot-password-screen.d.ts +2 -2
- package/dist/auth-email-password/web/forgot-password-screen.js +9 -9
- package/dist/auth-email-password/web/index.js +5 -5
- package/dist/auth-email-password/web/invite-accept-screen.d.ts +1 -1
- package/dist/auth-email-password/web/invite-accept-screen.js +16 -16
- package/dist/auth-email-password/web/login-screen.d.ts +1 -1
- package/dist/auth-email-password/web/login-screen.js +2 -3
- package/dist/auth-email-password/web/reset-password-screen.d.ts +4 -4
- package/dist/auth-email-password/web/reset-password-screen.js +12 -13
- package/dist/auth-email-password/web/session.d.ts +10 -9
- package/dist/auth-email-password/web/session.js +28 -29
- package/dist/auth-email-password/web/signup-complete-screen.js +3 -3
- package/dist/auth-email-password/web/signup-screen.d.ts +1 -1
- package/dist/auth-email-password/web/signup-screen.js +13 -13
- package/dist/auth-email-password/web/tenant-switcher.d.ts +4 -4
- package/dist/auth-email-password/web/tenant-switcher.js +21 -22
- package/dist/auth-email-password/web/use-shell-user.js +12 -13
- package/dist/auth-email-password/web/user-menu.d.ts +4 -4
- package/dist/auth-email-password/web/user-menu.js +12 -13
- package/dist/auth-email-password/web/verify-email-screen.d.ts +2 -2
- package/dist/auth-email-password/web/verify-email-screen.js +9 -10
- package/dist/auth-foundation/boot-checks.js +4 -4
- package/dist/auth-foundation/resolve-anonymous-access.d.ts +1 -1
- package/dist/auth-foundation/resolve-anonymous-access.js +3 -3
- package/dist/auth-foundation/types.js +2 -2
- package/dist/auth-mfa/feature-exports.d.ts +2 -2
- package/dist/auth-mfa/feature.js +8 -0
- package/dist/auth-mfa/handlers/disable.write.js +3 -3
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +4 -4
- package/dist/auth-mfa/handlers/enable-confirm.write.js +2 -2
- package/dist/auth-mfa/handlers/enable-start-preauth.write.js +1 -1
- package/dist/auth-mfa/handlers/reencrypt.job.js +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.js +5 -5
- package/dist/auth-mfa/handlers/verify.write.js +1 -1
- package/dist/auth-mfa/token-secrets.js +1 -1
- package/dist/auth-mfa/web/client-plugin.d.ts +1 -1
- package/dist/auth-mfa/web/client-plugin.js +1 -1
- package/dist/auth-mfa/web/i18n.js +8 -4
- package/dist/auth-mfa/web/index.js +4 -4
- package/dist/auth-mfa/web/mfa-client.js +17 -16
- package/dist/auth-mfa/web/mfa-code-or-recovery-field.d.ts +11 -0
- package/dist/auth-mfa/web/mfa-code-or-recovery-field.js +12 -0
- package/dist/auth-mfa/web/mfa-disable-dialog.js +14 -3
- package/dist/auth-mfa/web/mfa-regenerate-recovery-dialog.js +14 -3
- package/dist/auth-mfa/web/mfa-setup-preauth-screen.js +10 -5
- package/dist/auth-mfa/web/mfa-verify-screen.js +32 -11
- package/dist/billing-foundation/aggregate-id.d.ts +5 -5
- package/dist/billing-foundation/aggregate-id.js +10 -11
- package/dist/billing-foundation/checkout-core.d.ts +9 -14
- package/dist/billing-foundation/checkout-core.js +24 -39
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +8 -2
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +1 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +1 -0
- package/dist/billing-foundation/consumer-protection/issue-confirmation.js +9 -3
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +3 -1
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +4 -2
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +6 -5
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +5 -1
- package/dist/billing-foundation/consumer-protection/termination-record.js +27 -17
- package/dist/billing-foundation/consumer-protection/termination-request.js +12 -7
- package/dist/billing-foundation/consumer-protection/termination-revoke.js +12 -7
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +3 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +5 -2
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +8 -9
- package/dist/billing-foundation/entities.js +27 -27
- package/dist/billing-foundation/events.d.ts +1 -0
- package/dist/billing-foundation/events.js +19 -17
- package/dist/billing-foundation/feature.js +45 -44
- package/dist/billing-foundation/get-subscription-for-tenant.d.ts +3 -3
- package/dist/billing-foundation/get-subscription-for-tenant.js +3 -5
- package/dist/billing-foundation/handlers/billing-plans.query.js +35 -5
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +19 -17
- package/dist/billing-foundation/handlers/create-portal-session.write.js +11 -12
- package/dist/billing-foundation/handlers/list-subscriptions.query.js +4 -4
- package/dist/billing-foundation/handlers/plan-switch-preview.query.d.ts +13 -0
- package/dist/billing-foundation/handlers/plan-switch-preview.query.js +48 -0
- package/dist/billing-foundation/handlers/process-event.write.d.ts +6 -0
- package/dist/billing-foundation/handlers/process-event.write.js +28 -22
- package/dist/billing-foundation/handlers/start-plan-checkout.write.js +32 -5
- package/dist/billing-foundation/handlers/switch-plan.write.d.ts +2 -2
- package/dist/billing-foundation/handlers/switch-plan.write.js +56 -64
- package/dist/billing-foundation/handlers/sync-subscription.write.js +12 -1
- package/dist/billing-foundation/i18n.js +22 -0
- package/dist/billing-foundation/index.d.ts +1 -1
- package/dist/billing-foundation/plan-catalog.d.ts +21 -5
- package/dist/billing-foundation/plan-catalog.js +25 -16
- package/dist/billing-foundation/plan-switch.d.ts +12 -0
- package/dist/billing-foundation/plan-switch.js +60 -0
- package/dist/billing-foundation/projection.d.ts +15 -11
- package/dist/billing-foundation/projection.js +38 -37
- package/dist/billing-foundation/subscription-tier-sync.js +3 -3
- package/dist/billing-foundation/tenant-destroy-hook.d.ts +2 -2
- package/dist/billing-foundation/tenant-destroy-hook.js +7 -7
- package/dist/billing-foundation/types.d.ts +97 -42
- package/dist/billing-foundation/types.js +29 -20
- package/dist/billing-foundation/validate-options.js +17 -0
- package/dist/billing-foundation/web/billing-plans-panel.js +82 -36
- package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +17 -3
- package/dist/billing-foundation/web/checkout-consent-dialog.js +14 -10
- package/dist/billing-foundation/web/constants.d.ts +4 -0
- package/dist/billing-foundation/web/constants.js +4 -0
- package/dist/billing-foundation/web/plan-price.d.ts +15 -0
- package/dist/billing-foundation/web/plan-price.js +32 -0
- package/dist/billing-foundation/web/plan-switch-confirm-dialog.d.ts +15 -0
- package/dist/billing-foundation/web/plan-switch-confirm-dialog.js +66 -0
- package/dist/cap-counter/aggregate-id.js +8 -9
- package/dist/cap-counter/cap-reservation.js +5 -1
- package/dist/cap-counter/constants.js +11 -12
- package/dist/cap-counter/enforce-cap.d.ts +59 -62
- package/dist/cap-counter/enforce-cap.js +49 -49
- package/dist/cap-counter/entity.js +29 -29
- package/dist/cap-counter/feature.js +34 -35
- package/dist/cap-counter/handlers/increment-rolling.write.d.ts +2 -2
- package/dist/cap-counter/handlers/increment-rolling.write.js +31 -31
- package/dist/cap-counter/handlers/mark-soft-warned.write.js +4 -5
- package/dist/cap-counter/stock-cap-guard.js +8 -2
- package/dist/cap-counter/with-cap-enforcement.d.ts +3 -5
- package/dist/channel-email/feature.js +1 -1
- package/dist/channel-email/smtp-transport.d.ts +18 -17
- package/dist/channel-email/smtp-transport.js +24 -21
- package/dist/channel-in-app/handlers/unread-count.query.js +3 -4
- package/dist/compliance-profiles/feature.js +4 -4
- package/dist/compliance-profiles/handlers/for-tenant.query.js +3 -6
- package/dist/compliance-profiles/handlers/list-profiles.query.js +5 -5
- package/dist/compliance-profiles/handlers/needs-profile.query.js +4 -4
- package/dist/compliance-profiles/handlers/set-profile.write.js +13 -14
- package/dist/compliance-profiles/handlers/sub-processors.query.js +9 -11
- package/dist/compliance-profiles/resolve-for-tenant.js +7 -7
- package/dist/compliance-profiles/schema/profile-selection.js +18 -23
- package/dist/compliance-profiles/seeding.js +14 -15
- package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +3 -3
- package/dist/compliance-profiles-ops/index.js +2 -2
- package/dist/config/handlers/cascade.query.d.ts +5 -0
- package/dist/config/handlers/cascade.query.js +4 -3
- package/dist/config/handlers/readiness.query.d.ts +4 -4
- package/dist/config/handlers/readiness.query.js +1 -1
- package/dist/config/handlers/reencrypt.job.js +4 -5
- package/dist/config/handlers/set.write.js +4 -4
- package/dist/config/handlers/values.query.d.ts +5 -0
- package/dist/config/handlers/values.query.js +4 -3
- package/dist/config/resolver.js +4 -4
- package/dist/config/table.js +10 -10
- package/dist/config/write-helpers.d.ts +1 -0
- package/dist/config/write-helpers.js +19 -10
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +2 -2
- package/dist/crypto-shredding/handlers/forget-subject.write.js +25 -21
- package/dist/custom-fields/aggregate-id.d.ts +12 -13
- package/dist/custom-fields/aggregate-id.js +16 -17
- package/dist/custom-fields/entity.js +49 -47
- package/dist/custom-fields/events.js +7 -8
- package/dist/custom-fields/feature.d.ts +14 -14
- package/dist/custom-fields/feature.js +53 -54
- package/dist/custom-fields/handlers/clear-custom-field.write.d.ts +3 -2
- package/dist/custom-fields/handlers/clear-custom-field.write.js +9 -8
- package/dist/custom-fields/handlers/define-tenant-field.write.d.ts +1 -1
- package/dist/custom-fields/handlers/define-tenant-field.write.js +2 -2
- package/dist/custom-fields/handlers/delete-system-field.write.js +6 -7
- package/dist/custom-fields/handlers/delete-tenant-field.write.d.ts +1 -1
- package/dist/custom-fields/handlers/delete-tenant-field.write.js +3 -3
- package/dist/custom-fields/handlers/set-custom-field.write.d.ts +3 -3
- package/dist/custom-fields/handlers/set-custom-field.write.js +25 -28
- package/dist/custom-fields/handlers/update-tenant-field.write.d.ts +1 -1
- package/dist/custom-fields/handlers/update-tenant-field.write.js +1 -1
- package/dist/custom-fields/lib/field-access.js +2 -2
- package/dist/custom-fields/lib/parse-serialized-field.js +1 -1
- package/dist/custom-fields/lib/quota.js +1 -1
- package/dist/custom-fields/lib/value-schema.js +10 -12
- package/dist/custom-fields/run-retention.js +3 -3
- package/dist/custom-fields/schemas.js +14 -14
- package/dist/custom-fields/web/client-plugin.js +2 -5
- package/dist/custom-fields/web/constants.js +2 -2
- package/dist/custom-fields/web/custom-fields-form-section.d.ts +4 -4
- package/dist/custom-fields/web/custom-fields-form-section.js +22 -22
- package/dist/custom-fields/wire-for-entity.js +65 -67
- package/dist/custom-fields/wire-user-data-rights.js +3 -3
- package/dist/data-retention/_internal/parse-override.js +2 -3
- package/dist/data-retention/feature.js +11 -18
- package/dist/data-retention/handlers/policy-for.query.js +6 -6
- package/dist/data-retention/keep-for.d.ts +7 -7
- package/dist/data-retention/keep-for.js +15 -15
- package/dist/data-retention/override-schema.d.ts +3 -4
- package/dist/data-retention/override-schema.js +13 -15
- package/dist/data-retention/presets.d.ts +4 -5
- package/dist/data-retention/presets.js +18 -23
- package/dist/data-retention/resolve-tenant-preset.js +17 -16
- package/dist/data-retention/resolver.d.ts +12 -12
- package/dist/data-retention/resolver.js +24 -23
- package/dist/data-retention/run-retention-cleanup.d.ts +8 -3
- package/dist/data-retention/run-retention-cleanup.js +61 -51
- package/dist/data-retention/schema/tenant-retention-override.js +13 -11
- package/dist/delivery/channel-context.js +6 -3
- package/dist/delivery/db/queries/preferences.js +2 -2
- package/dist/delivery/delivery-service.js +22 -7
- package/dist/delivery/feature.js +2 -2
- package/dist/delivery/handlers/log.query.js +1 -1
- package/dist/delivery/handlers/preferences.query.d.ts +1 -1
- package/dist/delivery/handlers/preferences.query.js +20 -1
- package/dist/delivery/handlers/unsubscribe-page-chrome.query.js +1 -1
- package/dist/delivery/tables.js +9 -8
- package/dist/delivery/unsubscribe-link.d.ts +3 -1
- package/dist/delivery/unsubscribe-page-strings.d.ts +1 -0
- package/dist/delivery/unsubscribe-page-strings.js +1 -0
- package/dist/delivery/unsubscribe-page.d.ts +2 -0
- package/dist/delivery/unsubscribe-page.js +5 -0
- package/dist/delivery/unsubscribe.js +29 -11
- package/dist/document-ingest-foundation/entity.js +4 -4
- package/dist/document-ingest-foundation/events.js +2 -2
- package/dist/document-ingest-foundation/feature.js +2 -2
- package/dist/document-ingest-foundation/pages.js +1 -1
- package/dist/document-ingest-foundation/tenant-destroy-hook.js +4 -4
- package/dist/feature-toggles/compose-tier-resolver.js +1 -1
- package/dist/feature-toggles/feature.js +1 -1
- package/dist/feature-toggles/global-feature-state-table.js +1 -1
- package/dist/feature-toggles/handlers/registered.query.js +5 -5
- package/dist/feature-toggles/toggle-sync-signal.js +1 -1
- package/dist/feature-toggles/web/constants.js +1 -1
- package/dist/file-derivatives/feature.d.ts +1 -1
- package/dist/file-derivatives/feature.js +1 -1
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-derivatives/presets.js +1 -1
- package/dist/file-foundation/feature.js +6 -6
- package/dist/file-foundation/index.d.ts +1 -1
- package/dist/file-foundation/index.js +1 -1
- package/dist/file-foundation/read-file-ref-content.d.ts +3 -0
- package/dist/file-foundation/read-file-ref-content.js +6 -1
- package/dist/file-provider-inmemory/feature.d.ts +1 -1
- package/dist/file-provider-inmemory/feature.js +16 -16
- package/dist/file-provider-s3/env-named-feature.d.ts +0 -2
- package/dist/file-provider-s3/feature.js +16 -16
- package/dist/file-provider-s3/lib/s3-provider.js +6 -6
- package/dist/files/feature.js +6 -6
- package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +4 -4
- package/dist/files-tenant-data/hooks.js +24 -26
- package/dist/files-tenant-data/index.js +1 -1
- package/dist/folders/feature.d.ts +1 -1
- package/dist/folders/web/folder-manager.js +2 -2
- package/dist/folders/web/tree.js +5 -5
- package/dist/form-draft/db/queries/cleanup.js +3 -3
- package/dist/form-draft/db/queries/owned-file-refs.js +1 -1
- package/dist/form-draft/decrypt-draft.js +5 -1
- package/dist/form-draft/entity.js +3 -3
- package/dist/form-draft/feature.js +4 -5
- package/dist/form-draft/handlers/discard.write.js +1 -1
- package/dist/form-draft/release-file-refs.js +6 -6
- package/dist/form-draft/schemas.js +3 -3
- package/dist/form-draft/web/constants.js +0 -2
- package/dist/host-routing/dispatch.js +11 -20
- package/dist/inbound-mail-foundation/aggregate-id.d.ts +13 -13
- package/dist/inbound-mail-foundation/aggregate-id.js +19 -19
- package/dist/inbound-mail-foundation/connect-routes.js +4 -3
- package/dist/inbound-mail-foundation/constants.d.ts +22 -24
- package/dist/inbound-mail-foundation/constants.js +30 -33
- package/dist/inbound-mail-foundation/entities.d.ts +12 -12
- package/dist/inbound-mail-foundation/entities.js +47 -48
- package/dist/inbound-mail-foundation/events.d.ts +4 -3
- package/dist/inbound-mail-foundation/events.js +50 -47
- package/dist/inbound-mail-foundation/feature.js +39 -39
- package/dist/inbound-mail-foundation/handlers/account-state.js +8 -8
- package/dist/inbound-mail-foundation/handlers/connect-account.write.js +25 -26
- package/dist/inbound-mail-foundation/handlers/disconnect-account.write.js +7 -7
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +50 -51
- package/dist/inbound-mail-foundation/handlers/list-accounts.query.js +8 -8
- package/dist/inbound-mail-foundation/handlers/list-messages.query.js +10 -10
- package/dist/inbound-mail-foundation/handlers/scope-visibility.js +3 -4
- package/dist/inbound-mail-foundation/handlers/update-account.write.d.ts +2 -2
- package/dist/inbound-mail-foundation/handlers/update-account.write.js +22 -14
- package/dist/inbound-mail-foundation/i18n.js +0 -1
- package/dist/inbound-mail-foundation/oauth-state.d.ts +5 -5
- package/dist/inbound-mail-foundation/oauth-state.js +11 -10
- package/dist/inbound-mail-foundation/projection.d.ts +4 -4
- package/dist/inbound-mail-foundation/projection.js +31 -29
- package/dist/inbound-mail-foundation/provider-factory.d.ts +4 -4
- package/dist/inbound-mail-foundation/provider-factory.js +9 -9
- package/dist/inbound-mail-foundation/retention-sweep.d.ts +5 -3
- package/dist/inbound-mail-foundation/retention-sweep.js +38 -36
- package/dist/inbound-mail-foundation/screens.js +0 -3
- package/dist/inbound-mail-foundation/tenant-destroy-hook.js +14 -16
- package/dist/inbound-mail-foundation/types.d.ts +49 -49
- package/dist/inbound-mail-foundation/types.js +21 -21
- package/dist/inbound-mail-foundation/watch-supervisor.d.ts +10 -10
- package/dist/inbound-mail-foundation/watch-supervisor.js +85 -67
- package/dist/inbound-provider-imap/credential-document.js +13 -13
- package/dist/inbound-provider-imap/feature.d.ts +2 -2
- package/dist/inbound-provider-imap/feature.js +42 -40
- package/dist/inbound-provider-imap/imap-client.d.ts +12 -11
- package/dist/inbound-provider-imap/imap-client.js +20 -19
- package/dist/inbound-provider-inmemory/feature.d.ts +9 -9
- package/dist/inbound-provider-inmemory/feature.js +19 -19
- package/dist/jobs/db/queries/retention.js +2 -2
- package/dist/jobs/db/queries/stale-run-sweep.js +3 -3
- package/dist/jobs/events.js +1 -1
- package/dist/jobs/feature.d.ts +1 -0
- package/dist/jobs/feature.js +13 -10
- package/dist/jobs/handlers/detail.query.js +4 -4
- package/dist/jobs/handlers/list.query.js +1 -1
- package/dist/jobs/handlers/projection-rebuild.job.js +1 -1
- package/dist/jobs/handlers/reindex-entity.job.js +1 -1
- package/dist/jobs/handlers/retry.write.js +1 -1
- package/dist/jobs/handlers/tenant-failures.query.d.ts +1 -1
- package/dist/jobs/handlers/tenant-failures.query.js +45 -41
- package/dist/jobs/handlers/tenant-runs.query.d.ts +1 -1
- package/dist/jobs/handlers/tenant-runs.query.js +54 -50
- package/dist/jobs/job-instant.js +1 -1
- package/dist/jobs/job-run-logger.js +15 -15
- package/dist/jobs/job-run-table.js +3 -3
- package/dist/jobs/screens.js +1 -1
- package/dist/jobs/tenant-job-failure-table.js +1 -1
- package/dist/jobs/tenant-job-run-table.js +1 -1
- package/dist/ledger/entity.js +5 -5
- package/dist/ledger/feature.js +3 -3
- package/dist/ledger/handlers/confirm-schedule-period.write.js +3 -3
- package/dist/ledger/recurring.js +12 -12
- package/dist/ledger/schemas.js +1 -1
- package/dist/ledger/web/constants.js +2 -2
- package/dist/ledger/web/index.js +7 -7
- package/dist/legal-pages/feature.d.ts +4 -4
- package/dist/legal-pages/feature.js +25 -26
- package/dist/legal-pages/markdown.js +3 -3
- package/dist/legal-pages/web/client-plugin.js +23 -24
- package/dist/legal-pages/web/index.js +0 -1
- package/dist/mail-foundation/feature.d.ts +5 -5
- package/dist/mail-foundation/feature.js +27 -25
- package/dist/mail-transport-inmemory/feature.d.ts +4 -4
- package/dist/mail-transport-inmemory/feature.js +27 -28
- package/dist/mail-transport-smtp/feature.js +20 -20
- package/dist/managed-pages/branding.js +6 -6
- package/dist/managed-pages/feature.d.ts +26 -26
- package/dist/managed-pages/feature.js +34 -34
- package/dist/managed-pages/handlers/by-slug.query.js +5 -6
- package/dist/managed-pages/handlers/by-tenant-published.query.js +2 -2
- package/dist/managed-pages/handlers/set.write.js +5 -7
- package/dist/managed-pages/screens/page-screens.js +11 -11
- package/dist/managed-pages/seeding.js +8 -8
- package/dist/managed-pages/table.js +13 -12
- package/dist/managed-pages/web/client-plugin.d.ts +1 -1
- package/dist/managed-pages/web/client-plugin.js +5 -5
- package/dist/managed-pages/web/i18n.js +1 -1
- package/dist/notes-history/entity.js +4 -4
- package/dist/notes-history/feature.d.ts +2 -2
- package/dist/notes-history-user-data/hooks.js +1 -1
- package/dist/page-render/layout.js +9 -9
- package/dist/page-render/markdown.js +11 -11
- package/dist/page-render/security-headers.js +7 -6
- package/dist/personal-access-tokens/feature.d.ts +1 -1
- package/dist/personal-access-tokens/feature.js +9 -5
- package/dist/personal-access-tokens/roles.js +2 -2
- package/dist/readiness/handlers/status.query.js +3 -3
- package/dist/renderer-foundation/api.js +12 -13
- package/dist/renderer-foundation/constants.js +2 -2
- package/dist/renderer-foundation/feature.js +7 -7
- package/dist/renderer-pdf/render-table-pdf.d.ts +1 -1
- package/dist/renderer-pdf/render-table-pdf.js +22 -10
- package/dist/secrets/feature.js +9 -9
- package/dist/secrets/web/constants.js +2 -2
- package/dist/seo/feature.js +3 -3
- package/dist/sessions/constants.d.ts +1 -1
- package/dist/sessions/constants.js +1 -1
- package/dist/sessions/feature.d.ts +2 -2
- package/dist/sessions/feature.js +12 -8
- package/dist/sessions/handlers/revoke-all-for-user.write.js +2 -2
- package/dist/sessions/handlers/revoke-all-others.write.js +2 -2
- package/dist/sessions/handlers/revoke.write.js +1 -1
- package/dist/sessions/schema/user-session.js +4 -4
- package/dist/sessions/screens.js +2 -2
- package/dist/sessions/session-callbacks.js +2 -2
- package/dist/sessions/session-revoked-event.js +2 -2
- package/dist/shared/assert-no-where-rule-in-ownership-write.js +1 -1
- package/dist/shared/decrypt-stored-pii.d.ts +1 -0
- package/dist/shared/decrypt-stored-pii.js +7 -2
- package/dist/shared/encrypt-for-direct-write.js +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/mail-template-body.d.ts +6 -2
- package/dist/shared/mail-template-body.js +7 -4
- package/dist/shared/map-with-concurrency.js +5 -4
- package/dist/shared/parent-visibility.js +2 -2
- package/dist/shared/password-hashing.js +1 -1
- package/dist/shared/signup-handover.js +1 -2
- package/dist/shared/single-use-token-store.js +1 -1
- package/dist/step-dispatcher/feature.js +1 -0
- package/dist/subscription-dodo/client.d.ts +17 -0
- package/dist/subscription-dodo/client.js +31 -0
- package/dist/subscription-dodo/constants.d.ts +12 -0
- package/dist/subscription-dodo/constants.js +19 -0
- package/dist/subscription-dodo/feature.d.ts +37 -0
- package/dist/subscription-dodo/feature.js +144 -0
- package/dist/subscription-dodo/index.d.ts +2 -0
- package/dist/subscription-dodo/index.js +4 -0
- package/dist/subscription-dodo/plugin-methods.d.ts +23 -0
- package/dist/subscription-dodo/plugin-methods.js +260 -0
- package/dist/subscription-dodo/runtime.d.ts +27 -0
- package/dist/subscription-dodo/runtime.js +89 -0
- package/dist/subscription-dodo/verify-webhook.d.ts +37 -0
- package/dist/subscription-dodo/verify-webhook.js +189 -0
- package/dist/subscription-mollie/constants.js +15 -17
- package/dist/subscription-mollie/feature.d.ts +17 -18
- package/dist/subscription-mollie/feature.js +24 -24
- package/dist/subscription-mollie/index.js +3 -3
- package/dist/subscription-mollie/plugin-methods.d.ts +2 -2
- package/dist/subscription-mollie/plugin-methods.js +15 -14
- package/dist/subscription-mollie/verify-webhook.d.ts +10 -10
- package/dist/subscription-mollie/verify-webhook.js +27 -27
- package/dist/subscription-stripe/constants.js +4 -6
- package/dist/subscription-stripe/feature.d.ts +14 -14
- package/dist/subscription-stripe/feature.js +36 -34
- package/dist/subscription-stripe/index.js +6 -6
- package/dist/subscription-stripe/plugin-methods.js +18 -18
- package/dist/subscription-stripe/runtime.d.ts +1 -1
- package/dist/subscription-stripe/runtime.js +8 -7
- package/dist/subscription-stripe/verify-webhook.d.ts +11 -11
- package/dist/subscription-stripe/verify-webhook.js +59 -61
- package/dist/tags/feature.d.ts +3 -3
- package/dist/tags/screens.js +1 -1
- package/dist/template-resolver/api.d.ts +3 -2
- package/dist/template-resolver/api.js +34 -12
- package/dist/template-resolver/compile-document-body.js +1 -0
- package/dist/template-resolver/feature.js +2 -2
- package/dist/template-resolver/handlers/bundle-import.js +3 -3
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +5 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +22 -7
- package/dist/template-resolver/handlers/diff-to-system.query.js +3 -2
- package/dist/template-resolver/handlers/document-base.js +3 -2
- package/dist/template-resolver/handlers/find-by-id.query.js +8 -7
- package/dist/template-resolver/handlers/list.query.js +6 -7
- package/dist/template-resolver/handlers/overview.query.d.ts +3 -0
- package/dist/template-resolver/handlers/overview.query.js +6 -3
- package/dist/template-resolver/handlers/shared.js +2 -2
- package/dist/template-resolver/handlers/template-write.js +3 -3
- package/dist/template-resolver/handlers/tenant-override-counts.query.js +1 -3
- package/dist/template-resolver/handlers/tenant-override-selection.d.ts +2 -1
- package/dist/template-resolver/handlers/tenant-overrides.query.js +28 -5
- package/dist/template-resolver/handlers/upsert-system.write.js +3 -4
- package/dist/template-resolver/handlers/upsert-tenant.write.js +4 -5
- package/dist/template-resolver/handlers/usage.query.d.ts +2 -1
- package/dist/template-resolver/handlers/usage.query.js +26 -16
- package/dist/template-resolver/handlers/validate.query.js +7 -2
- package/dist/template-resolver/seeding.js +30 -14
- package/dist/template-resolver/store-template-resource.d.ts +1 -0
- package/dist/template-resolver/store-template-resource.js +19 -4
- package/dist/template-resolver/table.js +10 -11
- package/dist/template-resolver/template-delivery-policy.d.ts +0 -5
- package/dist/template-resolver/template-delivery-policy.js +0 -6
- package/dist/template-resolver/template-meta.js +7 -4
- package/dist/template-resolver/template-preview.js +4 -3
- package/dist/template-resolver/web/client-plugin.js +2 -2
- package/dist/template-resolver/web/constants.d.ts +2 -2
- package/dist/template-resolver/web/constants.js +3 -3
- package/dist/template-resolver/web/index.d.ts +1 -0
- package/dist/template-resolver/web/index.js +1 -0
- package/dist/template-resolver/web/mail-layout-constants.d.ts +5 -0
- package/dist/template-resolver/web/mail-layout-constants.js +7 -0
- package/dist/tenant/feature.js +7 -7
- package/dist/tenant/handlers/member-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/member-directory.query.js +11 -4
- package/dist/tenant/handlers/members.query.js +1 -1
- package/dist/tenant/handlers/team-list.query.js +1 -1
- package/dist/tenant/handlers/tenant-directory.query.d.ts +1 -0
- package/dist/tenant/handlers/tenant-directory.query.js +9 -5
- package/dist/tenant/invitation-table.js +37 -40
- package/dist/tenant/membership-table.js +3 -3
- package/dist/tenant/schema/tenant.js +7 -9
- package/dist/tenant/screens.js +1 -1
- package/dist/tenant/seeding.d.ts +6 -5
- package/dist/tenant/seeding.js +12 -12
- package/dist/tenant/testing.js +5 -6
- package/dist/tenant/web/constants.js +2 -2
- package/dist/tenant-handover/events.js +5 -7
- package/dist/tenant-handover/feature.js +1 -1
- package/dist/tenant-handover/grant.js +1 -1
- package/dist/tenant-handover/handlers/claim.write.js +1 -1
- package/dist/tenant-handover/move-entity-graph.js +8 -9
- package/dist/tenant-handover/signup-handover-provider.js +1 -2
- package/dist/tenant-handover/transfer-graph.js +4 -5
- package/dist/tenant-lifecycle/boot-checks.js +1 -1
- package/dist/tenant-lifecycle/constants.d.ts +1 -0
- package/dist/tenant-lifecycle/constants.js +4 -0
- package/dist/tenant-lifecycle/feature.js +1 -1
- package/dist/tenant-lifecycle/run-tenant-destroy.js +2 -2
- package/dist/tenant-settings/constants.js +1 -1
- package/dist/tenant-settings/tenant-defaults.d.ts +1 -1
- package/dist/tenant-settings/tenant-defaults.js +9 -2
- package/dist/tier-engine/aggregate-id.d.ts +11 -11
- package/dist/tier-engine/aggregate-id.js +15 -16
- package/dist/tier-engine/compose-app.d.ts +14 -14
- package/dist/tier-engine/compose-app.js +1 -1
- package/dist/tier-engine/entity.js +18 -19
- package/dist/tier-engine/feature.d.ts +19 -17
- package/dist/tier-engine/feature.js +68 -69
- package/dist/tier-engine/handlers/active-tier.query.js +6 -7
- package/dist/tier-engine/handlers/get-tenant-tier.query.js +4 -4
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +7 -8
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +7 -4
- package/dist/tier-engine/tier-resolver.js +2 -2
- package/dist/tier-engine/trial.js +8 -8
- package/dist/tier-engine/web/constants.js +2 -2
- package/dist/tier-engine/web/index.js +4 -4
- package/dist/user/db/queries/stream-tenant-backfill.js +8 -8
- package/dist/user/handlers/create.write.js +4 -4
- package/dist/user/schema/user.d.ts +11 -11
- package/dist/user/schema/user.js +45 -44
- package/dist/user/seeding.d.ts +15 -15
- package/dist/user/seeding.js +28 -28
- package/dist/user/testing.js +3 -3
- package/dist/user-data-rights/audit-download.d.ts +8 -8
- package/dist/user-data-rights/audit-download.js +19 -20
- package/dist/user-data-rights/boot-checks.js +1 -1
- package/dist/user-data-rights/deletion-token.js +1 -1
- package/dist/user-data-rights/feature.d.ts +49 -50
- package/dist/user-data-rights/feature.js +54 -53
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +9 -9
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +8 -8
- package/dist/user-data-rights/handlers/download-by-job.query.js +29 -30
- package/dist/user-data-rights/handlers/download-by-token.query.js +40 -42
- package/dist/user-data-rights/handlers/export-status.query.js +8 -9
- package/dist/user-data-rights/handlers/lift-restriction.write.js +7 -8
- package/dist/user-data-rights/handlers/list-download-attempts.query.js +2 -2
- package/dist/user-data-rights/handlers/my-audit-log.query.js +4 -4
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +5 -5
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +16 -15
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +2 -2
- package/dist/user-data-rights/handlers/request-deletion.write.js +7 -9
- package/dist/user-data-rights/handlers/request-export.write.js +42 -41
- package/dist/user-data-rights/handlers/restrict-account.write.js +22 -19
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +8 -8
- package/dist/user-data-rights/index.js +8 -10
- package/dist/user-data-rights/lib/default-mailers.js +3 -3
- package/dist/user-data-rights/lib/update-user-lifecycle.js +14 -17
- package/dist/user-data-rights/run-export-jobs.d.ts +38 -42
- package/dist/user-data-rights/run-export-jobs.js +130 -237
- package/dist/user-data-rights/run-forget-cleanup.d.ts +16 -20
- package/dist/user-data-rights/run-forget-cleanup.js +65 -83
- package/dist/user-data-rights/run-user-export.d.ts +34 -33
- package/dist/user-data-rights/run-user-export.js +55 -57
- package/dist/user-data-rights/schema/download-attempt.js +11 -11
- package/dist/user-data-rights/schema/download-token.js +37 -41
- package/dist/user-data-rights/schema/export-job.d.ts +4 -4
- package/dist/user-data-rights/schema/export-job.js +65 -67
- package/dist/user-data-rights/token-helpers.d.ts +8 -9
- package/dist/user-data-rights/token-helpers.js +21 -25
- package/dist/user-data-rights/web/client-plugin.d.ts +5 -5
- package/dist/user-data-rights/web/client-plugin.js +1 -1
- package/dist/user-data-rights/web/confirm-deletion-screen.js +6 -5
- package/dist/user-data-rights/web/constants.js +1 -1
- package/dist/user-data-rights/web/index.js +5 -5
- package/dist/user-data-rights/web/privacy-center-screen.js +14 -13
- package/dist/user-data-rights/web/public-deletion-gate.d.ts +4 -4
- package/dist/user-data-rights/web/request-deletion-screen.js +6 -6
- package/dist/user-data-rights/zip-path.d.ts +15 -15
- package/dist/user-data-rights/zip-path.js +41 -40
- package/dist/user-data-rights-defaults/feature.js +15 -15
- package/dist/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.js +3 -5
- package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +45 -51
- package/dist/user-data-rights-defaults/hooks/job-run.userdata-hook.js +2 -2
- package/dist/user-data-rights-defaults/hooks/notification-preference.userdata-hook.js +1 -1
- package/dist/user-data-rights-defaults/hooks/user-session.userdata-hook.js +3 -3
- package/dist/user-data-rights-defaults/hooks/user.userdata-hook.js +32 -31
- package/dist/user-profile/handlers/change-email.write.js +13 -13
- package/dist/user-profile/index.js +3 -3
- package/dist/user-profile/web/client-plugin.js +1 -1
- package/dist/user-profile/web/constants.js +1 -1
- package/dist/user-profile/web/profile-screen.js +1 -1
- package/dist/waitlist/handlers/export-purge.write.js +2 -1
- package/dist/waitlist/handlers/submit.write.js +5 -5
- package/dist/waitlist/import.d.ts +1 -0
- package/dist/waitlist/import.js +4 -0
- package/dist/waitlist/legacy-migrate.js +17 -6
- package/dist/waitlist/link-on-signup.js +18 -6
- package/dist/waitlist/options.d.ts +3 -0
- package/dist/workflow-runner/aggregate-id.d.ts +1 -1
- package/dist/workflow-runner/aggregate-id.js +1 -1
- package/dist/workflow-runner/event-subscriber.js +4 -5
- package/dist/workflow-runner/event-trigger.js +4 -17
- package/dist/workflow-runner/feature.js +2 -2
- package/dist/workflow-runner/handlers/resume-run.write.js +4 -4
- package/dist/workflow-runner/pending-projection.js +6 -7
- package/dist/workflow-runner/runner.js +6 -6
- package/dist/workflow-runner/tables.js +6 -6
- package/dist/workflow-runner/workflow-registry.js +1 -1
- package/package.json +20 -10
- package/src/agent-tools/changes.json +6 -0
- package/src/audit/changes.json +12 -0
- package/src/auth-mfa/changes.json +6 -0
- package/src/billing-foundation/changes.json +55 -0
- package/src/cap-counter/changes.json +18 -0
- package/src/channel-email/changes.json +6 -0
- package/src/config/changes.json +12 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/delivery/changes.json +30 -0
- package/src/form-draft/changes.json +6 -0
- package/src/host-routing/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +18 -0
- package/src/jobs/changes.json +13 -0
- package/src/mail-foundation/changes.json +6 -0
- package/src/renderer-pdf/changes.json +6 -0
- package/src/step-dispatcher/changes.json +6 -0
- package/src/subscription-dodo/changes.json +14 -0
- package/src/subscription-stripe/changes.json +12 -0
- package/src/template-resolver/changes.json +48 -0
- package/src/tenant/changes.json +6 -0
- package/src/tenant-lifecycle/changes.json +6 -0
- package/src/tier-engine/changes.json +6 -0
- package/src/waitlist/changes.json +18 -0
- package/dist/billing-foundation/consumer-protection/stored-user-email.d.ts +0 -1
- package/dist/billing-foundation/consumer-protection/stored-user-email.js +0 -8
|
@@ -2,30 +2,56 @@
|
|
|
2
2
|
// the catalog while the tenant has no non-terminal subscription. Resolves
|
|
3
3
|
// the tier's live price server-side and opens a hosted checkout for it.
|
|
4
4
|
// Only registered when `createBillingFoundationFeature` gets a `catalog`.
|
|
5
|
-
import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
|
+
import { FeatureDisabledError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
6
6
|
import * as z from "zod";
|
|
7
|
-
import { assertBillingEnabled, assertNoActiveSubscription, isNonEmptyStringArray, joinBaseUrl,
|
|
7
|
+
import { assertBillingEnabled, assertNoActiveSubscription, isNonEmptyStringArray, joinBaseUrl, listOfferedCatalogProviders, openCheckout, } from "../checkout-core.js";
|
|
8
8
|
import { consentCheckoutFields, consentPayloadSchema, prepareConsent, recordConsent, } from "../consumer-protection/consent-gate.js";
|
|
9
9
|
import { purchaseRolesOf, resolvePlanPrices } from "../plan-catalog.js";
|
|
10
|
+
import { BILLING_FOUNDATION_FEATURE } from "../web/constants.js";
|
|
11
|
+
/** The provider this checkout goes to: the named one if it is live, the only
|
|
12
|
+
* live one if the buyer had no choice, otherwise a client error. */
|
|
13
|
+
async function pickCheckoutProvider(ctx, catalog, requestedName) {
|
|
14
|
+
const offered = await listOfferedCatalogProviders(ctx, catalog);
|
|
15
|
+
if (requestedName !== undefined) {
|
|
16
|
+
const chosen = offered.find((provider) => provider.name === requestedName);
|
|
17
|
+
if (!chosen) {
|
|
18
|
+
throw new UnprocessableError("provider_not_offered", {
|
|
19
|
+
i18nKey: "billing-foundation.errors.providerNotOffered",
|
|
20
|
+
message: `subscription-foundation: provider "${requestedName}" is not offered by this catalog right now`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return chosen;
|
|
24
|
+
}
|
|
25
|
+
const [only, second] = offered;
|
|
26
|
+
if (!only)
|
|
27
|
+
throw new FeatureDisabledError(BILLING_FOUNDATION_FEATURE, "start-plan-checkout");
|
|
28
|
+
if (second) {
|
|
29
|
+
throw new UnprocessableError("provider_required", {
|
|
30
|
+
i18nKey: "billing-foundation.errors.providerRequired",
|
|
31
|
+
message: "subscription-foundation: several providers are offered, name one as providerName",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return only;
|
|
35
|
+
}
|
|
10
36
|
export function createStartPlanCheckoutHandler(options, catalog) {
|
|
11
37
|
const plans = catalog.plans;
|
|
12
38
|
if (!isNonEmptyStringArray(plans)) {
|
|
13
39
|
throw new Error("start-plan-checkout: catalog.plans must not be empty");
|
|
14
40
|
}
|
|
15
41
|
const consumerProtection = options.consumerProtection;
|
|
16
|
-
const tierSchema = z.object({ tier: z.enum(plans) });
|
|
42
|
+
const tierSchema = z.object({ tier: z.enum(plans), providerName: z.string().min(1).optional() });
|
|
17
43
|
const schema = (consumerProtection
|
|
18
44
|
? tierSchema.extend({ consent: consentPayloadSchema.optional() })
|
|
19
45
|
: tierSchema).strict();
|
|
20
46
|
return {
|
|
21
47
|
name: "start-plan-checkout",
|
|
22
|
-
description: "Starts a hosted checkout for a plan tier when the tenant has no active subscription; the server picks price and redirect URLs.",
|
|
48
|
+
description: "Starts a hosted checkout for a plan tier when the tenant has no active subscription; the server picks price and redirect URLs. Name the provider with providerName when the catalog offers several.",
|
|
23
49
|
schema,
|
|
24
50
|
access: { roles: purchaseRolesOf(catalog) },
|
|
25
51
|
handler: async (event, ctx) => {
|
|
26
52
|
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
27
53
|
const payload = event.payload;
|
|
28
|
-
const { name: providerName, plugin } =
|
|
54
|
+
const { name: providerName, plugin } = await pickCheckoutProvider(ctx, catalog, payload.providerName);
|
|
29
55
|
await assertBillingEnabled(ctx, plugin, "start-plan-checkout");
|
|
30
56
|
const existing = await assertNoActiveSubscription(ctx, options.now());
|
|
31
57
|
const prices = await resolvePlanPrices(ctx, plugin, catalog);
|
|
@@ -61,6 +87,7 @@ export function createStartPlanCheckoutHandler(options, catalog) {
|
|
|
61
87
|
tier: payload.tier,
|
|
62
88
|
priceId: resolved.priceId,
|
|
63
89
|
price: resolved.price,
|
|
90
|
+
providerName,
|
|
64
91
|
});
|
|
65
92
|
}
|
|
66
93
|
return { isSuccess: true, data: { url: result.url } };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import type {
|
|
3
|
-
export declare function createSwitchPlanHandler(options:
|
|
2
|
+
import type { BillingPlanCatalog, ResolvedBillingFoundationOptions } from "../types.ts";
|
|
3
|
+
export declare function createSwitchPlanHandler(options: ResolvedBillingFoundationOptions, catalog: BillingPlanCatalog): WriteHandlerDef;
|
|
@@ -1,83 +1,75 @@
|
|
|
1
1
|
// switch-plan — a tenant-admin/purchase-role switches an existing,
|
|
2
|
-
// non-terminal subscription to a different plan tier
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
2
|
+
// non-terminal subscription to a different plan tier. A provider with a hosted
|
|
3
|
+
// switch session answers `{ url }`; a provider that switches in-app (preview +
|
|
4
|
+
// apply) applies the switch right here and answers `{ applied: true }`.
|
|
5
|
+
// Only registered when `createBillingFoundationFeature` gets a `catalog`.
|
|
6
|
+
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
6
7
|
import * as z from "zod";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { purchaseRolesOf
|
|
10
|
-
import {
|
|
8
|
+
import { assertRedirectOrigins, isNonEmptyStringArray, joinBaseUrl } from "../checkout-core.js";
|
|
9
|
+
import { consentPayloadSchema, prepareConsent, recordConsent, } from "../consumer-protection/consent-gate.js";
|
|
10
|
+
import { purchaseRolesOf } from "../plan-catalog.js";
|
|
11
|
+
import { preparePlanSwitch } from "../plan-switch.js";
|
|
11
12
|
export function createSwitchPlanHandler(options, catalog) {
|
|
12
13
|
const plans = catalog.plans;
|
|
13
14
|
if (!isNonEmptyStringArray(plans)) {
|
|
14
15
|
throw new Error("switch-plan: catalog.plans must not be empty");
|
|
15
16
|
}
|
|
16
|
-
const
|
|
17
|
+
const consumerProtection = options.consumerProtection;
|
|
18
|
+
const tierSchema = z.object({ tier: z.enum(plans) });
|
|
19
|
+
const schema = (consumerProtection
|
|
20
|
+
? tierSchema.extend({ consent: consentPayloadSchema.optional() })
|
|
21
|
+
: tierSchema).strict();
|
|
17
22
|
return {
|
|
18
23
|
name: "switch-plan",
|
|
19
|
-
description: "
|
|
24
|
+
description: "Switches the existing subscription to another plan tier: opens the provider's confirmation page (answers { url }), or applies the switch in-app for providers that preview and apply themselves (answers { applied: true }; send consent when consumer protection is configured).",
|
|
20
25
|
schema,
|
|
21
26
|
access: { roles: purchaseRolesOf(catalog) },
|
|
22
27
|
handler: async (event, ctx) => {
|
|
23
28
|
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
24
29
|
const payload = event.payload;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
const prepared = await preparePlanSwitch(ctx, catalog, payload.tier, "switch-plan");
|
|
31
|
+
const { plugin } = prepared.provider;
|
|
32
|
+
if (plugin.createPlanSwitchSession) {
|
|
33
|
+
const baseUrl = options.baseUrl ?? "";
|
|
34
|
+
const returnUrl = joinBaseUrl(baseUrl, catalog.returnPath ?? catalog.successPath);
|
|
35
|
+
assertRedirectOrigins([returnUrl], options.baseUrl);
|
|
36
|
+
const result = await plugin.createPlanSwitchSession(ctx, {
|
|
37
|
+
providerSubscriptionId: prepared.subscription.providerSubscriptionId,
|
|
38
|
+
targetPriceId: prepared.target.priceId,
|
|
39
|
+
allowedPriceIds: prepared.allowedPriceIds,
|
|
40
|
+
returnUrl,
|
|
32
41
|
});
|
|
42
|
+
return { isSuccess: true, data: { url: result.url } };
|
|
33
43
|
}
|
|
34
|
-
|
|
35
|
-
throw new ConflictError({
|
|
36
|
-
i18nKey: "billing-foundation.errors.cancellationScheduled",
|
|
37
|
-
message: "subscription has a scheduled cancellation; reactivate it via billing-foundation:write:create-portal-session before switching plans",
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
if (payload.tier === sub.tier) {
|
|
41
|
-
throw new ConflictError({
|
|
42
|
-
i18nKey: "billing-foundation.errors.alreadyOnPlan",
|
|
43
|
-
message: `tenant is already on plan "${payload.tier}"`,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
// providerSubscriptionId only exists at the subscription's own provider; after a
|
|
47
|
-
// provider migration the catalog provider can differ, which is not a plan switch.
|
|
48
|
-
if (sub.providerName !== catalogProviderName) {
|
|
49
|
-
throw new ConflictError({
|
|
50
|
-
i18nKey: "billing-foundation.errors.providerMismatch",
|
|
51
|
-
message: `tenant's active subscription is on provider "${sub.providerName}", the catalog resolves provider "${catalogProviderName}" — switch-plan cannot switch across providers`,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (!plugin.createPlanSwitchSession) {
|
|
55
|
-
throw new UnprocessableError("plan_switch_not_supported", {
|
|
56
|
-
i18nKey: "billing-foundation.errors.planSwitchNotSupported",
|
|
57
|
-
message: `subscription-foundation: provider "${sub.providerName}" has no createPlanSwitchSession-method`,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
const prices = await resolvePlanPrices(ctx, plugin, catalog);
|
|
61
|
-
const target = prices.get(payload.tier);
|
|
62
|
-
if (!target) {
|
|
63
|
-
throw new UnprocessableError("price_unavailable", {
|
|
64
|
-
i18nKey: "billing-foundation.errors.priceUnavailable",
|
|
65
|
-
message: `subscription-foundation: no live price resolved for tier "${payload.tier}"`,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
const allowedPriceIds = [...prices.values()]
|
|
69
|
-
.filter((p) => p !== null)
|
|
70
|
-
.map((p) => p.priceId);
|
|
71
|
-
const baseUrl = options.baseUrl ?? "";
|
|
72
|
-
const returnUrl = joinBaseUrl(baseUrl, catalog.returnPath ?? catalog.successPath);
|
|
73
|
-
assertRedirectOrigins([returnUrl], options.baseUrl);
|
|
74
|
-
const result = await plugin.createPlanSwitchSession(ctx, {
|
|
75
|
-
providerSubscriptionId: sub.providerSubscriptionId,
|
|
76
|
-
targetPriceId: target.priceId,
|
|
77
|
-
allowedPriceIds,
|
|
78
|
-
returnUrl,
|
|
79
|
-
});
|
|
80
|
-
return { isSuccess: true, data: { url: result.url } };
|
|
44
|
+
return applyInApp(prepared, payload, ctx, options);
|
|
81
45
|
},
|
|
82
46
|
};
|
|
83
47
|
}
|
|
48
|
+
// Consent is recorded before the provider call: the apply carries the consentId
|
|
49
|
+
// as provider metadata, and a failing apply rolls the whole handler back.
|
|
50
|
+
async function applyInApp(prepared, payload, ctx, options) {
|
|
51
|
+
const { plugin, name: providerName } = prepared.provider;
|
|
52
|
+
if (!plugin.applyPlanSwitch) {
|
|
53
|
+
throw new InternalError({
|
|
54
|
+
message: `billing-foundation: provider "${providerName}" has neither createPlanSwitchSession nor applyPlanSwitch`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const consent = options.consumerProtection
|
|
58
|
+
? await prepareConsent(ctx, options.consumerProtection, payload.consent, "subscription")
|
|
59
|
+
: undefined;
|
|
60
|
+
if (consent) {
|
|
61
|
+
await recordConsent(ctx, consent, {
|
|
62
|
+
tier: payload.tier,
|
|
63
|
+
priceId: prepared.target.priceId,
|
|
64
|
+
price: prepared.target.price,
|
|
65
|
+
providerName,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
await plugin.applyPlanSwitch(ctx, {
|
|
69
|
+
providerSubscriptionId: prepared.subscription.providerSubscriptionId,
|
|
70
|
+
targetPriceId: prepared.target.priceId,
|
|
71
|
+
tenantId: ctx.user.tenantId,
|
|
72
|
+
...(consent && { consentId: consent.consentId }),
|
|
73
|
+
});
|
|
74
|
+
return { isSuccess: true, data: { applied: true } };
|
|
75
|
+
}
|
|
@@ -48,6 +48,16 @@ function syncProviderEventId(snapshot, lastChangedAt) {
|
|
|
48
48
|
].join("|");
|
|
49
49
|
return `sync:${createHash("sha256").update(hashInput).digest("hex")}`;
|
|
50
50
|
}
|
|
51
|
+
// The retrieval time stands in for the provider's event time, so a webhook emitted before
|
|
52
|
+
// this snapshot cannot roll the synced state back later. Provider webhook times are
|
|
53
|
+
// whole-second and come from the provider's clock, so the stamp is floored and shifted back:
|
|
54
|
+
// a genuine webhook right after the sync must not look older than the snapshot.
|
|
55
|
+
const SYNC_SNAPSHOT_EVENT_TIME_MARGIN_SECONDS = 5;
|
|
56
|
+
function snapshotEventTime() {
|
|
57
|
+
return Temporal.Now.instant()
|
|
58
|
+
.round({ smallestUnit: "second", roundingMode: "floor" })
|
|
59
|
+
.subtract({ seconds: SYNC_SNAPSHOT_EVENT_TIME_MARGIN_SECONDS });
|
|
60
|
+
}
|
|
51
61
|
export const syncSubscriptionHandler = {
|
|
52
62
|
name: "sync-subscription",
|
|
53
63
|
agent: { expose: false },
|
|
@@ -113,10 +123,11 @@ export const syncSubscriptionHandler = {
|
|
|
113
123
|
tier: snapshot.tier,
|
|
114
124
|
currentPeriodEndIso: snapshot.currentPeriodEnd,
|
|
115
125
|
cancelAtIso: snapshot.cancelAt,
|
|
126
|
+
providerEventAtIso: snapshotEventTime().toString(),
|
|
116
127
|
});
|
|
117
128
|
return {
|
|
118
129
|
isSuccess: true,
|
|
119
|
-
data: (appendResult.duplicate
|
|
130
|
+
data: (appendResult.duplicate || appendResult.stale
|
|
120
131
|
? { synced: false, reason: "unchanged" }
|
|
121
132
|
: { synced: true }),
|
|
122
133
|
};
|
|
@@ -20,6 +20,20 @@ export const BILLING_FOUNDATION_I18N = {
|
|
|
20
20
|
"billing-foundation.plans.everyInterval.year": { en: "every {count} years" },
|
|
21
21
|
"billing-foundation.plans.choose": { en: "Choose {plan}" },
|
|
22
22
|
"billing-foundation.plans.switch": { en: "Switch to {plan}" },
|
|
23
|
+
"billing-foundation.plans.planSwitchRequested": {
|
|
24
|
+
en: "Plan switch requested. It becomes active once the payment is confirmed.",
|
|
25
|
+
},
|
|
26
|
+
"billing-foundation.planSwitch.title": { en: "Confirm plan switch" },
|
|
27
|
+
"billing-foundation.planSwitch.summary": { en: "{from} to {to}" },
|
|
28
|
+
"billing-foundation.planSwitch.dueNow": { en: "Due now: {amount}" },
|
|
29
|
+
"billing-foundation.planSwitch.includesTax": { en: "including {tax} VAT" },
|
|
30
|
+
"billing-foundation.planSwitch.credit": { en: "Credit for the unused time: {amount}" },
|
|
31
|
+
"billing-foundation.planSwitch.recurring": { en: "New recurring price: {amount}" },
|
|
32
|
+
"billing-foundation.planSwitch.effective": { en: "Effective immediately" },
|
|
33
|
+
"billing-foundation.planSwitch.pendingNote": {
|
|
34
|
+
en: "The switch becomes active after payment confirmation.",
|
|
35
|
+
},
|
|
36
|
+
"billing-foundation.planSwitch.order": { en: "Switch with obligation to pay" },
|
|
23
37
|
"billing-foundation.plans.manage": { en: "Manage subscription" },
|
|
24
38
|
"billing-foundation.plans.reactivate": { en: "Reactivate subscription" },
|
|
25
39
|
"billing-foundation.plans.cancelScheduled": {
|
|
@@ -65,6 +79,8 @@ export const BILLING_FOUNDATION_I18N = {
|
|
|
65
79
|
"billing-foundation.consent.link.withdrawal": { en: "Withdrawal policy" },
|
|
66
80
|
"billing-foundation.consent.link.privacy": { en: "Privacy policy" },
|
|
67
81
|
"billing-foundation.consent.order": { en: "Order with obligation to pay" },
|
|
82
|
+
"billing-foundation.plans.priceFrom": { en: "from" },
|
|
83
|
+
"billing-foundation.consent.chooseProvider": { en: "Payment provider" },
|
|
68
84
|
"billing-foundation.consent.back": { en: "Back" },
|
|
69
85
|
"billing-foundation.cancel.open": { en: "Cancel contract here" },
|
|
70
86
|
"billing-foundation.cancel.title": { en: "Cancel contract" },
|
|
@@ -125,6 +141,12 @@ export const BILLING_FOUNDATION_I18N = {
|
|
|
125
141
|
"billing-foundation.errors.providerMismatch": {
|
|
126
142
|
en: "This tenant's subscription is on a different provider than the plan catalog. Contact support to migrate.",
|
|
127
143
|
},
|
|
144
|
+
"billing-foundation.errors.providerNotOffered": {
|
|
145
|
+
en: "This payment provider is not available right now.",
|
|
146
|
+
},
|
|
147
|
+
"billing-foundation.errors.providerRequired": {
|
|
148
|
+
en: "Choose a payment provider to continue.",
|
|
149
|
+
},
|
|
128
150
|
"billing-foundation.errors.priceUnavailable": {
|
|
129
151
|
en: "This plan's price is temporarily unavailable.",
|
|
130
152
|
},
|
|
@@ -10,7 +10,7 @@ export { getSubscriptionForTenant, type SubscriptionView } from "./get-subscript
|
|
|
10
10
|
export { paymentsProjectionTable, subscriptionsProjectionTable } from "./projection.ts";
|
|
11
11
|
export { billingPlansPanel, createBillingPlansScreen } from "./screens.ts";
|
|
12
12
|
export { createSubscriptionTierSync, effectiveTierFromSubscription, SUBSCRIPTION_WEBHOOK_PATH, type SubscriptionTierSyncDeps, type TierSyncResult, } from "./subscription-tier-sync.ts";
|
|
13
|
-
export { type BillingFoundationOptions, type BillingPlanBenefit, type BillingPlanCatalog, type BillingPlanContactCard, type BillingPlanPrice, type BillingPlansResult, type BillingPlanView, type ConsumerProtectionOptions, KNOWN_RECURRING_INTERVALS, type LegalLinkSet, type PaymentEvent, type ProviderPrice, type ProviderSubscriptionSnapshot, type RecurringInterval, type SubscriptionEvent, type SubscriptionProviderPlugin, } from "./types.ts";
|
|
13
|
+
export { type BillingFoundationOptions, type BillingPlanBenefit, type BillingPlanCatalog, type BillingPlanContactCard, type BillingPlanPrice, type BillingPlansResult, type BillingPlanView, type BillingProviderView, type ConsumerProtectionOptions, KNOWN_RECURRING_INTERVALS, type LegalLinkSet, type PaymentEvent, type PlanSwitchPreview, type ProviderPrice, type ProviderSubscriptionSnapshot, type RecurringInterval, type SubscriptionEvent, type SubscriptionProviderPlugin, } from "./types.ts";
|
|
14
14
|
export { BILLING_FOUNDATION_FEATURE, BILLING_PLANS_PANEL_COMPONENT, BILLING_PLANS_SCREEN_ID, type BillingEventKind, BillingEventKinds, type BillingPlanAction, BillingPlanActions, DEFAULT_PURCHASE_ROLES, isSwitchableSubscriptionStatus, isTerminalSubscriptionStatus, parseProviderConsentId, SUBSCRIPTION_PROVIDER_EXTENSION, type SubscriptionCancelTiming, SubscriptionCancelTimings, type SubscriptionEventType, SubscriptionEventTypes, SubscriptionFoundationHandlers, SubscriptionFoundationQueries, type SubscriptionStatus, SubscriptionStatuses, } from "./web/constants.ts";
|
|
15
15
|
export { CONSENT_LOCALES, type ConsentLocale, resolveConsentLocale, } from "./web/consumer-protection/consent-locale.ts";
|
|
16
16
|
export { createSubscriptionWebhookRoute, type SubscriptionWebhookRouteOptions, } from "./webhook-handler.ts";
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
-
import
|
|
2
|
+
import { getSubscriptionForTenant } from "./get-subscription-for-tenant.ts";
|
|
3
|
+
import { type BillingPlanCatalog, type BillingPlansResult, type BillingPlanView, type ProviderPrice, type SubscriptionProviderPlugin } from "./types.ts";
|
|
3
4
|
/** `catalog?.purchaseRoles ?? DEFAULT_PURCHASE_ROLES` — the one place this
|
|
4
5
|
* fallback is spelled out; plan-catalog.ts and the start-plan-checkout/
|
|
5
6
|
* switch-plan/create-portal-session handlers all call this instead of
|
|
6
7
|
* repeating the literal. Accepts `undefined` so create-portal-session
|
|
7
8
|
* (mounted with or without a catalog) doesn't need its own fallback. */
|
|
8
9
|
export declare function purchaseRolesOf(catalog: Pick<BillingPlanCatalog, "purchaseRoles"> | undefined): readonly string[];
|
|
9
|
-
type ResolvedPlanPrice = {
|
|
10
|
+
export type ResolvedPlanPrice = {
|
|
10
11
|
readonly priceId: string;
|
|
11
12
|
readonly price: ProviderPrice;
|
|
12
13
|
} | null;
|
|
@@ -15,9 +16,24 @@ type ResolvedPlanPrice = {
|
|
|
15
16
|
* one the plan means — and resolves to null with a warning instead of an
|
|
16
17
|
* arbitrary pick. */
|
|
17
18
|
export declare function resolvePlanPrices(ctx: HandlerContext, plugin: SubscriptionProviderPlugin, catalog: BillingPlanCatalog): Promise<ReadonlyMap<string, ResolvedPlanPrice>>;
|
|
19
|
+
/** Tenant-level state every provider's plan list is measured against; loaded
|
|
20
|
+
* once so a multi-provider result does not repeat the reads per provider. */
|
|
21
|
+
export type BillingPlansBase = {
|
|
22
|
+
readonly currentTierValue: string;
|
|
23
|
+
readonly subscriptionView: Awaited<ReturnType<typeof getSubscriptionForTenant>>;
|
|
24
|
+
readonly subscription: BillingPlansResult["subscription"];
|
|
25
|
+
readonly blockingSubscription: boolean;
|
|
26
|
+
readonly canPurchase: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare function loadBillingPlansBase(ctx: HandlerContext, catalog: BillingPlanCatalog, now: () => Temporal.Instant): Promise<BillingPlansBase>;
|
|
29
|
+
export type ProviderPlanViews = {
|
|
30
|
+
readonly enabled: boolean;
|
|
31
|
+
readonly inAppSwitch: boolean;
|
|
32
|
+
readonly plans: readonly BillingPlanView[];
|
|
33
|
+
};
|
|
18
34
|
/** `plugin === null` — no provider is registered/resolvable for this
|
|
19
|
-
* catalog (see checkout-core's `
|
|
35
|
+
* catalog (see checkout-core's `listCatalogProviders`) — collapses onto the
|
|
20
36
|
* same "enabled: false" result a disabled provider produces, rather than
|
|
21
37
|
* a separate error shape the panel would need to special-case. */
|
|
22
|
-
export declare function
|
|
23
|
-
export
|
|
38
|
+
export declare function buildProviderPlanViews(ctx: HandlerContext, plugin: SubscriptionProviderPlugin | null, catalog: BillingPlanCatalog, base: BillingPlansBase): Promise<ProviderPlanViews>;
|
|
39
|
+
export declare function toBillingPlansResult(catalog: BillingPlanCatalog, base: BillingPlansBase, views: ProviderPlanViews): BillingPlansResult;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Internal — not re-exported from index.ts.
|
|
5
5
|
import { isPluginBillingEnabled } from "./checkout-core.js";
|
|
6
6
|
import { getSubscriptionForTenant } from "./get-subscription-for-tenant.js";
|
|
7
|
+
import { canSwitchPlans, canSwitchPlansInApp, } from "./types.js";
|
|
7
8
|
import { BillingPlanActions, DEFAULT_PURCHASE_ROLES, isSubscriptionBlockingCheckout, isSwitchableSubscriptionStatus, SubscriptionStatuses, } from "./web/constants.js";
|
|
8
9
|
function userHasAnyRole(userRoles, allowedRoles) {
|
|
9
10
|
return allowedRoles.some((role) => userRoles.includes(role));
|
|
@@ -80,7 +81,7 @@ function canSwitchToTier(tier, subscription, plugin) {
|
|
|
80
81
|
return (isSwitchableSubscriptionStatus(subscription.status) &&
|
|
81
82
|
tier !== subscription.tier &&
|
|
82
83
|
subscription.cancelAt === null &&
|
|
83
|
-
plugin
|
|
84
|
+
canSwitchPlans(plugin));
|
|
84
85
|
}
|
|
85
86
|
/** One plan-row's action — pulled out of `buildBillingPlans`' map callback so
|
|
86
87
|
* that function's own complexity stays under the guard's budget. paymentPending
|
|
@@ -105,29 +106,32 @@ function resolvePlanAction(tier, isCurrent, price, enabled, canPurchase, subscri
|
|
|
105
106
|
? BillingPlanActions.switch
|
|
106
107
|
: BillingPlanActions.unavailable;
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
-
* catalog (see checkout-core's `findCatalogProvider`) — collapses onto the
|
|
110
|
-
* same "enabled: false" result a disabled provider produces, rather than
|
|
111
|
-
* a separate error shape the panel would need to special-case. */
|
|
112
|
-
export async function buildBillingPlans(ctx, plugin, catalog, now) {
|
|
109
|
+
export async function loadBillingPlansBase(ctx, catalog, now) {
|
|
113
110
|
const currentTierValue = await catalog.resolveCurrentTier(ctx.db, ctx.user.tenantId);
|
|
114
|
-
const enabled = plugin !== null && (await isPluginBillingEnabled(ctx, plugin));
|
|
115
|
-
const resolvedPrices = enabled && plugin
|
|
116
|
-
? await resolvePlanPrices(ctx, plugin, catalog)
|
|
117
|
-
: new Map(catalog.plans.map((tier) => [tier, null]));
|
|
118
111
|
const subscriptionView = await getSubscriptionForTenant(ctx, ctx.user.tenantId);
|
|
119
|
-
const
|
|
112
|
+
const blockingSubscription = subscriptionView !== null && isSubscriptionBlockingCheckout(subscriptionView, now());
|
|
120
113
|
const subscription = subscriptionView
|
|
121
114
|
? {
|
|
122
115
|
status: subscriptionView.status,
|
|
123
116
|
tier: subscriptionView.tier,
|
|
124
|
-
terminal: !
|
|
117
|
+
terminal: !blockingSubscription,
|
|
125
118
|
currentPeriodEnd: subscriptionView.currentPeriodEnd.toString(),
|
|
126
119
|
cancelAt: subscriptionView.cancelAt?.toString() ?? null,
|
|
127
120
|
}
|
|
128
121
|
: null;
|
|
129
|
-
const
|
|
130
|
-
|
|
122
|
+
const canPurchase = userHasAnyRole(ctx.user.roles, purchaseRolesOf(catalog));
|
|
123
|
+
return { currentTierValue, subscriptionView, subscription, blockingSubscription, canPurchase };
|
|
124
|
+
}
|
|
125
|
+
/** `plugin === null` — no provider is registered/resolvable for this
|
|
126
|
+
* catalog (see checkout-core's `listCatalogProviders`) — collapses onto the
|
|
127
|
+
* same "enabled: false" result a disabled provider produces, rather than
|
|
128
|
+
* a separate error shape the panel would need to special-case. */
|
|
129
|
+
export async function buildProviderPlanViews(ctx, plugin, catalog, base) {
|
|
130
|
+
const { currentTierValue, subscription, canPurchase } = base;
|
|
131
|
+
const enabled = plugin !== null && (await isPluginBillingEnabled(ctx, plugin));
|
|
132
|
+
const resolvedPrices = enabled && plugin
|
|
133
|
+
? await resolvePlanPrices(ctx, plugin, catalog)
|
|
134
|
+
: new Map(catalog.plans.map((tier) => [tier, null]));
|
|
131
135
|
const plans = catalog.plans.map((tier) => {
|
|
132
136
|
const isCurrent = tier === currentTierValue;
|
|
133
137
|
const resolved = resolvedPrices.get(tier) ?? null;
|
|
@@ -149,8 +153,12 @@ export async function buildBillingPlans(ctx, plugin, catalog, now) {
|
|
|
149
153
|
action,
|
|
150
154
|
};
|
|
151
155
|
});
|
|
156
|
+
return { enabled, inAppSwitch: plugin !== null && canSwitchPlansInApp(plugin), plans };
|
|
157
|
+
}
|
|
158
|
+
export function toBillingPlansResult(catalog, base, views) {
|
|
159
|
+
const { currentTierValue, subscription, canPurchase } = base;
|
|
152
160
|
return {
|
|
153
|
-
enabled,
|
|
161
|
+
enabled: views.enabled,
|
|
154
162
|
currentTier: {
|
|
155
163
|
tier: currentTierValue,
|
|
156
164
|
labelKey: catalog.tierLabelKey(currentTierValue),
|
|
@@ -158,7 +166,8 @@ export async function buildBillingPlans(ctx, plugin, catalog, now) {
|
|
|
158
166
|
},
|
|
159
167
|
subscription,
|
|
160
168
|
canPurchase,
|
|
161
|
-
plans,
|
|
169
|
+
plans: views.plans,
|
|
170
|
+
...(views.inAppSwitch && { inAppPlanSwitch: true }),
|
|
162
171
|
...(catalog.contact !== undefined && { contact: catalog.contact }),
|
|
163
172
|
};
|
|
164
173
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { type ResolvedProvider } from "./checkout-core.ts";
|
|
3
|
+
import { type SubscriptionView } from "./get-subscription-for-tenant.ts";
|
|
4
|
+
import { type ResolvedPlanPrice } from "./plan-catalog.ts";
|
|
5
|
+
import { type BillingPlanCatalog } from "./types.ts";
|
|
6
|
+
export type PreparedPlanSwitch = {
|
|
7
|
+
readonly provider: ResolvedProvider;
|
|
8
|
+
readonly subscription: SubscriptionView;
|
|
9
|
+
readonly target: NonNullable<ResolvedPlanPrice>;
|
|
10
|
+
readonly allowedPriceIds: readonly string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function preparePlanSwitch(ctx: HandlerContext, catalog: BillingPlanCatalog, tier: string, handlerName: string): Promise<PreparedPlanSwitch>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Pre-checks shared by switch-plan and plan-switch-preview: both must agree on
|
|
2
|
+
// which subscription, provider and target price a plan switch means.
|
|
3
|
+
// Internal — not re-exported from index.ts.
|
|
4
|
+
import { ConflictError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
|
+
import { assertBillingEnabled, findProviderPlugin, listCatalogProviders, } from "./checkout-core.js";
|
|
6
|
+
import { getSubscriptionForTenant } from "./get-subscription-for-tenant.js";
|
|
7
|
+
import { resolvePlanPrices } from "./plan-catalog.js";
|
|
8
|
+
import { canSwitchPlans } from "./types.js";
|
|
9
|
+
import { isSwitchableSubscriptionStatus } from "./web/constants.js";
|
|
10
|
+
export async function preparePlanSwitch(ctx, catalog, tier, handlerName) {
|
|
11
|
+
const subscription = await getSubscriptionForTenant(ctx, ctx.user.tenantId);
|
|
12
|
+
if (!subscription || !isSwitchableSubscriptionStatus(subscription.status)) {
|
|
13
|
+
throw new ConflictError({
|
|
14
|
+
i18nKey: "billing-foundation.errors.noActiveSubscription",
|
|
15
|
+
message: "tenant has no switchable subscription; use billing-foundation:write:start-plan-checkout to start one",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// providerSubscriptionId only exists at the subscription's own provider; a catalog that
|
|
19
|
+
// no longer sells through that provider cannot switch it.
|
|
20
|
+
const provider = findProviderPlugin(ctx, subscription.providerName);
|
|
21
|
+
const isCatalogProvider = provider !== null &&
|
|
22
|
+
listCatalogProviders(ctx, catalog).some((candidate) => candidate.name === provider.name);
|
|
23
|
+
if (!provider || !isCatalogProvider) {
|
|
24
|
+
throw new ConflictError({
|
|
25
|
+
i18nKey: "billing-foundation.errors.providerMismatch",
|
|
26
|
+
message: `tenant's active subscription is on provider "${subscription.providerName}", which the plan catalog does not sell through — switch-plan cannot switch across providers`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
await assertBillingEnabled(ctx, provider.plugin, handlerName);
|
|
30
|
+
if (subscription.cancelAt !== null) {
|
|
31
|
+
throw new ConflictError({
|
|
32
|
+
i18nKey: "billing-foundation.errors.cancellationScheduled",
|
|
33
|
+
message: "subscription has a scheduled cancellation; reactivate it via billing-foundation:write:create-portal-session before switching plans",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (tier === subscription.tier) {
|
|
37
|
+
throw new ConflictError({
|
|
38
|
+
i18nKey: "billing-foundation.errors.alreadyOnPlan",
|
|
39
|
+
message: `tenant is already on plan "${tier}"`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!canSwitchPlans(provider.plugin)) {
|
|
43
|
+
throw new UnprocessableError("plan_switch_not_supported", {
|
|
44
|
+
i18nKey: "billing-foundation.errors.planSwitchNotSupported",
|
|
45
|
+
message: `subscription-foundation: provider "${subscription.providerName}" supports neither createPlanSwitchSession nor previewPlanSwitch + applyPlanSwitch`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const prices = await resolvePlanPrices(ctx, provider.plugin, catalog);
|
|
49
|
+
const target = prices.get(tier);
|
|
50
|
+
if (!target) {
|
|
51
|
+
throw new UnprocessableError("price_unavailable", {
|
|
52
|
+
i18nKey: "billing-foundation.errors.priceUnavailable",
|
|
53
|
+
message: `subscription-foundation: no live price resolved for tier "${tier}"`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const allowedPriceIds = [...prices.values()]
|
|
57
|
+
.filter((p) => p !== null)
|
|
58
|
+
.map((p) => p.priceId);
|
|
59
|
+
return { provider, subscription, target, allowedPriceIds };
|
|
60
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type EntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
|
|
1
2
|
export declare const subscriptionsProjectionTable: import("@cosmicdrift/kumiko-framework/db").EntityTable<import("@cosmicdrift/kumiko-types/fields").EntityDefinition<{
|
|
2
3
|
readonly providerName: import("@cosmicdrift/kumiko-types/fields").EntityTextFieldDef & {
|
|
3
4
|
required: true;
|
|
@@ -56,22 +57,25 @@ export declare const paymentsProjectionTable: import("@cosmicdrift/kumiko-framew
|
|
|
56
57
|
required: true;
|
|
57
58
|
};
|
|
58
59
|
}, "tenant">>;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*
|
|
60
|
+
export declare const subscriptionsProjectionMeta: EntityTableMeta;
|
|
61
|
+
export declare const paymentsProjectionMeta: EntityTableMeta;
|
|
62
|
+
/** subscription-created → UPSERT with all fields. PK = aggregateId =
|
|
63
|
+
* subscriptionAggregateId(tenantId), with one row per tenant. UPSERT allows
|
|
64
|
+
* the Disney+-change pattern (a second provider sends create for the same
|
|
65
|
+
* tenant) to overwrite the existing row instead of causing a PK conflict. */
|
|
63
66
|
export declare const applySubscriptionCreated: import("@cosmicdrift/kumiko-types/projection").SingleStreamApplyFn;
|
|
64
|
-
/** subscription-updated → UPSERT
|
|
67
|
+
/** subscription-updated → UPSERT with all fields. */
|
|
65
68
|
export declare const applySubscriptionUpdated: import("@cosmicdrift/kumiko-types/projection").SingleStreamApplyFn;
|
|
66
69
|
/** subscription-canceled → status/tier/currentPeriodEnd patchen. */
|
|
67
70
|
export declare const applySubscriptionCanceled: import("@cosmicdrift/kumiko-types/projection").SingleStreamApplyFn;
|
|
68
|
-
/** invoice-paid → state
|
|
69
|
-
*
|
|
71
|
+
/** invoice-paid → state update (status, tier, currentPeriodEnd).
|
|
72
|
+
* The invoice history itself lives in the event store, so it remains
|
|
73
|
+
* replayable. */
|
|
70
74
|
export declare const applyInvoicePaid: import("@cosmicdrift/kumiko-types/projection").SingleStreamApplyFn;
|
|
71
|
-
/** invoice-payment-failed → status (
|
|
72
|
-
* engine
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
+
/** invoice-payment-failed → status (typically past_due) + tier. The tier
|
|
76
|
+
* engine reads the row and decides whether to downgrade. currentPeriodEnd is
|
|
77
|
+
* intentionally unchanged: the period has not "ended"; payment simply
|
|
78
|
+
* failed. */
|
|
75
79
|
export declare const applyInvoicePaymentFailed: import("@cosmicdrift/kumiko-types/projection").SingleStreamApplyFn;
|
|
76
80
|
/** payment-received → INSERT-once row keyed by a deterministic uuid derived
|
|
77
81
|
* from (tenantId, providerName, providerEventId) — NOT `event.id`, which is
|