@cosmicdrift/kumiko-samples 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.
Files changed (530) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +1 -1
  3. package/package.json +2 -2
  4. package/packages/bundled-features/package.json +9 -2
  5. package/packages/bundled-features/src/agent-tools/changes.json +6 -0
  6. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -1
  7. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +9 -9
  8. package/packages/bundled-features/src/audit/changes.json +12 -0
  9. package/packages/bundled-features/src/audit/handlers/audit-events.ts +1 -1
  10. package/packages/bundled-features/src/audit/screens.ts +1 -1
  11. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +10 -10
  12. package/packages/bundled-features/src/auth-email-password/feature.ts +6 -6
  13. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +3 -3
  14. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
  16. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +18 -18
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +12 -12
  18. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
  19. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +24 -26
  20. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -1
  21. package/packages/bundled-features/src/auth-email-password/handlers/self-registration-status.query.ts +1 -1
  22. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +34 -37
  23. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +6 -7
  24. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +1 -5
  25. package/packages/bundled-features/src/auth-email-password/index.ts +5 -5
  26. package/packages/bundled-features/src/auth-email-password/lockout-store.ts +1 -1
  27. package/packages/bundled-features/src/auth-email-password/seeding.ts +63 -66
  28. package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +8 -8
  29. package/packages/bundled-features/src/auth-email-password/testing.ts +3 -3
  30. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +57 -57
  31. package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +14 -14
  32. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +15 -16
  33. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +11 -12
  34. package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +1 -1
  35. package/packages/bundled-features/src/auth-email-password/web/constants.ts +3 -3
  36. package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +14 -14
  37. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -11
  38. package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -5
  39. package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +17 -17
  40. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +3 -4
  41. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +16 -17
  42. package/packages/bundled-features/src/auth-email-password/web/session.tsx +34 -34
  43. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +3 -3
  44. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +14 -14
  45. package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +26 -27
  46. package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +12 -13
  47. package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +17 -18
  48. package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +11 -12
  49. package/packages/bundled-features/src/auth-foundation/boot-checks.ts +4 -4
  50. package/packages/bundled-features/src/auth-foundation/resolve-anonymous-access.ts +4 -4
  51. package/packages/bundled-features/src/auth-foundation/types.ts +2 -2
  52. package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
  53. package/packages/bundled-features/src/auth-mfa/feature-exports.ts +2 -2
  54. package/packages/bundled-features/src/auth-mfa/feature.ts +6 -0
  55. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +3 -3
  56. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +4 -4
  57. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +2 -2
  58. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
  59. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +1 -1
  60. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -5
  61. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
  62. package/packages/bundled-features/src/auth-mfa/token-secrets.ts +1 -1
  63. package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +2 -2
  64. package/packages/bundled-features/src/auth-mfa/web/i18n.ts +9 -4
  65. package/packages/bundled-features/src/auth-mfa/web/index.ts +4 -4
  66. package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +17 -16
  67. package/packages/bundled-features/src/auth-mfa/web/mfa-code-or-recovery-field.tsx +68 -0
  68. package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +23 -13
  69. package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +23 -13
  70. package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +15 -6
  71. package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +71 -24
  72. package/packages/bundled-features/src/billing-foundation/aggregate-id.ts +10 -11
  73. package/packages/bundled-features/src/billing-foundation/changes.json +55 -0
  74. package/packages/bundled-features/src/billing-foundation/checkout-core.ts +32 -42
  75. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +8 -2
  76. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +2 -0
  77. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +14 -3
  78. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +3 -4
  79. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +12 -6
  80. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +35 -19
  81. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +12 -8
  82. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +14 -8
  83. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +5 -0
  84. package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +8 -10
  85. package/packages/bundled-features/src/billing-foundation/entities.ts +27 -27
  86. package/packages/bundled-features/src/billing-foundation/events.ts +23 -21
  87. package/packages/bundled-features/src/billing-foundation/feature.ts +45 -44
  88. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +3 -5
  89. package/packages/bundled-features/src/billing-foundation/handlers/billing-plans.query.ts +71 -6
  90. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +19 -17
  91. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +11 -12
  92. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -4
  93. package/packages/bundled-features/src/billing-foundation/handlers/plan-switch-preview.query.ts +65 -0
  94. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +29 -22
  95. package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +44 -8
  96. package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +72 -82
  97. package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +14 -1
  98. package/packages/bundled-features/src/billing-foundation/i18n.ts +22 -0
  99. package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
  100. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +62 -27
  101. package/packages/bundled-features/src/billing-foundation/plan-switch.ts +84 -0
  102. package/packages/bundled-features/src/billing-foundation/projection.ts +38 -37
  103. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -3
  104. package/packages/bundled-features/src/billing-foundation/tenant-destroy-hook.ts +7 -8
  105. package/packages/bundled-features/src/billing-foundation/types.ts +144 -66
  106. package/packages/bundled-features/src/billing-foundation/validate-options.ts +19 -0
  107. package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +123 -51
  108. package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +89 -42
  109. package/packages/bundled-features/src/billing-foundation/web/constants.ts +4 -0
  110. package/packages/bundled-features/src/billing-foundation/web/plan-price.ts +55 -0
  111. package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +224 -0
  112. package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -1
  113. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +8 -9
  114. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +5 -1
  115. package/packages/bundled-features/src/cap-counter/changes.json +18 -0
  116. package/packages/bundled-features/src/cap-counter/constants.ts +11 -12
  117. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +66 -68
  118. package/packages/bundled-features/src/cap-counter/entity.ts +29 -29
  119. package/packages/bundled-features/src/cap-counter/feature.ts +34 -35
  120. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +31 -31
  121. package/packages/bundled-features/src/cap-counter/handlers/mark-soft-warned.write.ts +4 -5
  122. package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +9 -2
  123. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -5
  124. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -3
  125. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  126. package/packages/bundled-features/src/channel-email/changes.json +6 -0
  127. package/packages/bundled-features/src/channel-email/feature.ts +1 -1
  128. package/packages/bundled-features/src/channel-email/smtp-transport.ts +34 -31
  129. package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +3 -4
  130. package/packages/bundled-features/src/compliance-profiles/feature.ts +15 -16
  131. package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +3 -6
  132. package/packages/bundled-features/src/compliance-profiles/handlers/list-profiles.query.ts +5 -5
  133. package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +4 -4
  134. package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +13 -14
  135. package/packages/bundled-features/src/compliance-profiles/handlers/sub-processors.query.ts +9 -11
  136. package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +7 -7
  137. package/packages/bundled-features/src/compliance-profiles/schema/profile-selection.ts +18 -23
  138. package/packages/bundled-features/src/compliance-profiles/seeding.ts +14 -15
  139. package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +3 -3
  140. package/packages/bundled-features/src/compliance-profiles-ops/index.ts +2 -2
  141. package/packages/bundled-features/src/config/changes.json +12 -0
  142. package/packages/bundled-features/src/config/handlers/cascade.query.ts +4 -2
  143. package/packages/bundled-features/src/config/handlers/readiness.query.ts +7 -7
  144. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +4 -5
  145. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  146. package/packages/bundled-features/src/config/handlers/values.query.ts +4 -2
  147. package/packages/bundled-features/src/config/resolver.ts +4 -4
  148. package/packages/bundled-features/src/config/table.ts +10 -10
  149. package/packages/bundled-features/src/config/write-helpers.ts +28 -9
  150. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  151. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +23 -21
  152. package/packages/bundled-features/src/custom-fields/aggregate-id.ts +16 -17
  153. package/packages/bundled-features/src/custom-fields/entity.ts +49 -47
  154. package/packages/bundled-features/src/custom-fields/events.ts +7 -8
  155. package/packages/bundled-features/src/custom-fields/feature.ts +70 -72
  156. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +9 -8
  157. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +28 -26
  158. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +6 -7
  159. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +12 -13
  160. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +25 -28
  161. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +6 -6
  162. package/packages/bundled-features/src/custom-fields/lib/field-access.ts +2 -2
  163. package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
  164. package/packages/bundled-features/src/custom-fields/lib/quota.ts +1 -1
  165. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -12
  166. package/packages/bundled-features/src/custom-fields/run-retention.ts +3 -3
  167. package/packages/bundled-features/src/custom-fields/schemas.ts +14 -14
  168. package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +2 -5
  169. package/packages/bundled-features/src/custom-fields/web/constants.ts +2 -2
  170. package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +26 -26
  171. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +65 -67
  172. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +3 -3
  173. package/packages/bundled-features/src/data-retention/_internal/parse-override.ts +2 -3
  174. package/packages/bundled-features/src/data-retention/feature.ts +11 -18
  175. package/packages/bundled-features/src/data-retention/handlers/policy-for.query.ts +6 -6
  176. package/packages/bundled-features/src/data-retention/keep-for.ts +17 -17
  177. package/packages/bundled-features/src/data-retention/override-schema.ts +13 -15
  178. package/packages/bundled-features/src/data-retention/presets.ts +18 -23
  179. package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
  180. package/packages/bundled-features/src/data-retention/resolve-tenant-preset.ts +17 -16
  181. package/packages/bundled-features/src/data-retention/resolver.ts +33 -32
  182. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +71 -58
  183. package/packages/bundled-features/src/data-retention/schema/tenant-retention-override.ts +13 -11
  184. package/packages/bundled-features/src/delivery/changes.json +30 -0
  185. package/packages/bundled-features/src/delivery/channel-context.ts +9 -3
  186. package/packages/bundled-features/src/delivery/db/queries/preferences.ts +2 -2
  187. package/packages/bundled-features/src/delivery/delivery-service.ts +35 -5
  188. package/packages/bundled-features/src/delivery/feature.ts +3 -3
  189. package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
  190. package/packages/bundled-features/src/delivery/handlers/preferences.query.ts +20 -1
  191. package/packages/bundled-features/src/delivery/handlers/unsubscribe-page-chrome.query.ts +1 -1
  192. package/packages/bundled-features/src/delivery/tables.ts +9 -8
  193. package/packages/bundled-features/src/delivery/unsubscribe-link.ts +3 -1
  194. package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +1 -0
  195. package/packages/bundled-features/src/delivery/unsubscribe-page.ts +6 -0
  196. package/packages/bundled-features/src/delivery/unsubscribe.ts +40 -19
  197. package/packages/bundled-features/src/delivery/upsert-preference.ts +3 -4
  198. package/packages/bundled-features/src/document-ingest-foundation/entity.ts +5 -5
  199. package/packages/bundled-features/src/document-ingest-foundation/events.ts +2 -2
  200. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
  201. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +1 -1
  202. package/packages/bundled-features/src/document-ingest-foundation/tenant-destroy-hook.ts +4 -4
  203. package/packages/bundled-features/src/feature-toggles/compose-tier-resolver.ts +1 -1
  204. package/packages/bundled-features/src/feature-toggles/feature.ts +1 -1
  205. package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
  206. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +5 -5
  207. package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
  208. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  209. package/packages/bundled-features/src/feature-toggles/web/constants.ts +1 -1
  210. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  211. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  212. package/packages/bundled-features/src/file-derivatives/presets.ts +1 -1
  213. package/packages/bundled-features/src/file-foundation/feature.ts +6 -6
  214. package/packages/bundled-features/src/file-foundation/index.ts +1 -0
  215. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -1
  216. package/packages/bundled-features/src/file-provider-inmemory/feature.ts +16 -16
  217. package/packages/bundled-features/src/file-provider-s3/env-named-feature.ts +0 -2
  218. package/packages/bundled-features/src/file-provider-s3/feature.ts +16 -16
  219. package/packages/bundled-features/src/file-provider-s3/lib/s3-provider.ts +6 -6
  220. package/packages/bundled-features/src/files/README.md +1 -1
  221. package/packages/bundled-features/src/files/feature.ts +6 -6
  222. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +5 -5
  223. package/packages/bundled-features/src/files-tenant-data/hooks.ts +24 -26
  224. package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
  225. package/packages/bundled-features/src/folders/feature.ts +1 -1
  226. package/packages/bundled-features/src/folders/web/folder-manager.tsx +2 -2
  227. package/packages/bundled-features/src/folders/web/tree.ts +5 -5
  228. package/packages/bundled-features/src/form-draft/changes.json +6 -0
  229. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +3 -3
  230. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
  231. package/packages/bundled-features/src/form-draft/decrypt-draft.ts +4 -1
  232. package/packages/bundled-features/src/form-draft/entity.ts +3 -3
  233. package/packages/bundled-features/src/form-draft/feature.ts +4 -5
  234. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  235. package/packages/bundled-features/src/form-draft/release-file-refs.ts +6 -6
  236. package/packages/bundled-features/src/form-draft/schemas.ts +3 -3
  237. package/packages/bundled-features/src/form-draft/web/constants.ts +0 -2
  238. package/packages/bundled-features/src/host-routing/README.md +1 -1
  239. package/packages/bundled-features/src/host-routing/changes.json +6 -0
  240. package/packages/bundled-features/src/host-routing/dispatch.ts +16 -17
  241. package/packages/bundled-features/src/host-routing/options.ts +2 -1
  242. package/packages/bundled-features/src/inbound-mail-foundation/aggregate-id.ts +19 -19
  243. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
  244. package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +4 -3
  245. package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +30 -33
  246. package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +47 -48
  247. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +54 -50
  248. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +39 -39
  249. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +8 -8
  250. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +25 -26
  251. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +7 -7
  252. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +50 -51
  253. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +8 -8
  254. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +10 -10
  255. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +3 -4
  256. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +27 -15
  257. package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +0 -1
  258. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +16 -15
  259. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +31 -29
  260. package/packages/bundled-features/src/inbound-mail-foundation/provider-factory.ts +9 -9
  261. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +45 -41
  262. package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +0 -3
  263. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +14 -16
  264. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +70 -70
  265. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +101 -84
  266. package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +13 -13
  267. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +42 -40
  268. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +20 -19
  269. package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +21 -21
  270. package/packages/bundled-features/src/jobs/changes.json +13 -0
  271. package/packages/bundled-features/src/jobs/db/queries/retention.ts +2 -2
  272. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +3 -3
  273. package/packages/bundled-features/src/jobs/events.ts +1 -1
  274. package/packages/bundled-features/src/jobs/feature.ts +20 -11
  275. package/packages/bundled-features/src/jobs/handlers/detail.query.ts +4 -4
  276. package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
  277. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  278. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +1 -1
  279. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  280. package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +45 -40
  281. package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +63 -59
  282. package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
  283. package/packages/bundled-features/src/jobs/job-run-logger.ts +19 -19
  284. package/packages/bundled-features/src/jobs/job-run-table.ts +3 -3
  285. package/packages/bundled-features/src/jobs/screens.ts +1 -1
  286. package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +1 -1
  287. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +1 -1
  288. package/packages/bundled-features/src/ledger/entity.ts +5 -5
  289. package/packages/bundled-features/src/ledger/feature.ts +3 -3
  290. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +3 -3
  291. package/packages/bundled-features/src/ledger/recurring.ts +12 -12
  292. package/packages/bundled-features/src/ledger/schemas.ts +1 -1
  293. package/packages/bundled-features/src/ledger/web/constants.ts +2 -2
  294. package/packages/bundled-features/src/ledger/web/index.ts +7 -7
  295. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  296. package/packages/bundled-features/src/legal-pages/feature.ts +40 -41
  297. package/packages/bundled-features/src/legal-pages/markdown.ts +3 -3
  298. package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +23 -24
  299. package/packages/bundled-features/src/legal-pages/web/index.ts +0 -2
  300. package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
  301. package/packages/bundled-features/src/mail-foundation/feature.ts +31 -30
  302. package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +27 -28
  303. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +20 -20
  304. package/packages/bundled-features/src/managed-pages/branding.ts +6 -6
  305. package/packages/bundled-features/src/managed-pages/feature.ts +62 -62
  306. package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +5 -6
  307. package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +8 -8
  308. package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +5 -7
  309. package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +11 -11
  310. package/packages/bundled-features/src/managed-pages/seeding.ts +8 -8
  311. package/packages/bundled-features/src/managed-pages/table.ts +16 -15
  312. package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -6
  313. package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -1
  314. package/packages/bundled-features/src/notes-history/entity.ts +4 -4
  315. package/packages/bundled-features/src/notes-history/feature.ts +2 -2
  316. package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
  317. package/packages/bundled-features/src/page-render/layout.ts +9 -9
  318. package/packages/bundled-features/src/page-render/markdown.ts +11 -11
  319. package/packages/bundled-features/src/page-render/security-headers.ts +7 -6
  320. package/packages/bundled-features/src/personal-access-tokens/feature.ts +9 -6
  321. package/packages/bundled-features/src/personal-access-tokens/roles.ts +2 -2
  322. package/packages/bundled-features/src/readiness/handlers/status.query.ts +3 -3
  323. package/packages/bundled-features/src/renderer-foundation/api.ts +12 -13
  324. package/packages/bundled-features/src/renderer-foundation/constants.ts +2 -2
  325. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  326. package/packages/bundled-features/src/renderer-foundation/types.ts +11 -12
  327. package/packages/bundled-features/src/renderer-pdf/changes.json +6 -0
  328. package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +27 -10
  329. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  330. package/packages/bundled-features/src/secrets/web/constants.ts +2 -2
  331. package/packages/bundled-features/src/seo/feature.ts +3 -3
  332. package/packages/bundled-features/src/sessions/constants.ts +1 -1
  333. package/packages/bundled-features/src/sessions/feature.ts +16 -11
  334. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +2 -2
  335. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +2 -2
  336. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  337. package/packages/bundled-features/src/sessions/schema/user-session.ts +4 -4
  338. package/packages/bundled-features/src/sessions/screens.ts +2 -2
  339. package/packages/bundled-features/src/sessions/session-callbacks.ts +2 -2
  340. package/packages/bundled-features/src/sessions/session-revoked-event.ts +2 -2
  341. package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +1 -1
  342. package/packages/bundled-features/src/shared/decrypt-stored-pii.ts +11 -1
  343. package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +1 -1
  344. package/packages/bundled-features/src/shared/index.ts +1 -1
  345. package/packages/bundled-features/src/shared/mail-template-body.ts +10 -3
  346. package/packages/bundled-features/src/shared/map-with-concurrency.ts +5 -4
  347. package/packages/bundled-features/src/shared/parent-visibility.ts +2 -2
  348. package/packages/bundled-features/src/shared/password-hashing.ts +1 -1
  349. package/packages/bundled-features/src/shared/signup-handover.ts +1 -2
  350. package/packages/bundled-features/src/shared/single-use-token-store.ts +1 -1
  351. package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
  352. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -0
  353. package/packages/bundled-features/src/subscription-dodo/changes.json +14 -0
  354. package/packages/bundled-features/src/subscription-dodo/client.ts +45 -0
  355. package/packages/bundled-features/src/subscription-dodo/constants.ts +24 -0
  356. package/packages/bundled-features/src/subscription-dodo/feature.ts +207 -0
  357. package/packages/bundled-features/src/subscription-dodo/index.ts +16 -0
  358. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +345 -0
  359. package/packages/bundled-features/src/subscription-dodo/runtime.ts +126 -0
  360. package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +262 -0
  361. package/packages/bundled-features/src/subscription-mollie/constants.ts +15 -17
  362. package/packages/bundled-features/src/subscription-mollie/feature.ts +41 -42
  363. package/packages/bundled-features/src/subscription-mollie/index.ts +3 -3
  364. package/packages/bundled-features/src/subscription-mollie/plugin-methods.ts +17 -16
  365. package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +34 -34
  366. package/packages/bundled-features/src/subscription-stripe/changes.json +12 -0
  367. package/packages/bundled-features/src/subscription-stripe/constants.ts +4 -6
  368. package/packages/bundled-features/src/subscription-stripe/feature.ts +45 -43
  369. package/packages/bundled-features/src/subscription-stripe/index.ts +6 -6
  370. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +22 -27
  371. package/packages/bundled-features/src/subscription-stripe/runtime.ts +9 -8
  372. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +62 -64
  373. package/packages/bundled-features/src/tags/feature.ts +3 -3
  374. package/packages/bundled-features/src/tags/screens.ts +1 -1
  375. package/packages/bundled-features/src/template-resolver/api.ts +41 -14
  376. package/packages/bundled-features/src/template-resolver/changes.json +48 -0
  377. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -0
  378. package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
  379. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +3 -2
  380. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +27 -5
  381. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +3 -2
  382. package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +3 -2
  383. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +8 -7
  384. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -20
  385. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +15 -5
  386. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +2 -2
  387. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +3 -3
  388. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-counts.query.ts +9 -5
  389. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +8 -6
  390. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +44 -5
  391. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +3 -4
  392. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +4 -5
  393. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +37 -26
  394. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +7 -2
  395. package/packages/bundled-features/src/template-resolver/seeding.ts +36 -18
  396. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +22 -4
  397. package/packages/bundled-features/src/template-resolver/table.ts +13 -14
  398. package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +0 -11
  399. package/packages/bundled-features/src/template-resolver/template-meta.ts +12 -4
  400. package/packages/bundled-features/src/template-resolver/template-preview.ts +11 -3
  401. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -2
  402. package/packages/bundled-features/src/template-resolver/web/constants.ts +7 -5
  403. package/packages/bundled-features/src/template-resolver/web/index.ts +7 -0
  404. package/packages/bundled-features/src/template-resolver/web/mail-layout-constants.ts +8 -0
  405. package/packages/bundled-features/src/tenant/changes.json +6 -0
  406. package/packages/bundled-features/src/tenant/feature.ts +7 -7
  407. package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +10 -4
  408. package/packages/bundled-features/src/tenant/handlers/members.query.ts +1 -1
  409. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  410. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
  411. package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +8 -5
  412. package/packages/bundled-features/src/tenant/invitation-table.ts +37 -40
  413. package/packages/bundled-features/src/tenant/membership-table.ts +3 -3
  414. package/packages/bundled-features/src/tenant/schema/tenant.ts +7 -9
  415. package/packages/bundled-features/src/tenant/screens.ts +1 -1
  416. package/packages/bundled-features/src/tenant/seeding.ts +19 -18
  417. package/packages/bundled-features/src/tenant/testing.ts +5 -6
  418. package/packages/bundled-features/src/tenant/web/constants.ts +2 -2
  419. package/packages/bundled-features/src/tenant-handover/events.ts +5 -7
  420. package/packages/bundled-features/src/tenant-handover/feature.ts +1 -1
  421. package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
  422. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  423. package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +8 -9
  424. package/packages/bundled-features/src/tenant-handover/signup-handover-provider.ts +1 -2
  425. package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +4 -5
  426. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  427. package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
  428. package/packages/bundled-features/src/tenant-lifecycle/constants.ts +5 -0
  429. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -1
  430. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +7 -3
  431. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +1 -1
  432. package/packages/bundled-features/src/tenant-settings/constants.ts +1 -1
  433. package/packages/bundled-features/src/tenant-settings/tenant-defaults.ts +12 -5
  434. package/packages/bundled-features/src/tier-engine/aggregate-id.ts +15 -16
  435. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  436. package/packages/bundled-features/src/tier-engine/compose-app.ts +14 -14
  437. package/packages/bundled-features/src/tier-engine/entity.ts +18 -19
  438. package/packages/bundled-features/src/tier-engine/feature.ts +88 -87
  439. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +6 -7
  440. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +4 -4
  441. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +12 -9
  442. package/packages/bundled-features/src/tier-engine/tier-resolver.ts +2 -2
  443. package/packages/bundled-features/src/tier-engine/trial.ts +13 -13
  444. package/packages/bundled-features/src/tier-engine/web/constants.ts +3 -3
  445. package/packages/bundled-features/src/tier-engine/web/index.ts +4 -4
  446. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +8 -8
  447. package/packages/bundled-features/src/user/handlers/create.write.ts +4 -4
  448. package/packages/bundled-features/src/user/schema/user.ts +45 -44
  449. package/packages/bundled-features/src/user/seeding.ts +40 -40
  450. package/packages/bundled-features/src/user/testing.ts +3 -3
  451. package/packages/bundled-features/src/user-data-rights/audit-download.ts +27 -28
  452. package/packages/bundled-features/src/user-data-rights/boot-checks.ts +1 -1
  453. package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
  454. package/packages/bundled-features/src/user-data-rights/feature.ts +112 -113
  455. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +9 -9
  456. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +8 -8
  457. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +29 -30
  458. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +40 -42
  459. package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +8 -9
  460. package/packages/bundled-features/src/user-data-rights/handlers/lift-restriction.write.ts +7 -8
  461. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -2
  462. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +4 -4
  463. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion-by-email.write.ts +30 -29
  464. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +15 -18
  465. package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +42 -41
  466. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +22 -19
  467. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +8 -8
  468. package/packages/bundled-features/src/user-data-rights/index.ts +8 -10
  469. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +3 -3
  470. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +24 -27
  471. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +169 -280
  472. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +87 -108
  473. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +67 -69
  474. package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +11 -11
  475. package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +37 -41
  476. package/packages/bundled-features/src/user-data-rights/schema/export-job.ts +65 -67
  477. package/packages/bundled-features/src/user-data-rights/token-helpers.ts +21 -25
  478. package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +6 -6
  479. package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +6 -5
  480. package/packages/bundled-features/src/user-data-rights/web/constants.ts +1 -1
  481. package/packages/bundled-features/src/user-data-rights/web/index.ts +5 -5
  482. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +14 -13
  483. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +13 -12
  484. package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +6 -6
  485. package/packages/bundled-features/src/user-data-rights/zip-path.ts +41 -40
  486. package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +15 -15
  487. package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +3 -5
  488. package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +45 -51
  489. package/packages/bundled-features/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +2 -2
  490. package/packages/bundled-features/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +1 -1
  491. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +3 -3
  492. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +32 -31
  493. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +13 -13
  494. package/packages/bundled-features/src/user-profile/index.ts +3 -3
  495. package/packages/bundled-features/src/user-profile/web/client-plugin.ts +1 -1
  496. package/packages/bundled-features/src/user-profile/web/constants.ts +1 -1
  497. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +1 -1
  498. package/packages/bundled-features/src/waitlist/changes.json +18 -0
  499. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +2 -1
  500. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +5 -5
  501. package/packages/bundled-features/src/waitlist/import.ts +5 -0
  502. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +22 -3
  503. package/packages/bundled-features/src/waitlist/link-on-signup.ts +20 -10
  504. package/packages/bundled-features/src/waitlist/options.ts +3 -0
  505. package/packages/bundled-features/src/workflow-runner/aggregate-id.ts +1 -1
  506. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -5
  507. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +4 -15
  508. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  509. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +4 -4
  510. package/packages/bundled-features/src/workflow-runner/pending-projection.ts +6 -7
  511. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -6
  512. package/packages/bundled-features/src/workflow-runner/tables.ts +6 -6
  513. package/packages/bundled-features/src/workflow-runner/workflow-registry.ts +1 -1
  514. package/samples/apps/styleguide/src/features/widgets/i18n.ts +11 -0
  515. package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +24 -0
  516. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +5 -0
  517. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +90 -15
  518. package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +16 -6
  519. package/samples/apps/use-all-bundled/feature-manifest.json +120 -3
  520. package/samples/apps/use-all-bundled/src/run-config.ts +9 -3
  521. package/samples/recipes/auth-mfa/README.md +5 -0
  522. package/samples/recipes/auth-mfa/package.json +5 -2
  523. package/samples/recipes/auth-mfa/src/web/confirmation-code-step.tsx +49 -0
  524. package/samples/recipes/auth-mfa/tsconfig.json +8 -0
  525. package/samples/recipes/env-composition/README.md +3 -1
  526. package/samples/recipes/env-composition/src/env.ts +2 -0
  527. package/samples/recipes/legal-pages/README.md +1 -1
  528. package/samples/recipes/rate-limiting/README.md +18 -0
  529. package/samples/recipes/rate-limiting/src/feature.ts +12 -0
  530. package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +0 -12
@@ -1,18 +1,18 @@
1
- // create-checkout-session — Tenant-Admin klickt "Upgrade to Pro" (oder
2
- // wählt zwischen Karte/PayPal/Apple-Pay). Handler findet den
3
- // providerName aus der payload, lookuppt den Plugin, ruft
4
- // `plugin.createCheckoutSession(ctx, ...)`, returnt hosted-page-URL.
5
- // Tenant-Admin wird dorthin redirected.
1
+ // create-checkout-session — Tenant admin clicks "Upgrade to Pro" (or
2
+ // chooses between card/PayPal/Apple Pay). The handler reads providerName
3
+ // from the payload, looks up the plugin, calls
4
+ // `plugin.createCheckoutSession(ctx, ...)`, and returns the hosted-page URL.
5
+ // The tenant admin is redirected there.
6
6
  //
7
- // **Multi-Provider-Pfad:** payload.providerName ist der entityName
8
- // eines registrierten Plugins ("stripe" / "paypal" / "mollie" / ...).
9
- // App-Builder zeigt eine UI-Liste der gemounteten provider, Endkunde
10
- // pickt einen, dieser handler dispatched zum richtigen Plugin.
7
+ // **Multi-provider path:** payload.providerName is the entityName
8
+ // of a registered plugin ("stripe" / "paypal" / "mollie" / ...).
9
+ // The app builder shows a UI list of mounted providers; the customer
10
+ // selects one, and this handler dispatches to the matching plugin.
11
11
  //
12
- // **Tenant-resolution:** ctx.user.tenantId wird als metadata an den
13
- // Provider mitgegeben. Beim subsequent webhook (subscription.created)
14
- // liest verifyAndParseWebhook das aus dem provider-payload zurück und
15
- // resolved den Tenant.
12
+ // **Tenant resolution:** ctx.user.tenantId is passed to the provider as
13
+ // metadata. On the subsequent webhook (subscription.created),
14
+ // verifyAndParseWebhook reads it back from the provider payload and
15
+ // resolves the tenant.
16
16
  //
17
17
  // **Hardening:** successUrl/cancelUrl must share `options.baseUrl`'s
18
18
  // origin; mode:"subscription" requires priceId to be a known price of the
@@ -51,12 +51,13 @@ const createCheckoutSessionSchema = z.object({
51
51
  providerName: z.string().min(1).max(50),
52
52
  /** Provider-eigene price/plan-ID. */
53
53
  priceId: z.string().min(1).max(200),
54
- /** Wo der Endkunde nach erfolgreichem checkout landed. */
54
+ /** Where the customer lands after a successful checkout. */
55
55
  successUrl: z.string().url(),
56
- /** Wo der Endkunde landed wenn er abbricht. */
56
+ /** Where the customer lands if they cancel. */
57
57
  cancelUrl: z.string().url(),
58
- /** Optional: existierender provider-customer wenn der Tenant schon
59
- * einen account beim Provider hat (= Plan-Wechsel statt Neuregistrierung). */
58
+ /** Optional: existing provider customer when the tenant already
59
+ * has an account with the provider (= plan change instead of a new
60
+ * registration). */
60
61
  providerCustomerId: z.string().max(200).optional(),
61
62
  /** Optional: `"payment"` for a one-off checkout (e.g. credit top-up)
62
63
  * instead of a recurring subscription. Defaults to `"subscription"`. */
@@ -120,6 +121,7 @@ export function createCheckoutSessionHandler(
120
121
  tier: mode === "subscription" ? (plugin.priceToTier?.[payload.priceId] ?? null) : null,
121
122
  priceId: payload.priceId,
122
123
  price,
124
+ providerName: payload.providerName,
123
125
  ...(orderItem && { orderItem }),
124
126
  });
125
127
 
@@ -1,17 +1,16 @@
1
- // create-portal-session — Tenant-Admin klickt "Manage Subscription".
2
- // Handler findet die current subscription des Tenants, lookuppt den
3
- // passenden Plugin (= subscription.providerName-Spalte), ruft
4
- // `plugin.createPortalSession(ctx, ...)`, returnt hosted-portal-URL.
1
+ // create-portal-session — Tenant admin clicks "Manage Subscription".
2
+ // The handler finds the tenant's current subscription, looks up the matching
3
+ // plugin (= the `subscription.providerName` column), calls
4
+ // `plugin.createPortalSession(ctx, ...)`, and returns the hosted portal URL.
5
5
  //
6
- // **Provider-resolution:** anders als create-checkout-session (= der
7
- // Tenant wählt einen NEUEN Provider beim Subscribe) ist hier der
8
- // Provider durch die existing subscription-row festgelegt — Tenant
9
- // kann nicht zum Portal eines OTHER Providers, weil der ihn nicht
10
- // kennt.
6
+ // **Provider resolution:** unlike create-checkout-session (where the tenant
7
+ // chooses a NEW provider when subscribing), the provider here is fixed by the
8
+ // existing subscription row. The tenant cannot access another provider's
9
+ // portal because the subscription does not belong to that provider.
11
10
  //
12
- // **returnUrl:** computed server-side via `portalReturnUrl` (catalog's
13
- // returnPath, or baseUrl itself) — the client no longer supplies one, so
14
- // there is no client-controlled redirect to harden against.
11
+ // **returnUrl:** computed server-side via `portalReturnUrl` (the catalog's
12
+ // returnPath, or baseUrl itself). The client no longer supplies one, so there
13
+ // is no client-controlled redirect to harden against.
15
14
 
16
15
  import {
17
16
  configuredPiiSubjectKms,
@@ -1,7 +1,7 @@
1
- // list-subscriptions — sysadmin-cross-tenant list-query auf der
2
- // `read_subscriptions`-projection. Tenant-Admins lesen ihre eigene
3
- // subscription via getSubscriptionForTenant-helper (= ctx.db ist
4
- // tenant-scoped, gibt automatisch nur die row des Callers zurück).
1
+ // list-subscriptions — sysadmin cross-tenant list query on the
2
+ // `read_subscriptions` projection. Tenant admins read their own
3
+ // subscription through the getSubscriptionForTenant helper (= ctx.db is
4
+ // tenant-scoped and automatically returns only the caller's row).
5
5
  import {
6
6
  configuredPiiSubjectKms,
7
7
  decryptPiiFieldValues,
@@ -0,0 +1,65 @@
1
+ // plan-switch-preview — what switching to a tier would cost right now, for
2
+ // providers that switch plans in-app (previewPlanSwitch + applyPlanSwitch).
3
+ // Only registered when `createBillingFoundationFeature` gets a `catalog`.
4
+
5
+ import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
6
+ import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
7
+ import * as z from "zod";
8
+ import { isNonEmptyStringArray } from "../checkout-core.ts";
9
+ import { purchaseRolesOf } from "../plan-catalog.ts";
10
+ import { preparePlanSwitch } from "../plan-switch.ts";
11
+ import type { BillingPlanCatalog, PlanSwitchPreview } from "../types.ts";
12
+
13
+ export type PlanSwitchPreviewResult = {
14
+ readonly preview: PlanSwitchPreview;
15
+ readonly target: {
16
+ readonly tier: string;
17
+ readonly unitAmount: number;
18
+ readonly currency: string;
19
+ readonly interval: "day" | "week" | "month" | "year" | null;
20
+ readonly intervalCount: number | null;
21
+ };
22
+ };
23
+
24
+ export function createPlanSwitchPreviewQuery(catalog: BillingPlanCatalog): QueryHandlerDef {
25
+ const plans = catalog.plans;
26
+ if (!isNonEmptyStringArray(plans)) {
27
+ throw new Error("plan-switch-preview: catalog.plans must not be empty");
28
+ }
29
+ const schema = z.object({ tier: z.enum(plans) }).strict();
30
+
31
+ return {
32
+ name: "plan-switch-preview",
33
+ description:
34
+ "Previews what switching the existing subscription to another plan tier costs right now (amount due, tax, credit, effective date) plus the target plan's recurring price. Only for providers that switch plans in-app.",
35
+ schema,
36
+ access: { roles: purchaseRolesOf(catalog) },
37
+ handler: async (query, ctx): Promise<PlanSwitchPreviewResult> => {
38
+ // @cast-boundary engine-payload — dispatcher-zod-validated payload
39
+ const { tier } = query.payload as { tier: string };
40
+ const prepared = await preparePlanSwitch(ctx, catalog, tier, "plan-switch-preview");
41
+ const { plugin } = prepared.provider;
42
+ if (!plugin.previewPlanSwitch || !plugin.applyPlanSwitch) {
43
+ throw new UnprocessableError("plan_switch_not_supported", {
44
+ i18nKey: "billing-foundation.errors.planSwitchNotSupported",
45
+ message: `subscription-foundation: provider "${prepared.provider.name}" switches plans on a hosted page and has no in-app preview`,
46
+ });
47
+ }
48
+ const preview = await plugin.previewPlanSwitch(ctx, {
49
+ providerSubscriptionId: prepared.subscription.providerSubscriptionId,
50
+ targetPriceId: prepared.target.priceId,
51
+ });
52
+ const { price } = prepared.target;
53
+ return {
54
+ preview,
55
+ target: {
56
+ tier,
57
+ unitAmount: price.unitAmount ?? 0,
58
+ currency: price.currency,
59
+ interval: price.interval,
60
+ intervalCount: price.intervalCount,
61
+ },
62
+ };
63
+ },
64
+ };
65
+ }
@@ -1,16 +1,16 @@
1
- // process-event — programmatic write-handler den der webhook-handler
2
- // (createSubscriptionWebhookRoute) aufruft NACHDEM Plugin den raw-body
3
- // verifiziert + zu SubscriptionEvent normalisiert hat.
1
+ // process-event — programmatic write handler called by the webhook handler
2
+ // (createSubscriptionWebhookRoute) AFTER the plugin has verified the raw body
3
+ // and normalized it into a SubscriptionEvent.
4
4
  //
5
- // **ES-Pattern:**
6
- // 1. Idempotency-check: lädt subscription-stream + scannt nach
7
- // bereits gesehenem `metadata.providerEventId`. Provider-Replay
8
- // (Stripe-Retry-Storm) sieht denselben event-id → duplicate=true,
9
- // kein zweiter append.
10
- // 2. Type-mapping: SubscriptionEvent.type (= normalisiert vom Plugin)
11
- // → einer der 5 ES-event-typen.
12
- // 3. ctx.unsafeAppendEvent — Inline-projection materialisiert die
13
- // `read_subscriptions`-row in derselben TX.
5
+ // **ES pattern:**
6
+ // 1. Idempotency check: loads the subscription stream and scans for an
7
+ // already-seen `metadata.providerEventId`. A provider replay
8
+ // (Stripe retry storm) has the same event ID → duplicate=true,
9
+ // with no second append.
10
+ // 2. Type mapping: SubscriptionEvent.type (= normalized by the plugin)
11
+ // → one of the 5 ES event types.
12
+ // 3. ctx.unsafeAppendEvent — the inline projection materializes the
13
+ // `read_subscriptions` row in the same TX.
14
14
 
15
15
  import {
16
16
  configuredPiiSubjectKms,
@@ -42,9 +42,9 @@ import {
42
42
  } from "../web/constants.ts";
43
43
 
44
44
  // =============================================================================
45
- // Input-Schema = der normalisierte SubscriptionEvent (ohne tenantId, der
46
- // kommt aus event.user.tenantId — der webhook-handler setzt den
47
- // programmatic-user mit der vom Plugin aufgelösten tenantId).
45
+ // Input schema = the normalized SubscriptionEvent (without tenantId, which
46
+ // comes from event.user.tenantId — the webhook handler sets the
47
+ // programmatic user with the tenantId resolved by the plugin).
48
48
  // =============================================================================
49
49
 
50
50
  const eventTypeSchema = z.enum([
@@ -93,7 +93,7 @@ const STATE_EVENT_TYPES: ReadonlySet<string> = new Set([
93
93
  SubscriptionEventTypes.canceled,
94
94
  ]);
95
95
 
96
- function isLateStateEvent(
96
+ export function isLateStateEvent(
97
97
  storedEvents: readonly {
98
98
  readonly payload: unknown;
99
99
  readonly metadata: { readonly headers?: Record<string, unknown> };
@@ -111,7 +111,14 @@ function isLateStateEvent(
111
111
  // A stored value that does not parse can never prove a newer event; skip it instead of
112
112
  // failing every later delivery of this tenant's stream.
113
113
  try {
114
- return Temporal.Instant.compare(Temporal.Instant.from(storedAt), incoming) > 0;
114
+ const order = Temporal.Instant.compare(Temporal.Instant.from(storedAt), incoming);
115
+ if (order !== 0) return order > 0;
116
+ // Provider timestamps have second resolution: on a tie a stored cancellation wins over
117
+ // a non-terminal state, so a reordered same-second `updated` cannot revive a canceled one.
118
+ return (
119
+ propertyOf(stored.payload, "status") === SubscriptionStatuses.canceled &&
120
+ payload.status !== SubscriptionStatuses.canceled
121
+ );
115
122
  } catch {
116
123
  return false;
117
124
  }
@@ -208,9 +215,9 @@ export async function appendSubscriptionEvent(
208
215
  // read_subscriptions row with a single call. The subject is the
209
216
  // TENANT (tenantOwned) — tenant-destroy's subject-keys stage
210
217
  // (eraseSubjectKeys) erases exactly this key, so both copies become
211
- // genuinely unreadable (#800) once that stage runs, not just
218
+ // genuinely unreadable once that stage runs, not just
212
219
  // "encrypted at rest". No adapter configured = engine off (fields
213
- // stay plaintext, pre-#724-phase-C behavior) — mirrors how the
220
+ // stay plaintext) — mirrors how the
214
221
  // event-store-executor treats an absent piiKms().
215
222
  // ---------------------------------------------------------------
216
223
  const piiKms = configuredPiiSubjectKms();
@@ -268,9 +275,9 @@ export async function appendSubscriptionEvent(
268
275
  // Handler
269
276
  // =============================================================================
270
277
  //
271
- // SystemAdmin-only: dieser handler wird ausschließlich vom programmatic
272
- // webhook-handler aufgerufen (mit einem internal SystemUser), nie vom
273
- // Tenant-Admin direkt.
278
+ // SystemAdmin-only: this handler is called exclusively by the programmatic
279
+ // webhook handler (with an internal SystemUser), never directly by the
280
+ // tenant admin.
274
281
  // One subscription aggregate exists per tenant, but a tenant can legitimately hold two provider
275
282
  // subscriptions over time (a stale `incomplete` one superseded by a fresh checkout). A late or
276
283
  // retried event of the old subscription must not overwrite the row of the live one, so an event of a
@@ -3,16 +3,17 @@
3
3
  // the tier's live price server-side and opens a hosted checkout for it.
4
4
  // Only registered when `createBillingFoundationFeature` gets a `catalog`.
5
5
 
6
- import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
7
- import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
6
+ import type { HandlerContext, WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
7
+ import { FeatureDisabledError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
8
8
  import * as z from "zod";
9
9
  import {
10
10
  assertBillingEnabled,
11
11
  assertNoActiveSubscription,
12
12
  isNonEmptyStringArray,
13
13
  joinBaseUrl,
14
+ listOfferedCatalogProviders,
14
15
  openCheckout,
15
- resolveCatalogProvider,
16
+ type ResolvedProvider,
16
17
  } from "../checkout-core.ts";
17
18
  import {
18
19
  type ConsentPayload,
@@ -23,6 +24,36 @@ import {
23
24
  } from "../consumer-protection/consent-gate.ts";
24
25
  import { purchaseRolesOf, resolvePlanPrices } from "../plan-catalog.ts";
25
26
  import type { BillingPlanCatalog, ResolvedBillingFoundationOptions } from "../types.ts";
27
+ import { BILLING_FOUNDATION_FEATURE } from "../web/constants.ts";
28
+
29
+ /** The provider this checkout goes to: the named one if it is live, the only
30
+ * live one if the buyer had no choice, otherwise a client error. */
31
+ async function pickCheckoutProvider(
32
+ ctx: HandlerContext,
33
+ catalog: BillingPlanCatalog,
34
+ requestedName: string | undefined,
35
+ ): Promise<ResolvedProvider> {
36
+ const offered = await listOfferedCatalogProviders(ctx, catalog);
37
+ if (requestedName !== undefined) {
38
+ const chosen = offered.find((provider) => provider.name === requestedName);
39
+ if (!chosen) {
40
+ throw new UnprocessableError("provider_not_offered", {
41
+ i18nKey: "billing-foundation.errors.providerNotOffered",
42
+ message: `subscription-foundation: provider "${requestedName}" is not offered by this catalog right now`,
43
+ });
44
+ }
45
+ return chosen;
46
+ }
47
+ const [only, second] = offered;
48
+ if (!only) throw new FeatureDisabledError(BILLING_FOUNDATION_FEATURE, "start-plan-checkout");
49
+ if (second) {
50
+ throw new UnprocessableError("provider_required", {
51
+ i18nKey: "billing-foundation.errors.providerRequired",
52
+ message: "subscription-foundation: several providers are offered, name one as providerName",
53
+ });
54
+ }
55
+ return only;
56
+ }
26
57
 
27
58
  export function createStartPlanCheckoutHandler(
28
59
  options: ResolvedBillingFoundationOptions,
@@ -33,7 +64,7 @@ export function createStartPlanCheckoutHandler(
33
64
  throw new Error("start-plan-checkout: catalog.plans must not be empty");
34
65
  }
35
66
  const consumerProtection = options.consumerProtection;
36
- const tierSchema = z.object({ tier: z.enum(plans) });
67
+ const tierSchema = z.object({ tier: z.enum(plans), providerName: z.string().min(1).optional() });
37
68
  const schema = (
38
69
  consumerProtection
39
70
  ? tierSchema.extend({ consent: consentPayloadSchema.optional() })
@@ -43,16 +74,20 @@ export function createStartPlanCheckoutHandler(
43
74
  return {
44
75
  name: "start-plan-checkout",
45
76
  description:
46
- "Starts a hosted checkout for a plan tier when the tenant has no active subscription; the server picks price and redirect URLs.",
77
+ "Starts a hosted checkout for a plan tier when the tenant has no active subscription; the server picks price and redirect URLs. Name the provider with providerName when the catalog offers several.",
47
78
  schema,
48
79
  access: { roles: purchaseRolesOf(catalog) },
49
80
  handler: async (event, ctx) => {
50
81
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
51
- const payload = event.payload as { tier: string; consent?: ConsentPayload };
52
- const { name: providerName, plugin } = resolveCatalogProvider(
82
+ const payload = event.payload as {
83
+ tier: string;
84
+ providerName?: string;
85
+ consent?: ConsentPayload;
86
+ };
87
+ const { name: providerName, plugin } = await pickCheckoutProvider(
53
88
  ctx,
54
89
  catalog,
55
- "start-plan-checkout",
90
+ payload.providerName,
56
91
  );
57
92
  await assertBillingEnabled(ctx, plugin, "start-plan-checkout");
58
93
 
@@ -98,6 +133,7 @@ export function createStartPlanCheckoutHandler(
98
133
  tier: payload.tier,
99
134
  priceId: resolved.priceId,
100
135
  price: resolved.price,
136
+ providerName,
101
137
  });
102
138
  }
103
139
 
@@ -1,109 +1,99 @@
1
1
  // switch-plan — a tenant-admin/purchase-role switches an existing,
2
- // non-terminal subscription to a different plan tier via the provider's
3
- // own confirmation page. Only registered when `createBillingFoundationFeature`
4
- // gets a `catalog`.
2
+ // non-terminal subscription to a different plan tier. A provider with a hosted
3
+ // switch session answers `{ url }`; a provider that switches in-app (preview +
4
+ // apply) applies the switch right here and answers `{ applied: true }`.
5
+ // Only registered when `createBillingFoundationFeature` gets a `catalog`.
5
6
 
6
- import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
7
- import { ConflictError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
7
+ import type { HandlerContext, WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
8
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
8
9
  import * as z from "zod";
10
+ import { assertRedirectOrigins, isNonEmptyStringArray, joinBaseUrl } from "../checkout-core.ts";
9
11
  import {
10
- assertBillingEnabled,
11
- assertRedirectOrigins,
12
- isNonEmptyStringArray,
13
- joinBaseUrl,
14
- resolveCatalogProvider,
15
- } from "../checkout-core.ts";
16
- import { getSubscriptionForTenant } from "../get-subscription-for-tenant.ts";
17
- import { purchaseRolesOf, resolvePlanPrices } from "../plan-catalog.ts";
18
- import type { BillingFoundationOptions, BillingPlanCatalog } from "../types.ts";
19
- import { isSwitchableSubscriptionStatus } from "../web/constants.ts";
12
+ type ConsentPayload,
13
+ consentPayloadSchema,
14
+ prepareConsent,
15
+ recordConsent,
16
+ } from "../consumer-protection/consent-gate.ts";
17
+ import { purchaseRolesOf } from "../plan-catalog.ts";
18
+ import { type PreparedPlanSwitch, preparePlanSwitch } from "../plan-switch.ts";
19
+ import type { BillingPlanCatalog, ResolvedBillingFoundationOptions } from "../types.ts";
20
20
 
21
21
  export function createSwitchPlanHandler(
22
- options: BillingFoundationOptions,
22
+ options: ResolvedBillingFoundationOptions,
23
23
  catalog: BillingPlanCatalog,
24
24
  ): WriteHandlerDef {
25
25
  const plans = catalog.plans;
26
26
  if (!isNonEmptyStringArray(plans)) {
27
27
  throw new Error("switch-plan: catalog.plans must not be empty");
28
28
  }
29
- const schema = z.object({ tier: z.enum(plans) }).strict();
29
+ const consumerProtection = options.consumerProtection;
30
+ const tierSchema = z.object({ tier: z.enum(plans) });
31
+ const schema = (
32
+ consumerProtection
33
+ ? tierSchema.extend({ consent: consentPayloadSchema.optional() })
34
+ : tierSchema
35
+ ).strict();
30
36
 
31
37
  return {
32
38
  name: "switch-plan",
33
39
  description:
34
- "Opens the provider's confirmation page for switching the existing subscription to another plan tier.",
40
+ "Switches the existing subscription to another plan tier: opens the provider's confirmation page (answers { url }), or applies the switch in-app for providers that preview and apply themselves (answers { applied: true }; send consent when consumer protection is configured).",
35
41
  schema,
36
42
  access: { roles: purchaseRolesOf(catalog) },
37
43
  handler: async (event, ctx) => {
38
44
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
39
- const payload = event.payload as { tier: string };
40
- const { name: catalogProviderName, plugin } = resolveCatalogProvider(
41
- ctx,
42
- catalog,
43
- "switch-plan",
44
- );
45
- await assertBillingEnabled(ctx, plugin, "switch-plan");
45
+ const payload = event.payload as { tier: string; consent?: ConsentPayload };
46
+ const prepared = await preparePlanSwitch(ctx, catalog, payload.tier, "switch-plan");
47
+ const { plugin } = prepared.provider;
46
48
 
47
- const sub = await getSubscriptionForTenant(ctx, event.user.tenantId);
48
- if (!sub || !isSwitchableSubscriptionStatus(sub.status)) {
49
- throw new ConflictError({
50
- i18nKey: "billing-foundation.errors.noActiveSubscription",
51
- message:
52
- "tenant has no switchable subscription; use billing-foundation:write:start-plan-checkout to start one",
53
- });
54
- }
55
- if (sub.cancelAt !== null) {
56
- throw new ConflictError({
57
- i18nKey: "billing-foundation.errors.cancellationScheduled",
58
- message:
59
- "subscription has a scheduled cancellation; reactivate it via billing-foundation:write:create-portal-session before switching plans",
60
- });
61
- }
62
- if (payload.tier === sub.tier) {
63
- throw new ConflictError({
64
- i18nKey: "billing-foundation.errors.alreadyOnPlan",
65
- message: `tenant is already on plan "${payload.tier}"`,
66
- });
67
- }
68
- // providerSubscriptionId only exists at the subscription's own provider; after a
69
- // provider migration the catalog provider can differ, which is not a plan switch.
70
- if (sub.providerName !== catalogProviderName) {
71
- throw new ConflictError({
72
- i18nKey: "billing-foundation.errors.providerMismatch",
73
- message: `tenant's active subscription is on provider "${sub.providerName}", the catalog resolves provider "${catalogProviderName}" — switch-plan cannot switch across providers`,
74
- });
75
- }
76
- if (!plugin.createPlanSwitchSession) {
77
- throw new UnprocessableError("plan_switch_not_supported", {
78
- i18nKey: "billing-foundation.errors.planSwitchNotSupported",
79
- message: `subscription-foundation: provider "${sub.providerName}" has no createPlanSwitchSession-method`,
49
+ if (plugin.createPlanSwitchSession) {
50
+ const baseUrl = options.baseUrl ?? "";
51
+ const returnUrl = joinBaseUrl(baseUrl, catalog.returnPath ?? catalog.successPath);
52
+ assertRedirectOrigins([returnUrl], options.baseUrl);
53
+ const result = await plugin.createPlanSwitchSession(ctx, {
54
+ providerSubscriptionId: prepared.subscription.providerSubscriptionId,
55
+ targetPriceId: prepared.target.priceId,
56
+ allowedPriceIds: prepared.allowedPriceIds,
57
+ returnUrl,
80
58
  });
59
+ return { isSuccess: true as const, data: { url: result.url } };
81
60
  }
82
61
 
83
- const prices = await resolvePlanPrices(ctx, plugin, catalog);
84
- const target = prices.get(payload.tier);
85
- if (!target) {
86
- throw new UnprocessableError("price_unavailable", {
87
- i18nKey: "billing-foundation.errors.priceUnavailable",
88
- message: `subscription-foundation: no live price resolved for tier "${payload.tier}"`,
89
- });
90
- }
91
- const allowedPriceIds = [...prices.values()]
92
- .filter((p): p is NonNullable<typeof p> => p !== null)
93
- .map((p) => p.priceId);
94
-
95
- const baseUrl = options.baseUrl ?? "";
96
- const returnUrl = joinBaseUrl(baseUrl, catalog.returnPath ?? catalog.successPath);
97
- assertRedirectOrigins([returnUrl], options.baseUrl);
98
-
99
- const result = await plugin.createPlanSwitchSession(ctx, {
100
- providerSubscriptionId: sub.providerSubscriptionId,
101
- targetPriceId: target.priceId,
102
- allowedPriceIds,
103
- returnUrl,
104
- });
105
-
106
- return { isSuccess: true as const, data: { url: result.url } };
62
+ return applyInApp(prepared, payload, ctx, options);
107
63
  },
108
64
  };
109
65
  }
66
+
67
+ // Consent is recorded before the provider call: the apply carries the consentId
68
+ // as provider metadata, and a failing apply rolls the whole handler back.
69
+ async function applyInApp(
70
+ prepared: PreparedPlanSwitch,
71
+ payload: { readonly tier: string; readonly consent?: ConsentPayload },
72
+ ctx: HandlerContext,
73
+ options: ResolvedBillingFoundationOptions,
74
+ ) {
75
+ const { plugin, name: providerName } = prepared.provider;
76
+ if (!plugin.applyPlanSwitch) {
77
+ throw new InternalError({
78
+ message: `billing-foundation: provider "${providerName}" has neither createPlanSwitchSession nor applyPlanSwitch`,
79
+ });
80
+ }
81
+ const consent = options.consumerProtection
82
+ ? await prepareConsent(ctx, options.consumerProtection, payload.consent, "subscription")
83
+ : undefined;
84
+ if (consent) {
85
+ await recordConsent(ctx, consent, {
86
+ tier: payload.tier,
87
+ priceId: prepared.target.priceId,
88
+ price: prepared.target.price,
89
+ providerName,
90
+ });
91
+ }
92
+ await plugin.applyPlanSwitch(ctx, {
93
+ providerSubscriptionId: prepared.subscription.providerSubscriptionId,
94
+ targetPriceId: prepared.target.priceId,
95
+ tenantId: ctx.user.tenantId,
96
+ ...(consent && { consentId: consent.consentId }),
97
+ });
98
+ return { isSuccess: true as const, data: { applied: true as const } };
99
+ }
@@ -72,6 +72,18 @@ function syncProviderEventId(
72
72
  return `sync:${createHash("sha256").update(hashInput).digest("hex")}`;
73
73
  }
74
74
 
75
+ // The retrieval time stands in for the provider's event time, so a webhook emitted before
76
+ // this snapshot cannot roll the synced state back later. Provider webhook times are
77
+ // whole-second and come from the provider's clock, so the stamp is floored and shifted back:
78
+ // a genuine webhook right after the sync must not look older than the snapshot.
79
+ const SYNC_SNAPSHOT_EVENT_TIME_MARGIN_SECONDS = 5;
80
+
81
+ function snapshotEventTime(): Temporal.Instant {
82
+ return Temporal.Now.instant()
83
+ .round({ smallestUnit: "second", roundingMode: "floor" })
84
+ .subtract({ seconds: SYNC_SNAPSHOT_EVENT_TIME_MARGIN_SECONDS });
85
+ }
86
+
75
87
  export const syncSubscriptionHandler: WriteHandlerDef = {
76
88
  name: "sync-subscription",
77
89
  agent: { expose: false },
@@ -143,11 +155,12 @@ export const syncSubscriptionHandler: WriteHandlerDef = {
143
155
  tier: snapshot.tier,
144
156
  currentPeriodEndIso: snapshot.currentPeriodEnd,
145
157
  cancelAtIso: snapshot.cancelAt,
158
+ providerEventAtIso: snapshotEventTime().toString(),
146
159
  });
147
160
 
148
161
  return {
149
162
  isSuccess: true as const,
150
- data: (appendResult.duplicate
163
+ data: (appendResult.duplicate || appendResult.stale
151
164
  ? { synced: false, reason: "unchanged" }
152
165
  : { synced: true }) satisfies SyncSubscriptionResult,
153
166
  };
@@ -25,6 +25,20 @@ export const BILLING_FOUNDATION_I18N: Readonly<Record<string, LocalizedString>>
25
25
  "billing-foundation.plans.everyInterval.year": { en: "every {count} years" },
26
26
  "billing-foundation.plans.choose": { en: "Choose {plan}" },
27
27
  "billing-foundation.plans.switch": { en: "Switch to {plan}" },
28
+ "billing-foundation.plans.planSwitchRequested": {
29
+ en: "Plan switch requested. It becomes active once the payment is confirmed.",
30
+ },
31
+ "billing-foundation.planSwitch.title": { en: "Confirm plan switch" },
32
+ "billing-foundation.planSwitch.summary": { en: "{from} to {to}" },
33
+ "billing-foundation.planSwitch.dueNow": { en: "Due now: {amount}" },
34
+ "billing-foundation.planSwitch.includesTax": { en: "including {tax} VAT" },
35
+ "billing-foundation.planSwitch.credit": { en: "Credit for the unused time: {amount}" },
36
+ "billing-foundation.planSwitch.recurring": { en: "New recurring price: {amount}" },
37
+ "billing-foundation.planSwitch.effective": { en: "Effective immediately" },
38
+ "billing-foundation.planSwitch.pendingNote": {
39
+ en: "The switch becomes active after payment confirmation.",
40
+ },
41
+ "billing-foundation.planSwitch.order": { en: "Switch with obligation to pay" },
28
42
  "billing-foundation.plans.manage": { en: "Manage subscription" },
29
43
  "billing-foundation.plans.reactivate": { en: "Reactivate subscription" },
30
44
  "billing-foundation.plans.cancelScheduled": {
@@ -70,6 +84,8 @@ export const BILLING_FOUNDATION_I18N: Readonly<Record<string, LocalizedString>>
70
84
  "billing-foundation.consent.link.withdrawal": { en: "Withdrawal policy" },
71
85
  "billing-foundation.consent.link.privacy": { en: "Privacy policy" },
72
86
  "billing-foundation.consent.order": { en: "Order with obligation to pay" },
87
+ "billing-foundation.plans.priceFrom": { en: "from" },
88
+ "billing-foundation.consent.chooseProvider": { en: "Payment provider" },
73
89
  "billing-foundation.consent.back": { en: "Back" },
74
90
  "billing-foundation.cancel.open": { en: "Cancel contract here" },
75
91
  "billing-foundation.cancel.title": { en: "Cancel contract" },
@@ -130,6 +146,12 @@ export const BILLING_FOUNDATION_I18N: Readonly<Record<string, LocalizedString>>
130
146
  "billing-foundation.errors.providerMismatch": {
131
147
  en: "This tenant's subscription is on a different provider than the plan catalog. Contact support to migrate.",
132
148
  },
149
+ "billing-foundation.errors.providerNotOffered": {
150
+ en: "This payment provider is not available right now.",
151
+ },
152
+ "billing-foundation.errors.providerRequired": {
153
+ en: "Choose a payment provider to continue.",
154
+ },
133
155
  "billing-foundation.errors.priceUnavailable": {
134
156
  en: "This plan's price is temporarily unavailable.",
135
157
  },
@@ -62,10 +62,12 @@ export {
62
62
  type BillingPlanPrice,
63
63
  type BillingPlansResult,
64
64
  type BillingPlanView,
65
+ type BillingProviderView,
65
66
  type ConsumerProtectionOptions,
66
67
  KNOWN_RECURRING_INTERVALS,
67
68
  type LegalLinkSet,
68
69
  type PaymentEvent,
70
+ type PlanSwitchPreview,
69
71
  type ProviderPrice,
70
72
  type ProviderSubscriptionSnapshot,
71
73
  type RecurringInterval,