@cosmicdrift/kumiko-bundled-features 0.337.1 → 0.339.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 (235) hide show
  1. package/dist/agent-tools/tool-catalog.js +37 -17
  2. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +3 -2
  3. package/dist/auth-email-password/handlers/invite-accept.write.js +3 -4
  4. package/dist/auth-email-password/handlers/invite-info.query.js +4 -1
  5. package/dist/auth-email-password/handlers/invite-signup-complete.write.js +3 -4
  6. package/dist/auth-email-password/handlers/signup-confirm.write.js +2 -1
  7. package/dist/auth-email-password/handlers/signup-request.write.js +2 -2
  8. package/dist/auth-email-password/web/auth-gate.d.ts +12 -2
  9. package/dist/auth-email-password/web/auth-gate.js +25 -1
  10. package/dist/auth-email-password/web/auth-redirect.d.ts +5 -0
  11. package/dist/auth-email-password/web/auth-redirect.js +58 -0
  12. package/dist/auth-email-password/web/client-plugin.d.ts +8 -1
  13. package/dist/auth-email-password/web/client-plugin.js +2 -0
  14. package/dist/auth-email-password/web/index.d.ts +2 -1
  15. package/dist/auth-email-password/web/index.js +1 -0
  16. package/dist/auth-email-password/web/session.d.ts +3 -1
  17. package/dist/auth-email-password/web/session.js +8 -2
  18. package/dist/auth-mfa/feature.js +1 -1
  19. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +1 -1
  20. package/dist/auth-mfa/handlers/enable-start-preauth.write.js +2 -1
  21. package/dist/auth-mfa/handlers/verify.write.js +1 -1
  22. package/dist/auth-mfa/mfa-code-verifier.js +1 -1
  23. package/dist/auth-mfa/mfa-status-checker.js +1 -1
  24. package/dist/billing-foundation/aggregate-id.d.ts +2 -0
  25. package/dist/billing-foundation/aggregate-id.js +8 -0
  26. package/dist/billing-foundation/checkout-core.d.ts +3 -0
  27. package/dist/billing-foundation/checkout-core.js +3 -0
  28. package/dist/billing-foundation/constants.d.ts +38 -0
  29. package/dist/billing-foundation/constants.js +43 -0
  30. package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +21 -0
  31. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +125 -0
  32. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +32 -0
  33. package/dist/billing-foundation/consumer-protection/consent-gate.js +96 -0
  34. package/dist/billing-foundation/consumer-protection/consent-locale.d.ts +5 -0
  35. package/dist/billing-foundation/consumer-protection/consent-locale.js +17 -0
  36. package/dist/billing-foundation/consumer-protection/consent-text.d.ts +11 -0
  37. package/dist/billing-foundation/consumer-protection/consent-text.js +28 -0
  38. package/dist/billing-foundation/consumer-protection/issue-confirmation.d.ts +15 -0
  39. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +149 -0
  40. package/dist/billing-foundation/consumer-protection/register-consent.d.ts +3 -0
  41. package/dist/billing-foundation/consumer-protection/register-consent.js +63 -0
  42. package/dist/billing-foundation/consumer-protection/register-termination.d.ts +3 -0
  43. package/dist/billing-foundation/consumer-protection/register-termination.js +40 -0
  44. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +45 -0
  45. package/dist/billing-foundation/consumer-protection/termination-mail.js +76 -0
  46. package/dist/billing-foundation/consumer-protection/termination-notify.d.ts +18 -0
  47. package/dist/billing-foundation/consumer-protection/termination-notify.js +21 -0
  48. package/dist/billing-foundation/consumer-protection/termination-pages.d.ts +8 -0
  49. package/dist/billing-foundation/consumer-protection/termination-pages.js +268 -0
  50. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +73 -0
  51. package/dist/billing-foundation/consumer-protection/termination-record.js +230 -0
  52. package/dist/billing-foundation/consumer-protection/termination-request.d.ts +45 -0
  53. package/dist/billing-foundation/consumer-protection/termination-request.js +231 -0
  54. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +62 -0
  55. package/dist/billing-foundation/consumer-protection/termination-texts.js +168 -0
  56. package/dist/billing-foundation/events.d.ts +105 -2
  57. package/dist/billing-foundation/events.js +88 -2
  58. package/dist/billing-foundation/feature.js +6 -2
  59. package/dist/billing-foundation/handlers/billing-plans.query.js +19 -1
  60. package/dist/billing-foundation/handlers/create-checkout-session.write.js +33 -2
  61. package/dist/billing-foundation/handlers/process-event.write.d.ts +2 -2
  62. package/dist/billing-foundation/handlers/process-event.write.js +43 -15
  63. package/dist/billing-foundation/handlers/process-payment-event.write.d.ts +2 -1
  64. package/dist/billing-foundation/handlers/process-payment-event.write.js +6 -2
  65. package/dist/billing-foundation/handlers/start-plan-checkout.write.js +17 -1
  66. package/dist/billing-foundation/handlers/sync-subscription.write.js +0 -1
  67. package/dist/billing-foundation/i18n.js +61 -0
  68. package/dist/billing-foundation/index.d.ts +3 -2
  69. package/dist/billing-foundation/index.js +2 -1
  70. package/dist/billing-foundation/tenant-destroy-hook.js +4 -4
  71. package/dist/billing-foundation/types.d.ts +54 -9
  72. package/dist/billing-foundation/validate-options.js +51 -10
  73. package/dist/billing-foundation/web/billing-plans-panel.js +40 -3
  74. package/dist/billing-foundation/web/cancel-contract-dialog.d.ts +7 -0
  75. package/dist/billing-foundation/web/cancel-contract-dialog.js +57 -0
  76. package/dist/billing-foundation/web/checkout-consent-dialog.d.ts +28 -0
  77. package/dist/billing-foundation/web/checkout-consent-dialog.js +38 -0
  78. package/dist/billing-foundation/webhook-handler.js +5 -2
  79. package/dist/cap-counter/with-cap-enforcement.js +7 -3
  80. package/dist/cap-overview/access.d.ts +2 -1
  81. package/dist/cap-overview/access.js +26 -5
  82. package/dist/cap-overview/feature.d.ts +4 -0
  83. package/dist/cap-overview/feature.js +8 -6
  84. package/dist/cap-overview/handlers/caps-usage.query.d.ts +1 -1
  85. package/dist/cap-overview/handlers/caps-usage.query.js +6 -4
  86. package/dist/cap-overview/handlers/tenant-caps-list.query.js +12 -7
  87. package/dist/cap-overview/index.d.ts +2 -2
  88. package/dist/cap-overview/index.js +2 -2
  89. package/dist/cap-overview/screens.d.ts +1 -1
  90. package/dist/cap-overview/screens.js +17 -16
  91. package/dist/channel-discord/discord-channel.d.ts +6 -0
  92. package/dist/channel-discord/discord-channel.js +19 -0
  93. package/dist/channel-discord/feature.d.ts +3 -0
  94. package/dist/channel-discord/feature.js +30 -0
  95. package/dist/channel-discord/index.d.ts +2 -0
  96. package/dist/channel-discord/index.js +2 -0
  97. package/dist/channel-slack/feature.d.ts +3 -0
  98. package/dist/channel-slack/feature.js +30 -0
  99. package/dist/channel-slack/index.d.ts +2 -0
  100. package/dist/channel-slack/index.js +2 -0
  101. package/dist/channel-slack/slack-channel.d.ts +5 -0
  102. package/dist/channel-slack/slack-channel.js +16 -0
  103. package/dist/channel-teams/feature.d.ts +3 -0
  104. package/dist/channel-teams/feature.js +34 -0
  105. package/dist/channel-teams/index.d.ts +2 -0
  106. package/dist/channel-teams/index.js +2 -0
  107. package/dist/channel-teams/teams-channel.d.ts +5 -0
  108. package/dist/channel-teams/teams-channel.js +38 -0
  109. package/dist/channel-telegram/feature.d.ts +3 -0
  110. package/dist/channel-telegram/feature.js +28 -0
  111. package/dist/channel-telegram/index.d.ts +2 -0
  112. package/dist/channel-telegram/index.js +2 -0
  113. package/dist/channel-telegram/telegram-channel.d.ts +8 -0
  114. package/dist/channel-telegram/telegram-channel.js +54 -0
  115. package/dist/config/handlers/set.write.js +18 -2
  116. package/dist/crypto-shredding/feature.js +3 -2
  117. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  118. package/dist/crypto-shredding/handlers/forget-subject.write.js +30 -19
  119. package/dist/custom-fields/feature.js +9 -6
  120. package/dist/custom-fields/handlers/clear-custom-field.write.js +2 -1
  121. package/dist/custom-fields/handlers/define-tenant-field.write.js +2 -1
  122. package/dist/custom-fields/handlers/set-custom-field.write.js +2 -1
  123. package/dist/custom-fields/wire-user-data-rights.js +1 -1
  124. package/dist/data-retention/feature.js +1 -1
  125. package/dist/delivery/channel-context.d.ts +2 -1
  126. package/dist/delivery/channel-context.js +2 -2
  127. package/dist/delivery/chat-webhook-channel.d.ts +19 -0
  128. package/dist/delivery/chat-webhook-channel.js +48 -0
  129. package/dist/delivery/chat-webhook-sender.d.ts +21 -0
  130. package/dist/delivery/chat-webhook-sender.js +102 -0
  131. package/dist/delivery/delivery-service.js +12 -7
  132. package/dist/delivery/index.d.ts +2 -0
  133. package/dist/delivery/index.js +2 -0
  134. package/dist/delivery/jobs.js +1 -1
  135. package/dist/delivery/testing.d.ts +2 -0
  136. package/dist/delivery/types.d.ts +3 -1
  137. package/dist/delivery/types.js +1 -2
  138. package/dist/document-ingest-foundation/feature.js +3 -2
  139. package/dist/file-derivatives/handlers/public-variant-by-file-ref.query.js +1 -1
  140. package/dist/files-tenant-data/index.js +1 -1
  141. package/dist/form-draft/feature.js +1 -1
  142. package/dist/form-draft/handlers/discard.write.js +2 -1
  143. package/dist/form-draft/handlers/save.write.js +2 -1
  144. package/dist/inbound-mail-foundation/connect-routes.d.ts +7 -0
  145. package/dist/inbound-mail-foundation/connect-routes.js +13 -8
  146. package/dist/inbound-mail-foundation/feature.js +1 -1
  147. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +2 -1
  148. package/dist/inbound-mail-foundation/index.d.ts +1 -0
  149. package/dist/inbound-mail-foundation/index.js +1 -0
  150. package/dist/inbound-mail-foundation/oauth-access-token.d.ts +28 -0
  151. package/dist/inbound-mail-foundation/oauth-access-token.js +131 -0
  152. package/dist/inbound-mail-foundation/tenant-destroy-hook.js +1 -1
  153. package/dist/inbound-mail-foundation/types.d.ts +18 -1
  154. package/dist/inbound-mail-foundation/watch-supervisor.d.ts +5 -0
  155. package/dist/inbound-mail-foundation/watch-supervisor.js +42 -14
  156. package/dist/managed-pages/handlers/set.write.js +2 -1
  157. package/dist/notes-history/executor.d.ts +2 -0
  158. package/dist/notes-history/executor.js +9 -1
  159. package/dist/notes-history/feature.d.ts +2 -3
  160. package/dist/notes-history/feature.js +2 -1
  161. package/dist/notes-history/handlers/add-note.write.d.ts +2 -1
  162. package/dist/notes-history/handlers/add-note.write.js +21 -4
  163. package/dist/notes-history-user-data/hooks.js +26 -8
  164. package/dist/renderer-simple/feature.d.ts +8 -2
  165. package/dist/renderer-simple/feature.js +8 -5
  166. package/dist/renderer-simple/index.d.ts +2 -2
  167. package/dist/renderer-simple/index.js +1 -1
  168. package/dist/renderer-simple/simple-renderer.d.ts +15 -0
  169. package/dist/renderer-simple/simple-renderer.js +74 -27
  170. package/dist/secrets/feature.js +1 -1
  171. package/dist/sessions/feature.js +1 -1
  172. package/dist/sessions/handlers/revoke-all-for-user.write.js +2 -1
  173. package/dist/subscription-mollie/plugin-methods.js +1 -0
  174. package/dist/subscription-mollie/verify-webhook.js +5 -3
  175. package/dist/subscription-stripe/plugin-methods.d.ts +3 -1
  176. package/dist/subscription-stripe/plugin-methods.js +78 -13
  177. package/dist/subscription-stripe/verify-webhook.js +7 -3
  178. package/dist/tags/executor.d.ts +2 -0
  179. package/dist/tags/executor.js +9 -1
  180. package/dist/tags/feature.d.ts +4 -3
  181. package/dist/tags/feature.js +4 -2
  182. package/dist/tags/handlers/assign-tag.write.d.ts +2 -1
  183. package/dist/tags/handlers/assign-tag.write.js +4 -4
  184. package/dist/tags/handlers/remove-tag.write.d.ts +2 -1
  185. package/dist/tags/handlers/remove-tag.write.js +3 -3
  186. package/dist/tenant-handover/handlers/claim.write.js +2 -2
  187. package/dist/tenant-lifecycle/feature.js +1 -1
  188. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +4 -1
  189. package/dist/tenant-lifecycle/handlers/request-destruction.write.js +2 -1
  190. package/dist/tier-engine/feature.js +1 -1
  191. package/dist/tier-engine/handlers/get-tenant-tier.query.js +2 -1
  192. package/dist/tier-engine/handlers/set-tenant-tier.write.js +2 -1
  193. package/dist/user/handlers/detail.query.js +1 -0
  194. package/dist/user/handlers/list.query.js +1 -0
  195. package/dist/user-data-rights/feature.js +2 -2
  196. package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
  197. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +1 -1
  198. package/dist/user-data-rights/handlers/download-attempt-list.query.js +1 -0
  199. package/dist/user-data-rights/handlers/download-by-job.query.js +2 -1
  200. package/dist/user-data-rights/handlers/download-by-token.query.js +2 -1
  201. package/dist/user-data-rights/handlers/export-status.query.js +2 -1
  202. package/dist/user-data-rights/handlers/lift-restriction.write.js +3 -4
  203. package/dist/user-data-rights/handlers/my-audit-log.query.js +2 -1
  204. package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +2 -1
  205. package/dist/user-data-rights/handlers/request-deletion.write.js +1 -1
  206. package/dist/user-data-rights/handlers/request-export.write.js +2 -1
  207. package/dist/user-data-rights/handlers/restrict-account.write.js +2 -2
  208. package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +2 -1
  209. package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
  210. package/dist/user-data-rights/run-forget-cleanup.js +30 -15
  211. package/dist/workflow-runner/event-subscriber.js +6 -2
  212. package/dist/workflow-runner/event-trigger.js +5 -0
  213. package/dist/workflow-runner/handlers/resume-run.write.js +147 -59
  214. package/dist/workflow-runner/pending-projection.js +1 -0
  215. package/dist/workflow-runner/runner.d.ts +7 -1
  216. package/dist/workflow-runner/runner.js +1 -1
  217. package/dist/workflow-runner/tables.js +8 -1
  218. package/package.json +41 -9
  219. package/src/agent-tools/changes.json +14 -0
  220. package/src/auth-email-password/changes.json +7 -0
  221. package/src/billing-foundation/changes.json +83 -0
  222. package/src/cap-counter/changes.json +7 -0
  223. package/src/cap-overview/changes.json +14 -0
  224. package/src/config/changes.json +7 -0
  225. package/src/crypto-shredding/changes.json +13 -0
  226. package/src/custom-fields/changes.json +7 -0
  227. package/src/delivery/changes.json +14 -0
  228. package/src/inbound-mail-foundation/changes.json +7 -0
  229. package/src/notes-history/changes.json +7 -0
  230. package/src/renderer-simple/changes.json +9 -1
  231. package/src/subscription-mollie/changes.json +13 -0
  232. package/src/subscription-stripe/changes.json +13 -0
  233. package/src/tags/changes.json +7 -0
  234. package/src/user-data-rights/changes.json +6 -0
  235. package/src/workflow-runner/changes.json +7 -0
@@ -11,6 +11,7 @@
11
11
  // type ohne payload-discriminator.
12
12
  import * as z from "zod";
13
13
  import { BILLING_FOUNDATION_FEATURE, SubscriptionStatuses } from "./constants.js";
14
+ import { CONSENT_LOCALES } from "./consumer-protection/consent-locale.js";
14
15
  // Aggregate-type für den event-store. Eine subscription pro Tenant ist
15
16
  // ein stream; der subscriptionAggregateId-helper liefert die stream-id.
16
17
  export const SUBSCRIPTION_AGGREGATE_TYPE = "subscription";
@@ -36,8 +37,8 @@ const statusEnum = z.enum([
36
37
  ]);
37
38
  // Common payload — alle 5 events tragen denselben subscription-state-
38
39
  // snapshot. Event-type tagged was passiert ist, payload den state-after.
39
- // Provider-spezifischer rawPayload ist in metadata.rawPayload (nicht in
40
- // payload — payload ist domain-clean, metadata ist provider-truth).
40
+ // The raw provider event is deliberately not stored: it carries plaintext
41
+ // PII and event headers are not crypto-shreddable.
41
42
  export const subscriptionEventPayloadSchema = z.object({
42
43
  providerName: z.string().min(1).max(50),
43
44
  // 1000, not 200: these two are `tenantOwned: true` on the entity (see
@@ -54,6 +55,7 @@ export const subscriptionEventPayloadSchema = z.object({
54
55
  // upcaster. null = the subscription renews; undefined = the provider
55
56
  // doesn't report cancelAt at all — projection.ts tells the two apart.
56
57
  cancelAtIso: z.string().min(1).nullable().optional(),
58
+ consentId: z.string().min(1).max(100).optional(),
57
59
  });
58
60
  // =============================================================================
59
61
  // payment-received — one-off-payments (checkout mode "payment")
@@ -74,4 +76,88 @@ export const paymentEventPayloadSchema = z.object({
74
76
  // subscriptionEventPayloadSchema's same rationale.
75
77
  providerCustomerId: z.string().min(1).max(1000),
76
78
  priceId: z.string().min(1).max(200),
79
+ consentId: z.string().min(1).max(100).optional(),
80
+ });
81
+ // =============================================================================
82
+ // checkout-consent-recorded — consumer-protection consent
83
+ // =============================================================================
84
+ //
85
+ // Appended onto the subscription- or payment-stream (by checkout mode) after
86
+ // the provider handed out the checkout URL. No projection applies it. Carries
87
+ // no email or IP: the consenting user is identified by `actorUserId` only.
88
+ export const CHECKOUT_CONSENT_RECORDED_EVENT_SHORT = "checkout-consent-recorded";
89
+ export const CHECKOUT_CONSENT_RECORDED_EVENT_QN = `${BILLING_FOUNDATION_FEATURE}:event:${CHECKOUT_CONSENT_RECORDED_EVENT_SHORT}`;
90
+ export const checkoutConsentRecordedPayloadSchema = z.object({
91
+ consentId: z.string().min(1).max(100),
92
+ mode: z.enum(["subscription", "payment"]),
93
+ tier: z.string().min(1).max(50).nullable(),
94
+ priceId: z.string().min(1).max(200),
95
+ unitAmount: z.number().nullable(),
96
+ currency: z.string().nullable(),
97
+ interval: z.string().nullable(),
98
+ intervalCount: z.number().nullable(),
99
+ consentTextVersion: z.string().min(1).max(64),
100
+ termsHash: z.string().length(64),
101
+ termsTemplateVersion: z.number(),
102
+ locale: z.string().min(2).max(35),
103
+ actorUserId: z.string().min(1),
104
+ });
105
+ // =============================================================================
106
+ // contract-confirmation-issued — § 312f confirmation mail sent
107
+ // =============================================================================
108
+ //
109
+ // Appended onto the stream that carries the consent, BEFORE the mail is
110
+ // handed to delivery: the append is the optimistic-concurrency guard that
111
+ // keeps a concurrent or replayed run from sending a second mail.
112
+ export const CONTRACT_CONFIRMATION_ISSUED_EVENT_SHORT = "contract-confirmation-issued";
113
+ export const CONTRACT_CONFIRMATION_ISSUED_EVENT_QN = `${BILLING_FOUNDATION_FEATURE}:event:${CONTRACT_CONFIRMATION_ISSUED_EVENT_SHORT}`;
114
+ export const contractConfirmationIssuedPayloadSchema = z.object({
115
+ consentId: z.string().min(1).max(100),
116
+ issuedAtIso: z.string().min(1),
117
+ locale: z.string().min(2).max(35),
118
+ termsTemplateVersion: z.number(),
119
+ });
120
+ // =============================================================================
121
+ // contract-termination-requested / -unmatched — § 312k cancellation
122
+ // =============================================================================
123
+ //
124
+ // Payloads carry no name, email or reason: those only travel in the mails.
125
+ // `requested` goes onto the tenant's subscription stream; `unmatched` onto a
126
+ // system-tenant stream of its own, keyed by the request id.
127
+ export const CONTRACT_TERMINATION_DECLARATION_TYPES = ["termination", "withdrawal"];
128
+ export const CONTRACT_TERMINATION_KINDS = ["ordinary", "extraordinary"];
129
+ export const CONTRACT_TERMINATION_CHANNELS = ["public", "account"];
130
+ export const PROVIDER_CANCEL_OUTCOMES = ["period-end", "immediately", "none"];
131
+ export const CONTRACT_TERMINATION_REQUESTED_EVENT_SHORT = "contract-termination-requested";
132
+ export const CONTRACT_TERMINATION_REQUESTED_EVENT_QN = `${BILLING_FOUNDATION_FEATURE}:event:${CONTRACT_TERMINATION_REQUESTED_EVENT_SHORT}`;
133
+ export const contractTerminationRequestedPayloadSchema = z.object({
134
+ requestId: z.string().min(1).max(100),
135
+ declarationType: z.enum(CONTRACT_TERMINATION_DECLARATION_TYPES),
136
+ terminationKind: z.enum(CONTRACT_TERMINATION_KINDS),
137
+ channel: z.enum(CONTRACT_TERMINATION_CHANNELS),
138
+ receivedAtIso: z.string().min(1),
139
+ effectiveAtIso: z.string().min(1).nullable(),
140
+ providerCancel: z.enum(PROVIDER_CANCEL_OUTCOMES),
141
+ });
142
+ // A public declaration matched to this tenant. The provider cancel runs in a
143
+ // job triggered by this event, so the request itself only looks up and appends.
144
+ export const CONTRACT_TERMINATION_DECLARED_EVENT_SHORT = "contract-termination-declared";
145
+ export const CONTRACT_TERMINATION_DECLARED_EVENT_QN = `${BILLING_FOUNDATION_FEATURE}:event:${CONTRACT_TERMINATION_DECLARED_EVENT_SHORT}`;
146
+ export const contractTerminationDeclaredPayloadSchema = z.object({
147
+ requestId: z.string().min(1).max(100),
148
+ declarationType: z.enum(CONTRACT_TERMINATION_DECLARATION_TYPES),
149
+ terminationKind: z.enum(CONTRACT_TERMINATION_KINDS),
150
+ receivedAtIso: z.string().min(1),
151
+ locale: z.enum(CONSENT_LOCALES),
152
+ });
153
+ export const CONTRACT_TERMINATION_UNMATCHED_AGGREGATE_TYPE = "contract-termination-unmatched";
154
+ export const CONTRACT_TERMINATION_UNMATCHED_EVENT_SHORT = "contract-termination-unmatched";
155
+ export const CONTRACT_TERMINATION_UNMATCHED_EVENT_QN = `${BILLING_FOUNDATION_FEATURE}:event:${CONTRACT_TERMINATION_UNMATCHED_EVENT_SHORT}`;
156
+ export const contractTerminationUnmatchedPayloadSchema = z.object({
157
+ requestId: z.string().min(1).max(100),
158
+ declarationType: z.enum(CONTRACT_TERMINATION_DECLARATION_TYPES),
159
+ terminationKind: z.enum(CONTRACT_TERMINATION_KINDS),
160
+ channel: z.literal("public"),
161
+ receivedAtIso: z.string().min(1),
162
+ matchResult: z.enum(["none", "ambiguous"]),
77
163
  });
@@ -55,13 +55,14 @@ import { defineFeature, EXT_TENANT_DATA, } from "@cosmicdrift/kumiko-framework/e
55
55
  // resolves against, see event-store.ts's own import comment (#1438).
56
56
  import { Temporal as TemporalPolyfill } from "temporal-polyfill";
57
57
  import { BILLING_FOUNDATION_FEATURE, SUBSCRIPTION_PROVIDER_EXTENSION, SubscriptionFoundationHandlers, } from "./constants.js";
58
+ import { registerConsumerProtection } from "./consumer-protection/register-consent.js";
58
59
  import { paymentEntity, subscriptionEntity } from "./entities.js";
59
60
  import { INVOICE_PAID_EVENT_QN, INVOICE_PAID_EVENT_SHORT, INVOICE_PAYMENT_FAILED_EVENT_QN, INVOICE_PAYMENT_FAILED_EVENT_SHORT, PAYMENT_AGGREGATE_TYPE, PAYMENT_RECEIVED_EVENT_QN, PAYMENT_RECEIVED_EVENT_SHORT, paymentEventPayloadSchema, SUBSCRIPTION_AGGREGATE_TYPE, SUBSCRIPTION_CANCELED_EVENT_QN, SUBSCRIPTION_CANCELED_EVENT_SHORT, SUBSCRIPTION_CREATED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_SHORT, SUBSCRIPTION_UPDATED_EVENT_QN, SUBSCRIPTION_UPDATED_EVENT_SHORT, subscriptionEventPayloadSchema, } from "./events.js";
60
61
  import { createBillingPlansQuery } from "./handlers/billing-plans.query.js";
61
62
  import { createCheckoutSessionHandler } from "./handlers/create-checkout-session.write.js";
62
63
  import { createPortalSessionHandler } from "./handlers/create-portal-session.write.js";
63
64
  import { listSubscriptionsQuery } from "./handlers/list-subscriptions.query.js";
64
- import { processEventHandler } from "./handlers/process-event.write.js";
65
+ import { createProcessEventHandler } from "./handlers/process-event.write.js";
65
66
  import { processPaymentEventHandler } from "./handlers/process-payment-event.write.js";
66
67
  import { createStartPlanCheckoutHandler } from "./handlers/start-plan-checkout.write.js";
67
68
  import { createSwitchPlanHandler } from "./handlers/switch-plan.write.js";
@@ -163,7 +164,7 @@ export function createBillingFoundationFeature(options = {}) {
163
164
  // - create-checkout-session: bare priceId-driven checkout (hardened,
164
165
  // see checkout-core)
165
166
  // - create-portal-session: Tenant-Admin "Manage Subscription"-flow
166
- r.writeHandler(processEventHandler);
167
+ r.writeHandler(createProcessEventHandler(widened.now));
167
168
  r.writeHandler(createCheckoutSessionHandler(widened));
168
169
  r.writeHandler(createPortalSessionHandler(widened));
169
170
  // - process-payment-event: programmatic entry-point from the webhook-
@@ -182,6 +183,9 @@ export function createBillingFoundationFeature(options = {}) {
182
183
  // fire from create-checkout-session even without a catalog — register
183
184
  // unconditionally rather than splitting the i18n surface by option.
184
185
  r.translations({ keys: BILLING_FOUNDATION_I18N });
186
+ if (widened.consumerProtection) {
187
+ registerConsumerProtection(r, widened, widened.consumerProtection);
188
+ }
185
189
  if (widened.catalog) {
186
190
  const { catalog } = widened;
187
191
  r.queryHandler(createBillingPlansQuery(widened, catalog));
@@ -3,6 +3,7 @@
3
3
  // when `createBillingFoundationFeature` gets a `catalog`.
4
4
  import * as z from "zod";
5
5
  import { findCatalogProvider } from "../checkout-core.js";
6
+ import { CONSENT_TEXTS, consentTextVersion, } from "../consumer-protection/consent-text.js";
6
7
  import { buildBillingPlans } from "../plan-catalog.js";
7
8
  const billingPlansSchema = z.object({}).strict();
8
9
  export function createBillingPlansQuery(options, catalog) {
@@ -13,7 +14,24 @@ export function createBillingPlansQuery(options, catalog) {
13
14
  access: { roles: catalog.viewRoles },
14
15
  handler: async (_query, ctx) => {
15
16
  const found = findCatalogProvider(ctx, catalog);
16
- return buildBillingPlans(ctx, found?.plugin ?? null, catalog, options.now);
17
+ const result = await buildBillingPlans(ctx, found?.plugin ?? null, catalog, options.now);
18
+ const { consumerProtection } = options;
19
+ if (!consumerProtection)
20
+ return result;
21
+ return {
22
+ ...result,
23
+ consumerProtection: {
24
+ consentTexts: {
25
+ de: consentTextView("de"),
26
+ en: consentTextView("en"),
27
+ },
28
+ legalLinks: consumerProtection.legalLinks,
29
+ },
30
+ };
17
31
  },
18
32
  };
19
33
  }
34
+ function consentTextView(locale) {
35
+ const { earlyPerformance, withdrawalLoss } = CONSENT_TEXTS[locale];
36
+ return { earlyPerformance, withdrawalLoss, consentTextVersion: consentTextVersion(locale) };
37
+ }
@@ -22,7 +22,8 @@
22
22
  // prefer start-plan-checkout/switch-plan over calling this handler with a
23
23
  // hand-picked priceId.
24
24
  import * as z from "zod";
25
- import { openCheckout } from "../checkout-core.js";
25
+ import { findProviderPlugin, openCheckout } from "../checkout-core.js";
26
+ import { consentCheckoutFields, consentPayloadSchema, prepareConsent, recordConsent, } from "../consumer-protection/consent-gate.js";
26
27
  const createCheckoutSessionSchema = z.object({
27
28
  /** Welcher Provider — entityName eines registrierten subscription-
28
29
  * Plugins ("stripe" / "paypal" / ...). */
@@ -41,16 +42,24 @@ const createCheckoutSessionSchema = z.object({
41
42
  mode: z.enum(["subscription", "payment"]).optional(),
42
43
  });
43
44
  export function createCheckoutSessionHandler(options) {
45
+ const consumerProtection = options.consumerProtection;
46
+ const schema = consumerProtection
47
+ ? createCheckoutSessionSchema.extend({ consent: consentPayloadSchema.optional() })
48
+ : createCheckoutSessionSchema;
44
49
  return {
45
50
  name: "create-checkout-session",
46
51
  description: 'Opens a hosted checkout page at the named subscription provider for the caller\'s tenant and returns its URL; use it when a tenant admin wants to make a one-off payment (mode: "payment") or subscribe to a price already known to be valid. Redirect URLs must share the configured baseUrl origin. Prefer billing-foundation:write:start-plan-checkout / :switch-plan for catalog-driven plan purchases.',
47
- schema: createCheckoutSessionSchema,
52
+ schema,
48
53
  // Tenant-Admin-only — the tenant must deliberately configure its own
49
54
  // subscription. SystemAdmin as a fallback for operator-initiated flows.
50
55
  access: { roles: ["TenantAdmin", "SystemAdmin"] },
51
56
  handler: async (event, ctx) => {
52
57
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
53
58
  const payload = event.payload;
59
+ const mode = payload.mode ?? "subscription";
60
+ const consent = consumerProtection
61
+ ? await prepareConsent(ctx, consumerProtection, payload.consent, mode)
62
+ : undefined;
54
63
  const result = await openCheckout(ctx, { baseUrl: options.baseUrl, catalog: options.catalog, now: options.now }, {
55
64
  providerName: payload.providerName,
56
65
  priceId: payload.priceId,
@@ -58,7 +67,16 @@ export function createCheckoutSessionHandler(options) {
58
67
  cancelUrl: payload.cancelUrl,
59
68
  ...(payload.providerCustomerId && { providerCustomerId: payload.providerCustomerId }),
60
69
  ...(payload.mode && { mode: payload.mode }),
70
+ ...(consent && consentCheckoutFields(consent)),
61
71
  });
72
+ if (consent) {
73
+ const plugin = findProviderPlugin(ctx, payload.providerName)?.plugin;
74
+ await recordConsent(ctx, consent, {
75
+ tier: mode === "subscription" ? (plugin?.priceToTier?.[payload.priceId] ?? null) : null,
76
+ priceId: payload.priceId,
77
+ price: await lookupPrice(ctx, plugin, payload.priceId),
78
+ });
79
+ }
62
80
  return {
63
81
  isSuccess: true,
64
82
  data: { url: result.url, providerName: result.providerName },
@@ -66,3 +84,16 @@ export function createCheckoutSessionHandler(options) {
66
84
  },
67
85
  };
68
86
  }
87
+ // A failed price lookup must not lose a consent the buyer already gave at the
88
+ // provider, so it degrades to null price fields.
89
+ async function lookupPrice(ctx, plugin, priceId) {
90
+ if (!plugin?.retrievePrices)
91
+ return null;
92
+ try {
93
+ const prices = await plugin.retrievePrices(ctx, [priceId]);
94
+ return prices.find((p) => p.priceId === priceId) ?? null;
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
@@ -22,11 +22,11 @@ export declare const processEventSchema: z.ZodObject<{
22
22
  tier: z.ZodString;
23
23
  currentPeriodEndIso: z.ZodString;
24
24
  cancelAtIso: z.ZodOptional<z.ZodNullable<z.ZodString>>;
25
- rawPayload: z.ZodString;
25
+ consentId: z.ZodOptional<z.ZodString>;
26
26
  }, z.core.$strip>;
27
27
  export type ProcessEventPayload = z.infer<typeof processEventSchema>;
28
28
  export declare function appendSubscriptionEvent(ctx: HandlerContext, tenantId: string, payload: ProcessEventPayload): Promise<{
29
29
  readonly duplicate: boolean;
30
30
  readonly subscriptionAggregateId: string;
31
31
  }>;
32
- export declare const processEventHandler: WriteHandlerDef;
32
+ export declare function createProcessEventHandler(now: () => Temporal.Instant): WriteHandlerDef;
@@ -14,9 +14,10 @@
14
14
  import { configuredPiiSubjectKms, encryptPiiFieldValues, } from "@cosmicdrift/kumiko-framework/crypto";
15
15
  import * as z from "zod";
16
16
  import { subscriptionAggregateId } from "../aggregate-id.js";
17
- import { SubscriptionEventTypes, SubscriptionStatuses } from "../constants.js";
17
+ import { isSubscriptionBlockingCheckout, SubscriptionEventTypes, SubscriptionStatuses, } from "../constants.js";
18
18
  import { SUBSCRIPTION_PII_FIELDS, subscriptionEntity } from "../entities.js";
19
19
  import { INVOICE_PAID_EVENT_QN, INVOICE_PAYMENT_FAILED_EVENT_QN, SUBSCRIPTION_AGGREGATE_TYPE, SUBSCRIPTION_CANCELED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_QN, SUBSCRIPTION_UPDATED_EVENT_QN, } from "../events.js";
20
+ import { getSubscriptionForTenant } from "../get-subscription-for-tenant.js";
20
21
  // =============================================================================
21
22
  // Input-Schema = der normalisierte SubscriptionEvent (ohne tenantId, der
22
23
  // kommt aus event.user.tenantId — der webhook-handler setzt den
@@ -46,7 +47,7 @@ export const processEventSchema = z.object({
46
47
  tier: z.string().min(1).max(50),
47
48
  currentPeriodEndIso: z.string().min(1),
48
49
  cancelAtIso: z.string().min(1).nullable().optional(),
49
- rawPayload: z.string().min(1),
50
+ consentId: z.string().min(1).max(100).optional(),
50
51
  });
51
52
  // Map normalized SubscriptionEventType → fully-qualified ES event-name.
52
53
  const NORMALIZED_TO_ES_EVENT = {
@@ -129,11 +130,11 @@ export async function appendSubscriptionEvent(ctx, tenantId, payload) {
129
130
  tier: payload.tier,
130
131
  currentPeriodEndIso: payload.currentPeriodEndIso,
131
132
  ...(payload.cancelAtIso !== undefined && { cancelAtIso: payload.cancelAtIso }),
133
+ ...(payload.consentId !== undefined && { consentId: payload.consentId }),
132
134
  };
133
135
  const headers = {
134
136
  providerEventId: payload.providerEventId,
135
137
  providerName: payload.providerName,
136
- rawPayload: payload.rawPayload,
137
138
  };
138
139
  await ctx.unsafeAppendEvent({
139
140
  aggregateId: aggId,
@@ -151,15 +152,42 @@ export async function appendSubscriptionEvent(ctx, tenantId, payload) {
151
152
  // SystemAdmin-only: dieser handler wird ausschließlich vom programmatic
152
153
  // webhook-handler aufgerufen (mit einem internal SystemUser), nie vom
153
154
  // Tenant-Admin direkt.
154
- export const processEventHandler = {
155
- name: "process-event",
156
- agent: { expose: false },
157
- schema: processEventSchema,
158
- access: { roles: ["SystemAdmin"] },
159
- handler: async (event, ctx) => {
160
- // @cast-boundary engine-payload — dispatcher-zod-validated payload
161
- const payload = event.payload;
162
- const result = await appendSubscriptionEvent(ctx, event.user.tenantId, payload);
163
- return { isSuccess: true, data: result };
164
- },
165
- };
155
+ // One subscription aggregate exists per tenant, but a tenant can legitimately hold two provider
156
+ // subscriptions over time (a stale `incomplete` one superseded by a fresh checkout). A late or
157
+ // retried event of the old subscription must not overwrite the row of the live one, so only a
158
+ // `created` event may replace a blocking row that belongs to a different provider subscription.
159
+ function isStaleSubscriptionEvent(current, payload, now) {
160
+ if (current === null)
161
+ return false;
162
+ if (payload.type === SubscriptionEventTypes.created)
163
+ return false;
164
+ const sameSubscription = current.providerName === payload.providerName &&
165
+ current.providerSubscriptionId === payload.providerSubscriptionId;
166
+ return !sameSubscription && isSubscriptionBlockingCheckout(current, now);
167
+ }
168
+ export function createProcessEventHandler(now) {
169
+ return {
170
+ name: "process-event",
171
+ agent: { expose: false },
172
+ schema: processEventSchema,
173
+ access: { roles: ["SystemAdmin"] },
174
+ handler: async (event, ctx) => {
175
+ // @cast-boundary engine-payload — dispatcher-zod-validated payload
176
+ const payload = event.payload;
177
+ const current = await getSubscriptionForTenant(ctx, event.user.tenantId);
178
+ if (isStaleSubscriptionEvent(current, payload, now())) {
179
+ ctx.log?.warn(`[billing-foundation:process-event] dropped stale ${payload.type} ${payload.providerEventId}: tenant already tracks another subscription`);
180
+ return {
181
+ isSuccess: true,
182
+ data: {
183
+ duplicate: false,
184
+ stale: true,
185
+ subscriptionAggregateId: subscriptionAggregateId(event.user.tenantId),
186
+ },
187
+ };
188
+ }
189
+ const result = await appendSubscriptionEvent(ctx, event.user.tenantId, payload);
190
+ return { isSuccess: true, data: result };
191
+ },
192
+ };
193
+ }
@@ -3,8 +3,9 @@ import * as z from "zod";
3
3
  export declare const processPaymentEventSchema: z.ZodObject<{
4
4
  providerEventId: z.ZodString;
5
5
  providerName: z.ZodString;
6
+ providerCheckoutId: z.ZodOptional<z.ZodString>;
6
7
  providerCustomerId: z.ZodString;
7
8
  priceId: z.ZodString;
8
- rawPayload: z.ZodString;
9
+ consentId: z.ZodOptional<z.ZodString>;
9
10
  }, z.core.$strip>;
10
11
  export declare const processPaymentEventHandler: WriteHandlerDef;
@@ -17,9 +17,10 @@ import { PAYMENT_AGGREGATE_TYPE, PAYMENT_RECEIVED_EVENT_QN, } from "../events.js
17
17
  export const processPaymentEventSchema = z.object({
18
18
  providerEventId: z.string().min(1).max(200),
19
19
  providerName: z.string().min(1).max(50),
20
+ providerCheckoutId: z.string().min(1).max(200).optional(),
20
21
  providerCustomerId: z.string().min(1).max(200),
21
22
  priceId: z.string().min(1).max(200),
22
- rawPayload: z.string().min(1),
23
+ consentId: z.string().min(1).max(100).optional(),
23
24
  });
24
25
  // SystemAdmin-only: this handler is called exclusively by the programmatic
25
26
  // webhook-handler (with an internal SystemUser), never directly by the
@@ -71,11 +72,14 @@ export const processPaymentEventHandler = {
71
72
  providerName: payload.providerName,
72
73
  providerCustomerId: encryptedFields["providerCustomerId"],
73
74
  priceId: payload.priceId,
75
+ ...(payload.consentId !== undefined && { consentId: payload.consentId }),
74
76
  };
75
77
  const headers = {
76
78
  providerEventId: payload.providerEventId,
77
79
  providerName: payload.providerName,
78
- rawPayload: payload.rawPayload,
80
+ ...(payload.providerCheckoutId !== undefined && {
81
+ providerCheckoutId: payload.providerCheckoutId,
82
+ }),
79
83
  };
80
84
  await ctx.unsafeAppendEvent({
81
85
  aggregateId: aggId,
@@ -5,13 +5,18 @@
5
5
  import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
6
6
  import * as z from "zod";
7
7
  import { assertBillingEnabled, assertNoActiveSubscription, isNonEmptyStringArray, joinBaseUrl, openCheckout, resolveCatalogProvider, } from "../checkout-core.js";
8
+ import { consentCheckoutFields, consentPayloadSchema, prepareConsent, recordConsent, } from "../consumer-protection/consent-gate.js";
8
9
  import { purchaseRolesOf, resolvePlanPrices } from "../plan-catalog.js";
9
10
  export function createStartPlanCheckoutHandler(options, catalog) {
10
11
  const plans = catalog.plans;
11
12
  if (!isNonEmptyStringArray(plans)) {
12
13
  throw new Error("start-plan-checkout: catalog.plans must not be empty");
13
14
  }
14
- const schema = z.object({ tier: z.enum(plans) }).strict();
15
+ const consumerProtection = options.consumerProtection;
16
+ const tierSchema = z.object({ tier: z.enum(plans) });
17
+ const schema = (consumerProtection
18
+ ? tierSchema.extend({ consent: consentPayloadSchema.optional() })
19
+ : tierSchema).strict();
15
20
  return {
16
21
  name: "start-plan-checkout",
17
22
  description: "Starts a hosted checkout for a plan tier when the tenant has no active subscription; the server picks price and redirect URLs.",
@@ -31,6 +36,9 @@ export function createStartPlanCheckoutHandler(options, catalog) {
31
36
  message: `subscription-foundation: no live price resolved for tier "${payload.tier}"`,
32
37
  });
33
38
  }
39
+ const consent = consumerProtection
40
+ ? await prepareConsent(ctx, consumerProtection, payload.consent, "subscription")
41
+ : undefined;
34
42
  const baseUrl = options.baseUrl ?? "";
35
43
  const result = await openCheckout(ctx, { baseUrl: options.baseUrl, catalog, now: options.now }, {
36
44
  providerName,
@@ -38,9 +46,17 @@ export function createStartPlanCheckoutHandler(options, catalog) {
38
46
  successUrl: joinBaseUrl(baseUrl, catalog.successPath),
39
47
  cancelUrl: joinBaseUrl(baseUrl, catalog.cancelPath),
40
48
  mode: "subscription",
49
+ ...(consent && consentCheckoutFields(consent)),
41
50
  ...(existing?.providerName === providerName &&
42
51
  existing.providerCustomerId && { providerCustomerId: existing.providerCustomerId }),
43
52
  });
53
+ if (consent) {
54
+ await recordConsent(ctx, consent, {
55
+ tier: payload.tier,
56
+ priceId: resolved.priceId,
57
+ price: resolved.price,
58
+ });
59
+ }
44
60
  return { isSuccess: true, data: { url: result.url } };
45
61
  },
46
62
  };
@@ -116,7 +116,6 @@ export const syncSubscriptionHandler = {
116
116
  tier: snapshot.tier,
117
117
  currentPeriodEndIso: snapshot.currentPeriodEnd,
118
118
  cancelAtIso: snapshot.cancelAt,
119
- rawPayload: snapshot.rawPayload,
120
119
  });
121
120
  return {
122
121
  isSuccess: true,
@@ -35,6 +35,58 @@ export const BILLING_FOUNDATION_I18N = {
35
35
  "billing-foundation.plans.subscriptionPending": {
36
36
  en: "Your plan change is being processed.",
37
37
  },
38
+ "billing-foundation.consent.title": { en: "Review your order" },
39
+ "billing-foundation.consent.cancelAnytime": {
40
+ en: "You can cancel at any time, effective at the end of the current period.",
41
+ },
42
+ "billing-foundation.consent.renews.day": { en: "Renews every day until cancelled." },
43
+ "billing-foundation.consent.renews.week": { en: "Renews every week until cancelled." },
44
+ "billing-foundation.consent.renews.month": { en: "Renews every month until cancelled." },
45
+ "billing-foundation.consent.renews.year": { en: "Renews every year until cancelled." },
46
+ "billing-foundation.consent.renewsEvery.day": {
47
+ en: "Renews every {count} days until cancelled.",
48
+ },
49
+ "billing-foundation.consent.renewsEvery.week": {
50
+ en: "Renews every {count} weeks until cancelled.",
51
+ },
52
+ "billing-foundation.consent.renewsEvery.month": {
53
+ en: "Renews every {count} months until cancelled.",
54
+ },
55
+ "billing-foundation.consent.renewsEvery.year": {
56
+ en: "Renews every {count} years until cancelled.",
57
+ },
58
+ "billing-foundation.consent.link.terms": { en: "Terms" },
59
+ "billing-foundation.consent.link.withdrawal": { en: "Withdrawal policy" },
60
+ "billing-foundation.consent.link.privacy": { en: "Privacy policy" },
61
+ "billing-foundation.consent.order": { en: "Order with obligation to pay" },
62
+ "billing-foundation.consent.back": { en: "Back" },
63
+ "billing-foundation.cancel.open": { en: "Cancel contract here" },
64
+ "billing-foundation.cancel.title": { en: "Cancel contract" },
65
+ "billing-foundation.cancel.declaration": { en: "Declaration" },
66
+ "billing-foundation.cancel.declaration.termination": { en: "Termination" },
67
+ "billing-foundation.cancel.declaration.withdrawal": { en: "Withdrawal" },
68
+ "billing-foundation.cancel.withdrawalHint": {
69
+ en: "A withdrawal is only possible within the statutory withdrawal period.",
70
+ },
71
+ "billing-foundation.cancel.kind": { en: "Type of termination" },
72
+ "billing-foundation.cancel.kind.ordinary": { en: "Ordinary" },
73
+ "billing-foundation.cancel.kind.extraordinary": { en: "Extraordinary (for cause)" },
74
+ "billing-foundation.cancel.reason": { en: "Reason" },
75
+ "billing-foundation.cancel.continue": { en: "Continue" },
76
+ "billing-foundation.cancel.back": { en: "Back" },
77
+ "billing-foundation.cancel.close": { en: "Close" },
78
+ "billing-foundation.cancel.confirmQuestion": {
79
+ en: "Do you want to submit this declaration now? Declaration: {declaration}, type: {kind}.",
80
+ },
81
+ "billing-foundation.cancel.submit": { en: "Cancel now" },
82
+ "billing-foundation.cancel.receivedAt": { en: "We received your declaration on {date}." },
83
+ "billing-foundation.cancel.effectiveAt": { en: "It takes effect on {date}." },
84
+ "billing-foundation.cancel.effectiveUnknown": {
85
+ en: "We will confirm the effective date by email.",
86
+ },
87
+ "billing-foundation.cancel.emailConfirmation": {
88
+ en: "You will receive a confirmation by email.",
89
+ },
38
90
  "billing-foundation.errors.redirectOriginNotAllowed": {
39
91
  en: "This redirect URL is not allowed for this app.",
40
92
  },
@@ -67,4 +119,13 @@ export const BILLING_FOUNDATION_I18N = {
67
119
  "billing-foundation.errors.priceUnavailable": {
68
120
  en: "This plan's price is temporarily unavailable.",
69
121
  },
122
+ "billing-foundation.errors.consentRequired": {
123
+ en: "Confirm both consent boxes before you continue.",
124
+ },
125
+ "billing-foundation.errors.consentTextOutdated": {
126
+ en: "The consent text has changed. Reload the page and confirm again.",
127
+ },
128
+ "billing-foundation.errors.termsUnavailable": {
129
+ en: "The terms are temporarily unavailable. Please try again later.",
130
+ },
70
131
  };
@@ -1,7 +1,8 @@
1
1
  export { paymentAggregateId, subscriptionAggregateId } from "./aggregate-id.js";
2
2
  export { type BillingInfo, type BillingInfoQueryDeps, createBillingInfoQueryConfig, } from "./billing-info-query.js";
3
3
  export { isBillingEnabled, type ResolvedProvider, resolveProviderPlugin, } from "./checkout-core.js";
4
- export { BILLING_FOUNDATION_FEATURE, BILLING_PLANS_PANEL_COMPONENT, BILLING_PLANS_SCREEN_ID, type BillingEventKind, BillingEventKinds, type BillingPlanAction, BillingPlanActions, DEFAULT_PURCHASE_ROLES, isSwitchableSubscriptionStatus, isTerminalSubscriptionStatus, SUBSCRIPTION_PROVIDER_EXTENSION, type SubscriptionEventType, SubscriptionEventTypes, SubscriptionFoundationHandlers, SubscriptionFoundationQueries, type SubscriptionStatus, SubscriptionStatuses, } from "./constants.js";
4
+ export { BILLING_FOUNDATION_FEATURE, BILLING_PLANS_PANEL_COMPONENT, BILLING_PLANS_SCREEN_ID, type BillingEventKind, BillingEventKinds, type BillingPlanAction, BillingPlanActions, DEFAULT_PURCHASE_ROLES, isSwitchableSubscriptionStatus, isTerminalSubscriptionStatus, parseProviderConsentId, SUBSCRIPTION_PROVIDER_EXTENSION, type SubscriptionCancelTiming, SubscriptionCancelTimings, type SubscriptionEventType, SubscriptionEventTypes, SubscriptionFoundationHandlers, SubscriptionFoundationQueries, type SubscriptionStatus, SubscriptionStatuses, } from "./constants.js";
5
+ export { type ContractTerminationRoutesOptions, createContractTerminationRoutes, } from "./consumer-protection/termination-pages.js";
5
6
  export { paymentEntity, subscriptionEntity } from "./entities.js";
6
7
  export { INVOICE_PAID_EVENT_QN, INVOICE_PAID_EVENT_SHORT, INVOICE_PAYMENT_FAILED_EVENT_QN, INVOICE_PAYMENT_FAILED_EVENT_SHORT, PAYMENT_AGGREGATE_TYPE, PAYMENT_RECEIVED_EVENT_QN, PAYMENT_RECEIVED_EVENT_SHORT, type PaymentEventHeaders, type PaymentEventPayload, paymentEventPayloadSchema, SUBSCRIPTION_AGGREGATE_TYPE, SUBSCRIPTION_CANCELED_EVENT_QN, SUBSCRIPTION_CANCELED_EVENT_SHORT, SUBSCRIPTION_CREATED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_SHORT, SUBSCRIPTION_UPDATED_EVENT_QN, SUBSCRIPTION_UPDATED_EVENT_SHORT, type SubscriptionEventHeaders, type SubscriptionEventPayload, subscriptionEventPayloadSchema, } from "./events.js";
7
8
  export { billingFoundationFeature, createBillingFoundationFeature } from "./feature.js";
@@ -9,5 +10,5 @@ export { getSubscriptionForTenant, type SubscriptionView } from "./get-subscript
9
10
  export { paymentsProjectionTable, subscriptionsProjectionTable } from "./projection.js";
10
11
  export { billingPlansPanel, createBillingPlansScreen } from "./screens.js";
11
12
  export { createSubscriptionTierSync, effectiveTierFromSubscription, SUBSCRIPTION_WEBHOOK_PATH, type SubscriptionTierSyncDeps, } from "./subscription-tier-sync.js";
12
- export { type BillingFoundationOptions, type BillingPlanBenefit, type BillingPlanCatalog, type BillingPlanPrice, type BillingPlansResult, type BillingPlanView, KNOWN_RECURRING_INTERVALS, type PaymentEvent, type ProviderPrice, type ProviderSubscriptionSnapshot, type RecurringInterval, type SubscriptionEvent, type SubscriptionProviderPlugin, } from "./types.js";
13
+ export { type BillingFoundationOptions, type BillingPlanBenefit, type BillingPlanCatalog, type BillingPlanPrice, type BillingPlansResult, type BillingPlanView, type ConsumerProtectionOptions, KNOWN_RECURRING_INTERVALS, type PaymentEvent, type ProviderPrice, type ProviderSubscriptionSnapshot, type RecurringInterval, type SubscriptionEvent, type SubscriptionProviderPlugin, } from "./types.js";
13
14
  export { createSubscriptionWebhookRoute, type SubscriptionWebhookRouteOptions, } from "./webhook-handler.js";
@@ -2,7 +2,8 @@
2
2
  export { paymentAggregateId, subscriptionAggregateId } from "./aggregate-id.js";
3
3
  export { createBillingInfoQueryConfig, } from "./billing-info-query.js";
4
4
  export { isBillingEnabled, resolveProviderPlugin, } from "./checkout-core.js";
5
- export { BILLING_FOUNDATION_FEATURE, BILLING_PLANS_PANEL_COMPONENT, BILLING_PLANS_SCREEN_ID, BillingEventKinds, BillingPlanActions, DEFAULT_PURCHASE_ROLES, isSwitchableSubscriptionStatus, isTerminalSubscriptionStatus, SUBSCRIPTION_PROVIDER_EXTENSION, SubscriptionEventTypes, SubscriptionFoundationHandlers, SubscriptionFoundationQueries, SubscriptionStatuses, } from "./constants.js";
5
+ export { BILLING_FOUNDATION_FEATURE, BILLING_PLANS_PANEL_COMPONENT, BILLING_PLANS_SCREEN_ID, BillingEventKinds, BillingPlanActions, DEFAULT_PURCHASE_ROLES, isSwitchableSubscriptionStatus, isTerminalSubscriptionStatus, parseProviderConsentId, SUBSCRIPTION_PROVIDER_EXTENSION, SubscriptionCancelTimings, SubscriptionEventTypes, SubscriptionFoundationHandlers, SubscriptionFoundationQueries, SubscriptionStatuses, } from "./constants.js";
6
+ export { createContractTerminationRoutes, } from "./consumer-protection/termination-pages.js";
6
7
  export { paymentEntity, subscriptionEntity } from "./entities.js";
7
8
  export { INVOICE_PAID_EVENT_QN, INVOICE_PAID_EVENT_SHORT, INVOICE_PAYMENT_FAILED_EVENT_QN, INVOICE_PAYMENT_FAILED_EVENT_SHORT, PAYMENT_AGGREGATE_TYPE, PAYMENT_RECEIVED_EVENT_QN, PAYMENT_RECEIVED_EVENT_SHORT, paymentEventPayloadSchema, SUBSCRIPTION_AGGREGATE_TYPE, SUBSCRIPTION_CANCELED_EVENT_QN, SUBSCRIPTION_CANCELED_EVENT_SHORT, SUBSCRIPTION_CREATED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_SHORT, SUBSCRIPTION_UPDATED_EVENT_QN, SUBSCRIPTION_UPDATED_EVENT_SHORT, subscriptionEventPayloadSchema, } from "./events.js";
8
9
  export { billingFoundationFeature, createBillingFoundationFeature } from "./feature.js";
@@ -20,7 +20,7 @@ export const PAYMENT_TENANT_DESTROY_ARCHIVE_REASON = "tenant-destroy looks up th
20
20
  export async function subscriptionTenantDestroyHook(ctx) {
21
21
  declareEscapeHatch({ reason: SUBSCRIPTION_TENANT_DESTROY_ARCHIVE_REASON });
22
22
  const { profile } = await resolveProfileForTenant({
23
- db: ctx.db.unsafeRaw(SUBSCRIPTION_TENANT_DESTROY_ARCHIVE_REASON),
23
+ db: ctx.db.unsafeRaw(),
24
24
  tenantId: ctx.tenantId,
25
25
  });
26
26
  const aggregateId = subscriptionAggregateId(ctx.tenantId);
@@ -30,7 +30,7 @@ export async function subscriptionTenantDestroyHook(ctx) {
30
30
  else {
31
31
  await ctx.db.deleteMany(subscriptionsProjectionTable, { id: aggregateId });
32
32
  }
33
- await archiveStream(ctx.db.unsafeRaw(SUBSCRIPTION_TENANT_DESTROY_ARCHIVE_REASON), {
33
+ await archiveStream(ctx.db.unsafeRaw(), {
34
34
  tenantId: ctx.tenantId,
35
35
  aggregateId,
36
36
  aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
@@ -46,7 +46,7 @@ export async function subscriptionTenantDestroyHook(ctx) {
46
46
  export async function paymentTenantDestroyHook(ctx) {
47
47
  declareEscapeHatch({ reason: PAYMENT_TENANT_DESTROY_ARCHIVE_REASON });
48
48
  const { profile } = await resolveProfileForTenant({
49
- db: ctx.db.unsafeRaw(PAYMENT_TENANT_DESTROY_ARCHIVE_REASON),
49
+ db: ctx.db.unsafeRaw(),
50
50
  tenantId: ctx.tenantId,
51
51
  });
52
52
  const aggregateId = paymentAggregateId(ctx.tenantId);
@@ -58,7 +58,7 @@ export async function paymentTenantDestroyHook(ctx) {
58
58
  tenantId: ctx.tenantId,
59
59
  });
60
60
  }
61
- await archiveStream(ctx.db.unsafeRaw(PAYMENT_TENANT_DESTROY_ARCHIVE_REASON), {
61
+ await archiveStream(ctx.db.unsafeRaw(), {
62
62
  tenantId: ctx.tenantId,
63
63
  aggregateId,
64
64
  aggregateType: PAYMENT_AGGREGATE_TYPE,