@yiaany/ghostapi 0.1.7 → 0.1.8

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 (323) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/CONTRIBUTING.md +119 -117
  3. package/LICENSE +21 -21
  4. package/README.md +208 -185
  5. package/SECURITY.md +53 -37
  6. package/dist/actions/gateway.d.ts +179 -0
  7. package/dist/actions/gateway.js +514 -0
  8. package/dist/actions/gateway.js.map +1 -0
  9. package/dist/actions/index.d.ts +2 -0
  10. package/dist/actions/index.js +2 -0
  11. package/dist/actions/index.js.map +1 -0
  12. package/dist/ai/aiClient.js +4 -4
  13. package/dist/ai/aiClient.js.map +1 -1
  14. package/dist/ai/aiGenerator.d.ts +2 -2
  15. package/dist/ai/aiGenerator.js +21 -72
  16. package/dist/ai/aiGenerator.js.map +1 -1
  17. package/dist/ai/prompts.js +51 -51
  18. package/dist/ai/prompts.js.map +1 -1
  19. package/dist/approvals/approvalInbox.d.ts +150 -0
  20. package/dist/approvals/approvalInbox.js +412 -0
  21. package/dist/approvals/approvalInbox.js.map +1 -0
  22. package/dist/approvals/index.d.ts +2 -0
  23. package/dist/approvals/index.js +2 -0
  24. package/dist/approvals/index.js.map +1 -0
  25. package/dist/behavior/behaviorStore.d.ts +2 -0
  26. package/dist/behavior/behaviorStore.js +50 -46
  27. package/dist/behavior/behaviorStore.js.map +1 -1
  28. package/dist/cache/index.d.ts +2 -0
  29. package/dist/cache/index.js +58 -14
  30. package/dist/cache/index.js.map +1 -1
  31. package/dist/cli/index.js +438 -58
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/openUrl.d.ts +10 -0
  34. package/dist/cli/openUrl.js +31 -0
  35. package/dist/cli/openUrl.js.map +1 -0
  36. package/dist/cli/parser.d.ts +155 -0
  37. package/dist/cli/parser.js +504 -2
  38. package/dist/cli/parser.js.map +1 -1
  39. package/dist/config/dataPaths.d.ts +31 -0
  40. package/dist/config/dataPaths.js +45 -0
  41. package/dist/config/dataPaths.js.map +1 -0
  42. package/dist/config/localConfig.d.ts +1 -2
  43. package/dist/config/localConfig.js +27 -21
  44. package/dist/config/localConfig.js.map +1 -1
  45. package/dist/config/serverConfig.d.ts +2 -0
  46. package/dist/config/serverConfig.js +8 -1
  47. package/dist/config/serverConfig.js.map +1 -1
  48. package/dist/contracts/contract.d.ts +76 -0
  49. package/dist/contracts/contract.js +603 -0
  50. package/dist/contracts/contract.js.map +1 -0
  51. package/dist/contracts/index.d.ts +2 -0
  52. package/dist/contracts/index.js +2 -0
  53. package/dist/contracts/index.js.map +1 -0
  54. package/dist/credentials/credentialBroker.d.ts +207 -0
  55. package/dist/credentials/credentialBroker.js +588 -0
  56. package/dist/credentials/credentialBroker.js.map +1 -0
  57. package/dist/credentials/index.d.ts +2 -0
  58. package/dist/credentials/index.js +2 -0
  59. package/dist/credentials/index.js.map +1 -0
  60. package/dist/dashboard/app.js +685 -615
  61. package/dist/dashboard/index.html +13 -7
  62. package/dist/dashboard/styles.css +349 -349
  63. package/dist/egress/capabilities.d.ts +32 -0
  64. package/dist/egress/capabilities.js +165 -0
  65. package/dist/egress/capabilities.js.map +1 -0
  66. package/dist/egress/linuxBootstrap.d.ts +1 -0
  67. package/dist/egress/linuxBootstrap.js +121 -0
  68. package/dist/egress/linuxBootstrap.js.map +1 -0
  69. package/dist/egress/run.d.ts +18 -0
  70. package/dist/egress/run.js +305 -0
  71. package/dist/egress/run.js.map +1 -0
  72. package/dist/errors/errorEngine.d.ts +2 -2
  73. package/dist/errors/errorEngine.js +3 -40
  74. package/dist/errors/errorEngine.js.map +1 -1
  75. package/dist/evals/evals.d.ts +126 -0
  76. package/dist/evals/evals.js +466 -0
  77. package/dist/evals/evals.js.map +1 -0
  78. package/dist/evals/index.d.ts +2 -0
  79. package/dist/evals/index.js +2 -0
  80. package/dist/evals/index.js.map +1 -0
  81. package/dist/evidence/index.d.ts +2 -0
  82. package/dist/evidence/index.js +2 -0
  83. package/dist/evidence/index.js.map +1 -0
  84. package/dist/evidence/report.d.ts +143 -0
  85. package/dist/evidence/report.js +556 -0
  86. package/dist/evidence/report.js.map +1 -0
  87. package/dist/fault/faultLab.d.ts +5 -4
  88. package/dist/fault/faultLab.js +66 -24
  89. package/dist/fault/faultLab.js.map +1 -1
  90. package/dist/index.d.ts +46 -0
  91. package/dist/index.js +23 -0
  92. package/dist/index.js.map +1 -1
  93. package/dist/inventory/analysis.d.ts +12 -0
  94. package/dist/inventory/analysis.js +457 -0
  95. package/dist/inventory/analysis.js.map +1 -0
  96. package/dist/inventory/index.d.ts +5 -0
  97. package/dist/inventory/index.js +4 -0
  98. package/dist/inventory/index.js.map +1 -0
  99. package/dist/inventory/inventoryController.d.ts +53 -0
  100. package/dist/inventory/inventoryController.js +1044 -0
  101. package/dist/inventory/inventoryController.js.map +1 -0
  102. package/dist/inventory/types.d.ts +579 -0
  103. package/dist/inventory/types.js +787 -0
  104. package/dist/inventory/types.js.map +1 -0
  105. package/dist/landing/assets/index-CxWsopzP.css +1 -0
  106. package/dist/landing/assets/index-DZlpyhqd.js +55 -0
  107. package/dist/landing/assets/index-Dk-Xezs3.js +55 -0
  108. package/dist/landing/index.html +15 -15
  109. package/dist/ledger/actionLedger.d.ts +125 -0
  110. package/dist/ledger/actionLedger.js +594 -0
  111. package/dist/ledger/actionLedger.js.map +1 -0
  112. package/dist/ledger/index.d.ts +2 -0
  113. package/dist/ledger/index.js +2 -0
  114. package/dist/ledger/index.js.map +1 -0
  115. package/dist/mcp/server.js +4 -3
  116. package/dist/mcp/server.js.map +1 -1
  117. package/dist/policy/index.d.ts +3 -0
  118. package/dist/policy/index.js +2 -0
  119. package/dist/policy/index.js.map +1 -0
  120. package/dist/policy/policy.d.ts +14 -0
  121. package/dist/policy/policy.js +282 -0
  122. package/dist/policy/policy.js.map +1 -0
  123. package/dist/policy/types.d.ts +52 -0
  124. package/dist/policy/types.js +2 -0
  125. package/dist/policy/types.js.map +1 -0
  126. package/dist/productTelemetry/index.d.ts +16 -0
  127. package/dist/productTelemetry/index.js +142 -0
  128. package/dist/productTelemetry/index.js.map +1 -0
  129. package/dist/providers/conformance.d.ts +7 -0
  130. package/dist/providers/conformance.js +48 -0
  131. package/dist/providers/conformance.js.map +1 -0
  132. package/dist/providers/index.d.ts +4 -2
  133. package/dist/providers/index.js +3 -1
  134. package/dist/providers/index.js.map +1 -1
  135. package/dist/providers/packs/resendPack.d.ts +2 -0
  136. package/dist/providers/packs/resendPack.js +147 -0
  137. package/dist/providers/packs/resendPack.js.map +1 -0
  138. package/dist/providers/packs/stripePack.d.ts +2 -0
  139. package/dist/providers/packs/stripePack.js +852 -0
  140. package/dist/providers/packs/stripePack.js.map +1 -0
  141. package/dist/providers/registry.d.ts +7 -3
  142. package/dist/providers/registry.js +55 -4
  143. package/dist/providers/registry.js.map +1 -1
  144. package/dist/providers/resend.js +2 -11
  145. package/dist/providers/resend.js.map +1 -1
  146. package/dist/providers/runtime.d.ts +8 -0
  147. package/dist/providers/runtime.js +48 -0
  148. package/dist/providers/runtime.js.map +1 -0
  149. package/dist/providers/stripeWebhook.d.ts +13 -0
  150. package/dist/providers/stripeWebhook.js +50 -0
  151. package/dist/providers/stripeWebhook.js.map +1 -0
  152. package/dist/providers/types.d.ts +153 -0
  153. package/dist/proxy/cacheKey.js +3 -3
  154. package/dist/proxy/cacheKey.js.map +1 -1
  155. package/dist/proxy/providerDetector.d.ts +2 -7
  156. package/dist/proxy/providerDetector.js +4 -4
  157. package/dist/proxy/providerDetector.js.map +1 -1
  158. package/dist/proxy/proxyHandler.js +116 -12
  159. package/dist/proxy/proxyHandler.js.map +1 -1
  160. package/dist/proxy/requestNormalizer.d.ts +0 -1
  161. package/dist/proxy/requestNormalizer.js +2 -5
  162. package/dist/proxy/requestNormalizer.js.map +1 -1
  163. package/dist/reliability/costGovernance.d.ts +175 -0
  164. package/dist/reliability/costGovernance.js +515 -0
  165. package/dist/reliability/costGovernance.js.map +1 -0
  166. package/dist/reliability/index.d.ts +8 -0
  167. package/dist/reliability/index.js +5 -0
  168. package/dist/reliability/index.js.map +1 -0
  169. package/dist/reliability/reconciliation.d.ts +157 -0
  170. package/dist/reliability/reconciliation.js +522 -0
  171. package/dist/reliability/reconciliation.js.map +1 -0
  172. package/dist/reliability/runtimeHealth.d.ts +60 -0
  173. package/dist/reliability/runtimeHealth.js +356 -0
  174. package/dist/reliability/runtimeHealth.js.map +1 -0
  175. package/dist/reliability/slo.d.ts +131 -0
  176. package/dist/reliability/slo.js +375 -0
  177. package/dist/reliability/slo.js.map +1 -0
  178. package/dist/report/safetyReport.js +7 -3
  179. package/dist/report/safetyReport.js.map +1 -1
  180. package/dist/safety/index.d.ts +2 -0
  181. package/dist/safety/index.js +2 -0
  182. package/dist/safety/index.js.map +1 -0
  183. package/dist/safety/safetyController.d.ts +187 -0
  184. package/dist/safety/safetyController.js +477 -0
  185. package/dist/safety/safetyController.js.map +1 -0
  186. package/dist/scenarios/scenarioBundle.d.ts +75 -0
  187. package/dist/scenarios/scenarioBundle.js +750 -0
  188. package/dist/scenarios/scenarioBundle.js.map +1 -0
  189. package/dist/scenarios/scenarioStore.d.ts +1 -0
  190. package/dist/scenarios/scenarioStore.js +47 -32
  191. package/dist/scenarios/scenarioStore.js.map +1 -1
  192. package/dist/security/headerSanitizer.d.ts +2 -0
  193. package/dist/security/headerSanitizer.js +36 -1
  194. package/dist/security/headerSanitizer.js.map +1 -1
  195. package/dist/security/secrets.js +24 -4
  196. package/dist/security/secrets.js.map +1 -1
  197. package/dist/server/accessControl.d.ts +5 -0
  198. package/dist/server/accessControl.js +100 -0
  199. package/dist/server/accessControl.js.map +1 -0
  200. package/dist/server/createServer.d.ts +0 -5
  201. package/dist/server/createServer.js +9 -29
  202. package/dist/server/createServer.js.map +1 -1
  203. package/dist/server/eventsStore.d.ts +6 -1
  204. package/dist/server/eventsStore.js +83 -15
  205. package/dist/server/eventsStore.js.map +1 -1
  206. package/dist/server/routes.js +36 -10
  207. package/dist/server/routes.js.map +1 -1
  208. package/dist/server/sse.d.ts +2 -1
  209. package/dist/server/sse.js +15 -3
  210. package/dist/server/sse.js.map +1 -1
  211. package/dist/setup/setupGenerator.js +53 -6
  212. package/dist/setup/setupGenerator.js.map +1 -1
  213. package/dist/state/stateStore.d.ts +5 -0
  214. package/dist/state/stateStore.js +48 -62
  215. package/dist/state/stateStore.js.map +1 -1
  216. package/dist/storage/fileStore.d.ts +10 -0
  217. package/dist/storage/fileStore.js +131 -0
  218. package/dist/storage/fileStore.js.map +1 -0
  219. package/dist/teamControl/controlPlane.d.ts +255 -0
  220. package/dist/teamControl/controlPlane.js +918 -0
  221. package/dist/teamControl/controlPlane.js.map +1 -0
  222. package/dist/teamControl/deployment.d.ts +33 -0
  223. package/dist/teamControl/deployment.js +67 -0
  224. package/dist/teamControl/deployment.js.map +1 -0
  225. package/dist/teamControl/index.d.ts +4 -0
  226. package/dist/teamControl/index.js +3 -0
  227. package/dist/teamControl/index.js.map +1 -0
  228. package/dist/tests/testGenerator.js +4 -1
  229. package/dist/tests/testGenerator.js.map +1 -1
  230. package/dist/trust/index.d.ts +2 -0
  231. package/dist/trust/index.js +2 -0
  232. package/dist/trust/index.js.map +1 -0
  233. package/dist/trust/trustLadder.d.ts +182 -0
  234. package/dist/trust/trustLadder.js +560 -0
  235. package/dist/trust/trustLadder.js.map +1 -0
  236. package/dist/worlds/index.d.ts +2 -0
  237. package/dist/worlds/index.js +2 -0
  238. package/dist/worlds/index.js.map +1 -0
  239. package/dist/worlds/worlds.d.ts +160 -0
  240. package/dist/worlds/worlds.js +491 -0
  241. package/dist/worlds/worlds.js.map +1 -0
  242. package/docs/assets/dashboard.png +0 -0
  243. package/docs/ci.md +49 -0
  244. package/docs/commercial/README.md +29 -0
  245. package/docs/commercial/data-inventory.md +36 -0
  246. package/docs/commercial/manual-invoicing.md +44 -0
  247. package/docs/commercial/metrics.md +51 -0
  248. package/docs/commercial/pricing.md +103 -0
  249. package/docs/commercial/privacy-policy-draft.md +41 -0
  250. package/docs/commercial/terms-topics-for-counsel.md +51 -0
  251. package/docs/design-partners/README.md +52 -0
  252. package/docs/design-partners/demo-narratives.md +33 -0
  253. package/docs/design-partners/design-partner-offer.md +39 -0
  254. package/docs/design-partners/discovery-questions.md +28 -0
  255. package/docs/design-partners/feedback-capture-template.md +48 -0
  256. package/docs/design-partners/icp-one-pager.md +43 -0
  257. package/docs/design-partners/onboarding-checklist.md +33 -0
  258. package/docs/design-partners/pilot-success-criteria.md +27 -0
  259. package/docs/design-partners/pricing-interview-script.md +18 -0
  260. package/docs/design-partners/telemetry-plan.md +48 -0
  261. package/docs/development/baseline.md +114 -0
  262. package/docs/development/onboarding-smoke.md +37 -0
  263. package/docs/development/verification-0.1.8.md +25 -0
  264. package/docs/enterprise-product-roadmap-ru.md +1879 -0
  265. package/docs/fundraising/README.md +87 -0
  266. package/docs/fundraising/data-room-checklist.md +72 -0
  267. package/docs/fundraising/demo-script.md +52 -0
  268. package/docs/fundraising/design-partners-50.md +74 -0
  269. package/docs/fundraising/launch-posts.md +37 -0
  270. package/docs/fundraising/metrics-and-evidence.md +30 -0
  271. package/docs/fundraising/roadmap-12-month.md +25 -0
  272. package/docs/fundraising/technical-due-diligence-index.md +37 -0
  273. package/docs/fundraising/yc-application.md +25 -0
  274. package/docs/github-actions.md +84 -0
  275. package/docs/hosted-pilot.md +111 -0
  276. package/docs/mcp.md +52 -50
  277. package/docs/operations/disaster-recovery-runbook.md +81 -0
  278. package/docs/operations/kill-switch-runbook.md +32 -0
  279. package/docs/policy.md +61 -0
  280. package/docs/providers/authoring-packs.md +175 -0
  281. package/docs/providers/stripe-core-pack.md +82 -0
  282. package/docs/release-checklist.md +83 -71
  283. package/docs/release-migration-and-rollback.md +63 -0
  284. package/docs/release-readiness.md +39 -0
  285. package/docs/security/action-gateway-threat-model.md +34 -0
  286. package/docs/security/action-ledger-incident-replay-threat-model.md +42 -0
  287. package/docs/security/approval-inbox-threat-model.md +35 -0
  288. package/docs/security/credential-broker-threat-model.md +52 -0
  289. package/docs/security/egress-threat-model.md +126 -0
  290. package/docs/security/inventory-threat-model.md +104 -0
  291. package/docs/security/kill-switch-budgets-threat-model.md +37 -0
  292. package/docs/security/reliability-threat-model.md +111 -0
  293. package/docs/security/trust-ladder-threat-model.md +33 -0
  294. package/docs/team-control-plane.md +72 -0
  295. package/docs/usage.md +571 -57
  296. package/examples/README.md +10 -0
  297. package/examples/agent-instructions/README.md +39 -39
  298. package/examples/ci-smoke/README.md +21 -0
  299. package/examples/ci-smoke/ghostapi.policy.yaml +22 -0
  300. package/examples/ci-smoke/package.json +9 -0
  301. package/examples/ci-smoke/production-egress.mjs +2 -0
  302. package/examples/ci-smoke/safe.mjs +17 -0
  303. package/examples/evals/README.md +24 -0
  304. package/examples/evals/retry-after.eval.json +52 -0
  305. package/examples/generic-rest/README.md +27 -27
  306. package/examples/github-fetch/README.md +35 -35
  307. package/examples/openai-streaming/README.md +16 -0
  308. package/examples/openai-streaming/streaming-tool-call.mjs +31 -0
  309. package/examples/policy/ghostapi.policy.yaml +27 -0
  310. package/examples/record-replay/README.md +19 -0
  311. package/examples/record-replay/replay-requests.json +22 -0
  312. package/examples/record-replay/stripe-checkout.har.json +30 -0
  313. package/examples/resend-node/README.md +22 -22
  314. package/examples/stripe-node/README.md +35 -29
  315. package/examples/stripe-node/checkout-flow.mjs +61 -0
  316. package/examples/twilio-node/README.md +21 -21
  317. package/examples/worlds/README.md +19 -0
  318. package/examples/worlds/subscription-recovery.mjs +12 -0
  319. package/package.json +27 -12
  320. package/dist/providers/stripe.d.ts +0 -2
  321. package/dist/providers/stripe.js +0 -15
  322. package/dist/providers/stripe.js.map +0 -1
  323. package/docs/assets/ghostapi-avatar.png +0 -0
@@ -0,0 +1,852 @@
1
+ import { isJsonObject } from "../../utils/json.js";
2
+ const STRIPE_API_VERSION = "2026-02-25.clover";
3
+ const STRIPE_PACK_VERSION = "1.1.0";
4
+ const IDEMPOTENCY_TTL_SECONDS = 24 * 60 * 60;
5
+ const stripeScenarios = [
6
+ {
7
+ id: "stripe-payment-intent-card-declined",
8
+ title: "Stripe card declined",
9
+ provider: "stripe",
10
+ description: "A confirmed Payment Intent using pm_card_chargeDeclined returns a Stripe card_error.",
11
+ steps: [{
12
+ name: "Confirm declined payment intent",
13
+ method: "POST",
14
+ path: "/v1/payment_intents",
15
+ status: 402,
16
+ body: { error: { type: "card_error", code: "card_declined", decline_code: "generic_decline", message: "Your card was declined.", param: "payment_method" } }
17
+ }]
18
+ },
19
+ {
20
+ id: "stripe-subscription-payment-failed",
21
+ title: "Stripe subscription renewal payment failed",
22
+ provider: "stripe",
23
+ description: "A renewal with pm_card_chargeDeclined leaves the invoice open and subscription past_due.",
24
+ steps: [{
25
+ name: "Renew subscription with declined card",
26
+ method: "POST",
27
+ path: "/v1/subscriptions/sub_ghostapi/renew",
28
+ status: 402,
29
+ body: { error: { type: "card_error", code: "card_declined", decline_code: "generic_decline", message: "Your card was declined.", param: "payment_method" } }
30
+ }]
31
+ }
32
+ ];
33
+ const stripeConformanceFixtures = [
34
+ {
35
+ name: "create a form-encoded customer",
36
+ request: {
37
+ method: "POST",
38
+ path: "/v1/customers",
39
+ query: {},
40
+ headers: { "content-type": "application/x-www-form-urlencoded", "stripe-version": STRIPE_API_VERSION },
41
+ body: "email=ada%40example.com&name=Ada%20Lovelace&metadata%5Bsource%5D=fixture",
42
+ receivedAt: "2026-08-06T12:00:00.000Z"
43
+ },
44
+ assertResponse(response) {
45
+ if (!isJsonObject(response.body) || response.body.object !== "customer" || response.body.id !== "cus_fixture" || response.body.email !== "ada@example.com") {
46
+ return "response must contain a deterministic Stripe customer";
47
+ }
48
+ if (response.headers["request-id"] !== "req_fixture")
49
+ return "response must contain a deterministic request-id";
50
+ return null;
51
+ },
52
+ assertStateTransition(transition, response) {
53
+ if (transition === null || transition.key !== "stripe:cus_fixture" || transition.value !== response.body) {
54
+ return "state transition must persist the exact customer under its Stripe id";
55
+ }
56
+ return null;
57
+ }
58
+ },
59
+ {
60
+ name: "create a deterministic billing product",
61
+ request: {
62
+ method: "POST",
63
+ path: "/v1/products",
64
+ query: {},
65
+ headers: { "content-type": "application/json", "stripe-version": STRIPE_API_VERSION },
66
+ body: { name: "GhostAPI Pro", metadata: { tier: "pro" } },
67
+ receivedAt: "2026-08-06T12:00:00.000Z"
68
+ },
69
+ assertResponse(response) {
70
+ if (!isJsonObject(response.body) || response.body.object !== "product" || response.body.id !== "prod_fixture" || response.body.name !== "GhostAPI Pro") {
71
+ return "response must contain a deterministic Stripe billing product";
72
+ }
73
+ return null;
74
+ },
75
+ assertStateTransition(transition, response) {
76
+ if (transition === null || transition.key !== "stripe:prod_fixture" || transition.value !== response.body) {
77
+ return "state transition must persist the exact billing product under its Stripe id";
78
+ }
79
+ return null;
80
+ }
81
+ }
82
+ ];
83
+ export const stripePack = {
84
+ name: "stripe",
85
+ displayName: "Stripe",
86
+ manifest: {
87
+ schemaVersion: 1,
88
+ name: "stripe",
89
+ displayName: "Stripe",
90
+ implementation: "pack",
91
+ packVersion: STRIPE_PACK_VERSION,
92
+ apiVersions: { default: STRIPE_API_VERSION, supported: [STRIPE_API_VERSION] },
93
+ capabilities: {
94
+ detection: true,
95
+ requestParsing: true,
96
+ validation: true,
97
+ deterministicResponses: true,
98
+ stateTransitions: true,
99
+ providerErrors: true,
100
+ scenarios: true,
101
+ webhooks: true,
102
+ conformanceFixtures: true
103
+ }
104
+ },
105
+ detection: {
106
+ priority: 400,
107
+ matches: ({ path }) => /^\/v1\/(?:customers|products|prices|subscriptions|invoices|events|payment_intents|payment_methods|checkout\/sessions|refunds)(?:\/|$)/.test(path.toLowerCase())
108
+ },
109
+ parseRequest(request) {
110
+ return parseStripeRequest(request);
111
+ },
112
+ selectApiVersion(request) {
113
+ const requested = firstHeaderValue(request.headers["stripe-version"]) ?? STRIPE_API_VERSION;
114
+ if (requested === STRIPE_API_VERSION)
115
+ return { version: requested };
116
+ return {
117
+ error: {
118
+ status: 400,
119
+ type: "invalid_request_error",
120
+ code: "invalid_api_version",
121
+ param: "Stripe-Version",
122
+ message: `Unsupported Stripe API version: ${requested}. Supported version: ${STRIPE_API_VERSION}.`
123
+ }
124
+ };
125
+ },
126
+ validate(parsedRequest, request) {
127
+ const parsed = readStripeRequest(parsedRequest);
128
+ if (request.method === "POST" && request.path === "/v1/customers" && Object.keys(parsed.params).length === 0)
129
+ return missingParam("email");
130
+ if (request.method === "POST" && request.path === "/v1/payment_intents") {
131
+ if (parsed.params.amount === undefined)
132
+ return missingParam("amount");
133
+ if (!hasPositiveInteger(parsed.params.amount))
134
+ return invalidParamError("amount", "Invalid positive integer: amount.");
135
+ if (!hasCurrency(parsed.params.currency))
136
+ return missingParam("currency");
137
+ }
138
+ if (request.method === "POST" && request.path === "/v1/payment_methods" && typeof parsed.params.type !== "string")
139
+ return missingParam("type");
140
+ if (request.method === "POST" && request.path === "/v1/checkout/sessions") {
141
+ if (!hasMode(parsed.params.mode))
142
+ return missingParam("mode");
143
+ if (typeof parsed.params.success_url !== "string")
144
+ return missingParam("success_url");
145
+ if (typeof parsed.params.cancel_url !== "string")
146
+ return missingParam("cancel_url");
147
+ }
148
+ if (request.method === "POST" && request.path === "/v1/products" && typeof parsed.params.name !== "string")
149
+ return missingParam("name");
150
+ if (request.method === "POST" && request.path === "/v1/prices") {
151
+ if (!hasPositiveInteger(parsed.params.unit_amount))
152
+ return invalidParamError("unit_amount", "Invalid positive integer: unit_amount.");
153
+ if (!hasCurrency(parsed.params.currency))
154
+ return missingParam("currency");
155
+ }
156
+ if (request.method === "POST" && request.path === "/v1/subscriptions") {
157
+ if (typeof parsed.params.customer !== "string")
158
+ return missingParam("customer");
159
+ if (subscriptionPriceId(parsed.params.items) === undefined)
160
+ return missingParam("items[0][price]");
161
+ const trialDays = parsed.params.trial_period_days;
162
+ if (trialDays !== undefined && readNonNegativeInteger(trialDays) === undefined)
163
+ return invalidParamError("trial_period_days", "Invalid non-negative integer: trial_period_days.");
164
+ }
165
+ if (request.method === "POST" && request.path === "/v1/refunds" && typeof parsed.params.payment_intent !== "string" && typeof parsed.params.charge !== "string") {
166
+ return missingParam("payment_intent");
167
+ }
168
+ return null;
169
+ },
170
+ handleDeterministic({ request, parsedRequest, runtime }) {
171
+ const parsed = readStripeRequest(parsedRequest);
172
+ const now = Math.floor(runtime.requireCapability("clock").now().getTime() / 1000);
173
+ const snapshot = runtime.requireCapability("state").snapshot();
174
+ const replay = resolveIdempotencyReplay(snapshot, request, parsed, now);
175
+ if (replay !== null)
176
+ return replay;
177
+ const response = dispatchStripeRequest(request, parsed.params, snapshot, runtime, now);
178
+ return {
179
+ ...response,
180
+ headers: {
181
+ "content-type": "application/json",
182
+ ...createStripeResponseHeaders(runtime),
183
+ "stripe-version": STRIPE_API_VERSION,
184
+ ...response.headers
185
+ }
186
+ };
187
+ },
188
+ createResponseHeaders({ runtime }) {
189
+ return createStripeResponseHeaders(runtime);
190
+ },
191
+ transitionState({ request, response, runtime }) {
192
+ if (response.headers["x-ghostapi-idempotency"] === "REPLAY")
193
+ return null;
194
+ const resource = resourceFromResponse(response.body);
195
+ if (!isMutation(request.method))
196
+ return null;
197
+ const parsed = parseStripeRequest(request);
198
+ const now = Math.floor(runtime.requireCapability("clock").now().getTime() / 1000);
199
+ const additionalWrites = [...(response.stateWrites ?? []), ...(parsed.idempotencyKey === undefined || request.method !== "POST" ? [] : [{
200
+ key: `stripe:idempotency:${parsed.idempotencyKey}`,
201
+ value: {
202
+ method: request.method,
203
+ path: request.path,
204
+ params: stableStringify(parsed.params),
205
+ created: now,
206
+ response: { status: response.status, headers: response.headers, body: response.body }
207
+ }
208
+ }])];
209
+ if (resource === null) {
210
+ const [firstWrite, ...remainingWrites] = additionalWrites;
211
+ return firstWrite === undefined ? null : { key: firstWrite.key, value: firstWrite.value, additionalWrites: remainingWrites };
212
+ }
213
+ return { key: `stripe:${resource.id}`, value: resource, additionalWrites };
214
+ },
215
+ stateful: true,
216
+ formatError(details) {
217
+ return stripeError(details);
218
+ },
219
+ promptHints: [
220
+ "This is the Stripe API billing pack with deterministic Customers, Products, Prices, Subscriptions, Invoices, Payment Intents, Payment Methods, Checkout Sessions, Refunds, and pull-based signed webhook events.",
221
+ "Unsupported Stripe endpoints return a diagnostic invalid_request_error rather than a generated success."
222
+ ],
223
+ scenarios: stripeScenarios,
224
+ conformanceFixtures: stripeConformanceFixtures
225
+ };
226
+ function dispatchStripeRequest(request, params, state, runtime, now) {
227
+ const segments = request.path.split("/").filter(Boolean);
228
+ const resource = segments[1];
229
+ const id = segments[2];
230
+ const action = segments[3];
231
+ if (resource === "customers")
232
+ return handleCustomers(request.method, id, action, params, request.query, state, runtime, now);
233
+ if (resource === "products")
234
+ return handleProducts(request.method, id, action, params, request.query, state, runtime, now);
235
+ if (resource === "prices")
236
+ return handlePrices(request.method, id, action, params, request.query, state, runtime, now);
237
+ if (resource === "subscriptions")
238
+ return handleSubscriptions(request.method, id, action, params, request.query, state, runtime, now);
239
+ if (resource === "invoices")
240
+ return handleInvoices(request.method, id, action, params, request.query, state, runtime, now);
241
+ if (resource === "events")
242
+ return handleEvents(request.method, id, action, params, request.query, state);
243
+ if (resource === "payment_intents")
244
+ return handlePaymentIntents(request.method, id, action, params, request.query, state, runtime, now);
245
+ if (resource === "payment_methods")
246
+ return handlePaymentMethods(request.method, id, action, params, request.query, state, runtime, now);
247
+ if (resource === "checkout" && segments[2] === "sessions")
248
+ return handleCheckoutSessions(request.method, segments[3], segments[4], params, request.query, state, runtime, now);
249
+ if (resource === "refunds")
250
+ return handleRefunds(request.method, id, action, params, request.query, state, runtime, now);
251
+ return unsupportedEndpoint(request);
252
+ }
253
+ function handleProducts(method, id, action, params, query, state, runtime, now) {
254
+ if (action !== undefined)
255
+ return unsupportedEndpointFor("products", method);
256
+ if (method === "GET" && id === undefined)
257
+ return stripeList("/v1/products", listResources(state, "product"), query);
258
+ if (method === "POST" && id === undefined) {
259
+ return stripeObject({ id: runtime.requireCapability("idGenerator").create("prod"), object: "product", active: params.active !== false, name: stringOrNull(params.name) ?? "GhostAPI product", description: stringOrNull(params.description), metadata: recordOrEmpty(params.metadata), livemode: false, created: now, updated: now });
260
+ }
261
+ const product = id === undefined ? null : getResource(state, id, "product");
262
+ if (product === null)
263
+ return notFound("product", id);
264
+ if (method === "GET")
265
+ return stripeObject(product);
266
+ if (method === "POST")
267
+ return stripeObject({ ...product, ...pick(params, ["name", "description", "active", "metadata"]), updated: now, metadata: isJsonObject(params.metadata) ? params.metadata : product.metadata });
268
+ return unsupportedEndpointFor("products", method);
269
+ }
270
+ function handlePrices(method, id, action, params, query, state, runtime, now) {
271
+ if (action !== undefined)
272
+ return unsupportedEndpointFor("prices", method);
273
+ if (method === "GET" && id === undefined)
274
+ return stripeList("/v1/prices", listResources(state, "price"), query);
275
+ if (method === "POST" && id === undefined) {
276
+ const productId = typeof params.product === "string" ? params.product : undefined;
277
+ if (productId !== undefined && getResource(state, productId, "product") === null)
278
+ return notFound("product", productId);
279
+ if (!hasPositiveInteger(params.unit_amount))
280
+ return invalidParam("unit_amount", "Invalid positive integer: unit_amount.");
281
+ if (!hasCurrency(params.currency))
282
+ return missingParamResponse("currency");
283
+ const recurring = parseRecurring(params.recurring);
284
+ if (recurring.error !== null)
285
+ return stripeFailure(400, "invalid_request_error", recurring.error, "recurring[interval]", "parameter_invalid_enum");
286
+ return stripeObject({ id: runtime.requireCapability("idGenerator").create("price"), object: "price", active: params.active !== false, currency: String(params.currency).toLowerCase(), unit_amount: params.unit_amount, product: productId ?? null, nickname: stringOrNull(params.nickname), recurring: recurring.value, metadata: recordOrEmpty(params.metadata), livemode: false, created: now });
287
+ }
288
+ const price = id === undefined ? null : getResource(state, id, "price");
289
+ if (price === null)
290
+ return notFound("price", id);
291
+ if (method === "GET")
292
+ return stripeObject(price);
293
+ if (method === "POST")
294
+ return stripeObject({ ...price, ...pick(params, ["active", "nickname", "metadata"]), metadata: isJsonObject(params.metadata) ? params.metadata : price.metadata });
295
+ return unsupportedEndpointFor("prices", method);
296
+ }
297
+ function handleSubscriptions(method, id, action, params, query, state, runtime, now) {
298
+ if (method === "GET" && id === undefined)
299
+ return stripeList("/v1/subscriptions", listResources(state, "subscription"), query);
300
+ if (method === "POST" && id === undefined)
301
+ return createSubscription(params, state, runtime, now);
302
+ const subscription = id === undefined ? null : getResource(state, id, "subscription");
303
+ if (subscription === null)
304
+ return notFound("subscription", id);
305
+ if (method === "GET" && action === undefined)
306
+ return stripeObject(subscription);
307
+ if (method === "POST" && action === "cancel")
308
+ return updateSubscriptionStatus(subscription, "canceled", now, ["customer.subscription.deleted"], runtime);
309
+ if (method === "POST" && action === "renew")
310
+ return renewSubscription(subscription, params, state, runtime, now);
311
+ if (method === "POST" && action === undefined) {
312
+ if (params.cancel_at_period_end === true)
313
+ return responseWithWebhookWrites({ ...subscription, cancel_at_period_end: true, updated: now }, ["customer.subscription.updated"], runtime, now);
314
+ return responseWithWebhookWrites({ ...subscription, ...pick(params, ["metadata", "default_payment_method"]), metadata: isJsonObject(params.metadata) ? params.metadata : subscription.metadata, updated: now }, ["customer.subscription.updated"], runtime, now);
315
+ }
316
+ if (method === "DELETE" && action === undefined)
317
+ return updateSubscriptionStatus(subscription, "canceled", now, ["customer.subscription.deleted"], runtime);
318
+ return unsupportedEndpointFor("subscriptions", method);
319
+ }
320
+ function handleInvoices(method, id, action, params, query, state, runtime, now) {
321
+ if (method === "GET" && id === undefined)
322
+ return stripeList("/v1/invoices", listResources(state, "invoice"), query);
323
+ const invoice = id === undefined ? null : getResource(state, id, "invoice");
324
+ if (invoice === null)
325
+ return notFound("invoice", id);
326
+ if (method === "GET" && action === undefined)
327
+ return stripeObject(invoice);
328
+ if (method === "POST" && action === "pay")
329
+ return payInvoice(invoice, params, state, runtime, now);
330
+ if (method === "POST" && action === "void")
331
+ return responseWithWebhookWrites({ ...invoice, status: "void", paid: false, updated: now }, ["invoice.voided"], runtime, now);
332
+ return unsupportedEndpointFor("invoices", method);
333
+ }
334
+ function handleEvents(method, id, action, _params, query, state) {
335
+ if (method === "GET" && id === undefined)
336
+ return stripeList("/v1/events", orderedWebhookEvents(state, query), query);
337
+ if (method === "GET" && id !== undefined && (action === undefined || action === "deliver")) {
338
+ const event = getResource(state, id, "event");
339
+ if (event === null)
340
+ return notFound("event", id);
341
+ return stripeObject(event);
342
+ }
343
+ return unsupportedEndpointFor("events", method);
344
+ }
345
+ function createSubscription(params, state, runtime, now) {
346
+ const customerId = typeof params.customer === "string" ? params.customer : undefined;
347
+ if (customerId === undefined)
348
+ return missingParamResponse("customer");
349
+ if (getResource(state, customerId, "customer") === null)
350
+ return notFound("customer", customerId);
351
+ const priceId = subscriptionPriceId(params.items);
352
+ if (priceId === undefined)
353
+ return missingParamResponse("items[0][price]");
354
+ const price = getResource(state, priceId, "price");
355
+ if (price === null)
356
+ return notFound("price", priceId);
357
+ if (!isJsonObject(price.recurring))
358
+ return stripeFailure(400, "invalid_request_error", "Subscription prices require recurring configuration.", "items[0][price]", "parameter_invalid_empty");
359
+ const trialDays = readNonNegativeInteger(params.trial_period_days) ?? 0;
360
+ const id = runtime.requireCapability("idGenerator").create("sub");
361
+ const periodEnd = now + (trialDays > 0 ? trialDays * 86_400 : recurringSeconds(price.recurring));
362
+ const subscription = {
363
+ id,
364
+ object: "subscription",
365
+ customer: customerId,
366
+ status: trialDays > 0 ? "trialing" : "active",
367
+ cancel_at_period_end: false,
368
+ current_period_start: now,
369
+ current_period_end: periodEnd,
370
+ trial_start: trialDays > 0 ? now : null,
371
+ trial_end: trialDays > 0 ? periodEnd : null,
372
+ default_payment_method: stringOrNull(params.default_payment_method),
373
+ items: { object: "list", data: [{ id: runtime.requireCapability("idGenerator").create("si"), object: "subscription_item", price: priceId, quantity: subscriptionQuantity(params.items) }] },
374
+ latest_invoice: null,
375
+ metadata: recordOrEmpty(params.metadata),
376
+ livemode: false,
377
+ created: now,
378
+ updated: now
379
+ };
380
+ if (trialDays === 0) {
381
+ const invoice = createInvoice(subscription, price, runtime, now, "paid", "subscription_create");
382
+ const paymentIntent = createPaymentIntent({ amount: price.unit_amount, currency: price.currency, customer: customerId, payment_method: params.default_payment_method }, runtime, now, true);
383
+ const createdSubscription = { ...subscription, latest_invoice: invoice.id };
384
+ const response = responseWithWebhookWrites(createdSubscription, ["customer.subscription.created", "invoice.paid", "invoice.payment_succeeded"], runtime, now, { "invoice.paid": invoice, "invoice.payment_succeeded": invoice });
385
+ response.stateWrites = [...(response.stateWrites ?? []), { key: `stripe:${invoice.id}`, value: { ...invoice, payment_intent: paymentIntent.id } }, { key: `stripe:${paymentIntent.id}`, value: paymentIntent }];
386
+ return response;
387
+ }
388
+ return responseWithWebhookWrites(subscription, ["customer.subscription.created"], runtime, now);
389
+ }
390
+ function renewSubscription(subscription, params, state, runtime, now) {
391
+ if (subscription.status === "canceled")
392
+ return stripeFailure(400, "invalid_request_error", "Canceled subscriptions cannot be renewed.", "id", "resource_missing");
393
+ const priceId = subscriptionPriceId(subscription.items);
394
+ const price = priceId === undefined ? null : getResource(state, priceId, "price");
395
+ if (price === null)
396
+ return stripeFailure(500, "api_error", "Subscription has no valid recurring price.", undefined, "subscription_invalid");
397
+ const declined = isDeclined(params) || isDeclined({ payment_method: params.payment_method ?? subscription.default_payment_method });
398
+ const status = declined ? "open" : "paid";
399
+ const invoice = createInvoice(subscription, price, runtime, now, status, "subscription_cycle");
400
+ const nextStatus = declined ? "past_due" : "active";
401
+ const nextSubscription = {
402
+ ...subscription,
403
+ status: nextStatus,
404
+ current_period_start: now,
405
+ current_period_end: now + recurringSeconds(price.recurring),
406
+ latest_invoice: invoice.id,
407
+ updated: now
408
+ };
409
+ if (declined) {
410
+ const failure = cardDeclined();
411
+ return {
412
+ ...failure,
413
+ stateWrites: [
414
+ { key: `stripe:${invoice.id}`, value: invoice },
415
+ { key: `stripe:${subscription.id}`, value: nextSubscription },
416
+ ...createWebhookWrites(["invoice.payment_failed", "customer.subscription.updated"], nextSubscription, runtime, now)
417
+ ]
418
+ };
419
+ }
420
+ const paymentIntent = createPaymentIntent({ amount: price.unit_amount, currency: price.currency, customer: subscription.customer, payment_method: params.payment_method ?? subscription.default_payment_method }, runtime, now, true);
421
+ const response = responseWithWebhookWrites(nextSubscription, ["invoice.paid", "invoice.payment_succeeded", "customer.subscription.updated"], runtime, now, { "invoice.paid": invoice, "invoice.payment_succeeded": invoice });
422
+ response.stateWrites = [...(response.stateWrites ?? []), { key: `stripe:${invoice.id}`, value: { ...invoice, payment_intent: paymentIntent.id } }, { key: `stripe:${paymentIntent.id}`, value: paymentIntent }];
423
+ return response;
424
+ }
425
+ function payInvoice(invoice, params, state, runtime, now) {
426
+ if (invoice.status === "paid")
427
+ return stripeObject(invoice);
428
+ if (invoice.status === "void")
429
+ return stripeFailure(400, "invalid_request_error", "Void invoices cannot be paid.", "id", "invoice_invalid");
430
+ const subscription = typeof invoice.subscription === "string" ? getResource(state, invoice.subscription, "subscription") : null;
431
+ if (isDeclined(params) || isDeclined({ payment_method: params.payment_method ?? subscription?.default_payment_method })) {
432
+ const failedInvoice = { ...invoice, status: "open", paid: false, updated: now, attempt_count: numberOr(invoice.attempt_count, 0) + 1 };
433
+ const writes = [{ key: `stripe:${String(invoice.id)}`, value: failedInvoice }, ...(subscription === null ? [] : [{ key: `stripe:${subscription.id}`, value: { ...subscription, status: "past_due", updated: now } }]), ...createWebhookWrites(["invoice.payment_failed", ...(subscription === null ? [] : ["customer.subscription.updated"])], failedInvoice, runtime, now)];
434
+ return { ...cardDeclined(), stateWrites: writes };
435
+ }
436
+ const paidInvoice = { ...invoice, status: "paid", paid: true, amount_paid: invoice.amount_due, amount_remaining: 0, updated: now, attempt_count: numberOr(invoice.attempt_count, 0) + 1 };
437
+ const response = responseWithWebhookWrites(paidInvoice, ["invoice.paid", "invoice.payment_succeeded"], runtime, now);
438
+ if (subscription !== null)
439
+ response.stateWrites = [...(response.stateWrites ?? []), { key: `stripe:${subscription.id}`, value: { ...subscription, status: "active", latest_invoice: invoice.id, updated: now } }];
440
+ return response;
441
+ }
442
+ function updateSubscriptionStatus(subscription, status, now, eventTypes, runtime) {
443
+ return responseWithWebhookWrites({ ...subscription, status, canceled_at: status === "canceled" ? now : null, cancel_at_period_end: false, updated: now }, eventTypes, runtime, now);
444
+ }
445
+ function createInvoice(subscription, price, runtime, now, status, billingReason) {
446
+ const id = runtime.requireCapability("idGenerator").create("in");
447
+ const amount = numberOr(price.unit_amount, 0);
448
+ return { id, object: "invoice", customer: subscription.customer, subscription: subscription.id, status, paid: status === "paid", amount_due: amount, amount_paid: status === "paid" ? amount : 0, amount_remaining: status === "paid" ? 0 : amount, currency: price.currency, billing_reason: billingReason, payment_intent: null, attempt_count: 1, metadata: {}, livemode: false, created: now, updated: now };
449
+ }
450
+ function responseWithWebhookWrites(resource, eventTypes, runtime, now, eventObjects = {}) {
451
+ return { status: 200, headers: {}, body: resource, stateWrites: createWebhookWrites(eventTypes, resource, runtime, now, eventObjects) };
452
+ }
453
+ function createWebhookWrites(eventTypes, dataObject, runtime, now, eventObjects = {}) {
454
+ return eventTypes.map((type) => {
455
+ const id = runtime.requireCapability("idGenerator").create("evt");
456
+ return { key: `stripe:${id}`, value: { id, object: "event", api_version: STRIPE_API_VERSION, created: now, livemode: false, pending_webhooks: 1, request: { id: null, idempotency_key: null }, type, data: { object: structuredClone(eventObjects[type] ?? dataObject) } } };
457
+ });
458
+ }
459
+ function createStripeResponseHeaders(runtime) {
460
+ return { "request-id": runtime.requireCapability("idGenerator").create("req") };
461
+ }
462
+ function handleCustomers(method, id, action, params, query, state, runtime, now) {
463
+ if (action !== undefined)
464
+ return unsupportedEndpointFor("customers", method);
465
+ if (method === "GET" && id === undefined)
466
+ return stripeList("/v1/customers", listResources(state, "customer"), query);
467
+ if (method === "POST" && id === undefined)
468
+ return stripeObject({ id: runtime.requireCapability("idGenerator").create("cus"), object: "customer", email: stringOrNull(params.email), name: stringOrNull(params.name), description: stringOrNull(params.description), metadata: recordOrEmpty(params.metadata), livemode: false, created: now });
469
+ const customer = id === undefined ? null : getResource(state, id, "customer");
470
+ if (customer === null)
471
+ return notFound("customer", id);
472
+ if (method === "GET")
473
+ return stripeObject(customer);
474
+ if (method === "POST")
475
+ return stripeObject({ ...customer, ...pick(params, ["email", "name", "description", "metadata"]), metadata: isJsonObject(params.metadata) ? params.metadata : customer.metadata });
476
+ if (method === "DELETE")
477
+ return stripeObject({ id, object: "customer", deleted: true });
478
+ return unsupportedEndpointFor("customers", method);
479
+ }
480
+ function handlePaymentMethods(method, id, action, params, query, state, runtime, now) {
481
+ if (action !== undefined)
482
+ return unsupportedEndpointFor("payment_methods", method);
483
+ if (method === "GET" && id === undefined)
484
+ return stripeList("/v1/payment_methods", listResources(state, "payment_method"), query);
485
+ if (method === "POST" && id === undefined) {
486
+ const type = String(params.type);
487
+ if (type !== "card")
488
+ return stripeFailure(400, "invalid_request_error", "Only payment method type 'card' is supported by this GhostAPI Stripe pack.", "type", "parameter_invalid_enum");
489
+ return stripeObject({ id: runtime.requireCapability("idGenerator").create("pm"), object: "payment_method", type, card: { brand: "visa", country: "US", exp_month: 12, exp_year: 2030, funding: "credit", last4: "4242" }, billing_details: recordOrEmpty(params.billing_details), customer: null, livemode: false, created: now });
490
+ }
491
+ const paymentMethod = id === undefined ? null : getResource(state, id, "payment_method");
492
+ if (paymentMethod === null)
493
+ return notFound("payment_method", id);
494
+ if (method === "GET")
495
+ return stripeObject(paymentMethod);
496
+ return unsupportedEndpointFor("payment_methods", method);
497
+ }
498
+ function handlePaymentIntents(method, id, action, params, query, state, runtime, now) {
499
+ if (method === "GET" && id === undefined)
500
+ return stripeList("/v1/payment_intents", listResources(state, "payment_intent"), query);
501
+ if (method === "POST" && id === undefined) {
502
+ if (isDeclined(params))
503
+ return cardDeclined();
504
+ const paymentIntent = createPaymentIntent(params, runtime, now, Boolean(params.confirm));
505
+ return params.confirm === true ? responseWithWebhookWrites(paymentIntent, ["payment_intent.succeeded"], runtime, now) : stripeObject(paymentIntent);
506
+ }
507
+ const paymentIntent = id === undefined ? null : getResource(state, id, "payment_intent");
508
+ if (paymentIntent === null)
509
+ return notFound("payment_intent", id);
510
+ if (method === "GET" && action === undefined)
511
+ return stripeObject(paymentIntent);
512
+ if (method === "POST" && action === "confirm") {
513
+ if (isDeclined(params))
514
+ return cardDeclined();
515
+ return responseWithWebhookWrites({ ...paymentIntent, ...pick(params, ["payment_method", "metadata"]), status: "succeeded", amount_received: paymentIntent.amount, latest_charge: `ch_${id}`, next_action: null }, ["payment_intent.succeeded"], runtime, now);
516
+ }
517
+ if (method === "POST" && action === undefined)
518
+ return stripeObject({ ...paymentIntent, ...pick(params, ["amount", "currency", "description", "metadata", "payment_method"]), amount: numberOr(paymentIntent.amount, params.amount), status: paymentIntent.status });
519
+ return unsupportedEndpointFor("payment_intents", method);
520
+ }
521
+ function handleCheckoutSessions(method, id, action, params, query, state, runtime, now) {
522
+ if (action !== undefined)
523
+ return unsupportedEndpointFor("checkout/sessions", method);
524
+ if (method === "GET" && id === undefined)
525
+ return stripeList("/v1/checkout/sessions", listResources(state, "checkout.session"), query);
526
+ if (method === "POST" && id === undefined) {
527
+ const sessionId = runtime.requireCapability("idGenerator").create("cs_test");
528
+ return stripeObject({ id: sessionId, object: "checkout.session", mode: params.mode, status: "open", payment_status: "unpaid", customer: stringOrNull(params.customer), success_url: params.success_url, cancel_url: params.cancel_url, url: `http://127.0.0.1:8080/v1/checkout/sessions/${sessionId}/mock-hosted-page`, amount_total: checkoutAmount(params), currency: checkoutCurrency(params), livemode: false, created: now, metadata: recordOrEmpty(params.metadata) });
529
+ }
530
+ const session = id === undefined ? null : getResource(state, id, "checkout.session");
531
+ if (session === null)
532
+ return notFound("checkout.session", id);
533
+ if (method === "GET")
534
+ return stripeObject(session);
535
+ return unsupportedEndpointFor("checkout/sessions", method);
536
+ }
537
+ function handleRefunds(method, id, action, params, query, state, runtime, now) {
538
+ if (action !== undefined)
539
+ return unsupportedEndpointFor("refunds", method);
540
+ if (method === "GET" && id === undefined)
541
+ return stripeList("/v1/refunds", listResources(state, "refund"), query);
542
+ if (method === "POST" && id === undefined) {
543
+ const paymentIntentId = typeof params.payment_intent === "string" ? params.payment_intent : undefined;
544
+ const paymentIntent = paymentIntentId === undefined ? null : getResource(state, paymentIntentId, "payment_intent");
545
+ if (paymentIntentId !== undefined && paymentIntent === null)
546
+ return notFound("payment_intent", paymentIntentId);
547
+ const amount = typeof params.amount === "number" ? params.amount : typeof paymentIntent?.amount === "number" ? paymentIntent.amount : null;
548
+ if (amount === null || !Number.isInteger(amount) || amount <= 0)
549
+ return invalidParam("amount", "Invalid positive integer: amount.");
550
+ const refund = { id: runtime.requireCapability("idGenerator").create("re"), object: "refund", amount, currency: typeof paymentIntent?.currency === "string" ? paymentIntent.currency : "usd", payment_intent: paymentIntentId ?? null, charge: typeof params.charge === "string" ? params.charge : null, status: "succeeded", reason: stringOrNull(params.reason), metadata: recordOrEmpty(params.metadata), livemode: false, created: now };
551
+ const response = responseWithWebhookWrites(refund, ["charge.refunded"], runtime, now);
552
+ if (paymentIntent !== null)
553
+ response.stateWrites = [...(response.stateWrites ?? []), { key: `stripe:${paymentIntent.id}`, value: { ...paymentIntent, amount_refunded: numberOr(paymentIntent.amount_refunded, 0) + amount, status: "succeeded" } }];
554
+ return response;
555
+ }
556
+ const refund = id === undefined ? null : getResource(state, id, "refund");
557
+ if (refund === null)
558
+ return notFound("refund", id);
559
+ if (method === "GET")
560
+ return stripeObject(refund);
561
+ return unsupportedEndpointFor("refunds", method);
562
+ }
563
+ function createPaymentIntent(params, runtime, now, confirmed) {
564
+ const id = runtime.requireCapability("idGenerator").create("pi");
565
+ return { id, object: "payment_intent", amount: params.amount, amount_received: confirmed ? params.amount : 0, currency: params.currency, customer: stringOrNull(params.customer), description: stringOrNull(params.description), metadata: recordOrEmpty(params.metadata), payment_method: stringOrNull(params.payment_method), client_secret: `${id}_secret_ghostapi`, status: confirmed ? "succeeded" : "requires_payment_method", confirmation_method: "automatic", capture_method: "automatic", latest_charge: confirmed ? `ch_${id}` : null, livemode: false, created: now };
566
+ }
567
+ function resolveIdempotencyReplay(state, request, parsed, now) {
568
+ if (parsed.idempotencyKey === undefined || !isMutation(request.method))
569
+ return null;
570
+ const stored = state[`stripe:idempotency:${parsed.idempotencyKey}`];
571
+ if (!isStoredIdempotency(stored))
572
+ return null;
573
+ if (stored.created + IDEMPOTENCY_TTL_SECONDS <= now)
574
+ return null;
575
+ if (stored.method !== request.method || stored.path !== request.path || stored.params !== stableStringify(parsed.params)) {
576
+ return stripeFailure(400, "idempotency_error", "Keys for idempotent requests can only be used with the same parameters they were first used with.", undefined, "idempotency_key_in_use");
577
+ }
578
+ return {
579
+ ...stored.response,
580
+ headers: { ...stored.response.headers, "x-ghostapi-idempotency": "REPLAY" },
581
+ body: restoreSyntheticClientSecret(stored.response.body)
582
+ };
583
+ }
584
+ function parseStripeRequest(request) {
585
+ const contentType = firstHeaderValue(request.headers["content-type"]) ?? "";
586
+ return {
587
+ params: parseStripeParams(request.body, contentType.includes("application/x-www-form-urlencoded")),
588
+ idempotencyKey: firstHeaderValue(request.headers["idempotency-key"])
589
+ };
590
+ }
591
+ function parseStripeParams(body, formEncoded) {
592
+ if (isJsonObject(body))
593
+ return formEncoded ? coerceFormParams(body) : structuredClone(body);
594
+ if (typeof body !== "string")
595
+ return {};
596
+ const params = {};
597
+ for (const [key, value] of new URLSearchParams(body))
598
+ setBracketValue(params, key, value);
599
+ return params;
600
+ }
601
+ function coerceFormParams(value) {
602
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, coerceFormParameter(entry)]));
603
+ }
604
+ function coerceFormParameter(value) {
605
+ if (Array.isArray(value))
606
+ return value.map(coerceFormParameter);
607
+ if (isJsonObject(value))
608
+ return coerceFormParams(value);
609
+ return typeof value === "string" ? coerceFormValue(value) : value;
610
+ }
611
+ function setBracketValue(target, key, value) {
612
+ const parts = key.match(/[^\[\]]+/g) ?? [];
613
+ if (parts.length === 0)
614
+ return;
615
+ let cursor = target;
616
+ for (const [index, part] of parts.entries()) {
617
+ const nextPart = parts[index + 1];
618
+ const numericPart = /^\d+$/.test(part) ? Number(part) : undefined;
619
+ if (index === parts.length - 1) {
620
+ if (Array.isArray(cursor) && numericPart !== undefined)
621
+ cursor[numericPart] = coerceFormValue(value);
622
+ else if (!Array.isArray(cursor))
623
+ cursor[part] = coerceFormValue(value);
624
+ return;
625
+ }
626
+ const createArray = nextPart !== undefined && /^\d+$/.test(nextPart);
627
+ if (Array.isArray(cursor) && numericPart !== undefined) {
628
+ const existing = cursor[numericPart];
629
+ if (!isJsonObject(existing) && !Array.isArray(existing))
630
+ cursor[numericPart] = createArray ? [] : {};
631
+ cursor = cursor[numericPart];
632
+ continue;
633
+ }
634
+ if (Array.isArray(cursor))
635
+ return;
636
+ const existing = cursor[part];
637
+ if (!isJsonObject(existing) && !Array.isArray(existing))
638
+ cursor[part] = createArray ? [] : {};
639
+ cursor = cursor[part];
640
+ }
641
+ }
642
+ function coerceFormValue(value) {
643
+ if (value === "true")
644
+ return true;
645
+ if (value === "false")
646
+ return false;
647
+ if (/^-?\d+$/.test(value))
648
+ return Number(value);
649
+ return value;
650
+ }
651
+ function stripeList(url, values, query) {
652
+ const limit = Math.min(Math.max(readPositiveInteger(query.limit) ?? 10, 1), 100);
653
+ const startingAfter = typeof query.starting_after === "string" ? query.starting_after : undefined;
654
+ const endingBefore = typeof query.ending_before === "string" ? query.ending_before : undefined;
655
+ let start = 0;
656
+ if (startingAfter !== undefined) {
657
+ const index = values.findIndex((value) => value.id === startingAfter);
658
+ if (index >= 0)
659
+ start = index + 1;
660
+ }
661
+ if (endingBefore !== undefined) {
662
+ const index = values.findIndex((value) => value.id === endingBefore);
663
+ if (index >= 0)
664
+ start = Math.max(0, index - limit);
665
+ }
666
+ const data = values.slice(start, start + limit);
667
+ return stripeObject({ object: "list", data, has_more: start + limit < values.length, url });
668
+ }
669
+ function stripeObject(body) {
670
+ return { status: 200, headers: {}, body };
671
+ }
672
+ function stripeFailure(status, type, message, param, code) {
673
+ return { status, headers: {}, body: stripeError({ status, type, message, param, code }) };
674
+ }
675
+ function stripeError(details) {
676
+ return { error: { type: details.type ?? "invalid_request_error", message: details.message, ...(details.code === undefined ? {} : { code: details.code }), param: details.param ?? "unknown" } };
677
+ }
678
+ function cardDeclined() {
679
+ return { status: 402, headers: {}, body: { error: { type: "card_error", code: "card_declined", decline_code: "generic_decline", message: "Your card was declined.", param: "payment_method" } } };
680
+ }
681
+ function unsupportedEndpoint(request) {
682
+ return stripeFailure(404, "invalid_request_error", `Unsupported Stripe endpoint: ${request.method} ${request.path}. This GhostAPI Stripe pack supports Customers, Payment Intents, Payment Methods, Checkout Sessions, and Refunds.`, undefined, "resource_missing");
683
+ }
684
+ function unsupportedEndpointFor(resource, method) {
685
+ return stripeFailure(405, "invalid_request_error", `Unsupported Stripe operation: ${method} /v1/${resource}.`, undefined, "method_not_allowed");
686
+ }
687
+ function notFound(resource, id) {
688
+ return stripeFailure(404, "invalid_request_error", `No such ${resource}: '${id ?? "unknown"}'`, "id", "resource_missing");
689
+ }
690
+ function missingParam(param) {
691
+ return { status: 400, type: "invalid_request_error", code: "parameter_missing", param, message: `Missing required param: ${param}.` };
692
+ }
693
+ function invalidParam(param, message) {
694
+ return stripeFailure(400, "invalid_request_error", message, param, "parameter_invalid_integer");
695
+ }
696
+ function invalidParamError(param, message) {
697
+ return { status: 400, type: "invalid_request_error", code: "parameter_invalid_integer", param, message };
698
+ }
699
+ function getResource(state, id, object) {
700
+ const value = state[`stripe:${id}`];
701
+ if (!isJsonObject(value) || value.object !== object || value.deleted === true)
702
+ return null;
703
+ const resource = structuredClone(value);
704
+ if (resource.object === "payment_intent" && resource.client_secret === "***")
705
+ resource.client_secret = `${resource.id}_secret_ghostapi`;
706
+ return resource;
707
+ }
708
+ function listResources(state, object) {
709
+ return Object.values(state)
710
+ .filter((value) => isJsonObject(value) && value.object === object && value.deleted !== true)
711
+ .sort((left, right) => Number(right.created ?? 0) - Number(left.created ?? 0) || String(right.id).localeCompare(String(left.id)))
712
+ .map((value) => structuredClone(value));
713
+ }
714
+ function resourceFromResponse(body) {
715
+ return isJsonObject(body) && typeof body.id === "string" && typeof body.object === "string" ? body : null;
716
+ }
717
+ function restoreSyntheticClientSecret(body) {
718
+ if (!isJsonObject(body) || body.object !== "payment_intent" || typeof body.id !== "string" || body.client_secret !== "***")
719
+ return body;
720
+ return { ...body, client_secret: `${body.id}_secret_ghostapi` };
721
+ }
722
+ function readStripeRequest(value) {
723
+ if (!isJsonObject(value) || !isJsonObject(value.params))
724
+ return { params: {}, idempotencyKey: undefined };
725
+ return { params: value.params, idempotencyKey: typeof value.idempotencyKey === "string" ? value.idempotencyKey : undefined };
726
+ }
727
+ function isStoredIdempotency(value) {
728
+ return isJsonObject(value) && typeof value.method === "string" && typeof value.path === "string" && typeof value.params === "string" && typeof value.created === "number" && isJsonObject(value.response) && typeof value.response.status === "number" && isJsonObject(value.response.headers) && "body" in value.response;
729
+ }
730
+ function hasPositiveInteger(value) {
731
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
732
+ }
733
+ function hasCurrency(value) {
734
+ return typeof value === "string" && /^[a-z]{3}$/i.test(value);
735
+ }
736
+ function hasMode(value) {
737
+ return value === "payment" || value === "setup" || value === "subscription";
738
+ }
739
+ function parseRecurring(value) {
740
+ if (value === undefined)
741
+ return { value: null, error: null };
742
+ if (!isJsonObject(value) || typeof value.interval !== "string")
743
+ return { value: null, error: "Recurring prices require recurring[interval] to be day, week, month, or year." };
744
+ if (!isRecurringInterval(value.interval))
745
+ return { value: null, error: "Recurring prices require recurring[interval] to be day, week, month, or year." };
746
+ const intervalCount = readPositiveInteger(value.interval_count) ?? 1;
747
+ return { value: { interval: value.interval, interval_count: intervalCount }, error: null };
748
+ }
749
+ function recurringSeconds(value) {
750
+ if (!isJsonObject(value) || typeof value.interval !== "string")
751
+ return 30 * 86_400;
752
+ const count = readPositiveInteger(value.interval_count) ?? 1;
753
+ const unit = value.interval === "day" ? 86_400 : value.interval === "week" ? 7 * 86_400 : value.interval === "year" ? 365 * 86_400 : 30 * 86_400;
754
+ return unit * count;
755
+ }
756
+ function subscriptionPriceId(items) {
757
+ const first = firstSubscriptionItem(items);
758
+ return isJsonObject(first) && typeof first.price === "string" ? first.price : undefined;
759
+ }
760
+ function subscriptionQuantity(items) {
761
+ const first = firstSubscriptionItem(items);
762
+ return isJsonObject(first) ? readPositiveInteger(first.quantity) ?? 1 : 1;
763
+ }
764
+ function firstSubscriptionItem(items) {
765
+ if (Array.isArray(items))
766
+ return items[0];
767
+ if (!isJsonObject(items))
768
+ return undefined;
769
+ if (Array.isArray(items.data))
770
+ return items.data[0];
771
+ return Object.values(items)[0];
772
+ }
773
+ function orderedWebhookEvents(state, query) {
774
+ const events = listResources(state, "event");
775
+ return query.delivery_mode === "out_of_order" ? events.reverse() : events;
776
+ }
777
+ function missingParamResponse(param) {
778
+ const details = missingParam(param);
779
+ return stripeFailure(details.status, details.type ?? "invalid_request_error", details.message, details.param, String(details.code));
780
+ }
781
+ function isRecurringInterval(value) {
782
+ return value === "day" || value === "week" || value === "month" || value === "year";
783
+ }
784
+ function isDeclined(params) {
785
+ return params.payment_method === "pm_card_chargeDeclined" || params.payment_method === "tok_chargeDeclined";
786
+ }
787
+ function isMutation(method) {
788
+ return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE";
789
+ }
790
+ function firstHeaderValue(value) {
791
+ return Array.isArray(value) ? value[0] : value;
792
+ }
793
+ function stringOrNull(value) {
794
+ return typeof value === "string" ? value : null;
795
+ }
796
+ function recordOrEmpty(value) {
797
+ return isJsonObject(value) ? structuredClone(value) : {};
798
+ }
799
+ function pick(value, keys) {
800
+ return Object.fromEntries(keys.filter((key) => value[key] !== undefined).map((key) => [key, structuredClone(value[key])]));
801
+ }
802
+ function numberOr(current, next) {
803
+ return typeof next === "number" ? next : typeof current === "number" ? current : 0;
804
+ }
805
+ function checkoutAmount(params) {
806
+ return checkoutLineItems(params).reduce((total, item) => {
807
+ if (total === null || !isJsonObject(item) || !isJsonObject(item.price_data) || typeof item.price_data.unit_amount !== "number")
808
+ return null;
809
+ const quantity = typeof item.quantity === "number" && item.quantity > 0 ? item.quantity : 1;
810
+ return total + item.price_data.unit_amount * quantity;
811
+ }, 0);
812
+ }
813
+ function checkoutCurrency(params) {
814
+ const first = checkoutLineItems(params)[0];
815
+ return isJsonObject(first) && isJsonObject(first.price_data) && typeof first.price_data.currency === "string" ? first.price_data.currency : null;
816
+ }
817
+ function checkoutLineItems(params) {
818
+ const lineItems = params.line_items;
819
+ if (Array.isArray(lineItems))
820
+ return lineItems;
821
+ if (isJsonObject(lineItems))
822
+ return Object.values(lineItems);
823
+ return [];
824
+ }
825
+ function readPositiveInteger(value) {
826
+ if (typeof value === "number" && Number.isInteger(value) && value > 0)
827
+ return value;
828
+ if (typeof value === "string" && /^\d+$/.test(value)) {
829
+ const parsed = Number(value);
830
+ if (Number.isSafeInteger(parsed) && parsed > 0)
831
+ return parsed;
832
+ }
833
+ return undefined;
834
+ }
835
+ function readNonNegativeInteger(value) {
836
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0)
837
+ return value;
838
+ if (typeof value === "string" && /^\d+$/.test(value)) {
839
+ const parsed = Number(value);
840
+ if (Number.isSafeInteger(parsed))
841
+ return parsed;
842
+ }
843
+ return undefined;
844
+ }
845
+ function stableStringify(value) {
846
+ if (Array.isArray(value))
847
+ return `[${value.map(stableStringify).join(",")}]`;
848
+ if (isJsonObject(value))
849
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`).join(",")}}`;
850
+ return JSON.stringify(value);
851
+ }
852
+ //# sourceMappingURL=stripePack.js.map