@volter/twin-stripe 2.0.38 → 3.0.1

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 (402) hide show
  1. package/README.md +112 -207
  2. package/dist/generated/pack-facts.json +94 -0
  3. package/dist/journeys/coverage.json +596 -0
  4. package/dist/journeys/customer-life.json +6108 -0
  5. package/dist/journeys/customer-life.outline.md +371 -0
  6. package/dist/journeys/decisions.json +677 -0
  7. package/dist/journeys/demand.json +706 -0
  8. package/dist/journeys/score.json +853 -0
  9. package/dist/journeys/unreachable.json +102 -0
  10. package/dist/journeys/vendor-examples.json +8010 -0
  11. package/dist/spec/SOURCE.md +18 -0
  12. package/dist/spec/doc-examples.json +445 -0
  13. package/dist/spec/fixtures3.json.gz +0 -0
  14. package/dist/spec/openapi.json.gz +0 -0
  15. package/dist/spec/patches.json +34 -0
  16. package/dist/spec/sources.json +306 -0
  17. package/dist/src/cli.d.ts +0 -1
  18. package/dist/src/cli.js +11 -31
  19. package/dist/src/engine/after-payment.d.ts +8 -0
  20. package/dist/src/engine/after-payment.js +27 -0
  21. package/dist/src/engine/apps-secrets.d.ts +1 -0
  22. package/dist/src/engine/apps-secrets.js +3 -0
  23. package/dist/src/engine/balance.d.ts +5 -0
  24. package/dist/src/engine/balance.js +15 -0
  25. package/dist/src/engine/checkout.d.ts +24 -0
  26. package/dist/src/engine/checkout.js +30 -0
  27. package/dist/src/engine/common.d.ts +19 -0
  28. package/dist/src/engine/common.js +34 -0
  29. package/dist/src/engine/connect.d.ts +45 -0
  30. package/dist/src/engine/connect.js +108 -0
  31. package/dist/src/engine/customers.d.ts +4 -0
  32. package/dist/src/engine/customers.js +8 -0
  33. package/dist/src/engine/display.d.ts +11 -0
  34. package/dist/src/engine/display.js +13 -0
  35. package/dist/src/engine/files.d.ts +11 -0
  36. package/dist/src/engine/files.js +23 -0
  37. package/dist/src/engine/invoices.d.ts +21 -0
  38. package/dist/src/engine/invoices.js +84 -0
  39. package/dist/src/engine/issuing.d.ts +33 -0
  40. package/dist/src/engine/issuing.js +54 -0
  41. package/dist/src/engine/ledger.d.ts +8 -0
  42. package/dist/src/engine/ledger.js +8 -0
  43. package/dist/src/engine/oauth.d.ts +4 -0
  44. package/dist/src/engine/oauth.js +24 -0
  45. package/dist/src/engine/onboarding.d.ts +13 -0
  46. package/dist/src/engine/onboarding.js +42 -0
  47. package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
  48. package/dist/src/{stripe-params.js → engine/params.js} +7 -60
  49. package/dist/src/engine/payment-intents.d.ts +10 -0
  50. package/dist/src/engine/payment-intents.js +27 -0
  51. package/dist/src/engine/payment-methods.d.ts +16 -0
  52. package/dist/src/engine/payment-methods.js +22 -0
  53. package/dist/src/engine/radar.d.ts +2 -0
  54. package/dist/src/engine/radar.js +5 -0
  55. package/dist/src/engine/refunds.d.ts +1 -0
  56. package/dist/src/engine/refunds.js +3 -0
  57. package/dist/src/engine/renewals.d.ts +36 -0
  58. package/dist/src/engine/renewals.js +65 -0
  59. package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +12 -45
  60. package/dist/src/{stripe-twin.js → engine/stripe.js} +53 -387
  61. package/dist/src/engine/subscriptions.d.ts +11 -0
  62. package/dist/src/engine/subscriptions.js +30 -0
  63. package/dist/src/engine/transfers.d.ts +2 -0
  64. package/dist/src/engine/transfers.js +1 -0
  65. package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -2
  66. package/dist/src/{stripe-version.js → engine/version.js} +21 -9
  67. package/dist/src/engine/webhook-endpoints.d.ts +1 -0
  68. package/dist/src/engine/webhook-endpoints.js +12 -0
  69. package/dist/src/engine/webhooks.d.ts +31 -0
  70. package/dist/src/engine/webhooks.js +68 -0
  71. package/dist/src/fetch.d.ts +2 -0
  72. package/dist/src/fetch.js +26 -0
  73. package/dist/src/generated/surface.gen.json +1 -1
  74. package/dist/src/generated/ui.gen.json +1 -1
  75. package/dist/src/index.d.ts +5 -14
  76. package/dist/src/index.js +10 -75
  77. package/dist/src/manifest.js +478 -876
  78. package/dist/src/screens/billing-portal.d.ts +3 -0
  79. package/dist/src/screens/{portal.js → billing-portal.js} +32 -58
  80. package/dist/src/screens/checkout-js.d.ts +2 -0
  81. package/dist/src/screens/checkout-js.js +84 -0
  82. package/dist/src/screens/checkout.d.ts +2 -31
  83. package/dist/src/screens/checkout.js +32 -121
  84. package/dist/src/screens/connect-onboarding.d.ts +3 -0
  85. package/dist/src/screens/{onboarding.js → connect-onboarding.js} +38 -104
  86. package/dist/src/screens/dashboard.d.ts +3 -0
  87. package/dist/src/screens/dashboard.js +116 -0
  88. package/dist/src/screens/shared.d.ts +27 -0
  89. package/dist/src/screens/shared.js +87 -0
  90. package/dist/src/screens/stripe-js.d.ts +3 -0
  91. package/dist/src/screens/stripe-js.js +95 -0
  92. package/dist/src/semantics/account.d.ts +2 -0
  93. package/dist/src/semantics/account.js +8 -0
  94. package/dist/src/semantics/account_links.d.ts +2 -0
  95. package/dist/src/semantics/account_links.js +22 -0
  96. package/dist/src/semantics/accounts.d.ts +4 -0
  97. package/dist/src/semantics/accounts.js +40 -0
  98. package/dist/src/semantics/apps.d.ts +4 -0
  99. package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
  100. package/dist/src/semantics/around.d.ts +2 -0
  101. package/dist/src/semantics/around.js +13 -0
  102. package/dist/src/semantics/balance.d.ts +2 -11
  103. package/dist/src/semantics/balance.js +3 -194
  104. package/dist/src/semantics/balance_transactions.d.ts +2 -0
  105. package/dist/src/semantics/balance_transactions.js +15 -0
  106. package/dist/src/semantics/billing_portal.d.ts +4 -0
  107. package/dist/src/semantics/billing_portal.js +36 -0
  108. package/dist/src/semantics/charges.d.ts +3 -28
  109. package/dist/src/semantics/charges.js +5 -202
  110. package/dist/src/semantics/checkout.d.ts +3 -20
  111. package/dist/src/semantics/checkout.js +21 -271
  112. package/dist/src/semantics/clock.d.ts +2 -0
  113. package/dist/src/semantics/clock.js +15 -0
  114. package/dist/src/semantics/coupons.d.ts +2 -6
  115. package/dist/src/semantics/coupons.js +2 -60
  116. package/dist/src/semantics/customers.d.ts +5 -6
  117. package/dist/src/semantics/customers.js +47 -413
  118. package/dist/src/semantics/doors.d.ts +23 -0
  119. package/dist/src/semantics/doors.js +73 -0
  120. package/dist/src/semantics/entitlements.d.ts +0 -2
  121. package/dist/src/semantics/entitlements.js +4 -95
  122. package/dist/src/semantics/events.d.ts +11 -0
  123. package/dist/src/semantics/events.js +31 -0
  124. package/dist/src/semantics/index.d.ts +2 -0
  125. package/dist/src/semantics/index.js +85 -0
  126. package/dist/src/semantics/invoice_payments.d.ts +2 -0
  127. package/dist/src/semantics/invoice_payments.js +12 -0
  128. package/dist/src/semantics/invoices.d.ts +11 -18
  129. package/dist/src/semantics/invoices.js +121 -440
  130. package/dist/src/semantics/issuing.d.ts +6 -13
  131. package/dist/src/semantics/issuing.js +58 -556
  132. package/dist/src/semantics/payment_intents.d.ts +3 -0
  133. package/dist/src/semantics/payment_intents.js +71 -0
  134. package/dist/src/semantics/payment_methods.d.ts +3 -0
  135. package/dist/src/semantics/payment_methods.js +15 -0
  136. package/dist/src/semantics/payouts.d.ts +3 -0
  137. package/dist/src/semantics/payouts.js +28 -0
  138. package/dist/src/semantics/prices.d.ts +4 -0
  139. package/dist/src/semantics/prices.js +37 -0
  140. package/dist/src/semantics/products.d.ts +4 -2
  141. package/dist/src/semantics/products.js +12 -138
  142. package/dist/src/semantics/promotion_codes.d.ts +3 -0
  143. package/dist/src/semantics/promotion_codes.js +35 -0
  144. package/dist/src/semantics/radar.d.ts +2 -2
  145. package/dist/src/semantics/radar.js +5 -74
  146. package/dist/src/semantics/refunds.d.ts +2 -9
  147. package/dist/src/semantics/refunds.js +3 -193
  148. package/dist/src/semantics/shared.d.ts +558 -55
  149. package/dist/src/semantics/shared.js +2070 -114
  150. package/dist/src/semantics/states.d.ts +462 -0
  151. package/dist/src/semantics/states.js +634 -0
  152. package/dist/src/semantics/subscriptions.d.ts +6 -11
  153. package/dist/src/semantics/subscriptions.js +37 -530
  154. package/dist/src/semantics/test_helpers.d.ts +3 -0
  155. package/dist/src/semantics/test_helpers.js +125 -0
  156. package/dist/src/semantics/transfers.d.ts +2 -2
  157. package/dist/src/semantics/transfers.js +6 -101
  158. package/dist/src/semantics/webhook_endpoints.d.ts +2 -0
  159. package/dist/src/semantics/webhook_endpoints.js +26 -0
  160. package/dist/src/server.d.ts +7 -0
  161. package/dist/src/server.js +7 -0
  162. package/generated/pack-facts.json +94 -0
  163. package/journeys/coverage.json +596 -0
  164. package/journeys/customer-life.json +6108 -0
  165. package/journeys/customer-life.outline.md +371 -0
  166. package/journeys/decisions.json +677 -0
  167. package/journeys/demand.json +706 -0
  168. package/journeys/score.json +853 -0
  169. package/journeys/unreachable.json +102 -0
  170. package/journeys/vendor-examples.json +8010 -0
  171. package/package.json +11 -20
  172. package/spec/SOURCE.md +18 -0
  173. package/spec/doc-examples.json +445 -0
  174. package/spec/fixtures3.json.gz +0 -0
  175. package/spec/openapi.json.gz +0 -0
  176. package/spec/patches.json +34 -0
  177. package/spec/sources.json +306 -0
  178. package/src/cli.ts +11 -28
  179. package/src/engine/after-payment.ts +29 -0
  180. package/src/engine/apps-secrets.ts +4 -0
  181. package/src/engine/balance.ts +32 -0
  182. package/src/engine/checkout.ts +39 -0
  183. package/src/engine/common.ts +49 -0
  184. package/src/engine/connect.ts +106 -0
  185. package/src/engine/customers.ts +10 -0
  186. package/src/engine/display.ts +18 -0
  187. package/src/engine/files.ts +28 -0
  188. package/src/engine/invoices.ts +102 -0
  189. package/src/engine/issuing.ts +66 -0
  190. package/src/engine/ledger.ts +48 -0
  191. package/src/engine/oauth.ts +18 -0
  192. package/src/engine/onboarding.ts +53 -0
  193. package/src/{stripe-params.ts → engine/params.ts} +7 -56
  194. package/src/engine/payment-intents.ts +32 -0
  195. package/src/engine/payment-methods.ts +24 -0
  196. package/src/engine/radar.ts +6 -0
  197. package/src/engine/refunds.ts +3 -0
  198. package/src/engine/renewals.ts +72 -0
  199. package/src/{stripe-twin.ts → engine/stripe.ts} +57 -400
  200. package/src/engine/subscriptions.ts +37 -0
  201. package/src/engine/transfers.ts +22 -0
  202. package/src/{stripe-version.ts → engine/version.ts} +19 -10
  203. package/src/engine/webhook-endpoints.ts +14 -0
  204. package/src/engine/webhooks.ts +70 -0
  205. package/src/fetch.ts +27 -0
  206. package/src/generated/surface.gen.json +1 -1
  207. package/src/generated/ui.gen.json +1 -1
  208. package/src/index.ts +10 -116
  209. package/src/manifest.ts +480 -904
  210. package/src/screens/{portal.tsx → billing-portal.tsx} +35 -42
  211. package/src/screens/checkout-js.ts +80 -0
  212. package/src/screens/checkout.tsx +32 -96
  213. package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +39 -85
  214. package/src/screens/dashboard.tsx +178 -0
  215. package/src/screens/shared.tsx +84 -0
  216. package/src/screens/stripe-js.ts +102 -0
  217. package/src/semantics/account.ts +10 -0
  218. package/src/semantics/account_links.ts +20 -0
  219. package/src/semantics/accounts.ts +44 -0
  220. package/src/semantics/{apps-secrets.ts → apps.ts} +17 -33
  221. package/src/semantics/around.ts +17 -0
  222. package/src/semantics/balance.ts +6 -208
  223. package/src/semantics/balance_transactions.ts +18 -0
  224. package/src/semantics/billing_portal.ts +45 -0
  225. package/src/semantics/charges.ts +8 -212
  226. package/src/semantics/checkout.ts +25 -276
  227. package/src/semantics/clock.ts +19 -0
  228. package/src/semantics/coupons.ts +5 -68
  229. package/src/semantics/customers.ts +48 -414
  230. package/src/semantics/doors.ts +79 -0
  231. package/src/semantics/entitlements.ts +3 -94
  232. package/src/semantics/events.ts +37 -0
  233. package/src/semantics/index.ts +89 -0
  234. package/src/semantics/invoice_payments.ts +16 -0
  235. package/src/semantics/invoices.ts +112 -441
  236. package/src/semantics/issuing.ts +57 -573
  237. package/src/semantics/payment_intents.ts +76 -0
  238. package/src/semantics/payment_methods.ts +20 -0
  239. package/src/semantics/payouts.ts +29 -0
  240. package/src/semantics/prices.ts +41 -0
  241. package/src/semantics/products.ts +12 -146
  242. package/src/semantics/promotion_codes.ts +38 -0
  243. package/src/semantics/radar.ts +8 -79
  244. package/src/semantics/refunds.ts +4 -216
  245. package/src/semantics/shared.ts +2089 -135
  246. package/src/semantics/states.ts +662 -0
  247. package/src/semantics/subscriptions.ts +39 -542
  248. package/src/semantics/test_helpers.ts +119 -0
  249. package/src/semantics/transfers.ts +10 -132
  250. package/src/semantics/webhook_endpoints.ts +27 -0
  251. package/src/server.ts +7 -0
  252. package/client/dashboard-api.ts +0 -286
  253. package/client/stripe-mirror.css +0 -277
  254. package/client/stripe-mirror.tsx +0 -1559
  255. package/dist/client/dashboard-api.d.ts +0 -107
  256. package/dist/client/dashboard-api.js +0 -238
  257. package/dist/client/dashboard-api.ts +0 -286
  258. package/dist/client/stripe-mirror.bundle.js +0 -236
  259. package/dist/client/stripe-mirror.css +0 -277
  260. package/dist/client/stripe-mirror.d.ts +0 -134
  261. package/dist/client/stripe-mirror.js +0 -859
  262. package/dist/client/stripe-mirror.tsx +0 -1559
  263. package/dist/src/screens/connect-oauth.d.ts +0 -27
  264. package/dist/src/screens/connect-oauth.js +0 -414
  265. package/dist/src/screens/connect-settings.d.ts +0 -22
  266. package/dist/src/screens/connect-settings.js +0 -103
  267. package/dist/src/screens/consent-skin.d.ts +0 -4
  268. package/dist/src/screens/consent-skin.js +0 -18
  269. package/dist/src/screens/financial-connections.d.ts +0 -5
  270. package/dist/src/screens/financial-connections.js +0 -90
  271. package/dist/src/screens/identity.d.ts +0 -5
  272. package/dist/src/screens/identity.js +0 -86
  273. package/dist/src/screens/onboarding.d.ts +0 -13
  274. package/dist/src/screens/portal.d.ts +0 -5
  275. package/dist/src/screens/public-details.d.ts +0 -5
  276. package/dist/src/screens/public-details.js +0 -90
  277. package/dist/src/semantics/after-payment.d.ts +0 -22
  278. package/dist/src/semantics/after-payment.js +0 -99
  279. package/dist/src/semantics/apps-secrets.d.ts +0 -2
  280. package/dist/src/semantics/billing.d.ts +0 -2
  281. package/dist/src/semantics/billing.js +0 -220
  282. package/dist/src/semantics/connect.d.ts +0 -5
  283. package/dist/src/semantics/connect.js +0 -493
  284. package/dist/src/semantics/credit-notes.d.ts +0 -2
  285. package/dist/src/semantics/credit-notes.js +0 -172
  286. package/dist/src/semantics/disputes.d.ts +0 -2
  287. package/dist/src/semantics/disputes.js +0 -51
  288. package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
  289. package/dist/src/semantics/ephemeral-keys.js +0 -34
  290. package/dist/src/semantics/files.d.ts +0 -2
  291. package/dist/src/semantics/files.js +0 -125
  292. package/dist/src/semantics/ledger.d.ts +0 -59
  293. package/dist/src/semantics/ledger.js +0 -200
  294. package/dist/src/semantics/payment-intents.d.ts +0 -18
  295. package/dist/src/semantics/payment-intents.js +0 -452
  296. package/dist/src/semantics/payment-links.d.ts +0 -2
  297. package/dist/src/semantics/payment-links.js +0 -133
  298. package/dist/src/semantics/payment-methods.d.ts +0 -20
  299. package/dist/src/semantics/payment-methods.js +0 -140
  300. package/dist/src/semantics/plans.d.ts +0 -5
  301. package/dist/src/semantics/plans.js +0 -121
  302. package/dist/src/semantics/platform.d.ts +0 -9
  303. package/dist/src/semantics/platform.js +0 -206
  304. package/dist/src/semantics/renewals.d.ts +0 -47
  305. package/dist/src/semantics/renewals.js +0 -281
  306. package/dist/src/semantics/setup-intents.d.ts +0 -2
  307. package/dist/src/semantics/setup-intents.js +0 -84
  308. package/dist/src/semantics/subscription-schedules.d.ts +0 -2
  309. package/dist/src/semantics/subscription-schedules.js +0 -119
  310. package/dist/src/semantics/tax.d.ts +0 -2
  311. package/dist/src/semantics/tax.js +0 -197
  312. package/dist/src/semantics/terminal.d.ts +0 -5
  313. package/dist/src/semantics/terminal.js +0 -182
  314. package/dist/src/semantics/test-clocks.d.ts +0 -6
  315. package/dist/src/semantics/test-clocks.js +0 -73
  316. package/dist/src/semantics/tokens.d.ts +0 -4
  317. package/dist/src/semantics/tokens.js +0 -44
  318. package/dist/src/semantics/treasury.d.ts +0 -2
  319. package/dist/src/semantics/treasury.js +0 -377
  320. package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
  321. package/dist/src/semantics/webhook-endpoints.js +0 -85
  322. package/dist/src/stripe-budget.d.ts +0 -55
  323. package/dist/src/stripe-budget.js +0 -155
  324. package/dist/src/stripe-capabilities.d.ts +0 -3
  325. package/dist/src/stripe-capabilities.js +0 -5836
  326. package/dist/src/stripe-conformance.d.ts +0 -43
  327. package/dist/src/stripe-conformance.js +0 -105
  328. package/dist/src/stripe-connector.d.ts +0 -161
  329. package/dist/src/stripe-connector.js +0 -414
  330. package/dist/src/stripe-emit.d.ts +0 -2
  331. package/dist/src/stripe-emit.js +0 -145
  332. package/dist/src/stripe-events.d.ts +0 -93
  333. package/dist/src/stripe-events.js +0 -392
  334. package/dist/src/stripe-js.d.ts +0 -4
  335. package/dist/src/stripe-js.js +0 -70
  336. package/dist/src/stripe-mirror-ui.d.ts +0 -15
  337. package/dist/src/stripe-mirror-ui.js +0 -87
  338. package/dist/src/stripe-perform-harness.d.ts +0 -9
  339. package/dist/src/stripe-perform-harness.js +0 -26
  340. package/dist/src/stripe-server.d.ts +0 -33
  341. package/dist/src/stripe-server.js +0 -429
  342. package/dist/src/stripe-shared.d.ts +0 -109
  343. package/dist/src/stripe-shared.js +0 -276
  344. package/dist/src/stripe-ui-conformance.d.ts +0 -5
  345. package/dist/src/stripe-ui-conformance.js +0 -79
  346. package/dist/src/stripe-ui-structure.d.ts +0 -3
  347. package/dist/src/stripe-ui-structure.js +0 -168
  348. package/dist/test-fixtures/stripe-known-deviations.json +0 -110
  349. package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
  350. package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
  351. package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
  352. package/dist/test-fixtures/stripe-schemas.json +0 -3813
  353. package/src/screens/connect-oauth.tsx +0 -400
  354. package/src/screens/connect-settings.tsx +0 -121
  355. package/src/screens/consent-skin.ts +0 -20
  356. package/src/screens/financial-connections.tsx +0 -101
  357. package/src/screens/identity.tsx +0 -96
  358. package/src/screens/public-details.tsx +0 -105
  359. package/src/semantics/after-payment.ts +0 -118
  360. package/src/semantics/billing.ts +0 -216
  361. package/src/semantics/connect.ts +0 -487
  362. package/src/semantics/credit-notes.ts +0 -168
  363. package/src/semantics/disputes.ts +0 -62
  364. package/src/semantics/ephemeral-keys.ts +0 -34
  365. package/src/semantics/files.ts +0 -143
  366. package/src/semantics/ledger.ts +0 -253
  367. package/src/semantics/payment-intents.ts +0 -463
  368. package/src/semantics/payment-links.ts +0 -148
  369. package/src/semantics/payment-methods.ts +0 -145
  370. package/src/semantics/plans.ts +0 -131
  371. package/src/semantics/platform.ts +0 -220
  372. package/src/semantics/renewals.ts +0 -279
  373. package/src/semantics/setup-intents.ts +0 -87
  374. package/src/semantics/subscription-schedules.ts +0 -129
  375. package/src/semantics/tax.ts +0 -220
  376. package/src/semantics/terminal.ts +0 -195
  377. package/src/semantics/test-clocks.ts +0 -77
  378. package/src/semantics/tokens.ts +0 -52
  379. package/src/semantics/treasury.ts +0 -383
  380. package/src/semantics/webhook-endpoints.ts +0 -87
  381. package/src/stripe-budget.ts +0 -181
  382. package/src/stripe-capabilities.ts +0 -5651
  383. package/src/stripe-conformance.ts +0 -124
  384. package/src/stripe-connector.ts +0 -462
  385. package/src/stripe-emit.ts +0 -157
  386. package/src/stripe-events.ts +0 -434
  387. package/src/stripe-js.ts +0 -70
  388. package/src/stripe-mirror-ui.ts +0 -90
  389. package/src/stripe-perform-harness.ts +0 -29
  390. package/src/stripe-server.ts +0 -393
  391. package/src/stripe-shared.ts +0 -297
  392. package/src/stripe-ui-conformance.ts +0 -88
  393. package/src/stripe-ui-structure.ts +0 -182
  394. package/test-fixtures/stripe-known-deviations.json +0 -110
  395. package/test-fixtures/stripe-openapi-operations.json +0 -4717
  396. package/test-fixtures/stripe-schemas.json +0 -3813
  397. /package/dist/src/{screens → engine}/industries.d.ts +0 -0
  398. /package/dist/src/{screens → engine}/industries.js +0 -0
  399. /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
  400. /package/dist/src/{semantics → engine}/test-cards.js +0 -0
  401. /package/src/{screens → engine}/industries.ts +0 -0
  402. /package/src/{semantics → engine}/test-cards.ts +0 -0
package/src/manifest.ts CHANGED
@@ -1,523 +1,290 @@
1
1
  // Stripe's manifest: the vendor facts its published spec does not carry, and the resources the
2
2
  // derived core serves (docs/contributing/architecture.md, "Protocol 3"). The surface itself is
3
3
  // generated (./generated/surface.gen.json, from ../spec by scripts/derive-pack.ts).
4
- import type { DerivedManifest, StateField, Transition } from '@volter/world-core';
5
- import { afterStripeWrite, embedIssuingAuthorizationCard, isValidApiVersion, view } from './stripe-twin.ts';
6
-
7
- const unexpected = (verb: string, allowed: string) => ({
8
- status: 400,
9
- code: 'payment_intent_unexpected_state',
10
- message: `This PaymentIntent could not be ${verb} because it has a status of {from}. ${allowed}`,
11
- });
12
- const OPEN = ['requires_payment_method', 'requires_confirmation', 'requires_action', 'requires_capture', 'processing'];
13
-
14
- /** A move no API call makes: Stripe's own (`vendor`), the clock's (`time`), or the person on a
15
- * Stripe-hosted page (`external`: Checkout, the billing portal, a hosted invoice or 3DS page). */
16
- const by = (actor: 'vendor' | 'time' | 'external', from: string[], to: string, source: string): Transition => ({ actor, from, to, source });
17
- /** A bank account's `status` as a payout or payment destination. Stripe validates its routing number,
18
- * verifies it (micro-deposits, or instantly) and marks it errored when a payout or debit to it fails
19
- * (the page each machine cites); no call the twin serves does any of that (the customer-source verify is
20
- * a gap), so no move of it is the twin's. */
21
- const bankAccountStatus = (_doc: string): StateField => ({ initial: 'new', transitions: [] });
22
-
23
- /** A flag an update sets either way. */
24
- const flag = (operation: string, source: string): Transition[] => ['true', 'false'].map((to) => ({ operation, from: '*' as const, to, source }));
25
-
26
- /** A PaymentIntent's `status`: which operation may move it from where, and what Stripe answers otherwise. */
27
- const paymentIntentStatus: StateField = {
28
- // "After you create the PaymentIntent, its status is `requires_payment_method` until you attach a payment method"
29
- // (docs.stripe.com/payments/paymentintents/lifecycle)
30
- initial: 'requires_payment_method',
31
- transitions: [
32
- // created with its payment method, it "enters the `requires_confirmation` status and is ready to confirm" (the same page)
33
- { operation: 'PostPaymentIntents', from: ['requires_payment_method'], to: 'requires_confirmation', source: 'https://docs.stripe.com/payments/paymentintents/lifecycle' },
34
- // confirm lands on one of its outcomes, decided by the payment method and the capture method
35
- ...['requires_payment_method', 'requires_action', 'requires_capture', 'succeeded'].map((to) => ({
36
- operation: 'PostPaymentIntentsIntentConfirm',
37
- from: ['requires_payment_method', 'requires_confirmation', 'requires_action'],
38
- to,
39
- refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'You cannot confirm this PaymentIntent because it has a status of {from}.' },
40
- source: 'https://docs.stripe.com/api/payment_intents/confirm',
41
- })),
42
- // a bank debit confirmed (created with `confirm`, confirmed, or verified by micro-deposits) is submitted and
43
- // processes: "The PaymentIntent you create initially has a status of `processing`"; "When the bank account is
44
- // successfully verified, Stripe returns the PaymentIntent object with a status of `processing`"
45
- ...['PostPaymentIntents', 'PostPaymentIntentsIntentConfirm'].map((operation) => ({
46
- operation, from: ['requires_payment_method', 'requires_confirmation', 'requires_action'], to: 'processing',
47
- source: 'https://docs.stripe.com/payments/ach-direct-debit/accept-a-payment?payment-ui=direct-api',
48
- })),
49
- // and succeeds: "After the payment has succeeded, the PaymentIntent status is updated from `processing`
50
- // to `succeeded`" (semantics/payment-intents.ts settleBankDebits)
51
- by('vendor', ['processing'], 'succeeded', 'https://docs.stripe.com/payments/ach-direct-debit/accept-a-payment?payment-ui=direct-api'),
52
- // creating one with `confirm` confirms it at once, landing where a confirm lands
53
- ...['requires_payment_method', 'requires_action', 'requires_capture', 'succeeded'].map((to) => ({
54
- operation: 'PostPaymentIntents',
55
- from: ['requires_payment_method', 'requires_confirmation'],
56
- to,
57
- source: 'spec:PostPaymentIntents "Set to `true` to attempt to [confirm this PaymentIntent](https://docs.stripe.com/api/payment_intents/confirm) immediately."',
58
- })),
59
- {
60
- operation: 'PostPaymentIntentsIntentCapture',
61
- from: ['requires_capture'],
62
- to: 'succeeded',
63
- refusal: unexpected('captured', 'Only a PaymentIntent with one of the following statuses may be captured: requires_capture.'),
64
- source: 'https://docs.stripe.com/api/payment_intents/capture',
65
- },
66
- {
67
- operation: 'PostPaymentIntentsIntentIncrementAuthorization',
68
- from: ['requires_capture'],
69
- to: 'requires_capture',
70
- refusal: unexpected('incremented', 'Only a PaymentIntent with a status of requires_capture may be incremented.'),
71
- source: 'https://docs.stripe.com/api/payment_intents/increment_authorization',
72
- },
73
- {
74
- operation: 'PostPaymentIntentsIntentCancel',
75
- from: OPEN,
76
- to: 'canceled',
77
- refusal: unexpected('canceled', 'Only a PaymentIntent with one of the following statuses may be canceled: requires_payment_method, requires_capture, requires_confirmation, requires_action, processing.'),
78
- source: 'https://docs.stripe.com/api/payment_intents/cancel',
79
- },
80
- {
81
- operation: 'PostPaymentIntentsIntentVerifyMicrodeposits',
82
- from: ['requires_action'],
83
- to: 'processing',
84
- refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'This PaymentIntent could not be verified because it has a status of {from}.' },
85
- source: 'https://docs.stripe.com/payments/ach-direct-debit/accept-a-payment?payment-ui=direct-api',
86
- },
87
- {
88
- // paying the invoice an intent collects for settles the intent
89
- operation: 'PostInvoicesInvoicePay',
90
- from: OPEN,
91
- to: 'succeeded',
92
- source: 'https://docs.stripe.com/api/invoices/pay',
93
- },
94
- ...['succeeded', 'requires_action'].map((to) => ({
95
- operation: 'PostPaymentIntentsIntentApplyCustomerBalance',
96
- from: OPEN,
97
- to,
98
- refusal: { status: 400, code: 'payment_intent_unexpected_state', message: 'This PaymentIntent could not be updated because it has a status of {from}.' },
99
- source: 'https://docs.stripe.com/api/payment_intents/apply_customer_balance',
100
- })),
101
- // a card presented to the Terminal reader processing an intent pays it (held, under manual capture)
102
- ...['succeeded', 'requires_capture'].map((to) => ({
103
- operation: 'PostTestHelpersTerminalReadersReaderPresentPaymentMethod', from: ['requires_payment_method', 'requires_confirmation'], to,
104
- source: 'https://docs.stripe.com/api/terminal/readers/present_payment_method',
105
- })),
106
- // the customer pays, in the billing portal, the invoice an intent collects for after its charge was declined
107
- by('external', ['requires_payment_method'], 'succeeded', 'https://docs.stripe.com/customer-management'),
108
- // Stripe also fails a bank debit back to needing a payment method, completes 3-D Secure on the bank's page and
109
- // cancels a hold left uncaptured; the twin's bank debits succeed unless their test account stays processing, it has no 3-D Secure page, and it keeps
110
- // holds until they are captured or canceled
111
- ],
112
- };
113
-
114
- /** A charge's `captured`: an authorization (capture=false) is captured once. */
115
- const chargeCaptured: StateField = {
116
- initial: true,
117
- transitions: [
118
- {
119
- operation: 'PostChargesChargeCapture',
120
- from: ['false'],
121
- to: 'true',
122
- refusal: { status: 400, code: 'charge_already_captured', message: 'Charge ch_xxx has already been captured.' },
123
- source: 'https://docs.stripe.com/api/charges/capture',
124
- },
125
- // a manual-capture intent's capture captures the charge its confirm authorized
126
- { operation: 'PostPaymentIntentsIntentCapture', from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/payment_intents/capture' },
127
- ],
128
- };
129
-
130
- /** A charge's `refunded`: true once refunds take its whole amount, false again when a refund that made it so is
131
- * canceled. A capture or a cancel never makes it so: "`refunded` will no longer be `true` for payment cancellation
132
- * flows", and a partial capture makes no Refund (docs.stripe.com/changelog/basil/2025-03-31/remove-refund-from-partial-
133
- * capture-and-payment-cancellation-flow). */
134
- const chargeRefunded: StateField = {
135
- initial: false,
136
- transitions: [
137
- ...['PostRefunds', 'PostChargesChargeRefunds', 'PostCreditNotes'].map((operation) => ({ operation, from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/charges/object#charge_object-refunded' })),
138
- { operation: 'PostRefundsRefundCancel', from: ['true'], to: 'false', source: 'https://docs.stripe.com/api/refunds/cancel' },
139
- // (an authorization left uncaptured is canceled after seven days, "Uncaptured PaymentIntents are cancelled a set
140
- // number of days (7 by default) after their creation", docs.stripe.com/api/payment_intents/capture; the twin keeps it)
141
- ],
142
- };
143
-
144
- /** A charge's `status`: the twin settles a charge when it is made (a declined one is never
145
- * stored); an asynchronous one settles later, on the network's word. */
146
- const chargeStatus: StateField = {
147
- initial: 'succeeded',
148
- transitions: [
149
- // (an asynchronous charge is pending at Stripe until it settles or fails; the twin's settle when made)
150
- ],
151
- };
152
-
153
- /** A charge's `paid`: true once it succeeded or was authorized; a pending one becomes paid when it settles. */
154
- const chargePaid: StateField = {
155
- initial: true,
156
- // (a charge is paid when it settles; the twin's settle when made)
157
- transitions: [],
158
- };
159
-
160
- /** A refund's `status`: only one waiting on the customer (requires_action) may be canceled; the
161
- * rest is the network's. */
162
- const refundStatus: StateField = {
163
- initial: 'succeeded',
164
- transitions: [
165
- {
166
- operation: 'PostRefundsRefundCancel',
167
- from: ['requires_action'],
168
- to: 'canceled',
169
- refusal: { status: 400, code: 'refund_status_invalid', message: 'Refund {id} cannot be canceled because its status is {from}.' },
170
- source: 'https://docs.stripe.com/api/refunds/cancel',
171
- },
172
- // a card refund the available balance does not cover is held pending, and made once the balance covers it: "Stripe
173
- // holds the refund as pending for card transactions ... until your Stripe balance becomes sufficient"
174
- // (docs.stripe.com/refunds; semantics/refunds.ts)
175
- // (a paid invoice's credit note makes its refund as a refund call does: "Refunds: create a new refund (using
176
- // refund_amount)", docs.stripe.com/api/credit_notes/create)
177
- ...['PostRefunds', 'PostChargesChargeRefunds', 'PostCreditNotes'].map((operation) => ({ operation, from: ['succeeded'], to: 'pending', source: 'https://docs.stripe.com/refunds' })),
178
- by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/refunds'),
179
- // a bank-transfer payment's refund waits in requires_action for the customer's bank details, and is made so
180
- // (semantics/refunds.ts); (Stripe also fails some refunds; the twin's do not fail)
181
- ],
182
- };
183
-
184
- const SETUP_OPEN = ['requires_payment_method', 'requires_confirmation', 'requires_action'];
185
- const setupUnexpected = (verb: string) => ({ status: 400, code: 'setup_intent_unexpected_state', message: `You cannot ${verb} this SetupIntent because it has a status of {from}.` });
186
-
187
- /** A SetupIntent's `status`: confirm settles it (or leaves a bank account awaiting micro-deposits),
188
- * cancel ends an open one, and verification completes the micro-deposit wait. */
189
- const setupIntentStatus: StateField = {
190
- // "After you create the SetupIntent, it has a status of `requires_payment_method` until you attach a payment method"
191
- // (docs.stripe.com/payments/paymentintents/lifecycle)
192
- initial: 'requires_payment_method',
193
- transitions: [
194
- // created with its payment method, it "enters the `requires_confirmation` status and is ready to confirm" (the same page)
195
- { operation: 'PostSetupIntents', from: ['requires_payment_method'], to: 'requires_confirmation', source: 'https://docs.stripe.com/payments/paymentintents/lifecycle' },
196
- ...['requires_action', 'succeeded'].map((to) => ({
197
- operation: 'PostSetupIntentsIntentConfirm',
198
- from: SETUP_OPEN,
199
- to,
200
- refusal: setupUnexpected('confirm'),
201
- source: 'https://docs.stripe.com/api/setup_intents/confirm',
202
- })),
203
- {
204
- operation: 'PostSetupIntentsIntentCancel',
205
- from: SETUP_OPEN,
206
- to: 'canceled',
207
- refusal: setupUnexpected('cancel'),
208
- source: 'https://docs.stripe.com/api/setup_intents/cancel',
209
- },
210
- {
211
- operation: 'PostSetupIntentsIntentVerifyMicrodeposits',
212
- from: ['requires_action'],
213
- to: 'succeeded',
214
- refusal: { status: 400, code: 'setup_intent_unexpected_state', message: 'This SetupIntent could not be verified because it has a status of {from}.' },
215
- source: 'https://docs.stripe.com/api/setup_intents/verify_microdeposits',
216
- },
217
- // (Stripe completes 3-D Secure on the bank's page and settles an asynchronous method from processing; the
218
- // twin has no such page and settles every method at once)
219
- ],
220
- };
221
-
222
- const invoiceRefusal = (message: string) => ({ status: 400, code: 'invoice_not_editable', message });
223
-
224
- /** An invoice's `status`: a draft is finalized (or sent, which finalizes it) into open, an open one
225
- * is paid, written off or voided. Sending leaves any other status where it is. */
226
- const invoiceStatus: StateField = {
227
- initial: 'draft',
228
- transitions: [
229
- {
230
- operation: 'PostInvoicesInvoiceFinalize',
231
- from: ['draft'],
232
- to: 'open',
233
- refusal: invoiceRefusal('This invoice cannot be finalized because it has status {from}.'),
234
- source: 'https://docs.stripe.com/api/invoices/finalize',
235
- },
236
- // docs.stripe.com/invoicing/integration: "To finalize a draft invoice, use the Dashboard, send it to the customer, or pay it."
237
- { operation: 'PostInvoicesInvoiceSend', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/invoicing/integration' },
238
- { operation: 'PostInvoicesInvoiceSend', from: ['open', 'paid', 'uncollectible', 'void'], source: 'https://docs.stripe.com/api/invoices/send' },
239
- {
240
- operation: 'PostInvoicesInvoicePay',
241
- from: ['open', 'uncollectible'],
242
- to: 'paid',
243
- refusal: invoiceRefusal('This invoice cannot be paid because it has status {from}.'),
244
- refusals: { paid: { status: 400, code: 'invoice_payment_intent_requires_action', message: 'This invoice has already been paid.' } },
245
- source: 'https://docs.stripe.com/api/invoices/pay',
246
- },
247
- {
248
- operation: 'PostInvoicesInvoiceMarkUncollectible',
249
- from: ['open'],
250
- to: 'uncollectible',
251
- refusal: invoiceRefusal('Only open invoices can be marked uncollectible (this one is {from}).'),
252
- source: 'https://docs.stripe.com/api/invoices/mark_uncollectible',
253
- },
254
- {
255
- // only a finalized invoice is voided; a draft is deleted
256
- operation: 'PostInvoicesInvoiceVoid',
257
- from: ['open', 'uncollectible'],
258
- to: 'void',
259
- refusal: invoiceRefusal('This invoice cannot be voided because it has status {from}.'),
260
- source: 'https://docs.stripe.com/api/invoices/void',
261
- },
262
- // confirming or capturing the PaymentIntent an invoice collects through pays the invoice
263
- ...['PostPaymentIntentsIntentConfirm', 'PostPaymentIntentsIntentCapture'].map((operation) => ({
264
- operation, from: ['draft', 'open', 'uncollectible'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/build-subscriptions',
265
- })),
266
- // as does its bank debit succeeding once submitted (semantics/payment-intents.ts settleBankDebits)
267
- by('vendor', ['draft', 'open', 'uncollectible'], 'paid', 'https://docs.stripe.com/billing/subscriptions/build-subscriptions'),
268
- // a bank transfer that arrives in the customer's cash balance pays an open invoice that takes transfers
269
- // (automatic reconciliation; in test mode the funding helper stands in for the transfer)
270
- { operation: 'PostTestHelpersCustomersCustomerFundCashBalance', from: ['open'], to: 'paid', source: 'https://docs.stripe.com/payments/customer-balance/reconciliation' },
271
- // a subscription's first invoice is paid on the Checkout page that started it
272
- by('external', ['draft'], 'paid', 'https://docs.stripe.com/payments/checkout/how-checkout-works'),
273
- // a subscription's renewal, drafted at its period's end, is finalized about an hour later and charged: paid, or
274
- // open when the charge is declined (semantics/renewals.ts)
275
- by('time', ['draft'], 'paid', 'https://docs.stripe.com/invoicing/integration/workflow-transitions#finalized'),
276
- by('time', ['draft'], 'open', 'https://docs.stripe.com/billing/subscriptions/overview#payment-status'),
277
- // an update that ends a subscription's trial invoices its new period and attempts payment at once: paid, or open
278
- // when the charge is declined or not attempted (semantics/subscriptions.ts applyTrialEnd)
279
- { operation: 'PostSubscriptionsSubscriptionExposedId', from: ['draft'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment' },
280
- // a trialing subscription's first invoice, for nothing, is paid as it is made ("An immediate invoice is still
281
- // created, but the amount is 0", docs.stripe.com/billing/subscriptions/trials/free-trials)
282
- { operation: 'PostSubscriptions', from: ['draft'], to: 'paid', source: 'https://docs.stripe.com/billing/subscriptions/trials/free-trials' },
283
- { operation: 'PostSubscriptionsSubscriptionExposedId', from: ['draft'], to: 'open', source: 'https://docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment' },
284
- // the customer pays an open invoice in the billing portal
285
- by('external', ['open'], 'paid', 'https://docs.stripe.com/customer-management'),
286
- // (Stripe also finalizes any other auto-advancing draft after an hour, retries an open invoice on its schedule
287
- // and, retries exhausted, may write it off, and takes payment on the hosted invoice page; the twin's other
288
- // invoices move only by the calls above)
289
- ],
290
- };
291
-
292
- /** A subscription's `status` as the twin's own operations move it; canceling ends any subscription. */
293
- const subscriptionStatus: StateField = {
294
- initial: 'active',
295
- transitions: [
296
- { operation: 'DeleteSubscriptionsSubscriptionExposedId', from: '*', to: 'canceled', source: 'https://docs.stripe.com/api/subscriptions/cancel' },
297
- // (a test clock's advance moves no subscription itself: when the clock reaches its target, a trial that ended there
298
- // ends by time's move below, on the next request; semantics/test-clocks.ts finishClockAdvances)
299
- // "Deleting the simulation also deletes the test customers associated with the clock and cancels their
300
- // subscriptions" (docs.stripe.com/billing/testing/test-clocks/api-advanced-usage)
301
- { operation: 'DeleteTestHelpersTestClocksTestClock', from: '*', to: 'canceled', source: 'https://docs.stripe.com/billing/testing/test-clocks/api-advanced-usage' },
302
- // "Cancels a subscription schedule and its associated subscription immediately" (docs.stripe.com/api/subscription_schedules/cancel)
303
- { operation: 'PostSubscriptionSchedulesScheduleCancel', from: ['active', 'trialing', 'past_due', 'incomplete'], to: 'canceled', source: 'https://docs.stripe.com/api/subscription_schedules/cancel' },
304
- // confirming or capturing the first invoice's PaymentIntent, or paying the invoice itself, pays it and
305
- // activates the subscription
306
- ...['PostPaymentIntentsIntentConfirm', 'PostPaymentIntentsIntentCapture'].map((operation) => ({
307
- operation, from: ['incomplete'], to: 'active', source: 'https://docs.stripe.com/billing/subscriptions/build-subscriptions',
308
- })),
309
- // or when its bank debit succeeds once submitted (semantics/payment-intents.ts settleBankDebits)
310
- by('vendor', ['incomplete'], 'active', 'https://docs.stripe.com/billing/subscriptions/build-subscriptions'),
311
- // paying the invoice a subscription waits on (its first, or a renewal that failed) makes it active
312
- { operation: 'PostInvoicesInvoicePay', from: ['incomplete', 'past_due'], to: 'active', source: 'https://docs.stripe.com/billing/subscriptions/overview#payment-status' },
313
- // an update's `trial_end`: `now` ends a trial at once and charges the new period, active when paid and past_due when
314
- // not; a future timestamp starts a trial on a subscription out of one (semantics/subscriptions.ts applyTrialEnd)
315
- { operation: 'PostSubscriptionsSubscriptionExposedId', from: ['trialing'], to: 'active', source: 'https://docs.stripe.com/api/subscriptions/update#update_subscription-trial_end' },
316
- { operation: 'PostSubscriptionsSubscriptionExposedId', from: ['trialing'], to: 'past_due', source: 'https://docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment' },
317
- {
318
- operation: 'PostSubscriptionsSubscriptionExposedId', from: ['active', 'past_due'], to: 'trialing',
319
- // (Where the documentation stops and the twin decides: the wording of the refusal for a subscription neither
320
- // active nor past_due; a canceled one answers what Stripe answers any update of a canceled subscription.)
321
- refusal: { status: 400, message: 'A trial can only be added to an active or past_due subscription; this one is {from}.' },
322
- refusals: { canceled: { status: 400, message: 'A canceled subscription can only update its cancellation_details and metadata.' } },
323
- source: 'https://docs.stripe.com/billing/subscriptions/trials/free-trials#adding-a-new-trial-to-a-subscription-previously-in-a-trial',
324
- },
325
- // a trial ends on its own clock (Stripe pauses one that ends with no payment method to bill; the twin's
326
- // trials end active), and a renewal whose charge is declined leaves the subscription past_due
327
- // (semantics/renewals.ts)
328
- by('time', ['trialing'], 'active', 'https://docs.stripe.com/billing/subscriptions/trials'),
329
- by('time', ['active'], 'past_due', 'https://docs.stripe.com/billing/subscriptions/overview#payment-status'),
330
- // one set to cancel at its period's end is canceled when the period ends
331
- by('time', ['active', 'trialing', 'past_due'], 'canceled', 'https://docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle'),
332
- // the customer pays the invoice it waits on in the billing portal
333
- by('external', ['past_due'], 'active', 'https://docs.stripe.com/customer-management'),
334
- // (Stripe also expires an incomplete subscription after 23 hours, and retries a failed renewal on its schedule,
335
- // the settings ending the retries unpaid or canceled; the twin makes no retry)
336
- // the customer cancels in the billing portal
337
- by('external', ['active', 'trialing', 'past_due', 'unpaid', 'paused'], 'canceled', 'spec:/components/schemas/portal_subscription_cancel "Whether to cancel subscriptions immediately or at the end of the billing period."'),
338
- ],
339
- };
340
-
341
- const notOpen = (verb: string) => ({ status: 400, code: 'checkout_session_not_open', message: `You may only ${verb} a Checkout Session that is currently in the \`open\` state.` });
342
-
343
- /** A Checkout Session's `status`: an open session expires (asked to, or 24 hours on), or completes
344
- * when the customer pays on the hosted page. Completion is the external actor's move, the one the
345
- * hosted Checkout page (src/screens/checkout.tsx) performs. */
346
- const checkoutStatus: StateField = {
347
- initial: 'open',
348
- transitions: [
349
- { operation: 'PostCheckoutSessionsSessionExpire', from: ['open'], to: 'expired', refusal: notOpen('expire'), source: 'https://docs.stripe.com/api/checkout/sessions/expire' },
350
- { actor: 'external', from: ['open'], to: 'complete', refusal: notOpen('complete'), source: 'https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-status' },
351
- // (Stripe expires an open session at expires_at; the twin keeps it open until asked)
352
- ],
353
- };
354
-
355
- /** A Checkout Session's `payment_status`: the customer completing a payment or subscription session
356
- * pays it; a delayed payment method settles later. */
357
- const checkoutPaymentStatus: StateField = {
358
- initial: 'unpaid',
359
- transitions: [
360
- { actor: 'external', from: '*', to: 'paid', source: 'https://docs.stripe.com/api/checkout/sessions/object#checkout_session_object-payment_status' },
361
- // (a delayed method settles a session later at Stripe; the twin's settle on the page)
362
- ],
363
- };
364
-
365
- /** A dispute's `status`: closing concedes it; the bank decides the rest, and an unanswered one is
366
- * lost when its evidence is due. */
367
- const disputeStatus: StateField = {
368
- initial: 'warning_needs_response',
369
- transitions: [
370
- { operation: 'PostDisputesDisputeClose', from: '*', to: 'lost', source: 'https://docs.stripe.com/api/disputes/close' },
371
- // submitting evidence puts a dispute (or an inquiry) under review; in test mode the evidence decides it
372
- { operation: 'PostDisputesDispute', from: ['needs_response'], to: 'under_review', source: 'https://docs.stripe.com/api/disputes/update#update_dispute-submit' },
373
- { operation: 'PostDisputesDispute', from: ['warning_needs_response'], to: 'warning_under_review', source: 'https://docs.stripe.com/api/disputes/update#update_dispute-submit' },
374
- by('vendor', ['under_review'], 'won', 'https://docs.stripe.com/testing#evidence'),
375
- by('vendor', ['under_review'], 'lost', 'https://docs.stripe.com/testing#evidence'),
376
- // (the bank decides a dispute under review, escalates an inquiry and loses one left unanswered; the twin
377
- // decides none)
378
- // Stripe also closes an inquiry the bank drops (warning_closed); no inquiry the twin holds is dropped
379
- ],
380
- };
381
-
382
- /** A payout's `status`: cancel ends it; reversing cancels it and pays a reversal, never twice. */
383
- const payoutStatus: StateField = {
384
- initial: 'pending',
385
- transitions: [
386
- // only a pending payout can be canceled (docs.stripe.com/api/payouts/cancel); reversing a paid one makes a new
387
- // payout and leaves the original paid (semantics/balance.ts)
388
- { operation: 'PostPayoutsPayoutCancel', from: ['pending'], to: 'canceled', source: 'https://docs.stripe.com/api/payouts/cancel' },
389
- // it arrives in the bank on its arrival date
390
- by('time', ['pending'], 'paid', 'https://docs.stripe.com/payouts#payout-statuses'),
391
- // (a bank may fail a payout at Stripe; the twin's arrive)
392
- ],
393
- };
394
-
395
- /** A credit note's `status`: issued, then voided once. */
396
- const creditNoteStatus: StateField = {
397
- initial: 'issued',
398
- transitions: [
399
- {
400
- operation: 'PostCreditNotesIdVoid',
401
- from: ['issued'],
402
- to: 'void',
403
- refusal: { status: 400, code: 'credit_note_already_voided', message: 'This credit note has already been voided.' },
404
- source: 'https://docs.stripe.com/api/credit_notes/void',
405
- },
406
- ],
407
- };
408
-
409
- const scheduleEnded = (verb: string) => ({ status: 400, code: 'subscription_schedule_status_invalid', message: `This subscription schedule has status {from} and cannot be ${verb}.` });
410
-
411
- /** A subscription schedule's `status`: canceling or releasing ends it; an ended one refuses both. */
412
- const scheduleStatus: StateField = {
413
- initial: 'active',
414
- transitions: [
415
- // "A subscription schedule can only be canceled if its status is not_started or active"; "A schedule can only be
416
- // released if its status is not_started or active" (the cancel and release pages)
417
- { operation: 'PostSubscriptionSchedulesScheduleCancel', from: ['active', 'not_started'], to: 'canceled', refusal: scheduleEnded('canceled'), source: 'https://docs.stripe.com/api/subscription_schedules/cancel' },
418
- { operation: 'PostSubscriptionSchedulesScheduleRelease', from: ['active', 'not_started'], to: 'released', refusal: scheduleEnded('released'), source: 'https://docs.stripe.com/api/subscription_schedules/release' },
419
- // its phases run on the clock: it starts at its start date and ends with its last phase
420
- // (a schedule whose end_behavior is cancel completes at its end at Stripe; the twin's release)
421
- // (Stripe starts a schedule at its start date and ends it with its last phase; the twin's start at once
422
- // and end when canceled or released)
423
- ],
424
- };
425
-
426
- const quoteRefusal = (verb: string) => ({ status: 400, code: 'quote_invalid_status', message: `This quote cannot be ${verb} because it has status {from}.` });
427
-
428
- /** A quote's `status`: a draft is finalized into open, an open one accepted, and either canceled. */
429
- const quoteStatus: StateField = {
430
- initial: 'draft',
431
- transitions: [
432
- { operation: 'PostQuotesQuoteFinalize', from: ['draft'], to: 'open', refusal: quoteRefusal('finalized'), source: 'https://docs.stripe.com/api/quotes/finalize' },
433
- { operation: 'PostQuotesQuoteAccept', from: ['open'], to: 'accepted', refusal: quoteRefusal('accepted'), source: 'https://docs.stripe.com/api/quotes/accept' },
434
- { operation: 'PostQuotesQuoteCancel', from: ['draft', 'open'], to: 'canceled', refusal: quoteRefusal('canceled'), source: 'https://docs.stripe.com/api/quotes/cancel' },
435
- // an open quote not accepted by its expires_at is canceled
436
- // (Stripe cancels an open quote at its expires_at; the twin keeps it open)
437
- ],
438
- };
439
-
440
- const setBy = (operation: string, values: string[], source: string): Transition[] => values.map((to) => ({ operation, from: '*' as const, to, source }));
441
-
442
- /** An Issuing authorization's `status`: approve and decline close a pending one, once; a capture
443
- * closes it, or leaves it pending when told not to close. */
444
- const issuingAuthorizationStatus: StateField = {
445
- initial: 'pending',
446
- transitions: [
447
- // approving keeps it pending (approved) until the merchant captures; declining closes it
448
- { operation: 'PostIssuingAuthorizationsAuthorizationApprove', from: ['pending'], refusal: { status: 400, code: 'authorization_already_finalized', message: 'This authorization has already been finalized (status {from}).' }, source: 'https://docs.stripe.com/issuing/purchases/authorizations' },
449
- { operation: 'PostIssuingAuthorizationsAuthorizationDecline', from: ['pending'], to: 'closed', refusal: { status: 400, code: 'authorization_already_finalized', message: 'This authorization has already been finalized (status {from}).' }, source: 'https://docs.stripe.com/api/issuing/authorizations/decline' },
450
- { operation: 'PostTestHelpersIssuingAuthorizationsAuthorizationCapture', from: ['pending'], to: 'closed', source: 'https://docs.stripe.com/api/issuing/authorizations/test_mode_capture' },
451
- { operation: 'PostTestHelpersIssuingAuthorizationsAuthorizationCapture', from: ['pending'], source: 'https://docs.stripe.com/api/issuing/authorizations/test_mode_capture' },
452
- // a real-time request no one answered is decided when its window ends: "If Stripe doesn't receive your approve or
453
- // decline response within 2 seconds, the Authorization is automatically approved or declined based on your timeout
454
- // settings" (semantics/issuing.ts lapseRealtimeRequests)
455
- by('time', ['pending'], 'closed', 'https://docs.stripe.com/issuing/controls/real-time-authorizations'),
456
- // (Stripe also closes, reverses and expires one as the merchant and the clock act; in test mode the
457
- // capture helper above closes it, and the twin serves no other)
458
- ],
459
- };
460
-
461
- /** A Treasury flow's `status` as the twin's own operations move it: only a processing flow cancels. */
462
- const flowCancel = (operation: string, code: string, name: string, network: Transition[]): StateField => ({
463
- initial: 'processing',
464
- transitions: [
465
- { operation, from: ['processing'], to: 'canceled', refusal: { status: 400, code, message: `This ${name} cannot be canceled because it is no longer processing.` }, source: 'https://docs.stripe.com/api/treasury' },
466
- ...network,
467
- ],
468
- });
469
- /** An outbound flow on the network: Stripe posts or fails it and a posted one may come back returned (in
470
- * test mode by test helpers the twin does not serve), so none of those moves is the twin's. */
471
- const outbound = (_doc: string): Transition[] => [];
4
+ import type { DerivedManifest } from '@volter/world-core';
5
+ import { data, values } from './semantics/events.ts';
6
+ import { STRIPE_EVENTS } from './semantics/shared.ts';
7
+ import { states } from './semantics/states.ts';
472
8
 
473
9
  export const manifest: DerivedManifest = {
474
10
  vendor: 'stripe',
475
11
  service: 'stripe',
476
12
  body: { form: { coerce: true } },
477
13
  ids: { template: '{prefix}_twin_{n}', acceptProvided: true },
478
- // on declared resources, but never served by the twin: Stripe's unknown-URL answer
479
14
  unmodeled: [
480
15
  'DeleteAccountsAccountBankAccountsId',
16
+ 'DeleteAccountsAccountExternalAccountsId',
481
17
  'DeleteAccountsAccountPeoplePerson',
18
+ 'DeleteAccountsAccountPersonsPerson',
19
+ 'DeleteCouponsCoupon',
20
+ 'DeleteCustomersCustomerSourcesId',
482
21
  'DeleteCustomersCustomerSubscriptionsSubscriptionExposedId',
22
+ 'DeleteCustomersCustomerTaxIdsId',
23
+ 'DeleteEphemeralKeysKey',
24
+ 'DeleteInvoiceitemsInvoiceitem',
25
+ 'DeleteInvoicesInvoice',
26
+ 'DeleteProductsId',
27
+ 'DeleteProductsProductFeaturesId',
28
+ 'DeleteRadarValueListItemsItem',
29
+ 'DeleteRadarValueListsValueList',
30
+ 'DeleteSubscriptionItemsItem',
483
31
  'DeleteTaxIdsId',
32
+ 'DeleteTerminalConfigurationsConfiguration',
33
+ 'DeleteTerminalLocationsLocation',
34
+ 'DeleteTerminalReadersReader',
35
+ 'DeleteTestHelpersTestClocksTestClock',
36
+ 'DeleteWebhookEndpointsWebhookEndpoint',
484
37
  'GetAccountsAccountBankAccountsId',
38
+ 'GetAccountsAccountCapabilities',
39
+ 'GetAccountsAccountCapabilitiesCapability',
40
+ 'GetAccountsAccountExternalAccountsId',
485
41
  'GetAccountsAccountPeople',
486
42
  'GetAccountsAccountPeoplePerson',
43
+ 'GetAccountsAccountPersonsPerson',
487
44
  'GetApplicationFeesFeeRefundsId',
45
+ 'GetApplicationFeesId',
46
+ 'GetAppsSecrets',
488
47
  'GetBalanceHistory',
489
48
  'GetBalanceHistoryId',
49
+ 'GetBalanceTransactionsId',
50
+ 'GetBillingAlertsId',
51
+ 'GetBillingCreditGrantsId',
52
+ 'GetBillingMetersId',
53
+ 'GetBillingPortalConfigurationsConfiguration',
490
54
  'GetChargesChargeDispute',
55
+ 'GetChargesChargeRefunds',
56
+ 'GetChargesChargeRefundsRefund',
57
+ 'GetClimateOrdersOrder',
58
+ 'GetCreditNotesId',
59
+ 'GetCreditNotesPreview',
60
+ 'GetCustomersCustomerBalanceTransactionsTransaction',
61
+ 'GetCustomersCustomerCashBalanceTransactionsTransaction',
62
+ 'GetCustomersCustomerPaymentMethods',
63
+ 'GetCustomersCustomerPaymentMethodsPaymentMethod',
64
+ 'GetCustomersCustomerSourcesId',
491
65
  'GetCustomersCustomerSubscriptions',
492
66
  'GetCustomersCustomerSubscriptionsSubscriptionExposedId',
493
- 'GetFileLinks',
67
+ 'GetCustomersCustomerTaxIds',
68
+ 'GetCustomersCustomerTaxIdsId',
69
+ 'GetDisputesDispute',
70
+ 'GetEntitlementsActiveEntitlements',
71
+ 'GetEntitlementsFeaturesId',
72
+ 'GetEventsId',
494
73
  'GetFileLinksLink',
74
+ 'GetFilesFile',
75
+ 'GetFinancialConnectionsAccountsAccount',
76
+ 'GetFinancialConnectionsSessionsSession',
77
+ 'GetFinancialConnectionsTransactions',
495
78
  'GetFinancialConnectionsTransactionsTransaction',
496
- 'GetIdentityVerificationSessions',
79
+ 'GetIdentityVerificationReportsReport',
80
+ 'GetIdentityVerificationSessionsSession',
81
+ 'GetInvoiceitemsInvoiceitem',
82
+ 'GetInvoicesInvoiceLines',
83
+ 'GetIssuingAuthorizationsAuthorization',
84
+ 'GetIssuingCardholdersCardholder',
85
+ 'GetIssuingDisputesDispute',
86
+ 'GetIssuingPersonalizationDesignsPersonalizationDesign',
87
+ 'GetIssuingTransactionsTransaction',
497
88
  'GetLinkAccountSessionsSession',
498
89
  'GetLinkedAccounts',
499
90
  'GetLinkedAccountsAccount',
91
+ 'GetMandatesMandate',
92
+ 'GetPaymentIntentsIntent',
93
+ 'GetPaymentIntentsSearch',
94
+ 'GetPaymentLinksPaymentLink',
95
+ 'GetPayoutsPayout',
96
+ 'GetProductsId',
97
+ 'GetProductsProductFeaturesId',
98
+ 'GetProductsSearch',
99
+ 'GetQuotesQuote',
100
+ 'GetRadarValueListItems',
101
+ 'GetRadarValueListItemsItem',
102
+ 'GetRadarValueListsValueList',
103
+ 'GetRefundsRefund',
104
+ 'GetReportingReportRunsReportRun',
105
+ 'GetReviewsReview',
106
+ 'GetSetupIntentsIntent',
107
+ 'GetSourcesSource',
108
+ 'GetSubscriptionItems',
109
+ 'GetSubscriptionItemsItem',
110
+ 'GetSubscriptionSchedulesSchedule',
111
+ 'GetSubscriptionsSearch',
112
+ 'GetTaxCalculationsCalculation',
500
113
  'GetTaxIdsId',
114
+ 'GetTaxRatesTaxRate',
115
+ 'GetTaxRegistrationsId',
116
+ 'GetTaxTransactionsTransaction',
117
+ 'GetTerminalConfigurationsConfiguration',
118
+ 'GetTerminalLocationsLocation',
119
+ 'GetTerminalReadersReader',
120
+ 'GetTestHelpersTestClocks',
121
+ 'GetTestHelpersTestClocksTestClock',
122
+ 'GetTokensToken',
123
+ 'GetTopups',
124
+ 'GetTopupsTopup',
125
+ 'GetTransfersTransfer',
501
126
  'GetTransfersTransferReversalsId',
127
+ 'GetTreasuryFinancialAccountsFinancialAccount',
128
+ 'GetTreasuryInboundTransfers',
129
+ 'GetTreasuryInboundTransfersId',
130
+ 'GetTreasuryOutboundPayments',
131
+ 'GetTreasuryOutboundPaymentsId',
132
+ 'GetTreasuryOutboundTransfers',
133
+ 'GetTreasuryOutboundTransfersOutboundTransfer',
134
+ 'GetTreasuryReceivedCredits',
135
+ 'GetTreasuryReceivedCreditsId',
136
+ 'GetTreasuryReceivedDebits',
137
+ 'GetTreasuryReceivedDebitsId',
138
+ 'GetTreasuryTransactionEntries',
139
+ 'GetTreasuryTransactionEntriesId',
140
+ 'GetTreasuryTransactions',
141
+ 'GetTreasuryTransactionsId',
142
+ 'GetWebhookEndpointsWebhookEndpoint',
143
+ 'PostAccountsAccount',
502
144
  'PostAccountsAccountBankAccounts',
503
145
  'PostAccountsAccountBankAccountsId',
146
+ 'PostAccountsAccountCapabilitiesCapability',
147
+ 'PostAccountsAccountExternalAccounts',
504
148
  'PostAccountsAccountExternalAccountsId',
505
149
  'PostAccountsAccountPeople',
506
150
  'PostAccountsAccountPeoplePerson',
151
+ 'PostAccountsAccountPersons',
152
+ 'PostAccountsAccountPersonsPerson',
507
153
  'PostApplicationFeesFeeRefundsId',
154
+ 'PostApplicationFeesIdRefunds',
155
+ 'PostBillingAlerts',
156
+ 'PostBillingAlertsIdActivate',
157
+ 'PostBillingAlertsIdArchive',
158
+ 'PostBillingAlertsIdDeactivate',
159
+ 'PostBillingCreditGrants',
160
+ 'PostBillingCreditGrantsId',
161
+ 'PostBillingMeters',
508
162
  'PostBillingMetersId',
163
+ 'PostBillingMetersIdDeactivate',
164
+ 'PostBillingMetersIdReactivate',
165
+ 'PostBillingPortalConfigurationsConfiguration',
166
+ 'PostCharges',
167
+ 'PostChargesCharge',
168
+ 'PostChargesChargeCapture',
169
+ 'PostChargesChargeRefunds',
170
+ 'PostChargesChargeRefundsRefund',
171
+ 'PostCheckoutSessionsSession',
172
+ 'PostCheckoutSessionsSessionExpire',
173
+ 'PostClimateOrders',
174
+ 'PostClimateOrdersOrder',
175
+ 'PostClimateOrdersOrderCancel',
176
+ 'PostCouponsCoupon',
177
+ 'PostCreditNotes',
178
+ 'PostCreditNotesId',
179
+ 'PostCreditNotesIdVoid',
180
+ 'PostCustomersCustomerBalanceTransactions',
509
181
  'PostCustomersCustomerBalanceTransactionsTransaction',
182
+ 'PostCustomersCustomerSources',
510
183
  'PostCustomersCustomerSubscriptions',
511
184
  'PostCustomersCustomerSubscriptionsSubscriptionExposedId',
185
+ 'PostCustomersCustomerTaxIds',
186
+ 'PostDisputesDispute',
187
+ 'PostDisputesDisputeClose',
188
+ 'PostEntitlementsFeatures',
189
+ 'PostEntitlementsFeaturesId',
190
+ 'PostFileLinks',
512
191
  'PostFileLinksLink',
192
+ 'PostFiles',
193
+ 'PostFinancialConnectionsAccountsAccountDisconnect',
194
+ 'PostFinancialConnectionsSessions',
195
+ 'PostIdentityVerificationSessions',
513
196
  'PostIdentityVerificationSessionsSession',
197
+ 'PostInvoiceitems',
198
+ 'PostInvoiceitemsInvoiceitem',
514
199
  'PostInvoicesInvoice',
200
+ 'PostInvoicesInvoiceFinalize',
201
+ 'PostInvoicesInvoiceMarkUncollectible',
202
+ 'PostInvoicesInvoiceSend',
203
+ 'PostIssuingAuthorizationsAuthorization',
204
+ 'PostIssuingCardholdersCardholder',
205
+ 'PostIssuingDisputes',
206
+ 'PostIssuingDisputesDispute',
207
+ 'PostIssuingDisputesDisputeSubmit',
208
+ 'PostIssuingPersonalizationDesigns',
209
+ 'PostIssuingPersonalizationDesignsPersonalizationDesign',
210
+ 'PostIssuingTransactionsTransaction',
515
211
  'PostLinkAccountSessions',
212
+ 'PostPaymentIntentsIntent',
213
+ 'PostPaymentIntentsIntentApplyCustomerBalance',
214
+ 'PostPaymentIntentsIntentCapture',
215
+ 'PostPaymentIntentsIntentConfirm',
216
+ 'PostPaymentIntentsIntentIncrementAuthorization',
217
+ 'PostPaymentIntentsIntentVerifyMicrodeposits',
218
+ 'PostPaymentLinks',
219
+ 'PostPaymentLinksPaymentLink',
220
+ 'PostPaymentMethods',
516
221
  'PostPaymentMethodsPaymentMethod',
222
+ 'PostPaymentMethodsPaymentMethodAttach',
517
223
  'PostPayoutsPayout',
224
+ 'PostPayoutsPayoutCancel',
225
+ 'PostPlans',
226
+ 'PostPricesPrice',
227
+ 'PostProductsId',
228
+ 'PostProductsProductFeatures',
229
+ 'PostQuotes',
518
230
  'PostQuotesQuote',
231
+ 'PostQuotesQuoteAccept',
232
+ 'PostQuotesQuoteCancel',
233
+ 'PostQuotesQuoteFinalize',
234
+ 'PostRadarValueLists',
235
+ 'PostRadarValueListsValueList',
236
+ 'PostRefundsRefund',
237
+ 'PostRefundsRefundCancel',
238
+ 'PostReportingReportRuns',
239
+ 'PostReviewsReviewApprove',
240
+ 'PostSetupIntents',
241
+ 'PostSetupIntentsIntent',
242
+ 'PostSetupIntentsIntentCancel',
243
+ 'PostSetupIntentsIntentConfirm',
244
+ 'PostSetupIntentsIntentVerifyMicrodeposits',
245
+ 'PostSources',
246
+ 'PostSourcesSource',
247
+ 'PostSubscriptionItems',
248
+ 'PostSubscriptionItemsItem',
249
+ 'PostSubscriptionSchedules',
250
+ 'PostSubscriptionSchedulesSchedule',
251
+ 'PostSubscriptionSchedulesScheduleCancel',
252
+ 'PostSubscriptionSchedulesScheduleRelease',
253
+ 'PostSubscriptions',
254
+ 'PostTaxCalculations',
255
+ 'PostTaxIds',
256
+ 'PostTaxRates',
257
+ 'PostTaxRatesTaxRate',
258
+ 'PostTaxRegistrations',
519
259
  'PostTaxRegistrationsId',
260
+ 'PostTerminalConfigurations',
261
+ 'PostTerminalConfigurationsConfiguration',
262
+ 'PostTerminalLocations',
263
+ 'PostTerminalLocationsLocation',
264
+ 'PostTerminalReaders',
265
+ 'PostTerminalReadersReader',
266
+ 'PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignActivate',
267
+ 'PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignDeactivate',
268
+ 'PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignReject',
269
+ 'PostTestHelpersTestClocks',
270
+ 'PostTestHelpersTestClocksTestClockAdvance',
271
+ 'PostTestHelpersTreasuryInboundTransfersIdSucceed',
272
+ 'PostTokens',
273
+ 'PostTopups',
274
+ 'PostTopupsTopup',
275
+ 'PostTopupsTopupCancel',
276
+ 'PostTransfersIdReversals',
277
+ 'PostTransfersTransfer',
520
278
  'PostTransfersTransferReversalsId',
279
+ 'PostTreasuryFinancialAccounts',
280
+ 'PostTreasuryFinancialAccountsFinancialAccount',
281
+ 'PostTreasuryInboundTransfers',
282
+ 'PostTreasuryInboundTransfersInboundTransferCancel',
283
+ 'PostTreasuryOutboundPayments',
284
+ 'PostTreasuryOutboundPaymentsIdCancel',
285
+ 'PostTreasuryOutboundTransfers',
286
+ 'PostTreasuryOutboundTransfersOutboundTransferCancel',
287
+ 'PostWebhookEndpointsWebhookEndpoint',
521
288
  ],
522
289
  time: 'unix',
523
290
  error: { error: { type: '{kind}', message: '{message}', code: '{code}', param: '{param}' } },
@@ -529,6 +296,10 @@ export const manifest: DerivedManifest = {
529
296
  pattern: '^\\d{4}-\\d{2}-\\d{2}(\\.[a-z_]+)?$',
530
297
  error: { status: 400, kind: 'invalid_request_error', code: 'invalid_api_version', message: 'Invalid Stripe version: {value}. Check the API version reference: https://stripe.com/docs/api/versioning' },
531
298
  },
299
+ // source: https://github.com/stripe/stripe-mock/issues/31 "Unrecognized request URL (GET: /v1/test)."
300
+ // source: spec:/components/schemas/api_errors/properties/type "invalid_request_error"
301
+ // Stripe's published API answer, shown by its maintainer for an unknown URL.
302
+ gap: { status: 404, kind: 'invalid_request_error', message: 'Unrecognized request URL ({method}: {path}). Please see https://stripe.com/docs or we can help at https://support.stripe.com/.' },
532
303
  notFound: { status: 404, kind: 'invalid_request_error', code: 'resource_missing', message: "No such {object}: '{id}'" },
533
304
  list: {
534
305
  style: 'envelope',
@@ -548,555 +319,360 @@ export const manifest: DerivedManifest = {
548
319
  message: 'Keys for idempotent requests can only be used with the same parameters they were originally used with. Provide a new idempotency key, or use the original parameters that were used with this key.',
549
320
  },
550
321
  },
322
+ // the pack as the World registers it (world-core packRegistry)
323
+ // the fail-closed ceiling on live calls against Stripe (world-core rateBudget.ts, architecture D8), inside its sandbox
324
+ // allowance
325
+ // source: https://docs.stripe.com/rate-limits "25 requests per second"
326
+ rateBudget: {
327
+ // the vendor's documented allowance this budget stays inside (cited above)
328
+ allowance: { perMinute: 1500, source: 'https://docs.stripe.com/rate-limits' },
329
+ windowMs: 60_000,
330
+ ceiling: 120,
331
+ defaultWeight: 1,
332
+ maxRetryAfterSeconds: 300,
333
+ rules: [
334
+ { match: '^POST /v1/payouts$', weight: 5 },
335
+ { match: '^(POST|DELETE) ', weight: 2 },
336
+ ],
337
+ reason:
338
+ 'Stripe documents scalar limits (docs.stripe.com/rate-limits, read 2026-07-26): 100 requests/second in live mode, 25/second in ' +
339
+ 'sandbox, 25/second for an individual endpoint unless noted, 15 payout creates/second; over it, 429 with a Stripe-Rate-Limited-Reason ' +
340
+ 'header. 120 weighted units / 60s is 2 requests/second, 2% of live mode and 8% of the tightest general limit. POST/DELETE cost 2, a ' +
341
+ 'judgement about blast radius (a write creates a charge, refund or receipt e-mail and cannot be taken back), not a published ratio; ' +
342
+ "POST /v1/payouts costs 5 because payout creation is documented at 15/second. Stripe's own window is a second, so an intra-second " +
343
+ 'burst reaches its limiter first; the 429 cooldown is the backstop for that shape.',
344
+ },
345
+ descriptor: {
346
+ protocol: '3',
347
+ transport: 'rest',
348
+ archetype: 'crud',
349
+ bin: 'world-stripe',
350
+ resources: ['charge', 'customer', 'payment_intent', 'setup_intent', 'payment_method', 'subscription', 'price', 'product', 'invoice', 'invoiceitem', 'refund', 'event'],
351
+ specSource: "Stripe's OpenAPI document (spec/, provenance in spec/SOURCE.md)",
352
+ description: 'Stripe REST twin — spec-correct shapes, signed events, and the hosted pages an application sends people through.',
353
+ // Stripe.js calls the same-origin '/v1/…' and loads from api.stripe.com — the dev proxy forwards '/v1/' to the twin
354
+ // and strips the absolute host so calls come back same-origin
355
+ browserRouting: { apiPathPrefix: '/v1/', loaderHost: 'https://api.stripe.com' },
356
+ // the official server SDK and its Python bindings, and the STRIPE_* credential stems (NEXT_PRIVATE_STRIPE_* too);
357
+ // `stripeconnect` is deliberately not claimed: no Connect-only credential stem is modelled
358
+ adoption: { pypi: ['stripe'], sdks: ['stripe'], envStems: ['STRIPE', 'NEXTPRIVATESTRIPE'] },
359
+ // the API and the screens' hosts, each claimed for the paths its screens serve: an application that sends a person
360
+ // to one of their urls lands in the World; any other path on a claimed host is refused in a World rather than sent
361
+ // to the real Stripe
362
+ // a World's applications hold the platform's keys the dashboard shows (the credential door), never fixtures: the API
363
+ // refuses a key Stripe did not issue
364
+ credentialDoor: { path: '/_twin/app-credentials', body: {}, fill: { STRIPE_SECRET_KEY: 'secret_key', STRIPE_API_KEY: 'secret_key', STRIPE_PRIVATE_KEY: 'secret_key', BILLING_STRIPE_API_KEY: 'secret_key', STRIPE_PUBLISHABLE_KEY: 'publishable_key', NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: 'publishable_key', NEXT_PUBLIC_STRIPE_PUBLIC_KEY: 'publishable_key', NEXT_PUBLIC_STRIPE_KEY: 'publishable_key', STRIPE_WEBHOOK_SECRET: 'webhook_secret', STRIPE_WEBHOOK_SIGNING_SECRET: 'webhook_secret', STRIPE_ENDPOINT_SECRET: 'webhook_secret', STRIPE_WEBHOOK_SECRET_KEY: 'webhook_secret', NEXT_PRIVATE_STRIPE_WEBHOOK_SECRET: 'webhook_secret', BILLING_STRIPE_WEBHOOK_SECRET: 'webhook_secret', STRIPE_CONNECT_WEBHOOK_SECRET: 'connect_webhook_secret', STRIPE_WEBHOOK_SECRET_CONNECT: 'connect_webhook_secret', STRIPE_CONNECT_WEBHOOK_SIGNING_SECRET: 'connect_webhook_secret' } },
365
+ hosts: [
366
+ { host: 'api.stripe.com' }, { host: 'checkout.stripe.com', pathPattern: '^/c/pay/' }, { host: 'billing.stripe.com', pathPattern: '^/p/session/' },
367
+ { host: 'connect.stripe.com', pathPattern: '^/setup/' },
368
+ { host: 'dashboard.stripe.com', pathPattern: '^/radar/lists(?:/[^/]+)?/?$|^/test/issuing/balance/?$|^/settings/public/?$' },
369
+ { host: 'verify.stripe.com', pathPattern: '^/start/' },
370
+ { host: 'js.stripe.com', pathPattern: '^/v3/?$|^/(v3|[a-z]+)/stripe\\.js$' },
371
+ ],
372
+ },
373
+ // what GET /twin says this twin is
374
+ discovery: {
375
+ twinOf: 'the Stripe REST API, Checkout, the customer portal, Connect onboarding, and Stripe.js',
376
+ stores: 'customers, payment objects, checkout sessions, products and prices, subscriptions and invoices, Connect accounts, Issuing, and signed events',
377
+ },
378
+ // the World's doors (./semantics/doors.ts): what stands in for an act Stripe's API does not have
379
+ doors: [
380
+ { id: 'account', method: 'POST', path: '/_twin/account', note: "the platform's own account settings, as the Dashboard changes them" },
381
+ { id: 'appCredentials', method: 'POST', path: '/_twin/app-credentials', note: 'the platform\'s API keys (secret and publishable), as the runtime issues them' },
382
+ { id: 'webhookEndpoints', method: 'POST', path: '/_twin/webhook-endpoints', note: '{ url, events, connect? }: an endpoint added on the Dashboard\'s Webhooks page, signed with the secret the World\'s application holds' },
383
+ { id: 'drain', method: 'POST', path: '/_twin/drain', note: "time's events, sent when a runner drives them: payouts paid and funds made available" },
384
+ ],
551
385
  // The screens applications and people reach (docs/contributing/architecture.md, "Screens"). Demand is the
552
- // ladder's 85 applications: the code that sends a customer to the page.
386
+ // applications' code that sends a customer to the page (journeys/demand.json).
553
387
  screens: [
388
+ { id: 'checkout-js', kind: 'content', host: 'api.stripe.com', path: '/v1/payment_pages/{session}', takes: ['POST'], status: 'done', source: 'https://docs.stripe.com/payments/advanced', demand: 'Postiz CheckoutProvider: the published Stripe.js contract over the session store; the private wire is authored (screens/checkout-js.ts).' },
554
389
  {
555
390
  id: 'checkout', kind: 'flow', host: 'checkout.stripe.com', path: '/c/pay/{session}', status: 'done',
556
- demand: '16 of 85 applications send customers to Checkout',
391
+ demand: 'Dub, Rallly, Postiz and Twenty send customers to Checkout (journeys/demand.json)',
557
392
  controls: ['email', 'cardNumber', 'cardExpiry', 'cardCvc', 'billingName', 'billingCountry', 'billingPostalCode', 'Pay', '← Back'],
558
393
  source: 'https://docs.stripe.com/payments/checkout/how-checkout-works',
559
394
  },
560
395
  {
561
396
  id: 'billing-portal', kind: 'flow', host: 'billing.stripe.com', path: '/p/session/{session}', status: 'done',
562
- demand: '15 of 85 applications send customers to the billing portal', controls: ['Cancel plan', 'Renew plan', '← Return to {business name}'],
397
+ demand: 'Cal.com, Dub, Postiz, Rallly and Twenty send customers to the billing portal', controls: ['Cancel plan', 'Renew plan', '← Return to {business name}'],
563
398
  source: 'https://docs.stripe.com/customer-management',
564
399
  },
565
400
  {
566
- id: 'connect-onboarding', kind: 'flow', host: 'connect.stripe.com', path: '/setup/{account_link} → /setup/s/{session}', status: 'done',
567
- demand: '3 of 85 applications onboard connected accounts with Account Links', controls: ['Agree and submit', '← Return to platform'],
401
+ // an Account Link's url (/setup/{account_link}) opens the onboarding session the page is (/setup/s/{session})
402
+ id: 'connect-onboarding', kind: 'flow', host: 'connect.stripe.com', path: '/setup', status: 'done',
403
+ demand: "Dub onboards its partners' connected accounts with Account Links", controls: ['Agree and submit', '← Return to platform'],
568
404
  source: 'https://docs.stripe.com/connect/hosted-onboarding',
569
405
  },
570
406
  {
571
- id: 'connect-oauth', kind: 'flow', host: 'connect.stripe.com', path: '/oauth/authorize', status: 'done',
572
- demand: "Cal.com's Stripe app connects each organizer's Standard account through Connect OAuth", controls: ['Skip this form', 'Deny access'],
573
- source: 'https://docs.stripe.com/connect/oauth-reference',
574
- },
575
- {
576
- id: 'identity-verification', kind: 'flow', host: 'verify.stripe.com', path: '/start/{verification_session}', status: 'done',
577
- demand: 'a verification report is written only when a person completes this page', controls: ['Verify', 'Fail verification'],
578
- source: 'https://docs.stripe.com/identity/how-sessions-work',
579
- },
580
- {
581
- id: 'financial-connections', kind: 'flow', host: 'js.stripe.com', path: '/v3/financial-connections/{client_secret}', status: 'done',
582
- demand: 'a linked bank account exists only once its holder links it here', controls: ['Link accounts', 'Cancel'],
583
- source: 'https://docs.stripe.com/js/financial_connections/collect_financial_connections_accounts',
407
+ // Stripe.js, the client library an application's page loads: /v3/, /v3/stripe.js and /<release train>/stripe.js
408
+ id: 'stripe-js', kind: 'content', host: 'js.stripe.com', path: '/', status: 'done',
409
+ demand: "Dub's upgrade button loads Stripe.js and calls redirectToCheckout",
410
+ source: 'https://docs.stripe.com/js',
584
411
  },
585
412
  {
586
- id: 'dashboard', kind: 'workspace', host: 'dashboard.stripe.com', path: '/test/{section}; /settings/public; /settings/connect/onboarding-options/oauth', status: 'done',
587
- demand: 'testers and agents inspect payments, customers and subscriptions; an operator sets the business name Checkout shows (Public details) and a Connect platform its OAuth client_id and redirect URIs',
588
- controls: ['Business name', 'Save', 'Enable OAuth for Standard accounts', 'Redirect URIs'], source: 'https://docs.stripe.com/dashboard/basics',
413
+ id: 'dashboard', kind: 'workspace', host: 'dashboard.stripe.com', path: '/', status: 'done',
414
+ demand: 'Operator setup for the cited application calls: Public details; Radar lists for Dub add-to-stripe-fraud-value-lists.ts:3-7; sandbox funding for Open Autonomy world/model/scenario.ts:60,62',
415
+ controls: ['Business name', 'Save', 'New list', 'Name', 'Alias', 'List type', 'Add', 'Add test funds', 'Amount'], source: 'https://docs.stripe.com/dashboard/basics',
589
416
  },
590
417
  ],
591
418
  resources: {
592
- 'billing.meter': {
593
- storedAs: 'billing_meter', idPrefix: 'mtr', notFound: "No such meter: '{id}'",
594
- notState: ['event_time_window'], filters: ['status'],
595
- state: { status: { initial: 'active', transitions: [
596
- { operation: 'PostBillingMetersIdDeactivate', from: '*', to: 'inactive', source: 'https://docs.stripe.com/api/billing/meter/deactivate' },
597
- { operation: 'PostBillingMetersIdReactivate', from: '*', to: 'active', source: 'https://docs.stripe.com/api/billing/meter/reactivate' },
598
- ] } },
599
- },
600
- 'billing.meter_event': { storedAs: 'billing_meter_event', idPrefix: 'mtr_evt' },
601
- 'billing.credit_grant': { storedAs: 'credit_grant', idPrefix: 'credgr', notState: ['category'], filters: ['customer'], notFound: "No such credit grant: '{id}'" },
602
- 'billing.alert': {
603
- storedAs: 'billing_alert', idPrefix: 'alert', notFound: "No such alert: '{id}'",
604
- state: { status: { initial: 'active', transitions: [
605
- { operation: 'PostBillingAlertsIdActivate', from: '*', to: 'active', source: 'https://docs.stripe.com/api/billing/alert/activate' },
606
- { operation: 'PostBillingAlertsIdDeactivate', from: '*', to: 'inactive', source: 'https://docs.stripe.com/api/billing/alert/deactivate' },
607
- { operation: 'PostBillingAlertsIdArchive', from: '*', to: 'archived', source: 'https://docs.stripe.com/api/billing/alert/archive' },
608
- ] } },
609
- },
610
- payment_intent: {
611
- idPrefix: 'pi',
612
- state: { status: paymentIntentStatus },
613
- notState: ['cancellation_reason', 'capture_method', 'confirmation_method', 'setup_future_usage'],
419
+ 'billing.meter': { refresh: { list: 'GetBillingMeters' },
420
+ // source: spec:/components/schemas/billing.meter/properties/status "active"
421
+ assigned: { object: { value: 'billing.meter' }, created: { now: true }, updated: { now: true }, livemode: { value: false }, status: { value: 'active' }, status_transitions: { value: { deactivated_at: null } },
422
+ // source: https://docs.stripe.com/api/billing/meter/create "stripe_customer_id"
423
+ // Where the page stops: omitted mappings use the creation example’s customer/value payload keys.
424
+ customer_mapping: { value: { type: 'by_id', event_payload_key: 'stripe_customer_id' } }, value_settings: { value: { event_payload_key: 'value' } } },
425
+ storedAs: 'billing_meter', idPrefix: 'mtr', notFound: "No such meter: '{id}'", ...states["billing.meter"], filters: ['status'],
426
+ },
427
+ 'billing.meter_event': { refresh: { none: 'a meter event is written, never read: the API reads back only its meter’s summaries (GET /v1/billing/meters/{id}/event_summaries)' }, storedAs: 'billing_meter_event', idPrefix: 'mtr_evt' },
428
+ payment_intent: { refresh: { list: 'GetPaymentIntents' },
429
+ idPrefix: 'pi', ...states["payment_intent"],
614
430
  embeds: { customer: 'customer', latest_charge: 'charge', payment_method: 'payment_method', invoice: 'invoice' },
615
431
  filters: ['customer'],
616
432
  },
617
433
  // an update stores what it is given, nested objects whole, as the twin always has (Stripe merges
618
434
  // metadata keys; `update: 'replace'` keeps the twin's answer)
619
- customer: { idPrefix: 'cus', update: 'replace', notState: ['tax_exempt'], filters: ['email'] },
620
- tax_id: { idPrefix: 'txi', notState: ['type'], filters: ['customer'] },
621
- customer_balance_transaction: { idPrefix: 'cbtxn', notState: ['type'] },
622
- customer_cash_balance_transaction: { storedAs: 'cash_balance_transaction', idPrefix: 'ccsbtxn', notState: ['type'] },
435
+ // next_invoice_sequence, "The suffix of the customer's next invoice number" (docs.stripe.com/api/customers/object): one
436
+ // past its numbered invoices (a finalized invoice is never deleted)
437
+ customer: { refresh: { list: 'GetCustomers' }, idPrefix: 'cus', update: 'replace', ...states["customer"], filters: ['email'], counts: { next_invoice_sequence: { of: 'invoice', by: 'customer', present: 'number', plus: 1 } } },
438
+ customer_balance_transaction: { refresh: { list: 'GetCustomersCustomerBalanceTransactions' }, parent: { param: 'customer', field: 'customer', resource: 'customer' }, idPrefix: 'cbtxn', ...states["customer_balance_transaction"], },
439
+ customer_cash_balance_transaction: { refresh: { list: 'GetCustomersCustomerCashBalanceTransactions' }, parent: { param: 'customer', field: 'customer', resource: 'customer' }, storedAs: 'cash_balance_transaction', idPrefix: 'ccsbtxn', ...states["customer_cash_balance_transaction"], },
623
440
  // a card is chargeable at once; any other source waits for its owner to authorize it through its
624
441
  // flow, and is used up (single-use) or lapses
625
442
  // what a customer's sources list answers (a card, a bank account, a Source, an account) as one
626
443
  // resource over the stored sources; the card fields and an account's type/business_type are
627
444
  // attributes. A customer's bank account verifies as a payout account does.
628
- payment_source: {
445
+ payment_source: { refresh: { none: 'a customer’s sources are listed only per customer, cards, bank accounts and Sources together (GET /v1/customers/{customer}/sources); the twin keeps them among the Sources, which no list reads' },
629
446
  storedAs: 'source',
630
- idPrefix: 'src',
631
- state: { status: bankAccountStatus('https://docs.stripe.com/api/customer_bank_accounts/object#customer_bank_account_object-status') },
632
- notState: ['allow_redisplay', 'business_type', 'regulated_status', 'type'],
447
+ idPrefix: 'src', ...states["payment_source"],
633
448
  },
634
- source: {
635
- idPrefix: 'src',
636
- state: {
637
- status: {
638
- initial: 'chargeable',
639
- transitions: [
640
- // (Stripe makes a redirect source chargeable when the customer authorizes it on the bank's page;
641
- // the twin has no such page, so a non-card source stays pending)
642
- // Stripe also fails a source the customer abandons, consumes a single-use one once charged and
643
- // cancels one left unused; the twin's sources stay chargeable
644
- ],
645
- },
646
- },
647
- notState: ['allow_redisplay', 'type'],
449
+ source: { refresh: { none: 'Stripe lists no Sources: each is read by its id (GET /v1/sources/{source})' },
450
+ idPrefix: 'src', ...states["source"],
648
451
  },
649
- charge: {
650
- idPrefix: 'ch',
651
- state: { status: chargeStatus, captured: chargeCaptured, paid: chargePaid, refunded: chargeRefunded },
452
+ // Connect OAuth's authorization codes: the row's id minted, the code's value drawn from the World's secret
453
+ _oauth_code: { idPrefix: '', ids: '{n}' },
454
+ charge: { refresh: { list: 'GetCharges' },
455
+ idPrefix: 'ch', ...states["charge"],
652
456
  embeds: { customer: 'customer', payment_intent: 'payment_intent', invoice: 'invoice', balance_transaction: 'balance_transaction' },
653
457
  filters: ['customer', 'payment_intent'],
654
458
  },
655
- refund: {
459
+ refund: { refresh: { list: 'GetRefunds' },
656
460
  idPrefix: 're',
657
- update: 'replace',
658
- state: { status: refundStatus },
659
- notState: ['pending_reason', 'reason'],
461
+ update: 'replace', ...states["refund"],
660
462
  embeds: { charge: 'charge', payment_intent: 'payment_intent', balance_transaction: 'balance_transaction' },
661
463
  filters: ['charge', 'payment_intent'],
662
464
  },
663
- setup_intent: {
465
+ setup_intent: { refresh: { list: 'GetSetupIntents' },
664
466
  idPrefix: 'seti',
665
- update: 'replace',
666
- state: { status: setupIntentStatus },
667
- notState: ['cancellation_reason'],
467
+ update: 'replace', ...states["setup_intent"],
668
468
  embeds: { customer: 'customer', payment_method: 'payment_method' },
669
469
  filters: ['customer', 'payment_method'],
670
470
  },
671
- payment_method: { idPrefix: 'pm', notState: ['allow_redisplay', 'type'], embeds: { customer: 'customer' }, filters: ['customer', 'type'] },
672
- subscription: {
673
- idPrefix: 'sub',
674
- state: { status: subscriptionStatus },
675
- notState: ['collection_method'],
471
+ payment_method: { refresh: { list: 'GetPaymentMethods' }, idPrefix: 'pm', ...states["payment_method"], embeds: { customer: 'customer' }, filters: ['customer', 'type'] },
472
+ subscription: { refresh: { list: 'GetSubscriptions', complete: false },
473
+ idPrefix: 'sub', ...states["subscription"],
676
474
  embeds: { customer: 'customer', latest_invoice: 'invoice', default_payment_method: 'payment_method', schedule: 'subscription_schedule' },
475
+ // source: spec:/components/schemas/subscription/properties/items/description "List of subscription items, each with an attached price."
476
+ // source: https://docs.stripe.com/api/subscriptions/object "subscription_items?subscription="
477
+ collections: { items: { resource: 'subscription_item', by: 'subscription', list: { object: 'list', has_more: false, url: '/v1/subscription_items?subscription={id}' } } },
677
478
  filters: ['customer'],
678
479
  },
679
- subscription_item: { idPrefix: 'si' },
680
- invoice: {
681
- idPrefix: 'in',
682
- state: { status: invoiceStatus },
683
- notState: ['billing_reason', 'collection_method', 'customer_tax_exempt'],
480
+ subscription_item: { refresh: { none: 'subscription items are listed only per subscription (the list requires subscription), and each subscription answers its items in full (subscription.items)' }, idPrefix: 'si',
481
+ // source: spec:/components/schemas/subscription_item/properties/price "price"
482
+ embeds: { price: 'price' }, alwaysEmbedded: ['price'] },
483
+ invoice: { refresh: { list: 'GetInvoices' },
484
+ idPrefix: 'in', ...states["invoice"],
684
485
  embeds: { customer: 'customer', subscription: 'subscription', charge: 'charge', payment_intent: 'payment_intent' },
685
- filters: ['customer', 'status', 'subscription'],
486
+ filters: ['customer', 'status', 'subscription', 'collection_method'],
686
487
  },
687
- invoiceitem: { idPrefix: 'ii', embeds: { customer: 'customer', price: 'price' }, filters: ['customer', 'invoice'], order: { field: 'date', direction: 'desc' } },
688
- 'checkout.session': {
488
+ invoiceitem: { refresh: { list: 'GetInvoiceitems' }, idPrefix: 'ii', embeds: { customer: 'customer', price: 'price' }, filters: ['customer', 'invoice'], order: { field: 'date', direction: 'desc' } },
489
+ 'checkout.session': { refresh: { list: 'GetCheckoutSessions' },
689
490
  storedAs: 'checkout_session',
690
- idPrefix: 'cs',
691
- state: { status: checkoutStatus, payment_status: checkoutPaymentStatus },
692
- notState: ['billing_address_collection', 'customer_creation', 'locale', 'mode', 'origin_context', 'payment_method_collection', 'redirect_on_completion', 'submit_type', 'ui_mode'],
491
+ idPrefix: 'cs', ...states["checkout.session"],
693
492
  filters: ['customer', 'payment_intent', 'subscription', 'status'],
694
493
  // the objects a session links to, as its x-expandableFields name them
695
494
  embeds: { customer: 'customer', invoice: 'invoice', payment_intent: 'payment_intent', payment_link: 'payment_link', setup_intent: 'setup_intent', subscription: 'subscription' },
696
495
  },
697
- ephemeral_key: { idPrefix: 'ephkey' },
496
+ ephemeral_key: { refresh: { none: 'an ephemeral key is answered once, by its create: the API has no read of one' }, idPrefix: 'ephkey' },
698
497
  // a mandate's status is the network's to move; nothing here moves it
699
- mandate: {
700
- idPrefix: 'mandate',
701
- state: {
702
- status: {
703
- initial: 'active',
704
- transitions: [
705
- // a single-use mandate is spent by its payment: inactive "The mandate was rejected, revoked, or previously
706
- // used, and may not be used to initiate future payments" (semantics/payment-intents.ts settleBankDebits)
707
- by('vendor', ['active'], 'inactive', 'https://docs.stripe.com/api/mandates/object'),
708
- // (Stripe also activates a pending mandate and deactivates one revoked; the twin's are active when made)
709
- ],
710
- },
711
- },
712
- notState: ['type'],
498
+ mandate: { refresh: { none: 'a mandate is read by its id (GET /v1/mandates/{mandate}): the API lists none' },
499
+ idPrefix: 'mandate', ...states["mandate"],
713
500
  },
714
- dispute: { idPrefix: 'dp', update: 'replace', state: { status: disputeStatus }, notState: ['is_charge_refundable'], embeds: { charge: 'charge', payment_intent: 'payment_intent' }, filters: ['charge', 'payment_intent'] },
715
- payout: { idPrefix: 'po', state: { status: payoutStatus }, notState: ['reconciliation_status', 'type'], embeds: { balance_transaction: 'balance_transaction', destination: 'account' } },
501
+ dispute: { refresh: { list: 'GetDisputes' }, idPrefix: 'dp', update: 'replace', ...states["dispute"], embeds: { charge: 'charge', payment_intent: 'payment_intent' }, filters: ['charge', 'payment_intent'] },
502
+ payout: { refresh: { list: 'GetPayouts' }, idPrefix: 'po', ...states["payout"], embeds: { balance_transaction: 'balance_transaction', destination: 'account' } },
716
503
  // the twin's ledger is seeded available; Stripe holds funds pending until their available_on
717
- balance_transaction: {
718
- idPrefix: 'txn',
719
- state: { status: { initial: 'available', vendorInitial: 'pending', transitions: [by('time', ['pending'], 'available', 'https://docs.stripe.com/api/balance_transactions/object#balance_transaction_object-status')] } },
720
- notState: ['balance_type', 'type'],
504
+ balance_transaction: { refresh: { list: 'GetBalanceTransactions' },
505
+ idPrefix: 'txn', ...states["balance_transaction"],
721
506
  filters: ['type', 'currency'],
722
507
  },
723
508
  // a coupon lapses at its redeem_by: "Date after which the coupon can no longer be redeemed", and `valid` is
724
509
  // "Taking account of the above properties, whether this coupon can still be applied to a customer" (the served
725
510
  // spec's coupon); the twin keeps no redemption count, so max_redemptions never lapses one
726
- coupon: {
727
- idPrefix: 'coupon', update: 'replace', notState: ['duration'],
728
- state: { valid: { initial: true, transitions: [by('time', ['true'], 'false', 'spec:/components/schemas/coupon/properties/redeem_by "Date after which the coupon can no longer be redeemed."')] } },
511
+ coupon: { refresh: { list: 'GetCoupons' },
512
+ idPrefix: 'coupon', update: 'replace', ...states["coupon"],
729
513
  },
730
514
  // switched on and off by an update (Stripe also lapses one at expires_at or when fully redeemed; the
731
515
  // twin keeps no redemption count or expiry)
732
- promotion_code: {
516
+ // source: https://docs.stripe.com/changelog/clover/2025-09-30/polymorphic-coupon "When you retrieve Promotion Codes, they don’t auto-expand the promotion.coupon field"
517
+ // Before Clover the top-level coupon auto-expands; Clover omits that field in its view.
518
+ promotion_code: { embeds: { coupon: 'coupon' }, alwaysEmbedded: ['coupon'], refresh: { list: 'GetPromotionCodes' },
733
519
  idPrefix: 'promo',
734
520
  update: 'replace',
735
- notFound: "No such promotion code: '{id}'",
736
- state: {
737
- active: {
738
- initial: true,
739
- transitions: [
740
- ...flag('PostPromotionCodesPromotionCode', 'https://docs.stripe.com/api/promotion_codes/update'),
741
- ],
742
- },
743
- },
521
+ notFound: "No such promotion code: '{id}'", ...states["promotion_code"],
744
522
  },
745
- credit_note: { idPrefix: 'cn', state: { status: creditNoteStatus }, notState: ['reason', 'type'], embeds: { customer: 'customer', invoice: 'invoice' }, filters: ['invoice', 'customer'] },
746
- subscription_schedule: {
523
+ subscription_schedule: { refresh: { list: 'GetSubscriptionSchedules' },
747
524
  idPrefix: 'sub_sched',
748
- update: 'replace',
749
- state: { status: scheduleStatus },
750
- notState: ['end_behavior'],
525
+ update: 'replace', ...states["subscription_schedule"],
751
526
  embeds: { customer: 'customer', subscription: 'subscription' },
752
527
  filters: ['customer', 'subscription'],
753
528
  },
754
- 'entitlements.feature': {
755
- storedAs: 'entitlements_feature', idPrefix: 'feat', notFound: "No such feature: '{id}'",
756
- state: { active: { initial: true, transitions: flag('PostEntitlementsFeaturesId', 'spec:PostEntitlementsFeaturesId "Update a feature’s metadata or permanently deactivate it."') } },
529
+ 'entitlements.feature': { refresh: { list: 'GetEntitlementsFeatures' },
530
+ storedAs: 'entitlements_feature', idPrefix: 'feat', notFound: "No such feature: '{id}'", ...states["entitlements.feature"],
757
531
  },
758
- // an advance answers advancing and the clock is ready once it has reached the time (semantics/test-clocks.ts)
759
- 'test_helpers.test_clock': {
760
- storedAs: 'test_clock',
761
- idPrefix: 'clock',
762
- notFound: "No such test clock: '{id}'",
763
- state: {
764
- status: {
765
- initial: 'ready',
766
- transitions: [
767
- // "advancing: The clock has started to advance but hasn't reached the specified time"; "ready: The clock has
768
- // completed advancing to the specified time" (docs.stripe.com/billing/testing/test-clocks/api-advanced-usage)
769
- { operation: 'PostTestHelpersTestClocksTestClockAdvance', from: ['ready'], to: 'advancing', refusal: { status: 400, code: 'test_clock_not_ready', message: 'This test clock is still advancing.' }, source: 'https://docs.stripe.com/api/test_clocks/advance' },
770
- by('vendor', ['advancing'], 'ready', 'https://docs.stripe.com/billing/testing/test-clocks/api-advanced-usage'),
771
- // (Stripe may also end an advance in internal_failure; the twin's advances complete)
772
- ],
773
- },
774
- },
775
- },
776
- event: { idPrefix: 'evt', filters: ['type'] },
532
+ // an advance answers advancing and the clock is ready once it has reached the time (./engine/test-clocks.ts)
533
+
534
+ event: { refresh: { list: 'GetEvents', complete: false }, idPrefix: 'evt', filters: ['type'] },
777
535
  // disabled and re-enabled by an update's `disabled` (Stripe also disables one that keeps failing; the
778
536
  // twin counts no failures)
779
- webhook_endpoint: {
537
+ webhook_endpoint: { refresh: { list: 'GetWebhookEndpoints' },
780
538
  idPrefix: 'we',
781
- notFound: "No such webhook endpoint: '{id}'",
782
- state: {
783
- status: {
784
- initial: 'enabled',
785
- transitions: [
786
- ...['enabled', 'disabled'].map((to) => ({ operation: 'PostWebhookEndpointsWebhookEndpoint', from: '*' as const, to, source: 'https://docs.stripe.com/api/webhook_endpoints/update#update_webhook_endpoint-disabled' })),
787
- ],
788
- },
789
- },
539
+ notFound: "No such webhook endpoint: '{id}'", ...states["webhook_endpoint"],
790
540
  },
791
- account: { idPrefix: 'acct', notState: ['business_type', 'type'] },
792
- person: { idPrefix: 'person', notState: ['political_exposure'], parent: { param: 'account', field: 'account', resource: 'account' } },
541
+ account: {
542
+ // source: spec:/components/schemas/account/properties/individual "person"
543
+ embeds: { individual: 'person' }, alwaysEmbedded: ['individual'], refresh: { list: 'GetAccounts', complete: false }, idPrefix: 'acct', ...states["account"], },
544
+ person: { refresh: { list: 'GetAccountsAccountPersons' }, idPrefix: 'person', ...states["person"], parent: { param: 'account', field: 'account', resource: 'account' } },
793
545
  // a capability lives in its account's capability map; requesting one leaves it pending, withdrawing it inactive
794
- capability: {
795
- idPrefix: 'cap',
796
- state: {
797
- status: {
798
- initial: 'unrequested',
799
- transitions: [
800
- ...['pending', 'inactive'].map((to) => ({ operation: 'PostAccountsAccountCapabilitiesCapability', from: '*' as const, to, source: 'https://docs.stripe.com/api/capabilities/update' })),
801
- // Stripe reviews the account's requirements
802
- by('vendor', ['pending', 'inactive'], 'active', 'https://docs.stripe.com/connect/account-capabilities#capability-status'),
803
- by('vendor', ['active', 'pending'], 'inactive', 'https://docs.stripe.com/connect/account-capabilities#capability-status'),
804
- ],
805
- },
806
- },
546
+ capability: { refresh: { none: 'the twin keeps a capability in its account’s capabilities, which the accounts’ list reads back (GET /v1/accounts)' },
547
+ idPrefix: 'cap', ...states["capability"],
807
548
  },
808
549
  // allow_redisplay and regulated_status belong to a card destination: consent and the issuer's
809
550
  // regulation, set when it is added, not a lifecycle
810
- external_account: {
811
- idPrefix: 'ba',
812
- state: { status: bankAccountStatus('https://docs.stripe.com/api/external_account_bank_accounts/object#account_bank_account_object-status') },
813
- notState: ['allow_redisplay', 'regulated_status'],
551
+ external_account: { refresh: { none: 'an account answers its external accounts (account.external_accounts), which the accounts’ list reads back (GET /v1/accounts)' },
552
+ idPrefix: 'ba', ...states["external_account"],
814
553
  },
815
- account_link: { idPrefix: 'acctlink' },
816
- account_session: { idPrefix: 'accts' },
817
- transfer: { idPrefix: 'tr', update: 'replace', filters: ['destination'] },
818
- transfer_reversal: { idPrefix: 'trr' },
554
+ account_link: { refresh: { none: 'an account link is answered once, by its create: the API has no read of one' }, idPrefix: 'acctlink' },
555
+ account_session: { refresh: { none: 'an account session is answered once, by its create: the API has no read of one' }, idPrefix: 'accts' },
556
+ transfer: { refresh: { list: 'GetTransfers' }, idPrefix: 'tr', update: 'replace', filters: ['destination'] },
557
+ transfer_reversal: { refresh: { list: 'GetTransfersIdReversals' }, parent: { param: 'id', field: 'transfer', resource: 'transfer' }, idPrefix: 'trr' },
819
558
  // refunded once fee refunds take its whole amount
820
- application_fee: {
821
- idPrefix: 'fee', notFound: "No such application fee: '{id}'", filters: ['charge'],
822
- state: { refunded: { initial: false, transitions: [
823
- { operation: 'PostApplicationFeesIdRefunds', from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/fee_refunds/create' },
824
- // a refund of its charge with refund_application_fee refunds it in proportion
825
- ...['PostRefunds', 'PostChargesChargeRefunds'].map((operation) => ({ operation, from: ['false'], to: 'true', source: 'https://docs.stripe.com/api/refunds/create#create_refund-refund_application_fee' })),
826
- ] } },
827
- },
828
- fee_refund: { idPrefix: 'fr' },
829
- 'apps.secret': { storedAs: 'apps_secret', idPrefix: 'apmc' },
830
- token: { idPrefix: 'tok' },
831
- file: { idPrefix: 'file', notState: ['purpose'], filters: ['purpose'] },
832
- file_link: { idPrefix: 'link' },
833
- // the user submits on Stripe's hosted verification page and Stripe checks it (the twin's own helper
834
- // outside Stripe's surface, POST …/verify, stands in for both at once)
835
- 'identity.verification_session': {
836
- storedAs: 'verification_session',
837
- idPrefix: 'vs',
838
- notFound: "No such VerificationSession: '{id}'",
839
- state: {
840
- status: {
841
- initial: 'requires_input',
842
- transitions: [
843
- by('external', ['requires_input'], 'processing', 'https://docs.stripe.com/identity/how-sessions-work'),
844
- by('vendor', ['processing'], 'verified', 'https://docs.stripe.com/identity/how-sessions-work'),
845
- by('vendor', ['processing'], 'requires_input', 'https://docs.stripe.com/identity/how-sessions-work'),
846
- ],
847
- },
848
- },
849
- notState: ['type'],
559
+ application_fee: { refresh: { list: 'GetApplicationFees' },
560
+ idPrefix: 'fee', notFound: "No such application fee: '{id}'", filters: ['charge'], ...states["application_fee"],
850
561
  },
851
- 'identity.verification_report': { storedAs: 'verification_report', idPrefix: 'vr', notFound: "No such verification report: '{id}'", notState: ['type'], filters: ['verification_session', 'type'] },
852
- 'billing_portal.session': { storedAs: 'billing_portal_session', idPrefix: 'bps', notState: ['locale'] },
562
+ fee_refund: { refresh: { list: 'GetApplicationFeesIdRefunds' }, parent: { param: 'id', field: 'fee', resource: 'application_fee' }, idPrefix: 'fr' },
563
+ 'apps.secret': { refresh: { none: 'secrets are listed only per scope (the list requires scope), which a root does not name' }, storedAs: 'apps_secret', idPrefix: 'apmc' },
564
+ 'billing_portal.session': { refresh: { none: 'a portal session is answered once, by its create: the API has no read of one' }, storedAs: 'billing_portal_session', idPrefix: 'bps', ...states["billing_portal.session"], },
853
565
  // is_default is Stripe's: the configuration the dashboard made first
854
- 'billing_portal.configuration': {
855
- storedAs: 'billing_portal_configuration', idPrefix: 'bpc', notFound: "No such configuration: '{id}'",
856
- state: { active: { initial: true, transitions: flag('PostBillingPortalConfigurationsConfiguration', 'https://docs.stripe.com/api/customer_portal/configurations/update') } },
857
- notState: ['is_default'],
566
+ 'billing_portal.configuration': { refresh: { list: 'GetBillingPortalConfigurations' },
567
+ storedAs: 'billing_portal_configuration', idPrefix: 'bpc', notFound: "No such configuration: '{id}'", ...states["billing_portal.configuration"],
858
568
  },
859
- // `state` is a US state code, not a lifecycle
860
- tax_rate: {
861
- idPrefix: 'txr', notFound: "No such tax rate: '{id}'",
862
- state: { active: { initial: true, transitions: flag('PostTaxRatesTaxRate', 'https://docs.stripe.com/api/tax_rates/update') } },
863
- notState: ['jurisdiction_level', 'rate_type', 'state', 'tax_type'],
864
- },
865
- // its status is derived on every read from whether a head office is set; nothing stores it
866
- 'tax.settings': { storedAs: 'tax_settings', idPrefix: 'taxset', notState: ['status'] },
867
- 'tax.calculation': { storedAs: 'tax_calculation', idPrefix: 'taxcalc', notFound: "No such tax calculation: '{id}'" },
868
- // a registration is active from creation here; its dates move it on the clock
869
- 'tax.registration': {
870
- storedAs: 'tax_registration', idPrefix: 'taxreg', notFound: "No such tax registration: '{id}'", filters: ['status'],
871
- state: {
872
- // Stripe schedules a registration with a future active_from and expires one at expires_at; the twin's
873
- // are active from the start and never expire
874
- status: { initial: 'active', transitions: [] },
875
- },
876
- },
877
- 'tax.transaction': { storedAs: 'tax_transaction', idPrefix: 'tax', notFound: "No such tax transaction: '{id}'", notState: ['type'] },
878
- payment_link: {
879
- idPrefix: 'plink',
880
- update: 'replace', notFound: "No such payment link: '{id}'",
881
- state: { active: { initial: true, transitions: flag('PostPaymentLinksPaymentLink', 'https://docs.stripe.com/api/payment_links/payment_links/update') } },
882
- notState: ['billing_address_collection', 'customer_creation', 'payment_method_collection', 'submit_type'],
883
- },
884
- quote: { idPrefix: 'qt', state: { status: quoteStatus }, notState: ['collection_method'], filters: ['customer', 'status'] },
569
+ 'tax.calculation': { refresh: { none: 'a calculation is read by its id (GET /v1/tax/calculations/{calculation}): the API lists none' }, storedAs: 'tax_calculation', idPrefix: 'taxcalc', notFound: "No such tax calculation: '{id}'" },
570
+ 'tax.transaction': { refresh: { none: 'a tax transaction is read by its id (GET /v1/tax/transactions/{transaction}): the API lists none' }, storedAs: 'tax_transaction', idPrefix: 'tax', notFound: "No such tax transaction: '{id}'", ...states["tax.transaction"], },
885
571
  // a review is open until it is closed; approving closes it, once
886
- review: {
572
+ review: { refresh: { list: 'GetReviews', complete: false },
887
573
  storedAs: 'radar_review',
888
- idPrefix: 'prv',
889
- state: {
890
- open: {
891
- initial: true,
892
- transitions: [
893
- // (Stripe also closes one when the payment is refunded or disputed; the twin's close only on approval)
894
- { operation: 'PostReviewsReviewApprove', from: ['true'], to: 'false', refusal: { status: 400, code: 'review_already_closed', message: 'This review has already been closed.' }, source: 'https://docs.stripe.com/api/radar/reviews/approve' },
895
- ],
896
- },
897
- },
898
- notState: ['closed_reason', 'opened_reason'],
899
- },
900
- 'radar.value_list': { storedAs: 'radar_value_list', idPrefix: 'rsl', notFound: "No such radar value list: '{id}'", notState: ['item_type'], filters: ['alias'] },
901
- 'radar.value_list_item': { storedAs: 'radar_value_list_item', idPrefix: 'rsli', notFound: "No such radar value list item: '{id}'" },
902
- 'issuing.cardholder': {
574
+ idPrefix: 'prv', ...states["review"],
575
+ },
576
+ 'radar.value_list': { refresh: { list: 'GetRadarValueLists' }, storedAs: 'radar_value_list', idPrefix: 'rsl', notFound: "No such radar value list: '{id}'", ...states["radar.value_list"],
577
+ // source: spec:/components/schemas/radar.value_list/properties/created/description "Time at which the object was created."
578
+ assigned: { created: { now: true }, object: { value: 'radar.value_list' }, livemode: { value: false }, metadata: { value: {} },
579
+ // source: spec:/components/schemas/radar.value_list/properties/created_by/description "The name or email address of the user who created this value list."
580
+ // Where documentation stops: local API-key creates identify their synthetic account operator as twin.
581
+ created_by: { value: 'twin' }, list_items: { value: { object: 'list', data: [], has_more: false, url: '/v1/radar/value_list_items' } } }, filters: ['alias'] },
582
+ 'radar.value_list_item': { refresh: { none: 'value list items are listed only per value list (the list requires value_list), and each list answers its items (radar.value_list.list_items)' }, storedAs: 'radar_value_list_item', idPrefix: 'rsli', notFound: "No such radar value list item: '{id}'" },
583
+ 'issuing.cardholder': { refresh: { list: 'GetIssuingCardholders' },
903
584
  storedAs: 'issuing_cardholder',
904
585
  idPrefix: 'ich',
905
- notFound: "No such cardholder: '{id}'",
906
- state: { status: { initial: 'active', transitions: setBy('PostIssuingCardholdersCardholder', ['active', 'inactive', 'blocked'], 'https://docs.stripe.com/api/issuing/cardholders/update') } },
907
- notState: ['type'],
586
+ // source: spec:/components/schemas/issuing.cardholder/properties/created/description "Time at which the object was created."
587
+ assigned: { created: { now: true }, object: { value: 'issuing.cardholder' }, livemode: { value: false }, metadata: { value: {} },
588
+ // source: spec:/components/schemas/issuing_cardholder_requirements/properties/past_due/description "Array of fields that need to be collected"
589
+ requirements: { value: { disabled_reason: null, past_due: [] } } },
590
+ notFound: "No such cardholder: '{id}'", ...states["issuing.cardholder"],
908
591
  filters: ['status', 'type', 'email'],
909
592
  },
910
- 'issuing.card': {
593
+ 'issuing.card': { refresh: { list: 'GetIssuingCards' },
911
594
  storedAs: 'issuing_card',
912
595
  idPrefix: 'ic',
913
- notFound: "No such card: '{id}'",
914
- state: { status: { initial: 'active', transitions: setBy('PostIssuingCardsCard', ['active', 'inactive', 'canceled'], 'https://docs.stripe.com/api/issuing/cards/update') } },
915
- notState: ['cancellation_reason', 'replacement_reason', 'type'],
596
+ notFound: "No such card: '{id}'", ...states["issuing.card"],
597
+ // source: spec:/components/schemas/issuing.card/properties/cardholder "issuing.cardholder"
598
+ embeds: { cardholder: 'issuing.cardholder' }, alwaysEmbedded: ['cardholder'],
916
599
  filters: ['cardholder', 'status', 'type'],
917
600
  },
918
- 'issuing.authorization': { storedAs: 'issuing_authorization', idPrefix: 'iauth', state: { status: issuingAuthorizationStatus }, notState: ['authorization_method', 'card_presence'] },
919
- 'issuing.transaction': { storedAs: 'issuing_transaction', idPrefix: 'ipi', notFound: "No such transaction: '{id}'", notState: ['type', 'wallet'], filters: ['card', 'cardholder'] },
920
- 'issuing.dispute': {
921
- storedAs: 'issuing_dispute',
922
- idPrefix: 'idp',
923
- notFound: "No such dispute: '{id}'",
924
- state: {
925
- status: {
926
- initial: 'unsubmitted',
927
- transitions: [
928
- { operation: 'PostIssuingDisputesDisputeSubmit', from: ['unsubmitted'], to: 'submitted', refusal: { status: 400, code: 'dispute_unsubmitted_required', message: 'This dispute has already been submitted (status {from}).' }, source: 'spec:PostIssuingDisputesDisputeSubmit "Submits an Issuing <code>Dispute</code> to the card network."' },
929
- // (Stripe decides a submitted dispute won or lost and expires one never submitted; the twin decides none)
930
- ],
931
- },
932
- },
933
- notState: ['loss_reason'],
934
- filters: ['status', 'transaction'],
935
- },
936
- 'issuing.token': {
937
- storedAs: 'issuing_token',
938
- idPrefix: 'iss_tok',
939
- notFound: "No such issuing token: '{id}'",
940
- state: { status: { initial: 'active', transitions: [...setBy('PostIssuingTokensToken', ['active', 'deleted', 'suspended'], 'https://docs.stripe.com/api/issuing/tokens/update')] } },
941
- notState: ['network', 'wallet_provider'],
942
- filters: ['card', 'status'],
943
- },
944
- // a design waits in review (inactive) until the review test helpers decide it
945
- 'issuing.personalization_design': {
946
- storedAs: 'personalization_design',
947
- idPrefix: 'pd',
948
- notFound: "No such personalization design: '{id}'",
949
- state: {
950
- status: {
951
- // a new design waits on Stripe's review (docs.stripe.com/issuing/cards/physical/personalization-design)
952
- initial: 'review',
953
- transitions: [
954
- ...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignActivate', ['active'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignActivate "to <code>active</code>"'),
955
- ...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignDeactivate', ['inactive'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignDeactivate "to <code>inactive</code>"'),
956
- ...setBy('PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignReject', ['rejected'], 'spec:PostTestHelpersIssuingPersonalizationDesignsPersonalizationDesignReject "to <code>rejected</code>"'),
957
- // (outside test mode Stripe's review decides it; the twin is test mode, where these helpers do)
958
- ],
959
- },
960
- },
961
- },
962
- 'terminal.location': { storedAs: 'terminal_location', idPrefix: 'tml', notFound: "No such location: '{id}'" },
963
- // a simulated reader is online from registration; a real one drops off and comes back
964
- 'terminal.reader': {
965
- storedAs: 'terminal_reader', idPrefix: 'tmr', notFound: "No such reader: '{id}'", notState: ['device_type'], filters: ['location', 'status'],
966
- // (a real reader goes offline and back; the twin's simulated readers stay online)
967
- state: { status: { initial: 'online', transitions: [] } },
968
- },
969
- 'terminal.configuration': { storedAs: 'terminal_configuration', idPrefix: 'tmc', notFound: "No such configuration: '{id}'", notState: ['is_account_default'] },
970
- topup: {
971
- idPrefix: 'tu',
972
- state: {
973
- status: {
974
- // a top-up starts pending (the create page's example answers "status": "pending") and its funds arrive days
975
- // later: "USA (USD) ACH Debit Transfer 5 days" (docs.stripe.com/connect/top-ups; semantics/terminal.ts)
976
- initial: 'pending',
977
- transitions: [
978
- { operation: 'PostTopupsTopupCancel', from: ['pending'], to: 'canceled', refusal: { status: 400, code: 'topup_unexpected_state', message: 'This top-up cannot be canceled because it has a status of {from}.' }, source: 'https://docs.stripe.com/api/topups/cancel' },
979
- // to the Issuing balance alike: "Your top-ups can take up to 5 business days to become available"
980
- // (docs.stripe.com/issuing/funding/balance)
981
- by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/connect/top-ups'),
982
- // (Stripe also fails a top-up or reverses a settled one; the twin's top-ups do neither)
983
- ],
984
- },
985
- },
986
- notState: ['initiated_by'],
987
- filters: ['status'],
988
- },
989
- // closing an account (POST …/close) is not modeled here; is_default is Stripe's
990
- 'treasury.financial_account': { storedAs: 'financial_account', idPrefix: 'fa', notFound: "No such financial account: '{id}'", state: { status: { initial: 'open', transitions: [] } }, notState: ['is_default'], filters: ['status'] },
991
- 'treasury.outbound_payment': {
601
+ 'issuing.authorization': { refresh: { list: 'GetIssuingAuthorizations' }, storedAs: 'issuing_authorization', idPrefix: 'iauth', ...states["issuing.authorization"],
602
+ // source: spec:/components/schemas/issuing.authorization/properties/card "issuing.card"
603
+ embeds: { card: 'issuing.card', cardholder: 'issuing.cardholder' }, alwaysEmbedded: ['card'], filters: ['card', 'cardholder', 'status'], },
604
+ 'issuing.transaction': { refresh: { list: 'GetIssuingTransactions' }, storedAs: 'issuing_transaction', idPrefix: 'ipi', notFound: "No such transaction: '{id}'", ...states["issuing.transaction"], filters: ['card', 'cardholder'] },
605
+
606
+ 'treasury.outbound_payment': { refresh: { none: 'outbound payments are listed only per financial account (the list requires financial_account)' },
992
607
  storedAs: 'outbound_payment',
993
608
  idPrefix: 'obp',
994
- notFound: "No such outbound payment: '{id}'",
995
- state: { status: flowCancel('PostTreasuryOutboundPaymentsIdCancel', 'outbound_payment_not_cancelable', 'OutboundPayment', outbound('https://docs.stripe.com/treasury/moving-money/financial-accounts/out-of/outbound-payments')) },
609
+ notFound: "No such outbound payment: '{id}'", ...states["treasury.outbound_payment"],
996
610
  filters: ['financial_account', 'status'],
997
611
  },
998
- 'treasury.outbound_transfer': {
612
+ 'treasury.outbound_transfer': { refresh: { none: 'outbound transfers are listed only per financial account (the list requires financial_account)' },
999
613
  storedAs: 'outbound_transfer',
1000
614
  idPrefix: 'obt',
1001
- notFound: "No such outbound transfer: '{id}'",
1002
- state: { status: flowCancel('PostTreasuryOutboundTransfersOutboundTransferCancel', 'outbound_transfer_not_cancelable', 'OutboundTransfer', outbound('https://docs.stripe.com/treasury/moving-money/financial-accounts/out-of/outbound-transfers')) },
615
+ notFound: "No such outbound transfer: '{id}'", ...states["treasury.outbound_transfer"],
1003
616
  filters: ['financial_account', 'status'],
1004
617
  },
1005
- 'treasury.inbound_transfer': {
618
+ 'treasury.inbound_transfer': { refresh: { none: 'inbound transfers are listed only per financial account (the list requires financial_account)' },
1006
619
  storedAs: 'inbound_transfer',
1007
620
  idPrefix: 'ibt',
1008
- notFound: "No such inbound transfer: '{id}'",
1009
- // "An InboundTransfer is processing if it is created and pending. The status changes to succeeded once the funds have
1010
- // been "confirmed""; in test mode the succeed helper confirms them (docs.stripe.com/api/treasury/inbound_transfers)
1011
- state: { status: flowCancel('PostTreasuryInboundTransfersInboundTransferCancel', 'inbound_transfer_not_cancelable', 'InboundTransfer', [
1012
- { operation: 'PostTestHelpersTreasuryInboundTransfersIdSucceed', from: ['processing'], to: 'succeeded', refusal: { status: 400, code: 'inbound_transfer_not_processing', message: 'This InboundTransfer cannot succeed because it is no longer processing.' }, source: 'https://docs.stripe.com/api/treasury/inbound_transfers' },
1013
- ]) },
621
+ notFound: "No such inbound transfer: '{id}'", ...states["treasury.inbound_transfer"],
1014
622
  filters: ['financial_account', 'status'],
1015
623
  },
1016
624
  // received credits and debits are materialized by the network (test helpers the twin does not serve)
1017
- 'treasury.received_credit': { storedAs: 'received_credit', idPrefix: 'rc', notFound: "No such received credit: '{id}'", state: { status: { initial: 'succeeded', transitions: [] } }, notState: ['failure_code', 'network'], filters: ['financial_account', 'status'] },
1018
- 'treasury.received_debit': { storedAs: 'received_debit', idPrefix: 'rd', notFound: "No such received debit: '{id}'", state: { status: { initial: 'succeeded', transitions: [] } }, notState: ['failure_code', 'network'], filters: ['financial_account', 'status'] },
625
+ 'treasury.received_credit': { refresh: { none: 'received credits are listed only per financial account (the list requires financial_account)' }, storedAs: 'received_credit', idPrefix: 'rc', notFound: "No such received credit: '{id}'", ...states["treasury.received_credit"], filters: ['financial_account', 'status'] },
626
+ 'treasury.received_debit': { refresh: { none: 'received debits are listed only per financial account (the list requires financial_account)' }, storedAs: 'received_debit', idPrefix: 'rd', notFound: "No such received debit: '{id}'", ...states["treasury.received_debit"], filters: ['financial_account', 'status'] },
1019
627
  // a ledger transaction is posted or left open by the flow that writes it; the network settles an open one
1020
- 'treasury.transaction': {
1021
- storedAs: 'treasury_transaction', idPrefix: 'trxn', notFound: "No such treasury transaction: '{id}'", notState: ['flow_type'],
1022
- // void: "The Transaction never impacted the balance. For example, a Transaction would enter this state if an
1023
- // OutboundPayment was initiated but then canceled" (docs.stripe.com/api/treasury/transactions/object); canceling an
1024
- // outbound flow voids its open transaction (semantics/treasury.ts). (Stripe also posts an open transaction as its
1025
- // flow settles; the twin's outbound flows stay processing)
1026
- state: { status: { initial: 'open', transitions: [
1027
- ...['PostTreasuryOutboundPaymentsIdCancel', 'PostTreasuryOutboundTransfersOutboundTransferCancel', 'PostTreasuryInboundTransfersInboundTransferCancel'].map((operation) => ({ operation, from: ['open'], to: 'void', source: 'https://docs.stripe.com/api/treasury/transactions/object' })),
1028
- // an inbound transfer's transaction posts when it succeeds ("a transaction is created and posted")
1029
- { operation: 'PostTestHelpersTreasuryInboundTransfersIdSucceed', from: ['open'], to: 'posted', source: 'https://docs.stripe.com/api/treasury/inbound_transfers' },
1030
- ] } },
1031
- },
1032
- 'treasury.transaction_entry': { storedAs: 'treasury_transaction_entry', idPrefix: 'trxne', notFound: "No such transaction entry: '{id}'", notState: ['flow_type', 'type'] },
1033
- 'climate.order': {
1034
- storedAs: 'climate_order',
1035
- idPrefix: 'climorder',
1036
- notFound: "No such order: '{id}'",
1037
- state: {
1038
- status: {
1039
- // an order is created confirmed: the create page's example answers "status": "confirmed" with confirmed_at set
1040
- // (docs.stripe.com/api/climate/order/create)
1041
- initial: 'confirmed',
1042
- transitions: [
1043
- { operation: 'PostClimateOrdersOrderCancel', from: ['awaiting_funds', 'confirmed', 'open'], to: 'canceled', refusal: { status: 400, code: 'climate_order_not_cancelable', message: 'This order can no longer be canceled because it is {from}.' }, source: 'https://docs.stripe.com/api/climate/order/cancel' },
1044
- // funded, confirmed by the supplier, then delivered
1045
- // (Stripe funds, confirms and delivers an order over months; the twin's orders stay where they are made)
1046
- ],
1047
- },
1048
- },
1049
- notState: ['cancellation_reason'],
1050
- },
1051
- 'financial_connections.session': { storedAs: 'fc_session', idPrefix: 'fcsess' },
1052
- 'financial_connections.account': {
1053
- storedAs: 'fc_account',
1054
- idPrefix: 'fca',
1055
- notFound: "No such account: '{id}'",
1056
- state: {
1057
- status: {
1058
- initial: 'active',
1059
- transitions: [
1060
- { operation: 'PostFinancialConnectionsAccountsAccountDisconnect', from: '*', to: 'disconnected', source: 'https://docs.stripe.com/api/financial_connections/accounts/disconnect' },
1061
- // (Stripe marks an account inactive when the bank link breaks, and active again; the twin's links hold)
1062
- ],
1063
- },
1064
- },
1065
- notState: ['category', 'subcategory'],
628
+ 'treasury.transaction': { refresh: { none: 'Treasury transactions are listed only per financial account (the list requires financial_account)' },
629
+ storedAs: 'treasury_transaction', idPrefix: 'trxn', notFound: "No such treasury transaction: '{id}'", ...states["treasury.transaction"],
1066
630
  },
631
+ 'treasury.transaction_entry': { refresh: { none: 'transaction entries are listed only per financial account (the list requires financial_account)' }, storedAs: 'treasury_transaction_entry', idPrefix: 'trxne', notFound: "No such transaction entry: '{id}'", ...states["treasury.transaction_entry"], },
632
+ 'financial_connections.session': { refresh: { none: 'a session is read by its id (GET /v1/financial_connections/sessions/{session}): the API lists none' }, storedAs: 'fc_session', idPrefix: 'fcsess' },
1067
633
  // a linked account's transactions arrive posted here; the bank settles a pending one
1068
- 'financial_connections.transaction': {
1069
- storedAs: 'fc_transaction', idPrefix: 'fctxn',
1070
- // (a bank's pending transaction posts or is voided at Stripe; the twin's arrive posted)
1071
- state: { status: { initial: 'posted', transitions: [] } },
1072
- },
1073
- 'forwarding.request': { storedAs: 'forwarding_request', idPrefix: 'fwdr', notFound: "No such forwarding request: '{id}'" },
1074
- // the twin runs a report at once; Stripe runs it in the background
1075
- 'reporting.report_run': {
1076
- storedAs: 'report_run', idPrefix: 'frr', notFound: "No such report run: '{id}'",
1077
- // "When first created, the object appears with status="pending""; "When the run completes, Stripe updates the
1078
- // object, and it has a status of succeeded" (docs.stripe.com/reports/api; semantics/platform.ts finishReportRuns).
1079
- // (Stripe may also fail a run; the twin's runs succeed)
1080
- state: { status: { initial: 'pending', transitions: [by('vendor', ['pending'], 'succeeded', 'https://docs.stripe.com/reports/api')] } },
634
+ 'financial_connections.transaction': { refresh: { none: 'transactions are listed only per linked account (the list requires account)' },
635
+ storedAs: 'fc_transaction', idPrefix: 'fctxn', ...states["financial_connections.transaction"],
1081
636
  },
1082
637
  // archived (active=false) and restored by an update
1083
- product: { idPrefix: 'prod', update: 'replace', state: { active: { initial: true, transitions: flag('PostProductsId', 'https://docs.stripe.com/api/products/update#update_product-active') } } },
1084
- product_feature: { idPrefix: 'prodft' },
1085
- price: {
638
+ product: { refresh: { list: 'GetProducts' }, idPrefix: 'prod', update: 'replace', ...states["product"], },
639
+ product_feature: {
640
+ // source: spec:/components/schemas/product_feature/properties/entitlement_feature "entitlements.feature"
641
+ embeds: { entitlement_feature: 'entitlements.feature' }, alwaysEmbedded: ['entitlement_feature'], refresh: { list: 'GetProductsProductFeatures' }, parent: { param: 'product', field: '_product', resource: 'product' }, idPrefix: 'prodft' },
642
+ price: { refresh: { list: 'GetPrices', complete: false },
1086
643
  idPrefix: 'price',
1087
- update: 'replace', notState: ['billing_scheme', 'tax_behavior', 'tiers_mode', 'type'], embeds: { product: 'product' },
1088
- // deleting a plan leaves its price for its subscribers, inactive: "Deleting plans means new subscribers can’t be added.
1089
- // Existing subscribers aren’t affected." (docs.stripe.com/api/plans/delete; semantics/plans.ts)
1090
- state: { active: { initial: true, transitions: [...flag('PostPricesPrice', 'https://docs.stripe.com/api/prices/update#update_price-active'), { operation: 'DeletePlansPlan', from: ['true'], to: 'false', source: 'https://docs.stripe.com/api/plans/delete' }] } },
644
+ update: 'replace', ...states["price"], embeds: { product: 'product' },
1091
645
  },
1092
646
  },
1093
- view: (storedType, row) => view(storedType, row),
1094
- onWrite: async ({ operation, storedType, body, root, occurredAt, request }) => {
1095
- const version = request.headers.get('stripe-version') ?? undefined;
1096
- // an Issuing authorization carries its full card in every egress, webhooks included; the write
1097
- // hook is where the root is known, and the body it embeds into is the one the handler answers
1098
- if (storedType === 'issuing_authorization') embedIssuingAuthorizationCard(body, root);
1099
- await afterStripeWrite(storedType, operation, body, root, occurredAt, version && isValidApiVersion(version) ? version : undefined, request.headers.get('stripe-account') ?? undefined);
647
+ // the webhooks Stripe's writes send (./semantics/shared.ts), each event's data and account rendered by ./semantics/events.ts
648
+ events: { ...STRIPE_EVENTS, render: data, values },
649
+ // a vendor-backed World's root: Stripe's webhooks sent to the World's ingest door, signed as Stripe signs every event
650
+ // (Stripe-Signature: t=<seconds>,v1=<HMAC-SHA256 of "<t>.<body>">, docs.stripe.com/webhooks#verify-manually), the
651
+ // event's type in its body and its object at data.object (docs.stripe.com/api/events/object); the types whose
652
+ // resource neither the events' write names nor <resource>.created|updated|deleted say
653
+ ingest: {
654
+ scheme: { kind: 'timestamp-v1', header: 'Stripe-Signature' },
655
+ type: { body: 'type' },
656
+ object: 'data.object',
657
+ types: {
658
+ 'checkout.session.completed': { resource: 'checkout.session' },
659
+ 'checkout.session.expired': { resource: 'checkout.session' },
660
+ 'charge.succeeded': { resource: 'charge' },
661
+ 'charge.failed': { resource: 'charge' },
662
+ 'charge.refunded': { resource: 'charge' },
663
+ 'payment_intent.requires_action': { resource: 'payment_intent' },
664
+ 'payment_intent.canceled': { resource: 'payment_intent' },
665
+ 'payment_method.automatically_updated': { resource: 'payment_method' },
666
+ 'invoice.payment_succeeded': { resource: 'invoice' },
667
+ 'invoice.payment_failed': { resource: 'invoice' },
668
+ 'invoice.voided': { resource: 'invoice' },
669
+ 'transfer.reversed': { resource: 'transfer' },
670
+ 'account.external_account.updated': { resource: 'external_account' },
671
+ 'account.external_account.created': { resource: 'external_account' },
672
+ 'account.external_account.deleted': { resource: 'external_account', deleted: true },
673
+ 'payout.paid': { resource: 'payout' },
674
+ 'payout.failed': { resource: 'payout' },
675
+ 'issuing_authorization.request': { resource: 'issuing.authorization' },
676
+ },
1100
677
  },
1101
678
  };
1102
-