create-ailk 0.22.0 → 0.23.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/component-catalog.json +149 -5
- package/component-catalog.md +7 -6
- package/dist/add-module.js +11 -9
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +294 -12
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +7 -5
- package/dist/derive-architecture.js +9 -32
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +0 -4
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +3 -2
- package/dist/lib/scaffold-render-sweep.js +5 -8
- package/dist/module-architecture.js +98 -10
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/spoke-checkout.d.ts +38 -35
- package/dist/spoke-checkout.js +66 -229
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +33 -12
- package/dist/sync-routes.d.ts +38 -1
- package/dist/sync-routes.js +184 -11
- package/dist/template-record.d.ts +1 -1
- package/dist/template-record.js +3 -2
- package/dist/update.js +10 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +9 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +27 -0
- package/templates/apps/api/CLAUDE.md +77 -16
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/stripe.ts +74 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/spec.ts +10 -7
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +4 -2
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/index.ts +17 -14
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +91 -17
- package/templates/apps/api/src/server.ts +140 -51
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
- package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +113 -58
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +124 -9
- package/templates/apps/api/src/services/plan-offerings.ts +96 -30
- package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
- package/templates/apps/mcp/CLAUDE.md +22 -14
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +10 -31
- package/templates/apps/web/CLAUDE.md +315 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +6 -2
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +9 -20
- package/templates/apps/web/lib/deck-page.ts +1 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +42 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +202 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
- package/templates/apps/web/messages/en.json +49 -15
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +31 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +31 -1
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +30 -2
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
- package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/checkout.ts +0 -93
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -57,9 +57,10 @@
|
|
|
57
57
|
* file leaves nothing to find, and its retry writes again. `subscribe` and
|
|
58
58
|
* `callback` never read the header. The key is never logged or echoed.
|
|
59
59
|
*
|
|
60
|
-
* CONTACT TERMS ACCEPTANCE (#8730). A `submit` whose `source` is a
|
|
61
|
-
* source (`
|
|
62
|
-
* `contact-form`, `conversational-intake`, `flow:contact
|
|
60
|
+
* CONTACT TERMS ACCEPTANCE (#8730). A `submit` whose `source` is a
|
|
61
|
+
* consent-bearing source (`isConsentLeadSource` in @working-theory/validation:
|
|
62
|
+
* the contact sources `contact-form`, `conversational-intake`, `flow:contact`,
|
|
63
|
+
* and since #9130 the Free apply page's `flow:job-application`) must carry a
|
|
63
64
|
* top-level `consent: true` — the boolean, nothing else — or it is
|
|
64
65
|
* `400 consent_required` before the tenant read or any write. An accepted one
|
|
65
66
|
* gets `payload.consent = { statement, acceptedAt }` written HERE, replacing
|
|
@@ -110,7 +111,7 @@
|
|
|
110
111
|
import { track } from "@working-theory/analytics/server";
|
|
111
112
|
import {
|
|
112
113
|
CONTACT_FORM_TERMS_STATEMENT,
|
|
113
|
-
|
|
114
|
+
isConsentLeadSource,
|
|
114
115
|
type LeadSubmitNext,
|
|
115
116
|
} from "@working-theory/validation";
|
|
116
117
|
import type { FastifyPluginAsync } from "fastify";
|
|
@@ -156,17 +157,18 @@ function liftString(
|
|
|
156
157
|
return (typeof fromPayload === "string" ? fromPayload : topLevel) as string;
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
/** #8730 — the refusal a
|
|
160
|
+
/** #8730, #9130 — the refusal a consent-bearing lead without terms acceptance gets. */
|
|
160
161
|
const CONSENT_REQUIRED_BODY = {
|
|
161
162
|
error: "consent_required",
|
|
162
|
-
message: "Accept the terms to send
|
|
163
|
+
message: "Accept the terms to send this form.",
|
|
163
164
|
} as const;
|
|
164
165
|
|
|
165
166
|
/**
|
|
166
|
-
* #8730 D3 — the accepted terms, recorded by the server on a
|
|
167
|
-
* payload, replacing any `consent` the client wrote
|
|
168
|
-
* object payload is stamped: anything else is left for
|
|
169
|
-
* to refuse, never spread into an object that would then
|
|
167
|
+
* #8730 D3, #9130 — the accepted terms, recorded by the server on a
|
|
168
|
+
* consent-bearing lead's payload, replacing any `consent` the client wrote
|
|
169
|
+
* there. Only a plain object payload is stamped: anything else is left for
|
|
170
|
+
* `routeLead`'s schema to refuse, never spread into an object that would then
|
|
171
|
+
* pass it.
|
|
170
172
|
*/
|
|
171
173
|
function withConsentRecord(payload: unknown, acceptedAt: string): unknown {
|
|
172
174
|
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
@@ -205,7 +207,7 @@ function buildInput(
|
|
|
205
207
|
siteId: workspaceId,
|
|
206
208
|
source: body["source"] as string,
|
|
207
209
|
email: liftString(body, payload, "email"),
|
|
208
|
-
payload:
|
|
210
|
+
payload: isConsentLeadSource(body["source"])
|
|
209
211
|
? (withConsentRecord(payload, receivedAt) as Record<string, unknown>)
|
|
210
212
|
: payload,
|
|
211
213
|
};
|
|
@@ -246,11 +248,12 @@ function makeLeadsRoute(kind: LeadKind): FastifyPluginAsync {
|
|
|
246
248
|
fastify.post(`/v1/leads/${kind}`, async (request, reply) => {
|
|
247
249
|
const body = request.body as Record<string, unknown>;
|
|
248
250
|
|
|
249
|
-
// #8730 D1 — a contact lead
|
|
250
|
-
// it is refused before the tenant read
|
|
251
|
+
// #8730 D1, #9130 — a contact lead or a job application carries the
|
|
252
|
+
// visitor's terms acceptance, or it is refused before the tenant read
|
|
253
|
+
// and any write.
|
|
251
254
|
if (
|
|
252
255
|
kind === "submit" &&
|
|
253
|
-
|
|
256
|
+
isConsentLeadSource(body?.["source"]) &&
|
|
254
257
|
body?.["consent"] !== true
|
|
255
258
|
) {
|
|
256
259
|
return reply.status(400).send(CONSENT_REQUIRED_BODY);
|
|
@@ -199,6 +199,72 @@ through `metadata.site`.
|
|
|
199
199
|
site, which silences the hub's dunning record for that subscription — the
|
|
200
200
|
same key can already cancel or re-price it outright.
|
|
201
201
|
|
|
202
|
+
## Per-Organization endpoint (#8932)
|
|
203
|
+
|
|
204
|
+
`POST /v1/webhooks/stripe/:connectionToken` (`stripe-offerings.ts`) is the
|
|
205
|
+
receiver for one Organization's own Stripe connection — the Offerings money
|
|
206
|
+
path (spec `docs/specs/8932-the-per-organization-stripe-webhook-spec.md`,
|
|
207
|
+
contract `docs/specs/8879-the-offerings-security-contract-key-spec.md` W1,
|
|
208
|
+
W3–W11, G5, T8). The 43-character token selects one `StripeConnection` row
|
|
209
|
+
(`findStripeConnectionByWebhookToken`, #8907); that row's decrypted secret
|
|
210
|
+
verifies the signature through `verifyWebhookSignatureKeyless` (no env key),
|
|
211
|
+
and its `organizationId` is the only Organization the event can act for.
|
|
212
|
+
Registered beside the legacy route on every build, with its own 100/min
|
|
213
|
+
per-IP bucket; the commerce claim is read at call time.
|
|
214
|
+
|
|
215
|
+
**The ladder** — the first step that answers ends the request; every `200`
|
|
216
|
+
body is `{ received: true }` and the outcome is the log line's only:
|
|
217
|
+
|
|
218
|
+
| # | Step | Answer | Seen |
|
|
219
|
+
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------- |
|
|
220
|
+
| 1 | the token resolves to no row (or is malformed) | `404 not-found`, logs nothing | no |
|
|
221
|
+
| 1a | the token lookup rejects | `503 webhook-unavailable` | no |
|
|
222
|
+
| 2 | the row has no usable secret | `503 webhook-not-configured` | no |
|
|
223
|
+
| 3–6 | signature settled, then the claim (as the legacy route): `undetermined` → `503` + `Retry-After: 300`; `unclaimed` → signed `200 dropped_no_commerce_claim`, unsigned the module-disabled `404`; then the held `400` | as stated | no |
|
|
224
|
+
| 7 | `event.livemode` is not the row's mode | `200 livemode_mismatch` | no |
|
|
225
|
+
| 8 | not `checkout.session.completed` / `.async_payment_succeeded` on a session object | `200 ignored_type` | no |
|
|
226
|
+
| 9–11 | the hub's session record (#8930): rejects / unreadable / none | `503 record-unavailable` / `500 record-unreadable` / `200 unrecorded` | no |
|
|
227
|
+
| 12 | another Organization's record, or `metadata.site` is not the record's workspace | `200 foreign_organization` | no |
|
|
228
|
+
| 13 | the record's mode is not the row's | `200 livemode_mismatch` | no |
|
|
229
|
+
| 14 | already seen for this Organization | `200 duplicate` | — |
|
|
230
|
+
| 15 | a `completed` session neither `paid` nor `no_payment_required` | `200 unpaid` | yes |
|
|
231
|
+
| 16 | the connection's client or its `listLineItems` call fails | `503 stripe-unavailable` (never the env key) | no |
|
|
232
|
+
| 17 | not exactly one line item whose Price id is the record's | `200 offering_mismatch` | yes |
|
|
233
|
+
| 18 | `routeLead` (below) | `200 fulfilled` / `already_fulfilled` / `unfulfillable`, else `503 fulfilment-unavailable` | yes / no |
|
|
234
|
+
|
|
235
|
+
**The lead.** One per session: `routeLead` with the idempotency key
|
|
236
|
+
`"stripe-offerings " + sessionId` — a space, which no `Idempotency-Key`
|
|
237
|
+
header can carry, so no anonymous lead can take the key first — and
|
|
238
|
+
`offeringsCheckout: true`. Its offering is the record's FULL lookup key,
|
|
239
|
+
never `metadata.ailk_offering`. A live session's lead is marked
|
|
240
|
+
`spokeCheckout`, the hold #8793's `releaseHeldCheckoutPlan` releases at the
|
|
241
|
+
buyer's verified first link; no path here writes `Person.planOffering`. A
|
|
242
|
+
test-mode session's lead is written unmarked: no hold, purchase message,
|
|
243
|
+
email or consent grant. A replay or a conflict on that key is
|
|
244
|
+
`already_fulfilled`; a lead the database did not take is a `503`, so Stripe
|
|
245
|
+
retries.
|
|
246
|
+
|
|
247
|
+
**The legacy endpoint steps aside (W8).** `POST /v1/webhooks/stripe` acks
|
|
248
|
+
any event carrying an `ailk_offering` key — in a session's metadata or any
|
|
249
|
+
site-stamp source above — with `200 { received: true }` and
|
|
250
|
+
`outcome: "offerings_path"`, **before** `markSeen`, and writes nothing.
|
|
251
|
+
Every event without the key takes the legacy path unchanged.
|
|
252
|
+
|
|
253
|
+
**Two dedup stores.** The legacy route keeps `hasSeen` / `markSeen`; the
|
|
254
|
+
per-Organization route uses its own `hasSeenForOrganization` /
|
|
255
|
+
`markSeenForOrganization`, keyed `<organizationId>:<eventId>`. Neither
|
|
256
|
+
endpoint's store can swallow the other's event. Both are in-memory per
|
|
257
|
+
instance; the per-Organization route's correctness rests on the lead's
|
|
258
|
+
database key.
|
|
259
|
+
|
|
260
|
+
**Logs.** Each line carries `organizationId`, `eventType`, an 8-character
|
|
261
|
+
`eventIdPrefix` and `outcome` (fewer before an event is verified) — never
|
|
262
|
+
the token, the secret, a full Stripe id, an email, a lookup key, a metadata
|
|
263
|
+
value or Stripe product text. Request and error lines log the path with
|
|
264
|
+
the token replaced whole by `:connectionToken` (`redactLogPath`).
|
|
265
|
+
|
|
266
|
+
**W11.** No billing identity is bound from a connection's event.
|
|
267
|
+
|
|
202
268
|
## Dunning (#6034)
|
|
203
269
|
|
|
204
270
|
Two arms record a per-subscriber lifecycle state so a failed renewal is no
|