@odla-ai/chapter 0.47.7 → 0.47.9
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/dist/index.cjs +24 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -1
- package/dist/testing.cjs +102 -0
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +56 -1
- package/dist/testing.d.ts +56 -1
- package/dist/testing.js +102 -0
- package/dist/testing.js.map +1 -1
- package/dist/worker/index.cjs +144 -32
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +144 -32
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing-probe.ts","../src/testing.ts"],"sourcesContent":["import type {\n ChapterAcceptanceManifest,\n ChapterAcceptanceTarget,\n ChapterJoinConfigProbe,\n ChapterJoinContractFinding,\n ChapterJoinContractProbeResult,\n} from \"./testing-contract\";\nimport { validateChapterAcceptanceManifest } from \"./testing\";\n\nfunction activeMemberships(site: ChapterAcceptanceTarget) {\n return site.skus.filter((sku) => sku.active && sku.kind === \"membership\");\n}\n\n/** Probe public deployed join contracts without creating applications or provider objects. */\nexport async function probeChapterJoinContracts(\n manifest: ChapterAcceptanceManifest,\n fetcher: typeof fetch = fetch,\n): Promise<ChapterJoinContractProbeResult> {\n const validation = validateChapterAcceptanceManifest(manifest);\n if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join(\"; \")}`);\n const findings: ChapterJoinContractFinding[] = [];\n for (const site of manifest.targets) {\n let health: Response;\n let configResponse: Response;\n try {\n [health, configResponse] = await Promise.all([\n fetcher(`${site.origin}/api/health`, { headers: { \"cache-control\": \"no-cache\" } }),\n fetcher(`${site.origin}/api/join-config`, { headers: { \"cache-control\": \"no-cache\" } }),\n ]);\n } catch (error) {\n findings.push({ targetId: site.targetId, siteId: site.siteId, path: \"fetch\", expected: \"reachable\", actual: error instanceof Error ? error.message : String(error) });\n continue;\n }\n const targetNote = (path: string, expected: unknown, actual: unknown): void => {\n if (JSON.stringify(expected) !== JSON.stringify(actual)) findings.push({ targetId: site.targetId, siteId: site.siteId, path, expected, actual });\n };\n targetNote(\"/api/health status\", 200, health.status);\n targetNote(\"/api/join-config status\", 200, configResponse.status);\n if (!configResponse.ok) continue;\n let config: ChapterJoinConfigProbe;\n try {\n config = await configResponse.json() as ChapterJoinConfigProbe;\n } catch {\n targetNote(\"/api/join-config JSON\", \"object\", \"invalid JSON\");\n continue;\n }\n targetNote(\"id\", site.target.chapterId, config.id);\n const actualTiers = Array.isArray(config.tiers) ? config.tiers : [];\n const expectedTiers = activeMemberships(site).map((sku) => ({\n id: sku.id, priceCents: sku.priceCents, currency: sku.currency,\n interval: sku.billing, free: sku.priceCents === 0,\n }));\n const projected = actualTiers.map((tier) => {\n const value = tier as Record<string, unknown>;\n return {\n id: value.id, priceCents: value.priceCents, currency: value.currency,\n interval: value.interval, free: value.free,\n };\n });\n targetNote(\"tiers\", expectedTiers, projected);\n targetNote(\"paymentsReady\", expectedTiers.some((tier) => !tier.free), config.paymentsReady);\n }\n return { ok: findings.length === 0, findings };\n}\n","import type {\n ChapterAcceptanceManifest, ChapterAcceptanceScenario, ChapterAcceptanceSku, ChapterAcceptanceTarget, ChapterAcceptanceValidation,\n} from \"./testing-contract\";\nexport type * from \"./testing-contract\";\nexport { probeChapterJoinContracts } from \"./testing-probe\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\nconst CURRENCY = /^[a-z]{3}$/;\nconst STRIPE_PRICE = /^price_[A-Za-z0-9]{8,}$/;\n\nfunction activeMemberships(site: ChapterAcceptanceTarget): ChapterAcceptanceSku[] {\n return site.skus.filter((sku) => sku.active && sku.kind === \"membership\");\n}\n\nfunction representativePaidMembership(site: ChapterAcceptanceTarget): ChapterAcceptanceSku | undefined {\n return activeMemberships(site).find((sku) => sku.priceCents > 0);\n}\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null;\n}\n\n/** Validate the exact facts a multi-site acceptance run needs before it writes anywhere. */\nexport function validateChapterAcceptanceManifest(value: unknown): ChapterAcceptanceValidation {\n const errors: string[] = [];\n const input = record(value);\n if (!input) return { ok: false, errors: [\"manifest must be an object\"] };\n if (input.schemaVersion !== 1) errors.push(\"schemaVersion must be 1\");\n const inputTargets = Array.isArray(input.targets) ? input.targets : [];\n if (inputTargets.length < 2) errors.push(\"targets must contain at least two deployed Chapter targets\");\n\n const targetIds = new Set<string>();\n const siteIds = new Set<string>();\n const targets = new Set<string>();\n const priceOwners = new Map<string, string>();\n for (const [siteIndex, rawSite] of inputTargets.entries()) {\n const path = `targets[${siteIndex}]`;\n const site = record(rawSite);\n if (!site) {\n errors.push(`${path} must be an object`);\n continue;\n }\n const targetId = typeof site.targetId === \"string\" ? site.targetId : \"\";\n const siteId = typeof site.siteId === \"string\" ? site.siteId : \"\";\n if (!SLUG.test(targetId)) errors.push(`${path}.targetId must be a stable lowercase slug`);\n if (targetIds.has(targetId)) errors.push(`${path}.targetId duplicates ${JSON.stringify(targetId)}`);\n if (targetId) targetIds.add(targetId);\n if (!SLUG.test(siteId)) errors.push(`${path}.siteId must be a stable lowercase slug`);\n if (siteId) siteIds.add(siteId);\n try {\n if (typeof site.origin !== \"string\") throw new TypeError(\"origin\");\n const origin = new URL(site.origin);\n if (origin.protocol !== \"https:\" || origin.pathname !== \"/\" || origin.search || origin.hash) {\n errors.push(`${path}.origin must be an HTTPS origin without a path, query, or fragment`);\n }\n } catch {\n errors.push(`${path}.origin must be a valid HTTPS origin`);\n }\n const target = record(site.target);\n const targetValues = {} as Record<\"appId\" | \"environment\" | \"dataEnvironment\" | \"runtime\" | \"chapterId\", string>;\n for (const key of [\"appId\", \"environment\", \"dataEnvironment\", \"runtime\", \"chapterId\"] as const) {\n targetValues[key] = typeof target?.[key] === \"string\" ? String(target[key]) : \"\";\n if (!targetValues[key]) errors.push(`${path}.target.${key} is required`);\n }\n const partition = Object.values(targetValues).join(\"\\u0000\");\n if (targets.has(partition)) errors.push(`${path}.target duplicates another site partition`);\n if (Object.values(targetValues).every(Boolean)) targets.add(partition);\n if (site.stripeMode !== \"test\" && site.stripeMode !== \"live\") {\n errors.push(`${path}.stripeMode must be test or live`);\n }\n if (site.stripeMode === \"live\" && targetValues.environment !== \"prod\" && targetValues.environment !== \"production\") {\n errors.push(`${path}.stripeMode cannot be live outside a production environment`);\n }\n\n const skuIds = new Set<string>();\n const inputSkus = Array.isArray(site.skus) ? site.skus : [];\n if (!Array.isArray(site.skus) || inputSkus.length === 0) errors.push(`${path}.skus must be a non-empty array`);\n const skus: ChapterAcceptanceSku[] = [];\n for (const [skuIndex, rawSku] of inputSkus.entries()) {\n const skuPath = `${path}.skus[${skuIndex}]`;\n const sku = record(rawSku);\n if (!sku) {\n errors.push(`${skuPath} must be an object`);\n continue;\n }\n const id = typeof sku.id === \"string\" ? sku.id : \"\";\n if (!SLUG.test(id)) errors.push(`${skuPath}.id must be a stable lowercase slug`);\n if (skuIds.has(id)) errors.push(`${skuPath}.id duplicates ${JSON.stringify(id)}`);\n if (id) skuIds.add(id);\n if (sku.kind !== \"membership\" && sku.kind !== \"add_on\") errors.push(`${skuPath}.kind must be membership or add_on`);\n if (typeof sku.active !== \"boolean\") errors.push(`${skuPath}.active must be boolean`);\n const priceCents = typeof sku.priceCents === \"number\" ? sku.priceCents : Number.NaN;\n if (!Number.isSafeInteger(priceCents) || priceCents < 0) errors.push(`${skuPath}.priceCents must be a non-negative integer`);\n if (typeof sku.currency !== \"string\" || !CURRENCY.test(sku.currency)) errors.push(`${skuPath}.currency must be a lowercase ISO currency code`);\n if (sku.billing !== \"month\" && sku.billing !== \"year\" && sku.billing !== \"once\") {\n errors.push(`${skuPath}.billing must be month, year, or once`);\n }\n if (sku.kind === \"membership\" && sku.billing === \"once\") {\n errors.push(`${skuPath}.billing must be month or year for a membership SKU`);\n }\n if (sku.active === true && priceCents > 0 && (typeof sku.stripePriceId !== \"string\" || !STRIPE_PRICE.test(sku.stripePriceId))) {\n errors.push(`${skuPath}.stripePriceId is required for an active paid SKU`);\n }\n if (priceCents === 0 && sku.stripePriceId) errors.push(`${skuPath}.stripePriceId must be absent for a free SKU`);\n if (typeof sku.stripePriceId === \"string\" && sku.stripePriceId) {\n const owner = `${siteId}/${id}`;\n const previous = priceOwners.get(sku.stripePriceId);\n if (previous && previous !== owner) errors.push(`${skuPath}.stripePriceId is already owned by ${previous}`);\n else priceOwners.set(sku.stripePriceId, owner);\n }\n if (sku.compareAtPriceCents !== undefined) {\n if (!Number.isSafeInteger(sku.compareAtPriceCents) || Number(sku.compareAtPriceCents) <= priceCents) {\n errors.push(`${skuPath}.compareAtPriceCents must be an integer greater than priceCents`);\n }\n if (sku.discountDuration !== \"first_term\" && sku.discountDuration !== \"while_active\") {\n errors.push(`${skuPath}.discountDuration is required with compareAtPriceCents`);\n }\n } else if (sku.discountDuration) {\n errors.push(`${skuPath}.compareAtPriceCents is required with discountDuration`);\n }\n skus.push(sku as unknown as ChapterAcceptanceSku);\n }\n if (!skus.some((sku) => sku.active && sku.kind === \"membership\")) errors.push(`${path}.skus must contain an active membership SKU`);\n const options = record(site.options);\n if (!options) errors.push(`${path}.options must be an object`);\n if (options?.ordinaryInterview !== true) errors.push(`${path}.options.ordinaryInterview must be true`);\n if (typeof options?.invitationInterviewWaiver !== \"boolean\") errors.push(`${path}.options.invitationInterviewWaiver must be boolean`);\n if (options?.autoRenew !== true) errors.push(`${path}.options.autoRenew must be true`);\n const seat = record(options?.additionalSeat);\n if (options?.additionalSeat !== undefined && !seat) errors.push(`${path}.options.additionalSeat must be an object`);\n if (seat) {\n const seatSku = skus.find((sku) => sku.id === seat.skuId);\n if (!seatSku || !seatSku.active || seatSku.kind !== \"add_on\") {\n errors.push(`${path}.options.additionalSeat.skuId must name an active add_on SKU`);\n } else if (seatSku.billing === \"once\") {\n errors.push(`${path}.options.additionalSeat.skuId must name a recurring add_on SKU`);\n }\n if (typeof seat.purchaseWindowCalendarMonths !== \"number\" ||\n !Number.isInteger(seat.purchaseWindowCalendarMonths) || seat.purchaseWindowCalendarMonths < 1) {\n errors.push(`${path}.options.additionalSeat.purchaseWindowCalendarMonths must be a positive integer`);\n }\n if (seat.named !== true || seat.uniqueEmail !== true || seat.renewal !== \"membership_aligned\" || seat.chargeTiming !== \"start\") {\n errors.push(`${path}.options.additionalSeat must require a named unique-email seat, membership-aligned renewal, and payment at start`);\n }\n }\n }\n if (siteIds.size < 2) errors.push(\"targets must cover at least two distinct logical sites\");\n return errors.length ? { ok: false, errors } : { ok: true, value: value as ChapterAcceptanceManifest };\n}\n\nfunction scenario(input: Omit<ChapterAcceptanceScenario, \"id\">): ChapterAcceptanceScenario {\n return {\n ...input,\n id: [input.suite, input.targetId, input.skuId, input.siblingTargetId, input.kind].filter(Boolean).join(\":\"),\n };\n}\n\n/**\n * Build exhaustive pure cases plus a bounded provider-backed development matrix.\n * Throws before returning provider work when the manifest is invalid or names live Stripe.\n */\nexport function planChapterAcceptanceMatrix(manifest: ChapterAcceptanceManifest): ChapterAcceptanceScenario[] {\n const validation = validateChapterAcceptanceManifest(manifest);\n if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join(\"; \")}`);\n if (manifest.targets.some((site) => site.stripeMode !== \"test\")) {\n throw new TypeError(\"provider acceptance runs are development-only and require Stripe test mode on every target\");\n }\n const scenarios: ChapterAcceptanceScenario[] = [];\n for (const site of manifest.targets) {\n for (const sku of site.skus.filter((candidate) => candidate.active)) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"catalog_contract\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n if (sku.priceCents > 0) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"amount_tamper\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"wrong_environment_event\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"post_stripe_compensation\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n for (const sibling of manifest.targets.filter((candidate) => candidate.targetId !== site.targetId)) {\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"sibling_event_isolation\", targetId: site.targetId, siteId: site.siteId,\n skuId: sku.id, siblingTargetId: sibling.targetId,\n }));\n }\n }\n }\n for (const sku of activeMemberships(site)) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"submit_replay\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({\n suite: \"provider_dev\", kind: sku.priceCents > 0 ? \"paid_signup\" : \"free_signup\",\n targetId: site.targetId, siteId: site.siteId, skuId: sku.id,\n }));\n }\n const paid = representativePaidMembership(site);\n if (paid) {\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"ordinary_interview\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"renewal\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"refund_cancel\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n if (site.options.invitationInterviewWaiver) {\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"invitation_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"expired_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"replayed_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n }\n if (site.options.additionalSeat) {\n scenarios.push(scenario({\n suite: \"provider_dev\", kind: \"named_seat\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"duplicate_seat_email\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"expired_seat_window\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n }\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"canceled_membership\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n }\n }\n return scenarios;\n}\n"],"mappings":"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","names":["activeMemberships"]}
|
|
1
|
+
{"version":3,"sources":["../src/testing-probe.ts","../src/testing-runner.ts","../src/testing.ts"],"sourcesContent":["import type {\n ChapterAcceptanceManifest,\n ChapterAcceptanceTarget,\n ChapterJoinConfigProbe,\n ChapterJoinContractFinding,\n ChapterJoinContractProbeResult,\n} from \"./testing-contract\";\nimport { validateChapterAcceptanceManifest } from \"./testing\";\n\nfunction activeMemberships(site: ChapterAcceptanceTarget) {\n return site.skus.filter((sku) => sku.active && sku.kind === \"membership\");\n}\n\n/** Probe public deployed join contracts without creating applications or provider objects. */\nexport async function probeChapterJoinContracts(\n manifest: ChapterAcceptanceManifest,\n fetcher: typeof fetch = fetch,\n): Promise<ChapterJoinContractProbeResult> {\n const validation = validateChapterAcceptanceManifest(manifest);\n if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join(\"; \")}`);\n const findings: ChapterJoinContractFinding[] = [];\n for (const site of manifest.targets) {\n let health: Response;\n let configResponse: Response;\n try {\n [health, configResponse] = await Promise.all([\n fetcher(`${site.origin}/api/health`, { headers: { \"cache-control\": \"no-cache\" } }),\n fetcher(`${site.origin}/api/join-config`, { headers: { \"cache-control\": \"no-cache\" } }),\n ]);\n } catch (error) {\n findings.push({ targetId: site.targetId, siteId: site.siteId, path: \"fetch\", expected: \"reachable\", actual: error instanceof Error ? error.message : String(error) });\n continue;\n }\n const targetNote = (path: string, expected: unknown, actual: unknown): void => {\n if (JSON.stringify(expected) !== JSON.stringify(actual)) findings.push({ targetId: site.targetId, siteId: site.siteId, path, expected, actual });\n };\n targetNote(\"/api/health status\", 200, health.status);\n targetNote(\"/api/join-config status\", 200, configResponse.status);\n if (!configResponse.ok) continue;\n let config: ChapterJoinConfigProbe;\n try {\n config = await configResponse.json() as ChapterJoinConfigProbe;\n } catch {\n targetNote(\"/api/join-config JSON\", \"object\", \"invalid JSON\");\n continue;\n }\n targetNote(\"id\", site.target.chapterId, config.id);\n const actualTiers = Array.isArray(config.tiers) ? config.tiers : [];\n const expectedTiers = activeMemberships(site).map((sku) => ({\n id: sku.id, priceCents: sku.priceCents, currency: sku.currency,\n interval: sku.billing, free: sku.priceCents === 0,\n }));\n const projected = actualTiers.map((tier) => {\n const value = tier as Record<string, unknown>;\n return {\n id: value.id, priceCents: value.priceCents, currency: value.currency,\n interval: value.interval, free: value.free,\n };\n });\n targetNote(\"tiers\", expectedTiers, projected);\n targetNote(\"paymentsReady\", expectedTiers.some((tier) => !tier.free), config.paymentsReady);\n }\n return { ok: findings.length === 0, findings };\n}\n","import type {\n ChapterAcceptanceCaseResult,\n ChapterAcceptanceCleanupEvidence,\n ChapterAcceptanceEvidence,\n ChapterAcceptanceExecutionContext,\n ChapterAcceptanceExecutor,\n ChapterAcceptanceManifest,\n ChapterAcceptanceProviderReference,\n ChapterAcceptanceRunResult,\n} from \"./testing-contract\";\nimport { planChapterAcceptanceMatrix } from \"./testing\";\n\nconst SAFE_LABEL = /^[a-z0-9][a-z0-9._:-]{0,159}$/;\nconst SAFE_REFERENCE = /^[A-Za-z0-9_:.=-]{3,240}$/;\n\nfunction safeErrorCode(error: unknown): string {\n if (error && typeof error === \"object\") {\n const code = (error as { code?: unknown }).code;\n if (typeof code === \"string\" && SAFE_LABEL.test(code)) return code;\n }\n return \"execution_failed\";\n}\n\nfunction validateLabels(values: readonly string[], path: string): void {\n if (!Array.isArray(values) || values.length === 0) throw Object.assign(new TypeError(`${path} must be non-empty`), { code: \"invalid_evidence\" });\n if (values.length > 64 || values.some((value) => typeof value !== \"string\" || !SAFE_LABEL.test(value))) {\n throw Object.assign(new TypeError(`${path} contains an unsafe or unbounded label`), { code: \"invalid_evidence\" });\n }\n}\n\nfunction validateReferences(values: readonly ChapterAcceptanceProviderReference[] | undefined): void {\n if (values === undefined) return;\n if (!Array.isArray(values) || values.length > 64) throw Object.assign(new TypeError(\"references must be bounded\"), { code: \"invalid_evidence\" });\n const kinds = new Set([\"stripe_event\", \"stripe_object\", \"clerk_user\", \"calendar_event\", \"email_message\", \"event_digest\"]);\n for (const reference of values) {\n if (!reference || !kinds.has(reference.kind) || typeof reference.value !== \"string\" ||\n !SAFE_REFERENCE.test(reference.value) || reference.value.includes(\"@\")) {\n throw Object.assign(new TypeError(\"references may contain only opaque provider ids or digests\"), { code: \"invalid_evidence\" });\n }\n }\n}\n\nfunction validateEvidence(evidence: ChapterAcceptanceEvidence, provider: boolean): void {\n if (!evidence || typeof evidence !== \"object\") throw Object.assign(new TypeError(\"executor returned no evidence\"), { code: \"invalid_evidence\" });\n validateLabels(evidence.checks, \"evidence.checks\");\n validateReferences(evidence.references);\n if (provider && (typeof evidence.deploymentVersion !== \"string\" || !SAFE_REFERENCE.test(evidence.deploymentVersion))) {\n throw Object.assign(new TypeError(\"provider evidence requires a safe deploymentVersion\"), { code: \"invalid_evidence\" });\n }\n}\n\nfunction validateCleanup(cleanup: ChapterAcceptanceCleanupEvidence): void {\n if (!cleanup || (cleanup.status !== \"complete\" && cleanup.status !== \"incomplete\")) {\n throw Object.assign(new TypeError(\"executor returned invalid cleanup evidence\"), { code: \"cleanup_failed\" });\n }\n validateLabels(cleanup.checks, \"cleanup.checks\");\n}\n\nfunction contextFor(manifest: ChapterAcceptanceManifest, id: string): ChapterAcceptanceExecutionContext {\n const scenario = planChapterAcceptanceMatrix(manifest).find((item) => item.id === id);\n if (!scenario) throw new TypeError(`unknown acceptance scenario ${id}`);\n const target = manifest.targets.find((item) => item.targetId === scenario.targetId)!;\n return {\n scenario,\n target,\n ...(scenario.skuId ? { sku: target.skus.find((item) => item.id === scenario.skuId) } : {}),\n ...(scenario.siblingTargetId ? {\n siblingTarget: manifest.targets.find((item) => item.targetId === scenario.siblingTargetId),\n } : {}),\n };\n}\n\n/**\n * Execute one suite serially. Provider runs fail before their first mutation\n * unless the app supplies cleanup, then invoke cleanup after every attempted\n * case even when execution throws. Raw provider error text is never retained.\n */\nexport async function runChapterAcceptanceSuite(\n manifest: ChapterAcceptanceManifest,\n suite: \"hermetic\" | \"provider_dev\",\n executor: ChapterAcceptanceExecutor,\n): Promise<ChapterAcceptanceRunResult> {\n const scenarios = planChapterAcceptanceMatrix(manifest).filter((item) => item.suite === suite);\n if (suite === \"provider_dev\" && typeof executor.cleanup !== \"function\") {\n throw new TypeError(\"provider_dev acceptance requires a cleanup executor before any case can run\");\n }\n const cases: ChapterAcceptanceCaseResult[] = [];\n for (const scenario of scenarios) {\n const context = contextFor(manifest, scenario.id);\n let evidence: ChapterAcceptanceEvidence | undefined;\n let errorCode: string | undefined;\n let cleanup: ChapterAcceptanceCleanupEvidence | undefined;\n try {\n evidence = await executor.execute(context);\n validateEvidence(evidence, suite === \"provider_dev\");\n } catch (error) {\n errorCode = safeErrorCode(error);\n } finally {\n if (suite === \"provider_dev\") {\n try {\n cleanup = await executor.cleanup!(context);\n validateCleanup(cleanup);\n if (cleanup.status !== \"complete\") errorCode = \"cleanup_incomplete\";\n } catch {\n errorCode = \"cleanup_failed\";\n }\n }\n }\n cases.push({\n id: scenario.id,\n suite,\n outcome: errorCode ? \"failed\" : \"passed\",\n ...(evidence ? { evidence } : {}),\n ...(cleanup ? { cleanup } : {}),\n ...(errorCode ? { errorCode } : {}),\n });\n }\n return { ok: cases.every((item) => item.outcome === \"passed\"), suite, cases };\n}\n\n/** PII-free line report suitable for CI logs and PM evidence. */\nexport function formatChapterAcceptanceRun(result: ChapterAcceptanceRunResult): string {\n const passed = result.cases.filter((item) => item.outcome === \"passed\").length;\n const lines = [`${result.ok ? \"ok\" : \"failed\"}: ${result.suite} ${passed}/${result.cases.length}`];\n for (const item of result.cases) {\n lines.push(`${item.outcome === \"passed\" ? \"PASS\" : \"FAIL\"} ${item.id}${item.errorCode ? ` ${item.errorCode}` : \"\"}`);\n }\n return lines.join(\"\\n\");\n}\n","import type {\n ChapterAcceptanceManifest, ChapterAcceptanceScenario, ChapterAcceptanceSku, ChapterAcceptanceTarget, ChapterAcceptanceValidation,\n} from \"./testing-contract\";\nexport type * from \"./testing-contract\";\nexport { probeChapterJoinContracts } from \"./testing-probe\";\nexport { formatChapterAcceptanceRun, runChapterAcceptanceSuite } from \"./testing-runner\";\n\nconst SLUG = /^[a-z0-9][a-z0-9-]{1,62}$/;\nconst CURRENCY = /^[a-z]{3}$/;\nconst STRIPE_PRICE = /^price_[A-Za-z0-9]{8,}$/;\n\nfunction activeMemberships(site: ChapterAcceptanceTarget): ChapterAcceptanceSku[] {\n return site.skus.filter((sku) => sku.active && sku.kind === \"membership\");\n}\n\nfunction representativePaidMembership(site: ChapterAcceptanceTarget): ChapterAcceptanceSku | undefined {\n return activeMemberships(site).find((sku) => sku.priceCents > 0);\n}\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : null;\n}\n\n/** Validate the exact facts a multi-site acceptance run needs before it writes anywhere. */\nexport function validateChapterAcceptanceManifest(value: unknown): ChapterAcceptanceValidation {\n const errors: string[] = [];\n const input = record(value);\n if (!input) return { ok: false, errors: [\"manifest must be an object\"] };\n if (input.schemaVersion !== 1) errors.push(\"schemaVersion must be 1\");\n const inputTargets = Array.isArray(input.targets) ? input.targets : [];\n if (inputTargets.length < 2) errors.push(\"targets must contain at least two deployed Chapter targets\");\n\n const targetIds = new Set<string>();\n const siteIds = new Set<string>();\n const targets = new Set<string>();\n const priceOwners = new Map<string, string>();\n for (const [siteIndex, rawSite] of inputTargets.entries()) {\n const path = `targets[${siteIndex}]`;\n const site = record(rawSite);\n if (!site) {\n errors.push(`${path} must be an object`);\n continue;\n }\n const targetId = typeof site.targetId === \"string\" ? site.targetId : \"\";\n const siteId = typeof site.siteId === \"string\" ? site.siteId : \"\";\n if (!SLUG.test(targetId)) errors.push(`${path}.targetId must be a stable lowercase slug`);\n if (targetIds.has(targetId)) errors.push(`${path}.targetId duplicates ${JSON.stringify(targetId)}`);\n if (targetId) targetIds.add(targetId);\n if (!SLUG.test(siteId)) errors.push(`${path}.siteId must be a stable lowercase slug`);\n if (siteId) siteIds.add(siteId);\n try {\n if (typeof site.origin !== \"string\") throw new TypeError(\"origin\");\n const origin = new URL(site.origin);\n if (origin.protocol !== \"https:\" || origin.pathname !== \"/\" || origin.search || origin.hash) {\n errors.push(`${path}.origin must be an HTTPS origin without a path, query, or fragment`);\n }\n } catch {\n errors.push(`${path}.origin must be a valid HTTPS origin`);\n }\n const target = record(site.target);\n const targetValues = {} as Record<\"appId\" | \"environment\" | \"dataEnvironment\" | \"runtime\" | \"chapterId\", string>;\n for (const key of [\"appId\", \"environment\", \"dataEnvironment\", \"runtime\", \"chapterId\"] as const) {\n targetValues[key] = typeof target?.[key] === \"string\" ? String(target[key]) : \"\";\n if (!targetValues[key]) errors.push(`${path}.target.${key} is required`);\n }\n const partition = Object.values(targetValues).join(\"\\u0000\");\n if (targets.has(partition)) errors.push(`${path}.target duplicates another site partition`);\n if (Object.values(targetValues).every(Boolean)) targets.add(partition);\n if (site.stripeMode !== \"test\" && site.stripeMode !== \"live\") {\n errors.push(`${path}.stripeMode must be test or live`);\n }\n if (site.stripeMode === \"live\" && targetValues.environment !== \"prod\" && targetValues.environment !== \"production\") {\n errors.push(`${path}.stripeMode cannot be live outside a production environment`);\n }\n\n const skuIds = new Set<string>();\n const inputSkus = Array.isArray(site.skus) ? site.skus : [];\n if (!Array.isArray(site.skus) || inputSkus.length === 0) errors.push(`${path}.skus must be a non-empty array`);\n const skus: ChapterAcceptanceSku[] = [];\n for (const [skuIndex, rawSku] of inputSkus.entries()) {\n const skuPath = `${path}.skus[${skuIndex}]`;\n const sku = record(rawSku);\n if (!sku) {\n errors.push(`${skuPath} must be an object`);\n continue;\n }\n const id = typeof sku.id === \"string\" ? sku.id : \"\";\n if (!SLUG.test(id)) errors.push(`${skuPath}.id must be a stable lowercase slug`);\n if (skuIds.has(id)) errors.push(`${skuPath}.id duplicates ${JSON.stringify(id)}`);\n if (id) skuIds.add(id);\n if (sku.kind !== \"membership\" && sku.kind !== \"add_on\") errors.push(`${skuPath}.kind must be membership or add_on`);\n if (typeof sku.active !== \"boolean\") errors.push(`${skuPath}.active must be boolean`);\n const priceCents = typeof sku.priceCents === \"number\" ? sku.priceCents : Number.NaN;\n if (!Number.isSafeInteger(priceCents) || priceCents < 0) errors.push(`${skuPath}.priceCents must be a non-negative integer`);\n if (typeof sku.currency !== \"string\" || !CURRENCY.test(sku.currency)) errors.push(`${skuPath}.currency must be a lowercase ISO currency code`);\n if (sku.billing !== \"month\" && sku.billing !== \"year\" && sku.billing !== \"once\") {\n errors.push(`${skuPath}.billing must be month, year, or once`);\n }\n if (sku.kind === \"membership\" && sku.billing === \"once\") {\n errors.push(`${skuPath}.billing must be month or year for a membership SKU`);\n }\n if (sku.active === true && priceCents > 0 && (typeof sku.stripePriceId !== \"string\" || !STRIPE_PRICE.test(sku.stripePriceId))) {\n errors.push(`${skuPath}.stripePriceId is required for an active paid SKU`);\n }\n if (priceCents === 0 && sku.stripePriceId) errors.push(`${skuPath}.stripePriceId must be absent for a free SKU`);\n if (typeof sku.stripePriceId === \"string\" && sku.stripePriceId) {\n const owner = `${siteId}/${id}`;\n const previous = priceOwners.get(sku.stripePriceId);\n if (previous && previous !== owner) errors.push(`${skuPath}.stripePriceId is already owned by ${previous}`);\n else priceOwners.set(sku.stripePriceId, owner);\n }\n if (sku.compareAtPriceCents !== undefined) {\n if (!Number.isSafeInteger(sku.compareAtPriceCents) || Number(sku.compareAtPriceCents) <= priceCents) {\n errors.push(`${skuPath}.compareAtPriceCents must be an integer greater than priceCents`);\n }\n if (sku.discountDuration !== \"first_term\" && sku.discountDuration !== \"while_active\") {\n errors.push(`${skuPath}.discountDuration is required with compareAtPriceCents`);\n }\n } else if (sku.discountDuration) {\n errors.push(`${skuPath}.compareAtPriceCents is required with discountDuration`);\n }\n skus.push(sku as unknown as ChapterAcceptanceSku);\n }\n if (!skus.some((sku) => sku.active && sku.kind === \"membership\")) errors.push(`${path}.skus must contain an active membership SKU`);\n const options = record(site.options);\n if (!options) errors.push(`${path}.options must be an object`);\n if (options?.ordinaryInterview !== true) errors.push(`${path}.options.ordinaryInterview must be true`);\n if (typeof options?.invitationInterviewWaiver !== \"boolean\") errors.push(`${path}.options.invitationInterviewWaiver must be boolean`);\n if (options?.autoRenew !== true) errors.push(`${path}.options.autoRenew must be true`);\n const seat = record(options?.additionalSeat);\n if (options?.additionalSeat !== undefined && !seat) errors.push(`${path}.options.additionalSeat must be an object`);\n if (seat) {\n const seatSku = skus.find((sku) => sku.id === seat.skuId);\n if (!seatSku || !seatSku.active || seatSku.kind !== \"add_on\") {\n errors.push(`${path}.options.additionalSeat.skuId must name an active add_on SKU`);\n } else if (seatSku.billing === \"once\") {\n errors.push(`${path}.options.additionalSeat.skuId must name a recurring add_on SKU`);\n }\n if (typeof seat.purchaseWindowCalendarMonths !== \"number\" ||\n !Number.isInteger(seat.purchaseWindowCalendarMonths) || seat.purchaseWindowCalendarMonths < 1) {\n errors.push(`${path}.options.additionalSeat.purchaseWindowCalendarMonths must be a positive integer`);\n }\n if (seat.named !== true || seat.uniqueEmail !== true || seat.renewal !== \"membership_aligned\" || seat.chargeTiming !== \"start\") {\n errors.push(`${path}.options.additionalSeat must require a named unique-email seat, membership-aligned renewal, and payment at start`);\n }\n }\n }\n if (siteIds.size < 2) errors.push(\"targets must cover at least two distinct logical sites\");\n return errors.length ? { ok: false, errors } : { ok: true, value: value as ChapterAcceptanceManifest };\n}\n\nfunction scenario(input: Omit<ChapterAcceptanceScenario, \"id\">): ChapterAcceptanceScenario {\n return {\n ...input,\n id: [input.suite, input.targetId, input.skuId, input.siblingTargetId, input.kind].filter(Boolean).join(\":\"),\n };\n}\n\n/**\n * Build exhaustive pure cases plus a bounded provider-backed development matrix.\n * Throws before returning provider work when the manifest is invalid or names live Stripe.\n */\nexport function planChapterAcceptanceMatrix(manifest: ChapterAcceptanceManifest): ChapterAcceptanceScenario[] {\n const validation = validateChapterAcceptanceManifest(manifest);\n if (!validation.ok) throw new TypeError(`invalid Chapter acceptance manifest: ${validation.errors.join(\"; \")}`);\n if (manifest.targets.some((site) => site.stripeMode !== \"test\")) {\n throw new TypeError(\"provider acceptance runs are development-only and require Stripe test mode on every target\");\n }\n const scenarios: ChapterAcceptanceScenario[] = [];\n for (const site of manifest.targets) {\n for (const sku of site.skus.filter((candidate) => candidate.active)) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"catalog_contract\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n if (sku.priceCents > 0) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"amount_tamper\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"wrong_environment_event\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"post_stripe_compensation\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n for (const sibling of manifest.targets.filter((candidate) => candidate.targetId !== site.targetId)) {\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"sibling_event_isolation\", targetId: site.targetId, siteId: site.siteId,\n skuId: sku.id, siblingTargetId: sibling.targetId,\n }));\n }\n }\n }\n for (const sku of activeMemberships(site)) {\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"submit_replay\", targetId: site.targetId, siteId: site.siteId, skuId: sku.id }));\n scenarios.push(scenario({\n suite: \"provider_dev\", kind: sku.priceCents > 0 ? \"paid_signup\" : \"free_signup\",\n targetId: site.targetId, siteId: site.siteId, skuId: sku.id,\n }));\n }\n const paid = representativePaidMembership(site);\n if (paid) {\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"ordinary_interview\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"renewal\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"refund_cancel\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n if (site.options.invitationInterviewWaiver) {\n scenarios.push(scenario({ suite: \"provider_dev\", kind: \"invitation_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"expired_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"replayed_interview_waiver\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n }\n if (site.options.additionalSeat) {\n scenarios.push(scenario({\n suite: \"provider_dev\", kind: \"named_seat\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"duplicate_seat_email\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n scenarios.push(scenario({\n suite: \"hermetic\", kind: \"expired_seat_window\", targetId: site.targetId, siteId: site.siteId,\n skuId: site.options.additionalSeat.skuId,\n }));\n }\n scenarios.push(scenario({ suite: \"hermetic\", kind: \"canceled_membership\", targetId: site.targetId, siteId: site.siteId, skuId: paid.id }));\n }\n }\n return scenarios;\n}\n"],"mappings":"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","names":["scenario","activeMemberships"]}
|
package/dist/worker/index.cjs
CHANGED
|
@@ -1525,12 +1525,12 @@ function validateControlledApplication(chapter, document) {
|
|
|
1525
1525
|
return [...document.application.required, ...document.application.optional].filter((field) => !declared.has(field)).map((field) => `document.application field "${field}" is not declared by this chapter schema`);
|
|
1526
1526
|
}
|
|
1527
1527
|
function groupAttrs(document) {
|
|
1528
|
-
const
|
|
1528
|
+
const first5 = [...document.tiers].filter((tier) => tier.available).sort((a, b) => a.sortOrder - b.sortOrder)[0];
|
|
1529
1529
|
const firstPaid = [...document.tiers].find((tier) => tier.available && tier.priceCents > 0);
|
|
1530
1530
|
return {
|
|
1531
|
-
...
|
|
1532
|
-
currency:
|
|
1533
|
-
interval:
|
|
1531
|
+
...first5 ? { standardPriceCents: first5.priceCents } : {},
|
|
1532
|
+
currency: first5?.currency ?? "usd",
|
|
1533
|
+
interval: first5?.interval ?? "year",
|
|
1534
1534
|
foundingDiscountCents: 0,
|
|
1535
1535
|
...firstPaid?.stripePriceId ? { stripePriceId: firstPaid.stripePriceId } : {},
|
|
1536
1536
|
...document.policy ?? {},
|
|
@@ -4498,12 +4498,130 @@ async function resolveStripeWebhookSecret(db, runtime) {
|
|
|
4498
4498
|
|
|
4499
4499
|
// src/payments-route-support.ts
|
|
4500
4500
|
var paymentErrorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : "unknown";
|
|
4501
|
-
function paymentLineItems(group) {
|
|
4501
|
+
function paymentLineItems(group, tier) {
|
|
4502
4502
|
const standard = Number(group.standardPriceCents ?? 0);
|
|
4503
4503
|
const discount = Number(group.foundingDiscountCents ?? 0);
|
|
4504
|
+
const legacyTier = !tier || tier.id === "standard" && tier.priceCents === standard && tier.stripePriceId === group.stripePriceId;
|
|
4505
|
+
if (!legacyTier) {
|
|
4506
|
+
return { standardCents: tier.priceCents, discountCents: 0, dueTodayCents: tier.priceCents };
|
|
4507
|
+
}
|
|
4504
4508
|
return { standardCents: standard, discountCents: discount, dueTodayCents: standard - discount };
|
|
4505
4509
|
}
|
|
4506
4510
|
|
|
4511
|
+
// src/subscription-checkout.ts
|
|
4512
|
+
var first3 = (value) => Array.isArray(value) ? value[0] : void 0;
|
|
4513
|
+
function mismatch() {
|
|
4514
|
+
const error = new Error("subscription checkout intent does not match this checkout");
|
|
4515
|
+
error.code = "checkout_intent_mismatch";
|
|
4516
|
+
throw error;
|
|
4517
|
+
}
|
|
4518
|
+
function intentAttrs(input) {
|
|
4519
|
+
return {
|
|
4520
|
+
id: input.applicationId,
|
|
4521
|
+
applicationId: input.applicationId,
|
|
4522
|
+
appId: input.appId,
|
|
4523
|
+
chapterId: input.chapterId,
|
|
4524
|
+
environment: input.environment,
|
|
4525
|
+
...input.runtime ? { runtime: input.runtime } : {},
|
|
4526
|
+
groupId: input.groupId,
|
|
4527
|
+
tierId: input.tierId,
|
|
4528
|
+
priceId: input.priceId,
|
|
4529
|
+
priceCents: input.priceCents,
|
|
4530
|
+
currency: input.currency,
|
|
4531
|
+
interval: input.interval,
|
|
4532
|
+
purchaserEmailNormalized: input.emailNormalized,
|
|
4533
|
+
...input.existingCustomerId ? { requestedCustomerId: input.existingCustomerId } : {},
|
|
4534
|
+
status: "created",
|
|
4535
|
+
createdAt: input.now,
|
|
4536
|
+
updatedAt: input.now
|
|
4537
|
+
};
|
|
4538
|
+
}
|
|
4539
|
+
function matchesIntent(stored, expected) {
|
|
4540
|
+
return [
|
|
4541
|
+
"id",
|
|
4542
|
+
"applicationId",
|
|
4543
|
+
"appId",
|
|
4544
|
+
"chapterId",
|
|
4545
|
+
"environment",
|
|
4546
|
+
"runtime",
|
|
4547
|
+
"groupId",
|
|
4548
|
+
"tierId",
|
|
4549
|
+
"priceId",
|
|
4550
|
+
"priceCents",
|
|
4551
|
+
"currency",
|
|
4552
|
+
"interval",
|
|
4553
|
+
"purchaserEmailNormalized",
|
|
4554
|
+
"requestedCustomerId"
|
|
4555
|
+
].every((key) => stored[key] === expected[key]);
|
|
4556
|
+
}
|
|
4557
|
+
async function loadIntent(db, id) {
|
|
4558
|
+
return first3((await db.query({
|
|
4559
|
+
subscriptionCheckoutIntents: { $: { where: { id }, limit: 1 } }
|
|
4560
|
+
})).subscriptionCheckoutIntents);
|
|
4561
|
+
}
|
|
4562
|
+
async function startSubscriptionCheckout(db, provider, input) {
|
|
4563
|
+
const expected = intentAttrs(input);
|
|
4564
|
+
let stored = await loadIntent(db, input.applicationId);
|
|
4565
|
+
if (!stored) {
|
|
4566
|
+
const write = await db.transact([{
|
|
4567
|
+
t: "update",
|
|
4568
|
+
ns: "subscriptionCheckoutIntents",
|
|
4569
|
+
id: input.applicationId,
|
|
4570
|
+
attrs: expected
|
|
4571
|
+
}], {
|
|
4572
|
+
mutationId: `subscription-checkout-intent:${input.applicationId}`,
|
|
4573
|
+
guards: [{ ns: "subscriptionCheckoutIntents", id: input.applicationId, exists: false }]
|
|
4574
|
+
});
|
|
4575
|
+
stored = write.duplicate ? await loadIntent(db, input.applicationId) : expected;
|
|
4576
|
+
}
|
|
4577
|
+
if (!stored || !matchesIntent(stored, expected)) mismatch();
|
|
4578
|
+
if (stored.status !== "created" && stored.status !== "checkout_ready") mismatch();
|
|
4579
|
+
const result = await provider.createSubscription({
|
|
4580
|
+
appId: input.appId,
|
|
4581
|
+
chapterId: input.chapterId,
|
|
4582
|
+
environment: input.environment,
|
|
4583
|
+
...input.runtime ? { runtime: input.runtime } : {},
|
|
4584
|
+
applicationId: input.applicationId,
|
|
4585
|
+
groupId: input.groupId,
|
|
4586
|
+
email: input.email,
|
|
4587
|
+
name: input.name,
|
|
4588
|
+
priceId: input.priceId,
|
|
4589
|
+
existingCustomerId: typeof stored.providerCustomerId === "string" ? stored.providerCustomerId : input.existingCustomerId
|
|
4590
|
+
});
|
|
4591
|
+
if (stored.providerCustomerId && stored.providerCustomerId !== result.customerId || stored.providerSubscriptionId && stored.providerSubscriptionId !== result.subscriptionId) mismatch();
|
|
4592
|
+
await db.transact([
|
|
4593
|
+
{
|
|
4594
|
+
t: "update",
|
|
4595
|
+
ns: "subscriptionCheckoutIntents",
|
|
4596
|
+
id: input.applicationId,
|
|
4597
|
+
attrs: {
|
|
4598
|
+
status: "checkout_ready",
|
|
4599
|
+
providerCustomerId: result.customerId,
|
|
4600
|
+
providerSubscriptionId: result.subscriptionId,
|
|
4601
|
+
updatedAt: input.now
|
|
4602
|
+
}
|
|
4603
|
+
},
|
|
4604
|
+
{
|
|
4605
|
+
t: "update",
|
|
4606
|
+
ns: "applications",
|
|
4607
|
+
id: input.applicationId,
|
|
4608
|
+
attrs: {
|
|
4609
|
+
stripeCustomerId: result.customerId,
|
|
4610
|
+
stripeSubscriptionId: result.subscriptionId,
|
|
4611
|
+
...input.runtime ? { stripeRuntime: input.runtime } : {},
|
|
4612
|
+
refundPolicyAckAt: input.now
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4615
|
+
], {
|
|
4616
|
+
mutationId: `subscription-checkout-ready:${input.applicationId}:${result.subscriptionId}`,
|
|
4617
|
+
guards: [
|
|
4618
|
+
{ ns: "subscriptionCheckoutIntents", id: input.applicationId, exists: true, equals: { status: stored.status } },
|
|
4619
|
+
{ ns: "applications", id: input.applicationId, exists: true, equals: { status: "submitted" } }
|
|
4620
|
+
]
|
|
4621
|
+
});
|
|
4622
|
+
return result;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4507
4625
|
// src/member-system-contract.ts
|
|
4508
4626
|
function classifyMemberStripeEvent(identity, target, hasProvenLegacyLocalLink) {
|
|
4509
4627
|
const keys = ["appId", "environment", "dataEnvironment", "runtime", "chapterId"];
|
|
@@ -4672,35 +4790,29 @@ async function startSubscription(req, env, ctx) {
|
|
|
4672
4790
|
const provider = createStripeProvider({ secretKey });
|
|
4673
4791
|
let result;
|
|
4674
4792
|
try {
|
|
4675
|
-
result = await provider
|
|
4793
|
+
result = await startSubscriptionCheckout(db, provider, {
|
|
4676
4794
|
appId: env.ODLA_APP_ID,
|
|
4677
4795
|
chapterId: ctx.chapter.id,
|
|
4678
4796
|
environment: env.ODLA_ENV,
|
|
4679
4797
|
runtime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
|
|
4680
4798
|
applicationId,
|
|
4681
4799
|
groupId: String(group.id),
|
|
4800
|
+
tierId: tier.id,
|
|
4801
|
+
priceCents: tier.priceCents,
|
|
4802
|
+
currency: tier.currency ?? String(group.currency ?? "usd"),
|
|
4803
|
+
interval: tier.interval ?? (group.interval === "month" ? "month" : "year"),
|
|
4682
4804
|
email: String(app.email ?? ""),
|
|
4805
|
+
emailNormalized: normalizeChapterEmail(String(app.email ?? "")),
|
|
4683
4806
|
name: `${app.firstName ?? ""} ${app.lastName ?? ""}`.trim(),
|
|
4684
4807
|
priceId: String(priceId),
|
|
4685
|
-
existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0
|
|
4808
|
+
existingCustomerId: typeof app.stripeCustomerId === "string" ? app.stripeCustomerId : void 0,
|
|
4809
|
+
now: Date.now()
|
|
4686
4810
|
});
|
|
4687
4811
|
} catch (err) {
|
|
4688
|
-
|
|
4812
|
+
const code = paymentErrorCode(err);
|
|
4813
|
+
return json({ error: "payment setup failed", code }, code === "checkout_intent_mismatch" ? 409 : 502);
|
|
4689
4814
|
}
|
|
4690
|
-
|
|
4691
|
-
{
|
|
4692
|
-
t: "update",
|
|
4693
|
-
ns: "applications",
|
|
4694
|
-
id: applicationId,
|
|
4695
|
-
attrs: {
|
|
4696
|
-
stripeCustomerId: result.customerId,
|
|
4697
|
-
stripeSubscriptionId: result.subscriptionId,
|
|
4698
|
-
stripeRuntime: stripeRuntimeIdentity(env.ODLA_RUNTIME),
|
|
4699
|
-
refundPolicyAckAt: Date.now()
|
|
4700
|
-
}
|
|
4701
|
-
}
|
|
4702
|
-
]);
|
|
4703
|
-
return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group) });
|
|
4815
|
+
return json({ clientSecret: result.clientSecret, publishableKey: group.stripePublishableKey ?? null, lineItems: paymentLineItems(group, tier) });
|
|
4704
4816
|
}
|
|
4705
4817
|
async function ingestWebhook(req, env, ctx) {
|
|
4706
4818
|
const db = ctx.makeDb(env);
|
|
@@ -4812,18 +4924,18 @@ var handlePayments = async (req, url, env, ctx) => {
|
|
|
4812
4924
|
};
|
|
4813
4925
|
|
|
4814
4926
|
// src/worker-routes-gifts.ts
|
|
4815
|
-
var
|
|
4927
|
+
var first4 = (value) => Array.isArray(value) ? value[0] : void 0;
|
|
4816
4928
|
var rows2 = (value) => Array.isArray(value) ? value : [];
|
|
4817
4929
|
var invalid = () => json({ error: "gift code is invalid or unavailable" }, 400);
|
|
4818
4930
|
var userFor = async (req, env, ctx) => {
|
|
4819
4931
|
const user = await ctx.verifyUser(req, env);
|
|
4820
4932
|
return user?.email ? user : null;
|
|
4821
4933
|
};
|
|
4822
|
-
var loadClaim = async (db, id) =>
|
|
4934
|
+
var loadClaim = async (db, id) => first4((await db.query({ giftClaims: { $: { where: { id }, limit: 1 } } })).giftClaims);
|
|
4823
4935
|
async function consumeRateLimit(db, chapterId, requester, now) {
|
|
4824
4936
|
const bucket = await giftRequesterBucket(chapterId, requester);
|
|
4825
4937
|
const id = `gift-rate:${chapterId}:${bucket}`;
|
|
4826
|
-
const current =
|
|
4938
|
+
const current = first4((await db.query({ giftRedemptionAttempts: { $: { where: { id }, limit: 1 } } })).giftRedemptionAttempts);
|
|
4827
4939
|
const within = Boolean(current && now - Number(current.windowStartedAt) < 15 * 6e4);
|
|
4828
4940
|
const attemptCount = within ? Number(current?.attemptCount ?? 0) + 1 : 1;
|
|
4829
4941
|
if (attemptCount > 10) return false;
|
|
@@ -4848,7 +4960,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4848
4960
|
purchaserEmailNormalized: normalizeChapterEmail(user.email),
|
|
4849
4961
|
chapterId: ctx.chapter.id
|
|
4850
4962
|
}, limit: 200 } } });
|
|
4851
|
-
const group =
|
|
4963
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4852
4964
|
return json({
|
|
4853
4965
|
offers: publicGiftOffers(control3?.document.giftOffers, Date.now()),
|
|
4854
4966
|
claims: buyerGiftClaims(rows2(found.giftClaims).sort((a, b) => Number(b.createdAt) - Number(a.createdAt)), Date.now()),
|
|
@@ -4870,7 +4982,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4870
4982
|
const control3 = await loadActiveSignupControl(db, ctx.chapter.id, env.ODLA_ENV);
|
|
4871
4983
|
const offer = findGiftOffer(control3?.document.giftOffers, typeof body.offerId === "string" ? body.offerId : "", Date.now());
|
|
4872
4984
|
if (!offer) return json({ error: "gift offer is unavailable" }, 409);
|
|
4873
|
-
const group =
|
|
4985
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4874
4986
|
const stripeSecret = await getVaultSecret(db, "stripe_secret_key");
|
|
4875
4987
|
if (!group || !stripeSecret || typeof group.stripePublishableKey !== "string") return json({ error: "payments not configured" }, 503);
|
|
4876
4988
|
const emailNormalized = normalizeChapterEmail(user.email);
|
|
@@ -4939,16 +5051,16 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4939
5051
|
return invalid();
|
|
4940
5052
|
}
|
|
4941
5053
|
if (!/^gift1_[A-Za-z0-9_-]{43}$/.test(code) || !applicationId) return invalid();
|
|
4942
|
-
const app =
|
|
5054
|
+
const app = first4((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
4943
5055
|
const emailNormalized = normalizeChapterEmail(user.email);
|
|
4944
5056
|
if (!app || String(app.groupId ?? "") !== ctx.chapter.id || String(app.emailNormalized ?? "") !== emailNormalized || typeof app.clerkUserId === "string" && app.clerkUserId !== user.userId) return invalid();
|
|
4945
5057
|
const codeHash = await hashGiftCode(ctx.chapter.id, code);
|
|
4946
|
-
const claim =
|
|
5058
|
+
const claim = first4((await db.query({ giftClaims: { $: { where: { codeHash }, limit: 1 } } })).giftClaims);
|
|
4947
5059
|
if (!claim || claim.chapterId !== ctx.chapter.id) return invalid();
|
|
4948
5060
|
const sameRedemption = (candidate) => candidate.status === "redeemed" && candidate.applicationId === applicationId && candidate.beneficiaryEmailNormalized === emailNormalized && candidate.beneficiaryUserId === user.userId;
|
|
4949
5061
|
const success = async () => {
|
|
4950
|
-
const current =
|
|
4951
|
-
const group2 =
|
|
5062
|
+
const current = first4((await db.query({ applications: { $: { where: { id: applicationId }, limit: 1 } } })).applications);
|
|
5063
|
+
const group2 = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4952
5064
|
const state = current && group2 ? await continueJoin(db, ctx, env, url.origin, current, group2) : null;
|
|
4953
5065
|
return json({
|
|
4954
5066
|
ok: true,
|
|
@@ -4967,7 +5079,7 @@ var handleGifts = async (req, url, env, ctx) => {
|
|
|
4967
5079
|
return invalid();
|
|
4968
5080
|
}
|
|
4969
5081
|
const target = claim.target;
|
|
4970
|
-
const group =
|
|
5082
|
+
const group = first4((await db.query({ groups: { $: { where: { id: ctx.chapter.id }, limit: 1 } } })).groups);
|
|
4971
5083
|
const tier = group ? findTier(await loadTiers(db, group), typeof app.tierId === "string" ? app.tierId : null) : void 0;
|
|
4972
5084
|
if (String(app.status ?? "") !== "submitted" || typeof app.stripeSubscriptionId === "string" || typeof app.giftClaimId === "string" || !tier || tierIsFree(tier) || target?.type !== "tier" || target.tierId !== app.tierId) return invalid();
|
|
4973
5085
|
const redeemed = await redeemGiftClaim(db, claim, {
|