@cosmicdrift/kumiko-samples 0.378.0 → 0.380.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/package.json +1 -1
- package/packages/bundled-features/package.json +18 -17
- package/packages/bundled-features/src/account-security/feature.ts +2 -2
- package/packages/bundled-features/src/account-security/index.ts +8 -8
- package/packages/bundled-features/src/account-security/{constants.ts → web/constants.ts} +0 -2
- package/packages/bundled-features/src/account-security/{i18n.ts → web/i18n.ts} +0 -1
- package/packages/bundled-features/src/admin-shell/feature.ts +10 -9
- package/packages/bundled-features/src/admin-shell/index.ts +8 -8
- package/packages/bundled-features/src/admin-shell/web/client-plugin.tsx +2 -3
- package/packages/bundled-features/src/admin-shell/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/admin-shell/web/default-translations.ts +10 -0
- package/packages/bundled-features/src/admin-shell/web/i18n.ts +51 -9
- package/packages/bundled-features/src/admin-shell/web/index.ts +0 -1
- package/packages/bundled-features/src/audit/changes.json +7 -0
- package/packages/bundled-features/src/audit/feature.ts +72 -64
- package/packages/bundled-features/src/audit/handlers/audit-events.ts +153 -0
- package/packages/bundled-features/src/audit/handlers/audit-system-db.ts +13 -0
- package/packages/bundled-features/src/audit/handlers/details-all-tenants.query.ts +26 -0
- package/packages/bundled-features/src/audit/handlers/details.query.ts +11 -39
- package/packages/bundled-features/src/audit/handlers/list-all-tenants.query.ts +47 -0
- package/packages/bundled-features/src/audit/handlers/list.query.ts +16 -123
- package/packages/bundled-features/src/audit/handlers/resolve-audit-tenant.ts +21 -7
- package/packages/bundled-features/src/audit/index.ts +1 -1
- package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +4 -4
- package/packages/bundled-features/src/audit/screens.ts +82 -0
- package/packages/bundled-features/src/audit/web/client-plugin.tsx +2 -3
- package/packages/bundled-features/src/audit/{constants.ts → web/constants.ts} +11 -1
- package/packages/bundled-features/src/audit/web/default-translations.ts +7 -0
- package/packages/bundled-features/src/audit/web/i18n.ts +33 -7
- package/packages/bundled-features/src/audit/web/index.ts +0 -2
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +6 -6
- package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
- package/packages/bundled-features/src/auth-email-password/errors.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +4 -4
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +38 -4
- package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/index.ts +6 -1
- package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/signup-activation.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +0 -1
- package/packages/bundled-features/src/auth-email-password/web/auth-form-logic.ts +0 -1
- package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +0 -1
- package/packages/bundled-features/src/auth-email-password/web/client-actions.ts +0 -1
- package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +1 -2
- package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/{constants.ts → web/constants.ts} +3 -6
- package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/{i18n.ts → web/i18n.ts} +3 -4
- package/packages/bundled-features/src/auth-email-password/web/index.ts +1 -2
- package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +2 -3
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/{mail-i18n.ts → web/mail-i18n.ts} +0 -1
- package/packages/bundled-features/src/auth-email-password/web/request-account-unlock-screen.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/session-bootstrap-error.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/session.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +2 -3
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/tenant-menu-items.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +0 -1
- package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +0 -1
- package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +0 -1
- package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
- package/packages/bundled-features/src/auth-mfa/errors.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/index.ts +8 -8
- package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/screens.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +0 -1
- package/packages/bundled-features/src/auth-mfa/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/auth-mfa/web/i18n.ts +0 -1
- package/packages/bundled-features/src/auth-mfa/web/index.ts +0 -1
- package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +0 -1
- package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +1 -2
- package/packages/bundled-features/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +0 -1
- package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +1 -2
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +21 -23
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +0 -1
- package/packages/bundled-features/src/billing-foundation/changes.json +18 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-text.ts +2 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +11 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +11 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +12 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +12 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +6 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +167 -28
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +102 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +43 -12
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +202 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +70 -0
- package/packages/bundled-features/src/billing-foundation/events.ts +20 -2
- package/packages/bundled-features/src/billing-foundation/feature.ts +5 -5
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +82 -9
- package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/index.ts +27 -27
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +7 -7
- package/packages/bundled-features/src/billing-foundation/screens.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +1 -1
- package/packages/bundled-features/src/billing-foundation/types.ts +17 -3
- package/packages/bundled-features/src/billing-foundation/validate-options.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/web/billing-contact-plan.tsx +1 -3
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +5 -7
- package/packages/bundled-features/src/billing-foundation/web/cancel-contract-dialog.tsx +1 -3
- package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +1 -3
- package/packages/bundled-features/src/billing-foundation/web/client-plugin.tsx +1 -2
- package/packages/bundled-features/src/billing-foundation/{constants.ts → web/constants.ts} +37 -31
- package/packages/bundled-features/src/billing-foundation/{consumer-protection → web/consumer-protection}/consent-locale.ts +0 -1
- package/packages/bundled-features/src/billing-foundation/{contact-card.ts → web/contact-card.ts} +0 -2
- package/packages/bundled-features/src/billing-foundation/web/index.ts +14 -15
- package/packages/bundled-features/src/billing-foundation/web/localized-params.ts +0 -2
- package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +5 -2
- package/packages/bundled-features/src/cap-overview/feature.ts +1 -1
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -2
- package/packages/bundled-features/src/cap-overview/index.ts +9 -9
- package/packages/bundled-features/src/cap-overview/screens.ts +2 -2
- package/packages/bundled-features/src/cap-overview/web/cap-cards-panel.tsx +1 -2
- package/packages/bundled-features/src/cap-overview/web/cap-usage-bar.tsx +1 -2
- package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +0 -2
- package/packages/bundled-features/src/cap-overview/web/client-plugin.tsx +3 -4
- package/packages/bundled-features/src/cap-overview/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/cap-overview/web/index.ts +0 -1
- package/packages/bundled-features/src/cap-overview/{usage-math.ts → web/usage-math.ts} +1 -2
- package/packages/bundled-features/src/compliance-profiles/feature.ts +5 -5
- package/packages/bundled-features/src/compliance-profiles/index.ts +6 -6
- package/packages/bundled-features/src/compliance-profiles/web/client-plugin.tsx +2 -3
- package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +1 -2
- package/packages/bundled-features/src/compliance-profiles/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/compliance-profiles/web/i18n.ts +0 -1
- package/packages/bundled-features/src/compliance-profiles/web/index.ts +0 -1
- package/packages/bundled-features/src/config/index.ts +6 -6
- package/packages/bundled-features/src/config/web/client-plugin.ts +1 -2
- package/packages/bundled-features/src/config/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/config/web/i18n.ts +0 -1
- package/packages/bundled-features/src/config/web/index.ts +1 -3
- package/packages/bundled-features/src/config/write-helpers.ts +1 -1
- package/packages/bundled-features/src/custom-fields/feature.ts +8 -8
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +4 -4
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +1 -1
- package/packages/bundled-features/src/custom-fields/index.ts +8 -8
- package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +1 -1
- package/packages/bundled-features/src/custom-fields/schemas.ts +1 -1
- package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +1 -2
- package/packages/bundled-features/src/custom-fields/{constants.ts → web/constants.ts} +18 -19
- package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +1 -2
- package/packages/bundled-features/src/custom-fields/web/i18n.ts +0 -1
- package/packages/bundled-features/src/custom-fields/web/index.ts +2 -3
- package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +1 -2
- package/packages/bundled-features/src/delivery/address-opt-out.ts +1 -1
- package/packages/bundled-features/src/delivery/attempt-log-retention.ts +24 -8
- package/packages/bundled-features/src/delivery/changes.json +6 -0
- package/packages/bundled-features/src/delivery/constants.ts +2 -2
- package/packages/bundled-features/src/delivery/i18n.ts +1 -1
- package/packages/bundled-features/src/delivery/unsubscribe-page.ts +1 -1
- package/packages/bundled-features/src/delivery/web/client-plugin.tsx +6 -7
- package/packages/bundled-features/src/delivery/web/delivery-channel-cell.tsx +0 -1
- package/packages/bundled-features/src/delivery/web/delivery-error-cell.tsx +0 -1
- package/packages/bundled-features/src/delivery/web/delivery-status-cell.tsx +1 -2
- package/packages/bundled-features/src/delivery/web/delivery-time-cell.tsx +0 -1
- package/packages/bundled-features/src/delivery/web/delivery-type-cell.tsx +0 -1
- package/packages/bundled-features/src/delivery/web/index.ts +0 -1
- package/packages/bundled-features/src/delivery/{public-names.ts → web/public-names.ts} +0 -1
- package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +4 -0
- package/packages/bundled-features/src/document-ingest-foundation/executor.ts +2 -1
- package/packages/bundled-features/src/document-ingest-foundation/feature.ts +18 -4
- package/packages/bundled-features/src/document-ingest-foundation/index.ts +1 -0
- package/packages/bundled-features/src/feature-toggles/feature.ts +7 -7
- package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +14 -13
- package/packages/bundled-features/src/feature-toggles/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +3 -3
- package/packages/bundled-features/src/feature-toggles/index.ts +6 -6
- package/packages/bundled-features/src/feature-toggles/web/client-plugin.tsx +1 -2
- package/packages/bundled-features/src/feature-toggles/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/feature-toggles/web/i18n.ts +0 -1
- package/packages/bundled-features/src/feature-toggles/web/index.ts +0 -1
- package/packages/bundled-features/src/files/changes.json +7 -0
- package/packages/bundled-features/src/folders/feature.ts +1 -1
- package/packages/bundled-features/src/folders/handlers/clear-folder.write.ts +1 -1
- package/packages/bundled-features/src/folders/handlers/delete-folder.write.ts +1 -1
- package/packages/bundled-features/src/folders/handlers/set-folder.write.ts +1 -1
- package/packages/bundled-features/src/folders/index.ts +8 -8
- package/packages/bundled-features/src/folders/web/client-plugin.tsx +1 -3
- package/packages/bundled-features/src/folders/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/folders/web/folder-manager.tsx +1 -2
- package/packages/bundled-features/src/folders/web/folder-section.tsx +1 -2
- package/packages/bundled-features/src/folders/web/i18n.ts +0 -1
- package/packages/bundled-features/src/folders/web/index.ts +1 -2
- package/packages/bundled-features/src/folders/web/tree.ts +0 -1
- package/packages/bundled-features/src/form-draft/changes.json +7 -0
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +25 -11
- package/packages/bundled-features/src/form-draft/decrypt-draft.ts +24 -0
- package/packages/bundled-features/src/form-draft/entity.ts +6 -9
- package/packages/bundled-features/src/form-draft/feature.ts +1 -1
- package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +26 -5
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
- package/packages/bundled-features/src/form-draft/handlers/get.query.ts +6 -7
- package/packages/bundled-features/src/form-draft/handlers/list.query.ts +13 -7
- package/packages/bundled-features/src/form-draft/handlers/save.write.ts +5 -5
- package/packages/bundled-features/src/form-draft/index.ts +8 -8
- package/packages/bundled-features/src/form-draft/lookup.ts +10 -4
- package/packages/bundled-features/src/form-draft/schemas.ts +1 -1
- package/packages/bundled-features/src/form-draft/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/form-draft-user-data/hooks.ts +2 -2
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +10 -10
- package/packages/bundled-features/src/jobs/feature.ts +7 -7
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +1 -1
- package/packages/bundled-features/src/jobs/index.ts +1 -1
- package/packages/bundled-features/src/jobs/job-run-table.ts +4 -3
- package/packages/bundled-features/src/jobs/web/client-plugin.tsx +2 -3
- package/packages/bundled-features/src/jobs/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/jobs/web/default-translations.ts +7 -0
- package/packages/bundled-features/src/jobs/web/i18n.ts +53 -7
- package/packages/bundled-features/src/jobs/web/index.ts +0 -1
- package/packages/bundled-features/src/ledger/changes.json +7 -0
- package/packages/bundled-features/src/ledger/entity.ts +1 -1
- package/packages/bundled-features/src/ledger/feature.ts +1 -1
- package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +2 -10
- package/packages/bundled-features/src/ledger/handlers/create-transaction.write.ts +1 -1
- package/packages/bundled-features/src/ledger/handlers/reports.query.ts +1 -1
- package/packages/bundled-features/src/ledger/handlers/reverse-transaction.write.ts +1 -1
- package/packages/bundled-features/src/ledger/index.ts +13 -13
- package/packages/bundled-features/src/ledger/recurring.ts +1 -1
- package/packages/bundled-features/src/ledger/reports.ts +1 -1
- package/packages/bundled-features/src/ledger/schemas.ts +1 -1
- package/packages/bundled-features/src/ledger/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/ledger/web/index.ts +11 -12
- package/packages/bundled-features/src/legal-pages/README.md +1 -1
- package/packages/bundled-features/src/legal-pages/feature.ts +5 -5
- package/packages/bundled-features/src/legal-pages/index.ts +11 -11
- package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +1 -1
- package/packages/bundled-features/src/legal-pages/lib/validate-routes.ts +1 -1
- package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +1 -2
- package/packages/bundled-features/src/legal-pages/{constants.ts → web/constants.ts} +6 -8
- package/packages/bundled-features/src/legal-pages/web/index.ts +0 -1
- package/packages/bundled-features/src/managed-pages/feature.ts +1 -1
- package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +1 -2
- package/packages/bundled-features/src/managed-pages/web/default-translations.ts +17 -0
- package/packages/bundled-features/src/managed-pages/web/i18n.ts +59 -16
- package/packages/bundled-features/src/managed-pages/web/index.ts +1 -2
- package/packages/bundled-features/src/metrics/compute.ts +1 -1
- package/packages/bundled-features/src/metrics/feature.ts +6 -6
- package/packages/bundled-features/src/metrics/index.ts +8 -8
- package/packages/bundled-features/src/metrics/types.ts +1 -1
- package/packages/bundled-features/src/metrics/validate.ts +1 -1
- package/packages/bundled-features/src/metrics/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/metrics/window.ts +1 -1
- package/packages/bundled-features/src/notes-history/feature.ts +1 -1
- package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
- package/packages/bundled-features/src/notes-history/index.ts +8 -8
- package/packages/bundled-features/src/notes-history/web/client-plugin.tsx +1 -3
- package/packages/bundled-features/src/notes-history/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/notes-history/web/i18n.ts +0 -1
- package/packages/bundled-features/src/notes-history/web/index.ts +2 -3
- package/packages/bundled-features/src/notes-history/web/notes-section.tsx +2 -3
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +12 -10
- package/packages/bundled-features/src/personal-access-tokens/handlers/available-scopes.query.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/hash.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/i18n.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/index.ts +5 -5
- package/packages/bundled-features/src/personal-access-tokens/resolver.ts +1 -1
- package/packages/bundled-features/src/personal-access-tokens/schema/api-token.ts +3 -3
- package/packages/bundled-features/src/personal-access-tokens/screens.ts +2 -2
- package/packages/bundled-features/src/personal-access-tokens/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/personal-access-tokens/{scopes.ts → web/scopes.ts} +0 -1
- package/packages/bundled-features/src/presets/changes.json +7 -0
- package/packages/bundled-features/src/presets/index.ts +0 -1
- package/packages/bundled-features/src/presets/security-baseline.ts +1 -1
- package/packages/bundled-features/src/renderer-foundation/types.ts +1 -1
- package/packages/bundled-features/src/secrets/feature.ts +1 -1
- package/packages/bundled-features/src/secrets/handlers/delete.write.ts +1 -1
- package/packages/bundled-features/src/secrets/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/secrets/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/secrets/i18n.ts +1 -1
- package/packages/bundled-features/src/secrets/index.ts +8 -8
- package/packages/bundled-features/src/secrets/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
- package/packages/bundled-features/src/sessions/feature.ts +7 -4
- package/packages/bundled-features/src/sessions/schema/user-session.ts +2 -3
- package/packages/bundled-features/src/sessions/{i18n.ts → web/i18n.ts} +0 -1
- package/packages/bundled-features/src/shared/index.ts +6 -1
- package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +10 -2
- package/packages/bundled-features/src/shared/web/translate-or-raw.ts +0 -1
- package/packages/bundled-features/src/subscription-stripe/feature.ts +2 -0
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -0
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +1 -0
- package/packages/bundled-features/src/tags/changes.json +6 -0
- package/packages/bundled-features/src/tags/feature.ts +1 -1
- package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +11 -7
- package/packages/bundled-features/src/tags/handlers/create-tag.write.ts +1 -1
- package/packages/bundled-features/src/tags/handlers/delete-tag.write.ts +4 -1
- package/packages/bundled-features/src/tags/handlers/remove-tag.write.ts +1 -1
- package/packages/bundled-features/src/tags/handlers/update-tag.write.ts +1 -1
- package/packages/bundled-features/src/tags/index.ts +12 -12
- package/packages/bundled-features/src/tags/screens.ts +1 -1
- package/packages/bundled-features/src/tags/web/client-plugin.tsx +1 -3
- package/packages/bundled-features/src/tags/{constants.ts → web/constants.ts} +4 -1
- package/packages/bundled-features/src/tags/web/entity-tags.tsx +1 -2
- package/packages/bundled-features/src/tags/web/i18n.ts +2 -2
- package/packages/bundled-features/src/tags/web/index.ts +2 -3
- package/packages/bundled-features/src/tags/web/tag-chip.tsx +0 -1
- package/packages/bundled-features/src/tags/web/tag-filter.tsx +1 -2
- package/packages/bundled-features/src/tags/web/tag-manager.tsx +5 -4
- package/packages/bundled-features/src/tags/web/tag-picker.tsx +0 -1
- package/packages/bundled-features/src/tags/web/tag-section.tsx +1 -2
- package/packages/bundled-features/src/tags/web/tags-cell.tsx +1 -2
- package/packages/bundled-features/src/template-resolver/README.md +1 -1
- package/packages/bundled-features/src/template-resolver/api.ts +75 -28
- package/packages/bundled-features/src/template-resolver/changes.json +43 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -1
- package/packages/bundled-features/src/template-resolver/compile-steps-block.ts +2 -2
- package/packages/bundled-features/src/template-resolver/document-ops.ts +5 -5
- package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +4 -4
- package/packages/bundled-features/src/template-resolver/feature.ts +13 -4
- package/packages/bundled-features/src/template-resolver/handlers/by-slug.query.ts +6 -1
- package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +6 -2
- package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +19 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +7 -1
- package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +8 -2
- package/packages/bundled-features/src/template-resolver/handlers/document-blocks.query.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +5 -2
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +4 -2
- package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +10 -5
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -2
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +68 -10
- package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +6 -6
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +8 -2
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +4 -2
- package/packages/bundled-features/src/template-resolver/i18n.ts +25 -0
- package/packages/bundled-features/src/template-resolver/index.ts +60 -59
- package/packages/bundled-features/src/template-resolver/layout-appearance.ts +1 -1
- package/packages/bundled-features/src/template-resolver/layout-regions.ts +1 -1
- package/packages/bundled-features/src/template-resolver/seeding.ts +16 -7
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +2 -2
- package/packages/bundled-features/src/template-resolver/table.ts +1 -1
- package/packages/bundled-features/src/template-resolver/template-bundle.ts +2 -2
- package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +82 -0
- package/packages/bundled-features/src/template-resolver/template-draft.ts +1 -1
- package/packages/bundled-features/src/template-resolver/template-edit-policy.ts +2 -2
- package/packages/bundled-features/src/template-resolver/template-meta.ts +2 -2
- package/packages/bundled-features/src/template-resolver/template-preview.ts +14 -10
- package/packages/bundled-features/src/template-resolver/template-usage.ts +1 -1
- package/packages/bundled-features/src/template-resolver/template-validation.ts +2 -2
- package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
- package/packages/bundled-features/src/template-resolver/user-content-table.ts +1 -1
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +4 -5
- package/packages/bundled-features/src/template-resolver/{constants.ts → web/constants.ts} +7 -10
- package/packages/bundled-features/src/template-resolver/{document-blocks.ts → web/document-blocks.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +2 -3
- package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +0 -1
- package/packages/bundled-features/src/template-resolver/{document-images.ts → web/document-images.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/{document-schema.ts → web/document-schema.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/{document-steps.ts → web/document-steps.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +6 -7
- package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +2 -3
- package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +1 -2
- package/packages/bundled-features/src/template-resolver/web/i18n.ts +2 -1
- package/packages/bundled-features/src/template-resolver/web/index.ts +13 -14
- package/packages/bundled-features/src/template-resolver/{mail-constants.ts → web/mail-constants.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/{pdf-constants.ts → web/pdf-constants.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +0 -1
- package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +5 -4
- package/packages/bundled-features/src/template-resolver/{placeholder-text.ts → web/placeholder-text.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/{qualified-names.ts → web/qualified-names.ts} +0 -1
- package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +0 -1
- package/packages/bundled-features/src/tenant/handlers/add-member.write.ts +1 -1
- package/packages/bundled-features/src/tenant/index.ts +7 -7
- package/packages/bundled-features/src/tenant/invitation-table.ts +1 -1
- package/packages/bundled-features/src/tenant/invited-membership.ts +1 -1
- package/packages/bundled-features/src/tenant/last-tenant-admin.ts +1 -1
- package/packages/bundled-features/src/tenant/member-role-options.ts +1 -1
- package/packages/bundled-features/src/tenant/screens.ts +2 -2
- package/packages/bundled-features/src/tenant/web/client-plugin.tsx +1 -2
- package/packages/bundled-features/src/tenant/{constants.ts → web/constants.ts} +3 -6
- package/packages/bundled-features/src/tenant/web/index.ts +0 -1
- package/packages/bundled-features/src/tenant/web/member-roles-cell.tsx +0 -1
- package/packages/bundled-features/src/tenant/web/member-status-cell.tsx +0 -1
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +2 -2
- package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
- package/packages/bundled-features/src/tier-engine/feature.ts +7 -7
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -1
- package/packages/bundled-features/src/tier-engine/index.ts +8 -8
- package/packages/bundled-features/src/tier-engine/web/client-plugin.tsx +2 -3
- package/packages/bundled-features/src/tier-engine/{constants.ts → web/constants.ts} +2 -3
- package/packages/bundled-features/src/tier-engine/{i18n.ts → web/i18n.ts} +0 -1
- package/packages/bundled-features/src/tier-engine/web/index.ts +0 -1
- package/packages/bundled-features/src/user/handlers/create.write.ts +1 -1
- package/packages/bundled-features/src/user/handlers/update-roles.ts +1 -1
- package/packages/bundled-features/src/user/handlers/update.write.ts +1 -1
- package/packages/bundled-features/src/user/index.ts +1 -1
- package/packages/bundled-features/src/user/invited-global-roles.ts +1 -1
- package/packages/bundled-features/src/user/schema/user.ts +1 -1
- package/packages/bundled-features/src/user/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/user-data-rights/changes.json +14 -0
- package/packages/bundled-features/src/user-data-rights/env.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/feature.ts +9 -9
- package/packages/bundled-features/src/user-data-rights/index.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/lib/resolve-tenant-model.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +2 -3
- package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +0 -1
- package/packages/bundled-features/src/user-data-rights/{constants.ts → web/constants.ts} +11 -12
- package/packages/bundled-features/src/user-data-rights/web/default-translations.ts +91 -0
- package/packages/bundled-features/src/user-data-rights/web/i18n.ts +41 -91
- package/packages/bundled-features/src/user-data-rights/web/index.ts +1 -2
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +1 -2
- package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +0 -1
- package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +0 -1
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +10 -1
- package/packages/bundled-features/src/user-profile/feature.ts +2 -2
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +1 -1
- package/packages/bundled-features/src/user-profile/index.ts +3 -2
- package/packages/bundled-features/src/user-profile/web/client-plugin.ts +2 -3
- package/packages/bundled-features/src/user-profile/{constants.ts → web/constants.ts} +0 -1
- package/packages/bundled-features/src/user-profile/{i18n.ts → web/i18n.ts} +0 -1
- package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -3
- package/packages/bundled-features/src/waitlist/changes.json +6 -0
- package/packages/bundled-features/src/waitlist/entity.ts +1 -1
- package/packages/bundled-features/src/waitlist/feature.ts +5 -5
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +3 -3
- package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +5 -1
- package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +3 -3
- package/packages/bundled-features/src/waitlist/import.ts +1 -1
- package/packages/bundled-features/src/waitlist/index.ts +11 -11
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +1 -1
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +1 -1
- package/packages/bundled-features/src/waitlist/mail.ts +7 -6
- package/packages/bundled-features/src/waitlist/payloads.ts +1 -1
- package/packages/bundled-features/src/waitlist/screens.ts +1 -1
- package/packages/bundled-features/src/waitlist/{constants.ts → web/constants.ts} +0 -1
- package/samples/apps/styleguide/src/features/content/web.tsx +0 -1
- package/samples/apps/ui-walkthrough/src/app/client.tsx +2 -0
- package/samples/apps/ui-walkthrough/src/app/seed-rental.ts +17 -1
- package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +11 -0
- package/samples/apps/ui-walkthrough/src/app/server.ts +2 -1
- package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +35 -7
- package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +6 -0
- package/samples/apps/ui-walkthrough/src/features/rental/entities.ts +27 -0
- package/samples/apps/ui-walkthrough/src/features/rental/feature.ts +61 -7
- package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +48 -24
- package/samples/apps/ui-walkthrough/src/features/rental/invoice-receive.ts +0 -1
- package/samples/apps/ui-walkthrough/src/features/rental/lease-support.ts +17 -1
- package/samples/apps/ui-walkthrough/src/features/rental/qualified-names.ts +3 -0
- package/samples/apps/ui-walkthrough/src/features/rental/receipts-upload.ts +70 -0
- package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +90 -13
- package/samples/apps/ui-walkthrough/src/features/rental/web/index.ts +0 -2
- package/samples/apps/ui-walkthrough/src/features/subscription-plans/feature.ts +103 -0
- package/samples/apps/ui-walkthrough/src/features/subscription-plans/i18n.ts +36 -0
- package/samples/apps/ui-walkthrough/src/features/subscription-plans/index.ts +2 -0
- package/samples/apps/ui-walkthrough/src/features/subscription-plans/seed.ts +21 -0
- package/samples/apps/ui-walkthrough/src/run-config.ts +11 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +1 -1
- package/samples/apps/use-all-bundled/feature-manifest.json +4 -4
- package/samples/apps/use-all-bundled/scripts/check-coverage.ts +1 -1
- package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +3 -1
- package/samples/recipes/compose-stacks/README.md +1 -2
- package/samples/recipes/embedded/README.md +5 -5
- package/samples/recipes/embedded/src/handlers/invoice-receive.write.ts +0 -1
- package/samples/recipes/embedded/src/screens/invoice-receive.screen.ts +9 -12
- package/samples/recipes/legal-pages/README.md +2 -2
- package/samples/recipes/legal-pages/src/feature.ts +1 -1
- package/samples/recipes/reference-data/src/feature.ts +3 -7
- package/packages/bundled-features/src/admin-shell/i18n.ts +0 -54
- package/packages/bundled-features/src/audit/i18n.ts +0 -25
- package/packages/bundled-features/src/auth-mfa/web/qrcode-browser.d.ts +0 -11
- package/packages/bundled-features/src/jobs/i18n.ts +0 -55
- package/packages/bundled-features/src/managed-pages/i18n.ts +0 -63
- package/packages/bundled-features/src/presets/dsgvo-self-service.ts +0 -35
- package/packages/bundled-features/src/user-data-rights/i18n.ts +0 -44
- package/samples/apps/ui-walkthrough/src/features/rental/web/upload-standard-layout.tsx +0 -61
package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// "This cancellation was not me": the signed link in the receipt mail of a
|
|
2
|
+
// matched public declaration takes the cancellation back until it takes effect.
|
|
3
|
+
// The anonymous handler only routes to the declaration's tenant; the system
|
|
4
|
+
// handler checks the token and does the work.
|
|
5
|
+
|
|
6
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
7
|
+
import {
|
|
8
|
+
access,
|
|
9
|
+
createSystemUser,
|
|
10
|
+
type WriteHandlerDef,
|
|
11
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
12
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
13
|
+
import * as z from "zod";
|
|
14
|
+
import { subscriptionAggregateId } from "../aggregate-id.ts";
|
|
15
|
+
import {
|
|
16
|
+
CONTRACT_TERMINATION_DECLARED_EVENT_QN,
|
|
17
|
+
CONTRACT_TERMINATION_REQUESTED_EVENT_QN,
|
|
18
|
+
CONTRACT_TERMINATION_REVOKED_EVENT_QN,
|
|
19
|
+
type ContractTerminationRevokedPayload,
|
|
20
|
+
contractTerminationDeclaredPayloadSchema,
|
|
21
|
+
contractTerminationRequestedPayloadSchema,
|
|
22
|
+
SUBSCRIPTION_AGGREGATE_TYPE,
|
|
23
|
+
} from "../events.ts";
|
|
24
|
+
import { getSubscriptionForTenant } from "../get-subscription-for-tenant.ts";
|
|
25
|
+
import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from "../types.ts";
|
|
26
|
+
import { SubscriptionFoundationHandlers } from "../web/constants.ts";
|
|
27
|
+
import type { ConsentLocale } from "./consent-text.ts";
|
|
28
|
+
import { notifyOperator } from "./termination-notify.ts";
|
|
29
|
+
import { hasRevocation, resumeAtProvider } from "./termination-record.ts";
|
|
30
|
+
import type { OperatorNoticeReason } from "./termination-texts.ts";
|
|
31
|
+
|
|
32
|
+
export const REVOCATION_PATHS: Readonly<Record<ConsentLocale, string>> = {
|
|
33
|
+
de: "/legal/kuendigen/zuruecknehmen",
|
|
34
|
+
en: "/legal/cancel/revoke",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const TOKEN_BYTES = 32;
|
|
38
|
+
|
|
39
|
+
export type RevocationOutcome = "revoked" | "already-revoked" | "too-late" | "invalid";
|
|
40
|
+
|
|
41
|
+
export function createRevocationToken(): { readonly token: string; readonly tokenHash: string } {
|
|
42
|
+
const token = Buffer.from(crypto.getRandomValues(new Uint8Array(TOKEN_BYTES))).toString(
|
|
43
|
+
"base64url",
|
|
44
|
+
);
|
|
45
|
+
return { token, tokenHash: hashRevocationToken(token) };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hashRevocationToken(token: string): string {
|
|
49
|
+
return createHash("sha256").update(token).digest("hex");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function tokenMatchesHash(token: string, expectedHash: string): boolean {
|
|
53
|
+
const actual = Buffer.from(hashRevocationToken(token));
|
|
54
|
+
const expected = Buffer.from(expectedHash);
|
|
55
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function revocationUrl(args: {
|
|
59
|
+
readonly baseUrl: string;
|
|
60
|
+
readonly locale: ConsentLocale;
|
|
61
|
+
readonly requestId: string;
|
|
62
|
+
readonly token: string;
|
|
63
|
+
readonly tenantId: string;
|
|
64
|
+
}): string {
|
|
65
|
+
// Origin only: a baseUrl path prefix would not match the routes mounted at the root.
|
|
66
|
+
const url = new URL(REVOCATION_PATHS[args.locale], new URL(args.baseUrl).origin);
|
|
67
|
+
url.searchParams.set("r", args.requestId);
|
|
68
|
+
url.searchParams.set("t", args.token);
|
|
69
|
+
url.searchParams.set("tenant", args.tenantId);
|
|
70
|
+
return url.toString();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const revocationFieldsSchema = {
|
|
74
|
+
requestId: z.string().min(1).max(100),
|
|
75
|
+
token: z.string().min(1).max(200),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const revokeContractTerminationSchema = z
|
|
79
|
+
.object({ ...revocationFieldsSchema, tenantId: z.uuid() })
|
|
80
|
+
.strict();
|
|
81
|
+
|
|
82
|
+
export const recordContractTerminationRevocationSchema = z.object(revocationFieldsSchema).strict();
|
|
83
|
+
|
|
84
|
+
function parseOutcome(data: unknown): RevocationOutcome {
|
|
85
|
+
const parsed = z
|
|
86
|
+
.object({ outcome: z.enum(["revoked", "already-revoked", "too-late", "invalid"]) })
|
|
87
|
+
.safeParse(data);
|
|
88
|
+
return parsed.success ? parsed.data.outcome : "invalid";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createRevokeContractTerminationHandler(): WriteHandlerDef {
|
|
92
|
+
return {
|
|
93
|
+
name: "revoke-contract-termination",
|
|
94
|
+
description:
|
|
95
|
+
'Public "this was not me" link of the § 312k receipt mail: takes a public cancellation back until it takes effect; an unknown request and a wrong token answer the same.',
|
|
96
|
+
schema: revokeContractTerminationSchema,
|
|
97
|
+
access: { roles: ["anonymous"] },
|
|
98
|
+
rateLimit: { per: "ip+handler", limit: 5, windowSeconds: 600 },
|
|
99
|
+
// The link only knows baseUrl, which may be a host that resolves no tenant; the token alone authorizes.
|
|
100
|
+
tenantlessAnonymous: true,
|
|
101
|
+
escapeHatch: {
|
|
102
|
+
reason:
|
|
103
|
+
"Anonymous revoker has no session — writes as the system user of the tenant named in the link; only a declared event in that tenant whose revocationTokenHash matches the link token lets the revocation through, every other case answers the same generic invalid.",
|
|
104
|
+
},
|
|
105
|
+
handler: async (event, ctx) => {
|
|
106
|
+
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
107
|
+
const payload = event.payload as z.infer<typeof revokeContractTerminationSchema>;
|
|
108
|
+
const result = await ctx.writeAs(
|
|
109
|
+
createSystemUser(payload.tenantId),
|
|
110
|
+
SubscriptionFoundationHandlers.recordContractTerminationRevocation,
|
|
111
|
+
{ requestId: payload.requestId, token: payload.token },
|
|
112
|
+
);
|
|
113
|
+
return {
|
|
114
|
+
isSuccess: true as const,
|
|
115
|
+
data: { outcome: result.isSuccess ? parseOutcome(result.data) : "invalid" },
|
|
116
|
+
};
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function createRecordContractTerminationRevocationHandler(
|
|
122
|
+
options: ResolvedBillingFoundationOptions,
|
|
123
|
+
consumerProtection: ConsumerProtectionOptions,
|
|
124
|
+
): WriteHandlerDef {
|
|
125
|
+
return {
|
|
126
|
+
name: "record-contract-termination-revocation",
|
|
127
|
+
agent: { expose: false },
|
|
128
|
+
schema: recordContractTerminationRevocationSchema,
|
|
129
|
+
access: { roles: access.system },
|
|
130
|
+
handler: async (event, ctx) => {
|
|
131
|
+
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
132
|
+
const payload = event.payload as z.infer<typeof recordContractTerminationRevocationSchema>;
|
|
133
|
+
const tenantId = event.user.tenantId;
|
|
134
|
+
const outcome = (value: RevocationOutcome) => ({
|
|
135
|
+
isSuccess: true as const,
|
|
136
|
+
data: { outcome: value },
|
|
137
|
+
});
|
|
138
|
+
const stream = await ctx.fetchForWriting({
|
|
139
|
+
aggregateId: subscriptionAggregateId(tenantId),
|
|
140
|
+
aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const declared = stream.events
|
|
144
|
+
.filter((e) => e.type === CONTRACT_TERMINATION_DECLARED_EVENT_QN)
|
|
145
|
+
.map((e) => contractTerminationDeclaredPayloadSchema.safeParse(e.payload).data)
|
|
146
|
+
.find((candidate) => candidate?.requestId === payload.requestId);
|
|
147
|
+
if (
|
|
148
|
+
declared?.revocationTokenHash === undefined ||
|
|
149
|
+
!tokenMatchesHash(payload.token, declared.revocationTokenHash)
|
|
150
|
+
) {
|
|
151
|
+
return outcome("invalid");
|
|
152
|
+
}
|
|
153
|
+
if (hasRevocation(stream.events, payload.requestId)) return outcome("already-revoked");
|
|
154
|
+
|
|
155
|
+
const requested = stream.events
|
|
156
|
+
.filter((e) => e.type === CONTRACT_TERMINATION_REQUESTED_EVENT_QN)
|
|
157
|
+
.map((e) => contractTerminationRequestedPayloadSchema.safeParse(e.payload).data)
|
|
158
|
+
.find((candidate) => candidate?.requestId === payload.requestId);
|
|
159
|
+
const subscription = await getSubscriptionForTenant(ctx, tenantId);
|
|
160
|
+
const effectiveAtIso = requested?.effectiveAtIso ?? subscription?.currentPeriodEnd.toString();
|
|
161
|
+
const now = options.now();
|
|
162
|
+
if (
|
|
163
|
+
effectiveAtIso !== undefined &&
|
|
164
|
+
Temporal.Instant.compare(now, Temporal.Instant.from(effectiveAtIso)) >= 0
|
|
165
|
+
) {
|
|
166
|
+
return outcome("too-late");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const reasons: OperatorNoticeReason[] = ["revoked"];
|
|
170
|
+
if (requested?.providerCancel === "period-end") {
|
|
171
|
+
reasons.push(...(await resumeAtProvider(ctx, subscription)));
|
|
172
|
+
}
|
|
173
|
+
// Re-fetch after the external call, like record-contract-termination.
|
|
174
|
+
const streamAfterProviderCall = await ctx.fetchForWriting({
|
|
175
|
+
aggregateId: subscriptionAggregateId(tenantId),
|
|
176
|
+
aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
|
|
177
|
+
});
|
|
178
|
+
const revoked: ContractTerminationRevokedPayload = {
|
|
179
|
+
requestId: payload.requestId,
|
|
180
|
+
revokedAtIso: now.toString(),
|
|
181
|
+
};
|
|
182
|
+
await streamAfterProviderCall.appendOne({
|
|
183
|
+
type: CONTRACT_TERMINATION_REVOKED_EVENT_QN,
|
|
184
|
+
payload: revoked,
|
|
185
|
+
});
|
|
186
|
+
await notifyOperator(ctx, consumerProtection, {
|
|
187
|
+
locale: declared.locale,
|
|
188
|
+
requestId: declared.requestId,
|
|
189
|
+
receivedAtIso: declared.receivedAtIso,
|
|
190
|
+
channel: "public",
|
|
191
|
+
declaration: {
|
|
192
|
+
declarationType: declared.declarationType,
|
|
193
|
+
terminationKind: declared.terminationKind,
|
|
194
|
+
},
|
|
195
|
+
reasons,
|
|
196
|
+
tenantId,
|
|
197
|
+
...(requested !== undefined && { providerCancel: requested.providerCancel }),
|
|
198
|
+
});
|
|
199
|
+
return outcome("revoked");
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|
package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts
CHANGED
|
@@ -19,6 +19,8 @@ export type TerminationTexts = {
|
|
|
19
19
|
readonly receiptIntro: string;
|
|
20
20
|
readonly receiptNextSteps: string;
|
|
21
21
|
readonly receiptRevoke: (operatorEmail: string) => string;
|
|
22
|
+
readonly receiptNotMe: string;
|
|
23
|
+
readonly receiptNotMeLink: string;
|
|
22
24
|
readonly contact: string;
|
|
23
25
|
readonly operatorSubject: Readonly<Record<ContractTerminationDeclarationType, string>>;
|
|
24
26
|
readonly operatorIntro: string;
|
|
@@ -61,6 +63,21 @@ export type PageTexts = {
|
|
|
61
63
|
readonly errorGeneric: string;
|
|
62
64
|
readonly rateLimitedTitle: string;
|
|
63
65
|
readonly rateLimitedBody: string;
|
|
66
|
+
readonly revoke: RevokePageTexts;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type RevokePageTexts = {
|
|
70
|
+
readonly confirmTitle: string;
|
|
71
|
+
readonly confirmLead: string;
|
|
72
|
+
readonly confirmButton: string;
|
|
73
|
+
readonly revokedTitle: string;
|
|
74
|
+
readonly revokedBody: string;
|
|
75
|
+
readonly alreadyRevokedTitle: string;
|
|
76
|
+
readonly alreadyRevokedBody: string;
|
|
77
|
+
readonly tooLateTitle: string;
|
|
78
|
+
readonly tooLateBody: string;
|
|
79
|
+
readonly invalidTitle: string;
|
|
80
|
+
readonly invalidBody: string;
|
|
64
81
|
};
|
|
65
82
|
|
|
66
83
|
export const OPERATOR_NOTICE_REASONS = [
|
|
@@ -74,6 +91,9 @@ export const OPERATOR_NOTICE_REASONS = [
|
|
|
74
91
|
"recording_failed",
|
|
75
92
|
"withdrawal_period_expired",
|
|
76
93
|
"receipt_rate_limited",
|
|
94
|
+
"revoked",
|
|
95
|
+
"revocation_resume_manual",
|
|
96
|
+
"revocation_resume_failed",
|
|
77
97
|
] as const;
|
|
78
98
|
export type OperatorNoticeReason = (typeof OPERATOR_NOTICE_REASONS)[number];
|
|
79
99
|
|
|
@@ -103,6 +123,9 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
|
|
|
103
123
|
"Wir prüfen deine Erklärung und melden uns, sobald sie bearbeitet ist oder wir Rückfragen haben.",
|
|
104
124
|
receiptRevoke: (operatorEmail) =>
|
|
105
125
|
`Hast du diese Erklärung nicht abgegeben oder möchtest du sie zurücknehmen? Schreib uns bitte umgehend an ${operatorEmail}.`,
|
|
126
|
+
receiptNotMe:
|
|
127
|
+
"Wurde diese Erklärung nicht von dir abgegeben? Dann nimm die Kündigung bis zu ihrem Wirksamwerden hier zurück:",
|
|
128
|
+
receiptNotMeLink: "Diese Kündigung war ich nicht",
|
|
106
129
|
contact: "Fragen richtest du bitte an",
|
|
107
130
|
operatorSubject: {
|
|
108
131
|
termination: "Kündigung: Bearbeitung nötig",
|
|
@@ -122,6 +145,12 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
|
|
|
122
145
|
"Widerruf nach Ablauf der Widerrufsfrist: zum Periodenende gekündigt, Anspruch prüfen",
|
|
123
146
|
receipt_rate_limited:
|
|
124
147
|
"Keine Eingangsbestätigung versendet: Limit pro Empfängeradresse erreicht",
|
|
148
|
+
revoked:
|
|
149
|
+
"Die Erklärung wurde vom Postfachinhaber über „Diese Kündigung war ich nicht“ zurückgenommen",
|
|
150
|
+
revocation_resume_manual:
|
|
151
|
+
"Die Kündigung beim Zahlungsanbieter bitte manuell zurücknehmen (keine Schnittstelle dafür)",
|
|
152
|
+
revocation_resume_failed:
|
|
153
|
+
"Die Kündigung beim Zahlungsanbieter konnte nicht zurückgenommen werden, bitte manuell zurücknehmen",
|
|
125
154
|
},
|
|
126
155
|
noticeReasons: "Grund der Benachrichtigung",
|
|
127
156
|
channel: "Kanal",
|
|
@@ -163,6 +192,22 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
|
|
|
163
192
|
rateLimitedTitle: "Zu viele Anfragen",
|
|
164
193
|
rateLimitedBody:
|
|
165
194
|
"Von deiner Adresse kamen zu viele Anfragen. Bitte versuch es später erneut.",
|
|
195
|
+
revoke: {
|
|
196
|
+
confirmTitle: "Kündigung zurücknehmen?",
|
|
197
|
+
confirmLead:
|
|
198
|
+
"Mit dem Klick auf den Button unten nimmst du die Kündigung zurück. Dein Vertrag läuft dann weiter.",
|
|
199
|
+
confirmButton: "Diese Kündigung war ich nicht",
|
|
200
|
+
revokedTitle: "Die Kündigung ist zurückgenommen",
|
|
201
|
+
revokedBody: "Dein Vertrag läuft weiter. Wir haben den Betreiber informiert.",
|
|
202
|
+
alreadyRevokedTitle: "Schon zurückgenommen",
|
|
203
|
+
alreadyRevokedBody: "Diese Kündigung wurde bereits zurückgenommen.",
|
|
204
|
+
tooLateTitle: "Zu spät für die Rücknahme",
|
|
205
|
+
tooLateBody:
|
|
206
|
+
"Die Kündigung ist bereits wirksam geworden. Bitte schreib dem Betreiber, die Adresse steht in der Eingangsbestätigung.",
|
|
207
|
+
invalidTitle: "Link ungültig",
|
|
208
|
+
invalidBody:
|
|
209
|
+
"Dieser Link ist ungültig oder abgelaufen. Bitte schreib dem Betreiber, die Adresse steht in der Eingangsbestätigung.",
|
|
210
|
+
},
|
|
166
211
|
},
|
|
167
212
|
},
|
|
168
213
|
en: {
|
|
@@ -190,6 +235,9 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
|
|
|
190
235
|
"We will review your declaration and get back to you once it is processed or if we have questions.",
|
|
191
236
|
receiptRevoke: (operatorEmail) =>
|
|
192
237
|
`If you did not make this declaration or want to take it back, please write to ${operatorEmail} right away.`,
|
|
238
|
+
receiptNotMe:
|
|
239
|
+
"Did you not make this declaration? Then take the termination back here until it takes effect:",
|
|
240
|
+
receiptNotMeLink: "I did not request this cancellation",
|
|
193
241
|
contact: "Questions go to",
|
|
194
242
|
operatorSubject: {
|
|
195
243
|
termination: "Termination: action needed",
|
|
@@ -208,6 +256,12 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
|
|
|
208
256
|
withdrawal_period_expired:
|
|
209
257
|
"Withdrawal after the withdrawal period: cancelled at period end, review the claim",
|
|
210
258
|
receipt_rate_limited: "No receipt mail sent: the per-recipient limit was reached",
|
|
259
|
+
revoked:
|
|
260
|
+
'The declaration was taken back by the mailbox owner ("I did not request this cancellation")',
|
|
261
|
+
revocation_resume_manual:
|
|
262
|
+
"Undo the cancellation at the payment provider manually (no API for it)",
|
|
263
|
+
revocation_resume_failed:
|
|
264
|
+
"Undoing the cancellation at the payment provider failed, undo it manually",
|
|
211
265
|
},
|
|
212
266
|
noticeReasons: "Reason for this notice",
|
|
213
267
|
channel: "Channel",
|
|
@@ -248,6 +302,22 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
|
|
|
248
302
|
errorGeneric: "Your declaration could not be processed. Please try again.",
|
|
249
303
|
rateLimitedTitle: "Too many requests",
|
|
250
304
|
rateLimitedBody: "Too many requests came from your address. Please try again later.",
|
|
305
|
+
revoke: {
|
|
306
|
+
confirmTitle: "Take the cancellation back?",
|
|
307
|
+
confirmLead:
|
|
308
|
+
"Click the button below to take the cancellation back. Your contract then continues.",
|
|
309
|
+
confirmButton: "I did not request this cancellation",
|
|
310
|
+
revokedTitle: "The cancellation was taken back",
|
|
311
|
+
revokedBody: "Your contract continues. We informed the operator.",
|
|
312
|
+
alreadyRevokedTitle: "Already taken back",
|
|
313
|
+
alreadyRevokedBody: "This cancellation was already taken back.",
|
|
314
|
+
tooLateTitle: "Too late to take it back",
|
|
315
|
+
tooLateBody:
|
|
316
|
+
"The cancellation has already taken effect. Please write to the operator, the address is in the receipt.",
|
|
317
|
+
invalidTitle: "Invalid link",
|
|
318
|
+
invalidBody:
|
|
319
|
+
"This link is invalid or has expired. Please write to the operator, the address is in the receipt.",
|
|
320
|
+
},
|
|
251
321
|
},
|
|
252
322
|
},
|
|
253
323
|
};
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// type ohne payload-discriminator.
|
|
12
12
|
|
|
13
13
|
import * as z from "zod";
|
|
14
|
-
import { BILLING_FOUNDATION_FEATURE, SubscriptionStatuses } from "./constants.ts";
|
|
15
|
-
import { CONSENT_LOCALES } from "./consumer-protection/consent-locale.ts";
|
|
14
|
+
import { BILLING_FOUNDATION_FEATURE, SubscriptionStatuses } from "./web/constants.ts";
|
|
15
|
+
import { CONSENT_LOCALES } from "./web/consumer-protection/consent-locale.ts";
|
|
16
16
|
|
|
17
17
|
// Aggregate-type für den event-store. Eine subscription pro Tenant ist
|
|
18
18
|
// ein stream; der subscriptionAggregateId-helper liefert die stream-id.
|
|
@@ -67,6 +67,8 @@ export const subscriptionEventPayloadSchema = z.object({
|
|
|
67
67
|
// doesn't report cancelAt at all — projection.ts tells the two apart.
|
|
68
68
|
cancelAtIso: z.string().min(1).nullable().optional(),
|
|
69
69
|
consentId: z.string().min(1).max(100).optional(),
|
|
70
|
+
// When the provider emitted the event; lets process-event drop late deliveries.
|
|
71
|
+
providerEventAtIso: z.iso.datetime().optional(),
|
|
70
72
|
});
|
|
71
73
|
export type SubscriptionEventPayload = z.infer<typeof subscriptionEventPayloadSchema>;
|
|
72
74
|
|
|
@@ -211,11 +213,27 @@ export const contractTerminationDeclaredPayloadSchema = z.object({
|
|
|
211
213
|
terminationKind: z.enum(CONTRACT_TERMINATION_KINDS),
|
|
212
214
|
receivedAtIso: z.string().min(1),
|
|
213
215
|
locale: z.enum(CONSENT_LOCALES),
|
|
216
|
+
// sha256 hex of the "this was not me" link token; events written before the
|
|
217
|
+
// link existed carry none and cannot be revoked.
|
|
218
|
+
revocationTokenHash: z.string().length(64).optional(),
|
|
214
219
|
});
|
|
215
220
|
export type ContractTerminationDeclaredPayload = z.infer<
|
|
216
221
|
typeof contractTerminationDeclaredPayloadSchema
|
|
217
222
|
>;
|
|
218
223
|
|
|
224
|
+
// The mailbox owner took a public declaration back ("this was not me").
|
|
225
|
+
export const CONTRACT_TERMINATION_REVOKED_EVENT_SHORT = "contract-termination-revoked" as const;
|
|
226
|
+
export const CONTRACT_TERMINATION_REVOKED_EVENT_QN =
|
|
227
|
+
`${BILLING_FOUNDATION_FEATURE}:event:${CONTRACT_TERMINATION_REVOKED_EVENT_SHORT}` as const;
|
|
228
|
+
|
|
229
|
+
export const contractTerminationRevokedPayloadSchema = z.object({
|
|
230
|
+
requestId: z.string().min(1).max(100),
|
|
231
|
+
revokedAtIso: z.string().min(1),
|
|
232
|
+
});
|
|
233
|
+
export type ContractTerminationRevokedPayload = z.infer<
|
|
234
|
+
typeof contractTerminationRevokedPayloadSchema
|
|
235
|
+
>;
|
|
236
|
+
|
|
219
237
|
export const CONTRACT_TERMINATION_UNMATCHED_AGGREGATE_TYPE =
|
|
220
238
|
"contract-termination-unmatched" as const;
|
|
221
239
|
export const CONTRACT_TERMINATION_UNMATCHED_EVENT_SHORT = "contract-termination-unmatched" as const;
|
|
@@ -56,11 +56,6 @@ import {
|
|
|
56
56
|
type FeatureDefinition,
|
|
57
57
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
58
58
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
59
|
-
import {
|
|
60
|
-
BILLING_FOUNDATION_FEATURE,
|
|
61
|
-
SUBSCRIPTION_PROVIDER_EXTENSION,
|
|
62
|
-
SubscriptionFoundationHandlers,
|
|
63
|
-
} from "./constants.ts";
|
|
64
59
|
import { registerConsumerProtection } from "./consumer-protection/register-consent.ts";
|
|
65
60
|
import { paymentEntity, subscriptionEntity } from "./entities.ts";
|
|
66
61
|
import {
|
|
@@ -110,6 +105,11 @@ import {
|
|
|
110
105
|
} from "./tenant-destroy-hook.ts";
|
|
111
106
|
import type { BillingFoundationOptions, ResolvedBillingFoundationOptions } from "./types.ts";
|
|
112
107
|
import { validateOptions } from "./validate-options.ts";
|
|
108
|
+
import {
|
|
109
|
+
BILLING_FOUNDATION_FEATURE,
|
|
110
|
+
SUBSCRIPTION_PROVIDER_EXTENSION,
|
|
111
|
+
SubscriptionFoundationHandlers,
|
|
112
|
+
} from "./web/constants.ts";
|
|
113
113
|
|
|
114
114
|
export function createBillingFoundationFeature<TTier extends string = string>(
|
|
115
115
|
options: BillingFoundationOptions<TTier> = {},
|
|
@@ -17,14 +17,10 @@ import {
|
|
|
17
17
|
encryptPiiFieldValues,
|
|
18
18
|
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
19
19
|
import type { HandlerContext, WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
20
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
21
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
20
22
|
import * as z from "zod";
|
|
21
23
|
import { subscriptionAggregateId } from "../aggregate-id.ts";
|
|
22
|
-
import {
|
|
23
|
-
isSubscriptionBlockingCheckout,
|
|
24
|
-
isSwitchableSubscriptionStatus,
|
|
25
|
-
SubscriptionEventTypes,
|
|
26
|
-
SubscriptionStatuses,
|
|
27
|
-
} from "../constants.ts";
|
|
28
24
|
import { SUBSCRIPTION_PII_FIELDS, subscriptionEntity } from "../entities.ts";
|
|
29
25
|
import {
|
|
30
26
|
INVOICE_PAID_EVENT_QN,
|
|
@@ -37,6 +33,13 @@ import {
|
|
|
37
33
|
type SubscriptionEventPayload,
|
|
38
34
|
} from "../events.ts";
|
|
39
35
|
import { getSubscriptionForTenant, type SubscriptionView } from "../get-subscription-for-tenant.ts";
|
|
36
|
+
import {
|
|
37
|
+
BILLING_SUBSCRIPTION_LOCK_NAMESPACE,
|
|
38
|
+
isSubscriptionBlockingCheckout,
|
|
39
|
+
isSwitchableSubscriptionStatus,
|
|
40
|
+
SubscriptionEventTypes,
|
|
41
|
+
SubscriptionStatuses,
|
|
42
|
+
} from "../web/constants.ts";
|
|
40
43
|
|
|
41
44
|
// =============================================================================
|
|
42
45
|
// Input-Schema = der normalisierte SubscriptionEvent (ohne tenantId, der
|
|
@@ -70,6 +73,7 @@ export const processEventSchema = z.object({
|
|
|
70
73
|
tier: z.string().min(1).max(50),
|
|
71
74
|
currentPeriodEndIso: z.string().min(1),
|
|
72
75
|
cancelAtIso: z.string().min(1).nullable().optional(),
|
|
76
|
+
providerEventAtIso: z.iso.datetime().optional(),
|
|
73
77
|
consentId: z.string().min(1).max(100).optional(),
|
|
74
78
|
});
|
|
75
79
|
export type ProcessEventPayload = z.infer<typeof processEventSchema>;
|
|
@@ -83,6 +87,37 @@ const NORMALIZED_TO_ES_EVENT: Readonly<Record<string, string>> = {
|
|
|
83
87
|
[SubscriptionEventTypes.invoicePaymentFailed]: INVOICE_PAYMENT_FAILED_EVENT_QN,
|
|
84
88
|
} satisfies Readonly<Record<string, string>>;
|
|
85
89
|
|
|
90
|
+
const STATE_EVENT_TYPES: ReadonlySet<string> = new Set([
|
|
91
|
+
SubscriptionEventTypes.created,
|
|
92
|
+
SubscriptionEventTypes.updated,
|
|
93
|
+
SubscriptionEventTypes.canceled,
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
function isLateStateEvent(
|
|
97
|
+
storedEvents: readonly {
|
|
98
|
+
readonly payload: unknown;
|
|
99
|
+
readonly metadata: { readonly headers?: Record<string, unknown> };
|
|
100
|
+
}[],
|
|
101
|
+
payload: ProcessEventPayload,
|
|
102
|
+
): boolean {
|
|
103
|
+
if (payload.providerEventAtIso === undefined || !STATE_EVENT_TYPES.has(payload.type)) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
const incoming = Temporal.Instant.from(payload.providerEventAtIso);
|
|
107
|
+
return storedEvents.some((stored) => {
|
|
108
|
+
const storedAt = propertyOf(stored.payload, "providerEventAtIso");
|
|
109
|
+
if (stored.metadata.headers?.["providerName"] !== payload.providerName) return false;
|
|
110
|
+
if (typeof storedAt !== "string") return false;
|
|
111
|
+
// A stored value that does not parse can never prove a newer event; skip it instead of
|
|
112
|
+
// failing every later delivery of this tenant's stream.
|
|
113
|
+
try {
|
|
114
|
+
return Temporal.Instant.compare(Temporal.Instant.from(storedAt), incoming) > 0;
|
|
115
|
+
} catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
86
121
|
// =============================================================================
|
|
87
122
|
// appendSubscriptionEvent — the append-body, shared by processEventHandler
|
|
88
123
|
// (webhook path) and sync-subscription.write.ts (backfill path). Both call
|
|
@@ -90,12 +125,37 @@ const NORMALIZED_TO_ES_EVENT: Readonly<Record<string, string>> = {
|
|
|
90
125
|
// who constructs `payload` and what `providerEventId` they use.
|
|
91
126
|
// =============================================================================
|
|
92
127
|
|
|
128
|
+
// A provider timestamp far in the future would make every later, genuine event look late.
|
|
129
|
+
const PROVIDER_EVENT_CLOCK_SKEW_SECONDS = 300;
|
|
130
|
+
|
|
131
|
+
export function clampProviderEventAt(
|
|
132
|
+
payload: ProcessEventPayload,
|
|
133
|
+
now: Temporal.Instant,
|
|
134
|
+
): ProcessEventPayload {
|
|
135
|
+
if (payload.providerEventAtIso === undefined) return payload;
|
|
136
|
+
const ceiling = now.add({ seconds: PROVIDER_EVENT_CLOCK_SKEW_SECONDS });
|
|
137
|
+
return Temporal.Instant.compare(Temporal.Instant.from(payload.providerEventAtIso), ceiling) > 0
|
|
138
|
+
? { ...payload, providerEventAtIso: ceiling.toString() }
|
|
139
|
+
: payload;
|
|
140
|
+
}
|
|
141
|
+
|
|
93
142
|
export async function appendSubscriptionEvent(
|
|
94
143
|
ctx: HandlerContext,
|
|
95
144
|
tenantId: string,
|
|
96
|
-
|
|
97
|
-
|
|
145
|
+
rawPayload: ProcessEventPayload,
|
|
146
|
+
now: Temporal.Instant = Temporal.Now.instant(),
|
|
147
|
+
): Promise<{
|
|
148
|
+
readonly duplicate: boolean;
|
|
149
|
+
readonly stale?: true;
|
|
150
|
+
readonly subscriptionAggregateId: string;
|
|
151
|
+
}> {
|
|
98
152
|
const aggId = subscriptionAggregateId(tenantId);
|
|
153
|
+
const payload = clampProviderEventAt(rawPayload, now);
|
|
154
|
+
|
|
155
|
+
// Serialises the read (idempotency + late check) with the append: the append re-reads the
|
|
156
|
+
// stream version itself, so without the lock a concurrent delivery could slip in between and
|
|
157
|
+
// the late check would have judged an outdated stream.
|
|
158
|
+
await ctx.db.advisoryXactLock(BILLING_SUBSCRIPTION_LOCK_NAMESPACE, aggId);
|
|
99
159
|
|
|
100
160
|
// ---------------------------------------------------------------
|
|
101
161
|
// 1. Idempotency: load the subscription stream and check whether this
|
|
@@ -120,6 +180,16 @@ export async function appendSubscriptionEvent(
|
|
|
120
180
|
return { duplicate: true, subscriptionAggregateId: aggId };
|
|
121
181
|
}
|
|
122
182
|
|
|
183
|
+
// A state event the provider emitted before the newest one already stored is a
|
|
184
|
+
// late delivery: appending it would roll the row back (a canceled subscription
|
|
185
|
+
// would come back active). Invoice events carry the live state, not a snapshot.
|
|
186
|
+
if (isLateStateEvent(existingEvents, payload)) {
|
|
187
|
+
ctx.log?.warn(
|
|
188
|
+
`[billing-foundation:process-event] dropped late ${payload.type} ${payload.providerEventId}: a newer provider event is already stored`,
|
|
189
|
+
);
|
|
190
|
+
return { duplicate: false, stale: true, subscriptionAggregateId: aggId };
|
|
191
|
+
}
|
|
192
|
+
|
|
123
193
|
// ---------------------------------------------------------------
|
|
124
194
|
// 2. Map normalized event-type → ES event-FQN.
|
|
125
195
|
// ---------------------------------------------------------------
|
|
@@ -175,6 +245,9 @@ export async function appendSubscriptionEvent(
|
|
|
175
245
|
currentPeriodEndIso: payload.currentPeriodEndIso,
|
|
176
246
|
...(payload.cancelAtIso !== undefined && { cancelAtIso: payload.cancelAtIso }),
|
|
177
247
|
...(payload.consentId !== undefined && { consentId: payload.consentId }),
|
|
248
|
+
...(payload.providerEventAtIso !== undefined && {
|
|
249
|
+
providerEventAtIso: payload.providerEventAtIso,
|
|
250
|
+
}),
|
|
178
251
|
};
|
|
179
252
|
const headers: SubscriptionEventHeaders = {
|
|
180
253
|
providerEventId: payload.providerEventId,
|
|
@@ -243,7 +316,7 @@ export function createProcessEventHandler(now: () => Temporal.Instant): WriteHan
|
|
|
243
316
|
},
|
|
244
317
|
};
|
|
245
318
|
}
|
|
246
|
-
const result = await appendSubscriptionEvent(ctx, event.user.tenantId, payload);
|
|
319
|
+
const result = await appendSubscriptionEvent(ctx, event.user.tenantId, payload, now());
|
|
247
320
|
return { isSuccess: true as const, data: result };
|
|
248
321
|
},
|
|
249
322
|
};
|
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
joinBaseUrl,
|
|
14
14
|
resolveCatalogProvider,
|
|
15
15
|
} from "../checkout-core.ts";
|
|
16
|
-
import { isSwitchableSubscriptionStatus } from "../constants.ts";
|
|
17
16
|
import { getSubscriptionForTenant } from "../get-subscription-for-tenant.ts";
|
|
18
17
|
import { purchaseRolesOf, resolvePlanPrices } from "../plan-catalog.ts";
|
|
19
18
|
import type { BillingFoundationOptions, BillingPlanCatalog } from "../types.ts";
|
|
19
|
+
import { isSwitchableSubscriptionStatus } from "../web/constants.ts";
|
|
20
20
|
|
|
21
21
|
export function createSwitchPlanHandler(
|
|
22
22
|
options: BillingFoundationOptions,
|
package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts
CHANGED
|
@@ -18,8 +18,8 @@ import { SYSTEM_ROLE, type WriteHandlerDef } from "@cosmicdrift/kumiko-framework
|
|
|
18
18
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
19
19
|
import * as z from "zod";
|
|
20
20
|
import { findProviderPlugin } from "../checkout-core.ts";
|
|
21
|
-
import { isTerminalSubscriptionStatus, SubscriptionEventTypes } from "../constants.ts";
|
|
22
21
|
import { getSubscriptionForTenant } from "../get-subscription-for-tenant.ts";
|
|
22
|
+
import { isTerminalSubscriptionStatus, SubscriptionEventTypes } from "../web/constants.ts";
|
|
23
23
|
import { appendSubscriptionEvent } from "./process-event.write.ts";
|
|
24
24
|
|
|
25
25
|
export const syncSubscriptionSchema = z.object({}).strict();
|
|
@@ -11,38 +11,11 @@ export {
|
|
|
11
11
|
type ResolvedProvider,
|
|
12
12
|
resolveProviderPlugin,
|
|
13
13
|
} from "./checkout-core.ts";
|
|
14
|
-
export {
|
|
15
|
-
BILLING_FOUNDATION_FEATURE,
|
|
16
|
-
BILLING_PLANS_PANEL_COMPONENT,
|
|
17
|
-
BILLING_PLANS_SCREEN_ID,
|
|
18
|
-
type BillingEventKind,
|
|
19
|
-
BillingEventKinds,
|
|
20
|
-
type BillingPlanAction,
|
|
21
|
-
BillingPlanActions,
|
|
22
|
-
DEFAULT_PURCHASE_ROLES,
|
|
23
|
-
isSwitchableSubscriptionStatus,
|
|
24
|
-
isTerminalSubscriptionStatus,
|
|
25
|
-
parseProviderConsentId,
|
|
26
|
-
SUBSCRIPTION_PROVIDER_EXTENSION,
|
|
27
|
-
type SubscriptionCancelTiming,
|
|
28
|
-
SubscriptionCancelTimings,
|
|
29
|
-
type SubscriptionEventType,
|
|
30
|
-
SubscriptionEventTypes,
|
|
31
|
-
SubscriptionFoundationHandlers,
|
|
32
|
-
SubscriptionFoundationQueries,
|
|
33
|
-
type SubscriptionStatus,
|
|
34
|
-
SubscriptionStatuses,
|
|
35
|
-
} from "./constants.ts";
|
|
36
14
|
export {
|
|
37
15
|
type ConsentPayload,
|
|
38
16
|
consentPayloadSchema,
|
|
39
17
|
type OrderItem,
|
|
40
18
|
} from "./consumer-protection/consent-gate.ts";
|
|
41
|
-
export {
|
|
42
|
-
CONSENT_LOCALES,
|
|
43
|
-
type ConsentLocale,
|
|
44
|
-
resolveConsentLocale,
|
|
45
|
-
} from "./consumer-protection/consent-locale.ts";
|
|
46
19
|
export {
|
|
47
20
|
type ContractTerminationRoutesOptions,
|
|
48
21
|
createContractTerminationRoutes,
|
|
@@ -99,6 +72,33 @@ export {
|
|
|
99
72
|
type SubscriptionEvent,
|
|
100
73
|
type SubscriptionProviderPlugin,
|
|
101
74
|
} from "./types.ts";
|
|
75
|
+
export {
|
|
76
|
+
BILLING_FOUNDATION_FEATURE,
|
|
77
|
+
BILLING_PLANS_PANEL_COMPONENT,
|
|
78
|
+
BILLING_PLANS_SCREEN_ID,
|
|
79
|
+
type BillingEventKind,
|
|
80
|
+
BillingEventKinds,
|
|
81
|
+
type BillingPlanAction,
|
|
82
|
+
BillingPlanActions,
|
|
83
|
+
DEFAULT_PURCHASE_ROLES,
|
|
84
|
+
isSwitchableSubscriptionStatus,
|
|
85
|
+
isTerminalSubscriptionStatus,
|
|
86
|
+
parseProviderConsentId,
|
|
87
|
+
SUBSCRIPTION_PROVIDER_EXTENSION,
|
|
88
|
+
type SubscriptionCancelTiming,
|
|
89
|
+
SubscriptionCancelTimings,
|
|
90
|
+
type SubscriptionEventType,
|
|
91
|
+
SubscriptionEventTypes,
|
|
92
|
+
SubscriptionFoundationHandlers,
|
|
93
|
+
SubscriptionFoundationQueries,
|
|
94
|
+
type SubscriptionStatus,
|
|
95
|
+
SubscriptionStatuses,
|
|
96
|
+
} from "./web/constants.ts";
|
|
97
|
+
export {
|
|
98
|
+
CONSENT_LOCALES,
|
|
99
|
+
type ConsentLocale,
|
|
100
|
+
resolveConsentLocale,
|
|
101
|
+
} from "./web/consumer-protection/consent-locale.ts";
|
|
102
102
|
export {
|
|
103
103
|
createSubscriptionWebhookRoute,
|
|
104
104
|
type SubscriptionWebhookRouteOptions,
|
|
@@ -5,13 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import { isPluginBillingEnabled } from "./checkout-core.ts";
|
|
8
|
-
import {
|
|
9
|
-
BillingPlanActions,
|
|
10
|
-
DEFAULT_PURCHASE_ROLES,
|
|
11
|
-
isSubscriptionBlockingCheckout,
|
|
12
|
-
isSwitchableSubscriptionStatus,
|
|
13
|
-
SubscriptionStatuses,
|
|
14
|
-
} from "./constants.ts";
|
|
15
8
|
import { getSubscriptionForTenant } from "./get-subscription-for-tenant.ts";
|
|
16
9
|
import type {
|
|
17
10
|
BillingPlanCatalog,
|
|
@@ -20,6 +13,13 @@ import type {
|
|
|
20
13
|
ProviderPrice,
|
|
21
14
|
SubscriptionProviderPlugin,
|
|
22
15
|
} from "./types.ts";
|
|
16
|
+
import {
|
|
17
|
+
BillingPlanActions,
|
|
18
|
+
DEFAULT_PURCHASE_ROLES,
|
|
19
|
+
isSubscriptionBlockingCheckout,
|
|
20
|
+
isSwitchableSubscriptionStatus,
|
|
21
|
+
SubscriptionStatuses,
|
|
22
|
+
} from "./web/constants.ts";
|
|
23
23
|
|
|
24
24
|
function userHasAnyRole(userRoles: readonly string[], allowedRoles: readonly string[]): boolean {
|
|
25
25
|
return allowedRoles.some((role) => userRoles.includes(role));
|