@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
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// Dodo Payments plugin methods for the post-tenant phase. Option shapes are
|
|
2
|
+
// derived from the plugin contract so a contract change fails at compile time.
|
|
3
|
+
// Every response is validated with Zod; every non-2xx throws a DodoHttpError.
|
|
4
|
+
import { KNOWN_RECURRING_INTERVALS, SubscriptionCancelTimings, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
|
|
5
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
6
|
+
import * as z from "zod";
|
|
7
|
+
import { DodoHttpError } from "./client.js";
|
|
8
|
+
import { dodoSubscriptionSchema, mapDodoSubscriptionState } from "./verify-webhook.js";
|
|
9
|
+
const NOT_FOUND = 404;
|
|
10
|
+
function subscriptionPath(providerSubscriptionId) {
|
|
11
|
+
return `/subscriptions/${encodeURIComponent(providerSubscriptionId)}`;
|
|
12
|
+
}
|
|
13
|
+
function forcedLanguage(locale) {
|
|
14
|
+
return (locale.split(/[-_]/)[0] ?? locale).toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Checkout + portal + cancel/resume
|
|
18
|
+
// =============================================================================
|
|
19
|
+
const checkoutResponseSchema = z.object({
|
|
20
|
+
session_id: z.string(),
|
|
21
|
+
checkout_url: z.string().nullable(),
|
|
22
|
+
});
|
|
23
|
+
export function createDodoCheckoutSession(runtime) {
|
|
24
|
+
return async (ctx, options) => {
|
|
25
|
+
// No Dodo session may be created while billing is not live.
|
|
26
|
+
await runtime.assertBillingLive(ctx);
|
|
27
|
+
const response = checkoutResponseSchema.parse(await runtime.call(ctx, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
path: "/checkouts",
|
|
30
|
+
body: {
|
|
31
|
+
product_cart: [{ product_id: options.priceId, quantity: 1 }],
|
|
32
|
+
...(options.providerCustomerId && {
|
|
33
|
+
customer: { customer_id: options.providerCustomerId },
|
|
34
|
+
}),
|
|
35
|
+
return_url: options.successUrl,
|
|
36
|
+
cancel_url: options.cancelUrl,
|
|
37
|
+
metadata: {
|
|
38
|
+
tenantId: options.tenantId,
|
|
39
|
+
...(options.consentId && { consentId: options.consentId }),
|
|
40
|
+
},
|
|
41
|
+
...(options.locale && {
|
|
42
|
+
customization: { force_language: forcedLanguage(options.locale) },
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
}));
|
|
46
|
+
if (!response.checkout_url) {
|
|
47
|
+
throw new Error("subscription-dodo: checkout session was created without a checkout_url");
|
|
48
|
+
}
|
|
49
|
+
return { url: response.checkout_url };
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const portalResponseSchema = z.object({ link: z.string() });
|
|
53
|
+
export function createDodoPortalSession(runtime) {
|
|
54
|
+
return async (ctx, options) => {
|
|
55
|
+
const response = portalResponseSchema.parse(await runtime.call(ctx, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
path: `/customers/${encodeURIComponent(options.providerCustomerId)}/customer-portal/session`,
|
|
58
|
+
query: { return_url: options.returnUrl },
|
|
59
|
+
}));
|
|
60
|
+
return { url: response.link };
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export function createDodoCancelSubscription(runtime) {
|
|
64
|
+
return async (ctx, options) => {
|
|
65
|
+
await runtime.call(ctx, {
|
|
66
|
+
method: "PATCH",
|
|
67
|
+
path: subscriptionPath(options.providerSubscriptionId),
|
|
68
|
+
body: options.when === SubscriptionCancelTimings.periodEnd
|
|
69
|
+
? { cancel_at_next_billing_date: true }
|
|
70
|
+
: { status: "cancelled" },
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function createDodoResumeSubscription(runtime) {
|
|
75
|
+
return async (ctx, options) => {
|
|
76
|
+
await runtime.call(ctx, {
|
|
77
|
+
method: "PATCH",
|
|
78
|
+
path: subscriptionPath(options.providerSubscriptionId),
|
|
79
|
+
body: { cancel_at_next_billing_date: false },
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// Prices
|
|
85
|
+
// =============================================================================
|
|
86
|
+
const productSchema = z.object({
|
|
87
|
+
product_id: z.string(),
|
|
88
|
+
price: z.discriminatedUnion("type", [
|
|
89
|
+
z.object({
|
|
90
|
+
type: z.literal("recurring_price"),
|
|
91
|
+
price: z.number(),
|
|
92
|
+
currency: z.string(),
|
|
93
|
+
payment_frequency_interval: z.string(),
|
|
94
|
+
payment_frequency_count: z.number(),
|
|
95
|
+
}),
|
|
96
|
+
z.object({
|
|
97
|
+
type: z.literal("one_time_price"),
|
|
98
|
+
price: z.number(),
|
|
99
|
+
currency: z.string(),
|
|
100
|
+
}),
|
|
101
|
+
z.object({ type: z.literal("usage_based_price") }),
|
|
102
|
+
]),
|
|
103
|
+
// nullish: an older product without metadata must not fail the whole price lookup.
|
|
104
|
+
metadata: z.record(z.string(), z.string()).nullish(),
|
|
105
|
+
});
|
|
106
|
+
function mapDodoProduct(product) {
|
|
107
|
+
const { price } = product;
|
|
108
|
+
if (price.type === "usage_based_price")
|
|
109
|
+
return null;
|
|
110
|
+
const base = {
|
|
111
|
+
priceId: product.product_id,
|
|
112
|
+
unitAmount: price.price,
|
|
113
|
+
currency: price.currency.toLowerCase(),
|
|
114
|
+
active: true,
|
|
115
|
+
metadata: product.metadata ?? {},
|
|
116
|
+
};
|
|
117
|
+
if (price.type === "one_time_price")
|
|
118
|
+
return { ...base, interval: null, intervalCount: 1 };
|
|
119
|
+
const rawInterval = price.payment_frequency_interval.toLowerCase();
|
|
120
|
+
const interval = KNOWN_RECURRING_INTERVALS.find((known) => known === rawInterval) ?? null;
|
|
121
|
+
return {
|
|
122
|
+
...base,
|
|
123
|
+
interval,
|
|
124
|
+
intervalCount: interval ? price.payment_frequency_count : null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Keyed by product id alone: ids are unique per account and a cache hit must not
|
|
128
|
+
// trigger an audited secret read on every billing-plans render. Failed lookups
|
|
129
|
+
// are never cached. `null` is a cached "skipped" product (usage-based).
|
|
130
|
+
export function createDodoPriceCache({ ttlMs = 600_000, now = () => Temporal.Now.instant().epochMilliseconds, } = {}) {
|
|
131
|
+
const store = new Map();
|
|
132
|
+
return {
|
|
133
|
+
get: (priceId) => {
|
|
134
|
+
const entry = store.get(priceId);
|
|
135
|
+
if (!entry)
|
|
136
|
+
return undefined;
|
|
137
|
+
if (entry.expiresAt <= now()) {
|
|
138
|
+
store.delete(priceId);
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
return entry.price;
|
|
142
|
+
},
|
|
143
|
+
set: (priceId, price) => {
|
|
144
|
+
store.set(priceId, { price, expiresAt: now() + ttlMs });
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
async function fillPriceCache(runtime, cache, ctx, priceIds) {
|
|
149
|
+
const misses = priceIds.filter((priceId) => cache.get(priceId) === undefined);
|
|
150
|
+
const settled = await Promise.allSettled(misses.map(async (priceId) => ({
|
|
151
|
+
priceId,
|
|
152
|
+
product: productSchema.parse(await runtime.call(ctx, {
|
|
153
|
+
method: "GET",
|
|
154
|
+
path: `/products/${encodeURIComponent(priceId)}`,
|
|
155
|
+
})),
|
|
156
|
+
})));
|
|
157
|
+
for (const outcome of settled) {
|
|
158
|
+
if (outcome.status === "fulfilled") {
|
|
159
|
+
cache.set(outcome.value.priceId, mapDodoProduct(outcome.value.product));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const message = outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
|
|
163
|
+
ctx.log?.warn("subscription-dodo: product lookup failed", { message });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function createDodoRetrievePrices(runtime, cache) {
|
|
168
|
+
return async (ctx, priceIds) => {
|
|
169
|
+
await fillPriceCache(runtime, cache, ctx, priceIds);
|
|
170
|
+
return priceIds.flatMap((priceId) => cache.get(priceId) ?? []);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// =============================================================================
|
|
174
|
+
// Plan switch (in-app: preview + apply)
|
|
175
|
+
// =============================================================================
|
|
176
|
+
const previewResponseSchema = z.object({
|
|
177
|
+
immediate_charge: z.object({
|
|
178
|
+
summary: z.object({
|
|
179
|
+
total_amount: z.number(),
|
|
180
|
+
tax: z.number().nullable(),
|
|
181
|
+
currency: z.string(),
|
|
182
|
+
customer_credits: z.number(),
|
|
183
|
+
}),
|
|
184
|
+
effective_at: z.string(),
|
|
185
|
+
}),
|
|
186
|
+
});
|
|
187
|
+
function changePlanBody(targetPriceId) {
|
|
188
|
+
return {
|
|
189
|
+
product_id: targetPriceId,
|
|
190
|
+
quantity: 1,
|
|
191
|
+
proration_billing_mode: "prorated_immediately",
|
|
192
|
+
effective_at: "immediately",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
export function createDodoPreviewPlanSwitch(runtime) {
|
|
196
|
+
return async (ctx, options) => {
|
|
197
|
+
const { immediate_charge: charge } = previewResponseSchema.parse(await runtime.call(ctx, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
path: `${subscriptionPath(options.providerSubscriptionId)}/change-plan/preview`,
|
|
200
|
+
body: changePlanBody(options.targetPriceId),
|
|
201
|
+
}));
|
|
202
|
+
return {
|
|
203
|
+
amountDueNow: charge.summary.total_amount,
|
|
204
|
+
tax: charge.summary.tax,
|
|
205
|
+
currency: charge.summary.currency.toLowerCase(),
|
|
206
|
+
credit: charge.summary.customer_credits,
|
|
207
|
+
effectiveAt: charge.effective_at,
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const applyResponseSchema = z.looseObject({
|
|
212
|
+
payment_id: z.string().nullish(),
|
|
213
|
+
payment_link: z.string().nullish(),
|
|
214
|
+
});
|
|
215
|
+
// The switch itself arrives later as a subscription.plan_changed webhook;
|
|
216
|
+
// prevent_change keeps the old plan if the immediate charge fails.
|
|
217
|
+
export function createDodoApplyPlanSwitch(runtime) {
|
|
218
|
+
return async (ctx, options) => {
|
|
219
|
+
applyResponseSchema.parse(await runtime.call(ctx, {
|
|
220
|
+
method: "POST",
|
|
221
|
+
path: `${subscriptionPath(options.providerSubscriptionId)}/change-plan`,
|
|
222
|
+
body: {
|
|
223
|
+
...changePlanBody(options.targetPriceId),
|
|
224
|
+
on_payment_failure: "prevent_change",
|
|
225
|
+
metadata: {
|
|
226
|
+
tenantId: options.tenantId,
|
|
227
|
+
...(options.consentId && { consentId: options.consentId }),
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
}));
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// =============================================================================
|
|
234
|
+
// retrieveSubscription
|
|
235
|
+
// =============================================================================
|
|
236
|
+
export function createDodoRetrieveSubscription(runtime, options) {
|
|
237
|
+
return async (ctx, providerSubscriptionId) => {
|
|
238
|
+
let raw;
|
|
239
|
+
try {
|
|
240
|
+
raw = await runtime.call(ctx, {
|
|
241
|
+
method: "GET",
|
|
242
|
+
path: subscriptionPath(providerSubscriptionId),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (error instanceof DodoHttpError && error.status === NOT_FOUND)
|
|
247
|
+
return null;
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
const sub = dodoSubscriptionSchema.parse(raw);
|
|
251
|
+
const state = mapDodoSubscriptionState(sub, options.priceToTier);
|
|
252
|
+
if (!state)
|
|
253
|
+
return null;
|
|
254
|
+
return {
|
|
255
|
+
providerCustomerId: sub.customer.customer_id,
|
|
256
|
+
providerSubscriptionId: sub.subscription_id,
|
|
257
|
+
...state,
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ConfigKeyHandle, type HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
|
|
3
|
+
import { type DodoRequest, type DodoSend } from "./client.ts";
|
|
4
|
+
export type DodoRuntimeDeps = {
|
|
5
|
+
readonly apiKeyHandle: ConfigKeyHandle<"text">;
|
|
6
|
+
readonly webhookSecretHandle: ConfigKeyHandle<"text">;
|
|
7
|
+
readonly billingLiveHandle: ConfigKeyHandle<"boolean">;
|
|
8
|
+
readonly environmentHandle: ConfigKeyHandle<"select">;
|
|
9
|
+
readonly send: DodoSend;
|
|
10
|
+
readonly fallback: {
|
|
11
|
+
readonly apiKey?: string;
|
|
12
|
+
readonly webhookSecret?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export type DodoCtxRuntime = {
|
|
16
|
+
readonly call: (ctx: HandlerContext, request: DodoRequest) => Promise<unknown>;
|
|
17
|
+
readonly assertBillingLive: (ctx: HandlerContext, handlerName?: string) => Promise<void>;
|
|
18
|
+
readonly isBillingEnabled: (ctx: HandlerContext) => Promise<boolean>;
|
|
19
|
+
};
|
|
20
|
+
export type DodoWebhookRuntime = {
|
|
21
|
+
readonly resolveWebhookSecret: (systemSecrets?: SecretsContext) => Promise<string>;
|
|
22
|
+
};
|
|
23
|
+
export type DodoRuntimes = {
|
|
24
|
+
readonly ctx: DodoCtxRuntime;
|
|
25
|
+
readonly webhook: DodoWebhookRuntime;
|
|
26
|
+
};
|
|
27
|
+
export declare function createDodoRuntimes(deps: DodoRuntimeDeps): DodoRuntimes;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Call-time key/flag resolution for the subscription-dodo plugin, mirroring
|
|
2
|
+
// subscription-stripe/runtime.ts: credentials come from backing:"secrets"
|
|
3
|
+
// config keys under SYSTEM_TENANT_ID, so rotation and go-live need no redeploy.
|
|
4
|
+
// The factory options survive as env-bridge fallbacks.
|
|
5
|
+
//
|
|
6
|
+
// Two read paths: post-tenant handlers read the key audited through the
|
|
7
|
+
// HandlerContext; the pre-tenant webhook reads the secret un-audited from the
|
|
8
|
+
// system SecretsContext the foundation passes in.
|
|
9
|
+
import { requireSecretsContext } from "@cosmicdrift/kumiko-bundled-features/secrets";
|
|
10
|
+
import { SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
11
|
+
import { FeatureDisabledError, UnconfiguredError } from "@cosmicdrift/kumiko-framework/errors";
|
|
12
|
+
import { parseJsonSafe } from "@cosmicdrift/kumiko-framework/utils";
|
|
13
|
+
import { createDodoHttp } from "./client.js";
|
|
14
|
+
import { DODO_BASE_URLS, DodoEnvironments, SUBSCRIPTION_DODO_FEATURE } from "./constants.js";
|
|
15
|
+
const API_KEY_HINT = "Set the Dodo Payments API key via config:write:set on `subscription-dodo:config:api-key` (or seed it from DODO_PAYMENTS_API_KEY during the env bridge).";
|
|
16
|
+
// backing:"secrets" values are stored JSON-serialized, so the store read has to
|
|
17
|
+
// be parsed back; the env fallback is a plain string and stays raw. The error
|
|
18
|
+
// must not carry the input: the JSON parser's own message would repeat the
|
|
19
|
+
// credential into logs and, for webhooks, into the 401 body.
|
|
20
|
+
function parseStoredSecret(raw) {
|
|
21
|
+
if (raw === undefined || raw.length === 0)
|
|
22
|
+
return undefined;
|
|
23
|
+
const parsed = parseJsonSafe(raw, undefined);
|
|
24
|
+
if (parsed === undefined) {
|
|
25
|
+
throw new Error("subscription-dodo credential (backing:secrets) is not valid JSON");
|
|
26
|
+
}
|
|
27
|
+
return typeof parsed === "string" && parsed.length > 0 ? parsed : undefined;
|
|
28
|
+
}
|
|
29
|
+
export function createDodoRuntimes(deps) {
|
|
30
|
+
const http = createDodoHttp(deps.send);
|
|
31
|
+
async function ctxApiKey(ctx) {
|
|
32
|
+
let key;
|
|
33
|
+
if (ctx.secrets) {
|
|
34
|
+
const got = await requireSecretsContext(ctx, SUBSCRIPTION_DODO_FEATURE).get(SYSTEM_TENANT_ID, deps.apiKeyHandle.name);
|
|
35
|
+
key = parseStoredSecret(got?.reveal());
|
|
36
|
+
}
|
|
37
|
+
if (!key && deps.fallback.apiKey && deps.fallback.apiKey.length > 0) {
|
|
38
|
+
key = deps.fallback.apiKey;
|
|
39
|
+
}
|
|
40
|
+
if (!key) {
|
|
41
|
+
throw new UnconfiguredError({
|
|
42
|
+
feature: SUBSCRIPTION_DODO_FEATURE,
|
|
43
|
+
key: deps.apiKeyHandle.name,
|
|
44
|
+
hint: API_KEY_HINT,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return key;
|
|
48
|
+
}
|
|
49
|
+
async function baseUrlFor(ctx) {
|
|
50
|
+
const environment = ctx.config ? await ctx.config(deps.environmentHandle) : undefined;
|
|
51
|
+
return DODO_BASE_URLS[environment === DodoEnvironments.live ? "live" : "test"];
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
ctx: {
|
|
55
|
+
call: async (ctx, request) => http({ baseUrl: await baseUrlFor(ctx), apiKey: await ctxApiKey(ctx) }, request),
|
|
56
|
+
assertBillingLive: async (ctx, handlerName = "create-checkout-session") => {
|
|
57
|
+
const live = ctx.config ? await ctx.config(deps.billingLiveHandle) : undefined;
|
|
58
|
+
if (live !== true)
|
|
59
|
+
throw new FeatureDisabledError(SUBSCRIPTION_DODO_FEATURE, handlerName);
|
|
60
|
+
},
|
|
61
|
+
isBillingEnabled: async (ctx) => {
|
|
62
|
+
const live = ctx.config ? await ctx.config(deps.billingLiveHandle) : undefined;
|
|
63
|
+
if (live !== true)
|
|
64
|
+
return false;
|
|
65
|
+
if (deps.fallback.apiKey !== undefined && deps.fallback.apiKey.length > 0)
|
|
66
|
+
return true;
|
|
67
|
+
// Existence-only probe on every billing-plans render: must stay un-audited.
|
|
68
|
+
return ctx.secrets
|
|
69
|
+
? await ctx.secrets.has(SYSTEM_TENANT_ID, deps.apiKeyHandle.name)
|
|
70
|
+
: false;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
webhook: {
|
|
74
|
+
resolveWebhookSecret: async (systemSecrets) => {
|
|
75
|
+
if (systemSecrets) {
|
|
76
|
+
// No auditCtx: every webhook would otherwise spam the audit trail.
|
|
77
|
+
const got = await systemSecrets.get(SYSTEM_TENANT_ID, deps.webhookSecretHandle.name);
|
|
78
|
+
const stored = parseStoredSecret(got?.reveal());
|
|
79
|
+
if (stored)
|
|
80
|
+
return stored;
|
|
81
|
+
}
|
|
82
|
+
const fallback = deps.fallback.webhookSecret;
|
|
83
|
+
if (fallback && fallback.length > 0)
|
|
84
|
+
return fallback;
|
|
85
|
+
throw new Error(`subscription-dodo: '${deps.webhookSecretHandle.name}' unresolved — no system-secret set and no factory fallback. Webhook cannot verify until it is configured.`);
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type PaymentEvent, type SubscriptionEvent, type SubscriptionStatus } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
|
|
2
|
+
import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
|
|
3
|
+
import * as z from "zod";
|
|
4
|
+
import type { DodoWebhookRuntime } from "./runtime.ts";
|
|
5
|
+
export declare const WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
6
|
+
export type StandardWebhookHeaders = Readonly<Record<string, string>>;
|
|
7
|
+
/** Throws unless the Standard Webhooks signature over `rawBody` is valid and
|
|
8
|
+
* the timestamp is within the tolerance in both directions. */
|
|
9
|
+
export declare function verifyStandardWebhook(rawBody: string, headers: StandardWebhookHeaders, secret: string, nowSeconds: number): void;
|
|
10
|
+
export declare const dodoSubscriptionSchema: z.ZodObject<{
|
|
11
|
+
subscription_id: z.ZodString;
|
|
12
|
+
product_id: z.ZodString;
|
|
13
|
+
status: z.ZodString;
|
|
14
|
+
customer: z.ZodObject<{
|
|
15
|
+
customer_id: z.ZodString;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
metadata: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
18
|
+
next_billing_date: z.ZodISODateTime;
|
|
19
|
+
cancel_at_next_billing_date: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
export type DodoSubscription = z.infer<typeof dodoSubscriptionSchema>;
|
|
22
|
+
export type MappedDodoSubscriptionState = {
|
|
23
|
+
readonly status: SubscriptionStatus;
|
|
24
|
+
readonly tier: string;
|
|
25
|
+
readonly currentPeriodEnd: string;
|
|
26
|
+
readonly cancelAt: string | null;
|
|
27
|
+
};
|
|
28
|
+
/** Shared by the webhook and retrieveSubscription so both map Dodo state
|
|
29
|
+
* identically. Null when the status is unknown or the product is not in
|
|
30
|
+
* `priceToTier` (SubscriptionEvent.tier is required). */
|
|
31
|
+
export declare function mapDodoSubscriptionState(sub: DodoSubscription, priceToTier: Readonly<Record<string, string>>): MappedDodoSubscriptionState | null;
|
|
32
|
+
export type DodoWebhookOptions = {
|
|
33
|
+
readonly priceToTier: Readonly<Record<string, string>>;
|
|
34
|
+
/** Test seam: Unix seconds. Defaults to the wall clock. */
|
|
35
|
+
readonly nowSeconds?: () => number;
|
|
36
|
+
};
|
|
37
|
+
export declare function verifyAndParseDodoWebhook(runtime: DodoWebhookRuntime, options: DodoWebhookOptions): (rawBody: string, headers: Record<string, string>, systemSecrets?: SecretsContext) => Promise<SubscriptionEvent | PaymentEvent | null>;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Dodo Payments webhook: Standard Webhooks signature verification plus mapping
|
|
2
|
+
// of Dodo's event envelope onto the foundation's normalized events.
|
|
3
|
+
//
|
|
4
|
+
// Verification always runs against the raw body string; re-serialized JSON
|
|
5
|
+
// would change whitespace and break the HMAC. The signature check happens
|
|
6
|
+
// before any parsing, so no path bypasses it.
|
|
7
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
8
|
+
import { BillingEventKinds, parseProviderConsentId, SubscriptionEventTypes, SubscriptionStatuses, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
|
|
9
|
+
import { parseJsonOrThrow } from "@cosmicdrift/kumiko-framework/utils";
|
|
10
|
+
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
11
|
+
import * as z from "zod";
|
|
12
|
+
import { DODO_PROVIDER_NAME } from "./constants.js";
|
|
13
|
+
export const WEBHOOK_TOLERANCE_SECONDS = 300;
|
|
14
|
+
const SECRET_PREFIX = "whsec_";
|
|
15
|
+
const SIGNATURE_VERSION = "v1";
|
|
16
|
+
const TIMESTAMP_PATTERN = /^\d+$/;
|
|
17
|
+
function signatureFor(secretKey, signedContent) {
|
|
18
|
+
return createHmac("sha256", secretKey).update(signedContent).digest();
|
|
19
|
+
}
|
|
20
|
+
function matchesAnyV1Signature(header, expected) {
|
|
21
|
+
return header.split(" ").some((entry) => {
|
|
22
|
+
const [version, encoded] = entry.split(",");
|
|
23
|
+
if (version !== SIGNATURE_VERSION || !encoded)
|
|
24
|
+
return false;
|
|
25
|
+
const candidate = Buffer.from(encoded, "base64");
|
|
26
|
+
return candidate.length === expected.length && timingSafeEqual(candidate, expected);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** Throws unless the Standard Webhooks signature over `rawBody` is valid and
|
|
30
|
+
* the timestamp is within the tolerance in both directions. */
|
|
31
|
+
export function verifyStandardWebhook(rawBody, headers, secret, nowSeconds) {
|
|
32
|
+
const id = headers["webhook-id"];
|
|
33
|
+
const timestamp = headers["webhook-timestamp"];
|
|
34
|
+
const signatureHeader = headers["webhook-signature"];
|
|
35
|
+
if (!id || !timestamp || !signatureHeader) {
|
|
36
|
+
throw new Error("subscription-dodo: webhook-id, webhook-timestamp or webhook-signature missing");
|
|
37
|
+
}
|
|
38
|
+
if (!TIMESTAMP_PATTERN.test(timestamp)) {
|
|
39
|
+
throw new Error("subscription-dodo: webhook-timestamp is not a Unix timestamp in seconds");
|
|
40
|
+
}
|
|
41
|
+
if (Math.abs(nowSeconds - Number(timestamp)) > WEBHOOK_TOLERANCE_SECONDS) {
|
|
42
|
+
throw new Error("subscription-dodo: webhook timestamp outside the tolerance window");
|
|
43
|
+
}
|
|
44
|
+
const secretKey = Buffer.from(secret.startsWith(SECRET_PREFIX) ? secret.slice(SECRET_PREFIX.length) : secret, "base64");
|
|
45
|
+
const expected = signatureFor(secretKey, `${id}.${timestamp}.${rawBody}`);
|
|
46
|
+
if (!matchesAnyV1Signature(signatureHeader, expected)) {
|
|
47
|
+
throw new Error("subscription-dodo: webhook signature verify failed");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Event mapping
|
|
52
|
+
// =============================================================================
|
|
53
|
+
const DODO_SUBSCRIPTION_EVENT_TYPES = {
|
|
54
|
+
"subscription.active": SubscriptionEventTypes.created,
|
|
55
|
+
"subscription.updated": SubscriptionEventTypes.updated,
|
|
56
|
+
"subscription.plan_changed": SubscriptionEventTypes.updated,
|
|
57
|
+
"subscription.paused": SubscriptionEventTypes.updated,
|
|
58
|
+
"subscription.unpaused": SubscriptionEventTypes.updated,
|
|
59
|
+
"subscription.update_payment_method": SubscriptionEventTypes.updated,
|
|
60
|
+
"subscription.renewed": SubscriptionEventTypes.invoicePaid,
|
|
61
|
+
"subscription.past_due": SubscriptionEventTypes.invoicePaymentFailed,
|
|
62
|
+
"subscription.on_hold": SubscriptionEventTypes.invoicePaymentFailed,
|
|
63
|
+
"subscription.cancelled": SubscriptionEventTypes.canceled,
|
|
64
|
+
"subscription.expired": SubscriptionEventTypes.canceled,
|
|
65
|
+
"subscription.failed": SubscriptionEventTypes.canceled,
|
|
66
|
+
};
|
|
67
|
+
const DODO_SUBSCRIPTION_STATUSES = {
|
|
68
|
+
pending: SubscriptionStatuses.incomplete,
|
|
69
|
+
active: SubscriptionStatuses.active,
|
|
70
|
+
on_hold: SubscriptionStatuses.pastDue,
|
|
71
|
+
past_due: SubscriptionStatuses.pastDue,
|
|
72
|
+
paused: SubscriptionStatuses.pastDue,
|
|
73
|
+
cancelled: SubscriptionStatuses.canceled,
|
|
74
|
+
failed: SubscriptionStatuses.canceled,
|
|
75
|
+
expired: SubscriptionStatuses.canceled,
|
|
76
|
+
};
|
|
77
|
+
const PAYMENT_SUCCEEDED = "payment.succeeded";
|
|
78
|
+
const metadataSchema = z.record(z.string(), z.unknown()).nullish();
|
|
79
|
+
const customerSchema = z.object({ customer_id: z.string().min(1) });
|
|
80
|
+
const envelopeSchema = z.object({
|
|
81
|
+
type: z.string(),
|
|
82
|
+
timestamp: z.iso.datetime({ offset: true }),
|
|
83
|
+
data: z.unknown(),
|
|
84
|
+
});
|
|
85
|
+
export const dodoSubscriptionSchema = z.object({
|
|
86
|
+
subscription_id: z.string().min(1),
|
|
87
|
+
product_id: z.string().min(1),
|
|
88
|
+
status: z.string(),
|
|
89
|
+
customer: customerSchema,
|
|
90
|
+
metadata: metadataSchema,
|
|
91
|
+
next_billing_date: z.iso.datetime({ offset: true }),
|
|
92
|
+
cancel_at_next_billing_date: z.boolean().nullish(),
|
|
93
|
+
});
|
|
94
|
+
const dodoPaymentSchema = z.object({
|
|
95
|
+
subscription_id: z.string().nullish(),
|
|
96
|
+
checkout_session_id: z.string().nullish(),
|
|
97
|
+
customer: customerSchema,
|
|
98
|
+
metadata: metadataSchema,
|
|
99
|
+
product_cart: z.array(z.object({ product_id: z.string().min(1) })).nullish(),
|
|
100
|
+
});
|
|
101
|
+
function metadataString(metadata, key) {
|
|
102
|
+
const value = metadata?.[key];
|
|
103
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
104
|
+
}
|
|
105
|
+
/** Shared by the webhook and retrieveSubscription so both map Dodo state
|
|
106
|
+
* identically. Null when the status is unknown or the product is not in
|
|
107
|
+
* `priceToTier` (SubscriptionEvent.tier is required). */
|
|
108
|
+
export function mapDodoSubscriptionState(sub, priceToTier) {
|
|
109
|
+
const status = DODO_SUBSCRIPTION_STATUSES[sub.status];
|
|
110
|
+
const tier = priceToTier[sub.product_id];
|
|
111
|
+
if (!status || !tier)
|
|
112
|
+
return null;
|
|
113
|
+
return {
|
|
114
|
+
status,
|
|
115
|
+
tier,
|
|
116
|
+
currentPeriodEnd: sub.next_billing_date,
|
|
117
|
+
cancelAt: sub.cancel_at_next_billing_date === true ? sub.next_billing_date : null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function currentUnixSeconds() {
|
|
121
|
+
return Math.floor(Temporal.Now.instant().epochMilliseconds / 1000);
|
|
122
|
+
}
|
|
123
|
+
function parsePaymentEvent(providerEventId, data) {
|
|
124
|
+
const parsed = dodoPaymentSchema.safeParse(data);
|
|
125
|
+
if (!parsed.success)
|
|
126
|
+
return null;
|
|
127
|
+
const payment = parsed.data;
|
|
128
|
+
// A subscription's own payments are covered by its subscription.* events.
|
|
129
|
+
if (payment.subscription_id)
|
|
130
|
+
return null;
|
|
131
|
+
const tenantId = metadataString(payment.metadata, "tenantId");
|
|
132
|
+
const priceId = payment.product_cart?.[0]?.product_id;
|
|
133
|
+
if (!tenantId || !priceId)
|
|
134
|
+
return null;
|
|
135
|
+
const consentId = parseProviderConsentId(payment.metadata?.["consentId"]);
|
|
136
|
+
return {
|
|
137
|
+
kind: BillingEventKinds.payment,
|
|
138
|
+
providerEventId,
|
|
139
|
+
providerName: DODO_PROVIDER_NAME,
|
|
140
|
+
tenantId,
|
|
141
|
+
...(consentId !== undefined && { consentId }),
|
|
142
|
+
...(payment.checkout_session_id && { providerCheckoutId: payment.checkout_session_id }),
|
|
143
|
+
providerCustomerId: payment.customer.customer_id,
|
|
144
|
+
priceId,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function parseSubscriptionEvent(providerEventId, type, providerEventAt, data, priceToTier) {
|
|
148
|
+
const parsed = dodoSubscriptionSchema.safeParse(data);
|
|
149
|
+
if (!parsed.success)
|
|
150
|
+
return null;
|
|
151
|
+
const sub = parsed.data;
|
|
152
|
+
const tenantId = metadataString(sub.metadata, "tenantId");
|
|
153
|
+
const state = mapDodoSubscriptionState(sub, priceToTier);
|
|
154
|
+
if (!tenantId || !state)
|
|
155
|
+
return null;
|
|
156
|
+
const consentId = parseProviderConsentId(sub.metadata?.["consentId"]);
|
|
157
|
+
return {
|
|
158
|
+
providerEventId,
|
|
159
|
+
providerName: DODO_PROVIDER_NAME,
|
|
160
|
+
type,
|
|
161
|
+
tenantId,
|
|
162
|
+
...(consentId !== undefined && { consentId }),
|
|
163
|
+
providerCustomerId: sub.customer.customer_id,
|
|
164
|
+
providerSubscriptionId: sub.subscription_id,
|
|
165
|
+
providerEventAt,
|
|
166
|
+
...state,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export function verifyAndParseDodoWebhook(runtime, options) {
|
|
170
|
+
const nowSeconds = options.nowSeconds ?? currentUnixSeconds;
|
|
171
|
+
return async (rawBody, headers, systemSecrets) => {
|
|
172
|
+
const secret = await runtime.resolveWebhookSecret(systemSecrets);
|
|
173
|
+
verifyStandardWebhook(rawBody, headers, secret, nowSeconds());
|
|
174
|
+
// Past verification the body is trusted to be Dodo's; a malformed shape is
|
|
175
|
+
// schema drift, so it is dropped (200 ignored) instead of answered with a 401.
|
|
176
|
+
const envelope = envelopeSchema.safeParse(parseJsonOrThrow(rawBody, "subscription-dodo webhook body"));
|
|
177
|
+
// verifyStandardWebhook has checked webhook-id is present.
|
|
178
|
+
const providerEventId = headers["webhook-id"];
|
|
179
|
+
if (!envelope.success || !providerEventId)
|
|
180
|
+
return null;
|
|
181
|
+
const { type, timestamp, data } = envelope.data;
|
|
182
|
+
if (type === PAYMENT_SUCCEEDED)
|
|
183
|
+
return parsePaymentEvent(providerEventId, data);
|
|
184
|
+
const normalizedType = DODO_SUBSCRIPTION_EVENT_TYPES[type];
|
|
185
|
+
if (!normalizedType)
|
|
186
|
+
return null;
|
|
187
|
+
return parseSubscriptionEvent(providerEventId, normalizedType, timestamp, data, options.priceToTier);
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
// Feature name
|
|
2
2
|
export const SUBSCRIPTION_MOLLIE_FEATURE = "subscription-mollie";
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// Entity name under which the plugin registers against
|
|
4
|
+
// "subscriptionProvider". Matches the path segment in the webhook URL
|
|
5
5
|
// `/api/subscription/webhook/mollie`.
|
|
6
6
|
export const MOLLIE_PROVIDER_NAME = "mollie";
|
|
7
7
|
// =============================================================================
|
|
8
|
-
// Mollie
|
|
8
|
+
// Mollie webhook pattern (unlike Stripe)
|
|
9
9
|
// =============================================================================
|
|
10
10
|
//
|
|
11
|
-
// Mollie
|
|
12
|
-
//
|
|
13
|
-
// - `id=tr_xxx` → payment
|
|
14
|
-
// - `id=sub_xxx` → subscription
|
|
11
|
+
// Mollie sends only the ID in the body for EVERY event (form-urlencoded
|
|
12
|
+
// or JSON). The plugin must ALWAYS fetch lazily via the Mollie API:
|
|
13
|
+
// - `id=tr_xxx` → payment ID, fetch payment + subscription
|
|
14
|
+
// - `id=sub_xxx` → subscription ID, fetch subscription directly
|
|
15
15
|
//
|
|
16
|
-
// **
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// weiterreicht).
|
|
16
|
+
// **No native HMAC signature verification** in Mollie SDK 4.5.0. Mollie's
|
|
17
|
+
// security model relies on non-guessable IDs (~10^25 brute-force space)
|
|
18
|
+
// plus API lookup validation (garbage ID → 401 from the Mollie API). In
|
|
19
|
+
// addition, the app builder can place a URL-token wrapper before the
|
|
20
|
+
// foundation route (a custom extraRoute that verifies a secret token in
|
|
21
|
+
// the URL path and then forwards to the foundation).
|
|
23
22
|
//
|
|
24
|
-
// **Event
|
|
25
|
-
// typed events:
|
|
23
|
+
// **Event type mapping** is heuristic—Mollie has no explicitly typed events:
|
|
26
24
|
// - sub_xxx + status=canceled/completed → SubscriptionEventTypes.canceled
|
|
27
25
|
// - sub_xxx + status=active/pending → SubscriptionEventTypes.updated
|
|
28
26
|
// - tr_xxx + sequenceType=first + paid → SubscriptionEventTypes.created
|
|
29
27
|
// - tr_xxx + sequenceType=recurring + paid → SubscriptionEventTypes.invoicePaid
|
|
30
28
|
// - tr_xxx + sequenceType=recurring + failed → SubscriptionEventTypes.invoicePaymentFailed
|
|
31
|
-
// -
|
|
29
|
+
// - everything else → null (foundation 200 ignored)
|