create-ailk 0.1.0 → 0.2.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 +124 -3
- package/component-catalog.md +456 -0
- package/dist/cli.js +92 -16
- package/dist/component-catalog.d.ts +11 -0
- package/dist/component-catalog.js +39 -0
- package/dist/copy.d.ts +7 -0
- package/dist/copy.js +24 -0
- package/dist/derive-architecture.d.ts +13 -0
- package/dist/derive-architecture.js +238 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +19 -2
- package/dist/lib/extract-module-bundle.d.ts +16 -0
- package/dist/lib/extract-module-bundle.js +184 -0
- package/dist/lib/fetch-module.d.ts +55 -0
- package/dist/lib/fetch-module.js +125 -0
- package/dist/lib/module-license-gate.d.ts +48 -0
- package/dist/lib/module-license-gate.js +67 -0
- package/dist/lib/module-tier.d.ts +35 -0
- package/dist/lib/module-tier.js +37 -0
- package/dist/module-architecture.d.ts +175 -0
- package/dist/module-architecture.js +663 -0
- package/dist/parse-args.d.ts +47 -1
- package/dist/parse-args.js +143 -2
- package/dist/programmatic.d.ts +160 -3
- package/dist/programmatic.js +196 -7
- package/dist/surfaces.d.ts +211 -0
- package/dist/surfaces.js +532 -0
- package/dist/sync-routes.d.ts +92 -0
- package/dist/sync-routes.js +350 -0
- package/package.json +12 -5
- package/templates/.claude/rules/architecture.md +6 -2
- package/templates/.claude/skills/README.md +9 -9
- package/templates/.claude/skills/add-schema/SKILL.md +14 -7
- package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
- package/templates/.claude/skills/provision-config/SKILL.md +1 -1
- package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
- package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
- package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
- package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
- package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
- package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
- package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
- package/templates/.env.example +126 -2
- package/templates/CLAUDE.md +11 -5
- package/templates/README.md +1 -1
- package/templates/apps/api/.env.example +38 -0
- package/templates/apps/api/CLAUDE.md +78 -1
- package/templates/apps/api/LICENSE +201 -0
- package/templates/apps/api/api/index.ts +6 -141
- package/templates/apps/api/jest.config.cjs +16 -0
- package/templates/apps/api/package.json +22 -6
- package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
- package/templates/apps/api/src/__tests__/server.test.ts +46 -0
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
- package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
- package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
- package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
- package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
- package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
- package/templates/apps/api/src/bin/seed-presets.ts +58 -0
- package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
- package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
- package/templates/apps/api/src/cli.ts +5 -11
- package/templates/apps/api/src/config/index.ts +35 -0
- package/templates/apps/api/src/config/modules.ts +43 -0
- package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +241 -0
- package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
- package/templates/apps/api/src/lib/site.ts +16 -0
- package/templates/apps/api/src/lib/stripe.ts +22 -0
- package/templates/apps/api/src/lib/tenant-db.ts +225 -0
- package/templates/apps/api/src/lib/ws-token.ts +91 -0
- package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
- package/templates/apps/api/src/middleware/auth.ts +43 -8
- package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
- package/templates/apps/api/src/middleware/tenant.ts +99 -0
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
- package/templates/apps/api/src/openapi/spec.ts +312 -3
- package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
- package/templates/apps/api/src/routes/aeo/index.ts +107 -0
- package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
- package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
- package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
- package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
- package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
- package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
- package/templates/apps/api/src/routes/billing/index.ts +36 -0
- package/templates/apps/api/src/routes/billing/portal.ts +182 -0
- package/templates/apps/api/src/routes/billing/read.ts +75 -0
- package/templates/apps/api/src/routes/billing/usage.ts +153 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
- package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
- package/templates/apps/api/src/routes/content/create.ts +1 -0
- package/templates/apps/api/src/routes/content/delete.ts +1 -0
- package/templates/apps/api/src/routes/content/index.ts +42 -6
- package/templates/apps/api/src/routes/content/update.ts +1 -0
- package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
- package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
- package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
- package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
- package/templates/apps/api/src/routes/flows/README.md +147 -0
- package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
- package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
- package/templates/apps/api/src/routes/flows/index.ts +202 -0
- package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
- package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
- package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
- package/templates/apps/api/src/routes/leads/index.ts +43 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +558 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +869 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
- package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
- package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
- package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
- package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
- package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
- package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
- package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
- package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
- package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
- package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
- package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
- package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
- package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
- package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
- package/templates/apps/api/src/routes/schedule/index.ts +249 -0
- package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
- package/templates/apps/api/src/routes/slack/actions.ts +177 -0
- package/templates/apps/api/src/routes/slack/index.ts +38 -0
- package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
- package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
- package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
- package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
- package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
- package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
- package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
- package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
- package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
- package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
- package/templates/apps/api/src/routes/webhooks/README.md +35 -12
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
- package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
- package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
- package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
- package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
- package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
- package/templates/apps/api/src/server.ts +307 -7
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
- package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
- package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
- package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
- package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
- package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
- package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
- package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
- package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
- package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
- package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
- package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
- package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
- package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
- package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
- package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
- package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
- package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
- package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
- package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
- package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
- package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
- package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
- package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
- package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
- package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
- package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
- package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
- package/templates/apps/api/src/services/abandon-email.ts +307 -0
- package/templates/apps/api/src/services/aeo-score.ts +288 -0
- package/templates/apps/api/src/services/answer-distributions.ts +138 -0
- package/templates/apps/api/src/services/consent.ts +236 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
- package/templates/apps/api/src/services/entitlements.ts +102 -0
- package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
- package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
- package/templates/apps/api/src/services/flow-engine.ts +1296 -0
- package/templates/apps/api/src/services/lead-promotion.ts +176 -0
- package/templates/apps/api/src/services/lead-routing.ts +3 -3
- package/templates/apps/api/src/services/listing-config.ts +102 -0
- package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
- package/templates/apps/api/src/services/listing-promotion.ts +342 -0
- package/templates/apps/api/src/services/playbook-compile.ts +398 -0
- package/templates/apps/api/src/services/playbook-render.ts +263 -0
- package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
- package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
- package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
- package/templates/apps/api/src/services/project-listings.ts +1472 -0
- package/templates/apps/api/src/services/recommender-capture.ts +835 -0
- package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
- package/templates/apps/api/src/services/scheduling/index.ts +63 -0
- package/templates/apps/api/src/services/scheduling/types.ts +88 -0
- package/templates/apps/api/src/services/slack-notify.ts +240 -0
- package/templates/apps/api/src/services/tenant-context.ts +451 -0
- package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
- package/templates/apps/api/src/services/usage-credits.ts +340 -0
- package/templates/apps/api/src/services/usage-metering.ts +699 -0
- package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
- package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
- package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
- package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
- package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
- package/templates/apps/api/src/vercel-handler.ts +165 -0
- package/templates/apps/api/tsconfig.json +0 -12
- package/templates/apps/mcp/CLAUDE.md +19 -9
- package/templates/apps/mcp/LICENSE +201 -0
- package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
- package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
- package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
- package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
- package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
- package/templates/apps/mcp/package.json +6 -4
- package/templates/apps/mcp/src/config/modules.ts +37 -0
- package/templates/apps/mcp/src/server.ts +109 -18
- package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
- package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
- package/templates/apps/mcp/src/tools/create_page.ts +1 -0
- package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
- package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
- package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
- package/templates/apps/mcp/src/tools/index.ts +25 -0
- package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
- package/templates/apps/mcp/src/tools/purchase.ts +3 -0
- package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
- package/templates/apps/mcp/src/tools/update_page.ts +1 -0
- package/templates/apps/mcp/tsconfig.json +1 -9
- package/templates/apps/web/.env.example +44 -0
- package/templates/apps/web/CLAUDE.md +28 -0
- package/templates/apps/web/LICENSE +201 -0
- package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
- package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
- package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
- package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
- package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
- package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
- package/templates/apps/web/__tests__/setup.ts +22 -32
- package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
- package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
- package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
- package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
- package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
- package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
- package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
- package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
- package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/layout.tsx +127 -16
- package/templates/apps/web/app/[locale]/page.tsx +10 -1
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
- package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
- package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
- package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
- package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
- package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
- package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
- package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
- package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
- package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/globals.css +35 -0
- package/templates/apps/web/app/llms.txt/route.ts +2 -1
- package/templates/apps/web/app/og/route.tsx +10 -2
- package/templates/apps/web/auth.ts +8 -0
- package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
- package/templates/apps/web/content/en/blog/README.txt +25 -0
- package/templates/apps/web/content/en/docs/index.mdx +28 -0
- package/templates/apps/web/jest.config.cjs +90 -27
- package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
- package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
- package/templates/apps/web/lib/account-config.ts +13 -0
- package/templates/apps/web/lib/server-api.ts +85 -0
- package/templates/apps/web/lib/site-brand.tsx +121 -0
- package/templates/apps/web/lib/site-theme.ts +74 -0
- package/templates/apps/web/messages/en.json +275 -7
- package/templates/apps/web/middleware.ts +11 -3
- package/templates/apps/web/next.config.mjs +8 -2
- package/templates/apps/web/package.json +17 -7
- package/templates/apps/web/postcss.config.mjs +23 -0
- package/templates/apps/web/public/.well-known/security.txt +3 -3
- package/templates/apps/web/src/lib/routes.ts +15 -2
- package/templates/apps/web/tsconfig.json +1 -16
- package/templates/content/_site.mdx +36 -25
- package/templates/content/en/AboutPage/about.mdx +24 -14
- package/templates/content/en/ContactPage/contact.mdx +23 -14
- package/templates/content/en/FAQPage/faq.mdx +10 -392
- package/templates/content/en/HomePage/home.mdx +14 -285
- package/templates/database/CHANGELOG.md +443 -0
- package/templates/database/LICENSE +201 -0
- package/templates/database/README.md +3 -1
- package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
- package/templates/database/__tests__/flows-presets.test.ts +173 -0
- package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
- package/templates/database/__tests__/seed.test.ts +88 -0
- package/templates/database/__tests__/user-cascade.test.ts +19 -4
- package/templates/database/auth/schema.prisma +521 -0
- package/templates/database/config/schema.prisma +234 -0
- package/templates/database/content/schema.prisma +263 -0
- package/templates/database/flows/listing-diagnostic.ts +182 -0
- package/templates/database/flows/presets.ts +315 -0
- package/templates/database/inbox/__tests__/schema.test.ts +4 -0
- package/templates/database/inbox/schema.prisma +574 -3
- package/templates/database/jest.config.cjs +8 -0
- package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
- package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
- package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
- package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
- package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
- package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
- package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
- package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
- package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
- package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
- package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
- package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
- package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
- package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
- package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
- package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
- package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
- package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
- package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
- package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
- package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
- package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
- package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
- package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
- package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
- package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
- package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
- package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
- package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
- package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
- package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
- package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
- package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
- package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
- package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
- package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
- package/templates/database/ops/schema.prisma +27 -0
- package/templates/database/package.json +42 -4
- package/templates/database/prisma.config.ts +1 -1
- package/templates/database/schema.prisma +19 -435
- package/templates/database/seed-test.ts +14 -4
- package/templates/database/seed.ts +148 -0
- package/templates/database/tenancy.ts +45 -0
- package/templates/database/tsconfig.flows.json +15 -0
- package/templates/database/tsconfig.json +7 -1
- package/templates/database/tsconfig.tenancy.json +14 -0
- package/templates/package.json +30 -55
- package/templates/pnpm-workspace.yaml +0 -3
- package/templates/project.yaml +1 -1
- package/templates/tsconfig.json +1 -37
- package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
- package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
- package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
- package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
- package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
- package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
- package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
- package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
- package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
- package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
- package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
- package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
- package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
- package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
- package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
- package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
- package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
- package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
- package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
- package/templates/apps/web/__tests__/page.test.tsx +0 -230
- package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
- package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
- package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
- package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
- package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
- package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
- package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
- package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
- package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
- package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
- package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
- package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
- package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
- package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
- package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
- package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
- package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
- package/templates/apps/web/content/en/blog/index.mdx +0 -15
- package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
- package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
- package/templates/content/.gitkeep +0 -0
- package/templates/content/CLAUDE.md +0 -85
- package/templates/content/ar/HomePage/home.mdx +0 -123
- package/templates/content/ar/_site.mdx +0 -35
- package/templates/content/de/_site.mdx +0 -29
- package/templates/content/en/.gitkeep +0 -0
- package/templates/content/en/AboutPage/about.schema.json +0 -82
- package/templates/content/en/BookCall/book-call.mdx +0 -19
- package/templates/content/en/BookCall/book-call.schema.json +0 -121
- package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
- package/templates/content/en/CareersList/careers.mdx +0 -24
- package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
- package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
- package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
- package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
- package/templates/content/en/ClientsList/clients.mdx +0 -38
- package/templates/content/en/ContactPage/contact.schema.json +0 -100
- package/templates/content/en/ContactPoint/primary.mdx +0 -14
- package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
- package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
- package/templates/content/en/CoursesList/courses.mdx +0 -22
- package/templates/content/en/DownloadsList/downloads.mdx +0 -22
- package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
- package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
- package/templates/content/en/EventsList/events.mdx +0 -26
- package/templates/content/en/FAQPage/faq.schema.json +0 -231
- package/templates/content/en/GlossaryList/glossary.mdx +0 -19
- package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
- package/templates/content/en/HomePage/home.schema.json +0 -88
- package/templates/content/en/HomePage/launch-service.mdx +0 -475
- package/templates/content/en/HomePage/launch-service.schema.json +0 -138
- package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
- package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
- package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
- package/templates/content/en/Integration/github-actions.mdx +0 -31
- package/templates/content/en/Integration/openai-api.mdx +0 -26
- package/templates/content/en/Integration/vercel.mdx +0 -32
- package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
- package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
- package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
- package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
- package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
- package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
- package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
- package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
- package/templates/content/en/LandingPage/developers.mdx +0 -19
- package/templates/content/en/LandingPage/developers.schema.json +0 -109
- package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
- package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
- package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
- package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
- package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
- package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
- package/templates/content/en/LandingPage/platform.mdx +0 -369
- package/templates/content/en/LandingPage/platform.schema.json +0 -166
- package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
- package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
- package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
- package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
- package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
- package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
- package/templates/content/en/Organization/site-owner.mdx +0 -16
- package/templates/content/en/Partner/prisma.mdx +0 -31
- package/templates/content/en/Partner/supabase.mdx +0 -30
- package/templates/content/en/Partner/vercel.mdx +0 -28
- package/templates/content/en/PartnersList/partners.mdx +0 -22
- package/templates/content/en/Person/founder.mdx +0 -18
- package/templates/content/en/Pricing/pricing.mdx +0 -538
- package/templates/content/en/Pricing/pricing.schema.json +0 -209
- package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
- package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
- package/templates/content/en/Product/ailk.mdx +0 -19
- package/templates/content/en/Product/ailk.schema.json +0 -94
- package/templates/content/en/Product/api-launch-kit.mdx +0 -26
- package/templates/content/en/Product/launch-kit.mdx +0 -28
- package/templates/content/en/ProductsList/products.mdx +0 -26
- package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
- package/templates/content/en/ProjectsList/projects.mdx +0 -25
- package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
- package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
- package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
- package/templates/content/en/ResourcesHub/resources.mdx +0 -22
- package/templates/content/en/Service/consulting.mdx +0 -22
- package/templates/content/en/Service/implementation.mdx +0 -30
- package/templates/content/en/Service/launch-service.mdx +0 -232
- package/templates/content/en/Service/launch.mdx +0 -228
- package/templates/content/en/ServicesList/services.mdx +0 -84
- package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
- package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
- package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
- package/templates/content/en/TeamList/team.mdx +0 -63
- package/templates/content/en/TeamMember/founder.mdx +0 -72
- package/templates/content/en/TermsPage/terms.mdx +0 -19
- package/templates/content/en/TermsPage/terms.schema.json +0 -99
- package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
- package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
- package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
- package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
- package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
- package/templates/content/en-XA/AboutPage/about.mdx +0 -8
- package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
- package/templates/content/en-XA/HomePage/home.mdx +0 -8
- package/templates/content/es/_site.mdx +0 -30
- package/templates/content/fr/_site.mdx +0 -30
- package/templates/content/ja/_site.mdx +0 -30
- package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
- package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
- package/templates/content/pt-BR/HomePage/home.mdx +0 -23
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time checkout route — scaffolded by `scaffold-commerce`'s checkout
|
|
3
|
+
* surface (FEAT-023 U2, Step 10e). Instantiated into the consumer's own
|
|
4
|
+
* `apps/api/src/routes/checkout/` (Fastify — checkout creation is
|
|
5
|
+
* server-side only, architecture.yaml `apps/web.may_not_import_from:
|
|
6
|
+
* packages/stripe-client`). Register the exported handler on a POST route,
|
|
7
|
+
* scoped behind the shared per-IP rate-limit middleware (mirrors the "leads
|
|
8
|
+
* routes" anonymous-write registration pattern in `apps/api/src/server.ts`
|
|
9
|
+
* — this route is exactly that shape: an anonymous write with no auth):
|
|
10
|
+
*
|
|
11
|
+
* await fastify.register(async (checkoutScope) => {
|
|
12
|
+
* checkoutScope.register(rateLimitMiddleware); // apps/api/src/middleware/rate-limit.ts
|
|
13
|
+
* checkoutScope.post("/api/checkout/one-time", oneTimeCheckoutHandler);
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* ── SCAFFOLD-TIME EDITS REQUIRED (both — the route is fail-closed until then) ─
|
|
17
|
+
* 1. `ALLOWED_LOOKUP_KEYS` below — replace the placeholder with the
|
|
18
|
+
* lookup_key(s) this surface may sell (Step 10e-i step 1's selection).
|
|
19
|
+
* Until you do, EVERY request 404s. That is deliberate.
|
|
20
|
+
* 2. `FRONTEND_URL` in the consumer's API env — the site origin every
|
|
21
|
+
* checkout redirect is locked to. Unset ⇒ this route 500s `config-error`.
|
|
22
|
+
*
|
|
23
|
+
* ── Auth posture — PUBLIC BY DESIGN, not an oversight ──────────────────────
|
|
24
|
+
* This handler is intentionally UNAUTHENTICATED. It is a storefront "buy"
|
|
25
|
+
* action, not a management endpoint — it does exactly ONE thing: create a
|
|
26
|
+
* NEW Stripe Checkout Session for ONE OF THE PRODUCTS THIS SURFACE WAS
|
|
27
|
+
* SCAFFOLDED TO SELL (`ALLOWED_LOOKUP_KEYS`, enforced server-side before any
|
|
28
|
+
* Stripe call; the caller can neither name an arbitrary price nor set an
|
|
29
|
+
* amount — the price is resolved server-side via
|
|
30
|
+
* `stripe.prices.list({lookup_keys})` inside `createCheckoutSession`).
|
|
31
|
+
* It has:
|
|
32
|
+
* - NO list/read of existing sessions, products, or any other data,
|
|
33
|
+
* - NO update/delete/archive of anything,
|
|
34
|
+
* - NO local database read or write (stateless — the only side effect is
|
|
35
|
+
* a new object created directly in the site owner's own Stripe account),
|
|
36
|
+
* - NO cross-tenant surface (single-tenant `packages/stripe-client`
|
|
37
|
+
* commerce catalog; the multi-tenant module's org-scoped billing is a
|
|
38
|
+
* SEPARATE surface — `apps/workspace` may not import `stripe-client` at
|
|
39
|
+
* all, architecture.yaml — so there is no tenant to scope this route to).
|
|
40
|
+
* A management surface (list/update/archive payment objects) is a DIFFERENT
|
|
41
|
+
* shape and MUST be authenticated + tenant-scoped — do not use this handler
|
|
42
|
+
* as a pattern for one.
|
|
43
|
+
*
|
|
44
|
+
* NOT a mirror of `apps/api/src/routes/checkout/sessions.ts`'s auth posture.
|
|
45
|
+
* That route carries an env-conditional `X-Internal-Auth` gate
|
|
46
|
+
* (`OSS_API_INTERNAL_SECRET`, #935); this one deliberately does not — it is
|
|
47
|
+
* public on purpose. If you have set `OSS_API_INTERNAL_SECRET` to lock
|
|
48
|
+
* `/v1/checkout/sessions` to internal callers, be aware that registering THIS
|
|
49
|
+
* route adds a second, ungated checkout entry point to the same API. Add your
|
|
50
|
+
* own gate here if that is not what you want.
|
|
51
|
+
*
|
|
52
|
+
* ── Abuse hardening ─────────────────────────────────────────────────────
|
|
53
|
+
* An unauthenticated session-creation endpoint is a known "card testing"
|
|
54
|
+
* target (repeated session-creation attempts against stolen card numbers).
|
|
55
|
+
* The registration example above wires the shared per-IP rate-limit
|
|
56
|
+
* middleware for exactly this reason — do not register this route without
|
|
57
|
+
* it. AILK's `apps/api/src/server.ts` also registers a GLOBAL rate limit, but
|
|
58
|
+
* it is IN-MEMORY: on a multi-instance or serverless deployment it does not
|
|
59
|
+
* bound the aggregate rate, so back it with a shared store (e.g. Redis) before
|
|
60
|
+
* taking real traffic. Input is otherwise tightly bounded: `lookupKey` must be
|
|
61
|
+
* in `ALLOWED_LOOKUP_KEYS`, `quantity` is clamped 1-999, and
|
|
62
|
+
* `successUrl`/`cancelUrl` are origin-locked to the site's own origin.
|
|
63
|
+
*
|
|
64
|
+
* Design decision (spec § 3): consumes `stripe-client/checkout.ts` directly
|
|
65
|
+
* — no new client method. One-time payment is `mode: "payment"` on the
|
|
66
|
+
* existing Checkout Session call (pinned here, never caller-overridable, so
|
|
67
|
+
* this endpoint can never be used to open a subscription session).
|
|
68
|
+
*
|
|
69
|
+
* Config: loads via the sibling `loadStripeSurfaceConfig()`
|
|
70
|
+
* (`stripe-config.template.ts`, instantiated alongside this file at Step
|
|
71
|
+
* 10d) rather than re-deriving env access — the SAME `assertSandboxKey`
|
|
72
|
+
* test-mode-only guard `getStripeClient()` already applies is enforced again
|
|
73
|
+
* here, belt-and-suspenders, before any Stripe call. This route deliberately
|
|
74
|
+
* does NOT hand-roll its own live-key prefix check — it defers ENTIRELY to
|
|
75
|
+
* the shared `assertSandboxKey` guard (`packages/stripe-client/src/client.ts`)
|
|
76
|
+
* so it inherits that guard's coverage (and any future fix to it) rather than
|
|
77
|
+
* risking a second, divergent implementation.
|
|
78
|
+
*
|
|
79
|
+
* Security invariants:
|
|
80
|
+
* - Allowlisted products only: a `lookupKey` outside `ALLOWED_LOOKUP_KEYS` is
|
|
81
|
+
* refused BEFORE any Stripe call, with the same response shape as a
|
|
82
|
+
* genuinely-unknown price (so the 404 is not an enumeration oracle).
|
|
83
|
+
* - Origin-locked redirects: `successUrl`/`cancelUrl` may only resolve to
|
|
84
|
+
* this site's own origin. An attacker cannot mint a real Checkout Session
|
|
85
|
+
* for this merchant that lands the paying victim on an attacker page.
|
|
86
|
+
* - Sandbox-only: a live Stripe key is rejected before any Stripe call.
|
|
87
|
+
* - No PII in logs: successUrl/cancelUrl and full session ids are never
|
|
88
|
+
* logged — only the first 8 chars of the session id.
|
|
89
|
+
* - Error responses never echo user input (lookupKey, URLs).
|
|
90
|
+
*
|
|
91
|
+
* Error mapping:
|
|
92
|
+
* 400 validation-error — Zod validation failed (missing/invalid fields)
|
|
93
|
+
* 400 invalid-redirect-url — successUrl/cancelUrl does not resolve same-origin
|
|
94
|
+
* 404 unknown-product-code — lookupKey is not allowlisted for this surface,
|
|
95
|
+
* OR no active Stripe price exists for it (the
|
|
96
|
+
* 404 deliberately does not distinguish the two)
|
|
97
|
+
* 429 rate_limited — per-IP rate limit exceeded (see registration example above)
|
|
98
|
+
* 500 live-mode-forbidden — the configured Stripe key is a LIVE key
|
|
99
|
+
* 500 config-error — the Stripe/site env is missing or misconfigured
|
|
100
|
+
* 502 checkout-unavailable — Stripe SDK threw an unexpected error
|
|
101
|
+
*/
|
|
102
|
+
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
103
|
+
import { z } from "zod";
|
|
104
|
+
import {
|
|
105
|
+
createCheckoutSession,
|
|
106
|
+
StripeConfigError,
|
|
107
|
+
UnknownProductCodeError,
|
|
108
|
+
} from "@working-theory/stripe-client";
|
|
109
|
+
|
|
110
|
+
// Sibling file instantiated at Step 10d — same directory as this route.
|
|
111
|
+
import { loadStripeSurfaceConfig } from "./stripe-config.js";
|
|
112
|
+
|
|
113
|
+
// ── Purchasable-product allowlist — SCAFFOLD-TIME EDIT ────────────────────────
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The ONLY lookup_keys this surface is allowed to sell.
|
|
117
|
+
*
|
|
118
|
+
* Without this allowlist the route resolves a caller-supplied `lookupKey`
|
|
119
|
+
* against the merchant's ENTIRE Stripe account — so ANY active price in it (a
|
|
120
|
+
* privately-negotiated discount, an internal comp price, a $0 test price)
|
|
121
|
+
* becomes publicly purchasable by an anonymous caller who guesses or
|
|
122
|
+
* enumerates its key. Pin the products, not the account.
|
|
123
|
+
*
|
|
124
|
+
* scaffold-commerce Step 10e-i replaces the placeholder below with the
|
|
125
|
+
* lookup_key(s) the consumer selected at step 1. Until it does, no request
|
|
126
|
+
* reaches Stripe — fail-closed.
|
|
127
|
+
*/
|
|
128
|
+
const ALLOWED_LOOKUP_KEYS: ReadonlySet<string> = new Set<string>([
|
|
129
|
+
"REPLACE_WITH_YOUR_STRIPE_LOOKUP_KEY",
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
// ── Input validation schema ───────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
const LOOKUP_KEY_REGEX = /^[A-Za-z0-9._-]{1,200}$/;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Redirects are validated in TWO stages:
|
|
138
|
+
* 1. here — shape only (non-empty, bounded length), because the site origin
|
|
139
|
+
* they must match is env-derived and so only known at request time;
|
|
140
|
+
* 2. `toSameOriginRedirect()` — the actual origin lock, applied after config
|
|
141
|
+
* load. THAT is the security-bearing check; this schema is not.
|
|
142
|
+
*/
|
|
143
|
+
const REDIRECT_MAX_LENGTH = 2048;
|
|
144
|
+
|
|
145
|
+
const OneTimeCheckoutRequestSchema = z.object({
|
|
146
|
+
lookupKey: z
|
|
147
|
+
.string()
|
|
148
|
+
.regex(LOOKUP_KEY_REGEX, "lookupKey must match /^[A-Za-z0-9._-]{1,200}$/"),
|
|
149
|
+
successUrl: z
|
|
150
|
+
.string()
|
|
151
|
+
.min(1, "successUrl is required")
|
|
152
|
+
.max(REDIRECT_MAX_LENGTH, "successUrl is too long"),
|
|
153
|
+
cancelUrl: z
|
|
154
|
+
.string()
|
|
155
|
+
.min(1, "cancelUrl is required")
|
|
156
|
+
.max(REDIRECT_MAX_LENGTH, "cancelUrl is too long"),
|
|
157
|
+
quantity: z.number().int().min(1).max(999).optional(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// ── Internal helpers (not exported — this module's only export is the handler) ─
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The site's own origin — the ONLY origin a checkout redirect may resolve to.
|
|
164
|
+
*
|
|
165
|
+
* `FRONTEND_URL` is the env var the consumer's `apps/api` already uses for its
|
|
166
|
+
* CORS origin (`apps/api/src/config/index.ts`), reused here rather than
|
|
167
|
+
* inventing a second site-origin contract. Fail-closed: unset or unparseable ⇒
|
|
168
|
+
* `config-error`, never a permissive default.
|
|
169
|
+
*/
|
|
170
|
+
function loadSiteOrigin(): URL {
|
|
171
|
+
const raw = process.env.FRONTEND_URL;
|
|
172
|
+
if (!raw) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
"FRONTEND_URL is missing. It is the site origin checkout redirects are locked to.",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
return new URL(raw);
|
|
179
|
+
} catch {
|
|
180
|
+
throw new Error("FRONTEND_URL is not a valid absolute URL.");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolve a caller-supplied redirect against the site's own origin. Returns the
|
|
186
|
+
* absolute same-origin URL, or `null` if the redirect lands ANYWHERE else.
|
|
187
|
+
*
|
|
188
|
+
* A relative path ("/checkout/success") is composed server-side onto the site
|
|
189
|
+
* origin — the recommended shape. An absolute URL is accepted only if it is
|
|
190
|
+
* exactly same-origin (scheme + host + port). Everything else — another host, a
|
|
191
|
+
* look-alike host, a non-http(s) scheme (`javascript:`, `data:`, …) — is `null`.
|
|
192
|
+
*
|
|
193
|
+
* Why this is security-bearing: without the origin lock, an anonymous caller
|
|
194
|
+
* can create a REAL Checkout Session for the REAL merchant whose `success_url`
|
|
195
|
+
* points at a site the attacker controls. The victim sees the merchant's real
|
|
196
|
+
* branding on Stripe's real domain, pays, and is redirected to the attacker —
|
|
197
|
+
* an open redirect laundered through a trusted payment page, carrying the
|
|
198
|
+
* credibility of a completed payment. `cancel_url` is the same vector without
|
|
199
|
+
* the victim even having to pay.
|
|
200
|
+
*/
|
|
201
|
+
function toSameOriginRedirect(raw: string, siteOrigin: URL): string | null {
|
|
202
|
+
let resolved: URL;
|
|
203
|
+
try {
|
|
204
|
+
resolved = new URL(raw, siteOrigin);
|
|
205
|
+
} catch {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (resolved.protocol !== "https:" && resolved.protocol !== "http:") {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
// Exact origin equality (scheme + host + port). A prefix/suffix comparison
|
|
213
|
+
// would accept `https://<site-origin>.evil.example`.
|
|
214
|
+
if (resolved.origin !== siteOrigin.origin) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return resolved.toString();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── Handler ───────────────────────────────────────────────────────────────────
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* POST handler — creates a test-mode ONE-TIME (`mode: "payment"`) Checkout
|
|
225
|
+
* Session for `lookupKey` and returns `{ sessionId, url }`. The caller (the
|
|
226
|
+
* scaffolded `CheckoutForm` block's `onCheckout`) redirects the browser to
|
|
227
|
+
* `url` on success.
|
|
228
|
+
*
|
|
229
|
+
* @public-by-design — a storefront "buy" action, intentionally
|
|
230
|
+
* UNAUTHENTICATED (see the file-header "Auth posture" note). It creates ONE
|
|
231
|
+
* allowlisted Checkout Session and reads/mutates nothing else; it is NOT a
|
|
232
|
+
* management surface. The per-handler marker (route-template-auth-gate,
|
|
233
|
+
* #3751) exempts THIS handler from the identity gate — the exemption is
|
|
234
|
+
* per-handler and reviewed, never a file-level blanket.
|
|
235
|
+
*/
|
|
236
|
+
export async function oneTimeCheckoutHandler(
|
|
237
|
+
request: FastifyRequest,
|
|
238
|
+
reply: FastifyReply,
|
|
239
|
+
): Promise<void> {
|
|
240
|
+
// ── Config: fail-closed, with an EXPLICIT cause classification ────────────
|
|
241
|
+
//
|
|
242
|
+
// The two causes are classified DELIBERATELY, never inferred from whichever
|
|
243
|
+
// Error subclass the sibling config helper happens to throw:
|
|
244
|
+
//
|
|
245
|
+
// (a) missing env → `config-error` — checked HERE, first.
|
|
246
|
+
// (b) live Stripe key → `live-mode-forbidden` — with (a) ruled out, the
|
|
247
|
+
// only thing `loadStripeSurfaceConfig()`'s `assertSandboxKey` can
|
|
248
|
+
// reject, so a StripeConfigError at this point unambiguously means a
|
|
249
|
+
// live key.
|
|
250
|
+
//
|
|
251
|
+
// Getting this backwards is not cosmetic: it makes the live-mode refusal
|
|
252
|
+
// signal dead code, and makes a mere missing-env fault cry "live mode
|
|
253
|
+
// forbidden" — training operators to ignore the one alarm that matters.
|
|
254
|
+
let siteOrigin: URL;
|
|
255
|
+
try {
|
|
256
|
+
if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_PUBLISHABLE_KEY) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
"STRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEY are missing. Run scaffold-commerce (direct payments path) first.",
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
siteOrigin = loadSiteOrigin();
|
|
262
|
+
loadStripeSurfaceConfig();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
if (err instanceof StripeConfigError) {
|
|
265
|
+
// LIVE KEY — the sandbox-only hard floor. Log loudly: this IS the alarm.
|
|
266
|
+
request.log.error(
|
|
267
|
+
{ method: request.method, path: request.url, message: err.message },
|
|
268
|
+
"[checkout] one-time: LIVE Stripe key REFUSED — live-mode-forbidden",
|
|
269
|
+
);
|
|
270
|
+
return reply.status(500).send({ error: "live-mode-forbidden" });
|
|
271
|
+
}
|
|
272
|
+
request.log.error(
|
|
273
|
+
{
|
|
274
|
+
method: request.method,
|
|
275
|
+
path: request.url,
|
|
276
|
+
message: err instanceof Error ? err.message : "unknown",
|
|
277
|
+
},
|
|
278
|
+
"[checkout] one-time: config error",
|
|
279
|
+
);
|
|
280
|
+
return reply.status(500).send({ error: "config-error" });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const parsed = OneTimeCheckoutRequestSchema.safeParse(request.body);
|
|
284
|
+
if (!parsed.success) {
|
|
285
|
+
return reply.status(400).send({
|
|
286
|
+
error: "validation-error",
|
|
287
|
+
details: parsed.error.issues,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const { lookupKey, successUrl, cancelUrl, quantity } = parsed.data;
|
|
292
|
+
|
|
293
|
+
// ── Allowlist gate — BEFORE any Stripe call ───────────────────────────────
|
|
294
|
+
// Same response shape as a genuinely-unknown price (the 404 below), so this
|
|
295
|
+
// is not an oracle telling an attacker which lookup_keys exist in the
|
|
296
|
+
// merchant's Stripe account.
|
|
297
|
+
if (!ALLOWED_LOOKUP_KEYS.has(lookupKey)) {
|
|
298
|
+
request.log.warn(
|
|
299
|
+
{ method: request.method, path: request.url, status: 404 },
|
|
300
|
+
"[checkout] one-time: lookupKey is not in this surface's allowlist; refused",
|
|
301
|
+
);
|
|
302
|
+
return reply.status(404).send({ error: "unknown-product-code" });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── Redirect origin lock — BEFORE any Stripe call ─────────────────────────
|
|
306
|
+
const safeSuccessUrl = toSameOriginRedirect(successUrl, siteOrigin);
|
|
307
|
+
const safeCancelUrl = toSameOriginRedirect(cancelUrl, siteOrigin);
|
|
308
|
+
if (safeSuccessUrl === null || safeCancelUrl === null) {
|
|
309
|
+
// Do NOT echo the rejected URL back to the caller.
|
|
310
|
+
request.log.warn(
|
|
311
|
+
{ method: request.method, path: request.url, status: 400 },
|
|
312
|
+
"[checkout] one-time: redirect URL is not same-origin; refused",
|
|
313
|
+
);
|
|
314
|
+
return reply.status(400).send({ error: "invalid-redirect-url" });
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
// Pinned mode: "payment" — this endpoint can never open a subscription
|
|
319
|
+
// session, regardless of caller input (spec § 3 design decision).
|
|
320
|
+
const { sessionId, url } = await createCheckoutSession({
|
|
321
|
+
productCode: lookupKey,
|
|
322
|
+
successUrl: safeSuccessUrl,
|
|
323
|
+
cancelUrl: safeCancelUrl,
|
|
324
|
+
mode: "payment",
|
|
325
|
+
quantity: quantity ?? 1,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
request.log.info(
|
|
329
|
+
{
|
|
330
|
+
method: request.method,
|
|
331
|
+
path: request.url,
|
|
332
|
+
sessionIdPrefix: sessionId.substring(0, 8),
|
|
333
|
+
status: 201,
|
|
334
|
+
},
|
|
335
|
+
"[checkout] one-time: session created",
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
return reply.status(201).send({ sessionId, url });
|
|
339
|
+
} catch (err) {
|
|
340
|
+
if (err instanceof UnknownProductCodeError) {
|
|
341
|
+
// 404 — do NOT echo lookupKey in the response body.
|
|
342
|
+
return reply.status(404).send({ error: "unknown-product-code" });
|
|
343
|
+
}
|
|
344
|
+
if (err instanceof StripeConfigError) {
|
|
345
|
+
// Config was already validated above, so this is a late/racing config
|
|
346
|
+
// fault raised inside `getStripeClient()` (which throws StripeConfigError
|
|
347
|
+
// for BOTH a missing and a live key — hence the generic mapping here; the
|
|
348
|
+
// authoritative live-key classification is the pre-flight block above).
|
|
349
|
+
request.log.error(
|
|
350
|
+
{
|
|
351
|
+
method: request.method,
|
|
352
|
+
path: request.url,
|
|
353
|
+
errName: err.name,
|
|
354
|
+
message: err.message,
|
|
355
|
+
},
|
|
356
|
+
"[checkout] one-time: config error",
|
|
357
|
+
);
|
|
358
|
+
return reply.status(500).send({ error: "config-error" });
|
|
359
|
+
}
|
|
360
|
+
request.log.error(
|
|
361
|
+
{
|
|
362
|
+
method: request.method,
|
|
363
|
+
path: request.url,
|
|
364
|
+
errName: err instanceof Error ? err.name : "unknown",
|
|
365
|
+
},
|
|
366
|
+
"[checkout] one-time: stripe error; checkout-unavailable",
|
|
367
|
+
);
|
|
368
|
+
return reply.status(502).send({ error: "checkout-unavailable" });
|
|
369
|
+
}
|
|
370
|
+
}
|