@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,29 +1,29 @@
1
1
  /// <reference types="temporal-polyfill/global" preserve="true" />
2
- // Plugin-Contract + Domain-Types der subscription-foundation. Provider-
3
- // Plugins (subscription-stripe, subscription-mollie, ...) implementieren
4
- // `SubscriptionProviderPlugin` und registrieren via
2
+ // Plugin contract and domain types for subscription-foundation. Provider
3
+ // plugins (subscription-stripe, subscription-mollie, ...) implement
4
+ // `SubscriptionProviderPlugin` and register via
5
5
  // `r.useExtension("subscriptionProvider", "<name>", { build })`.
6
6
  //
7
7
  // **Two-phase plugin contract:**
8
- // 1. **Pre-tenant-resolution:** `verifyAndParseWebhook` läuft BEVOR
9
- // ein Tenant aus dem Event aufgelöst ist — kein HandlerContext
10
- // verfügbar. Das **Webhook-secret ist app-wide** (App-Owner's
11
- // Stripe-/PayPal-Account, nicht Tenant-Sache). Damit ein Plugin den
12
- // secret ZUR LAUFFZEIT aus dem secrets-Feature lesen kann (statt aus
13
- // einem mount-time-Closure), reicht der webhook-handler einen
14
- // optionalen system-scoped `SecretsContext` als 3. Arg durch — der
15
- // Plugin liest seine app-wide-Secrets daraus un-audited unter
16
- // SYSTEM_TENANT_ID. Plugins, die ihre Keys weiter aus einem Closure
17
- // halten (z.B. mollie), ignorieren den Param → backward-compatible.
8
+ // 1. **Pre-tenant-resolution:** `verifyAndParseWebhook` runs BEFORE
9
+ // a tenant is resolved from the event, so no HandlerContext is
10
+ // available. The **webhook secret is app-wide** (the App Owner's
11
+ // Stripe-/PayPal-account, not tenant-specific). To let a plugin read
12
+ // the secret from the secrets feature AT RUNTIME (instead of a
13
+ // mount-time closure), the webhook handler passes an optional
14
+ // system-scoped `SecretsContext` as the third argument. The plugin
15
+ // reads its app-wide secrets from it without auditing under
16
+ // SYSTEM_TENANT_ID. Plugins that keep their keys in a closure
17
+ // (e.g. mollie) ignore the parameter → backward-compatible.
18
18
  // 2. **Post-tenant-resolution:** `createPortalSession` +
19
- // `cancelSubscription` werden aus regulären write-handlern
20
- // gerufen mit voll-aufgelöstem HandlerContext. Plugin kann
21
- // hier ctx.config + ctx.secrets nutzen für tenant-spezifische
22
- // Konfiguration (z.B. tenant-eigene customer-id-mapping).
19
+ // `cancelSubscription` are called from regular write handlers with
20
+ // a fully resolved HandlerContext. The plugin can use ctx.config +
21
+ // ctx.secrets for tenant-specific configuration (e.g. tenant-owned
22
+ // customer-id mappings).
23
23
  //
24
- // Foundation nutzt nur den common-subset der Provider-Funktionalität —
25
- // proration, multi-currency, coupons etc. bleiben provider-spezifisch
26
- // und sind über den Customer-Portal-Link erreichbar.
24
+ // Foundation uses only the common subset of provider functionality;
25
+ // proration, multi-currency, coupons, etc. remain provider-specific and
26
+ // are available through the Customer Portal link.
27
27
 
28
28
  import type { TenantDb } from "@cosmicdrift/kumiko-framework/db";
29
29
  import type { HandlerContext, TenantId } from "@cosmicdrift/kumiko-framework/engine";
@@ -41,12 +41,12 @@ import {
41
41
  import type { ConsentLocale } from "./web/consumer-protection/consent-locale.ts";
42
42
 
43
43
  // =============================================================================
44
- // Normalisierter Webhook-Event
44
+ // Normalized Webhook Event
45
45
  // =============================================================================
46
46
  //
47
- // Der Plugin parsed den raw provider-payload und liefert diese Form
48
- // zurück. Foundation kennt KEINE Stripe-/Mollie-types direkt — der
49
- // Plugin abstrahiert.
47
+ // The plugin parses the raw provider payload and returns this shape.
48
+ // Foundation has no direct knowledge of Stripe-/Mollie-types; the plugin
49
+ // provides the abstraction.
50
50
 
51
51
  export type SubscriptionEvent = {
52
52
  /** Discriminator against PaymentEvent. Optional (instead of required) so
@@ -55,32 +55,31 @@ export type SubscriptionEvent = {
55
55
  * `parsed.kind === BillingEventKinds.payment` correctly, because only
56
56
  * PaymentEvent["kind"] can take that value. */
57
57
  readonly kind?: typeof BillingEventKinds.subscription;
58
- /** Provider-eigene Event-ID — UNIQUE-key für Idempotency.
59
- * Stripe: "evt_..."; Mollie: payment-id oder subscription-id. */
58
+ /** Provider-owned event ID — UNIQUE key for idempotency.
59
+ * Stripe: "evt_..."; Mollie: payment-id or subscription-id. */
60
60
  readonly providerEventId: string;
61
61
  /** Discriminator — welcher Plugin diesen Event geliefert hat. */
62
62
  readonly providerName: string;
63
63
  /** Normalisierter Event-Type. Plugin filtert provider-spezifische
64
64
  * Sub-Types raus (Stripe hat ~80 Event-types, wir kennen 5). */
65
65
  readonly type: SubscriptionEventType;
66
- /** Plattform-Tenant-ID (provider-customer-metadata oder lookup
67
- * via providerCustomerId). Plugin macht die resolution. */
66
+ /** Platform tenant ID (provider-customer metadata or lookup via
67
+ * providerCustomerId). The plugin performs the resolution. */
68
68
  readonly tenantId: string;
69
69
  /** Consent id echoed back from checkout metadata; omitted when absent or
70
70
  * malformed. */
71
71
  readonly consentId?: string;
72
- /** Provider-eigene customer-id für späteren Lookup bei events
73
- * ohne metadata. */
72
+ /** Provider-owned customer ID for later lookup when events lack
73
+ * metadata. */
74
74
  readonly providerCustomerId: string;
75
75
  /** Provider-eigene subscription-id. */
76
76
  readonly providerSubscriptionId: string;
77
77
  /** Normalisierter status. */
78
78
  readonly status: SubscriptionStatus;
79
- /** Resolved tier — Plugin liest die price-id aus dem event und
80
- * mapped via plugin-eigenem `<plugin>:config:price-to-tier` auf
81
- * einen tier-name. Wenn der price-id im Mapping fehlt, returnt
82
- * der Plugin null aus verifyAndParseWebhook (= "unknown event,
83
- * ignore"). */
79
+ /** Resolved tier — the plugin reads the price ID from the event and
80
+ * maps it through the plugin's `<plugin>:config:price-to-tier` to a
81
+ * tier name. If the price ID is absent from the mapping, the plugin
82
+ * returns null from verifyAndParseWebhook (= "unknown event, ignore"). */
84
83
  readonly tier: string;
85
84
  /** ISO-timestamp wann die aktuelle Billing-Period endet. */
86
85
  readonly currentPeriodEnd: string;
@@ -137,6 +136,9 @@ export type PaymentEvent = {
137
136
  // =============================================================================
138
137
 
139
138
  export type SubscriptionProviderPlugin = {
139
+ /** Human-readable provider name for the plan panel's provider switch. */
140
+ readonly displayName?: string;
141
+
140
142
  /**
141
143
  * Verify webhook signature + parse provider-event into normalized
142
144
  * form. **Pre-tenant-resolution** — no HandlerContext, because at
@@ -172,32 +174,32 @@ export type SubscriptionProviderPlugin = {
172
174
  ) => Promise<SubscriptionEvent | PaymentEvent | null>;
173
175
 
174
176
  /**
175
- * **Post-tenant-resolution** — wird aus dem
176
- * `create-checkout-session`-write-handler gerufen. Plugin baut
177
- * eine provider-eigene checkout-session und returnt die hosted-
178
- * page-URL — Tenant-Admin wird dorthin redirected, schließt den
179
- * Bezahl-Flow ab, Provider sendet `subscription.created`-webhook
180
- * mit `metadata.tenantId` zurück.
177
+ * **Post-tenant-resolution** — called from the
178
+ * `create-checkout-session` write handler. The plugin builds a
179
+ * provider-specific checkout session and returns its hosted-page URL;
180
+ * the tenant admin is redirected there to complete payment, and the
181
+ * provider sends a `subscription.created` webhook with
182
+ * `metadata.tenantId`.
181
183
  *
182
- * Optional: Apple-IAP hat keinen Web-Checkout (alles in der App).
183
- * Apps die ausschließlich Apple-IAP nutzen lassen das weg.
184
+ * Optional: Apple-IAP has no web checkout (everything happens in the app).
185
+ * Apps that use only Apple-IAP omit this field.
184
186
  */
185
187
  readonly createCheckoutSession?: (
186
188
  ctx: HandlerContext,
187
189
  options: {
188
- /** Provider-eigene price/plan-ID die der Endkunde abonniert. */
190
+ /** Provider-owned price/plan ID that the customer subscribes to. */
189
191
  readonly priceId: string;
190
- /** Plattform-Tenant-ID — landet als metadata im checkout-session
191
- * damit der subsequent webhook den tenant resolved (siehe
192
- * `verifyAndParseWebhook`'s metadata.tenantId-lookup). */
192
+ /** Platform tenant ID — stored as metadata in the checkout session
193
+ * so the subsequent webhook can resolve the tenant (see the
194
+ * `verifyAndParseWebhook` metadata.tenantId lookup). */
193
195
  readonly tenantId: string;
194
- /** Wo der Endkunde nach erfolgreichem checkout landed. */
196
+ /** Where the customer lands after a successful checkout. */
195
197
  readonly successUrl: string;
196
- /** Wo der Endkunde landed wenn er abbricht. */
198
+ /** Where the customer lands if they cancel. */
197
199
  readonly cancelUrl: string;
198
- /** Optional: existierende provider-customer-id wenn der Tenant
199
- * schon einen Account beim Provider hat. Sonst legt der Provider
200
- * beim checkout einen neuen customer an. */
200
+ /** Optional: existing provider customer ID when the tenant already
201
+ * has an account with the provider. Otherwise, the provider
202
+ * creates a new customer during checkout. */
201
203
  readonly providerCustomerId?: string;
202
204
  /** Checkout mode. `"subscription"` (default) starts a recurring
203
205
  * billing flow; `"payment"` a one-off checkout (e.g. pay-per-use
@@ -215,17 +217,17 @@ export type SubscriptionProviderPlugin = {
215
217
  ) => Promise<{ readonly url: string }>;
216
218
 
217
219
  /**
218
- * **Post-tenant-resolution** — wird aus einem write-handler gerufen
219
- * mit voll-aufgelöstem ctx. Erstellt einen self-service Portal-Link.
220
+ * **Post-tenant-resolution** — called from a write handler with a
221
+ * fully resolved ctx. Creates a self-service Portal link.
220
222
  * Stripe: customer-portal-session, Mollie: hosted-management-page.
221
- * Tenant-Admin klickt darauf um Subscription selbst zu verwalten
222
- * (cancel, payment-method, ...).
223
+ * The tenant admin uses it to manage the subscription themselves
224
+ * (cancel, payment method, ...).
223
225
  *
224
- * Optional weil nicht jeder Provider einen Portal-Pattern hat
225
- * (Apple-IAP managed Subs in der Apple-App, kein Web-Portal).
226
- * Plugin der das nicht supported kann das Field weglassen — foundation
227
- * returnt dann "portal_not_supported"-error wenn ein Tenant-Admin
228
- * den Portal-Link anfordert.
226
+ * Optional because not every provider has a Portal pattern
227
+ * (Apple-IAP subscriptions are managed in the Apple app, with no web
228
+ * Portal). A plugin that does not support this can omit the field;
229
+ * the foundation then returns a "portal_not_supported" error when a
230
+ * tenant admin requests the Portal link.
229
231
  */
230
232
  readonly createPortalSession?: (
231
233
  ctx: HandlerContext,
@@ -236,10 +238,10 @@ export type SubscriptionProviderPlugin = {
236
238
  ) => Promise<{ readonly url: string }>;
237
239
 
238
240
  /**
239
- * Optional: Cancel-aus-der-App-API. Wenn nicht implementiert, kann
240
- * der Tenant nur über den Customer-Portal-Link cancellen (oder gar
241
- * nicht, wenn auch createPortalSession fehlt — dann ist
242
- * Cancel-Flow Provider-Dashboard-only).
241
+ * Optional: in-app cancellation API. If not implemented, the tenant
242
+ * can cancel only through the Customer Portal link (or not at all if
243
+ * createPortalSession is also missing — the cancellation flow is then
244
+ * provider-dashboard-only).
243
245
  */
244
246
  readonly cancelSubscription?: (
245
247
  ctx: HandlerContext,
@@ -314,6 +316,32 @@ export type SubscriptionProviderPlugin = {
314
316
  },
315
317
  ) => Promise<{ readonly url: string }>;
316
318
 
319
+ /**
320
+ * In-app plan switch, step 1: what the switch would cost right now. Together
321
+ * with `applyPlanSwitch` an alternative to the hosted `createPlanSwitchSession`.
322
+ */
323
+ readonly previewPlanSwitch?: (
324
+ ctx: HandlerContext,
325
+ options: {
326
+ readonly providerSubscriptionId: string;
327
+ readonly targetPriceId: string;
328
+ },
329
+ ) => Promise<PlanSwitchPreview>;
330
+
331
+ /**
332
+ * In-app plan switch, step 2: performs the switch at the provider. The
333
+ * state change itself arrives later through the webhook.
334
+ */
335
+ readonly applyPlanSwitch?: (
336
+ ctx: HandlerContext,
337
+ options: {
338
+ readonly providerSubscriptionId: string;
339
+ readonly targetPriceId: string;
340
+ readonly tenantId: string;
341
+ readonly consentId?: string;
342
+ },
343
+ ) => Promise<void>;
344
+
317
345
  /**
318
346
  * Fetches the live provider-side state of one subscription, for the
319
347
  * `sync-subscriptions` backfill job — catches drift (e.g. a `cancel_at`
@@ -331,6 +359,18 @@ export type SubscriptionProviderPlugin = {
331
359
  ) => Promise<ProviderSubscriptionSnapshot | null>;
332
360
  };
333
361
 
362
+ export type PlanSwitchPreview = {
363
+ /** Charged immediately, smallest currency unit, tax included. */
364
+ readonly amountDueNow: number;
365
+ /** Tax part of `amountDueNow`; null when the provider reports none. */
366
+ readonly tax: number | null;
367
+ /** Lower-case ISO currency code. */
368
+ readonly currency: string;
369
+ /** Credit for the unused time on the old plan, smallest currency unit. */
370
+ readonly credit: number;
371
+ readonly effectiveAt: string;
372
+ };
373
+
334
374
  export type ProviderSubscriptionSnapshot = {
335
375
  readonly providerCustomerId: string;
336
376
  readonly providerSubscriptionId: string;
@@ -400,7 +440,8 @@ export type BillingPlanCatalog<TTier extends string = string> = {
400
440
  * `baseUrl` itself when unset) and by `switch-plan` after a plan switch
401
441
  * (defaults to `successPath` there instead). */
402
442
  readonly returnPath?: string;
403
- /** Defaults to the only registered provider exposing `priceToTier`. */
443
+ /** Restricts the catalog to one provider; defaults to every registered
444
+ * provider exposing `priceToTier`. */
404
445
  readonly providerName?: string;
405
446
  /** Rendered as the last card, no price, visible to `viewRoles` regardless of `purchaseRoles`. */
406
447
  readonly contact?: BillingPlanContactCard;
@@ -430,6 +471,19 @@ export type ConsumerProtectionOptions = {
430
471
  readonly termsTextBlock: string;
431
472
  /** Per-locale VAT note; must carry at least `de` and `en`. */
432
473
  readonly vatNote: Readonly<Record<string, string>>;
474
+ /** Per-provider texts keyed by provider name, e.g. for a merchant-of-record
475
+ * provider that issues the invoice and VAT itself. `vatNote` here replaces
476
+ * the global one in that provider's confirmation mail; `checkoutNote` is
477
+ * shown under the provider choice. de and en are required for each text. */
478
+ readonly providerTexts?: Readonly<
479
+ Record<
480
+ string,
481
+ {
482
+ readonly vatNote?: Readonly<Record<string, string>>;
483
+ readonly checkoutNote?: Readonly<Record<string, string>>;
484
+ }
485
+ >
486
+ >;
433
487
  readonly operatorEmail: string;
434
488
  /** Where the public § 312k declaration is reachable. `"tenant-host"`
435
489
  * (default): on a host that resolves a tenant. `"platform"`: also on a
@@ -491,6 +545,23 @@ export type BillingPlanView = {
491
545
  readonly action: BillingPlanAction;
492
546
  };
493
547
 
548
+ export type BillingProviderView = {
549
+ readonly name: string;
550
+ readonly displayName: string;
551
+ readonly note?: Readonly<Record<string, string>>;
552
+ readonly plans: readonly BillingPlanView[];
553
+ };
554
+
555
+ export function canSwitchPlansInApp(plugin: SubscriptionProviderPlugin): boolean {
556
+ return plugin.previewPlanSwitch !== undefined && plugin.applyPlanSwitch !== undefined;
557
+ }
558
+
559
+ /** A provider switches plans via a hosted session or via in-app preview + apply. */
560
+ export function canSwitchPlans(plugin: SubscriptionProviderPlugin | null): boolean {
561
+ if (plugin === null) return false;
562
+ return plugin.createPlanSwitchSession !== undefined || canSwitchPlansInApp(plugin);
563
+ }
564
+
494
565
  export type BillingPlansResult = {
495
566
  readonly enabled: boolean;
496
567
  readonly currentTier: {
@@ -507,6 +578,13 @@ export type BillingPlansResult = {
507
578
  } | null;
508
579
  readonly canPurchase: boolean;
509
580
  readonly plans: readonly BillingPlanView[];
581
+ /** Present when more than one provider is offered, or the single offered
582
+ * provider has a `providerTexts` checkout note. `plans` above are the first
583
+ * entry's plans. */
584
+ readonly providers?: readonly BillingProviderView[];
585
+ /** True when the subscription's provider switches plans in-app (preview +
586
+ * confirm) instead of on a hosted page. */
587
+ readonly inAppPlanSwitch?: true;
510
588
  /** Present only when `catalog.contact` is configured. */
511
589
  readonly contact?: BillingPlanContactCard;
512
590
  /** Present only when `consumerProtection` is configured. */
@@ -28,6 +28,24 @@ function isRootRelativeOrHttpsUrl(link: string): boolean {
28
28
  }
29
29
  }
30
30
 
31
+ function validateProviderTexts(cp: ConsumerProtectionOptions): void {
32
+ for (const [providerName, texts] of Object.entries(cp.providerTexts ?? {})) {
33
+ for (const [textName, text] of [
34
+ ["vatNote", texts.vatNote],
35
+ ["checkoutNote", texts.checkoutNote],
36
+ ] as const) {
37
+ if (text === undefined) continue;
38
+ for (const locale of ["de", "en"] as const) {
39
+ if (!text[locale]?.trim()) {
40
+ throw new Error(
41
+ `createBillingFoundationFeature: consumerProtection.providerTexts.${providerName}.${textName} needs a non-empty "${locale}" entry.`,
42
+ );
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+
31
49
  function validateConsumerProtection(cp: ConsumerProtectionOptions): void {
32
50
  if (cp.termsTextBlock.trim() === "") {
33
51
  throw new Error("createBillingFoundationFeature: consumerProtection.termsTextBlock is empty.");
@@ -39,6 +57,7 @@ function validateConsumerProtection(cp: ConsumerProtectionOptions): void {
39
57
  );
40
58
  }
41
59
  }
60
+ validateProviderTexts(cp);
42
61
  if (!z.email().safeParse(cp.operatorEmail).success) {
43
62
  throw new Error(
44
63
  `createBillingFoundationFeature: consumerProtection.operatorEmail "${cp.operatorEmail}" is not a valid email address.`,
@@ -24,24 +24,13 @@ import {
24
24
  SubscriptionFoundationQueries,
25
25
  SubscriptionStatuses,
26
26
  } from "./constants.ts";
27
+ import { resolveConsentLocale } from "./consumer-protection/consent-locale.ts";
27
28
  import { localizedParams } from "./localized-params.ts";
29
+ import { cheapestPrice, intervalKey, pricesDiffer, renewalKey, tierPrices } from "./plan-price.ts";
30
+ import { PlanSwitchConfirmDialog } from "./plan-switch-confirm-dialog.tsx";
28
31
 
29
32
  type UseTranslation = ReturnType<typeof useTranslation>;
30
33
 
31
- function intervalKey(price: BillingPlanView["price"]): string | undefined {
32
- if (price === null || price.interval === null) return undefined;
33
- return price.intervalCount !== null && price.intervalCount > 1
34
- ? `billing-foundation.plans.everyInterval.${price.interval}`
35
- : `billing-foundation.plans.perInterval.${price.interval}`;
36
- }
37
-
38
- function renewalKey(price: BillingPlanView["price"]): string | undefined {
39
- if (price === null || price.interval === null) return undefined;
40
- return price.intervalCount !== null && price.intervalCount > 1
41
- ? `billing-foundation.consent.renewsEvery.${price.interval}`
42
- : `billing-foundation.consent.renews.${price.interval}`;
43
- }
44
-
45
34
  /** Which action the plan "would" offer if it weren't blocked — mirrors
46
35
  * plan-catalog.ts's own decision tree, since `unavailable` collapses both
47
36
  * "would checkout" and "would switch" into one action and the disabled CTA
@@ -78,10 +67,10 @@ function manageOrReactivateAction(
78
67
  };
79
68
  }
80
69
 
81
- /** `!canPurchase` hides every CTA outright (spec: "keine CTAs, Hinweis
82
- * purchaseNotAllowed") — distinct from the `unavailable` action's disabled
83
- * CTA, which still shows a button (e.g. for a plan whose price failed to
84
- * load) so this check runs before the general unavailable-fallback. */
70
+ /** `!canPurchase` hides every CTA outright (see `purchaseNotAllowed`) — distinct
71
+ * from the `unavailable` action's disabled CTA, which still shows a button
72
+ * (for example, when a plan's price failed to load), so this check runs
73
+ * before the general unavailable fallback. */
85
74
 
86
75
  function planCta(
87
76
  plan: BillingPlanView,
@@ -135,12 +124,25 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
135
124
  const [redirecting, setRedirecting] = useState(false);
136
125
  const [consentTier, setConsentTier] = useState<string | null>(null);
137
126
  const [cancelOpen, setCancelOpen] = useState(false);
127
+ const [chosenProvider, setChosenProvider] = useState<string | null>(null);
128
+ const [switchTier, setSwitchTier] = useState<string | null>(null);
129
+ const [switchRequested, setSwitchRequested] = useState(false);
130
+
131
+ // Only present when the tenant can choose (several offered providers) or
132
+ // the single offered provider has a note; absent means the server decides.
133
+ const providers = query.data?.providers;
134
+ const activeProvider = providers?.find((p) => p.name === chosenProvider) ?? providers?.[0];
135
+ const providerArg = activeProvider ? { providerName: activeProvider.name } : {};
138
136
 
139
137
  async function orderWithConsent(
140
138
  tier: string,
141
- consent: CheckoutConsentPayload,
139
+ consent: CheckoutConsentPayload | undefined,
142
140
  ): Promise<WriteResult<{ readonly url: string }>> {
143
- const result = await checkoutMutation.mutate({ tier, consent });
141
+ const result = await checkoutMutation.mutate({
142
+ tier,
143
+ ...(consent !== undefined && { consent }),
144
+ ...providerArg,
145
+ });
144
146
  if (result.isSuccess) {
145
147
  setRedirecting(true);
146
148
  window.location.assign(result.data.url);
@@ -149,13 +151,23 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
149
151
  }
150
152
 
151
153
  async function handleAction(tier: string, kind: "checkout" | "switch"): Promise<void> {
152
- if (kind === "checkout" && query.data?.consumerProtection !== undefined) {
154
+ if (
155
+ kind === "checkout" &&
156
+ (query.data?.consumerProtection !== undefined || (providers?.length ?? 0) > 1)
157
+ ) {
158
+ setChosenProvider(null);
153
159
  setConsentTier(tier);
154
160
  return;
155
161
  }
162
+ if (kind === "switch" && query.data?.inAppPlanSwitch) {
163
+ setSwitchTier(tier);
164
+ return;
165
+ }
156
166
  setRedirecting(true);
157
- const mutation = kind === "checkout" ? checkoutMutation : switchMutation;
158
- const result = await mutation.mutate({ tier });
167
+ const result =
168
+ kind === "checkout"
169
+ ? await checkoutMutation.mutate({ tier, ...providerArg })
170
+ : await switchMutation.mutate({ tier });
159
171
  if (result.isSuccess) {
160
172
  window.location.assign(result.data.url);
161
173
  } else {
@@ -189,6 +201,9 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
189
201
  }
190
202
  if (!query.data) return null;
191
203
  const result: BillingPlansResult = query.data;
204
+ // Cards show the first provider's price; the dialog then follows the chosen one.
205
+ const shownPlans = providers?.[0]?.plans ?? result.plans;
206
+ const dialogPlans = activeProvider?.plans ?? result.plans;
192
207
 
193
208
  // The consent dialog shows the checkout error itself.
194
209
  const mutationError =
@@ -199,7 +214,11 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
199
214
  result.subscription !== null && !result.subscription.terminal && result.canPurchase;
200
215
 
201
216
  function toCardProps(plan: BillingPlanView) {
202
- const key = intervalKey(plan.price);
217
+ const comparable =
218
+ providers !== undefined && providers.length > 1 ? tierPrices(providers, plan.tier) : [];
219
+ const fromPrice = pricesDiffer(comparable) ? cheapestPrice(comparable) : undefined;
220
+ const shownPrice = fromPrice ?? plan.price;
221
+ const key = intervalKey(shownPrice);
203
222
  return {
204
223
  title: t(plan.labelKey),
205
224
  description:
@@ -214,13 +233,14 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
214
233
  price:
215
234
  !result.enabled && plan.isCurrent
216
235
  ? undefined
217
- : plan.price === null
236
+ : shownPrice === null
218
237
  ? null
219
238
  : {
220
- amount: formatMoney(plan.price.unitAmount, plan.price.currency, locale),
239
+ amount: formatMoney(shownPrice.unitAmount, shownPrice.currency, locale),
240
+ ...(fromPrice !== undefined && { prefix: t("billing-foundation.plans.priceFrom") }),
221
241
  ...(key !== undefined && {
222
242
  period: t(key, {
223
- count: new Intl.NumberFormat(locale).format(plan.price.intervalCount ?? 1),
243
+ count: new Intl.NumberFormat(locale).format(shownPrice.intervalCount ?? 1),
224
244
  }),
225
245
  }),
226
246
  },
@@ -239,12 +259,41 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
239
259
  // rendered plans is the current one, a synthetic current-tier card (no
240
260
  // price, no cta) keeps it visible regardless of enabled/disabled.
241
261
  const consentPlan =
242
- consentTier !== null ? result.plans.find((plan) => plan.tier === consentTier) : undefined;
262
+ consentTier !== null ? dialogPlans.find((plan) => plan.tier === consentTier) : undefined;
263
+ const switchPlan =
264
+ switchTier !== null ? shownPlans.find((plan) => plan.tier === switchTier) : undefined;
243
265
  const consumerProtection = result.consumerProtection;
244
- const currentTierInPlans = result.plans.some((plan) => plan.isCurrent);
245
- const visiblePlans = result.enabled
246
- ? result.plans
247
- : result.plans.filter((plan) => plan.isCurrent);
266
+ const noteLocale = resolveConsentLocale(locale);
267
+ const noteOf = (provider: { readonly note?: Readonly<Record<string, string>> }) =>
268
+ provider.note?.[noteLocale] ?? provider.note?.["en"];
269
+ const singleProviderNote =
270
+ providers !== undefined && providers.length === 1 && providers[0] !== undefined
271
+ ? noteOf(providers[0])
272
+ : undefined;
273
+ function providerChoiceFor(tier: string) {
274
+ if (providers === undefined || providers.length < 2 || activeProvider === undefined) {
275
+ return undefined;
276
+ }
277
+ const prices = tierPrices(providers, tier);
278
+ const showPrices = pricesDiffer(prices);
279
+ return {
280
+ value: activeProvider.name,
281
+ onChange: setChosenProvider,
282
+ options: providers.map((provider) => {
283
+ const price = provider.plans.find((plan) => plan.tier === tier)?.price;
284
+ const note = noteOf(provider);
285
+ return {
286
+ value: provider.name,
287
+ title: provider.displayName,
288
+ ...(note !== undefined && { description: note }),
289
+ ...(showPrices &&
290
+ price && { aside: formatMoney(price.unitAmount, price.currency, locale) }),
291
+ };
292
+ }),
293
+ };
294
+ }
295
+ const currentTierInPlans = shownPlans.some((plan) => plan.isCurrent);
296
+ const visiblePlans = result.enabled ? shownPlans : shownPlans.filter((plan) => plan.isCurrent);
248
297
 
249
298
  return (
250
299
  <div className="flex flex-col gap-4" data-testid="billing-plans-panel">
@@ -258,6 +307,11 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
258
307
  {t("billing-foundation.plans.purchaseNotAllowed")}
259
308
  </Banner>
260
309
  )}
310
+ {switchRequested && (
311
+ <Banner variant="info" testId="billing-plans-panel-switch-requested">
312
+ {t("billing-foundation.plans.planSwitchRequested")}
313
+ </Banner>
314
+ )}
261
315
  {mutationError !== null && (
262
316
  <Banner variant="error" testId="billing-plans-panel-mutation-error">
263
317
  {t(mutationError.i18nKey, mutationError.i18nParams)}
@@ -318,25 +372,43 @@ export function BillingPlansPanel(_props: ExtensionSectionProps): ReactNode {
318
372
  ))}
319
373
  {result.contact !== undefined && <BillingContactPlan contact={result.contact} />}
320
374
  </PlanGrid>
321
- {consumerProtection !== undefined &&
322
- consentPlan !== undefined &&
323
- consentPlan.price !== null && (
324
- <CheckoutConsentDialog
325
- open
326
- onOpenChange={(open) => {
327
- if (!open) setConsentTier(null);
328
- }}
329
- planName={t(consentPlan.labelKey)}
330
- price={{
331
- amount: formatMoney(consentPlan.price.unitAmount, consentPlan.price.currency, locale),
332
- renewalKey: renewalKey(consentPlan.price),
333
- count: new Intl.NumberFormat(locale).format(consentPlan.price.intervalCount ?? 1),
334
- }}
335
- consumerProtection={consumerProtection}
336
- onOrder={(consent) => orderWithConsent(consentPlan.tier, consent)}
337
- onConsentTextOutdated={() => void query.refetch()}
338
- />
339
- )}
375
+ {consentPlan !== undefined && consentPlan.price !== null && (
376
+ <CheckoutConsentDialog
377
+ open
378
+ onOpenChange={(open) => {
379
+ if (!open) setConsentTier(null);
380
+ }}
381
+ planName={t(consentPlan.labelKey)}
382
+ price={{
383
+ amount: formatMoney(consentPlan.price.unitAmount, consentPlan.price.currency, locale),
384
+ renewalKey: renewalKey(consentPlan.price),
385
+ count: new Intl.NumberFormat(locale).format(consentPlan.price.intervalCount ?? 1),
386
+ }}
387
+ consumerProtection={consumerProtection}
388
+ providerChoice={providerChoiceFor(consentPlan.tier)}
389
+ note={singleProviderNote}
390
+ onOrder={(consent) => orderWithConsent(consentPlan.tier, consent)}
391
+ onConsentTextOutdated={() => void query.refetch()}
392
+ />
393
+ )}
394
+ {switchPlan !== undefined && (
395
+ <PlanSwitchConfirmDialog
396
+ open
397
+ onOpenChange={(open) => {
398
+ if (!open) setSwitchTier(null);
399
+ }}
400
+ tier={switchPlan.tier}
401
+ fromPlanName={t(result.currentTier.labelKey)}
402
+ toPlanName={t(switchPlan.labelKey)}
403
+ consumerProtection={consumerProtection}
404
+ onApplied={() => {
405
+ setSwitchTier(null);
406
+ setSwitchRequested(true);
407
+ void query.refetch();
408
+ }}
409
+ onConsentTextOutdated={() => void query.refetch()}
410
+ />
411
+ )}
340
412
  {consumerProtection !== undefined && canManage && (
341
413
  <CancelContractDialog
342
414
  open={cancelOpen}