@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
@@ -1,220 +1,16 @@
1
- // Charge semantics. The machine in ../manifest.ts says a charge is captured once; these handlers
2
- // compute the money: a charge's `refunds` sub-list is derived on every read from the refunds that
3
- // point at it, never a stored snapshot, while the cumulative `amount_refunded` / `refunded` stay
4
- // stored (a partial capture moves them for a reason that is also a Refund row).
5
- import type { Semantics, SemanticsContext } from '@volter/world-core';
6
- import { asBool, cardError, chargeDefaults, declineFor, searchOver, validateMoney } from '../stripe-twin.ts';
7
- import { afterCharge } from './after-payment.ts';
8
- import { settleCharge } from './ledger.ts';
9
- import { paymentMethodDetails } from './payment-methods.ts';
10
- import { refundCharge } from './refunds.ts';
11
- import { at, created, expanded, fail, finder, list, newest, path, refundDestination, send, where, type Row } from './shared.ts';
12
-
13
- const chargeMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such charge: '${id}'`, 404, 'resource_missing');
14
-
15
- /** Every egress of a charge carries its refunds, derived from the refund rows in list order. */
16
- export function chargeBody(ctx: SemanticsContext, c: Row): Row {
17
- const data = newest(ctx, 'refund').filter((r) => r.charge === c.id);
18
- return { ...c, refunds: { object: 'list', data, has_more: false, total_count: data.length, url: `/v1/charges/${String(c.id)}/refunds` } };
19
- }
20
-
21
- // A charge is written as the event Stripe sends for it (docs.stripe.com/api/events/types; Stripe has no
22
- // `charge.created`): charge.succeeded, "Occurs whenever a charge is successful", for one made or authorized
23
- // (capture=false: its status is succeeded, captured false), and charge.failed, "Occurs whenever a failed charge attempt
24
- // occurs", for a declined attempt. The twin makes no pending charge (charge.pending, "Occurs whenever a pending charge is
25
- // created"): its charges settle when made.
26
- /** The create's parameters as the charge keeps them: without `card`, the request's card details ("A token, like the ones
27
- * returned by Stripe.js", or a hash of the number, expiry and CVC). The Charge object has no `card` property (served
28
- * spec, charge); the card it was made with is its payment_method_details. Kept, it answered the card's full number and
29
- * CVC back on every read of the charge. */
30
- function withoutCard(params: Row): Row {
31
- const { card: _card, ...rest } = params;
32
- return rest;
33
- }
34
-
35
- /** A Charges-API charge a declining test card refuses: a 402 card_error naming the failed charge Stripe records for the
36
- * attempt (docs.stripe.com/declines). */
37
- async function declinedCharge(ctx: SemanticsContext, decline: { code: string; decline_code?: string; message: string }): Promise<Response> {
38
- const params = ctx.params;
39
- const failedId = ctx.mint('charge');
40
- const amount = Number(params.amount) || 0;
41
- await created(ctx, 'charge', { ...withoutCard(params), id: failedId }, {
42
- ...chargeDefaults(failedId, amount, false, ctx.occurredAt), status: 'failed', paid: false, captured: false, capture_before: null,
43
- failure_code: decline.code, failure_message: decline.message, balance_transaction: null,
44
- outcome: { type: 'issuer_declined', network_status: 'declined_by_network', reason: decline.decline_code ?? decline.code, risk_level: 'normal', seller_message: 'The bank did not return any further details with this decline.' },
45
- payment_method_details: paymentMethodDetails(ctx, params.payment_method ?? params.source ?? params.card) ?? null,
46
- }, { operation: 'charge.failed' });
47
- return send(ctx, cardError(decline, { charge: failedId }));
48
- }
49
-
50
- /** A charge's fraud report: "user_report … Assessments reported by you. If set, possible values of are `safe` and
51
- * `fraudulent`" (docs.stripe.com/api/charges/object, fraud_details). */
52
- function fraudReport(ctx: SemanticsContext, existing: Row, fd: Row): Row | Response {
53
- const report = String(fd.user_report);
54
- if (report !== 'safe' && report !== 'fraudulent') return fail(ctx, "Invalid fraud_details[user_report]: must be 'safe' or 'fraudulent'.", 400, 'parameter_invalid_string_enum');
55
- const prior = existing.fraud_details && typeof existing.fraud_details === 'object' ? (existing.fraud_details as Row) : {};
56
- return { user_report: report, stripe_report: prior.stripe_report ?? null };
57
- }
58
-
59
- const create: Semantics = async (ctx) => {
60
- const params = ctx.params;
61
- const bad = validateMoney(params);
62
- if (bad) return send(ctx, bad);
63
- // a declining test card answers a 402 card_error naming the failed charge Stripe records for the attempt
64
- const decline = declineFor(finder(ctx), params);
65
- if (decline) return declinedCharge(ctx, decline);
66
- // capture=false authorizes only: succeeded and paid, but not captured, with a capture deadline
67
- const captured = !(params.capture !== undefined && !asBool(params.capture));
68
- // the id comes first so the charge's own `refunds` list can carry its URL
69
- const id = typeof params.id === 'string' && params.id ? params.id : ctx.mint('charge');
70
- const amount = Number(params.amount) || 0;
71
- // a captured charge credits the balance; an authorization does when it is captured
72
- const card = String(params.source ?? params.payment_method ?? (params.card as Row | undefined)?.number ?? '');
73
- const bt = captured ? await settleCharge(ctx, id, amount, String(params.currency ?? 'usd'), card) : null;
74
- const details = paymentMethodDetails(ctx, params.payment_method ?? params.source ?? params.card);
75
- await created(ctx, 'charge', { ...withoutCard(params), id }, { ...chargeDefaults(id, amount, captured, ctx.occurredAt), balance_transaction: bt, payment_method_details: details ?? null }, { operation: 'charge.succeeded' });
76
- await afterCharge(ctx, id, { amount, currency: String(params.currency ?? 'usd'), application_fee_amount: params.application_fee_amount, destination: (params.transfer_data as Row | undefined)?.destination }, params.payment_method ?? params.source ?? params.card);
77
- return ctx.reply(chargeBody(ctx, ctx.get('charge', id)!));
78
- };
79
-
80
- // capture takes `amount` (the whole authorization by default; docs.stripe.com/api/charges/capture); the rest of a
81
- // partial capture is released with no Refund (captureAuthorization). `amount_to_capture` is the
82
- // PaymentIntent capture's parameter, which a charge's capture does not know.
83
- const capture: Semantics = async (ctx) => {
84
- const id = at(ctx, 'charge');
85
- const ch = ctx.get('charge', id);
86
- if (!ch) return chargeMissing(ctx, id);
87
- const refused = ctx.legal('charge', 'captured', 'PostChargesChargeCapture', ch.captured === true ? 'true' : 'false', undefined, id);
88
- if (refused) return ctx.refuse(refused);
89
- // "Capturing a charge will always succeed, unless the charge is already refunded, expired, captured, or an invalid
90
- // capture amount is specified" (docs.stripe.com/api/charges/capture): a released authorization is refunded
91
- if (ch.refunded === true) return fail(ctx, `Charge ${id} has already been refunded.`, 400, 'charge_already_refunded');
92
- // a canceled PaymentIntent's authorization is released: "After it's canceled, no additional charges are made by the
93
- // PaymentIntent and any operations on the PaymentIntent fail with an error" (docs.stripe.com/api/payment_intents/cancel).
94
- // Where the documentation stops and the twin decides: its charge's capture answers payment_intent_unexpected_state
95
- // ("The PaymentIntent's state was incompatible with the operation", docs.stripe.com/error-codes), in the twin's words.
96
- const intent = typeof ch.payment_intent === 'string' ? ctx.get('payment_intent', ch.payment_intent) : undefined;
97
- if (intent?.status === 'canceled') return fail(ctx, `Charge ${id} belongs to PaymentIntent ${String(intent.id)}, which is canceled; its authorization was released.`, 400, 'payment_intent_unexpected_state');
98
- const full = Number(ch.amount) || 0;
99
- const toCapture = ctx.params.amount !== undefined ? Math.min(full, Math.max(0, Math.trunc(Number(ctx.params.amount) || 0))) : full;
100
- return ctx.reply(chargeBody(ctx, await captureAuthorization(ctx, ch, toCapture, 'PostChargesChargeCapture')));
101
- };
102
-
103
- /** A capture=false charge refunded while uncaptured: its authorization is released by the refund, as it would be
104
- * "automatically refunded if uncaptured" (spec/openapi.json.gz, `capture_before`). The charge stays uncaptured and
105
- * becomes refunded in full, a Refund records the release, and no money moves: none was ever received. Written as
106
- * charge.refunded. Where the documentation stops and the twin decides: Stripe's basil change ("Partially capturing or
107
- * canceling payments no longer creates a Refund", docs.stripe.com/changelog/basil/2025-03-31/remove-refund-from-partial-
108
- * capture-and-payment-cancellation-flow) names partial capture and cancellation, not a refund asked for, so a refund
109
- * still makes one; its reason is null and it has no balance transaction. Answers the Refund. */
110
- export async function releaseAuthorization(ctx: SemanticsContext, ch: Row, operationId: string, given: Row = {}): Promise<Row> {
111
- const id = String(ch.id);
112
- const full = Number(ch.amount) || 0;
113
- if (ch.refunded !== true) ctx.legal('charge', 'refunded', operationId, 'false', 'true', id);
114
- const refund = await created(ctx, 'refund', { ...given, charge: id, amount: full - (Number(ch.amount_refunded) || 0), currency: String(ch.currency ?? 'usd') }, {
115
- status: 'succeeded', metadata: {}, reason: null, receipt_number: null,
116
- balance_transaction: null, source_transfer_reversal: null, transfer_reversal: null, ...refundDestination(ch, 'reversal'),
117
- ...(typeof ch.payment_intent === 'string' ? { payment_intent: ch.payment_intent } : {}),
118
- });
119
- await ctx.write('charge', id, { amount_refunded: full, refunded: true }, 'charge.refunded');
120
- return refund;
121
- }
122
-
123
- /** A PaymentIntent's authorization released by its cancel: "For PaymentIntents with a `status` of `requires_capture`, the
124
- * remaining `amount_capturable` is automatically refunded" (docs.stripe.com/api/payment_intents/cancel), and since basil
125
- * a cancellation makes no Refund: "`amount_captured` will be 0 instead of `nil` in payment cancellation flows",
126
- * "`amount_refunded` will no longer be updated by these actions", "`refunded` will no longer be `true` for payment
127
- * cancellation flows", and no charge.refunded is sent (docs.stripe.com/changelog/basil/2025-03-31/remove-refund-from-
128
- * partial-capture-and-payment-cancellation-flow). The charge stays uncaptured; nothing is written as an event. */
129
- export async function cancelAuthorization(ctx: SemanticsContext, ch: Row): Promise<Row> {
130
- return ctx.write('charge', String(ch.id), { amount_captured: 0 }, 'charge.authorization_released');
131
- }
132
-
133
- /** An authorized charge captured, by the charge's capture or its PaymentIntent's: what is captured credits the balance.
134
- * Written as charge.captured, "Occurs whenever a previously uncaptured charge is captured" (docs.stripe.com/api/events/types).
135
- * A partial capture releases the rest with no Refund and leaves amount_refunded and refunded as they were: "The following
136
- * flows no longer result in a `Refund` object created and linked to the payment: Partial capture", "`amount_refunded`
137
- * will no longer be updated by these actions", and "There will only be a single balance transaction for partial captures"
138
- * (docs.stripe.com/changelog/basil/2025-03-31/remove-refund-from-partial-capture-and-payment-cancellation-flow).
139
- * The caller has asked the machine whether it may capture. */
140
- export async function captureAuthorization(ctx: SemanticsContext, ch: Row, toCapture: number, _operationId: string): Promise<Row> {
141
- const id = String(ch.id);
142
- const bt = toCapture > 0 ? await settleCharge(ctx, id, toCapture, String(ch.currency ?? 'usd'), String(ch.source ?? ch.payment_method ?? '')) : null;
143
- return ctx.write('charge', id, { captured: true, amount_captured: toCapture, balance_transaction: bt }, 'charge.captured');
144
- }
145
-
146
- const listCharges: Semantics = async (ctx) => {
1
+ // Stripe's charges operations (docs/contributing/architecture.md, "What an author writes"): each handler over the
2
+ // handler contract's context, computing with the engine (../engine/).
3
+ import type { HandlerContext } from '@volter/world-core';
4
+ import { at, chargeBody, chargeMissing, expanded, list, newest, where } from './shared.ts';
5
+ export async function GetCharges(ctx: HandlerContext): Promise<Response> {
147
6
  const items = where(ctx, newest(ctx, 'charge'), {
148
7
  customer: (c, v) => c.customer === v,
149
8
  payment_intent: (c, v) => c.payment_intent === v,
150
9
  });
151
10
  return list(ctx, 'charge', items.map((c) => chargeBody(ctx, c)));
152
- };
153
-
154
- const searchCharges: Semantics = async (ctx) => {
155
- const found = searchOver(newest(ctx, 'charge'), ctx.params, path(ctx));
156
- const body = found.body as { data?: Row[] };
157
- if (found.status === 200 && Array.isArray(body.data)) body.data = body.data.map((c) => chargeBody(ctx, c));
158
- return send(ctx, found);
159
- };
11
+ }
160
12
 
161
- const retrieve: Semantics = async (ctx) => {
13
+ export async function GetChargesCharge(ctx: HandlerContext): Promise<Response> {
162
14
  const c = ctx.get('charge', at(ctx, 'charge'));
163
15
  return c ? ctx.reply(expanded(ctx, 'charge', chargeBody(ctx, c))) : chargeMissing(ctx, at(ctx, 'charge'));
164
- };
165
-
166
- // fraud_details[user_report] marks a charge safe or fraudulent; Stripe keeps its own stripe_report
167
- const update: Semantics = async (ctx) => {
168
- const id = at(ctx, 'charge');
169
- const existing = ctx.get('charge', id);
170
- if (!existing) return chargeMissing(ctx, id);
171
- const fields: Row = { ...ctx.params };
172
- const fd = ctx.params.fraud_details;
173
- const fraud = fd && typeof fd === 'object' && 'user_report' in (fd as Row) ? fraudReport(ctx, existing, fd as Row) : undefined;
174
- if (fraud instanceof Response) return fraud;
175
- if (fraud) fields.fraud_details = fraud;
176
- return ctx.reply(chargeBody(ctx, await ctx.write('charge', id, fields, 'charge.update')));
177
- };
178
-
179
- // ── the charge's own refunds: listed, created and read through it; another charge's refund is not found ──
180
-
181
- const refunds: Semantics = async (ctx) => {
182
- const id = at(ctx, 'charge');
183
- if (!ctx.get('charge', id)) return chargeMissing(ctx, id);
184
- return list(ctx, 'refund', newest(ctx, 'refund').filter((r) => r.charge === id));
185
- };
186
-
187
- const createRefund: Semantics = async (ctx) => {
188
- const ch = ctx.get('charge', at(ctx, 'charge'));
189
- if (!ch) return chargeMissing(ctx, at(ctx, 'charge'));
190
- return refundCharge(ctx, ch, ctx.params);
191
- };
192
-
193
- const chargeRefund = (ctx: SemanticsContext): Row | Response => {
194
- if (!ctx.get('charge', at(ctx, 'charge'))) return chargeMissing(ctx, at(ctx, 'charge'));
195
- const r = ctx.get('refund', at(ctx, 'refund'));
196
- return r && r.charge === at(ctx, 'charge') ? r : fail(ctx, `No such refund: '${at(ctx, 'refund')}'`, 404, 'resource_missing');
197
- };
198
-
199
- const refund: Semantics = async (ctx) => {
200
- const r = chargeRefund(ctx);
201
- return r instanceof Response ? r : ctx.reply(r);
202
- };
203
-
204
- const updateRefund: Semantics = async (ctx) => {
205
- const r = chargeRefund(ctx);
206
- return r instanceof Response ? r : ctx.reply(await ctx.write('refund', at(ctx, 'refund'), ctx.params, 'refund.update'));
207
- };
208
-
209
- export const charges: Record<string, Semantics> = {
210
- PostCharges: create,
211
- PostChargesChargeCapture: capture,
212
- GetCharges: listCharges,
213
- GetChargesSearch: searchCharges,
214
- GetChargesCharge: retrieve,
215
- PostChargesCharge: update,
216
- GetChargesChargeRefunds: refunds,
217
- PostChargesChargeRefunds: createRefund,
218
- GetChargesChargeRefundsRefund: refund,
219
- PostChargesChargeRefundsRefund: updateRefund,
220
- };
16
+ }
@@ -1,108 +1,35 @@
1
- // Checkout Session semantics. The machine in ../manifest.ts says an open session expires or
2
- // completes and nothing else does. Stripe completes a session when the customer pays on its hosted
3
- // page (src/screens/checkout.tsx, the session's url), which has no API verb; completing links what
4
- // the payment made: a succeeded PaymentIntent, an active (or
5
- // trialing) subscription, or a succeeded SetupIntent. List and retrieve are the derived core's.
6
- import type { Semantics, SemanticsContext } from '@volter/world-core';
7
- import { addBillingInterval, applyCouponDiscount, buildDiscount, buildSubscriptionItemsList, declineFor, lineItemEntries, mintClientSecret, paginate, paymentMethodSubObject, resolveLineItem, resolveSubscriptionBillingInterval, resolveTrial } from '../stripe-twin.ts';
8
- import { afterSuccessOf } from './after-payment.ts';
9
- import { actingAccount } from './ledger.ts';
10
- import { mintCharge } from './payment-intents.ts';
11
- import { collectSubscriptionInvoice, draftSubscriptionInvoice } from './renewals.ts';
12
- import { storeItems } from './subscriptions.ts';
13
- import { at, created, fail, finder, path, redeemCoupon, type Row } from './shared.ts';
14
-
15
- const CS = 'checkout.session';
16
- const sessionMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such checkout.session: '${id}'`, 404, 'resource_missing');
17
-
18
- // Stripe normalizes the create input into the Session: line_items become the list envelope, and
19
- // subscription_data (create-only) is kept aside for completion to copy onto the subscription
20
- /** The discount a session is created with (discounts[0][promotion_code] or [coupon]), or Stripe's refusal: an unknown
21
- * code or coupon, and one that has expired or been deactivated, are refused at create
22
- * (docs.stripe.com/api/checkout/sessions/create#create_checkout_session-discounts, docs.stripe.com/error-codes#coupon-expired).
23
- * Where the documentation stops and the twin decides: the refusals' wording. */
24
- function discountOf(ctx: SemanticsContext, params: Row, now: number): { coupon: Row; promotion?: string } | Response | undefined {
25
- const first = Array.isArray(params.discounts) ? (params.discounts[0] as Row | undefined) : undefined;
26
- if (!first || typeof first !== 'object') return undefined;
27
- const expired = (what: string) => ctx.refuse({ status: 400, code: 'coupon_expired', param: 'discounts', message: `This ${what} has expired.` });
28
- let promotion: string | undefined;
29
- let couponId = typeof first.coupon === 'string' ? first.coupon : undefined;
30
- if (typeof first.promotion_code === 'string') {
31
- const pc = ctx.get('promotion_code', first.promotion_code);
32
- if (!pc) return ctx.refuse({ status: 400, code: 'resource_missing', param: 'discounts[0][promotion_code]', message: `No such promotion code: '${first.promotion_code}'` });
33
- if (typeof pc.expires_at === 'number' && pc.expires_at <= now) return expired('promotion code');
34
- if (pc.active === false) return ctx.refuse({ status: 400, param: 'discounts[0][promotion_code]', message: 'This promotion code is inactive.' });
35
- promotion = String(pc.id);
36
- couponId = typeof pc.coupon === 'string' ? pc.coupon : String((pc.coupon as Row | undefined)?.id ?? '');
37
- }
38
- const coupon = couponId ? ctx.get('coupon', couponId) : undefined;
39
- if (!coupon) return ctx.refuse({ status: 400, code: 'resource_missing', param: 'discounts[0][coupon]', message: `No such coupon: '${couponId ?? ''}'` });
40
- if (coupon.valid === false || (typeof coupon.redeem_by === 'number' && coupon.redeem_by <= now)) return expired('coupon');
41
- return { coupon, ...(promotion ? { promotion } : {}) };
42
- }
43
-
44
- /** A payment or subscription session with nothing to sell (docs.stripe.com/api/checkout/sessions/create#create_checkout_session-line_items). */
45
- function noLineItems(ctx: SemanticsContext): Response {
46
- return fail(ctx, 'You must provide at least one recurring price in `subscription` mode when not using `line_items[].price_data` or a Price with `recurring`, and at least one `line_items` for `payment` mode.', 400, 'parameter_missing');
47
- }
48
-
49
- /** A destination charge to an account the platform does not have (docs.stripe.com/connect/destination-charges). */
50
- function noDestination(ctx: SemanticsContext, destination: unknown): Response {
51
- return ctx.refuse({ status: 400, code: 'resource_missing', param: 'payment_intent_data[transfer_data][destination]', message: `No such destination: '${String(destination)}'` });
52
- }
53
-
54
- /** A session's trial Stripe refuses: `subscription_data.trial_end` "Has to be at least 48 hours in the future", and
55
- * `subscription_data.trial_period_days` "Has to be at least 1" (docs.stripe.com/api/checkout/sessions/create). Where the
56
- * documentation stops and the twin decides: the refusals' wording. */
57
- function sessionTrialRefused(ctx: SemanticsContext, data: unknown, now: number): Response | undefined {
58
- if (!data || typeof data !== 'object') return undefined;
59
- const { trial_end: end, trial_period_days: days } = data as Row;
60
- const endRefused = end !== undefined && !(Number.isInteger(Number(end)) && Number(end) >= now + 48 * 3600);
61
- const daysRefused = days !== undefined && !(Number.isInteger(Number(days)) && Number(days) >= 1);
62
- return endRefused || daysRefused ? sessionTrialRefusal(ctx, endRefused ? 'trial_end' : 'trial_period_days') : undefined;
63
- }
64
-
65
- function sessionTrialRefusal(ctx: SemanticsContext, param: 'trial_end' | 'trial_period_days'): Response {
66
- const message = param === 'trial_end' ? 'The `subscription_data[trial_end]` timestamp has to be at least 48 hours in the future.' : 'The `subscription_data[trial_period_days]` has to be at least 1.';
67
- return ctx.refuse({ status: 400, param: `subscription_data[${param}]`, message });
68
- }
69
-
70
- /** The payment method types a session takes: the list a caller pinned to an earlier version gives as
71
- * payment_method_types ("A list of the types of payment methods (e.g., `card`) this Checkout Session can accept", the
72
- * parameter the served version's allowed_payment_method_types is described against: "Unlike `payment_method_types`,
73
- * this acts as a filter on the dynamically computed set of eligible payment methods", docs.stripe.com/api/checkout/
74
- * sessions/create), else the allowed_payment_method_types of the served one, else card. Where the documentation stops
75
- * and the twin decides: an allowed list is answered whole (the twin does not compute eligibility), and without one the
76
- * session takes card (the account's enabled methods are not modelled). */
77
- function sessionMethodTypes(params: Row): string[] {
78
- const given = Array.isArray(params.payment_method_types) ? params.payment_method_types : params.allowed_payment_method_types;
79
- return Array.isArray(given) && given.length ? given.map(String) : ['card'];
80
- }
81
-
82
- /** Metadata passed on to what a session makes (payment_intent_data[metadata], subscription_data[metadata]) as a
83
- * create's own is kept: "Individual keys can be unset by posting an empty value to them. All keys can be unset by
84
- * posting an empty value to `metadata`" (every metadata parameter, docs.stripe.com/api/metadata), so a key posted
85
- * empty is not set, and metadata posted empty is {} (stripe-server.ts createMetadata, the top-level case). */
86
- export function nestedMetadata(given: unknown): Row | undefined {
87
- if (given === '') return {};
88
- if (!given || typeof given !== 'object' || Array.isArray(given)) return undefined;
89
- return Object.fromEntries(Object.entries(given as Row).filter(([, v]) => v !== ''));
1
+ // Stripe's checkout operations (docs/contributing/architecture.md, "What an author writes"): each handler over the
2
+ // handler contract's context, computing with the engine (../engine/).
3
+ import type { HandlerContext } from '@volter/world-core';
4
+ import { CS, sessionMethodTypes } from '../engine/checkout.ts';
5
+ import type { Row } from '../engine/common.ts';
6
+ import { applyCouponDiscount, lineItemEntries, nowUnix, paginate, resolveLineItem } from '../engine/stripe.ts';
7
+ import { actingAccount, at, checkoutNoDestination, created, discountOf, fail, finder, noLineItems, path, sessionMissing, sessionTrialRefused } from './shared.ts';
8
+
9
+ export async function GetCheckoutSessionsSessionLineItems(ctx: HandlerContext): Promise<Response> {
10
+ const s = ctx.get(CS, at(ctx, 'session'));
11
+ if (!s) return sessionMissing(ctx, at(ctx, 'session'));
12
+ // source: spec:GetCheckoutSessionsSessionLineItems "There is also a URL where you can retrieve the full (paginated) list of line items."
13
+ const li = s.line_items as { data?: Row[] } | undefined;
14
+ const { page, hasMore } = paginate(Array.isArray(li?.data) ? li!.data! : [], ctx.params);
15
+ return ctx.reply({ object: 'list', url: path(ctx), has_more: hasMore, data: page });
90
16
  }
91
17
 
92
- const create: Semantics = async (ctx) => {
18
+ export async function PostCheckoutSessions(ctx: HandlerContext): Promise<Response> {
93
19
  const params = ctx.params;
94
20
  const mode = typeof params.mode === 'string' ? params.mode : 'payment';
95
21
  if (!['payment', 'subscription', 'setup'].includes(mode)) return fail(ctx, 'Invalid mode: must be one of payment, subscription, or setup.', 400, 'parameter_invalid_string_enum');
96
22
  const entries = lineItemEntries(params);
97
23
  if (mode !== 'setup' && entries.length === 0) return noLineItems(ctx);
98
- // hosted Checkout (the only ui_mode the twin models) needs success_url
99
- if (params.success_url === undefined || params.success_url === '') return fail(ctx, 'Missing required param: success_url.', 400, 'parameter_missing');
24
+ // source: spec:/paths/~1v1~1checkout~1sessions/post/requestBody/content/application~1x-www-form-urlencoded/schema/properties/ui_mode "The UI mode of the Session."
25
+ const custom = params.ui_mode === 'custom';
26
+ if (!custom && (params.success_url === undefined || params.success_url === '')) return fail(ctx, 'Missing required param: success_url.', 400, 'parameter_missing');
100
27
  const sessionCurrency = typeof params.currency === 'string' ? params.currency : 'usd';
101
28
  const resolved = entries.map((e, i) => resolveLineItem(e, i, sessionCurrency, finder(ctx)));
102
29
  const missing = resolved.find((r) => r.priceMissing);
103
30
  if (missing) return fail(ctx, `No such price: '${missing.priceMissing}'`, 400, 'resource_missing');
104
31
  const items = resolved.map((r) => r.item);
105
- const now = Number(ctx.now());
32
+ const now = Number(nowUnix(ctx.occurredAt));
106
33
  const trialRefused = sessionTrialRefused(ctx, params.subscription_data, now);
107
34
  if (trialRefused) return trialRefused;
108
35
  const discount = discountOf(ctx, params, now);
@@ -114,7 +41,10 @@ const create: Semantics = async (ctx) => {
114
41
  const fields: Row = {
115
42
  mode, status: 'open',
116
43
  payment_status: mode === 'setup' ? 'no_payment_required' : 'unpaid',
117
- url: `https://checkout.stripe.com/c/pay/${id}`, success_url: params.success_url ?? null, cancel_url: params.cancel_url ?? null,
44
+ ui_mode: custom ? 'custom' : 'hosted',
45
+ ...(custom ? { client_secret: `${id}_secret_${await ctx.secret(`checkout-secret:${id}`)}`, return_url: params.return_url ?? null } : {}),
46
+ allow_promotion_codes: params.allow_promotion_codes === true || params.allow_promotion_codes === 'true',
47
+ url: custom ? null : `https://checkout.stripe.com/c/pay/${id}`, success_url: params.success_url ?? null, cancel_url: params.cancel_url ?? null,
118
48
  customer: params.customer ?? null, client_reference_id: params.client_reference_id ?? null,
119
49
  customer_email: params.customer_email ?? null,
120
50
  // whether paying makes a customer: in payment mode only when asked (`always`), `if_required` by default
@@ -144,190 +74,9 @@ const create: Semantics = async (ctx) => {
144
74
  const pid = params.payment_intent_data && typeof params.payment_intent_data === 'object' ? (params.payment_intent_data as Row) : undefined;
145
75
  if (pid && mode === 'payment') {
146
76
  const destination = (pid.transfer_data as Row | undefined)?.destination;
147
- if (destination !== undefined && (typeof destination !== 'string' || !ctx.get('account', destination))) return noDestination(ctx, destination);
77
+ if (destination !== undefined && (typeof destination !== 'string' || !ctx.get('account', destination))) return checkoutNoDestination(ctx, destination);
148
78
  fields._payment_intent_data = pid;
149
79
  }
150
80
  if (discount) fields._discount = { coupon: String(discount.coupon.id), promotion_code: discount.promotion ?? null };
151
81
  return ctx.reply(await created(ctx, CS, { id, ...fields }, {}));
152
- };
153
-
154
- // the session's items, a page at a time
155
- const lineItems: Semantics = async (ctx) => {
156
- const s = ctx.get(CS, at(ctx, 'session'));
157
- if (!s) return sessionMissing(ctx, at(ctx, 'session'));
158
- const li = s.line_items as { data?: Row[] } | undefined;
159
- const { page, hasMore } = paginate(Array.isArray(li?.data) ? li!.data! : [], ctx.params);
160
- return ctx.reply({ object: 'list', url: path(ctx), has_more: hasMore, data: page });
161
- };
162
-
163
- const expire: Semantics = async (ctx) => {
164
- const id = at(ctx, 'session');
165
- const s = ctx.get(CS, id);
166
- if (!s) return sessionMissing(ctx, id);
167
- const refused = ctx.legal(CS, 'status', 'PostCheckoutSessionsSessionExpire', s.status, undefined, id);
168
- if (refused) return ctx.refuse(refused);
169
- return ctx.reply(await ctx.write(CS, id, { status: 'expired', url: null }, 'checkout.session.expire'));
170
- };
171
-
172
- /** The card the customer entered on the page. */
173
- export type EnteredCard = { number: string; exp_month: number; exp_year: number };
174
-
175
- /** What completing a session makes, by its mode, as the fields that link it: a payment is a PaymentIntent paid with
176
- * the entered card, its charge in the ledger as any payment's is. */
177
- async function complete(ctx: SemanticsContext, id: string, existing: Row, card?: EnteredCard): Promise<Row> {
178
- const link: Row = { status: 'complete' };
179
- const customer = typeof existing.customer === 'string' ? existing.customer : undefined;
180
- const currency = typeof existing.currency === 'string' ? existing.currency : 'usd';
181
- // the entered card becomes a PaymentMethod carrying what a later charge to it answers and what Stripe does after it
182
- // succeeds (docs.stripe.com/testing#declined-payments, #disputes)
183
- const made = card ? await created(ctx, 'payment_method', {}, { type: 'card', customer: null, livemode: false, billing_details: { address: null, email: null, name: null, phone: null }, ...paymentMethodSubObject('card', { card }), _declineOutcome: declineFor(() => undefined, { card }) ?? null, ...(afterSuccessOf(ctx, card) ? { _afterSuccess: afterSuccessOf(ctx, card) } : {}) }) : undefined;
184
- // and Checkout attaches it to the customer when it saves it: a subscription's card ("If your Checkout Session uses
185
- // subscription mode, Stripe saves the payment method by default", docs.stripe.com/payments/checkout/how-checkout-works),
186
- // a setup session's (it exists to save one), and a payment's only under payment_intent_data.setup_future_usage ("to have
187
- // Checkout automatically attach the payment method to the Customer you pass in", docs.stripe.com/api/checkout/sessions/
188
- // create#create_checkout_session-customer). Attaching is its own write, so payment_method.attached is delivered ("Occurs
189
- // whenever a new payment method is attached to a customer", docs.stripe.com/api/events/types) before what the card then
190
- // pays for. Where the documentation stops and the twin decides: the order of the events, which Stripe does not
191
- // guarantee (docs.stripe.com/webhooks#event-ordering). A gap: the customer's own opt-in to save a payment's card
192
- // (saved_payment_method_options.payment_method_save) is not modelled; the page offers no such box.
193
- const saves = existing.mode !== 'payment' || !!((ctx.row(CS, id)?._payment_intent_data ?? {}) as Row).setup_future_usage;
194
- const pm = made && customer && saves ? await ctx.write('payment_method', String(made.id), { customer }, 'payment_method.attach') : made;
195
- if (existing.mode === 'payment') {
196
- const amount = Number(existing.amount_total) || 0;
197
- const piId = ctx.mint('payment_intent');
198
- const pid = (ctx.row(CS, id)?._payment_intent_data ?? {}) as Row;
199
- const connect = {
200
- ...(pid.application_fee_amount !== undefined ? { application_fee_amount: Math.trunc(Number(pid.application_fee_amount) || 0) } : {}),
201
- ...(pid.transfer_data && typeof pid.transfer_data === 'object' ? { transfer_data: { destination: (pid.transfer_data as Row).destination } } : {}),
202
- ...(typeof pid.description === 'string' ? { description: pid.description } : {}),
203
- // the session's setup_future_usage is its PaymentIntent's (payment_intent_data: "A subset of parameters to be passed to PaymentIntent creation")
204
- ...(typeof pid.setup_future_usage === 'string' ? { setup_future_usage: pid.setup_future_usage } : {}),
205
- ...(nestedMetadata(pid.metadata) ? { metadata: nestedMetadata(pid.metadata) } : {}),
206
- };
207
- const fields = { amount, currency, id: piId, ...(customer ? { customer } : {}), ...(pm ? { payment_method: pm.id } : {}), ...connect };
208
- await created(ctx, 'payment_intent', fields, { status: 'succeeded', amount_received: amount, client_secret: mintClientSecret(piId), livemode: false, payment_method_types: Array.isArray(existing.payment_method_types) ? existing.payment_method_types : ['card'] });
209
- const charge = await mintCharge(ctx, piId, fields, amount);
210
- await ctx.write('payment_intent', piId, { latest_charge: charge }, 'payment_intent.succeeded');
211
- link.payment_intent = piId;
212
- link.payment_status = 'paid';
213
- } else if (existing.mode === 'subscription') {
214
- if (customer) {
215
- // the subscription bills the session's own (already resolved) line items, and takes the
216
- // session's subscription_data: metadata, description, and a trial as its first period
217
- const now = Number(ctx.now());
218
- // "Line items with one-time Prices will be on the initial invoice only" (docs.stripe.com/api/checkout/sessions/
219
- // create#create_checkout_session-line_items): the subscription's items are the recurring ones
220
- const inline = (ctx.row(CS, id)?._price_data as Array<Row | null> | undefined) ?? [];
221
- const lines = ((existing.line_items as { data?: unknown } | undefined)?.data as Row[] | undefined) ?? [];
222
- const allLineItems = inline.some(Boolean) ? await sessionPrices(ctx, lines, inline) : lines;
223
- const sessionLineItems = allLineItems.filter((it) => !isOneTime(it));
224
- const oneTime = allLineItems.filter((it) => isOneTime(it));
225
- const { interval, interval_count } = resolveSubscriptionBillingInterval(sessionLineItems, finder(ctx));
226
- const subId = ctx.mint('subscription');
227
- // the session's view never carries its create-only subscription_data: read the stored row
228
- const raw = ctx.row(CS, id)?._subscription_data;
229
- const subData = (raw && typeof raw === 'object' ? raw : {}) as Row;
230
- const subMetadata = nestedMetadata(subData.metadata) ?? {};
231
- // a trial of `trial_period_days` from now, or to `trial_end` (docs.stripe.com/payments/checkout/free-trials)
232
- const trialEnd = resolveTrial(subData, now)?.end ?? null;
233
- const sub = await created(ctx, 'subscription', { customer, id: subId }, {
234
- status: trialEnd === null ? 'active' : 'trialing', livemode: false, currency, collection_method: 'charge_automatically',
235
- // a trial's end is the billing anchor (docs.stripe.com/api/subscriptions/create#create_subscription-trial_end)
236
- cancel_at_period_end: false, start_date: now, billing_cycle_anchor: trialEnd ?? now, metadata: subMetadata,
237
- description: typeof subData.description === 'string' ? subData.description : null,
238
- // the session's discount is the subscription's (docs.stripe.com/payments/checkout/discounts)
239
- discounts: sessionDiscount(ctx, id, customer, now), billing_schedules: [],
240
- trial_start: trialEnd === null ? null : now, trial_end: trialEnd,
241
- current_period_start: now, current_period_end: trialEnd ?? addBillingInterval(now, interval, interval_count),
242
- // Checkout saves the card as the subscription's default payment method (docs.stripe.com/payments/checkout/how-checkout-works)
243
- default_payment_method: pm?.id ?? null,
244
- items: buildSubscriptionItemsList(sessionLineItems, subId, now, finder(ctx)),
245
- automatic_tax: { enabled: false, liability: null }, billing_mode: { type: 'classic' },
246
- invoice_settings: { issuer: { type: 'self' } },
247
- });
248
- await storeItems(ctx, subId, ((sub.items as Row | undefined)?.data as Row[] | undefined) ?? []);
249
- // its first invoice, paid on the page by the card just entered (nothing is due on a trial's)
250
- // (docs.stripe.com/billing/subscriptions/overview#how-payments-work-subscriptions)
251
- const invoice = await draftSubscriptionInvoice(ctx, sub, { start: now, end: Number(sub.current_period_end), reason: 'subscription_create', trial: trialEnd !== null, once: oneTime });
252
- await collectSubscriptionInvoice(ctx, invoice, now, 'external');
253
- await ctx.write('subscription', subId, { latest_invoice: invoice }, 'subscription.update');
254
- link.subscription = sub.id;
255
- link.invoice = invoice;
256
- }
257
- link.payment_status = 'paid';
258
- } else link.setup_intent = await completeSetup(ctx, customer, pm);
259
- return link;
260
82
  }
261
-
262
- /** A line item of a one-time Price: a stored Price, or an inline `price_data`, without `recurring` ("Line items with
263
- * one-time Prices will be on the initial invoice only", docs.stripe.com/api/checkout/sessions/create). */
264
- export function isOneTime(item: Row, priceData?: Row | null): boolean {
265
- if (priceData) return !priceData.recurring;
266
- return !!item.price && typeof item.price === 'object' && !(item.price as Row).recurring;
267
- }
268
-
269
- /** A subscription session's lines at completion, each inline `price_data` made the Price it generates ("Data used to
270
- * generate a new Price object inline", the same page), under the product it names or one made from its product_data,
271
- * so the subscription's items bill it. Where the documentation stops and the twin decides: such a Price is archived
272
- * (active=false), as a subscription item's price_data makes one (docs.stripe.com/products-prices/manage-prices). */
273
- async function sessionPrices(ctx: SemanticsContext, lines: Row[], inline: Array<Row | null>): Promise<Row[]> {
274
- const out: Row[] = [];
275
- for (const [i, line] of lines.entries()) {
276
- const d = inline[i];
277
- if (!d) { out.push(line); continue; }
278
- const pd = d.product_data && typeof d.product_data === 'object' ? (d.product_data as Row) : undefined;
279
- const product = typeof d.product === 'string' ? d.product : String((await created(ctx, 'product', { name: pd?.name ?? 'Item' }, { active: true, livemode: false, images: [], marketing_features: [], metadata: {}, updated: ctx.now() })).id);
280
- const r = d.recurring && typeof d.recurring === 'object' ? (d.recurring as Row) : undefined;
281
- const price = await created(ctx, 'price', {
282
- product, currency: String(d.currency ?? line.currency ?? 'usd').toLowerCase(),
283
- unit_amount: d.unit_amount_decimal !== undefined ? null : Math.trunc(Number(d.unit_amount) || 0),
284
- unit_amount_decimal: d.unit_amount_decimal !== undefined ? String(d.unit_amount_decimal) : String(Math.trunc(Number(d.unit_amount) || 0)),
285
- ...(r ? { recurring: { interval: r.interval, interval_count: Math.max(1, Math.trunc(Number(r.interval_count ?? 1)) || 1), usage_type: 'licensed', trial_period_days: null, meter: null } } : { recurring: null }),
286
- }, { active: false, livemode: false, billing_scheme: 'per_unit', type: r ? 'recurring' : 'one_time', metadata: {}, lookup_key: null, nickname: null, tax_behavior: 'unspecified' });
287
- out.push({ ...line, price });
288
- }
289
- return out;
290
- }
291
-
292
- /** A setup session saves the card through a succeeded SetupIntent (docs.stripe.com/payments/save-and-reuse?platform=checkout). */
293
- async function completeSetup(ctx: SemanticsContext, customer: string | undefined, pm: Row | undefined): Promise<unknown> {
294
- const siId = ctx.mint('setup_intent');
295
- const si = await created(ctx, 'setup_intent', { id: siId, ...(customer ? { customer } : {}), ...(pm ? { payment_method: pm.id } : {}) }, { status: 'succeeded', usage: 'off_session', client_secret: mintClientSecret(siId), payment_method_types: ['card'], livemode: false });
296
- return si.id;
297
- }
298
-
299
- /** The discount a completed subscription session gives its subscription. */
300
- function sessionDiscount(ctx: SemanticsContext, id: string, customer: string, now: number): Row[] {
301
- const d = ctx.row(CS, id)?._discount as Row | undefined;
302
- const coupon = d && typeof d.coupon === 'string' ? ctx.get('coupon', d.coupon) : undefined;
303
- return coupon ? [{ ...buildDiscount(coupon, customer, now), promotion_code: d?.promotion_code ?? null, checkout_session: id }] : [];
304
- }
305
-
306
- /** The customer paying on the hosted page (src/screens/checkout.tsx): the external actor's move on the
307
- * session's machine, then what the payment made, linked, and the session stored complete. */
308
- export async function completeSession(ctx: SemanticsContext, id: string, existing: Row, details: Row = {}, card?: EnteredCard): Promise<Row | Response> {
309
- const refused = ctx.legal(CS, 'status', 'PostCheckoutSessionsSession', existing.status, 'complete', id, 'external');
310
- if (refused) return ctx.refuse(refused);
311
- const link = await complete(ctx, id, existing, card);
312
- // the discount the buyer took is redeemed (shared.ts redeemCoupon)
313
- const d = ctx.row(CS, id)?._discount as Row | undefined;
314
- if (d) await redeemCoupon(ctx, d.coupon, d.promotion_code);
315
- // the session's url "is only present when the session is active" (docs.stripe.com/api/checkout/sessions/object)
316
- return ctx.write(CS, id, { ...details, ...link, url: null }, 'checkout.session.completed');
317
- }
318
-
319
- const update: Semantics = async (ctx) => {
320
- const id = at(ctx, 'session');
321
- const existing = ctx.get(CS, id);
322
- if (!existing) return sessionMissing(ctx, id);
323
- // a session completes only when its customer pays on the hosted page (src/screens/checkout.tsx); the API takes no
324
- // status, which the parameter check refuses as Stripe does (../stripe-params.ts)
325
- return ctx.reply(await ctx.write(CS, id, ctx.params, 'checkout.session.update'));
326
- };
327
-
328
- export const checkout: Record<string, Semantics> = {
329
- PostCheckoutSessions: create,
330
- GetCheckoutSessionsSessionLineItems: lineItems,
331
- PostCheckoutSessionsSessionExpire: expire,
332
- PostCheckoutSessionsSession: update,
333
- };
@@ -0,0 +1,19 @@
1
+ // Stripe's own moves as time passes (docs/contributing/architecture.md, "What an author writes": clock), caught up to
2
+ // the World's clock before anything is answered, so every door (the API, the hosted pages) reads the account as it
3
+ // stands now. Each move is the engine's.
4
+ import type { HandlerContext } from '@volter/world-core';
5
+ import { advanceBilling, advancePayouts, lapseCoupons, lapseRealtimeRequests, settleBankDebits, settleHeldRefunds } from './shared.ts';
6
+ export async function clock(ctx: HandlerContext): Promise<void> {
7
+ // a subscription renews at its period's end, its renewal charged an hour later
8
+ await advanceBilling(ctx);
9
+ // a submitted bank debit settles (in test mode at once)
10
+ await settleBankDebits(ctx);
11
+ // a coupon past its redeem_by is no longer valid
12
+ await lapseCoupons(ctx);
13
+ // a real-time authorization request no one answered is decided when its window ends
14
+ await lapseRealtimeRequests(ctx);
15
+ // a refund held for want of balance is made when funds cover it, before any payout takes them
16
+ await settleHeldRefunds(ctx);
17
+ // each account's automatic payouts
18
+ await advancePayouts(ctx);
19
+ }