@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,120 @@
1
+ // kumiko-feature-version: 1
2
+ //
3
+ // subscription-stripe — Stripe-Plugin für die subscription-foundation
4
+ // Plugin-API.
5
+ //
6
+ // **Factory-Pattern (= createSubscriptionStripeFeature(options)):**
7
+ // Im Gegensatz zu mail-transport-smtp / file-provider-s3 (die ihre
8
+ // secrets aus tenant-secrets lesen) ist Stripe's webhook-secret
9
+ // **app-wide** — App-Owner hat einen Stripe-account, alle Webhooks
10
+ // gehen dorthin. Plugin braucht den secret beim webhook-sig-verify-
11
+ // Zeitpunkt, der ist PRE-tenant-resolution (kein ctx).
12
+ //
13
+ // Lösung: factory-Funktion `createSubscriptionStripeFeature(options)`
14
+ // liest webhook-secret + apiKey beim mount-time aus dem Caller (= App-
15
+ // Builder's bin/server.ts der's aus process.env zieht). Closure
16
+ // hält's für den verifyAndParseWebhook-call.
17
+ //
18
+ // Beispiel-Verwendung in run-config.ts:
19
+ //
20
+ // import { createSubscriptionStripeFeature } from "@cosmicdrift/kumiko-bundled-features/subscription-stripe";
21
+ //
22
+ // const features = [
23
+ // billingFoundationFeature,
24
+ // createSubscriptionStripeFeature({
25
+ // webhookSecret: process.env.STRIPE_WEBHOOK_SECRET ?? "",
26
+ // apiKey: process.env.STRIPE_API_KEY ?? "",
27
+ // priceToTier: {
28
+ // "price_1ABC": "pro",
29
+ // "price_1XYZ": "business",
30
+ // },
31
+ // }),
32
+ // ];
33
+ //
34
+ // **Pattern-Vorbild:** mirrors createFeatureTogglesFeature(options) —
35
+ // gleiche factory-Form für features die module-load-time-Konfiguration
36
+ // haben (analog zum FeatureToggle-runtime-holder).
37
+
38
+ import type { SubscriptionProviderPlugin } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
39
+ import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
40
+ import Stripe from "stripe";
41
+ import { STRIPE_PROVIDER_NAME, SUBSCRIPTION_STRIPE_FEATURE } from "./constants";
42
+ import {
43
+ createStripeCancelSubscription,
44
+ createStripeCheckoutSession,
45
+ createStripePortalSession,
46
+ } from "./plugin-methods";
47
+ import { verifyAndParseStripeWebhook } from "./verify-webhook";
48
+
49
+ export type SubscriptionStripeOptions = {
50
+ /** Webhook-secret aus dem Stripe-Dashboard. App-wide. Plugin throws
51
+ * beim runtime wenn empty (= App-Owner hat sub-stripe gemountet
52
+ * aber Stripe-Account nicht konfiguriert). */
53
+ readonly webhookSecret: string;
54
+ /** Stripe-API-key (sk_live_... / sk_test_...). Heute nur für
55
+ * constructEvent-API-Version-Pin gebraucht; Phase 5.2b nutzt's
56
+ * für outgoing-API-calls (createPortalSession etc.). */
57
+ readonly apiKey: string;
58
+ /** Price-to-tier-Mapping. Plugin liest die price-id aus Stripe-event
59
+ * (subscription.items.data[0].price.id) und mappt auf einen tier-
60
+ * name. Fehlt die price-id im Mapping → null (foundation 200
61
+ * ignored — App-Owner-Bug, hat den Stripe-price angelegt aber
62
+ * nicht zur tier zugeordnet). */
63
+ readonly priceToTier: Readonly<Record<string, string>>;
64
+ };
65
+
66
+ /**
67
+ * Factory für das subscription-stripe-feature. Wird mit den App-Owner-
68
+ * eigenen Stripe-Credentials gemountet. Der returnte FeatureDefinition
69
+ * registriert den Plugin gegen subscription-foundation's
70
+ * "subscriptionProvider"-extension-point unter entityName "stripe".
71
+ */
72
+ export function createSubscriptionStripeFeature(
73
+ options: SubscriptionStripeOptions,
74
+ ): FeatureDefinition {
75
+ // Module-load-Validation: ohne webhook-secret kann der Plugin keinen
76
+ // single Webhook verifizieren. Throw vor dem mount damit der App-
77
+ // Owner nicht zur Laufzeit Mystery-401s sieht.
78
+ if (options.webhookSecret.length === 0) {
79
+ throw new Error(
80
+ "subscription-stripe: webhookSecret is empty. Set STRIPE_WEBHOOK_SECRET (or system-config) before mounting.",
81
+ );
82
+ }
83
+ if (options.apiKey.length === 0) {
84
+ throw new Error(
85
+ "subscription-stripe: apiKey is empty. Set STRIPE_API_KEY (or system-config) before mounting.",
86
+ );
87
+ }
88
+
89
+ // EIN Stripe-Client für alle vier plugin-methods (verify-webhook +
90
+ // checkout + portal + cancel). API-version-pin zentral, kein
91
+ // Connection-Duplikat.
92
+ const stripe = new Stripe(options.apiKey, { apiVersion: "2026-04-22.dahlia" });
93
+
94
+ const verifyAndParse = verifyAndParseStripeWebhook(stripe, {
95
+ webhookSecret: options.webhookSecret,
96
+ priceToTier: options.priceToTier,
97
+ });
98
+ const checkoutSession = createStripeCheckoutSession(stripe);
99
+ const portalSession = createStripePortalSession(stripe);
100
+ const cancel = createStripeCancelSubscription(stripe);
101
+
102
+ return defineFeature(SUBSCRIPTION_STRIPE_FEATURE, (r) => {
103
+ // Hard-deps: subscription-foundation als plugin-host. KEIN
104
+ // `r.requires("config", "secrets")` — der Plugin nutzt weder
105
+ // tenant-config noch tenant-secrets (alles app-wide via factory-
106
+ // options).
107
+ r.requires("billing-foundation");
108
+
109
+ // Plugin: register against subscription-foundation's
110
+ // "subscriptionProvider" extension. entityName "stripe" matcht den
111
+ // path-segment in der webhook-URL (`/api/subscription/webhook/stripe`).
112
+ const plugin: SubscriptionProviderPlugin = {
113
+ verifyAndParseWebhook: verifyAndParse,
114
+ createCheckoutSession: checkoutSession,
115
+ createPortalSession: portalSession,
116
+ cancelSubscription: cancel,
117
+ };
118
+ r.useExtension("subscriptionProvider", STRIPE_PROVIDER_NAME, plugin);
119
+ });
120
+ }
@@ -0,0 +1,14 @@
1
+ // Public API of the subscription-stripe bundled-feature.
2
+ //
3
+ // **Internal-only** (NICHT im public-barrel — App-Builder nutzt das nie direkt):
4
+ // - StripeWebhookOptions / verifyAndParseStripeWebhook (intern vom
5
+ // feature.ts factory aufgerufen)
6
+ // - mapStripeEventType / mapStripeStatus (pure helpers, test-only;
7
+ // direct-import aus dem File wenn echt mal extern gebraucht)
8
+
9
+ export {
10
+ STRIPE_PROVIDER_NAME,
11
+ StripeEventTypes,
12
+ SUBSCRIPTION_STRIPE_FEATURE,
13
+ } from "./constants";
14
+ export { createSubscriptionStripeFeature, type SubscriptionStripeOptions } from "./feature";
@@ -0,0 +1,91 @@
1
+ // Stripe-Plugin-Methoden für die POST-tenant-resolution-Phase:
2
+ // createCheckoutSession, createPortalSession, cancelSubscription.
3
+ //
4
+ // Werden vom Plugin-build (feature.ts) als methods auf dem
5
+ // SubscriptionProviderPlugin registriert. Anders als
6
+ // verifyAndParseWebhook (= pre-tenant) bekommen diese den vollen
7
+ // HandlerContext (für ggf. tenant-spezifische Lookups).
8
+ //
9
+ // **Type-Ableitung:** die options-shapes der drei methods werden
10
+ // **direkt vom Plugin-Contract** abgeleitet (`Parameters<NonNullable
11
+ // <SubscriptionProviderPlugin["...method"]>>[1]`). Wenn Foundation den
12
+ // Contract erweitert (z.B. neuer optionaler Field), bemerkt der
13
+ // Stripe-Plugin das beim TS-Compile, nicht erst zur Laufzeit.
14
+
15
+ import type { SubscriptionProviderPlugin } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
16
+ import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
17
+ import type Stripe from "stripe";
18
+
19
+ // =============================================================================
20
+ // createCheckoutSession
21
+ // =============================================================================
22
+ //
23
+ // Stripe-Checkout-Session erstellen. Der hosted-page-URL wird returnt;
24
+ // der App-Builder redirected den Tenant-Admin dorthin. Nach erfolgreichem
25
+ // checkout sendet Stripe `customer.subscription.created` mit
26
+ // `metadata.tenantId` zurück — das ist wie der subsequent webhook den
27
+ // Tenant resolved.
28
+
29
+ export type StripeCheckoutOptions = Parameters<
30
+ NonNullable<SubscriptionProviderPlugin["createCheckoutSession"]>
31
+ >[1];
32
+
33
+ export function createStripeCheckoutSession(stripe: Stripe) {
34
+ return async (_ctx: HandlerContext, options: StripeCheckoutOptions): Promise<{ url: string }> => {
35
+ const session = await stripe.checkout.sessions.create({
36
+ mode: "subscription",
37
+ line_items: [{ price: options.priceId, quantity: 1 }],
38
+ success_url: options.successUrl,
39
+ cancel_url: options.cancelUrl,
40
+ // metadata.tenantId landet auf der subscription die durch diese
41
+ // checkout-session entsteht — beim subsequent webhook lesen wir's
42
+ // aus subscription.metadata.tenantId zurück.
43
+ subscription_data: {
44
+ metadata: { tenantId: options.tenantId },
45
+ },
46
+ ...(options.providerCustomerId && { customer: options.providerCustomerId }),
47
+ });
48
+
49
+ if (!session.url) {
50
+ // Stripe garantiert url für mode: subscription. Defensive für
51
+ // zukünftige API-Drift.
52
+ throw new Error("subscription-stripe: checkout.sessions.create returned no url");
53
+ }
54
+ return { url: session.url };
55
+ };
56
+ }
57
+
58
+ // =============================================================================
59
+ // createPortalSession
60
+ // =============================================================================
61
+ //
62
+ // Stripe Customer-Portal-Session — Tenant verwaltet seine subscription
63
+ // selbst (cancel, payment-method, invoice-history).
64
+
65
+ export type StripePortalOptions = Parameters<
66
+ NonNullable<SubscriptionProviderPlugin["createPortalSession"]>
67
+ >[1];
68
+
69
+ export function createStripePortalSession(stripe: Stripe) {
70
+ return async (_ctx: HandlerContext, options: StripePortalOptions): Promise<{ url: string }> => {
71
+ const session = await stripe.billingPortal.sessions.create({
72
+ customer: options.providerCustomerId,
73
+ return_url: options.returnUrl,
74
+ });
75
+ return { url: session.url };
76
+ };
77
+ }
78
+
79
+ // =============================================================================
80
+ // cancelSubscription
81
+ // =============================================================================
82
+ //
83
+ // Stripe sendet danach `customer.subscription.deleted`-webhook → der
84
+ // state-update läuft über den normalen webhook-pfad. Diese function
85
+ // triggert nur die API-Cancellation.
86
+
87
+ export function createStripeCancelSubscription(stripe: Stripe) {
88
+ return async (_ctx: HandlerContext, providerSubscriptionId: string): Promise<void> => {
89
+ await stripe.subscriptions.cancel(providerSubscriptionId);
90
+ };
91
+ }
@@ -0,0 +1,235 @@
1
+ // verifyAndParseStripeWebhook — Stripe-spezifische sig-verify +
2
+ // event-mapping. Wird vom Plugin-Build (feature.ts) als
3
+ // `verifyAndParseWebhook` registriert.
4
+ //
5
+ // **Drei Schritte:**
6
+ // 1. Sig-verify via stripe.webhooks.constructEvent (HMAC-SHA-256
7
+ // gegen rawBody + Stripe-Signature-Header). Wirft bei mismatch
8
+ // oder älter als 5min (Replay-Protection).
9
+ // 2. Event-type-Filter: nur die 5 event-types die wir auf
10
+ // SubscriptionEventTypes mappen kommen weiter; alles andere
11
+ // returnt null (foundation antwortet 200 ignored).
12
+ // 3. Stripe-payload → SubscriptionEvent normalisieren
13
+ // (status-mapping, tenant-id aus metadata, price-to-tier-Lookup).
14
+ //
15
+ // **Invoice-event lazy-fetch (Phase 5.2b):**
16
+ // Bei `invoice.paid` und `invoice.payment_failed` enthält der webhook-
17
+ // payload nur die subscription-id (Stripe-Webhooks expanden subscription
18
+ // nicht automatisch). Plugin macht einen lazy-fetch via
19
+ // `stripe.subscriptions.retrieve(subId)` um an das full subscription-
20
+ // Object für status/tier/period-end-mapping zu kommen. Bei Stripe-API-
21
+ // failure (= subscription gelöscht zwischen webhook + retrieve)
22
+ // returnt der Plugin defensiv null — der nächste subscription-event
23
+ // wird den state korrekt handhaben.
24
+
25
+ import type { SubscriptionEvent } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
26
+ import {
27
+ type SubscriptionEventType,
28
+ SubscriptionEventTypes,
29
+ type SubscriptionStatus,
30
+ SubscriptionStatuses,
31
+ } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
32
+ import type Stripe from "stripe";
33
+ import { STRIPE_PROVIDER_NAME, StripeEventTypes } from "./constants";
34
+
35
+ // =============================================================================
36
+ // Sig-verify + parse
37
+ // =============================================================================
38
+
39
+ export type StripeWebhookOptions = {
40
+ /** Webhook-secret aus dem Stripe-Dashboard. **App-wide**, nicht
41
+ * per-tenant. Liest aus ENV-VAR oder system-config beim Plugin-
42
+ * build. */
43
+ readonly webhookSecret: string;
44
+ /** Price-to-tier-Map. Plugin liest die price-id aus dem event und
45
+ * mapped auf tier-name. Fehlt die price-id im Mapping → null. */
46
+ readonly priceToTier: Readonly<Record<string, string>>;
47
+ };
48
+
49
+ /**
50
+ * Stripe-webhook-handler. Implementiert den Plugin-Contract
51
+ * `verifyAndParseWebhook`. Closure über die `options` + den shared
52
+ * Stripe-Client (kein ctx-arg — das ist die Pre-tenant-resolution-Phase).
53
+ *
54
+ * **Shared Stripe-Client:** Der Caller (feature.ts) baut EINEN Stripe-
55
+ * Client beim mount und gibt ihn an alle vier plugin-methods weiter.
56
+ * Konstruktor-API-version-pin ist damit zentral; verify-webhook nutzt
57
+ * den client für `webhooks.constructEvent` (sig-verify) + lazy-fetch
58
+ * der invoice-events.
59
+ */
60
+ export function verifyAndParseStripeWebhook(
61
+ stripe: Stripe,
62
+ options: StripeWebhookOptions,
63
+ ): (rawBody: string, headers: Record<string, string>) => Promise<SubscriptionEvent | null> {
64
+ return async (rawBody, headers) => {
65
+ const sigHeader = headers["stripe-signature"];
66
+ if (!sigHeader) {
67
+ throw new Error("subscription-stripe: stripe-signature header missing");
68
+ }
69
+
70
+ // 1. Sig-verify. constructEvent throws bei mismatch (= invalid sig)
71
+ // oder timestamp-tolerance-violation (default 5min). Foundation
72
+ // mapped throw → HTTP 401.
73
+ let event: Stripe.Event;
74
+ try {
75
+ event = stripe.webhooks.constructEvent(rawBody, sigHeader, options.webhookSecret);
76
+ } catch (e) {
77
+ const msg = e instanceof Error ? e.message : String(e);
78
+ throw new Error(`subscription-stripe: webhook signature verify failed — ${msg}`);
79
+ }
80
+
81
+ // 2. Event-type-Filter — wir kennen nur 5.
82
+ const normalizedType = mapStripeEventType(event.type);
83
+ if (!normalizedType) {
84
+ return null; // foundation returnt 200 ignored
85
+ }
86
+
87
+ // 3. Payload-extraction. Stripe liefert je nach event.type
88
+ // verschiedene data.object-shapes. Wir extrahieren die
89
+ // Subscription-Daten — entweder direkt (subscription-events)
90
+ // oder via lazy-fetch (invoice-events, Phase 5.2b).
91
+ const sub = await extractSubscriptionFromEvent(event, stripe);
92
+ if (!sub) {
93
+ // event-type war unter den 5 (oben gefiltert), aber payload-shape
94
+ // matched nicht — Stripe-SDK-Schema-Drift, defensive null.
95
+ return null;
96
+ }
97
+
98
+ // 4. Tenant-resolution aus metadata. App-Builder setzt
99
+ // `metadata.tenantId` beim createCheckoutSession-call.
100
+ const tenantId = sub.metadata?.["tenantId"];
101
+ if (!tenantId || tenantId.length === 0) {
102
+ // Subscription ohne tenant-metadata → kann kein subscription
103
+ // erstellen ohne tenant-resolution. Drop the event silent
104
+ // (foundation 200 ignored). App-Owner-Bug, nicht foundation-Bug.
105
+ return null;
106
+ }
107
+
108
+ // 5. Price-to-tier-Mapping. Stripe-subscription hat items[0].price.id.
109
+ const priceId = sub.items.data[0]?.price.id;
110
+ if (!priceId) {
111
+ return null;
112
+ }
113
+ const tier = options.priceToTier[priceId];
114
+ if (!tier) {
115
+ // Price-id nicht im Mapping → App-Owner hat den Stripe-price
116
+ // angelegt aber nicht zur tier zugeordnet. Drop silent.
117
+ return null;
118
+ }
119
+
120
+ // 6. Status-Mapping + period-end. Stripe hat den period-end seit
121
+ // 2024 vom subscription-level auf item-level migriert (=
122
+ // subscription.items.data[i].current_period_end). Wir lesen
123
+ // das vom ersten item; multi-item-subs (Add-Ons) sind kein
124
+ // Phase-5-Scope.
125
+ const status = mapStripeStatus(sub.status);
126
+ const periodEndUnixSec = sub.items.data[0]?.current_period_end ?? 0;
127
+ // Stripe returns Unix-seconds; Temporal.Instant.fromEpochMilliseconds
128
+ // expects ms. Multiply, then ISO. (No-Date-API-Guard verbietet
129
+ // `new Date()` — Temporal ist global verfügbar via polyfill.)
130
+ const currentPeriodEnd = Temporal.Instant.fromEpochMilliseconds(
131
+ periodEndUnixSec * 1000,
132
+ ).toString();
133
+
134
+ return {
135
+ providerEventId: event.id,
136
+ providerName: STRIPE_PROVIDER_NAME,
137
+ type: normalizedType,
138
+ tenantId,
139
+ providerCustomerId: typeof sub.customer === "string" ? sub.customer : sub.customer.id,
140
+ providerSubscriptionId: sub.id,
141
+ status,
142
+ tier,
143
+ currentPeriodEnd,
144
+ rawPayload: JSON.stringify(event),
145
+ };
146
+ };
147
+ }
148
+
149
+ // =============================================================================
150
+ // Helpers
151
+ // =============================================================================
152
+
153
+ /** Stripe-event-type → normalisiert. null = ignore. */
154
+ export function mapStripeEventType(stripeType: string): SubscriptionEventType | null {
155
+ switch (stripeType) {
156
+ case StripeEventTypes.customerSubscriptionCreated:
157
+ return SubscriptionEventTypes.created;
158
+ case StripeEventTypes.customerSubscriptionUpdated:
159
+ return SubscriptionEventTypes.updated;
160
+ case StripeEventTypes.customerSubscriptionDeleted:
161
+ return SubscriptionEventTypes.canceled;
162
+ case StripeEventTypes.invoicePaid:
163
+ return SubscriptionEventTypes.invoicePaid;
164
+ case StripeEventTypes.invoicePaymentFailed:
165
+ return SubscriptionEventTypes.invoicePaymentFailed;
166
+ default:
167
+ return null;
168
+ }
169
+ }
170
+
171
+ /** Stripe-status → normalisiert. Defensive: unbekannte Status →
172
+ * incomplete (Plugin sollte das nicht erreichen, aber wir wollen
173
+ * kein ungültiger status-string in der DB). */
174
+ export function mapStripeStatus(stripeStatus: Stripe.Subscription.Status): SubscriptionStatus {
175
+ switch (stripeStatus) {
176
+ case "active":
177
+ return SubscriptionStatuses.active;
178
+ case "trialing":
179
+ return SubscriptionStatuses.trialing;
180
+ case "past_due":
181
+ case "unpaid":
182
+ case "paused":
183
+ return SubscriptionStatuses.pastDue;
184
+ case "canceled":
185
+ return SubscriptionStatuses.canceled;
186
+ case "incomplete":
187
+ case "incomplete_expired":
188
+ return SubscriptionStatuses.incomplete;
189
+ default:
190
+ return SubscriptionStatuses.incomplete;
191
+ }
192
+ }
193
+
194
+ /** Holt die Subscription aus dem Event. Subscription-events haben sie
195
+ * direkt im data.object; invoice-events haben nur die subscription-id
196
+ * und brauchen einen lazy-fetch via stripe.subscriptions.retrieve
197
+ * (Phase 5.2b). */
198
+ async function extractSubscriptionFromEvent(
199
+ event: Stripe.Event,
200
+ stripe: Stripe,
201
+ ): Promise<Stripe.Subscription | null> {
202
+ switch (event.type) {
203
+ case StripeEventTypes.customerSubscriptionCreated:
204
+ case StripeEventTypes.customerSubscriptionUpdated:
205
+ case StripeEventTypes.customerSubscriptionDeleted:
206
+ return event.data.object as Stripe.Subscription;
207
+ case StripeEventTypes.invoicePaid:
208
+ case StripeEventTypes.invoicePaymentFailed: {
209
+ // Lazy-fetch der subscription. invoice.subscription ist eine
210
+ // string-id (Stripe-Webhooks expanden nicht auto). Wir holen das
211
+ // full subscription-Object damit der downstream-mapping
212
+ // (status, tier via priceId, period-end) konsistent funktioniert.
213
+ const invoice = event.data.object as Stripe.Invoice;
214
+ const subRef = (invoice as { subscription?: string | Stripe.Subscription | null })
215
+ .subscription;
216
+ if (!subRef) {
217
+ // Invoice ohne subscription-reference (= one-shot-invoice, nicht
218
+ // recurring). Nicht unsere Domain — ignorieren.
219
+ return null;
220
+ }
221
+ const subId = typeof subRef === "string" ? subRef : subRef.id;
222
+ try {
223
+ return await stripe.subscriptions.retrieve(subId);
224
+ } catch {
225
+ // Stripe-API-failure beim retrieve (z.B. subscription gelöscht
226
+ // zwischen webhook + retrieve). Defensive: null returnen, damit
227
+ // foundation 200 ignored returnt — der nächste subscription-
228
+ // event wird's korrekt handhaben.
229
+ return null;
230
+ }
231
+ }
232
+ default:
233
+ return null;
234
+ }
235
+ }