@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,279 +0,0 @@
1
- // A subscription's invoices over time: the first one Checkout pays, and each renewal. Time's moves are made
2
- // as the twin's trial end already made them: caught up to the customer's clock (the World's, or its test
3
- // clock) whenever the twin is asked anything (../stripe-server.ts), each checked against the machines in
4
- // ../manifest.ts.
5
- //
6
- // - Checkout's subscription session pays the first invoice with the card the customer entered, which becomes the
7
- // subscription's default payment method (docs.stripe.com/payments/checkout/how-checkout-works); nothing is due
8
- // on a trial's.
9
- // - At each period's end Stripe drafts a `subscription_cycle` invoice for the next period and advances the
10
- // period; a trial's end is the first such renewal, and the subscription is active from then
11
- // (docs.stripe.com/billing/subscriptions/overview#how-payments-work-subscriptions,
12
- // docs.stripe.com/billing/subscriptions/trials).
13
- // - About an hour after, Stripe finalizes the draft and charges the subscription's default payment method,
14
- // else the customer's (docs.stripe.com/invoicing/integration/workflow-transitions#finalized,
15
- // docs.stripe.com/api/subscriptions/object#subscription_object-default_payment_method).
16
- // - A declined renewal leaves the invoice open with its attempt counted and the subscription past_due
17
- // (docs.stripe.com/billing/subscriptions/overview#payment-status); paying the invoice later, through the API
18
- // (docs.stripe.com/api/invoices/pay) or on the customer portal (docs.stripe.com/customer-management), makes it
19
- // active again.
20
- //
21
- // Where the documentation stops and the twin decides: Stripe retries a failed renewal on its Smart Retries
22
- // schedule and may then cancel or mark the subscription unpaid; the twin makes no retry (next_payment_attempt
23
- // is null) and leaves it past_due until the invoice is paid. A paused subscription (pause_collection) is not renewed.
24
- // - One set to cancel at its period's end is canceled when the period ends, with no renewal
25
- // (docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle).
26
- import { runAsVendorMove, type SemanticsContext } from '@volter/world-core';
27
- import { addBillingInterval, applyCouponDiscount, declineFor, mintClientSecret, priceAmount, resolveSubscriptionBillingInterval } from '../stripe-twin.ts';
28
- import { finalizedFields } from './invoices.ts';
29
- import { mintCharge } from './payment-intents.ts';
30
- import { at_, created, finder, type Row } from './shared.ts';
31
-
32
- const SUB = 'subscription';
33
- const INV = 'invoice';
34
- const HOUR = 3600;
35
- /** The statuses a subscription renews in. */
36
- const RENEWING = ['trialing', 'active', 'past_due'];
37
-
38
- /** A customer's now: its test clock's frozen time when it is on one (docs.stripe.com/billing/testing/test-clocks), the
39
- * World's otherwise. */
40
- export function clockNow(ctx: SemanticsContext, customer: unknown): number {
41
- const clock = typeof customer === 'string' ? ctx.get('customer', customer)?.test_clock : undefined;
42
- const frozen = typeof clock === 'string' ? ctx.get('test_helpers.test_clock', clock)?.frozen_time : undefined;
43
- return frozen !== undefined ? Number(frozen) : Number(ctx.now());
44
- }
45
-
46
- /** What a subscription's invoices charge: its own default payment method, else its customer's. */
47
- export function payerOf(ctx: SemanticsContext, sub: Row): string | undefined {
48
- return typeof sub.default_payment_method === 'string' && sub.default_payment_method ? sub.default_payment_method : customerDefault(ctx, sub);
49
- }
50
-
51
- /** The customer's default for invoices, charged when the subscription names none of its own. */
52
- function customerDefault(ctx: SemanticsContext, sub: Row): string | undefined {
53
- const customer = typeof sub.customer === 'string' ? ctx.get('customer', sub.customer) : undefined;
54
- const settings = customer?.invoice_settings as Row | undefined;
55
- return typeof settings?.default_payment_method === 'string' && settings.default_payment_method ? settings.default_payment_method : undefined;
56
- }
57
-
58
- const itemsOf = (sub: Row): Row[] => ((sub.items as { data?: Row[] } | undefined)?.data) ?? [];
59
-
60
- /** An item's quantity as it bills: 1 when none is set, and 0 when 0 is (a quantity of 0 bills nothing). */
61
- export function itemQuantity(it: Row): number {
62
- return it.quantity === undefined || it.quantity === null ? 1 : Math.max(0, Math.trunc(Number(it.quantity) || 0));
63
- }
64
-
65
- /** The subscription's discounts with a `once` coupon's spent: "When a subscription uses a coupon with `duration=once`, the
66
- * coupon is considered used after the invoice finalizes and is removed from the subscription's `discounts` array"
67
- * (docs.stripe.com/billing/subscriptions/coupons#coupon-duration). */
68
- export function unspentDiscounts(discounts: unknown): Row[] {
69
- return ((discounts as Row[] | undefined) ?? []).filter((d) => !(d && typeof d === 'object' && (d.coupon as Row | undefined)?.duration === 'once'));
70
- }
71
-
72
- /** The coupon ids an invoice's discounts name. */
73
- const couponsOf = (discounts: unknown): Set<string> => new Set(((discounts as Row[] | undefined) ?? []).map((d) => {
74
- const c = d && typeof d === 'object' ? d.coupon : undefined;
75
- return typeof c === 'string' ? c : c && typeof c === 'object' ? String((c as Row).id ?? '') : '';
76
- }).filter(Boolean));
77
-
78
- /** A subscription whose invoice has just been finalized: the `once` coupon that invoice took is spent, and a once coupon
79
- * it did not take (added after it was drafted) stays for the next (unspentDiscounts). */
80
- export async function spendOnceCoupon(ctx: SemanticsContext, invoice: Row): Promise<void> {
81
- const subId = typeof invoice.subscription === 'string' ? invoice.subscription : undefined;
82
- const sub = subId ? ctx.get(SUB, subId) : undefined;
83
- if (!sub || !subId) return;
84
- const all = (sub.discounts as Row[] | undefined) ?? [];
85
- const kept = keptAfter(all, invoice);
86
- if (kept.length !== all.length) await ctx.write(SUB, subId, { discounts: kept }, 'subscription.discount_spent');
87
- }
88
-
89
- /** A subscription's discounts after `invoice` finalizes: less the `once` coupons it took. */
90
- export function keptAfter(discounts: Row[], invoice: Row): Row[] {
91
- const taken = couponsOf(invoice.discounts);
92
- return discounts.filter((d) => !(unspentDiscounts([d]).length === 0 && [...couponsOf([d])].some((c) => taken.has(c))));
93
- }
94
-
95
- /** A draft invoice for one period of a subscription, its lines the subscription's items and any one-time lines `once`
96
- * gives it (a Checkout session's one-time prices, "on the initial invoice only", docs.stripe.com/api/checkout/sessions/
97
- * create#create_checkout_session-line_items), under the subscription's discount. Answers the invoice id. */
98
- export async function draftSubscriptionInvoice(ctx: SemanticsContext, sub: Row, o: { start: number; end: number; reason: 'subscription_create' | 'subscription_cycle' | 'subscription_update'; trial?: boolean; once?: Row[]; id?: string }): Promise<string> {
99
- const id = o.id ?? ctx.mint(INV);
100
- const currency = String(sub.currency ?? 'usd');
101
- const lines = itemsOf(sub).map((it, i) => {
102
- const price = it.price && typeof it.price === 'object' ? (it.price as Row) : ctx.get('price', String(it.price ?? ''));
103
- const quantity = itemQuantity(it);
104
- return {
105
- id: `il_twin_${id}_${i + 1}`, object: 'line_item', type: 'subscription',
106
- // a trial's first invoice bills nothing for the trial period (docs.stripe.com/billing/subscriptions/trials)
107
- amount: o.trial ? 0 : priceAmount(price, quantity), currency, quantity, proration: false,
108
- price: price ?? null, subscription: sub.id, subscription_item: it.id ?? null, invoice_item: null,
109
- period: { start: o.start, end: o.end }, description: o.trial ? `Trial period for ${String(ctx.get('product', String(price?.product ?? ''))?.name ?? 'the plan')}` : null,
110
- };
111
- });
112
- if (o.once?.length) lines.push(...oneTimeLines(id, sub, o.once, lines.length, o.start, currency));
113
- const subtotal = lines.reduce((s, l) => s + l.amount, 0);
114
- // a discount the subscription still carries applies; a `once` coupon is removed once an invoice finalizes
115
- // (spendOnceCoupon), so it discounts one invoice (docs.stripe.com/billing/subscriptions/coupons#coupon-duration)
116
- const discount = ((sub.discounts as Row[] | undefined) ?? [])[0];
117
- const coupon = discount && typeof discount === 'object' ? (discount.coupon as Row | undefined) : undefined;
118
- const off = coupon ? applyCouponDiscount(subtotal, coupon) : 0;
119
- const total = Math.max(0, subtotal - off);
120
- await created(ctx, INV, { id, customer: sub.customer, subscription: sub.id }, {
121
- created: o.start, status: 'draft', livemode: false, currency, collection_method: 'charge_automatically',
122
- billing_reason: o.reason, auto_advance: true, attempt_count: 0, attempted: false, next_payment_attempt: null,
123
- amount_due: total, amount_paid: 0, amount_remaining: total, amount_overpaid: 0, amount_paid_off_stripe: 0, amount_shipping: 0,
124
- subtotal, total, starting_balance: 0, post_payment_credit_notes_amount: 0, pre_payment_credit_notes_amount: 0,
125
- // "The invoice that consumed the coupon still shows the applied discount" (docs.stripe.com/billing/subscriptions/coupons)
126
- period_start: o.start, period_end: o.end, default_tax_rates: [], discounts: coupon && discount ? [discount] : [],
127
- lines: { object: 'list', data: lines, has_more: false, total_count: lines.length, url: `/v1/invoices/${id}/lines` },
128
- automatic_tax: { enabled: false, liability: null, status: null },
129
- status_transitions: { finalized_at: null, marked_uncollectible_at: null, paid_at: null, voided_at: null },
130
- issuer: { type: 'self' }, payment_settings: { default_mandate: null, payment_method_options: null, payment_method_types: null },
131
- payment_intent: null, charge: null,
132
- });
133
- return id;
134
- }
135
-
136
- /** A Checkout session's one-time line items as lines of its subscription's first invoice, numbered after its `after` lines. */
137
- function oneTimeLines(invoiceId: string, sub: Row, items: Row[], after: number, at: number, currency: string) {
138
- return items.map((it, i) => {
139
- const price = it.price && typeof it.price === 'object' ? (it.price as Row) : null;
140
- const quantity = itemQuantity(it);
141
- return {
142
- id: `il_twin_${invoiceId}_${after + i + 1}`, object: 'line_item', type: 'subscription',
143
- amount: priceAmount(price ?? undefined, quantity), currency, quantity, proration: false,
144
- price, subscription: sub.id, subscription_item: null, invoice_item: null,
145
- period: { start: at, end: at }, description: typeof it.description === 'string' ? it.description : null,
146
- };
147
- });
148
- }
149
-
150
- /** Finalize a subscription's draft invoice at `at` and charge what its subscription pays with. The move is time's (a
151
- * renewal), the customer's (the Checkout page they paid on) or the API's (an update that ends a trial, which names the
152
- * payer, may ask for no attempt, and moves the subscription itself: semantics/subscriptions.ts applyTrialEnd). Answers
153
- * whether it was paid. */
154
- export async function collectSubscriptionInvoice(ctx: SemanticsContext, invoiceId: string, at: number, actor: 'time' | 'external' | 'api', opts: { payer?: string; attempt?: boolean } = {}): Promise<boolean> {
155
- const inv = ctx.get(INV, invoiceId);
156
- const sub = inv && typeof inv.subscription === 'string' ? ctx.get(SUB, inv.subscription) : undefined;
157
- if (!inv || !sub || inv.status !== 'draft') return false;
158
- const total = Number(inv.total) || 0;
159
- const numbered = { ...finalizedFields(ctx, inv, at), status_transitions: { ...(inv.status_transitions as Row), finalized_at: at } };
160
- const op = ctx.call.operation.id;
161
- // nothing due (a trial's first invoice) is paid as it is finalized; finalizing spends a `once` coupon
162
- if (total <= 0) {
163
- if (ctx.legal(INV, 'status', op, 'draft', 'paid', invoiceId, actor)) return false;
164
- await spendOnceCoupon(ctx, inv);
165
- await ctx.write(INV, invoiceId, { ...numbered, status: 'paid', paid: true, auto_advance: false, status_transitions: { ...numbered.status_transitions, paid_at: at } }, 'invoice.paid');
166
- return true;
167
- }
168
- const payer = actor === 'api' ? opts.payer : payerOf(ctx, sub);
169
- // default_incomplete finalizes the invoice without attempting payment (docs.stripe.com/api/subscriptions/update#update_subscription-payment_behavior)
170
- const attempt = opts.attempt !== false;
171
- const decline = payer && attempt ? declineFor(finder(ctx), { payment_method: payer }) : undefined;
172
- const pays = attempt && !!payer && !decline;
173
- if (ctx.legal(INV, 'status', op, 'draft', pays ? 'paid' : 'open', invoiceId, actor)) return false;
174
- await spendOnceCoupon(ctx, inv);
175
- const piId = ctx.mint('payment_intent');
176
- const currency = String(inv.currency ?? 'usd');
177
- const chargeId = pays ? await mintCharge(ctx, piId, { currency, customer: inv.customer, payment_method: payer, invoice: invoiceId }, total) : null;
178
- await created(ctx, 'payment_intent', { id: piId, amount: total, currency, customer: inv.customer, invoice: invoiceId, payment_method: pays ? payer : null }, {
179
- status: pays ? 'succeeded' : 'requires_payment_method', client_secret: mintClientSecret(piId), livemode: false,
180
- capture_method: 'automatic', amount_capturable: 0, amount_received: pays ? total : 0, next_action: null, latest_charge: chargeId,
181
- last_payment_error: decline ? { type: 'card_error', code: decline.code, decline_code: decline.decline_code ?? null, message: decline.message, payment_method: { id: payer } } : null,
182
- automatic_payment_methods: null, payment_method_types: ['card'], payment_method_options: {},
183
- });
184
- await ctx.write(INV, invoiceId, {
185
- ...numbered, status: pays ? 'paid' : 'open', paid: pays, attempt_count: attempt ? 1 : 0, attempted: attempt, payment_intent: piId, charge: chargeId,
186
- amount_paid: pays ? total : 0, amount_remaining: pays ? 0 : total, auto_advance: attempt && !pays, next_payment_attempt: null,
187
- status_transitions: { ...numbered.status_transitions, paid_at: pays ? at : null },
188
- }, pays ? 'invoice.paid' : attempt ? 'invoice.payment_failed' : 'invoice.finalized');
189
- if (actor !== 'api' && !pays && sub.status !== 'past_due' && !ctx.legal(SUB, 'status', op, sub.status, 'past_due', String(sub.id), 'time')) {
190
- await ctx.write(SUB, String(sub.id), { status: 'past_due' }, 'customer.subscription.updated');
191
- }
192
- return pays;
193
- }
194
-
195
- /** Pay a subscription's open invoice with `paymentMethod` (the customer on the portal, `external`): the charge on the
196
- * invoice's PaymentIntent, the invoice paid, a past_due subscription active. Answers the decline, if the card is refused. */
197
- export async function payOpenInvoice(ctx: SemanticsContext, invoiceId: string, paymentMethod: string): Promise<{ code: string; message: string } | undefined> {
198
- const inv = ctx.get(INV, invoiceId);
199
- if (!inv || inv.status !== 'open') return { code: 'invoice_not_open', message: 'This invoice is no longer open.' };
200
- const decline = declineFor(finder(ctx), { payment_method: paymentMethod });
201
- if (decline) return decline;
202
- const op = ctx.call.operation.id;
203
- const refused = ctx.legal(INV, 'status', op, 'open', 'paid', invoiceId, 'external');
204
- if (refused) return { code: refused.code ?? 'invoice_not_open', message: refused.message };
205
- const total = Number(inv.amount_remaining ?? inv.total) || 0;
206
- const now = Number(ctx.now());
207
- const piId = typeof inv.payment_intent === 'string' ? inv.payment_intent : undefined;
208
- let chargeId: string | null = null;
209
- const pi = piId ? ctx.get('payment_intent', piId) : undefined;
210
- if (piId && pi && !ctx.legal('payment_intent', 'status', op, pi.status, 'succeeded', piId, 'external')) {
211
- chargeId = await mintCharge(ctx, piId, { ...pi, payment_method: paymentMethod, invoice: invoiceId }, total);
212
- await ctx.write('payment_intent', piId, { status: 'succeeded', payment_method: paymentMethod, amount_received: total, last_payment_error: null, latest_charge: chargeId }, 'payment_intent.succeeded');
213
- }
214
- await ctx.write(INV, invoiceId, {
215
- status: 'paid', paid: true, amount_paid: total, amount_remaining: 0, charge: chargeId, auto_advance: false,
216
- attempt_count: (Number(inv.attempt_count) || 0) + 1, attempted: true,
217
- status_transitions: { ...(inv.status_transitions as Row), paid_at: now },
218
- }, 'invoice.paid');
219
- const sub = typeof inv.subscription === 'string' ? ctx.get(SUB, inv.subscription) : undefined;
220
- if (sub && sub.status === 'past_due' && !ctx.legal(SUB, 'status', op, 'past_due', 'active', String(sub.id), 'external')) {
221
- await ctx.write(SUB, String(sub.id), { status: 'active' }, 'customer.subscription.updated');
222
- }
223
- return undefined;
224
- }
225
-
226
- /** Time's moves on subscriptions, caught up to each customer's clock in the order they fell due, each made at the
227
- * moment it was due: a period that ended renews (a trial's end among them) or, set to cancel, cancels; a renewal
228
- * drafted an hour or more ago is finalized and charged. */
229
- export async function advanceBilling(ctx: SemanticsContext, only?: (sub: Row) => boolean): Promise<void> {
230
- // the billing clock is the vendor's own move, wherever a handler reaches it: never the request's caller's
231
- return runAsVendorMove(() => advanceBillingDue(ctx, only));
232
- }
233
-
234
- async function advanceBillingDue(ctx: SemanticsContext, only?: (sub: Row) => boolean): Promise<void> {
235
- const at = at_(ctx);
236
- for (let guard = 0; guard < 10_000 && (await billingStep(ctx, at, only)); guard++);
237
- }
238
-
239
- /** The earliest move that has come due, made at its moment; false when none has. */
240
- async function billingStep(ctx: SemanticsContext, at: (t: number) => Promise<SemanticsContext>, only?: (sub: Row) => boolean): Promise<boolean> {
241
- // the earliest move that has come due, of any subscription
242
- let next: { t: number; kind: 'period' | 'collect'; id: string } | undefined;
243
- for (const sub of ctx.rows(SUB)) {
244
- if (only && !only(sub)) continue;
245
- if (!RENEWING.includes(String(sub.status)) || typeof sub.current_period_end !== 'number' || sub.pause_collection) continue;
246
- if (sub.current_period_end <= clockNow(ctx, sub.customer) && (!next || sub.current_period_end < next.t)) next = { t: sub.current_period_end, kind: 'period', id: String(sub.id) };
247
- }
248
- for (const inv of ctx.rows(INV)) {
249
- if (inv.status !== 'draft' || inv.billing_reason !== 'subscription_cycle' || inv.auto_advance !== true) continue;
250
- const sub = typeof inv.subscription === 'string' ? ctx.get(SUB, inv.subscription) : undefined;
251
- if (!sub || (only && !only(sub))) continue;
252
- const t = Number(inv.created) + HOUR;
253
- if (t <= clockNow(ctx, sub.customer) && (!next || t < next.t)) next = { t, kind: 'collect', id: String(inv.id) };
254
- }
255
- if (!next) return false;
256
- const c = await at(next.t);
257
- if (next.kind === 'collect') {
258
- await collectSubscriptionInvoice(c, next.id, next.t, 'time');
259
- return true;
260
- }
261
- const id = next.id;
262
- const sub = c.get(SUB, id)!;
263
- const op = c.call.operation.id;
264
- // one set to cancel at its period's end is canceled then, with no renewal
265
- // (docs.stripe.com/billing/subscriptions/cancel#cancel-at-end-of-cycle)
266
- if (sub.cancel_at_period_end === true) {
267
- if (c.legal(SUB, 'status', op, sub.status, 'canceled', id, 'time')) return false;
268
- await c.write(SUB, id, { status: 'canceled', ended_at: next.t, canceled_at: sub.canceled_at ?? next.t }, 'customer.subscription.deleted');
269
- return true;
270
- }
271
- const start = next.t;
272
- const { interval, interval_count } = resolveSubscriptionBillingInterval(itemsOf(sub), finder(c));
273
- const end = addBillingInterval(start, interval, interval_count);
274
- const trialEnds = sub.status === 'trialing';
275
- if (trialEnds && c.legal(SUB, 'status', op, 'trialing', 'active', id, 'time')) return false;
276
- const invoiceId = await draftSubscriptionInvoice(c, sub, { start, end, reason: 'subscription_cycle' });
277
- await c.write(SUB, id, { current_period_start: start, current_period_end: end, latest_invoice: invoiceId, ...(trialEnds ? { status: 'active' } : {}) }, 'customer.subscription.updated');
278
- return true;
279
- }
@@ -1,87 +0,0 @@
1
- // SetupIntent semantics. The machine in ../manifest.ts says which moves of `status` are legal and
2
- // what Stripe answers otherwise; these handlers compute what each move does. List, retrieve and
3
- // update are the derived core's.
4
- import type { Semantics, SemanticsContext } from '@volter/world-core';
5
- import { asBool, microdepositsNextAction, mintClientSecret, requiresMicrodeposits, verifyMicrodeposits } from '../stripe-twin.ts';
6
- import { mintMandate } from './after-payment.ts';
7
- import { attachable, attachPaymentMethod, methodFromData, microdepositsAsked } from './payment-methods.ts';
8
- import { at, confirmOnly, created, fail, mergeUpdate, send, type Row } from './shared.ts';
9
-
10
- const SI = 'setup_intent';
11
-
12
- /** The intent the path names, and the machine's refusal when this operation may not move it. */
13
- function load(ctx: SemanticsContext, operationId: string): { si: Row } | { answer: Response } {
14
- const id = at(ctx, 'intent');
15
- const si = ctx.get(SI, id);
16
- if (!si) return { answer: fail(ctx, `No such setup_intent: '${id}'`, 404, 'resource_missing') };
17
- const refused = ctx.legal(SI, 'status', operationId, si.status, undefined, id);
18
- return refused ? { answer: ctx.refuse(refused) } : { si };
19
- }
20
-
21
- const create: Semantics = async (ctx) => {
22
- // mandate_data and return_url each "can only be used with `confirm=true`" (the served spec; shared.ts confirmOnly)
23
- const unconfirmed = confirmOnly(ctx, ['mandate_data', 'return_url'], asBool(ctx.params.confirm));
24
- if (unconfirmed) return unconfirmed;
25
- // the id is minted first so the client secret can carry it: Stripe.js parses the id back out
26
- const id = typeof ctx.params.id === 'string' && ctx.params.id ? ctx.params.id : ctx.mint(SI);
27
- // it waits for a payment method until it has one, then for its confirmation (manifest.ts)
28
- const hasMethod = typeof ctx.params.payment_method === 'string' && ctx.params.payment_method !== '';
29
- if (hasMethod) ctx.legal(SI, 'status', 'PostSetupIntents', 'requires_payment_method', 'requires_confirmation', id);
30
- return ctx.reply(
31
- // allowed_payment_method_types in the served version names the methods (docs.stripe.com/api/setup_intents/create)
32
- await created(ctx, SI, { ...ctx.params, ...(Array.isArray(ctx.params.allowed_payment_method_types) ? { payment_method_types: ctx.params.allowed_payment_method_types.map(String) } : {}), id }, {
33
- status: hasMethod ? 'requires_confirmation' : 'requires_payment_method', usage: 'off_session', client_secret: mintClientSecret(id), payment_method_types: ['card'],
34
- next_action: null, latest_attempt: null, last_setup_error: null, livemode: false,
35
- }),
36
- );
37
- };
38
-
39
- // a bank account that needs micro-deposit verification waits in requires_action; anything else succeeds. The confirm's
40
- // payment_method_data and mandate_data are instructions, not fields of the SetupIntent.
41
- const confirm: Semantics = async (ctx) => {
42
- const loaded = load(ctx, 'PostSetupIntentsIntentConfirm');
43
- if ('answer' in loaded) return loaded.answer;
44
- const si = loaded.si;
45
- const id = String(si.id);
46
- const { payment_method_data: data, mandate_data: _mandate, ...fields } = ctx.params;
47
- const made = await methodFromData(ctx, data);
48
- const pm = made ?? (typeof ctx.params.payment_method === 'string' ? ctx.params.payment_method : typeof si.payment_method === 'string' ? si.payment_method : undefined);
49
- if (si.status !== 'requires_action' && (requiresMicrodeposits(ctx.params, si) || microdepositsAsked(ctx, ctx.params, si, pm))) {
50
- return ctx.reply(await ctx.write(SI, id, mergeUpdate(ctx, SI, id, { ...fields, ...(pm ? { payment_method: pm } : {}), status: 'requires_action', next_action: microdepositsNextAction() }), 'setup_intent.requires_action'));
51
- }
52
- // a setup for a customer saves the method to them (docs.stripe.com/payments/save-and-reuse): its payment method is attached
53
- const customer = typeof si.customer === 'string' ? si.customer : undefined;
54
- const saved = pm && customer && attachable(ctx, pm) && ctx.get('payment_method', pm)?.customer !== customer ? String((await attachPaymentMethod(ctx, pm, customer)).id) : pm;
55
- return ctx.reply(await ctx.write(SI, id, mergeUpdate(ctx, SI, id, { ...fields, ...(saved ? { payment_method: saved } : {}), status: 'succeeded' }), 'setup_intent.confirm'));
56
- };
57
-
58
- const cancel: Semantics = async (ctx) => {
59
- const loaded = load(ctx, 'PostSetupIntentsIntentCancel');
60
- if ('answer' in loaded) return loaded.answer;
61
- const cancellation_reason = typeof ctx.params.cancellation_reason === 'string' ? ctx.params.cancellation_reason : 'abandoned';
62
- return ctx.reply(await ctx.write(SI, String(loaded.si.id), { status: 'canceled', cancellation_reason }, 'setup_intent.canceled'));
63
- };
64
-
65
- const verify: Semantics = async (ctx) => {
66
- const id = at(ctx, 'intent');
67
- const si = ctx.get(SI, id);
68
- if (!si) return fail(ctx, `No such setup_intent: '${id}'`, 404, 'resource_missing');
69
- // the deposit amounts are checked before the intent's state, as Stripe does
70
- const wrong = verifyMicrodeposits(ctx.params, si);
71
- if (wrong) return send(ctx, wrong);
72
- const refused = ctx.legal(SI, 'status', 'PostSetupIntentsIntentVerifyMicrodeposits', si.status, undefined, id);
73
- if (refused) return ctx.refuse(refused);
74
- const mandate = await mintMandate(ctx, si.payment_method, 'multi_use');
75
- // verified, the bank account is saved to the setup's customer, as a confirmed setup's method is
76
- const pm = typeof si.payment_method === 'string' ? si.payment_method : undefined;
77
- const customer = typeof si.customer === 'string' ? si.customer : undefined;
78
- if (pm && customer && attachable(ctx, pm) && ctx.get('payment_method', pm)?.customer !== customer) await attachPaymentMethod(ctx, pm, customer);
79
- return ctx.reply(await ctx.write(SI, id, { status: 'succeeded', next_action: null, last_setup_error: null, mandate }, 'setup_intent.succeeded'));
80
- };
81
-
82
- export const setupIntents: Record<string, Semantics> = {
83
- PostSetupIntents: create,
84
- PostSetupIntentsIntentConfirm: confirm,
85
- PostSetupIntentsIntentCancel: cancel,
86
- PostSetupIntentsIntentVerifyMicrodeposits: verify,
87
- };
@@ -1,129 +0,0 @@
1
- // Subscription schedule semantics: a schedule drives a subscription through phases (items for a
2
- // duration). One made for a customer starts its subscription at once on the first phase's items. Cancel and
3
- // release end it, the machine's moves in ../manifest.ts. List and update are the derived core's.
4
- //
5
- // Where the documentation stops and the twin decides: the subscription a schedule starts raises no first invoice.
6
- import type { Semantics, SemanticsContext } from '@volter/world-core';
7
- import { addBillingInterval, buildSubscriptionItemsList } from '../stripe-twin.ts';
8
- import { at, created, fail, finder, type Row } from './shared.ts';
9
- import { storeItems } from './subscriptions.ts';
10
-
11
- const SS = 'subscription_schedule';
12
- const scheduleMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such subscription_schedule: '${id}'`, 404, 'resource_missing');
13
-
14
- type Interval = Parameters<typeof addBillingInterval>[1];
15
-
16
- /** A schedule's phases as Stripe answers them (the served spec's subscription_schedule_phase_configuration): each
17
- * starts where the last ended (the first at the schedule's start_date, "now" or a timestamp), and ends at its
18
- * end_date or after its duration ("duration: interval, interval_count", the served spec's phases[] parameter) on the
19
- * calendar; its currency is its first price's, each item carries its price, quantity and (empty) discounts. Where the
20
- * documentation stops and the twin decides: a phase with neither lasts one interval of its first price. */
21
- function phasesOf(ctx: SemanticsContext, given: Row[], start: number): Row[] {
22
- let cursor = start;
23
- return given.map((p) => {
24
- const items = (Array.isArray(p.items) ? (p.items as Row[]) : []).map((it) => ({ price: it.price ?? null, quantity: Math.max(1, Math.trunc(Number(it.quantity) || 1)), discounts: [], metadata: {}, tax_rates: [], billing_thresholds: null }));
25
- const price = typeof items[0]?.price === 'string' ? ctx.get('price', items[0]!.price as string) : undefined;
26
- const recurring = (price?.recurring as Row | undefined) ?? {};
27
- const d = p.duration && typeof p.duration === 'object' ? (p.duration as Row) : undefined;
28
- const startDate = cursor;
29
- const end = p.end_date !== undefined && p.end_date !== 'now' ? Number(p.end_date)
30
- : d ? addBillingInterval(startDate, String(d.interval) as Interval, Math.max(1, Number(d.interval_count) || 1))
31
- : addBillingInterval(startDate, String(recurring.interval ?? 'month') as Interval, Math.max(1, Number(recurring.interval_count) || 1));
32
- cursor = end;
33
- return {
34
- add_invoice_items: [], application_fee_percent: null, automatic_tax: { enabled: false, liability: null, disabled_reason: null }, billing_cycle_anchor: null,
35
- billing_thresholds: null, collection_method: null, currency: String(price?.currency ?? 'usd'), default_payment_method: null, default_tax_rates: [],
36
- description: null, discounts: [], end_date: end, invoice_settings: null, items, metadata: p.metadata && typeof p.metadata === 'object' ? p.metadata : {},
37
- on_behalf_of: null, proration_behavior: typeof p.proration_behavior === 'string' ? p.proration_behavior : 'create_prorations', start_date: startDate,
38
- transfer_data: null, trial_end: null,
39
- };
40
- });
41
- }
42
-
43
- // a schedule starts from a customer or from an existing subscription
44
- const create: Semantics = async (ctx) => {
45
- const params = ctx.params;
46
- const customer = typeof params.customer === 'string' ? params.customer : '';
47
- const fromSub = typeof params.from_subscription === 'string' ? params.from_subscription : '';
48
- if (!customer && !fromSub) return fail(ctx, 'Missing required param: customer (or from_subscription).', 400, 'parameter_missing');
49
- if (customer && !ctx.get('customer', customer)) return fail(ctx, `No such customer: '${customer}'`, 404, 'resource_missing');
50
- if (fromSub && !ctx.get('subscription', fromSub)) return fail(ctx, `No such subscription: '${fromSub}'`, 404, 'resource_missing');
51
- // start_date is an instruction ("now" or when), not a field of the schedule
52
- const start = params.start_date !== undefined && params.start_date !== 'now' ? Number(params.start_date) : Number(ctx.now());
53
- const phases = phasesOf(ctx, Array.isArray(params.phases) ? (params.phases as Row[]) : [], start);
54
- const { phases: _p, start_date: _s, default_settings: givenDefaults, ...rest } = params;
55
- const defaults = givenDefaults && typeof givenDefaults === 'object' ? (givenDefaults as Row) : {};
56
- const schedId = ctx.mint(SS);
57
- // a schedule for a customer whose first phase starts now starts its subscription at once, on that phase's items
58
- // (docs.stripe.com/billing/subscriptions/subscription-schedules#managing)
59
- let subscription = fromSub || null;
60
- if (!fromSub && phases.length) {
61
- const now = Number(ctx.now());
62
- const subId = ctx.mint('subscription');
63
- const items = buildSubscriptionItemsList(phases[0]!.items as Row[], subId, now, finder(ctx));
64
- await created(ctx, 'subscription', { customer, id: subId }, {
65
- status: 'active', livemode: false, currency: 'usd', collection_method: String((params.default_settings as Row | undefined)?.collection_method ?? 'charge_automatically'),
66
- cancel_at_period_end: false, start_date: now, billing_cycle_anchor: now, metadata: {}, discounts: [], billing_schedules: [],
67
- trial_start: null, trial_end: null, current_period_start: now, current_period_end: phases[0]!.end_date, items, schedule: schedId,
68
- automatic_tax: { enabled: false, liability: null }, billing_mode: { type: 'classic' }, invoice_settings: { issuer: { type: 'self' } },
69
- });
70
- await storeItems(ctx, subId, (items.data as Row[]) ?? []);
71
- subscription = subId;
72
- }
73
- return ctx.reply(
74
- await created(ctx, SS, { ...rest, id: schedId }, {
75
- status: 'active', livemode: false, phases, current_phase: phases.length ? { start_date: phases[0]!.start_date, end_date: phases[0]!.end_date } : null,
76
- subscription, released_subscription: null, released_at: null,
77
- canceled_at: null, completed_at: null, end_behavior: (params.end_behavior as string) ?? 'release',
78
- renewal_interval: null, metadata: {}, billing_mode: { type: 'classic' },
79
- // "billing_cycle_anchor: Possible values are phase_start or automatic" and invoice_settings are required (the
80
- // served spec's default settings); the rest answer what the request set, else null
81
- default_settings: {
82
- application_fee_percent: defaults.application_fee_percent ?? null, automatic_tax: { enabled: false, liability: null, disabled_reason: null },
83
- billing_cycle_anchor: typeof defaults.billing_cycle_anchor === 'string' ? defaults.billing_cycle_anchor : 'automatic', billing_thresholds: null,
84
- collection_method: typeof defaults.collection_method === 'string' ? defaults.collection_method : 'charge_automatically', default_payment_method: defaults.default_payment_method ?? null,
85
- description: defaults.description ?? null, invoice_settings: { account_tax_ids: null, days_until_due: null, issuer: { type: 'self' } }, on_behalf_of: null, transfer_data: null,
86
- },
87
- // a schedule made from a subscription is its customer's
88
- customer: customer || String(ctx.get('subscription', fromSub)?.customer ?? ''),
89
- }),
90
- );
91
- };
92
-
93
- function end(kind: 'cancel' | 'release'): Semantics {
94
- return async (ctx) => {
95
- const id = at(ctx, 'schedule');
96
- const sched = ctx.get(SS, id);
97
- if (!sched) return scheduleMissing(ctx, id);
98
- const refused = ctx.legal(SS, 'status', kind === 'cancel' ? 'PostSubscriptionSchedulesScheduleCancel' : 'PostSubscriptionSchedulesScheduleRelease', sched.status, undefined, id);
99
- if (refused) return ctx.refuse(refused);
100
- const now = ctx.now();
101
- const subId = typeof sched.subscription === 'string' ? sched.subscription : undefined;
102
- const sub = subId ? ctx.get('subscription', subId) : undefined;
103
- // "Cancels a subscription schedule and its associated subscription immediately (if the subscription schedule has an
104
- // active subscription)" (docs.stripe.com/api/subscription_schedules/cancel); releasing "will remove its subscription
105
- // property and set the subscription's ID to the released_subscription property", leaving the subscription in place
106
- // (docs.stripe.com/api/subscription_schedules/release)
107
- if (kind === 'cancel' && subId && sub && sub.status !== 'canceled') {
108
- ctx.legal('subscription', 'status', 'PostSubscriptionSchedulesScheduleCancel', String(sub.status), 'canceled', subId);
109
- await ctx.write('subscription', subId, { status: 'canceled', canceled_at: now, ended_at: now }, 'subscription.cancel');
110
- }
111
- if (kind === 'release' && subId && sub) await ctx.write('subscription', subId, { schedule: null }, 'subscription.update');
112
- const fields = kind === 'cancel'
113
- ? { status: 'canceled', canceled_at: now, current_phase: null }
114
- : { status: 'released', released_at: now, released_subscription: subId ?? null, subscription: null, current_phase: null };
115
- return ctx.reply(await ctx.write(SS, id, fields, `subscription_schedule.${kind}`));
116
- };
117
- }
118
-
119
- const retrieve: Semantics = async (ctx) => {
120
- const s = ctx.get(SS, at(ctx, 'schedule'));
121
- return s ? ctx.reply(s) : scheduleMissing(ctx, at(ctx, 'schedule'));
122
- };
123
-
124
- export const subscriptionSchedules: Record<string, Semantics> = {
125
- PostSubscriptionSchedules: create,
126
- PostSubscriptionSchedulesScheduleCancel: end('cancel'),
127
- PostSubscriptionSchedulesScheduleRelease: end('release'),
128
- GetSubscriptionSchedulesSchedule: retrieve,
129
- };