create-ailk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/component-catalog.md +50 -53
  2. package/dist/lib/apply-module-patches.d.ts +47 -0
  3. package/dist/lib/apply-module-patches.js +370 -0
  4. package/dist/lib/extract-module-bundle.d.ts +12 -3
  5. package/dist/lib/extract-module-bundle.js +236 -24
  6. package/dist/lib/fetch-module.d.ts +3 -38
  7. package/dist/lib/fetch-module.js +26 -8
  8. package/dist/lib/module-license-gate.d.ts +2 -0
  9. package/dist/lib/module-license-gate.js +1 -0
  10. package/dist/lib/paid-paths.d.ts +16 -0
  11. package/dist/lib/paid-paths.js +90 -0
  12. package/dist/module-architecture.d.ts +139 -23
  13. package/dist/module-architecture.js +668 -25
  14. package/dist/parse-args.d.ts +2 -2
  15. package/dist/parse-args.js +3 -1
  16. package/dist/programmatic.d.ts +2 -1
  17. package/dist/programmatic.js +15 -4
  18. package/dist/surfaces.d.ts +12 -36
  19. package/dist/surfaces.js +35 -5
  20. package/dist/sync-routes.js +31 -1
  21. package/package.json +2 -2
  22. package/templates/.claude/agents/web.md +2 -3
  23. package/templates/.claude/rules/architecture.md +5 -5
  24. package/templates/.claude/skills/README.md +2 -1
  25. package/templates/.env.example +21 -9
  26. package/templates/CONVENTIONS.md +6 -7
  27. package/templates/apps/api/.env.example +9 -0
  28. package/templates/apps/api/CLAUDE.md +33 -5
  29. package/templates/apps/api/package.json +0 -1
  30. package/templates/apps/api/src/__tests__/cors.test.ts +195 -0
  31. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +60 -2
  32. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +159 -0
  33. package/templates/apps/api/src/config/__tests__/modules.test.ts +76 -0
  34. package/templates/apps/api/src/config/index.ts +19 -0
  35. package/templates/apps/api/src/config/modules.ts +12 -14
  36. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  37. package/templates/apps/api/src/lib/__tests__/slice-load.test.ts +89 -0
  38. package/templates/apps/api/src/lib/slice-load.ts +90 -0
  39. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +52 -32
  40. package/templates/apps/api/src/openapi/__tests__/spec-identity.test.ts +182 -0
  41. package/templates/apps/api/src/openapi/__tests__/surface-drift.test.ts +293 -0
  42. package/templates/apps/api/src/openapi/index.ts +9 -3
  43. package/templates/apps/api/src/openapi/spec.ts +505 -65
  44. package/templates/apps/api/src/openapi/surface-drift.ts +195 -0
  45. package/templates/apps/api/src/routes/content/__tests__/create.test.ts +14 -0
  46. package/templates/apps/api/src/routes/content/__tests__/delete.test.ts +14 -0
  47. package/templates/apps/api/src/routes/content/__tests__/update.test.ts +14 -0
  48. package/templates/apps/api/src/routes/content/index.ts +38 -11
  49. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +17 -0
  50. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +17 -0
  51. package/templates/apps/api/src/routes/project-listings/__tests__/founder-identity.test.ts +796 -0
  52. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +10 -0
  53. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +38 -0
  54. package/templates/apps/api/src/routes/project-listings/__tests__/resume-email.test.ts +814 -0
  55. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +10 -0
  56. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +24 -0
  57. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +17 -0
  58. package/templates/apps/api/src/routes/project-listings/index.ts +37 -10
  59. package/templates/apps/api/src/routes/project-listings/me.ts +10 -0
  60. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +5 -0
  61. package/templates/apps/api/src/routes/project-listings/public.ts +5 -0
  62. package/templates/apps/api/src/routes/project-listings/respond.ts +11 -0
  63. package/templates/apps/api/src/routes/project-listings/resume-token.ts +5 -0
  64. package/templates/apps/api/src/routes/project-listings/start.ts +30 -0
  65. package/templates/apps/api/src/routes/project-listings/submit.ts +5 -0
  66. package/templates/apps/api/src/routes/project-listings/verify-email.ts +150 -0
  67. package/templates/apps/api/src/server.ts +459 -135
  68. package/templates/apps/api/src/services/deliverable-fulfillment.ts +10 -0
  69. package/templates/apps/api/src/services/listing-config.ts +58 -0
  70. package/templates/apps/api/src/services/project-listing-resume-email.ts +326 -0
  71. package/templates/apps/api/src/services/project-listings.ts +229 -4
  72. package/templates/apps/api/src/vercel-handler.ts +60 -26
  73. package/templates/apps/mcp/.env.example +8 -0
  74. package/templates/apps/mcp/CLAUDE.md +2 -2
  75. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +21 -0
  76. package/templates/apps/mcp/__tests__/config/modules.test.ts +78 -0
  77. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +1 -1
  78. package/templates/apps/mcp/src/config/modules.ts +14 -13
  79. package/templates/apps/mcp/src/server.ts +8 -4
  80. package/templates/apps/mcp/src/tools/index.ts +7 -22
  81. package/templates/apps/web/.env.example +15 -0
  82. package/templates/apps/web/app/[locale]/(authed)/{waitlist/__tests__ → __tests__}/gate.test.tsx +4 -4
  83. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +2 -1
  84. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +2 -1
  85. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +2 -1
  86. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +1 -0
  87. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +1 -0
  88. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +2 -1
  89. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +1 -0
  90. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +1 -0
  91. package/templates/apps/web/app/llms.txt/route.ts +12 -9
  92. package/templates/apps/web/jest.config.cjs +14 -13
  93. package/templates/apps/web/next.config.mjs +1 -3
  94. package/templates/apps/web/package.json +0 -6
  95. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  96. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  97. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  98. package/templates/apps/web/public/favicon-16x16.png +0 -0
  99. package/templates/apps/web/public/favicon-32x32.png +0 -0
  100. package/templates/apps/web/public/favicon.ico +0 -0
  101. package/templates/apps/web/public/favicon.svg +6 -3
  102. package/templates/apps/web/public/lockup-horizontal.svg +4 -0
  103. package/templates/apps/web/public/logomark.svg +4 -0
  104. package/templates/apps/web/public/site.webmanifest +2 -2
  105. package/templates/apps/web/public/wordmark.svg +4 -0
  106. package/templates/database/CHANGELOG.md +33 -0
  107. package/templates/database/inbox/schema.prisma +67 -0
  108. package/templates/database/migrations/20260914120000_listing_founder_identity/migration.sql +123 -0
  109. package/templates/database/package.json +1 -1
  110. package/templates/package.json +1 -1
  111. package/templates/.claude/skills/scaffold-commerce/SKILL.md +0 -807
  112. package/templates/.claude/skills/scaffold-commerce/references/lookup-keys-template.md +0 -71
  113. package/templates/.claude/skills/scaffold-commerce/references/marketplace-brand-pages.md +0 -83
  114. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +0 -370
  115. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +0 -376
  116. package/templates/.claude/skills/scaffold-commerce/templates/marketplace-cta-route.tsx.tmpl +0 -72
  117. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +0 -471
  118. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +0 -365
  119. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +0 -60
  120. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +0 -446
  121. package/templates/apps/api/src/__tests__/server.test.ts +0 -253
  122. package/templates/apps/api/src/bin/deliverable-resend.ts +0 -144
  123. package/templates/apps/api/src/bin/deliverable-upload.ts +0 -114
  124. package/templates/apps/api/src/bin/followup-sweep.ts +0 -44
  125. package/templates/apps/api/src/bin/listing-csv-sweep.ts +0 -46
  126. package/templates/apps/api/src/bin/seed-presets.ts +0 -58
  127. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +0 -104
  128. package/templates/apps/api/src/bin/session-retention-sweep.ts +0 -46
  129. package/templates/apps/api/src/lib/__tests__/stripe.test.ts +0 -74
  130. package/templates/apps/api/src/lib/stripe.ts +0 -52
  131. package/templates/apps/api/src/lib/tenant-db.ts +0 -225
  132. package/templates/apps/api/src/lib/ws-token.ts +0 -91
  133. package/templates/apps/api/src/middleware/tenant.ts +0 -99
  134. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +0 -450
  135. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +0 -262
  136. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +0 -436
  137. package/templates/apps/api/src/routes/billing/index.ts +0 -36
  138. package/templates/apps/api/src/routes/billing/portal.ts +0 -182
  139. package/templates/apps/api/src/routes/billing/read.ts +0 -75
  140. package/templates/apps/api/src/routes/billing/usage.ts +0 -153
  141. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +0 -687
  142. package/templates/apps/api/src/routes/checkout/index.ts +0 -13
  143. package/templates/apps/api/src/routes/checkout/sessions.ts +0 -197
  144. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +0 -393
  145. package/templates/apps/api/src/routes/deliverables/index.ts +0 -200
  146. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +0 -443
  147. package/templates/apps/api/src/routes/flow-checkouts/index.ts +0 -82
  148. package/templates/apps/api/src/routes/flows/README.md +0 -147
  149. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +0 -752
  150. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +0 -671
  151. package/templates/apps/api/src/routes/flows/index.ts +0 -202
  152. package/templates/apps/api/src/routes/flows/recommender.ts +0 -443
  153. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +0 -815
  154. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +0 -221
  155. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +0 -566
  156. package/templates/apps/api/src/routes/project-listings/get.ts +0 -70
  157. package/templates/apps/api/src/routes/project-listings/list.ts +0 -47
  158. package/templates/apps/api/src/routes/project-listings/patch.ts +0 -136
  159. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +0 -490
  160. package/templates/apps/api/src/routes/schedule/index.ts +0 -249
  161. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +0 -385
  162. package/templates/apps/api/src/routes/slack/actions.ts +0 -177
  163. package/templates/apps/api/src/routes/slack/index.ts +0 -38
  164. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +0 -577
  165. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +0 -78
  166. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +0 -41
  167. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +0 -64
  168. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +0 -42
  169. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +0 -84
  170. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +0 -119
  171. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +0 -238
  172. package/templates/apps/api/src/routes/waitlist-signups/index.ts +0 -101
  173. package/templates/apps/api/src/routes/webhooks/README.md +0 -80
  174. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +0 -315
  175. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-idempotency.test.ts +0 -247
  176. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +0 -270
  177. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-rate-limit.test.ts +0 -198
  178. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +0 -633
  179. package/templates/apps/api/src/routes/webhooks/index.ts +0 -41
  180. package/templates/apps/api/src/routes/webhooks/stripe.ts +0 -481
  181. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +0 -356
  182. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +0 -671
  183. package/templates/apps/api/src/routes/workspaces/config.ts +0 -125
  184. package/templates/apps/api/src/routes/workspaces/index.ts +0 -194
  185. package/templates/apps/api/src/routes/workspaces/sessions.ts +0 -218
  186. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +0 -425
  187. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +0 -2840
  188. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +0 -393
  189. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +0 -560
  190. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +0 -684
  191. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +0 -406
  192. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +0 -290
  193. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +0 -736
  194. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +0 -475
  195. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +0 -340
  196. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +0 -983
  197. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +0 -71
  198. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +0 -379
  199. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +0 -768
  200. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +0 -1314
  201. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +0 -341
  202. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +0 -567
  203. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +0 -474
  204. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +0 -354
  205. package/templates/apps/api/src/services/flow-aggregate.ts +0 -232
  206. package/templates/apps/api/src/services/flow-checkouts.ts +0 -123
  207. package/templates/apps/api/src/services/flow-engine.ts +0 -1296
  208. package/templates/apps/api/src/services/lead-promotion.ts +0 -176
  209. package/templates/apps/api/src/services/listing-csv-sweep.ts +0 -455
  210. package/templates/apps/api/src/services/listing-promotion.ts +0 -342
  211. package/templates/apps/api/src/services/playbook-compile.ts +0 -398
  212. package/templates/apps/api/src/services/playbook-render.ts +0 -263
  213. package/templates/apps/api/src/services/project-listing-decision.ts +0 -510
  214. package/templates/apps/api/src/services/project-listing-flow.ts +0 -426
  215. package/templates/apps/api/src/services/project-listing-issue.ts +0 -251
  216. package/templates/apps/api/src/services/recommender-capture.ts +0 -835
  217. package/templates/apps/api/src/services/scheduling/cal-provider.ts +0 -392
  218. package/templates/apps/api/src/services/scheduling/index.ts +0 -63
  219. package/templates/apps/api/src/services/scheduling/types.ts +0 -88
  220. package/templates/apps/api/src/services/tenant-context.ts +0 -451
  221. package/templates/apps/api/src/services/usage-metering.ts +0 -699
  222. package/templates/apps/api/src/services/waitlist-dashboard.ts +0 -947
  223. package/templates/apps/api/src/services/waitlist-experiments.ts +0 -213
  224. package/templates/apps/api/src/services/waitlist-followup.ts +0 -486
  225. package/templates/apps/api/src/services/waitlist-scoring.ts +0 -166
  226. package/templates/apps/api/src/services/waitlist-signups.ts +0 -165
  227. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +0 -242
  228. package/templates/apps/mcp/src/tools/create_booking.ts +0 -58
  229. package/templates/apps/mcp/src/tools/get_event_meta.ts +0 -55
  230. package/templates/apps/mcp/src/tools/get_flow.ts +0 -61
  231. package/templates/apps/mcp/src/tools/list_availability.ts +0 -67
  232. package/templates/apps/mcp/src/tools/submit_flow_step.ts +0 -83
  233. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +0 -309
  234. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +0 -149
  235. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +0 -273
  236. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +0 -171
  237. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +0 -179
  238. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +0 -116
  239. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +0 -66
  240. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +0 -61
  241. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +0 -114
  242. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +0 -139
  243. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +0 -85
  244. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +0 -67
  245. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +0 -179
  246. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +0 -178
  247. package/templates/apps/web/app/[locale]/dev/purchase/__tests__/actions.test.ts +0 -160
  248. package/templates/apps/web/app/[locale]/dev/purchase/error.tsx +0 -25
  249. package/templates/apps/web/app/[locale]/dev/purchase/page.tsx +0 -117
  250. package/templates/apps/web/app/[locale]/dev/purchase/purchase.actions.ts +0 -58
  251. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +0 -119
  252. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +0 -287
  253. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +0 -254
  254. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +0 -532
  255. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +0 -121
  256. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +0 -457
  257. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +0 -396
  258. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +0 -114
  259. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +0 -110
  260. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +0 -96
  261. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +0 -117
  262. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +0 -96
  263. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +0 -92
  264. package/templates/apps/web/app/[locale]/schedule/page.tsx +0 -82
  265. package/templates/apps/web/content/en/blog/README.txt +0 -25
  266. package/templates/apps/web/content/en/docs/index.mdx +0 -28
  267. package/templates/apps/web/lib/__mocks__/source-server.js +0 -31
  268. package/templates/apps/web/lib/blog-list-data.ts +0 -95
  269. package/templates/apps/web/lib/blog-post-data.ts +0 -79
  270. package/templates/apps/web/lib/source.ts +0 -34
  271. package/templates/apps/web/public/logomark-dark.svg +0 -4
  272. package/templates/apps/web/public/logomark-light.svg +0 -4
  273. package/templates/apps/web/source.config.ts +0 -78
@@ -1,71 +0,0 @@
1
- # Lookup Keys — generated by `scaffold-commerce`
2
-
3
- This file lists every Stripe product the `scaffold-commerce` skill
4
- created in your test-mode account. The skill writes one row per
5
- product as it goes; re-running the skill appends new rows under
6
- "## Added by re-run YYYY-MM-DD".
7
-
8
- ## How to use this file
9
-
10
- Stripe `lookup_key`s are stable identifiers — they survive price
11
- recreates (e.g., when you change an amount, you can keep the same
12
- `lookup_key`). They're the canonical reference for AILK pricing UI.
13
-
14
- When wiring a pricing page (e.g., from `scaffold-page` (#362) on a
15
- `Product` slug), refer to `lookup_key` in your code:
16
-
17
- ```ts
18
- // apps/web/app/pricing/page.tsx
19
- const PRICING_LOOKUP_KEYS = ["pro_monthly", "team_monthly"];
20
-
21
- // Server-side fetch (apps/api):
22
- const prices = await stripe.prices.list({
23
- lookup_keys: PRICING_LOOKUP_KEYS,
24
- expand: ["data.product"],
25
- });
26
- ```
27
-
28
- The pricing page renders from `prices`. If you change a price's
29
- amount in the Stripe dashboard later, the `lookup_key` resolves to
30
- the new price automatically — no app-code change needed.
31
-
32
- ## Products created this session
33
-
34
- (The skill fills this table during Step 8 of the Stripe path. Empty
35
- on first emit; populated as products are created.)
36
-
37
- | Product | Lookup key | Amount | Interval | Stripe ID |
38
- | ---------- | ---------- | ------ | -------- | --------- |
39
- | _none yet_ | _ -_ | _-_ | _-_ | _-_ |
40
-
41
- Example after a session that created two products:
42
-
43
- | Product | Lookup key | Amount | Interval | Stripe ID |
44
- | --------- | -------------- | ---------- | -------- | ---------------- |
45
- | Pro plan | `pro_monthly` | $29.00 USD | monthly | `price_1XXXX...` |
46
- | Team plan | `team_monthly` | $99.00 USD | monthly | `price_1YYYY...` |
47
-
48
- ## Re-running the skill
49
-
50
- If you re-run `scaffold-commerce` and add more products:
51
-
52
- - Existing rows are not modified.
53
- - New products land under `## Added by re-run <DATE>` heading at the
54
- end of the file.
55
- - Conflict-handling (skill Step 4) means existing `lookup_key`s are
56
- never silently changed — you'll be asked to keep / overwrite /
57
- pick a different key.
58
-
59
- ## Pointers
60
-
61
- - **Pricing page UI:** `scaffold-page` (#362) on a `Product` slug.
62
- Wire the `PRICING_LOOKUP_KEYS` array to point at the keys above.
63
- - **Production webhook:** `provision-config` (#367). Dev-mode
64
- webhook is set up via `stripe listen` per skill Step 7.
65
- - **Sandbox-only at v1:** AILK rejects live keys (`sk_live_*`,
66
- `pk_live_*`) at multiple layers. Production setup is a future
67
- deploy lane.
68
- - **Stripe dashboard:**
69
- - Test products: <https://dashboard.stripe.com/test/products>
70
- - Test prices: <https://dashboard.stripe.com/test/prices>
71
- - Test API keys: <https://dashboard.stripe.com/test/apikeys>
@@ -1,83 +0,0 @@
1
- # Marketplace Brand Pages
2
-
3
- AILK does **not** bundle marketplace logo SVGs (Amazon, Etsy, Shopify,
4
- eBay, Gumroad). Trademarks are not blanket-licensed for redistribution
5
- through third-party templates, and shipping them in the AILK template
6
- chain would put us in the redistribution chain.
7
-
8
- Brand recognition value is preserved via the **brand name in the
9
- button text** ("Buy on Amazon", "View on Etsy"). Consumers who want
10
- the logo too can fetch it themselves from each marketplace's official
11
- brand-kit page (links below) and drop it in their own `public/logos/`
12
- directory, then pass `iconSrc="/logos/amazon.svg"` to
13
- `<MarketplaceCTA>`.
14
-
15
- Each link below points at the marketplace's first-party brand-asset
16
- page. URLs may change; if a link breaks, search "<marketplace> brand
17
- guidelines" or "<marketplace> press kit" — the canonical source is
18
- always the marketplace's own brand site.
19
-
20
- ## Amazon
21
-
22
- - **Brand assets:** <https://brand.amazon.com> (US Brand Standards)
23
- - **Press / logo:** <https://www.aboutamazon.com/news/company-news/press-room>
24
- - Notes: Amazon's brand guidelines restrict use of the smile-arrow
25
- logo. The "Available at Amazon" badge is the consumer-permitted
26
- affiliate-style asset.
27
-
28
- ## Etsy
29
-
30
- - **Press kit:** <https://www.etsy.com/press>
31
- - **Logo + brand guidelines:** linked from the press page.
32
- - Notes: "Sell on Etsy" assets are for sellers; "Available on Etsy"
33
- / "Buy on Etsy" buttons are appropriate for outbound links.
34
-
35
- ## Shopify
36
-
37
- - **Brand assets:** <https://www.shopify.com/news/brand-assets> (or
38
- search "Shopify brand guidelines")
39
- - Notes: Shopify provides logo files for partners; consumers using
40
- Shopify as their backend can typically use the logo in
41
- reasonable-context "Powered by Shopify" / "Buy on our Shopify
42
- store" buttons.
43
-
44
- ## eBay
45
-
46
- - **Brand center:** <https://partnernetwork.ebay.com/marketing-center>
47
- (eBay Partner Network — affiliate context)
48
- - **Press / logo:** <https://www.ebayinc.com/company/press-room>
49
- - Notes: eBay distinguishes affiliate-context use (covered by EPN
50
- assets) from general use. For "View on eBay" buttons linking to a
51
- specific seller's storefront, the EPN assets apply.
52
-
53
- ## Gumroad
54
-
55
- - **Brand assets:** <https://gumroad.com> (search the footer for
56
- "press" or "brand"; Gumroad's logo policy is permissive for
57
- outbound links to creator pages)
58
- - Notes: simplest of the five; Gumroad's logo and "Available on
59
- Gumroad" badge are typically copyable directly from their
60
- about/press surface.
61
-
62
- ## Custom storefronts
63
-
64
- If the consumer has their own storefront (Webflow, Squarespace,
65
- Bigcartel, custom-built), they don't need a marketplace logo — a
66
- plain "Buy now" or "Shop" button works. The `iconSrc` prop is
67
- optional.
68
-
69
- ## Legal note (no legal advice)
70
-
71
- - Trademarks are owned by the marketplaces. AILK does not grant
72
- permission to use any marketplace logo; the consumer's use is
73
- governed by each marketplace's brand guidelines.
74
- - For affiliate context (consumer earns a referral fee), most
75
- marketplaces have explicit affiliate-asset programs (Amazon
76
- Associates, eBay Partner Network, etc.) with permitted logo use.
77
- - For non-affiliate "I have a store there, send people to it"
78
- context, the marketplace's general brand guidelines apply. When
79
- in doubt, the brand name in text only — `Buy on {marketplace}`
80
- — is always safe.
81
-
82
- This file is informational. Consumers responsible for compliance
83
- with each marketplace's brand guidelines for their specific use.
@@ -1,370 +0,0 @@
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
- }