@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.
- package/package.json +90 -0
- package/src/audit/__tests__/audit.integration.ts +328 -0
- package/src/audit/constants.ts +7 -0
- package/src/audit/feature.ts +23 -0
- package/src/audit/handlers/list.query.ts +98 -0
- package/src/audit/index.ts +2 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.ts +149 -0
- package/src/auth-email-password/__tests__/account-lockout.integration.ts +308 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.ts +512 -0
- package/src/auth-email-password/__tests__/auth.integration.ts +610 -0
- package/src/auth-email-password/__tests__/confirm-token-flow.test.ts +67 -0
- package/src/auth-email-password/__tests__/email-templates.test.ts +106 -0
- package/src/auth-email-password/__tests__/email-verification.integration.ts +327 -0
- package/src/auth-email-password/__tests__/identity-v3-hash.test.ts +174 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.ts +150 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.ts +458 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.ts +256 -0
- package/src/auth-email-password/__tests__/password-reset.integration.ts +346 -0
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.ts +144 -0
- package/src/auth-email-password/__tests__/seed-admin.integration.ts +176 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.ts +310 -0
- package/src/auth-email-password/__tests__/session-strict-mode.integration.ts +101 -0
- package/src/auth-email-password/__tests__/signed-token.test.ts +78 -0
- package/src/auth-email-password/__tests__/signup-flow.integration.ts +259 -0
- package/src/auth-email-password/auth-user-row.ts +41 -0
- package/src/auth-email-password/constants.ts +101 -0
- package/src/auth-email-password/email-templates.ts +283 -0
- package/src/auth-email-password/feature.ts +140 -0
- package/src/auth-email-password/handlers/change-password.write.ts +58 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +191 -0
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +203 -0
- package/src/auth-email-password/handlers/invite-accept.write.ts +189 -0
- package/src/auth-email-password/handlers/invite-create.write.ts +145 -0
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +192 -0
- package/src/auth-email-password/handlers/login.write.ts +208 -0
- package/src/auth-email-password/handlers/logout.write.ts +12 -0
- package/src/auth-email-password/handlers/request-email-verification.write.ts +29 -0
- package/src/auth-email-password/handlers/request-password-reset.write.ts +31 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +61 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +170 -0
- package/src/auth-email-password/handlers/signup-request.write.ts +104 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +114 -0
- package/src/auth-email-password/handlers/verify-email.write.ts +62 -0
- package/src/auth-email-password/i18n.ts +211 -0
- package/src/auth-email-password/identity-v3-hash.ts +97 -0
- package/src/auth-email-password/index.ts +35 -0
- package/src/auth-email-password/invite-token-store.ts +92 -0
- package/src/auth-email-password/lockout-store.ts +118 -0
- package/src/auth-email-password/password-hashing.ts +43 -0
- package/src/auth-email-password/reset-token.ts +28 -0
- package/src/auth-email-password/seeding.ts +183 -0
- package/src/auth-email-password/signed-token.ts +85 -0
- package/src/auth-email-password/signup-token-store.ts +104 -0
- package/src/auth-email-password/stream-tenant.ts +31 -0
- package/src/auth-email-password/testing.ts +5 -0
- package/src/auth-email-password/token-burn-store.ts +57 -0
- package/src/auth-email-password/verification-token.ts +27 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +51 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +80 -0
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +94 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +108 -0
- package/src/auth-email-password/web/__tests__/session-roles.test.ts +54 -0
- package/src/auth-email-password/web/__tests__/tenant-switcher.test.tsx +100 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +73 -0
- package/src/auth-email-password/web/__tests__/user-menu.test.tsx +55 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +59 -0
- package/src/auth-email-password/web/auth-client.ts +350 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +70 -0
- package/src/auth-email-password/web/auth-gate.tsx +33 -0
- package/src/auth-email-password/web/client-plugin.ts +48 -0
- package/src/auth-email-password/web/default-topbar-actions.tsx +47 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +110 -0
- package/src/auth-email-password/web/index.ts +56 -0
- package/src/auth-email-password/web/invite-accept-screen.tsx +220 -0
- package/src/auth-email-password/web/login-screen.tsx +150 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +152 -0
- package/src/auth-email-password/web/session.tsx +171 -0
- package/src/auth-email-password/web/signup-complete-screen.tsx +150 -0
- package/src/auth-email-password/web/signup-screen.tsx +130 -0
- package/src/auth-email-password/web/tenant-switcher.tsx +116 -0
- package/src/auth-email-password/web/use-shell-user.ts +34 -0
- package/src/auth-email-password/web/user-menu.tsx +89 -0
- package/src/auth-email-password/web/verify-email-screen.tsx +102 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.ts +568 -0
- package/src/billing-foundation/__tests__/feature.test.ts +110 -0
- package/src/billing-foundation/__tests__/webhook-handler.test.ts +199 -0
- package/src/billing-foundation/aggregate-id.ts +21 -0
- package/src/billing-foundation/constants.ts +70 -0
- package/src/billing-foundation/entities.ts +50 -0
- package/src/billing-foundation/events.ts +71 -0
- package/src/billing-foundation/feature.ts +122 -0
- package/src/billing-foundation/get-subscription-for-tenant.ts +39 -0
- package/src/billing-foundation/handlers/create-checkout-session.write.ts +79 -0
- package/src/billing-foundation/handlers/create-portal-session.write.ts +73 -0
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +20 -0
- package/src/billing-foundation/handlers/process-event.write.ts +160 -0
- package/src/billing-foundation/index.ts +42 -0
- package/src/billing-foundation/projection.ts +135 -0
- package/src/billing-foundation/types.ts +157 -0
- package/src/billing-foundation/webhook-handler.ts +184 -0
- package/src/cap-counter/__tests__/cap-counter.integration.ts +566 -0
- package/src/cap-counter/__tests__/enforce-cap.test.ts +422 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.ts +265 -0
- package/src/cap-counter/aggregate-id.ts +61 -0
- package/src/cap-counter/constants.ts +32 -0
- package/src/cap-counter/enforce-cap.ts +404 -0
- package/src/cap-counter/entity.ts +48 -0
- package/src/cap-counter/feature.ts +90 -0
- package/src/cap-counter/handlers/get-counter.query.ts +43 -0
- package/src/cap-counter/handlers/increment-rolling.write.ts +79 -0
- package/src/cap-counter/handlers/increment.write.ts +92 -0
- package/src/cap-counter/handlers/mark-soft-warned.write.ts +57 -0
- package/src/cap-counter/index.ts +34 -0
- package/src/cap-counter/with-cap-enforcement.ts +179 -0
- package/src/channel-email/email-channel.ts +48 -0
- package/src/channel-email/feature.ts +15 -0
- package/src/channel-email/index.ts +4 -0
- package/src/channel-email/smtp-transport.ts +65 -0
- package/src/channel-email/types.ts +34 -0
- package/src/channel-in-app/constants.ts +11 -0
- package/src/channel-in-app/feature.ts +30 -0
- package/src/channel-in-app/handlers/inbox.query.ts +28 -0
- package/src/channel-in-app/handlers/mark-all-read.write.ts +21 -0
- package/src/channel-in-app/handlers/mark-read.write.ts +32 -0
- package/src/channel-in-app/handlers/unread-count.query.ts +20 -0
- package/src/channel-in-app/in-app-channel.ts +44 -0
- package/src/channel-in-app/index.ts +4 -0
- package/src/channel-in-app/tables.ts +22 -0
- package/src/channel-push/feature.ts +15 -0
- package/src/channel-push/index.ts +3 -0
- package/src/channel-push/push-channel.ts +33 -0
- package/src/channel-push/types.ts +22 -0
- package/src/config/__tests__/app-overrides.test.ts +118 -0
- package/src/config/__tests__/config.integration.ts +1246 -0
- package/src/config/constants.ts +23 -0
- package/src/config/feature.ts +117 -0
- package/src/config/handlers/__tests__/prepare-config-write.test.ts +209 -0
- package/src/config/handlers/reset.write.ts +45 -0
- package/src/config/handlers/schema.query.ts +22 -0
- package/src/config/handlers/set.write.ts +93 -0
- package/src/config/handlers/values.query.ts +43 -0
- package/src/config/index.ts +15 -0
- package/src/config/resolver.ts +283 -0
- package/src/config/table.ts +35 -0
- package/src/config/write-helpers.ts +268 -0
- package/src/delivery/__tests__/delivery-events.integration.ts +166 -0
- package/src/delivery/__tests__/delivery.integration.ts +1405 -0
- package/src/delivery/constants.ts +33 -0
- package/src/delivery/delivery-service.ts +489 -0
- package/src/delivery/events.ts +18 -0
- package/src/delivery/feature.ts +70 -0
- package/src/delivery/handlers/log.query.ts +21 -0
- package/src/delivery/handlers/preferences.query.ts +18 -0
- package/src/delivery/handlers/set-preference.write.ts +28 -0
- package/src/delivery/index.ts +35 -0
- package/src/delivery/tables.ts +74 -0
- package/src/delivery/testing.ts +47 -0
- package/src/delivery/types.ts +71 -0
- package/src/delivery/unsubscribe.ts +99 -0
- package/src/delivery/upsert-preference.ts +145 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.ts +687 -0
- package/src/feature-toggles/constants.ts +20 -0
- package/src/feature-toggles/events.ts +18 -0
- package/src/feature-toggles/feature.ts +98 -0
- package/src/feature-toggles/global-feature-state-table.ts +28 -0
- package/src/feature-toggles/handlers/list.query.ts +26 -0
- package/src/feature-toggles/handlers/registered.query.ts +56 -0
- package/src/feature-toggles/handlers/set.write.ts +158 -0
- package/src/feature-toggles/index.ts +9 -0
- package/src/feature-toggles/toggle-runtime.ts +73 -0
- package/src/file-foundation/__tests__/feature.test.ts +35 -0
- package/src/file-foundation/__tests__/file-foundation.integration.ts +235 -0
- package/src/file-foundation/feature.ts +123 -0
- package/src/file-foundation/index.ts +7 -0
- package/src/file-provider-inmemory/__tests__/feature.test.ts +35 -0
- package/src/file-provider-inmemory/feature.ts +73 -0
- package/src/file-provider-inmemory/index.ts +3 -0
- package/src/file-provider-s3/__tests__/feature.test.ts +54 -0
- package/src/file-provider-s3/feature.ts +169 -0
- package/src/file-provider-s3/index.ts +3 -0
- package/src/files-provider-s3/__tests__/env-helper.test.ts +161 -0
- package/src/files-provider-s3/__tests__/s3-provider.integration.ts +134 -0
- package/src/files-provider-s3/__tests__/s3-provider.test.ts +36 -0
- package/src/files-provider-s3/env-helper.ts +49 -0
- package/src/files-provider-s3/index.ts +3 -0
- package/src/files-provider-s3/s3-provider.ts +114 -0
- package/src/foundation-shared/config-helpers.ts +67 -0
- package/src/foundation-shared/index.ts +4 -0
- package/src/jobs/__tests__/job-system-user.integration.ts +194 -0
- package/src/jobs/__tests__/jobs-events.integration.ts +143 -0
- package/src/jobs/__tests__/jobs-feature.integration.ts +342 -0
- package/src/jobs/constants.ts +21 -0
- package/src/jobs/events.ts +39 -0
- package/src/jobs/feature.ts +150 -0
- package/src/jobs/handlers/detail.query.ts +30 -0
- package/src/jobs/handlers/list.query.ts +36 -0
- package/src/jobs/handlers/retry.write.ts +69 -0
- package/src/jobs/handlers/trigger.write.ts +39 -0
- package/src/jobs/index.ts +5 -0
- package/src/jobs/job-run-logger.ts +213 -0
- package/src/jobs/job-run-table.ts +55 -0
- package/src/legal-pages/README.md +195 -0
- package/src/legal-pages/__tests__/legal-pages.integration.ts +361 -0
- package/src/legal-pages/constants.ts +36 -0
- package/src/legal-pages/feature.ts +187 -0
- package/src/legal-pages/index.ts +13 -0
- package/src/legal-pages/markdown.ts +69 -0
- package/src/mail-foundation/__tests__/feature.test.ts +46 -0
- package/src/mail-foundation/__tests__/mail-foundation.integration.ts +247 -0
- package/src/mail-foundation/feature.ts +160 -0
- package/src/mail-foundation/index.ts +14 -0
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +37 -0
- package/src/mail-transport-inmemory/feature.ts +90 -0
- package/src/mail-transport-inmemory/index.ts +3 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +61 -0
- package/src/mail-transport-smtp/feature.ts +182 -0
- package/src/mail-transport-smtp/index.ts +3 -0
- package/src/rate-limiting/__tests__/rate-limiting.integration.ts +84 -0
- package/src/rate-limiting/constants.ts +9 -0
- package/src/rate-limiting/feature.ts +16 -0
- package/src/rate-limiting/handlers/status.query.ts +52 -0
- package/src/rate-limiting/index.ts +2 -0
- package/src/renderer-simple/__tests__/simple-renderer.test.ts +97 -0
- package/src/renderer-simple/feature.ts +12 -0
- package/src/renderer-simple/index.ts +2 -0
- package/src/renderer-simple/simple-renderer.ts +72 -0
- package/src/secrets/__tests__/rotate.integration.ts +176 -0
- package/src/secrets/__tests__/secrets-events.integration.ts +125 -0
- package/src/secrets/__tests__/secrets.integration.ts +118 -0
- package/src/secrets/feature.ts +84 -0
- package/src/secrets/handlers/delete.write.ts +20 -0
- package/src/secrets/handlers/list.query.ts +38 -0
- package/src/secrets/handlers/rotate.job.ts +193 -0
- package/src/secrets/handlers/set.write.ts +50 -0
- package/src/secrets/index.ts +16 -0
- package/src/secrets/secrets-context.ts +296 -0
- package/src/secrets/table.ts +68 -0
- package/src/sessions/__tests__/cleanup.integration.ts +175 -0
- package/src/sessions/__tests__/password-auto-revoke.integration.ts +202 -0
- package/src/sessions/__tests__/sessions.integration.ts +472 -0
- package/src/sessions/__tests__/test-helpers.ts +66 -0
- package/src/sessions/constants.ts +43 -0
- package/src/sessions/feature.ts +84 -0
- package/src/sessions/handlers/cleanup.job.ts +109 -0
- package/src/sessions/handlers/list.query.ts +35 -0
- package/src/sessions/handlers/mine.query.ts +37 -0
- package/src/sessions/handlers/revoke-all-others.write.ts +42 -0
- package/src/sessions/handlers/revoke.write.ts +76 -0
- package/src/sessions/index.ts +17 -0
- package/src/sessions/schema/index.ts +5 -0
- package/src/sessions/schema/user-session.ts +67 -0
- package/src/sessions/session-callbacks.ts +110 -0
- package/src/sessions/testing.ts +42 -0
- package/src/subscription-mollie/__tests__/feature.test.ts +106 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.ts +421 -0
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +388 -0
- package/src/subscription-mollie/constants.ts +33 -0
- package/src/subscription-mollie/feature.ts +144 -0
- package/src/subscription-mollie/index.ts +13 -0
- package/src/subscription-mollie/plugin-methods.ts +79 -0
- package/src/subscription-mollie/verify-webhook.ts +244 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +98 -0
- package/src/subscription-stripe/__tests__/plugin-methods.test.ts +161 -0
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.ts +315 -0
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +306 -0
- package/src/subscription-stripe/constants.ts +20 -0
- package/src/subscription-stripe/feature.ts +120 -0
- package/src/subscription-stripe/index.ts +14 -0
- package/src/subscription-stripe/plugin-methods.ts +91 -0
- package/src/subscription-stripe/verify-webhook.ts +235 -0
- package/src/tenant/__tests__/multi-tenant.integration.ts +278 -0
- package/src/tenant/__tests__/seed-testing.integration.ts +229 -0
- package/src/tenant/__tests__/tenant.integration.ts +347 -0
- package/src/tenant/command-schemas.ts +37 -0
- package/src/tenant/constants.ts +37 -0
- package/src/tenant/feature.ts +109 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +19 -0
- package/src/tenant/handlers/add-member.write.ts +53 -0
- package/src/tenant/handlers/cancel-invitation.write.ts +87 -0
- package/src/tenant/handlers/create.write.ts +21 -0
- package/src/tenant/handlers/disable.write.ts +18 -0
- package/src/tenant/handlers/invitations.query.ts +31 -0
- package/src/tenant/handlers/list.query.ts +17 -0
- package/src/tenant/handlers/me.query.ts +17 -0
- package/src/tenant/handlers/members.query.ts +22 -0
- package/src/tenant/handlers/memberships.query.ts +24 -0
- package/src/tenant/handlers/remove-member.write.ts +40 -0
- package/src/tenant/handlers/resolve-user-ids.query.ts +43 -0
- package/src/tenant/handlers/update-member-roles.write.ts +54 -0
- package/src/tenant/handlers/update.write.ts +20 -0
- package/src/tenant/index.ts +12 -0
- package/src/tenant/invitation-table.ts +93 -0
- package/src/tenant/membership-table.ts +35 -0
- package/src/tenant/schema/index.ts +5 -0
- package/src/tenant/schema/tenant.ts +27 -0
- package/src/tenant/seeding.ts +155 -0
- package/src/tenant/testing.ts +8 -0
- package/src/text-content/README.md +190 -0
- package/src/text-content/__tests__/text-content.integration.ts +415 -0
- package/src/text-content/api.ts +92 -0
- package/src/text-content/constants.ts +19 -0
- package/src/text-content/feature.ts +29 -0
- package/src/text-content/handlers/by-slug.query.ts +55 -0
- package/src/text-content/handlers/set.write.ts +118 -0
- package/src/text-content/index.ts +14 -0
- package/src/text-content/seeding.ts +91 -0
- package/src/text-content/table.ts +45 -0
- package/src/tier-engine/__tests__/compose-app.test.ts +182 -0
- package/src/tier-engine/__tests__/drift.test.ts +42 -0
- package/src/tier-engine/__tests__/tier-engine.integration.ts +241 -0
- package/src/tier-engine/aggregate-id.ts +27 -0
- package/src/tier-engine/compose-app.ts +150 -0
- package/src/tier-engine/constants.ts +15 -0
- package/src/tier-engine/entity.ts +30 -0
- package/src/tier-engine/feature.ts +72 -0
- package/src/tier-engine/handlers/active-tier.query.ts +23 -0
- package/src/tier-engine/index.ts +22 -0
- package/src/user/__tests__/seed-testing.integration.ts +127 -0
- package/src/user/__tests__/user.integration.ts +198 -0
- package/src/user/command-schemas.ts +15 -0
- package/src/user/constants.ts +23 -0
- package/src/user/feature.ts +32 -0
- package/src/user/handlers/create.write.ts +54 -0
- package/src/user/handlers/detail.query.ts +9 -0
- package/src/user/handlers/find-for-auth.query.ts +38 -0
- package/src/user/handlers/list.query.ts +8 -0
- package/src/user/handlers/me.query.ts +15 -0
- package/src/user/handlers/update.write.ts +54 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/index.ts +5 -0
- package/src/user/schema/user.ts +69 -0
- package/src/user/seeding.ts +93 -0
- 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 };
|