create-ailk 0.24.0 → 0.26.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/README.md +53 -22
- package/component-catalog.json +3 -3
- package/component-catalog.md +3 -3
- package/dist/add-module.js +4 -2
- package/dist/add-route.d.ts +12 -1
- package/dist/add-route.js +31 -3
- package/dist/cli.js +12 -7
- package/dist/derive-architecture.d.ts +3 -4
- package/dist/derive-architecture.js +65 -29
- package/dist/lib/design-checks.d.ts +9 -4
- package/dist/lib/design-checks.js +13 -7
- package/dist/lib/extract-module-bundle.js +29 -2
- package/dist/lib/fetch-template-snapshot.d.ts +2 -1
- package/dist/lib/fetch-template-snapshot.js +8 -3
- package/dist/lib/import-graph.d.ts +1 -1
- package/dist/lib/import-graph.js +4 -4
- package/dist/lib/module-license-gate.d.ts +12 -1
- package/dist/lib/module-license-gate.js +36 -15
- package/dist/lib/module-tier.d.ts +2 -8
- package/dist/lib/module-tier.js +13 -12
- package/dist/module-architecture.d.ts +20 -3
- package/dist/module-architecture.js +129 -552
- package/dist/paid-sections.d.ts +25 -1
- package/dist/paid-sections.js +88 -3
- package/dist/starter-grid.d.ts +6 -6
- package/dist/starter-grid.js +12 -5
- package/dist/surfaces.js +39 -2
- package/dist/template-record.d.ts +21 -3
- package/dist/template-record.js +38 -7
- package/dist/topology.d.ts +16 -3
- package/dist/topology.js +54 -23
- package/dist/update.d.ts +10 -1
- package/dist/update.js +715 -21
- package/package.json +3 -3
- package/release-scope.json +2 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +8 -2
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +2 -2
- package/site-templates/marketing-site/ailk-template.json +7 -1
- package/site-templates/marketing-site/provenance.json +7 -1
- package/site-templates/saas-product/ailk-template.json +2 -1
- package/site-templates/saas-product/provenance.json +1 -0
- package/templates/.claude/cloudparity-repo-hooks.json +5 -0
- package/templates/.claude/hooks/template-guard.sh +137 -0
- package/templates/.claude/rules/extending.md +106 -0
- package/templates/.claude/rules/loop-contract.md +2 -2
- package/templates/.claude/settings.json +11 -0
- package/templates/.env.example +20 -5
- package/templates/.github/workflows/ci.yml +7 -4
- package/templates/CLAUDE.md +11 -1
- package/templates/CONVENTIONS.md +8 -10
- package/templates/README.md +14 -13
- package/templates/apps/api/.env.example +23 -2
- package/templates/apps/api/CLAUDE.md +119 -83
- package/templates/apps/api/jest.config.cjs +15 -5
- package/templates/apps/api/package.json +16 -20
- package/templates/apps/api/src/__tests__/trusted-origins.server.test.ts +2 -2
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +4 -4
- package/templates/apps/api/src/index.ts +1 -1
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +16 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -3
- package/templates/apps/api/src/modules/commerce.ts +10 -0
- package/templates/apps/api/src/modules/content-write.ts +10 -0
- package/templates/apps/api/src/modules/multi-tenant.ts +10 -0
- package/templates/apps/api/src/modules/scheduling.ts +10 -0
- package/templates/apps/api/src/modules/signal-collectors.ts +10 -0
- package/templates/apps/api/src/modules/workspace.ts +10 -0
- package/templates/apps/api/src/server.ts +117 -1882
- package/templates/apps/api/vercel.json +2 -2
- package/templates/apps/mcp/CLAUDE.md +90 -26
- package/templates/apps/mcp/__tests__/app-entry.test.ts +4 -254
- package/templates/apps/mcp/__tests__/boot-file.test.ts +92 -0
- package/templates/apps/mcp/jest.config.cjs +6 -1
- package/templates/apps/mcp/package.json +5 -13
- package/templates/apps/mcp/src/app.ts +8 -7
- package/templates/apps/mcp/src/index.ts +6 -34
- package/templates/apps/mcp/src/lib/paid-tools.generated.ts +10 -0
- package/templates/apps/mcp/src/lib/topology.generated.ts +2 -2
- package/templates/apps/mcp/src/site.ts +12 -0
- package/templates/apps/web/.env.example +1 -1
- package/templates/apps/web/CLAUDE.md +31 -3
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +9 -3
- package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +43 -4
- package/templates/apps/web/app/[locale]/(auth)/legal-footnote.tsx +35 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +117 -8
- package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +14 -37
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +6 -6
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +20 -35
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +108 -0
- package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +111 -5
- package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +78 -16
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +132 -12
- package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +53 -1
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +4 -2
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +47 -24
- package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +23 -4
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +1 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +22 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +7 -3
- package/templates/apps/web/app/[locale]/layout.tsx +33 -13
- package/templates/apps/web/app/[locale]/meet/confirmed/BookingHandOff.tsx +128 -0
- package/templates/apps/web/app/[locale]/meet/confirmed/__tests__/BookingHandOff.test.tsx +126 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +7 -3
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +222 -0
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +10 -3
- package/templates/apps/web/app/api/revalidate/route.ts +3 -3
- package/templates/apps/web/auth.ts +10 -6
- package/templates/apps/web/i18n/request.ts +49 -1
- package/templates/apps/web/jest.config.cjs +1 -0
- package/templates/apps/web/lib/__tests__/account-config.test.ts +7 -0
- package/templates/apps/web/lib/__tests__/auth-email.test.ts +276 -0
- package/templates/apps/web/lib/auth-email.ts +103 -21
- package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +72 -23
- package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +94 -70
- package/templates/apps/web/lib/prisma-for-auth.ts +5 -2
- package/templates/apps/web/lib/relay.ts +2 -2
- package/templates/apps/web/lib/release-scope.ts +47 -0
- package/templates/apps/web/lib/server-api.ts +1 -1
- package/templates/apps/web/lib/sign-up-agreements.ts +291 -28
- package/templates/apps/web/lib/site-contributions.ts +18 -0
- package/templates/apps/web/messages/en.json +186 -20
- package/templates/apps/web/messages/site/en.json +1 -0
- package/templates/apps/web/src/lib/commerce-claim.ts +1 -1
- package/templates/customizations.yaml +37 -0
- package/templates/database/prisma.config.ts +10 -14
- package/templates/database/seed-test.ts +2 -2
- package/templates/database/seed.ts +5 -5
- package/templates/package.json +12 -6
- package/templates/pnpm-workspace.yaml +0 -1
- package/templates/project.yaml +16 -8
- package/templates/tools/jest/reporters.cjs +65 -0
- package/templates/tools/lint-architecture.ts +553 -7
- package/templates/tsconfig.json +1 -2
- package/versions.json +49 -36
- package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +0 -409
- package/templates/apps/api/src/__tests__/factories/agent-caller.ts +0 -102
- package/templates/apps/api/src/__tests__/factories/content-fixture.ts +0 -138
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +0 -441
- package/templates/apps/api/src/__tests__/factories/index.ts +0 -70
- package/templates/apps/api/src/__tests__/factories/lead-fixture.ts +0 -82
- package/templates/apps/api/src/__tests__/leads-forwarded-rate-limit.test.ts +0 -135
- package/templates/apps/api/src/__tests__/services/enrichment-shim.test.ts +0 -126
- package/templates/apps/api/src/__tests__/stripe-boot.test.ts +0 -132
- package/templates/apps/api/src/__tests__/support/hub-sign-in-fake-db.ts +0 -362
- package/templates/apps/api/src/bin/__tests__/rate-limit-probe.test.ts +0 -184
- package/templates/apps/api/src/bin/abandon-sweep.ts +0 -42
- package/templates/apps/api/src/bin/rate-limit-probe.ts +0 -53
- package/templates/apps/api/src/bin/spokes.ts +0 -198
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +0 -45
- package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +0 -196
- package/templates/apps/api/src/config/__tests__/index.test.ts +0 -233
- package/templates/apps/api/src/config/__tests__/modules.test.ts +0 -76
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +0 -185
- package/templates/apps/api/src/config/__tests__/trusted-origins.test.ts +0 -238
- package/templates/apps/api/src/config/index.ts +0 -596
- package/templates/apps/api/src/config/modules.ts +0 -83
- package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +0 -581
- package/templates/apps/api/src/lib/__tests__/auth-email-budget.test.ts +0 -143
- package/templates/apps/api/src/lib/__tests__/consent-ledger-double.ts +0 -237
- package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +0 -353
- package/templates/apps/api/src/lib/__tests__/content-adapter.test.ts +0 -201
- package/templates/apps/api/src/lib/__tests__/deliverable-asset-store.test.ts +0 -224
- package/templates/apps/api/src/lib/__tests__/error-serializer.test.ts +0 -120
- package/templates/apps/api/src/lib/__tests__/errors.test.ts +0 -94
- package/templates/apps/api/src/lib/__tests__/file-fallback.test.ts +0 -144
- package/templates/apps/api/src/lib/__tests__/fixtures/HomePage/home.mdx +0 -8
- package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +0 -149
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +0 -116
- package/templates/apps/api/src/lib/__tests__/hub-sign-in-store.test.ts +0 -209
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +0 -257
- package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +0 -96
- package/templates/apps/api/src/lib/__tests__/outbound-http.test.ts +0 -125
- package/templates/apps/api/src/lib/__tests__/prisma.test.ts +0 -165
- package/templates/apps/api/src/lib/__tests__/require-commerce-claim.test.ts +0 -317
- package/templates/apps/api/src/lib/__tests__/require-paid-tier.test.ts +0 -129
- package/templates/apps/api/src/lib/__tests__/site.test.ts +0 -308
- package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +0 -89
- package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +0 -202
- package/templates/apps/api/src/lib/__tests__/stripe-admission.test.ts +0 -217
- package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -82
- package/templates/apps/api/src/lib/agent-store.ts +0 -613
- package/templates/apps/api/src/lib/auth-email-budget.ts +0 -161
- package/templates/apps/api/src/lib/consent-store.ts +0 -363
- package/templates/apps/api/src/lib/content-adapter.ts +0 -112
- package/templates/apps/api/src/lib/deliverable-asset-store.ts +0 -96
- package/templates/apps/api/src/lib/error-serializer.ts +0 -164
- package/templates/apps/api/src/lib/errors.ts +0 -95
- package/templates/apps/api/src/lib/file-fallback.ts +0 -87
- package/templates/apps/api/src/lib/flow-completion-store.ts +0 -136
- package/templates/apps/api/src/lib/hub-sign-in-store.ts +0 -455
- package/templates/apps/api/src/lib/lead-capture-store.ts +0 -122
- package/templates/apps/api/src/lib/lead-delivery.ts +0 -52
- package/templates/apps/api/src/lib/license-key-store.ts +0 -45
- package/templates/apps/api/src/lib/locales.ts +0 -201
- package/templates/apps/api/src/lib/outbound-http.ts +0 -127
- package/templates/apps/api/src/lib/prisma.ts +0 -21
- package/templates/apps/api/src/lib/proposal-paid-store.ts +0 -66
- package/templates/apps/api/src/lib/rate-limit-probe.ts +0 -165
- package/templates/apps/api/src/lib/rate-limit-store.ts +0 -142
- package/templates/apps/api/src/lib/require-commerce-claim.ts +0 -250
- package/templates/apps/api/src/lib/require-paid-tier.ts +0 -53
- package/templates/apps/api/src/lib/site.ts +0 -257
- package/templates/apps/api/src/lib/slice-load.ts +0 -90
- package/templates/apps/api/src/lib/spoke-store.ts +0 -180
- package/templates/apps/api/src/lib/stripe.ts +0 -321
- package/templates/apps/api/src/lib/webhook-idempotency.ts +0 -156
- package/templates/apps/api/src/middleware/__tests__/adversarial/bearer-token-format.test.ts +0 -111
- package/templates/apps/api/src/middleware/__tests__/adversarial/cookie-or-bearer-precedence.test.ts +0 -172
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-bearer-skip.test.ts +0 -119
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-cookie-path.test.ts +0 -138
- package/templates/apps/api/src/middleware/__tests__/adversarial/csrf-trusted-origins.test.ts +0 -142
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +0 -155
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-timeout.test.ts +0 -139
- package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +0 -562
- package/templates/apps/api/src/middleware/__tests__/auth.both-present-precedence.test.ts +0 -76
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +0 -97
- package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +0 -557
- package/templates/apps/api/src/middleware/__tests__/auth.neither-present.test.ts +0 -72
- package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +0 -140
- package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +0 -721
- package/templates/apps/api/src/middleware/__tests__/rate-limit-cross-isolate.db.test.ts +0 -220
- package/templates/apps/api/src/middleware/__tests__/rate-limit-store.test.ts +0 -430
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +0 -1409
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +0 -174
- package/templates/apps/api/src/middleware/auth.ts +0 -864
- package/templates/apps/api/src/middleware/default-tenant.ts +0 -166
- package/templates/apps/api/src/middleware/rate-limit.ts +0 -983
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +0 -391
- package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +0 -182
- package/templates/apps/api/src/openapi/index.ts +0 -18
- package/templates/apps/api/src/openapi/spec.ts +0 -2148
- package/templates/apps/api/src/openapi/surface-drift.ts +0 -195
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +0 -175
- package/templates/apps/api/src/routes/aeo/index.ts +0 -107
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +0 -196
- package/templates/apps/api/src/routes/auth/__tests__/auth-user.functional.test.ts +0 -249
- package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +0 -532
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +0 -313
- package/templates/apps/api/src/routes/auth/__tests__/transactional-email.test.ts +0 -413
- package/templates/apps/api/src/routes/auth/__tests__/user.test.ts +0 -609
- package/templates/apps/api/src/routes/auth/hub-sign-in.ts +0 -242
- package/templates/apps/api/src/routes/auth/tokens.ts +0 -211
- package/templates/apps/api/src/routes/auth/transactional-email.ts +0 -301
- package/templates/apps/api/src/routes/auth/user.ts +0 -305
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +0 -332
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +0 -178
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +0 -1426
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +0 -448
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -1742
- package/templates/apps/api/src/routes/checkout/index.ts +0 -34
- package/templates/apps/api/src/routes/checkout/session.ts +0 -171
- package/templates/apps/api/src/routes/checkout/sessions.ts +0 -540
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +0 -386
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +0 -160
- package/templates/apps/api/src/routes/consent/account.ts +0 -268
- package/templates/apps/api/src/routes/consent/cookie.ts +0 -90
- package/templates/apps/api/src/routes/consent/index.ts +0 -26
- package/templates/apps/api/src/routes/consent/unsubscribe.ts +0 -127
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +0 -853
- package/templates/apps/api/src/routes/content/__tests__/list-pages.test.ts +0 -230
- package/templates/apps/api/src/routes/content/__tests__/list-types.test.ts +0 -44
- package/templates/apps/api/src/routes/content/__tests__/locale.test.ts +0 -118
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +0 -731
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +0 -753
- package/templates/apps/api/src/routes/content/__tests__/rate-limit.test.ts +0 -51
- package/templates/apps/api/src/routes/content/__tests__/search.test.ts +0 -450
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +0 -762
- package/templates/apps/api/src/routes/content/get-page.ts +0 -183
- package/templates/apps/api/src/routes/content/index.ts +0 -49
- package/templates/apps/api/src/routes/content/list-pages.ts +0 -74
- package/templates/apps/api/src/routes/content/list-types.ts +0 -19
- package/templates/apps/api/src/routes/content/offerings.ts +0 -184
- package/templates/apps/api/src/routes/content/pricing.ts +0 -176
- package/templates/apps/api/src/routes/content/rate-limit.ts +0 -22
- package/templates/apps/api/src/routes/content/search.ts +0 -100
- package/templates/apps/api/src/routes/content/shown-offerings.ts +0 -229
- package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +0 -88
- package/templates/apps/api/src/routes/leads/__tests__/list.test.ts +0 -171
- package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +0 -515
- package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +0 -392
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +0 -165
- package/templates/apps/api/src/routes/leads/index.ts +0 -639
- package/templates/apps/api/src/routes/leads/list.ts +0 -104
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +0 -626
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +0 -1268
- package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +0 -847
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +0 -491
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +0 -476
- package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +0 -872
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +0 -1022
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +0 -484
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +0 -535
- package/templates/apps/api/src/routes/project-listings/index.ts +0 -164
- package/templates/apps/api/src/routes/project-listings/me.ts +0 -87
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +0 -224
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +0 -102
- package/templates/apps/api/src/routes/project-listings/public.ts +0 -98
- package/templates/apps/api/src/routes/project-listings/respond.ts +0 -48
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +0 -27
- package/templates/apps/api/src/routes/project-listings/start.ts +0 -140
- package/templates/apps/api/src/routes/project-listings/submit.ts +0 -130
- package/templates/apps/api/src/routes/project-listings/verify-email.ts +0 -150
- package/templates/apps/api/src/routes/requests/__tests__/confirm.test.ts +0 -686
- package/templates/apps/api/src/routes/requests/confirm.ts +0 -601
- package/templates/apps/api/src/routes/requests/index.ts +0 -54
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +0 -306
- package/templates/apps/api/src/routes/test/auth/session.ts +0 -143
- package/templates/apps/api/src/routes/webhooks/README.md +0 -398
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +0 -649
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-dunning.test.ts +0 -749
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -449
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -264
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode-live.test.ts +0 -159
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-livemode.test.ts +0 -158
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +0 -814
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-proposal-paid.test.ts +0 -369
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -215
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-request-paid.test.ts +0 -301
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +0 -809
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -1155
- package/templates/apps/api/src/routes/webhooks/index.ts +0 -141
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +0 -384
- package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -2028
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +0 -645
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +0 -132
- package/templates/apps/api/src/services/__tests__/audit-log.test.ts +0 -1028
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +0 -313
- package/templates/apps/api/src/services/__tests__/consent-ledger.test.ts +0 -503
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +0 -65
- package/templates/apps/api/src/services/__tests__/consent.test.ts +0 -299
- package/templates/apps/api/src/services/__tests__/default-tenant.test.ts +0 -147
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +0 -877
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +0 -379
- package/templates/apps/api/src/services/__tests__/dunning-policy.test.ts +0 -368
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +0 -142
- package/templates/apps/api/src/services/__tests__/hub-sign-in.test.ts +0 -854
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +0 -234
- package/templates/apps/api/src/services/__tests__/lead-accept-user.test.ts +0 -251
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +0 -2660
- package/templates/apps/api/src/services/__tests__/listing-config.test.ts +0 -166
- package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +0 -389
- package/templates/apps/api/src/services/__tests__/person.test.ts +0 -1204
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +0 -450
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +0 -202
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +0 -151
- package/templates/apps/api/src/services/__tests__/public-site-resolver.test.ts +0 -137
- package/templates/apps/api/src/services/__tests__/request-confirm.test.ts +0 -531
- package/templates/apps/api/src/services/__tests__/revalidation.test.ts +0 -372
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +0 -348
- package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +0 -660
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +0 -941
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +0 -588
- package/templates/apps/api/src/services/__tests__/verified-email.test.ts +0 -78
- package/templates/apps/api/src/services/abandon-email.ts +0 -336
- package/templates/apps/api/src/services/aeo-score.ts +0 -325
- package/templates/apps/api/src/services/answer-distributions.ts +0 -138
- package/templates/apps/api/src/services/audit-log.ts +0 -961
- package/templates/apps/api/src/services/checkout-buyer.ts +0 -168
- package/templates/apps/api/src/services/consent.ts +0 -834
- package/templates/apps/api/src/services/default-tenant.ts +0 -416
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +0 -750
- package/templates/apps/api/src/services/dunning-policy.ts +0 -294
- package/templates/apps/api/src/services/enrichment-shim.ts +0 -53
- package/templates/apps/api/src/services/entitlements.ts +0 -338
- package/templates/apps/api/src/services/hub-sign-in.ts +0 -472
- package/templates/apps/api/src/services/lead-accept-user.ts +0 -126
- package/templates/apps/api/src/services/lead-promotion.ts +0 -160
- package/templates/apps/api/src/services/lead-routing.ts +0 -1372
- package/templates/apps/api/src/services/leads-inbox.ts +0 -312
- package/templates/apps/api/src/services/listing-config.ts +0 -178
- package/templates/apps/api/src/services/magnet-completion.ts +0 -242
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +0 -348
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +0 -268
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +0 -975
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +0 -416
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +0 -400
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +0 -685
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +0 -231
- package/templates/apps/api/src/services/offerings/catalog.ts +0 -318
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +0 -403
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +0 -192
- package/templates/apps/api/src/services/offerings/connection.ts +0 -1083
- package/templates/apps/api/src/services/offerings/index.ts +0 -614
- package/templates/apps/api/src/services/offerings/source.ts +0 -140
- package/templates/apps/api/src/services/offerings/store.ts +0 -150
- package/templates/apps/api/src/services/owner-workspaces.ts +0 -132
- package/templates/apps/api/src/services/person.ts +0 -1211
- package/templates/apps/api/src/services/plan-offerings.ts +0 -125
- package/templates/apps/api/src/services/project-listing-resume-email.ts +0 -326
- package/templates/apps/api/src/services/project-listings.ts +0 -2085
- package/templates/apps/api/src/services/public-site-resolver.ts +0 -396
- package/templates/apps/api/src/services/request-confirm.ts +0 -698
- package/templates/apps/api/src/services/revalidation.ts +0 -235
- package/templates/apps/api/src/services/slack-notify.ts +0 -291
- package/templates/apps/api/src/services/usage-aggregation.ts +0 -516
- package/templates/apps/api/src/services/usage-credits.ts +0 -340
- package/templates/apps/api/src/services/verified-email.ts +0 -71
- package/templates/apps/mcp/__tests__/abuse/daily-cap.test.ts +0 -52
- package/templates/apps/mcp/__tests__/abuse/email-validator.test.ts +0 -46
- package/templates/apps/mcp/__tests__/abuse/guard.test.ts +0 -198
- package/templates/apps/mcp/__tests__/abuse/honeypot.test.ts +0 -36
- package/templates/apps/mcp/__tests__/abuse/user-agent-deny.test.ts +0 -54
- package/templates/apps/mcp/__tests__/aeo-cli-parity.test.ts +0 -116
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +0 -299
- package/templates/apps/mcp/__tests__/boot.test.ts +0 -280
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +0 -359
- package/templates/apps/mcp/__tests__/config/modules.test.ts +0 -78
- package/templates/apps/mcp/__tests__/get_business_hours.test.ts +0 -82
- package/templates/apps/mcp/__tests__/get_faq.test.ts +0 -80
- package/templates/apps/mcp/__tests__/get_locations.test.ts +0 -65
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +0 -557
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +0 -524
- package/templates/apps/mcp/__tests__/get_site_info.test.ts +0 -81
- package/templates/apps/mcp/__tests__/http-transport.test.ts +0 -1232
- package/templates/apps/mcp/__tests__/index.test.ts +0 -195
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +0 -191
- package/templates/apps/mcp/__tests__/lib/commerce-claim.test.ts +0 -531
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +0 -70
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -162
- package/templates/apps/mcp/__tests__/lib/content-root.test.ts +0 -93
- package/templates/apps/mcp/__tests__/lib/errors.test.ts +0 -75
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +0 -180
- package/templates/apps/mcp/__tests__/lib/public-workspace.test.ts +0 -124
- package/templates/apps/mcp/__tests__/list_pages.test.ts +0 -268
- package/templates/apps/mcp/__tests__/locale.test.ts +0 -120
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +0 -175
- package/templates/apps/mcp/__tests__/purchase-disabled.test.ts +0 -101
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +0 -189
- package/templates/apps/mcp/__tests__/read_content.test.ts +0 -511
- package/templates/apps/mcp/__tests__/request_callback.test.ts +0 -160
- package/templates/apps/mcp/__tests__/search_content.test.ts +0 -271
- package/templates/apps/mcp/__tests__/server.test.ts +0 -388
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +0 -448
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +0 -222
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +0 -152
- package/templates/apps/mcp/__tests__/tools/create_project.test.ts +0 -243
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +0 -451
- package/templates/apps/mcp/src/abuse/daily-cap.ts +0 -47
- package/templates/apps/mcp/src/abuse/email-validator.ts +0 -25
- package/templates/apps/mcp/src/abuse/honeypot.ts +0 -21
- package/templates/apps/mcp/src/abuse/index.ts +0 -349
- package/templates/apps/mcp/src/abuse/user-agent-deny.ts +0 -58
- package/templates/apps/mcp/src/boot.ts +0 -167
- package/templates/apps/mcp/src/config/modules.ts +0 -43
- package/templates/apps/mcp/src/http-transport.ts +0 -564
- package/templates/apps/mcp/src/lib/audit-single-page.ts +0 -308
- package/templates/apps/mcp/src/lib/commerce-claim.ts +0 -263
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -116
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +0 -140
- package/templates/apps/mcp/src/lib/errors.ts +0 -61
- package/templates/apps/mcp/src/lib/license-key-store.ts +0 -45
- package/templates/apps/mcp/src/lib/locales.ts +0 -171
- package/templates/apps/mcp/src/lib/prisma.ts +0 -39
- package/templates/apps/mcp/src/lib/public-workspace.ts +0 -133
- package/templates/apps/mcp/src/lib/resolve-locale.ts +0 -30
- package/templates/apps/mcp/src/lib/shown-offerings.ts +0 -105
- package/templates/apps/mcp/src/lib/site-config.ts +0 -53
- package/templates/apps/mcp/src/lib/stripe-tax-signal.ts +0 -59
- package/templates/apps/mcp/src/request-context.ts +0 -52
- package/templates/apps/mcp/src/server.ts +0 -343
- package/templates/apps/mcp/src/serverless-app.ts +0 -88
- package/templates/apps/mcp/src/tools/audit.ts +0 -127
- package/templates/apps/mcp/src/tools/capture_lead.ts +0 -66
- package/templates/apps/mcp/src/tools/create_project.ts +0 -134
- package/templates/apps/mcp/src/tools/get_business_hours.ts +0 -68
- package/templates/apps/mcp/src/tools/get_faq.ts +0 -71
- package/templates/apps/mcp/src/tools/get_locations.ts +0 -88
- package/templates/apps/mcp/src/tools/get_offerings.ts +0 -96
- package/templates/apps/mcp/src/tools/get_pricing.ts +0 -94
- package/templates/apps/mcp/src/tools/get_site_info.ts +0 -86
- package/templates/apps/mcp/src/tools/index.ts +0 -138
- package/templates/apps/mcp/src/tools/list_pages.ts +0 -68
- package/templates/apps/mcp/src/tools/person_notes.ts +0 -25
- package/templates/apps/mcp/src/tools/proposals.ts +0 -27
- package/templates/apps/mcp/src/tools/purchase.ts +0 -205
- package/templates/apps/mcp/src/tools/read_content.ts +0 -81
- package/templates/apps/mcp/src/tools/request_callback.ts +0 -52
- package/templates/apps/mcp/src/tools/search_content.ts +0 -63
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +0 -54
- package/templates/apps/mcp/src/workspace-server.ts +0 -224
- package/templates/database/CHANGELOG.md +0 -981
- package/templates/database/LICENSE +0 -202
- package/templates/database/NOTICE +0 -2
- package/templates/database/README.md +0 -243
- package/templates/database/__tests__/agents.db.test.ts +0 -320
- package/templates/database/__tests__/appointment.db.test.ts +0 -106
- package/templates/database/__tests__/artifact-content-store.db.test.ts +0 -316
- package/templates/database/__tests__/composite-fence.db.test.ts +0 -206
- package/templates/database/__tests__/consent-ledger.db.test.ts +0 -345
- package/templates/database/__tests__/consent-source-checkout-migration.test.ts +0 -79
- package/templates/database/__tests__/fence-fan-out.db.test.ts +0 -715
- package/templates/database/__tests__/fixtures/appointment-create-probe.ts +0 -79
- package/templates/database/__tests__/fixtures/person-backfill.json +0 -264
- package/templates/database/__tests__/fixtures/person-contact-backfill.json +0 -182
- package/templates/database/__tests__/fixtures/project-listing-consent-probe.ts +0 -100
- package/templates/database/__tests__/flow-completion-migration.test.ts +0 -115
- package/templates/database/__tests__/flow-completion.db.test.ts +0 -136
- package/templates/database/__tests__/flow-workspace-key.db.test.ts +0 -263
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +0 -392
- package/templates/database/__tests__/flows-presets.test.ts +0 -173
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +0 -161
- package/templates/database/__tests__/lead-idempotency-key.db.test.ts +0 -92
- package/templates/database/__tests__/member-area.db.test.ts +0 -257
- package/templates/database/__tests__/member-contract-migration.test.ts +0 -85
- package/templates/database/__tests__/member-contract.db.test.ts +0 -413
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +0 -270
- package/templates/database/__tests__/offerings-migration.test.ts +0 -197
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +0 -86
- package/templates/database/__tests__/offerings.db.test.ts +0 -303
- package/templates/database/__tests__/outbound-message-action.db.test.ts +0 -80
- package/templates/database/__tests__/person-contact.db.test.ts +0 -563
- package/templates/database/__tests__/person-notes.db.test.ts +0 -249
- package/templates/database/__tests__/person.db.test.ts +0 -586
- package/templates/database/__tests__/piece-kind-migration.test.ts +0 -67
- package/templates/database/__tests__/piece-kind.db.test.ts +0 -208
- package/templates/database/__tests__/prefixed-ids.db.test.ts +0 -222
- package/templates/database/__tests__/project-listing-consent-at.db.test.ts +0 -111
- package/templates/database/__tests__/seed.test.ts +0 -272
- package/templates/database/__tests__/spoke-registry.db.test.ts +0 -191
- package/templates/database/__tests__/tenancy.test.ts +0 -141
- package/templates/database/__tests__/trial-magnet-migration.test.ts +0 -186
- package/templates/database/__tests__/user-cascade.test.ts +0 -140
- package/templates/database/_gitignore +0 -8
- package/templates/database/auth/.gitkeep +0 -0
- package/templates/database/auth/schema.prisma +0 -641
- package/templates/database/config/.gitkeep +0 -0
- package/templates/database/config/schema.prisma +0 -441
- package/templates/database/content/.gitkeep +0 -0
- package/templates/database/content/schema.prisma +0 -339
- package/templates/database/eslint.config.mjs +0 -69
- package/templates/database/flows/listing-diagnostic.ts +0 -182
- package/templates/database/flows/presets.ts +0 -315
- package/templates/database/inbox/.gitkeep +0 -0
- package/templates/database/inbox/README.md +0 -3
- package/templates/database/inbox/__tests__/schema.test.ts +0 -44
- package/templates/database/inbox/schema.prisma +0 -1447
- package/templates/database/jest.config.cjs +0 -36
- package/templates/database/migrations/20260508000000_add_inbox_namespace/migration.sql +0 -328
- package/templates/database/migrations/20260513000000_better_auth_migration/migration.sql +0 -100
- package/templates/database/migrations/20260515000000_add_user_billing_fields/migration.sql +0 -28
- package/templates/database/migrations/20260516000000_add_user_tier_care_plan_seat/migration.sql +0 -25
- package/templates/database/migrations/20260518000000_add_telemetry_records/migration.sql +0 -42
- package/templates/database/migrations/20260525000000_theme_wiring/migration.sql +0 -67
- package/templates/database/migrations/20260710000000_retire_commercial_tier/migration.sql +0 -21
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +0 -201
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +0 -67
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +0 -46
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +0 -25
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +0 -49
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +0 -197
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +0 -32
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +0 -22
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +0 -151
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +0 -223
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +0 -22
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +0 -32
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +0 -40
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +0 -4
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +0 -2
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +0 -2
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +0 -29
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +0 -6
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +0 -39
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +0 -24
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +0 -3
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +0 -33
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +0 -30
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +0 -47
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +0 -16
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +0 -47
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +0 -10
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +0 -26
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +0 -90
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +0 -174
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +0 -14
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +0 -25
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +0 -32
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +0 -71
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +0 -30
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +0 -49
- package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +0 -123
- package/templates/database/migrations/20260915120000_project_listing_solution/migration.sql +0 -15
- package/templates/database/migrations/20260916120000_artifact_content_store/migration.sql +0 -158
- package/templates/database/migrations/20260917120000_callback_request_confirm/migration.sql +0 -40
- package/templates/database/migrations/20260918120000_add_appointment/migration.sql +0 -67
- package/templates/database/migrations/20260918120000_asset_metadata_columns/migration.sql +0 -70
- package/templates/database/migrations/20260919120000_flow_workspace_key/migration.sql +0 -181
- package/templates/database/migrations/20260919130000_flow_definition_admin_columns/migration.sql +0 -172
- package/templates/database/migrations/20260919130000_workspace_meetings/migration.sql +0 -41
- package/templates/database/migrations/20260919140000_inbox_workspace_key/migration.sql +0 -185
- package/templates/database/migrations/20260920120000_lead_handled_at/migration.sql +0 -70
- package/templates/database/migrations/20260920130000_lead_workspace_source_index/migration.sql +0 -57
- package/templates/database/migrations/20260921120000_deliverable_grant_workspace_key/migration.sql +0 -68
- package/templates/database/migrations/20260922120000_tenancy_fence/migration.sql +0 -1067
- package/templates/database/migrations/20260923120000_listing_capturing_magnet/migration.sql +0 -210
- package/templates/database/migrations/20261003120000_spoke_registry/migration.sql +0 -80
- package/templates/database/migrations/20261003130000_scheduling_connection/migration.sql +0 -49
- package/templates/database/migrations/20261005120000_agents/migration.sql +0 -84
- package/templates/database/migrations/20261008120000_user_last_workspace/migration.sql +0 -35
- package/templates/database/migrations/20261008130000_lead_idempotency_key/migration.sql +0 -20
- package/templates/database/migrations/20261009120000_agent_grants/migration.sql +0 -33
- package/templates/database/migrations/20261010120000_person/migration.sql +0 -106
- package/templates/database/migrations/20261010121000_person_backfill/migration.sql +0 -132
- package/templates/database/migrations/20261010130000_agent_grants_people_read/migration.sql +0 -20
- package/templates/database/migrations/20261011120000_member_area/migration.sql +0 -158
- package/templates/database/migrations/20261011130000_agent_grants_member_pieces_manage/migration.sql +0 -21
- package/templates/database/migrations/20261012120000_member_contract/migration.sql +0 -192
- package/templates/database/migrations/20261013120000_lead_spoke_checkout/migration.sql +0 -20
- package/templates/database/migrations/20261014120000_consent_ledger/migration.sql +0 -122
- package/templates/database/migrations/20261014120000_person_contact/migration.sql +0 -231
- package/templates/database/migrations/20261015120000_person_notes/migration.sql +0 -77
- package/templates/database/migrations/20261015120000_rate_limit_counters/migration.sql +0 -43
- package/templates/database/migrations/20261016120000_piece_kind/migration.sql +0 -95
- package/templates/database/migrations/20261017120000_outbound_message_action/migration.sql +0 -30
- package/templates/database/migrations/20261018120000_flow_completion/migration.sql +0 -52
- package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +0 -27
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +0 -146
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +0 -77
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +0 -45
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +0 -47
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +0 -16
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +0 -28
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +0 -23
- package/templates/database/migrations/migration_lock.toml +0 -3
- package/templates/database/ops/.gitkeep +0 -0
- package/templates/database/ops/schema.prisma +0 -43
- package/templates/database/package.json +0 -89
- package/templates/database/schema.prisma +0 -71
- package/templates/database/scripts/db-generate-locked.sh +0 -80
- package/templates/database/scripts/person-backfill.sql +0 -132
- package/templates/database/tenancy.ts +0 -183
- package/templates/database/tsconfig.build.json +0 -16
- package/templates/database/tsconfig.flows.json +0 -15
- package/templates/database/tsconfig.json +0 -16
- package/templates/database/tsconfig.tenancy.json +0 -14
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* POST /api/checkout/sessions
|
|
3
|
-
*
|
|
4
|
-
* Generic Stripe checkout primitive. Resolves a productCode to a Stripe
|
|
5
|
-
* hosted-page checkout session and returns the session ID + URL. The session
|
|
6
|
-
* mode is derived from the resolved price by the stripe-client (a recurring
|
|
7
|
-
* price opens a subscription session), so this route serves both (#4398).
|
|
8
|
-
*
|
|
9
|
-
* Security invariants:
|
|
10
|
-
* - Admitted key only (#8450): before any Stripe call, a test-mode key or
|
|
11
|
-
* the restricted live key the process admitted at boot passes
|
|
12
|
-
* (`assertAdmittedStripeKey`); every other live key is refused.
|
|
13
|
-
* - No PII in logs: successUrl/cancelUrl and full session IDs are never logged.
|
|
14
|
-
* Only the first 8 chars of the session ID appear in log lines.
|
|
15
|
-
* - Error responses never echo user input (productCode, URLs).
|
|
16
|
-
* - Two paths (#8931 D2, D3): for a server-resolved workspace whose
|
|
17
|
-
* Organization has a Stripe connection, the Offerings path —
|
|
18
|
-
* `services/offerings/checkout-authorization.ts` parses the key by the one
|
|
19
|
-
* grammar, authorizes it against org-enabled ∩ workspace-shown from the
|
|
20
|
-
* rows, sells the authorized Price id on the connection's own client in an
|
|
21
|
-
* anonymous session stamped `{ site, ailk_offering }`, and records it
|
|
22
|
-
* before this route answers. It never reads the env key, the snapshot or
|
|
23
|
-
* a caller value, charges a per-IP and a per-workspace bucket on every
|
|
24
|
-
* request, and never resolves a signed-in buyer. A spoke's workspace
|
|
25
|
-
* whose Organization has no row or no connection is the one 404 before
|
|
26
|
-
* any budget, env-key read or Stripe call — never the env path (#9126).
|
|
27
|
-
* Every other request takes the env path below, which now refuses a
|
|
28
|
-
* Price whose Product carries the Offerings marker (A9). The body schema
|
|
29
|
-
* is the same on both.
|
|
30
|
-
* - No DB write on the env path: it creates a Stripe session only. It READS
|
|
31
|
-
* the database to choose the path and to resolve an optional signed-in
|
|
32
|
-
* buyer (below).
|
|
33
|
-
* - Optional cookie identity (#6866): a valid, same-origin Better Auth
|
|
34
|
-
* session cookie whose user has a verified email makes the Checkout a
|
|
35
|
-
* signed-in one — the session is pinned to the buyer's Stripe customer and
|
|
36
|
-
* stamped `metadata.ailk_user_id`, so the #6852 webhook binds it. The id
|
|
37
|
-
* comes ONLY from the session cookie, never the body; a bearer token never
|
|
38
|
-
* identifies a buyer. Any lookup failure, and a pinned customer Stripe
|
|
39
|
-
* does not know, degrades to today's anonymous Checkout — never an error.
|
|
40
|
-
* The stamped id is never echoed or logged.
|
|
41
|
-
* - Site stamp (#7868): every session carries `metadata.site` from
|
|
42
|
-
* `siteStamp()` (lib/site.ts) — on the signed-in try and the anonymous
|
|
43
|
-
* retry alike — so the Stripe webhook can tell this site's sessions from
|
|
44
|
-
* a spoke's on a shared account. The body schema has no metadata field,
|
|
45
|
-
* so no caller can set or override the stamp.
|
|
46
|
-
* - X-Internal-Auth gate: when OSS_API_INTERNAL_SECRET env var is set,
|
|
47
|
-
* the X-Internal-Auth header must match; requests without a matching header
|
|
48
|
-
* are rejected with 401. When the env var is unset, no gate is applied
|
|
49
|
-
* (backward-compat for OSS standalone deployments). See #935.
|
|
50
|
-
* - Same-origin redirects (#6877): a request that did not pass an ENABLED
|
|
51
|
-
* internal-auth gate (the OSS default) must send successUrl and cancelUrl
|
|
52
|
-
* each on one of the trusted browser origins — `config.TRUSTED_ORIGINS`
|
|
53
|
-
* (#7395: FRONTEND_URL's origin plus AILK_TRUSTED_ORIGINS), exact scheme +
|
|
54
|
-
* host + port, checked on the raw string as well as on the parse, because
|
|
55
|
-
* the raw string is what Stripe receives. Anything else is a 400 before
|
|
56
|
-
* any Stripe call or database read. A caller authenticated by
|
|
57
|
-
* X-Internal-Auth keeps its redirects; that exemption holds only while
|
|
58
|
-
* every internal caller builds its redirects itself — an internal caller
|
|
59
|
-
* MUST NEVER forward a redirect taken from its own end user.
|
|
60
|
-
*
|
|
61
|
-
* Error mapping:
|
|
62
|
-
* 401 internal-auth-required — X-Internal-Auth missing/mismatch (only when env var set)
|
|
63
|
-
* 400 validation-error — Zod validation failed (missing/invalid fields), or an
|
|
64
|
-
* unauthenticated redirect is off the site origin (#6877)
|
|
65
|
-
* 404 unknown-product-code — no active Stripe price for the given productCode; on the
|
|
66
|
-
* env path, a Price whose Product is Offerings-marked; on the
|
|
67
|
-
* Offerings path, every key that is not sellable here (#8931);
|
|
68
|
-
* every key, for a spoke whose Organization is unconnected (#9126)
|
|
69
|
-
* 429 rate_limited — Offerings path: the per-IP or per-workspace bucket (#8931 D4)
|
|
70
|
-
* 503 tenant_unresolved — a presented spoke key the resolver refused (#8931 A2)
|
|
71
|
-
* 500 stripe-not-configured — STRIPE_SECRET_KEY is missing/empty (no Stripe call)
|
|
72
|
-
* 500 live-mode-forbidden — STRIPE_SECRET_KEY is a live key the process did not admit
|
|
73
|
-
* 500 config-error — STRIPE_SECRET_KEY invalid configuration (from the SDK wrapper),
|
|
74
|
-
* or no trusted browser origin is configured when the origin
|
|
75
|
-
* lock applies (#6877, #7395)
|
|
76
|
-
* 502 checkout-unavailable — Stripe SDK threw an unexpected error; a database read
|
|
77
|
-
* choosing the path failed; Offerings path: Stripe, the rows,
|
|
78
|
-
* the connection or the session record failed (#8931)
|
|
79
|
-
*/
|
|
80
|
-
import { timingSafeEqual } from "node:crypto";
|
|
81
|
-
|
|
82
|
-
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
83
|
-
import { z } from "zod";
|
|
84
|
-
|
|
85
|
-
import { config } from "../../config/index.js";
|
|
86
|
-
import { siteStamp } from "../../lib/site.js";
|
|
87
|
-
import {
|
|
88
|
-
assertAdmittedStripeKey,
|
|
89
|
-
createCheckoutSession,
|
|
90
|
-
StripeConfigError,
|
|
91
|
-
UnknownCustomerError,
|
|
92
|
-
UnknownProductCodeError,
|
|
93
|
-
} from "../../lib/stripe.js";
|
|
94
|
-
import type {
|
|
95
|
-
CreateCheckoutSessionInput,
|
|
96
|
-
CreateCheckoutSessionOutput,
|
|
97
|
-
} from "../../lib/stripe.js";
|
|
98
|
-
import {
|
|
99
|
-
type OptionalCookieUser,
|
|
100
|
-
resolveOptionalCookieUser,
|
|
101
|
-
} from "../../middleware/auth.js";
|
|
102
|
-
import {
|
|
103
|
-
chargeWriteBudget,
|
|
104
|
-
DEFAULT_PER_IP_PER_MINUTE_STRICT,
|
|
105
|
-
} from "../../middleware/rate-limit.js";
|
|
106
|
-
import {
|
|
107
|
-
type CheckoutBuyerResult,
|
|
108
|
-
resolveCheckoutBuyer,
|
|
109
|
-
} from "../../services/checkout-buyer.js";
|
|
110
|
-
import { isOfferingsProduct } from "../../services/offerings/catalog.js";
|
|
111
|
-
import {
|
|
112
|
-
OFFERINGS_CHECKOUT_PER_WORKSPACE_PER_MINUTE,
|
|
113
|
-
type OfferingsCheckoutPath,
|
|
114
|
-
resolveOfferingsCheckoutPath,
|
|
115
|
-
startOfferingsCheckout,
|
|
116
|
-
} from "../../services/offerings/checkout-authorization.js";
|
|
117
|
-
|
|
118
|
-
type OptionalCookieUserNone = Extract<OptionalCookieUser, { kind: "none" }>;
|
|
119
|
-
|
|
120
|
-
// ── Input validation schema ───────────────────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Validates a redirect URL: must parse and must use http: or https: scheme.
|
|
124
|
-
* All other schemes (javascript:, ftp:, data:, file:, etc.) are rejected.
|
|
125
|
-
* Accepts http: for dev flexibility; Stripe enforces https: on production
|
|
126
|
-
* hosted-page redirects at their end.
|
|
127
|
-
*/
|
|
128
|
-
function isValidRedirectUrl(raw: string): boolean {
|
|
129
|
-
let parsed: URL;
|
|
130
|
-
try {
|
|
131
|
-
parsed = new URL(raw);
|
|
132
|
-
} catch {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
return parsed.protocol === "https:" || parsed.protocol === "http:";
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const PRODUCT_CODE_REGEX = /^[A-Za-z0-9._-]{1,200}$/;
|
|
139
|
-
|
|
140
|
-
const CheckoutSessionRequestSchema = z.object({
|
|
141
|
-
productCode: z
|
|
142
|
-
.string()
|
|
143
|
-
.regex(
|
|
144
|
-
PRODUCT_CODE_REGEX,
|
|
145
|
-
"productCode must match /^[A-Za-z0-9._-]{1,200}$/",
|
|
146
|
-
),
|
|
147
|
-
successUrl: z.string().refine(isValidRedirectUrl, {
|
|
148
|
-
message:
|
|
149
|
-
"successUrl must be a valid URL with a non-empty host and not use javascript: scheme",
|
|
150
|
-
}),
|
|
151
|
-
cancelUrl: z.string().refine(isValidRedirectUrl, {
|
|
152
|
-
message:
|
|
153
|
-
"cancelUrl must be a valid URL with a non-empty host and not use javascript: scheme",
|
|
154
|
-
}),
|
|
155
|
-
quantity: z.number().int().min(1).max(999).optional(),
|
|
156
|
-
allowPromotionCodes: z.boolean().optional(),
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// ── Internal-auth gate ────────────────────────────────────────────────────────
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Classify the request against the X-Internal-Auth gate (#935, #6877 D3).
|
|
163
|
-
*
|
|
164
|
-
* Gate is env-var-conditional:
|
|
165
|
-
* - env var SET, header missing or wrong → "refused" (401)
|
|
166
|
-
* - env var SET, header matches → "authenticated" (redirects kept)
|
|
167
|
-
* - env var UNSET → "open" (OSS standalone backward-
|
|
168
|
-
* compat; the request is anonymous, so the origin lock applies)
|
|
169
|
-
*
|
|
170
|
-
* Uses a constant-time string comparison to prevent timing attacks on the
|
|
171
|
-
* secret value. Node's `crypto.timingSafeEqual` requires equal-length Buffers,
|
|
172
|
-
* so we compare lengths first (short-circuit is safe — length mismatch reveals
|
|
173
|
-
* no information about the secret's content beyond its length, which is
|
|
174
|
-
* acceptable for a shared internal secret).
|
|
175
|
-
*/
|
|
176
|
-
function internalAuthVerdict(
|
|
177
|
-
request: FastifyRequest,
|
|
178
|
-
): "refused" | "open" | "authenticated" {
|
|
179
|
-
const expectedSecret = process.env["OSS_API_INTERNAL_SECRET"];
|
|
180
|
-
// Gate is disabled when env var is not set — OSS standalone backward-compat.
|
|
181
|
-
if (!expectedSecret) return "open";
|
|
182
|
-
|
|
183
|
-
const provided = request.headers["x-internal-auth"];
|
|
184
|
-
if (typeof provided !== "string" || !provided) return "refused";
|
|
185
|
-
|
|
186
|
-
// Constant-time comparison.
|
|
187
|
-
const expectedBuf = Buffer.from(expectedSecret, "utf8");
|
|
188
|
-
const providedBuf = Buffer.from(provided, "utf8");
|
|
189
|
-
if (expectedBuf.length !== providedBuf.length) return "refused";
|
|
190
|
-
|
|
191
|
-
return timingSafeEqual(expectedBuf, providedBuf)
|
|
192
|
-
? "authenticated"
|
|
193
|
-
: "refused";
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ── Same-origin redirect lock (#6877) ────────────────────────────────────────
|
|
197
|
-
|
|
198
|
-
/** C0 controls, space, DEL and backslash — anything a parser may normalize. */
|
|
199
|
-
// eslint-disable-next-line no-control-regex -- the control-character range is exactly what D2 check 1 must refuse.
|
|
200
|
-
const NON_CANONICAL_URL_CHAR = /[\u0000-\u0020\u007f\\]/;
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* True only when `raw` is on `siteOrigin` both as written and as parsed
|
|
204
|
-
* (#6877 D2). The raw-string checks matter because the route forwards `raw`
|
|
205
|
-
* itself to Stripe: WHATWG parses `https://example.com\@evil.test/x` to
|
|
206
|
-
* origin `https://example.com`, and another parser may not. Requiring the
|
|
207
|
-
* literal origin followed by `/`, `?`, `#` or the end keeps
|
|
208
|
-
* `https://example.com.evil.test` out, and exact origin equality keeps a
|
|
209
|
-
* scheme downgrade or another port out.
|
|
210
|
-
*/
|
|
211
|
-
function isSameOriginRedirect(raw: string, siteOrigin: string): boolean {
|
|
212
|
-
if (NON_CANONICAL_URL_CHAR.test(raw)) return false;
|
|
213
|
-
if (!raw.startsWith(siteOrigin)) return false;
|
|
214
|
-
const boundary = raw.charAt(siteOrigin.length);
|
|
215
|
-
if (boundary !== "" && !"/?#".includes(boundary)) return false;
|
|
216
|
-
let resolved: URL;
|
|
217
|
-
try {
|
|
218
|
-
resolved = new URL(raw);
|
|
219
|
-
} catch {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
if (resolved.protocol !== "https:" && resolved.protocol !== "http:")
|
|
223
|
-
return false;
|
|
224
|
-
return resolved.origin === siteOrigin;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ── Optional signed-in buyer (#6866) ─────────────────────────────────────────
|
|
228
|
-
|
|
229
|
-
type Buyer = NonNullable<CreateCheckoutSessionInput["buyer"]>;
|
|
230
|
-
|
|
231
|
-
/** Why a present cookie produced no buyer — the closed set D6 logs. */
|
|
232
|
-
type IdentityReason =
|
|
233
|
-
| Exclude<OptionalCookieUserNone["reason"], "no_cookie">
|
|
234
|
-
| Extract<CheckoutBuyerResult, { ok: false }>["reason"]
|
|
235
|
-
| "buyer_lookup_failed"
|
|
236
|
-
| "buyer_invalid"
|
|
237
|
-
| "stale_customer";
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Resolve the signed-in buyer from the session cookie alone (#6866 D2/D3).
|
|
241
|
-
* Never throws: a failed lookup is anonymous (F7). `identity` is the reason a
|
|
242
|
-
* present cookie produced no buyer — logged, never the user's id; absent for
|
|
243
|
-
* a call with no cookie (D6).
|
|
244
|
-
*/
|
|
245
|
-
async function resolveBuyer(
|
|
246
|
-
request: FastifyRequest,
|
|
247
|
-
): Promise<{ buyer?: Buyer; identity?: IdentityReason }> {
|
|
248
|
-
const cookieUser = await resolveOptionalCookieUser(request, {
|
|
249
|
-
trustedOrigins: config.TRUSTED_ORIGINS,
|
|
250
|
-
});
|
|
251
|
-
if (cookieUser.kind === "none") {
|
|
252
|
-
return cookieUser.reason === "no_cookie"
|
|
253
|
-
? {}
|
|
254
|
-
: { identity: cookieUser.reason };
|
|
255
|
-
}
|
|
256
|
-
try {
|
|
257
|
-
const result = await resolveCheckoutBuyer(cookieUser.userId);
|
|
258
|
-
if (!result.ok) return { identity: result.reason };
|
|
259
|
-
const { userId, email, customerId } = result.buyer;
|
|
260
|
-
return {
|
|
261
|
-
buyer: { userId, email, ...(customerId !== null && { customerId }) },
|
|
262
|
-
};
|
|
263
|
-
} catch {
|
|
264
|
-
return { identity: "buyer_lookup_failed" };
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ── Handler ───────────────────────────────────────────────────────────────────
|
|
269
|
-
|
|
270
|
-
export async function checkoutSessionsHandler(
|
|
271
|
-
request: FastifyRequest,
|
|
272
|
-
reply: FastifyReply,
|
|
273
|
-
): Promise<void> {
|
|
274
|
-
// ── X-Internal-Auth gate (env-var-conditional) ────────────────────────────
|
|
275
|
-
const authVerdict = internalAuthVerdict(request);
|
|
276
|
-
if (authVerdict === "refused") {
|
|
277
|
-
return reply.status(401).send({ error: "internal-auth-required" });
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// ── Which path (#8931 D2) — from the server-resolved workspace alone ──────
|
|
281
|
-
let checkoutPath: OfferingsCheckoutPath;
|
|
282
|
-
try {
|
|
283
|
-
checkoutPath = await resolveOfferingsCheckoutPath(request);
|
|
284
|
-
} catch (err) {
|
|
285
|
-
request.log.error(
|
|
286
|
-
{
|
|
287
|
-
method: request.method,
|
|
288
|
-
path: request.url,
|
|
289
|
-
outcome: "offerings_path_failed",
|
|
290
|
-
errName: err instanceof Error ? err.name : "unknown",
|
|
291
|
-
},
|
|
292
|
-
"[checkout] path read failed; checkout-unavailable",
|
|
293
|
-
);
|
|
294
|
-
return reply.status(502).send({ error: "checkout-unavailable" });
|
|
295
|
-
}
|
|
296
|
-
if (checkoutPath.kind === "refused") {
|
|
297
|
-
return reply.status(503).send({ error: "tenant_unresolved" });
|
|
298
|
-
}
|
|
299
|
-
if (checkoutPath.kind === "unavailable") {
|
|
300
|
-
return reply.status(502).send({ error: "checkout-unavailable" });
|
|
301
|
-
}
|
|
302
|
-
if (checkoutPath.kind === "unconnected") {
|
|
303
|
-
// A spoke sells nothing until its Organization connects Stripe (#9126):
|
|
304
|
-
// the one 404 every miss answers, before any budget, env-key read or
|
|
305
|
-
// Stripe call, so the hub's env key never sells for a spoke.
|
|
306
|
-
request.log.warn(
|
|
307
|
-
{ outcome: "unconnected", workspaceId: checkoutPath.workspaceId },
|
|
308
|
-
"[checkout] spoke has no Stripe connection; unknown-product-code",
|
|
309
|
-
);
|
|
310
|
-
return reply.status(404).send({ error: "unknown-product-code" });
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (checkoutPath.kind === "env") {
|
|
314
|
-
const stripeSecretKey = process.env["STRIPE_SECRET_KEY"];
|
|
315
|
-
if (!stripeSecretKey) {
|
|
316
|
-
return reply.status(500).send({ error: "stripe-not-configured" });
|
|
317
|
-
}
|
|
318
|
-
try {
|
|
319
|
-
assertAdmittedStripeKey(stripeSecretKey);
|
|
320
|
-
} catch {
|
|
321
|
-
return reply.status(500).send({ error: "live-mode-forbidden" });
|
|
322
|
-
}
|
|
323
|
-
} else {
|
|
324
|
-
// The Offerings bucket (#8931 D4), before the body is read, whatever the
|
|
325
|
-
// process's mode: per caller IP, and per workspace — the bound a
|
|
326
|
-
// caller-set X-Forwarded-For cannot dodge. One charge at a time, the IP
|
|
327
|
-
// first: a request its IP bucket refuses never spends the workspace's,
|
|
328
|
-
// so one address cannot shut a seller's checkout for every buyer.
|
|
329
|
-
const charges = [
|
|
330
|
-
{ key: `ip,${request.ip}`, limit: DEFAULT_PER_IP_PER_MINUTE_STRICT },
|
|
331
|
-
{
|
|
332
|
-
key: `ws,${checkoutPath.workspaceId}`,
|
|
333
|
-
limit: OFFERINGS_CHECKOUT_PER_WORKSPACE_PER_MINUTE,
|
|
334
|
-
},
|
|
335
|
-
];
|
|
336
|
-
for (const charge of charges) {
|
|
337
|
-
let budget: Awaited<ReturnType<typeof chargeWriteBudget>>;
|
|
338
|
-
try {
|
|
339
|
-
budget = await chargeWriteBudget({
|
|
340
|
-
scope: "offeringscheckout",
|
|
341
|
-
charges: [charge],
|
|
342
|
-
windowMs: 60_000,
|
|
343
|
-
now: Date.now(),
|
|
344
|
-
warn: (obj, msg) => request.log.warn(obj, msg),
|
|
345
|
-
});
|
|
346
|
-
} catch (err) {
|
|
347
|
-
// A budget store that throws is a store failure, and the Offerings
|
|
348
|
-
// path's contract for one is 502 checkout-unavailable (code review on
|
|
349
|
-
// PR #9032) — never Fastify's 500.
|
|
350
|
-
request.log.error(
|
|
351
|
-
{
|
|
352
|
-
method: request.method,
|
|
353
|
-
path: request.url,
|
|
354
|
-
outcome: "offerings_budget_failed",
|
|
355
|
-
errName: err instanceof Error ? err.name : "unknown",
|
|
356
|
-
},
|
|
357
|
-
"[checkout] budget read failed; checkout-unavailable",
|
|
358
|
-
);
|
|
359
|
-
return reply.status(502).send({ error: "checkout-unavailable" });
|
|
360
|
-
}
|
|
361
|
-
if (!budget.ok) {
|
|
362
|
-
return reply
|
|
363
|
-
.header("Retry-After", String(budget.retryAfterSeconds))
|
|
364
|
-
.status(429)
|
|
365
|
-
.send({
|
|
366
|
-
error: "rate_limited",
|
|
367
|
-
retryAfterSeconds: budget.retryAfterSeconds,
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const parsed = CheckoutSessionRequestSchema.safeParse(request.body);
|
|
374
|
-
if (!parsed.success) {
|
|
375
|
-
return reply.status(400).send({
|
|
376
|
-
error: "validation-error",
|
|
377
|
-
details: parsed.error.issues,
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const { productCode, successUrl, cancelUrl, quantity, allowPromotionCodes } =
|
|
382
|
-
parsed.data;
|
|
383
|
-
|
|
384
|
-
// ── Same-origin redirect lock (#6877) — before any Stripe or DB call ──────
|
|
385
|
-
if (authVerdict === "open") {
|
|
386
|
-
// An empty list is FRONTEND_URL unset in production or unusable, with no
|
|
387
|
-
// AILK_TRUSTED_ORIGINS (#7395 D4) — refused before any Stripe or DB call.
|
|
388
|
-
const trustedOrigins = config.TRUSTED_ORIGINS;
|
|
389
|
-
if (trustedOrigins.length === 0) {
|
|
390
|
-
request.log.error(
|
|
391
|
-
{ method: request.method, path: request.url },
|
|
392
|
-
"[checkout] no trusted browser origin is configured",
|
|
393
|
-
);
|
|
394
|
-
return reply.status(500).send({ error: "config-error" });
|
|
395
|
-
}
|
|
396
|
-
const refused = (["successUrl", "cancelUrl"] as const).filter(
|
|
397
|
-
(field) =>
|
|
398
|
-
!trustedOrigins.some((origin) =>
|
|
399
|
-
isSameOriginRedirect(parsed.data[field], origin),
|
|
400
|
-
),
|
|
401
|
-
);
|
|
402
|
-
if (refused.length > 0) {
|
|
403
|
-
// Field names only — the refused URL is never logged or echoed.
|
|
404
|
-
request.log.warn(
|
|
405
|
-
{
|
|
406
|
-
method: request.method,
|
|
407
|
-
path: request.url,
|
|
408
|
-
outcome: "redirect_origin_refused",
|
|
409
|
-
fields: refused,
|
|
410
|
-
},
|
|
411
|
-
"[checkout] redirect origin refused",
|
|
412
|
-
);
|
|
413
|
-
return reply.status(400).send({
|
|
414
|
-
error: "validation-error",
|
|
415
|
-
details: refused.map((field) => ({
|
|
416
|
-
code: "custom",
|
|
417
|
-
path: [field],
|
|
418
|
-
message: `${field} must be on this site's origin`,
|
|
419
|
-
})),
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
let created: CreateCheckoutSessionOutput;
|
|
425
|
-
let buyer: Buyer | undefined;
|
|
426
|
-
let identity: IdentityReason | undefined;
|
|
427
|
-
|
|
428
|
-
if (checkoutPath.kind === "offerings") {
|
|
429
|
-
// Anonymous by construction (#8931 C4): no buyer is resolved, and the
|
|
430
|
-
// session always allows promotion codes (A7, C3).
|
|
431
|
-
const outcome = await startOfferingsCheckout({
|
|
432
|
-
workspaceId: checkoutPath.workspaceId,
|
|
433
|
-
organizationId: checkoutPath.organizationId,
|
|
434
|
-
productCode,
|
|
435
|
-
successUrl,
|
|
436
|
-
cancelUrl,
|
|
437
|
-
quantity: quantity ?? 1,
|
|
438
|
-
});
|
|
439
|
-
if (outcome.kind === "unknown") {
|
|
440
|
-
// The one 404 every miss answers — never the key (A3, A5).
|
|
441
|
-
return reply.status(404).send({ error: "unknown-product-code" });
|
|
442
|
-
}
|
|
443
|
-
if (outcome.kind === "unavailable") {
|
|
444
|
-
return reply.status(502).send({ error: "checkout-unavailable" });
|
|
445
|
-
}
|
|
446
|
-
created = outcome;
|
|
447
|
-
} else {
|
|
448
|
-
const sessionInput: CreateCheckoutSessionInput = {
|
|
449
|
-
productCode,
|
|
450
|
-
successUrl,
|
|
451
|
-
cancelUrl,
|
|
452
|
-
quantity: quantity ?? 1,
|
|
453
|
-
// Omitted when the caller says nothing — Stripe's default (no promo-code
|
|
454
|
-
// field) is unchanged for every existing caller.
|
|
455
|
-
...(allowPromotionCodes !== undefined && { allowPromotionCodes }),
|
|
456
|
-
metadata: siteStamp(),
|
|
457
|
-
};
|
|
458
|
-
// The env path never sells an Offerings product (#8931 A9, D8): the one
|
|
459
|
-
// marker rule decides it, on the first try and the anonymous retry.
|
|
460
|
-
const envOptions = { refuseProduct: isOfferingsProduct };
|
|
461
|
-
// Built only from the session cookie — never from `parsed.data` (#6866 D5).
|
|
462
|
-
({ buyer, identity } = await resolveBuyer(request));
|
|
463
|
-
|
|
464
|
-
try {
|
|
465
|
-
try {
|
|
466
|
-
created = await createCheckoutSession(
|
|
467
|
-
{
|
|
468
|
-
...sessionInput,
|
|
469
|
-
...(buyer !== undefined && { buyer }),
|
|
470
|
-
},
|
|
471
|
-
envOptions,
|
|
472
|
-
);
|
|
473
|
-
} catch (err) {
|
|
474
|
-
// The buyer itself cannot be used — a pinned customer Stripe does not
|
|
475
|
-
// know (#6866 D8), or a stored email / customer id the library's schema
|
|
476
|
-
// refuses (F7: no new failure mode). Retry once as anonymous; the
|
|
477
|
-
// stored binding is never touched here. The request fields already
|
|
478
|
-
// passed the route's schema, so an anonymous retry that still fails
|
|
479
|
-
// maps exactly as today.
|
|
480
|
-
const reason: IdentityReason | undefined =
|
|
481
|
-
err instanceof UnknownCustomerError
|
|
482
|
-
? "stale_customer"
|
|
483
|
-
: err instanceof Error && err.name === "ZodError"
|
|
484
|
-
? "buyer_invalid"
|
|
485
|
-
: undefined;
|
|
486
|
-
if (buyer === undefined || reason === undefined) throw err;
|
|
487
|
-
buyer = undefined;
|
|
488
|
-
identity = reason;
|
|
489
|
-
created = await createCheckoutSession(sessionInput, envOptions);
|
|
490
|
-
}
|
|
491
|
-
} catch (err) {
|
|
492
|
-
if (err instanceof UnknownProductCodeError) {
|
|
493
|
-
// 404 — do NOT echo productCode in the response body
|
|
494
|
-
return reply.status(404).send({ error: "unknown-product-code" });
|
|
495
|
-
}
|
|
496
|
-
if (err instanceof StripeConfigError) {
|
|
497
|
-
// 500 — misconfiguration; log message (no PII) but never echo in response
|
|
498
|
-
request.log.error(
|
|
499
|
-
{
|
|
500
|
-
method: request.method,
|
|
501
|
-
path: request.url,
|
|
502
|
-
errName: err.name,
|
|
503
|
-
message: err.message,
|
|
504
|
-
},
|
|
505
|
-
"[checkout] config error",
|
|
506
|
-
);
|
|
507
|
-
return reply.status(500).send({ error: "config-error" });
|
|
508
|
-
}
|
|
509
|
-
request.log.error(
|
|
510
|
-
{
|
|
511
|
-
method: request.method,
|
|
512
|
-
path: request.url,
|
|
513
|
-
errName: err instanceof Error ? err.name : "unknown",
|
|
514
|
-
},
|
|
515
|
-
"[checkout] stripe error; checkout-unavailable",
|
|
516
|
-
);
|
|
517
|
-
return reply.status(502).send({ error: "checkout-unavailable" });
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
const { sessionId, url } = created;
|
|
522
|
-
|
|
523
|
-
request.log.info(
|
|
524
|
-
{
|
|
525
|
-
method: request.method,
|
|
526
|
-
path: request.url,
|
|
527
|
-
sessionIdPrefix: sessionId.substring(0, 8),
|
|
528
|
-
status: 201,
|
|
529
|
-
buyer: buyer !== undefined ? "signed_in" : "anonymous",
|
|
530
|
-
...(identity !== undefined && { identity }),
|
|
531
|
-
...(checkoutPath.kind === "offerings" && {
|
|
532
|
-
checkout: "offerings",
|
|
533
|
-
workspaceId: checkoutPath.workspaceId,
|
|
534
|
-
}),
|
|
535
|
-
},
|
|
536
|
-
"[checkout] session created",
|
|
537
|
-
);
|
|
538
|
-
|
|
539
|
-
return reply.status(201).send({ sessionId, url });
|
|
540
|
-
}
|