@cosmicdrift/kumiko-bundled-features 0.1.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 (333) hide show
  1. package/package.json +90 -0
  2. package/src/audit/__tests__/audit.integration.ts +328 -0
  3. package/src/audit/constants.ts +7 -0
  4. package/src/audit/feature.ts +23 -0
  5. package/src/audit/handlers/list.query.ts +98 -0
  6. package/src/audit/index.ts +2 -0
  7. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +149 -0
  8. package/src/auth-email-password/__tests__/account-lockout.integration.ts +308 -0
  9. package/src/auth-email-password/__tests__/auth-claims.integration.ts +512 -0
  10. package/src/auth-email-password/__tests__/auth.integration.ts +610 -0
  11. package/src/auth-email-password/__tests__/confirm-token-flow.test.ts +67 -0
  12. package/src/auth-email-password/__tests__/email-templates.test.ts +106 -0
  13. package/src/auth-email-password/__tests__/email-verification.integration.ts +327 -0
  14. package/src/auth-email-password/__tests__/identity-v3-hash.test.ts +174 -0
  15. package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +150 -0
  16. package/src/auth-email-password/__tests__/invite-flow.integration.ts +458 -0
  17. package/src/auth-email-password/__tests__/multi-roles.integration.ts +256 -0
  18. package/src/auth-email-password/__tests__/password-reset.integration.ts +346 -0
  19. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +144 -0
  20. package/src/auth-email-password/__tests__/seed-admin.integration.ts +176 -0
  21. package/src/auth-email-password/__tests__/session-callbacks.integration.ts +310 -0
  22. package/src/auth-email-password/__tests__/session-strict-mode.integration.ts +101 -0
  23. package/src/auth-email-password/__tests__/signed-token.test.ts +78 -0
  24. package/src/auth-email-password/__tests__/signup-flow.integration.ts +259 -0
  25. package/src/auth-email-password/auth-user-row.ts +41 -0
  26. package/src/auth-email-password/constants.ts +101 -0
  27. package/src/auth-email-password/email-templates.ts +283 -0
  28. package/src/auth-email-password/feature.ts +140 -0
  29. package/src/auth-email-password/handlers/change-password.write.ts +58 -0
  30. package/src/auth-email-password/handlers/confirm-token-flow.ts +191 -0
  31. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +203 -0
  32. package/src/auth-email-password/handlers/invite-accept.write.ts +189 -0
  33. package/src/auth-email-password/handlers/invite-create.write.ts +145 -0
  34. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +192 -0
  35. package/src/auth-email-password/handlers/login.write.ts +208 -0
  36. package/src/auth-email-password/handlers/logout.write.ts +12 -0
  37. package/src/auth-email-password/handlers/request-email-verification.write.ts +29 -0
  38. package/src/auth-email-password/handlers/request-password-reset.write.ts +31 -0
  39. package/src/auth-email-password/handlers/reset-password.write.ts +61 -0
  40. package/src/auth-email-password/handlers/signup-confirm.write.ts +170 -0
  41. package/src/auth-email-password/handlers/signup-request.write.ts +104 -0
  42. package/src/auth-email-password/handlers/token-request-handler.ts +114 -0
  43. package/src/auth-email-password/handlers/verify-email.write.ts +62 -0
  44. package/src/auth-email-password/i18n.ts +211 -0
  45. package/src/auth-email-password/identity-v3-hash.ts +97 -0
  46. package/src/auth-email-password/index.ts +35 -0
  47. package/src/auth-email-password/invite-token-store.ts +92 -0
  48. package/src/auth-email-password/lockout-store.ts +118 -0
  49. package/src/auth-email-password/password-hashing.ts +43 -0
  50. package/src/auth-email-password/reset-token.ts +28 -0
  51. package/src/auth-email-password/seeding.ts +183 -0
  52. package/src/auth-email-password/signed-token.ts +85 -0
  53. package/src/auth-email-password/signup-token-store.ts +104 -0
  54. package/src/auth-email-password/stream-tenant.ts +31 -0
  55. package/src/auth-email-password/testing.ts +5 -0
  56. package/src/auth-email-password/token-burn-store.ts +57 -0
  57. package/src/auth-email-password/verification-token.ts +27 -0
  58. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +51 -0
  59. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +80 -0
  60. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +94 -0
  61. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +108 -0
  62. package/src/auth-email-password/web/__tests__/session-roles.test.ts +54 -0
  63. package/src/auth-email-password/web/__tests__/tenant-switcher.test.tsx +100 -0
  64. package/src/auth-email-password/web/__tests__/test-utils.tsx +73 -0
  65. package/src/auth-email-password/web/__tests__/user-menu.test.tsx +55 -0
  66. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +59 -0
  67. package/src/auth-email-password/web/auth-client.ts +350 -0
  68. package/src/auth-email-password/web/auth-form-primitives.tsx +70 -0
  69. package/src/auth-email-password/web/auth-gate.tsx +33 -0
  70. package/src/auth-email-password/web/client-plugin.ts +48 -0
  71. package/src/auth-email-password/web/default-topbar-actions.tsx +47 -0
  72. package/src/auth-email-password/web/forgot-password-screen.tsx +110 -0
  73. package/src/auth-email-password/web/index.ts +56 -0
  74. package/src/auth-email-password/web/invite-accept-screen.tsx +220 -0
  75. package/src/auth-email-password/web/login-screen.tsx +150 -0
  76. package/src/auth-email-password/web/reset-password-screen.tsx +152 -0
  77. package/src/auth-email-password/web/session.tsx +171 -0
  78. package/src/auth-email-password/web/signup-complete-screen.tsx +150 -0
  79. package/src/auth-email-password/web/signup-screen.tsx +130 -0
  80. package/src/auth-email-password/web/tenant-switcher.tsx +116 -0
  81. package/src/auth-email-password/web/use-shell-user.ts +34 -0
  82. package/src/auth-email-password/web/user-menu.tsx +89 -0
  83. package/src/auth-email-password/web/verify-email-screen.tsx +102 -0
  84. package/src/billing-foundation/__tests__/billing-foundation.integration.ts +568 -0
  85. package/src/billing-foundation/__tests__/feature.test.ts +110 -0
  86. package/src/billing-foundation/__tests__/webhook-handler.test.ts +199 -0
  87. package/src/billing-foundation/aggregate-id.ts +21 -0
  88. package/src/billing-foundation/constants.ts +70 -0
  89. package/src/billing-foundation/entities.ts +50 -0
  90. package/src/billing-foundation/events.ts +71 -0
  91. package/src/billing-foundation/feature.ts +122 -0
  92. package/src/billing-foundation/get-subscription-for-tenant.ts +39 -0
  93. package/src/billing-foundation/handlers/create-checkout-session.write.ts +79 -0
  94. package/src/billing-foundation/handlers/create-portal-session.write.ts +73 -0
  95. package/src/billing-foundation/handlers/list-subscriptions.query.ts +20 -0
  96. package/src/billing-foundation/handlers/process-event.write.ts +160 -0
  97. package/src/billing-foundation/index.ts +42 -0
  98. package/src/billing-foundation/projection.ts +135 -0
  99. package/src/billing-foundation/types.ts +157 -0
  100. package/src/billing-foundation/webhook-handler.ts +184 -0
  101. package/src/cap-counter/__tests__/cap-counter.integration.ts +566 -0
  102. package/src/cap-counter/__tests__/enforce-cap.test.ts +422 -0
  103. package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +265 -0
  104. package/src/cap-counter/aggregate-id.ts +61 -0
  105. package/src/cap-counter/constants.ts +32 -0
  106. package/src/cap-counter/enforce-cap.ts +404 -0
  107. package/src/cap-counter/entity.ts +48 -0
  108. package/src/cap-counter/feature.ts +90 -0
  109. package/src/cap-counter/handlers/get-counter.query.ts +43 -0
  110. package/src/cap-counter/handlers/increment-rolling.write.ts +79 -0
  111. package/src/cap-counter/handlers/increment.write.ts +92 -0
  112. package/src/cap-counter/handlers/mark-soft-warned.write.ts +57 -0
  113. package/src/cap-counter/index.ts +34 -0
  114. package/src/cap-counter/with-cap-enforcement.ts +179 -0
  115. package/src/channel-email/email-channel.ts +48 -0
  116. package/src/channel-email/feature.ts +15 -0
  117. package/src/channel-email/index.ts +4 -0
  118. package/src/channel-email/smtp-transport.ts +65 -0
  119. package/src/channel-email/types.ts +34 -0
  120. package/src/channel-in-app/constants.ts +11 -0
  121. package/src/channel-in-app/feature.ts +30 -0
  122. package/src/channel-in-app/handlers/inbox.query.ts +28 -0
  123. package/src/channel-in-app/handlers/mark-all-read.write.ts +21 -0
  124. package/src/channel-in-app/handlers/mark-read.write.ts +32 -0
  125. package/src/channel-in-app/handlers/unread-count.query.ts +20 -0
  126. package/src/channel-in-app/in-app-channel.ts +44 -0
  127. package/src/channel-in-app/index.ts +4 -0
  128. package/src/channel-in-app/tables.ts +22 -0
  129. package/src/channel-push/feature.ts +15 -0
  130. package/src/channel-push/index.ts +3 -0
  131. package/src/channel-push/push-channel.ts +33 -0
  132. package/src/channel-push/types.ts +22 -0
  133. package/src/config/__tests__/app-overrides.test.ts +118 -0
  134. package/src/config/__tests__/config.integration.ts +1246 -0
  135. package/src/config/constants.ts +23 -0
  136. package/src/config/feature.ts +117 -0
  137. package/src/config/handlers/__tests__/prepare-config-write.test.ts +209 -0
  138. package/src/config/handlers/reset.write.ts +45 -0
  139. package/src/config/handlers/schema.query.ts +22 -0
  140. package/src/config/handlers/set.write.ts +93 -0
  141. package/src/config/handlers/values.query.ts +43 -0
  142. package/src/config/index.ts +15 -0
  143. package/src/config/resolver.ts +283 -0
  144. package/src/config/table.ts +35 -0
  145. package/src/config/write-helpers.ts +268 -0
  146. package/src/delivery/__tests__/delivery-events.integration.ts +166 -0
  147. package/src/delivery/__tests__/delivery.integration.ts +1405 -0
  148. package/src/delivery/constants.ts +33 -0
  149. package/src/delivery/delivery-service.ts +489 -0
  150. package/src/delivery/events.ts +18 -0
  151. package/src/delivery/feature.ts +70 -0
  152. package/src/delivery/handlers/log.query.ts +21 -0
  153. package/src/delivery/handlers/preferences.query.ts +18 -0
  154. package/src/delivery/handlers/set-preference.write.ts +28 -0
  155. package/src/delivery/index.ts +35 -0
  156. package/src/delivery/tables.ts +74 -0
  157. package/src/delivery/testing.ts +47 -0
  158. package/src/delivery/types.ts +71 -0
  159. package/src/delivery/unsubscribe.ts +99 -0
  160. package/src/delivery/upsert-preference.ts +145 -0
  161. package/src/feature-toggles/__tests__/feature-toggles.integration.ts +687 -0
  162. package/src/feature-toggles/constants.ts +20 -0
  163. package/src/feature-toggles/events.ts +18 -0
  164. package/src/feature-toggles/feature.ts +98 -0
  165. package/src/feature-toggles/global-feature-state-table.ts +28 -0
  166. package/src/feature-toggles/handlers/list.query.ts +26 -0
  167. package/src/feature-toggles/handlers/registered.query.ts +56 -0
  168. package/src/feature-toggles/handlers/set.write.ts +158 -0
  169. package/src/feature-toggles/index.ts +9 -0
  170. package/src/feature-toggles/toggle-runtime.ts +73 -0
  171. package/src/file-foundation/__tests__/feature.test.ts +35 -0
  172. package/src/file-foundation/__tests__/file-foundation.integration.ts +235 -0
  173. package/src/file-foundation/feature.ts +123 -0
  174. package/src/file-foundation/index.ts +7 -0
  175. package/src/file-provider-inmemory/__tests__/feature.test.ts +35 -0
  176. package/src/file-provider-inmemory/feature.ts +73 -0
  177. package/src/file-provider-inmemory/index.ts +3 -0
  178. package/src/file-provider-s3/__tests__/feature.test.ts +54 -0
  179. package/src/file-provider-s3/feature.ts +169 -0
  180. package/src/file-provider-s3/index.ts +3 -0
  181. package/src/files-provider-s3/__tests__/env-helper.test.ts +161 -0
  182. package/src/files-provider-s3/__tests__/s3-provider.integration.ts +134 -0
  183. package/src/files-provider-s3/__tests__/s3-provider.test.ts +36 -0
  184. package/src/files-provider-s3/env-helper.ts +49 -0
  185. package/src/files-provider-s3/index.ts +3 -0
  186. package/src/files-provider-s3/s3-provider.ts +114 -0
  187. package/src/foundation-shared/config-helpers.ts +67 -0
  188. package/src/foundation-shared/index.ts +4 -0
  189. package/src/jobs/__tests__/job-system-user.integration.ts +194 -0
  190. package/src/jobs/__tests__/jobs-events.integration.ts +143 -0
  191. package/src/jobs/__tests__/jobs-feature.integration.ts +342 -0
  192. package/src/jobs/constants.ts +21 -0
  193. package/src/jobs/events.ts +39 -0
  194. package/src/jobs/feature.ts +150 -0
  195. package/src/jobs/handlers/detail.query.ts +30 -0
  196. package/src/jobs/handlers/list.query.ts +36 -0
  197. package/src/jobs/handlers/retry.write.ts +69 -0
  198. package/src/jobs/handlers/trigger.write.ts +39 -0
  199. package/src/jobs/index.ts +5 -0
  200. package/src/jobs/job-run-logger.ts +213 -0
  201. package/src/jobs/job-run-table.ts +55 -0
  202. package/src/legal-pages/README.md +195 -0
  203. package/src/legal-pages/__tests__/legal-pages.integration.ts +361 -0
  204. package/src/legal-pages/constants.ts +36 -0
  205. package/src/legal-pages/feature.ts +187 -0
  206. package/src/legal-pages/index.ts +13 -0
  207. package/src/legal-pages/markdown.ts +69 -0
  208. package/src/mail-foundation/__tests__/feature.test.ts +46 -0
  209. package/src/mail-foundation/__tests__/mail-foundation.integration.ts +247 -0
  210. package/src/mail-foundation/feature.ts +160 -0
  211. package/src/mail-foundation/index.ts +14 -0
  212. package/src/mail-transport-inmemory/__tests__/feature.test.ts +37 -0
  213. package/src/mail-transport-inmemory/feature.ts +90 -0
  214. package/src/mail-transport-inmemory/index.ts +3 -0
  215. package/src/mail-transport-smtp/__tests__/feature.test.ts +61 -0
  216. package/src/mail-transport-smtp/feature.ts +182 -0
  217. package/src/mail-transport-smtp/index.ts +3 -0
  218. package/src/rate-limiting/__tests__/rate-limiting.integration.ts +84 -0
  219. package/src/rate-limiting/constants.ts +9 -0
  220. package/src/rate-limiting/feature.ts +16 -0
  221. package/src/rate-limiting/handlers/status.query.ts +52 -0
  222. package/src/rate-limiting/index.ts +2 -0
  223. package/src/renderer-simple/__tests__/simple-renderer.test.ts +97 -0
  224. package/src/renderer-simple/feature.ts +12 -0
  225. package/src/renderer-simple/index.ts +2 -0
  226. package/src/renderer-simple/simple-renderer.ts +72 -0
  227. package/src/secrets/__tests__/rotate.integration.ts +176 -0
  228. package/src/secrets/__tests__/secrets-events.integration.ts +125 -0
  229. package/src/secrets/__tests__/secrets.integration.ts +118 -0
  230. package/src/secrets/feature.ts +84 -0
  231. package/src/secrets/handlers/delete.write.ts +20 -0
  232. package/src/secrets/handlers/list.query.ts +38 -0
  233. package/src/secrets/handlers/rotate.job.ts +193 -0
  234. package/src/secrets/handlers/set.write.ts +50 -0
  235. package/src/secrets/index.ts +16 -0
  236. package/src/secrets/secrets-context.ts +296 -0
  237. package/src/secrets/table.ts +68 -0
  238. package/src/sessions/__tests__/cleanup.integration.ts +175 -0
  239. package/src/sessions/__tests__/password-auto-revoke.integration.ts +202 -0
  240. package/src/sessions/__tests__/sessions.integration.ts +472 -0
  241. package/src/sessions/__tests__/test-helpers.ts +66 -0
  242. package/src/sessions/constants.ts +43 -0
  243. package/src/sessions/feature.ts +84 -0
  244. package/src/sessions/handlers/cleanup.job.ts +109 -0
  245. package/src/sessions/handlers/list.query.ts +35 -0
  246. package/src/sessions/handlers/mine.query.ts +37 -0
  247. package/src/sessions/handlers/revoke-all-others.write.ts +42 -0
  248. package/src/sessions/handlers/revoke.write.ts +76 -0
  249. package/src/sessions/index.ts +17 -0
  250. package/src/sessions/schema/index.ts +5 -0
  251. package/src/sessions/schema/user-session.ts +67 -0
  252. package/src/sessions/session-callbacks.ts +110 -0
  253. package/src/sessions/testing.ts +42 -0
  254. package/src/subscription-mollie/__tests__/feature.test.ts +106 -0
  255. package/src/subscription-mollie/__tests__/mollie-foundation.integration.ts +421 -0
  256. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +388 -0
  257. package/src/subscription-mollie/constants.ts +33 -0
  258. package/src/subscription-mollie/feature.ts +144 -0
  259. package/src/subscription-mollie/index.ts +13 -0
  260. package/src/subscription-mollie/plugin-methods.ts +79 -0
  261. package/src/subscription-mollie/verify-webhook.ts +244 -0
  262. package/src/subscription-stripe/__tests__/feature.test.ts +98 -0
  263. package/src/subscription-stripe/__tests__/plugin-methods.test.ts +161 -0
  264. package/src/subscription-stripe/__tests__/stripe-foundation.integration.ts +315 -0
  265. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +306 -0
  266. package/src/subscription-stripe/constants.ts +20 -0
  267. package/src/subscription-stripe/feature.ts +120 -0
  268. package/src/subscription-stripe/index.ts +14 -0
  269. package/src/subscription-stripe/plugin-methods.ts +91 -0
  270. package/src/subscription-stripe/verify-webhook.ts +235 -0
  271. package/src/tenant/__tests__/multi-tenant.integration.ts +278 -0
  272. package/src/tenant/__tests__/seed-testing.integration.ts +229 -0
  273. package/src/tenant/__tests__/tenant.integration.ts +347 -0
  274. package/src/tenant/command-schemas.ts +37 -0
  275. package/src/tenant/constants.ts +37 -0
  276. package/src/tenant/feature.ts +109 -0
  277. package/src/tenant/handlers/active-tenant-ids.query.ts +19 -0
  278. package/src/tenant/handlers/add-member.write.ts +53 -0
  279. package/src/tenant/handlers/cancel-invitation.write.ts +87 -0
  280. package/src/tenant/handlers/create.write.ts +21 -0
  281. package/src/tenant/handlers/disable.write.ts +18 -0
  282. package/src/tenant/handlers/invitations.query.ts +31 -0
  283. package/src/tenant/handlers/list.query.ts +17 -0
  284. package/src/tenant/handlers/me.query.ts +17 -0
  285. package/src/tenant/handlers/members.query.ts +22 -0
  286. package/src/tenant/handlers/memberships.query.ts +24 -0
  287. package/src/tenant/handlers/remove-member.write.ts +40 -0
  288. package/src/tenant/handlers/resolve-user-ids.query.ts +43 -0
  289. package/src/tenant/handlers/update-member-roles.write.ts +54 -0
  290. package/src/tenant/handlers/update.write.ts +20 -0
  291. package/src/tenant/index.ts +12 -0
  292. package/src/tenant/invitation-table.ts +93 -0
  293. package/src/tenant/membership-table.ts +35 -0
  294. package/src/tenant/schema/index.ts +5 -0
  295. package/src/tenant/schema/tenant.ts +27 -0
  296. package/src/tenant/seeding.ts +155 -0
  297. package/src/tenant/testing.ts +8 -0
  298. package/src/text-content/README.md +190 -0
  299. package/src/text-content/__tests__/text-content.integration.ts +415 -0
  300. package/src/text-content/api.ts +92 -0
  301. package/src/text-content/constants.ts +19 -0
  302. package/src/text-content/feature.ts +29 -0
  303. package/src/text-content/handlers/by-slug.query.ts +55 -0
  304. package/src/text-content/handlers/set.write.ts +118 -0
  305. package/src/text-content/index.ts +14 -0
  306. package/src/text-content/seeding.ts +91 -0
  307. package/src/text-content/table.ts +45 -0
  308. package/src/tier-engine/__tests__/compose-app.test.ts +182 -0
  309. package/src/tier-engine/__tests__/drift.test.ts +42 -0
  310. package/src/tier-engine/__tests__/tier-engine.integration.ts +241 -0
  311. package/src/tier-engine/aggregate-id.ts +27 -0
  312. package/src/tier-engine/compose-app.ts +150 -0
  313. package/src/tier-engine/constants.ts +15 -0
  314. package/src/tier-engine/entity.ts +30 -0
  315. package/src/tier-engine/feature.ts +72 -0
  316. package/src/tier-engine/handlers/active-tier.query.ts +23 -0
  317. package/src/tier-engine/index.ts +22 -0
  318. package/src/user/__tests__/seed-testing.integration.ts +127 -0
  319. package/src/user/__tests__/user.integration.ts +198 -0
  320. package/src/user/command-schemas.ts +15 -0
  321. package/src/user/constants.ts +23 -0
  322. package/src/user/feature.ts +32 -0
  323. package/src/user/handlers/create.write.ts +54 -0
  324. package/src/user/handlers/detail.query.ts +9 -0
  325. package/src/user/handlers/find-for-auth.query.ts +38 -0
  326. package/src/user/handlers/list.query.ts +8 -0
  327. package/src/user/handlers/me.query.ts +15 -0
  328. package/src/user/handlers/update.write.ts +54 -0
  329. package/src/user/index.ts +4 -0
  330. package/src/user/schema/index.ts +5 -0
  331. package/src/user/schema/user.ts +69 -0
  332. package/src/user/seeding.ts +93 -0
  333. package/src/user/testing.ts +5 -0
@@ -0,0 +1,157 @@
1
+ // Plugin-Contract + Domain-Types der subscription-foundation. Provider-
2
+ // Plugins (subscription-stripe, subscription-mollie, ...) implementieren
3
+ // `SubscriptionProviderPlugin` und registrieren via
4
+ // `r.useExtension("subscriptionProvider", "<name>", { build })`.
5
+ //
6
+ // **Two-phase plugin contract:**
7
+ // 1. **Pre-tenant-resolution:** `verifyAndParseWebhook` läuft BEVOR
8
+ // ein Tenant aus dem Event aufgelöst ist — kein HandlerContext
9
+ // verfügbar. Plugin liest seinen webhook-secret aus
10
+ // module-load-Closure (ENV-VAR oder system-config), NICHT aus
11
+ // ctx. **Webhook-secret ist app-wide**, nicht per-tenant — das
12
+ // ist App-Owner's Stripe-/PayPal-Account, nicht Tenant-Sache.
13
+ // 2. **Post-tenant-resolution:** `createPortalSession` +
14
+ // `cancelSubscription` werden aus regulären write-handlern
15
+ // gerufen mit voll-aufgelöstem HandlerContext. Plugin kann
16
+ // hier ctx.config + ctx.secrets nutzen für tenant-spezifische
17
+ // Konfiguration (z.B. tenant-eigene customer-id-mapping).
18
+ //
19
+ // Foundation nutzt nur den common-subset der Provider-Funktionalität —
20
+ // proration, multi-currency, coupons etc. bleiben provider-spezifisch
21
+ // und sind über den Customer-Portal-Link erreichbar.
22
+
23
+ import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
24
+ import type { SubscriptionEventType, SubscriptionStatus } from "./constants";
25
+
26
+ // =============================================================================
27
+ // Normalisierter Webhook-Event
28
+ // =============================================================================
29
+ //
30
+ // Der Plugin parsed den raw provider-payload und liefert diese Form
31
+ // zurück. Foundation kennt KEINE Stripe-/Mollie-types direkt — der
32
+ // Plugin abstrahiert.
33
+
34
+ export type SubscriptionEvent = {
35
+ /** Provider-eigene Event-ID — UNIQUE-key für Idempotency.
36
+ * Stripe: "evt_..."; Mollie: payment-id oder subscription-id. */
37
+ readonly providerEventId: string;
38
+ /** Discriminator — welcher Plugin diesen Event geliefert hat. */
39
+ readonly providerName: string;
40
+ /** Normalisierter Event-Type. Plugin filtert provider-spezifische
41
+ * Sub-Types raus (Stripe hat ~80 Event-types, wir kennen 5). */
42
+ readonly type: SubscriptionEventType;
43
+ /** Plattform-Tenant-ID (provider-customer-metadata oder lookup
44
+ * via providerCustomerId). Plugin macht die resolution. */
45
+ readonly tenantId: string;
46
+ /** Provider-eigene customer-id für späteren Lookup bei events
47
+ * ohne metadata. */
48
+ readonly providerCustomerId: string;
49
+ /** Provider-eigene subscription-id. */
50
+ readonly providerSubscriptionId: string;
51
+ /** Normalisierter status. */
52
+ readonly status: SubscriptionStatus;
53
+ /** Resolved tier — Plugin liest die price-id aus dem event und
54
+ * mapped via plugin-eigenem `<plugin>:config:price-to-tier` auf
55
+ * einen tier-name. Wenn der price-id im Mapping fehlt, returnt
56
+ * der Plugin null aus verifyAndParseWebhook (= "unknown event,
57
+ * ignore"). */
58
+ readonly tier: string;
59
+ /** ISO-timestamp wann die aktuelle Billing-Period endet. */
60
+ readonly currentPeriodEnd: string;
61
+ /** Raw provider-payload — wird 1:1 in subscription-event.rawPayload
62
+ * archiviert. Plugin liefert das als JSON-stringified-string. */
63
+ readonly rawPayload: string;
64
+ };
65
+
66
+ // =============================================================================
67
+ // Plugin-Contract
68
+ // =============================================================================
69
+
70
+ export type SubscriptionProviderPlugin = {
71
+ /**
72
+ * Verify webhook signature + parse provider-event into normalized
73
+ * form. **Pre-tenant-resolution** — kein HandlerContext, weil zum
74
+ * Zeitpunkt des sig-verify der Tenant noch nicht aufgelöst ist
75
+ * (Plugin macht die Tenant-Resolution selbst aus dem provider-
76
+ * payload metadata).
77
+ *
78
+ * Plugin liest seinen webhook-secret aus module-load-Closure
79
+ * (process.env.STRIPE_WEBHOOK_SECRET oder system-config), NICHT
80
+ * aus ctx. App-wide-secret = App-Owner's eigener Provider-Account.
81
+ *
82
+ * Returns null für events die der Plugin nicht versteht oder die
83
+ * foundation nicht braucht (= filter out, foundation returnt 200
84
+ * "ignored").
85
+ *
86
+ * **Throws** bei sig-mismatch — der webhook-handler mapped das auf
87
+ * 401 damit der Provider keine retries macht (sig-fail = config-bug,
88
+ * nicht transient).
89
+ */
90
+ readonly verifyAndParseWebhook: (
91
+ rawBody: string,
92
+ headers: Record<string, string>,
93
+ ) => Promise<SubscriptionEvent | null>;
94
+
95
+ /**
96
+ * **Post-tenant-resolution** — wird aus dem
97
+ * `create-checkout-session`-write-handler gerufen. Plugin baut
98
+ * eine provider-eigene checkout-session und returnt die hosted-
99
+ * page-URL — Tenant-Admin wird dorthin redirected, schließt den
100
+ * Bezahl-Flow ab, Provider sendet `subscription.created`-webhook
101
+ * mit `metadata.tenantId` zurück.
102
+ *
103
+ * Optional: Apple-IAP hat keinen Web-Checkout (alles in der App).
104
+ * Apps die ausschließlich Apple-IAP nutzen lassen das weg.
105
+ */
106
+ readonly createCheckoutSession?: (
107
+ ctx: HandlerContext,
108
+ options: {
109
+ /** Provider-eigene price/plan-ID die der Endkunde abonniert. */
110
+ readonly priceId: string;
111
+ /** Plattform-Tenant-ID — landet als metadata im checkout-session
112
+ * damit der subsequent webhook den tenant resolved (siehe
113
+ * `verifyAndParseWebhook`'s metadata.tenantId-lookup). */
114
+ readonly tenantId: string;
115
+ /** Wo der Endkunde nach erfolgreichem checkout landed. */
116
+ readonly successUrl: string;
117
+ /** Wo der Endkunde landed wenn er abbricht. */
118
+ readonly cancelUrl: string;
119
+ /** Optional: existierende provider-customer-id wenn der Tenant
120
+ * schon einen Account beim Provider hat. Sonst legt der Provider
121
+ * beim checkout einen neuen customer an. */
122
+ readonly providerCustomerId?: string;
123
+ },
124
+ ) => Promise<{ readonly url: string }>;
125
+
126
+ /**
127
+ * **Post-tenant-resolution** — wird aus einem write-handler gerufen
128
+ * mit voll-aufgelöstem ctx. Erstellt einen self-service Portal-Link.
129
+ * Stripe: customer-portal-session, Mollie: hosted-management-page.
130
+ * Tenant-Admin klickt darauf um Subscription selbst zu verwalten
131
+ * (cancel, payment-method, ...).
132
+ *
133
+ * Optional weil nicht jeder Provider einen Portal-Pattern hat
134
+ * (Apple-IAP managed Subs in der Apple-App, kein Web-Portal).
135
+ * Plugin der das nicht supported kann das Field weglassen — foundation
136
+ * returnt dann "portal_not_supported"-error wenn ein Tenant-Admin
137
+ * den Portal-Link anfordert.
138
+ */
139
+ readonly createPortalSession?: (
140
+ ctx: HandlerContext,
141
+ options: {
142
+ readonly providerCustomerId: string;
143
+ readonly returnUrl: string;
144
+ },
145
+ ) => Promise<{ readonly url: string }>;
146
+
147
+ /**
148
+ * Optional: Cancel-aus-der-App-API. Wenn nicht implementiert, kann
149
+ * der Tenant nur über den Customer-Portal-Link cancellen (oder gar
150
+ * nicht, wenn auch createPortalSession fehlt — dann ist
151
+ * Cancel-Flow Provider-Dashboard-only).
152
+ */
153
+ readonly cancelSubscription?: (
154
+ ctx: HandlerContext,
155
+ providerSubscriptionId: string,
156
+ ) => Promise<void>;
157
+ };
@@ -0,0 +1,184 @@
1
+ // createSubscriptionWebhookHandler — Hono-route-factory den der App-
2
+ // Owner via `extraRoutes` in seinem bin/server.ts mountet.
3
+ //
4
+ // **Multi-Provider:** der Plugin wird via Pfad-Parameter
5
+ // `:providerName` ausgewählt — Stripe-Dashboard zeigt auf
6
+ // `/api/subscription/webhook/stripe`, PayPal auf
7
+ // `/api/subscription/webhook/paypal`. Eine Hono-Route, alle Plugins
8
+ // gleichzeitig aktiv.
9
+ //
10
+ // Beispiel-Verwendung in bin/server.ts:
11
+ //
12
+ // await runDevApp({
13
+ // features: APP_FEATURES,
14
+ // extraRoutes: (app, deps) => {
15
+ // const handler = createSubscriptionWebhookHandler(deps);
16
+ // app.post("/api/subscription/webhook/:providerName", handler);
17
+ // },
18
+ // });
19
+ //
20
+ // Was der handler macht:
21
+ // 1. providerName aus dem URL-Pfad lesen
22
+ // 2. raw-body via c.req.text() lesen (NICHT JSON-parsen — Stripe-Sig
23
+ // prüft exakte bytes)
24
+ // 3. Headers sammeln + an Plugin durchreichen
25
+ // 4. Plugin-Lookup im Registry via "subscriptionProvider"-extension
26
+ // und dem providerName aus dem URL-Pfad
27
+ // 5. plugin.verifyAndParseWebhook(raw, headers, ctx) → SubscriptionEvent | null
28
+ // 6. Bei null (= Plugin filtert event-type raus): 200 OK ohne side-effects
29
+ // 7. Bei Event: ctx.write("billing-foundation:write:process-event")
30
+ // mit der vom Plugin aufgelösten tenantId
31
+ // 8. Returnt 200 OK an Provider
32
+ //
33
+ // **Auth:** kein JWT/Cookie. Authentifizierung läuft via Provider-
34
+ // Webhook-Sig im Plugin. Kein `c.get("user")`-call hier.
35
+
36
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
37
+ import type { Context, Hono } from "hono";
38
+ import {
39
+ BILLING_FOUNDATION_FEATURE,
40
+ SUBSCRIPTION_PROVIDER_EXTENSION,
41
+ SubscriptionFoundationHandlers,
42
+ } from "./constants";
43
+ import type { SubscriptionProviderPlugin } from "./types";
44
+
45
+ /**
46
+ * Dependencies the App-Owner gibt dem webhook-handler. Identisch zum
47
+ * `extraRoutes`-Callback-Argument-shape von runDevApp/runProdApp.
48
+ */
49
+ export type SubscriptionWebhookDeps = {
50
+ /** Schreibt durch den Standard-Dispatcher mit einem auto-konstruierten
51
+ * SystemUser. Muss `process-event` als SystemAdmin durchlassen. */
52
+ readonly dispatchWrite: (args: {
53
+ readonly handlerQn: string;
54
+ readonly payload: unknown;
55
+ readonly tenantId: string;
56
+ }) => Promise<{
57
+ readonly isSuccess: boolean;
58
+ readonly data?: unknown;
59
+ readonly error?: unknown;
60
+ }>;
61
+
62
+ /** Plugin-Lookup-Function — bekommt den providerName aus dem URL-
63
+ * Pfad und returnt den passenden Plugin (= entityName-match in
64
+ * registry.getExtensionUsages("subscriptionProvider")). */
65
+ readonly resolveProvider: (providerName: string) => SubscriptionProviderPlugin | undefined;
66
+ };
67
+
68
+ /**
69
+ * Returnt einen Hono-handler. Mounten via
70
+ * `app.post("/api/subscription/webhook/:providerName", handler)`.
71
+ */
72
+ export function createSubscriptionWebhookHandler(deps: SubscriptionWebhookDeps) {
73
+ return async (c: Context): Promise<Response> => {
74
+ // 1. providerName aus URL-Pfad. Hono-Standard via c.req.param.
75
+ const providerName = c.req.param("providerName");
76
+ if (!providerName || providerName.length === 0) {
77
+ return c.json(
78
+ {
79
+ error: {
80
+ code: "subscription_provider_path_missing",
81
+ message: `${BILLING_FOUNDATION_FEATURE}: Mount the route as POST /api/subscription/webhook/:providerName so each provider has its own URL (Stripe-Dashboard → /stripe, PayPal-Dashboard → /paypal).`,
82
+ },
83
+ },
84
+ 400,
85
+ );
86
+ }
87
+
88
+ // 2. Raw-body. Provider-Sigs werden gegen die exakten bytes verifiziert.
89
+ const rawBody = await c.req.text();
90
+ const headers: Record<string, string> = {};
91
+ c.req.raw.headers.forEach((value, key) => {
92
+ headers[key.toLowerCase()] = value;
93
+ });
94
+
95
+ // 3. Plugin-Lookup via path-segment. Jeder gemountete Plugin hat
96
+ // sich mit `r.useExtension("subscriptionProvider", entityName,
97
+ // {...})` registriert; entityName matcht hier den path-segment.
98
+ const plugin = deps.resolveProvider(providerName);
99
+ if (!plugin) {
100
+ return c.json(
101
+ {
102
+ error: {
103
+ code: "subscription_provider_not_registered",
104
+ message: `${BILLING_FOUNDATION_FEATURE}: provider "${providerName}" not registered as '${SUBSCRIPTION_PROVIDER_EXTENSION}'-plugin. Mount the matching subscription-${providerName} feature.`,
105
+ },
106
+ },
107
+ 404,
108
+ );
109
+ }
110
+
111
+ // 4. Plugin verifies + parses. **Pre-tenant-resolution** — kein
112
+ // ctx, Plugin liest seinen webhook-secret aus eigener
113
+ // module-load-Closure (ENV-VAR oder system-config).
114
+ // Throws on sig-mismatch — wir mappen auf 401 (= "config-bug,
115
+ // retry won't help, Provider stopp").
116
+ let parsed: Awaited<ReturnType<SubscriptionProviderPlugin["verifyAndParseWebhook"]>>;
117
+ try {
118
+ parsed = await plugin.verifyAndParseWebhook(rawBody, headers);
119
+ } catch (e) {
120
+ const msg = e instanceof Error ? e.message : String(e);
121
+ return c.json(
122
+ {
123
+ error: {
124
+ code: "subscription_webhook_signature_invalid",
125
+ message: `Plugin "${providerName}" rejected webhook: ${msg}`,
126
+ },
127
+ },
128
+ 401,
129
+ );
130
+ }
131
+
132
+ // 5. Plugin returned null = "ich kenne diesen event-type nicht / ist
133
+ // nicht relevant". 200 OK damit der Provider keine retries macht.
134
+ if (parsed === null) {
135
+ return c.json({ ignored: true }, 200);
136
+ }
137
+
138
+ // 6. Dispatch process-event-handler durch den Standard-Dispatcher.
139
+ // Idempotency macht der handler intern via deterministic
140
+ // aggregate-id + UNIQUE-constraint.
141
+ const dispatched = await deps.dispatchWrite({
142
+ handlerQn: SubscriptionFoundationHandlers.processEvent,
143
+ tenantId: parsed.tenantId,
144
+ payload: {
145
+ providerEventId: parsed.providerEventId,
146
+ providerName: parsed.providerName,
147
+ type: parsed.type,
148
+ providerCustomerId: parsed.providerCustomerId,
149
+ providerSubscriptionId: parsed.providerSubscriptionId,
150
+ status: parsed.status,
151
+ tier: parsed.tier,
152
+ currentPeriodEndIso: parsed.currentPeriodEnd,
153
+ rawPayload: parsed.rawPayload,
154
+ },
155
+ });
156
+
157
+ if (!dispatched.isSuccess) {
158
+ // Internal error — Provider soll retry'n. 500 statt 401/404 weil
159
+ // das transient ist (DB down etc.) nicht config-bug.
160
+ return c.json(
161
+ {
162
+ error: {
163
+ code: "subscription_webhook_processing_failed",
164
+ message: "Internal error processing subscription event",
165
+ details: dispatched.error,
166
+ },
167
+ },
168
+ 500,
169
+ );
170
+ }
171
+
172
+ return c.json({ processed: true, ...((dispatched.data as object) ?? {}) }, 200);
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Convenience für TypeScript-IDE: `Hono.post(...)`-Call-Type damit der
178
+ * App-Owner-Code typed bleibt ohne Hono-types zu importieren.
179
+ */
180
+ export type SubscriptionWebhookHandler = ReturnType<typeof createSubscriptionWebhookHandler>;
181
+
182
+ // Re-export für convenience: App-Owner kann den TenantId-type aus dem
183
+ // gleichen Modul importieren (vermeidet ein zweites Framework-import).
184
+ export type { Hono, TenantId };