@volter/twin-stripe 2.0.38 → 3.0.0

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 (442) hide show
  1. package/README.md +82 -207
  2. package/dist/generated/pack-facts.json +94 -0
  3. package/dist/spec/SOURCE.md +14 -0
  4. package/dist/spec/fixtures3.json.gz +0 -0
  5. package/dist/spec/openapi.json.gz +0 -0
  6. package/dist/spec/patches.json +34 -0
  7. package/dist/spec/sources.json +378 -0
  8. package/dist/src/cli.js +12 -30
  9. package/dist/src/engine/after-payment.d.ts +9 -0
  10. package/dist/src/engine/after-payment.js +28 -0
  11. package/dist/src/engine/apps-secrets.d.ts +1 -0
  12. package/dist/src/engine/apps-secrets.js +3 -0
  13. package/dist/src/engine/balance.d.ts +5 -0
  14. package/dist/src/engine/balance.js +15 -0
  15. package/dist/src/engine/billing.d.ts +5 -0
  16. package/dist/src/engine/billing.js +6 -0
  17. package/dist/src/engine/charges.d.ts +6 -0
  18. package/dist/src/engine/charges.js +13 -0
  19. package/dist/src/engine/checkout.d.ts +24 -0
  20. package/dist/src/engine/checkout.js +30 -0
  21. package/dist/src/engine/common.d.ts +19 -0
  22. package/dist/src/engine/common.js +32 -0
  23. package/dist/src/{screens → engine}/connect-settings.d.ts +1 -7
  24. package/dist/src/engine/connect-settings.js +26 -0
  25. package/dist/src/engine/connect.d.ts +66 -0
  26. package/dist/src/engine/connect.js +189 -0
  27. package/dist/src/engine/coupons.d.ts +0 -0
  28. package/dist/src/engine/coupons.js +5 -0
  29. package/dist/src/engine/credit-notes.d.ts +1 -0
  30. package/dist/src/engine/credit-notes.js +5 -0
  31. package/dist/src/engine/customers.d.ts +12 -0
  32. package/dist/src/engine/customers.js +30 -0
  33. package/dist/src/engine/display.d.ts +14 -0
  34. package/dist/src/engine/display.js +16 -0
  35. package/dist/src/engine/disputes.d.ts +0 -0
  36. package/dist/src/engine/disputes.js +9 -0
  37. package/dist/src/engine/entitlements.d.ts +1 -0
  38. package/dist/src/engine/entitlements.js +4 -0
  39. package/dist/src/engine/ephemeral-keys.d.ts +0 -0
  40. package/dist/src/engine/ephemeral-keys.js +4 -0
  41. package/dist/src/engine/files.d.ts +18 -0
  42. package/dist/src/engine/files.js +33 -0
  43. package/dist/src/engine/front.d.ts +5 -0
  44. package/dist/src/engine/front.js +25 -0
  45. package/dist/src/engine/invoices.d.ts +24 -0
  46. package/dist/src/engine/invoices.js +57 -0
  47. package/dist/src/engine/issuing.d.ts +35 -0
  48. package/dist/src/engine/issuing.js +45 -0
  49. package/dist/src/engine/ledger.d.ts +9 -0
  50. package/dist/src/engine/ledger.js +9 -0
  51. package/dist/src/engine/oauth.d.ts +4 -0
  52. package/dist/src/engine/oauth.js +25 -0
  53. package/dist/src/engine/onboarding.d.ts +13 -0
  54. package/dist/src/engine/onboarding.js +42 -0
  55. package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
  56. package/dist/src/{stripe-params.js → engine/params.js} +7 -60
  57. package/dist/src/engine/payment-intents.d.ts +10 -0
  58. package/dist/src/engine/payment-intents.js +27 -0
  59. package/dist/src/engine/payment-links.d.ts +1 -0
  60. package/dist/src/engine/payment-links.js +5 -0
  61. package/dist/src/engine/payment-methods.d.ts +16 -0
  62. package/dist/src/engine/payment-methods.js +22 -0
  63. package/dist/src/engine/plans.d.ts +6 -0
  64. package/dist/src/engine/plans.js +26 -0
  65. package/dist/src/engine/platform.d.ts +39 -0
  66. package/dist/src/engine/platform.js +34 -0
  67. package/dist/src/engine/products.d.ts +0 -0
  68. package/dist/src/engine/products.js +4 -0
  69. package/dist/src/engine/radar.d.ts +2 -0
  70. package/dist/src/engine/radar.js +5 -0
  71. package/dist/src/engine/refunds.d.ts +1 -0
  72. package/dist/src/engine/refunds.js +28 -0
  73. package/dist/src/engine/renewals.d.ts +36 -0
  74. package/dist/src/engine/renewals.js +65 -0
  75. package/dist/src/engine/setup-intents.d.ts +1 -0
  76. package/dist/src/engine/setup-intents.js +4 -0
  77. package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +11 -39
  78. package/dist/src/{stripe-twin.js → engine/stripe.js} +25 -310
  79. package/dist/src/engine/subscription-schedules.d.ts +3 -0
  80. package/dist/src/engine/subscription-schedules.js +1 -0
  81. package/dist/src/engine/subscriptions.d.ts +12 -0
  82. package/dist/src/engine/subscriptions.js +31 -0
  83. package/dist/src/engine/tax.d.ts +7 -0
  84. package/dist/src/engine/tax.js +28 -0
  85. package/dist/src/engine/terminal.d.ts +4 -0
  86. package/dist/src/engine/terminal.js +20 -0
  87. package/dist/src/engine/test-clocks.d.ts +5 -0
  88. package/dist/src/engine/test-clocks.js +9 -0
  89. package/dist/src/engine/tokens.d.ts +7 -0
  90. package/dist/src/engine/tokens.js +20 -0
  91. package/dist/src/engine/transfers.d.ts +2 -0
  92. package/dist/src/engine/transfers.js +1 -0
  93. package/dist/src/engine/treasury.d.ts +23 -0
  94. package/dist/src/engine/treasury.js +45 -0
  95. package/dist/src/{stripe-version.js → engine/version.js} +4 -3
  96. package/dist/src/engine/webhook-endpoints.d.ts +1 -0
  97. package/dist/src/engine/webhook-endpoints.js +12 -0
  98. package/dist/src/engine/webhooks.d.ts +31 -0
  99. package/dist/src/engine/webhooks.js +68 -0
  100. package/dist/src/fetch.d.ts +2 -0
  101. package/dist/src/fetch.js +26 -0
  102. package/dist/src/generated/surface.gen.json +1 -1
  103. package/dist/src/generated/ui.gen.json +1 -1
  104. package/dist/src/index.d.ts +5 -14
  105. package/dist/src/index.js +8 -75
  106. package/dist/src/manifest.js +232 -758
  107. package/dist/src/screens/billing-portal.d.ts +3 -0
  108. package/dist/src/screens/{portal.js → billing-portal.js} +28 -54
  109. package/dist/src/screens/checkout.d.ts +2 -31
  110. package/dist/src/screens/checkout.js +32 -121
  111. package/dist/src/screens/connect-oauth.d.ts +3 -27
  112. package/dist/src/screens/connect-oauth.js +51 -132
  113. package/dist/src/screens/connect-onboarding.d.ts +3 -0
  114. package/dist/src/screens/{onboarding.js → connect-onboarding.js} +36 -102
  115. package/dist/src/screens/dashboard.d.ts +3 -0
  116. package/dist/src/screens/dashboard.js +117 -0
  117. package/dist/src/screens/shared.d.ts +27 -0
  118. package/dist/src/screens/shared.js +87 -0
  119. package/dist/src/screens/stripe-js.d.ts +3 -0
  120. package/dist/src/screens/stripe-js.js +135 -0
  121. package/dist/src/semantics/account_links.d.ts +2 -0
  122. package/dist/src/semantics/account_links.js +22 -0
  123. package/dist/src/semantics/accounts.d.ts +4 -0
  124. package/dist/src/semantics/accounts.js +38 -0
  125. package/dist/src/semantics/apps.d.ts +4 -0
  126. package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
  127. package/dist/src/semantics/around.d.ts +2 -0
  128. package/dist/src/semantics/around.js +16 -0
  129. package/dist/src/semantics/balance.d.ts +2 -11
  130. package/dist/src/semantics/balance.js +3 -194
  131. package/dist/src/semantics/balance_transactions.d.ts +2 -0
  132. package/dist/src/semantics/balance_transactions.js +15 -0
  133. package/dist/src/semantics/billing_portal.d.ts +5 -0
  134. package/dist/src/semantics/billing_portal.js +42 -0
  135. package/dist/src/semantics/charges.d.ts +3 -28
  136. package/dist/src/semantics/charges.js +5 -202
  137. package/dist/src/semantics/checkout.d.ts +3 -20
  138. package/dist/src/semantics/checkout.js +8 -261
  139. package/dist/src/semantics/clock.d.ts +2 -0
  140. package/dist/src/semantics/clock.js +21 -0
  141. package/dist/src/semantics/coupons.d.ts +2 -6
  142. package/dist/src/semantics/coupons.js +2 -60
  143. package/dist/src/semantics/customers.d.ts +6 -6
  144. package/dist/src/semantics/customers.js +27 -387
  145. package/dist/src/semantics/disputes.d.ts +2 -2
  146. package/dist/src/semantics/disputes.js +3 -49
  147. package/dist/src/semantics/doors.d.ts +23 -0
  148. package/dist/src/semantics/doors.js +73 -0
  149. package/dist/src/semantics/entitlements.d.ts +2 -2
  150. package/dist/src/semantics/entitlements.js +5 -50
  151. package/dist/src/semantics/events.d.ts +12 -0
  152. package/dist/src/semantics/events.js +36 -0
  153. package/dist/src/semantics/index.d.ts +2 -0
  154. package/dist/src/semantics/index.js +107 -0
  155. package/dist/src/semantics/invoice_payments.d.ts +2 -0
  156. package/dist/src/semantics/invoice_payments.js +12 -0
  157. package/dist/src/semantics/invoiceitems.d.ts +3 -0
  158. package/dist/src/semantics/invoiceitems.js +59 -0
  159. package/dist/src/semantics/invoices.d.ts +16 -18
  160. package/dist/src/semantics/invoices.js +118 -444
  161. package/dist/src/semantics/payment_intents.d.ts +4 -0
  162. package/dist/src/semantics/payment_intents.js +79 -0
  163. package/dist/src/semantics/payment_methods.d.ts +5 -0
  164. package/dist/src/semantics/payment_methods.js +39 -0
  165. package/dist/src/semantics/payouts.d.ts +2 -0
  166. package/dist/src/semantics/payouts.js +21 -0
  167. package/dist/src/semantics/prices.d.ts +4 -0
  168. package/dist/src/semantics/prices.js +37 -0
  169. package/dist/src/semantics/products.d.ts +6 -2
  170. package/dist/src/semantics/products.js +15 -130
  171. package/dist/src/semantics/promotion_codes.d.ts +3 -0
  172. package/dist/src/semantics/promotion_codes.js +35 -0
  173. package/dist/src/semantics/radar.d.ts +2 -2
  174. package/dist/src/semantics/radar.js +5 -74
  175. package/dist/src/semantics/refunds.d.ts +3 -9
  176. package/dist/src/semantics/refunds.js +6 -192
  177. package/dist/src/semantics/setup_intents.d.ts +3 -0
  178. package/dist/src/semantics/{setup-intents.js → setup_intents.js} +10 -53
  179. package/dist/src/semantics/shared.d.ts +822 -47
  180. package/dist/src/semantics/shared.js +3044 -78
  181. package/dist/src/semantics/states.d.ts +531 -0
  182. package/dist/src/semantics/states.js +852 -0
  183. package/dist/src/semantics/subscription_items.d.ts +5 -0
  184. package/dist/src/semantics/subscription_items.js +55 -0
  185. package/dist/src/semantics/subscription_schedules.d.ts +4 -0
  186. package/dist/src/semantics/subscription_schedules.js +60 -0
  187. package/dist/src/semantics/subscriptions.d.ts +7 -11
  188. package/dist/src/semantics/subscriptions.js +44 -415
  189. package/dist/src/semantics/test_helpers.d.ts +6 -0
  190. package/dist/src/semantics/{test-clocks.js → test_helpers.js} +14 -40
  191. package/dist/src/semantics/transfers.d.ts +2 -2
  192. package/dist/src/semantics/transfers.js +6 -101
  193. package/dist/src/server.d.ts +7 -0
  194. package/dist/src/server.js +7 -0
  195. package/generated/pack-facts.json +94 -0
  196. package/package.json +10 -20
  197. package/spec/SOURCE.md +14 -0
  198. package/spec/fixtures3.json.gz +0 -0
  199. package/spec/openapi.json.gz +0 -0
  200. package/spec/patches.json +34 -0
  201. package/spec/sources.json +378 -0
  202. package/src/cli.ts +12 -26
  203. package/src/engine/after-payment.ts +31 -0
  204. package/src/engine/apps-secrets.ts +4 -0
  205. package/src/engine/balance.ts +33 -0
  206. package/src/engine/billing.ts +16 -0
  207. package/src/engine/charges.ts +19 -0
  208. package/src/engine/checkout.ts +40 -0
  209. package/src/engine/common.ts +47 -0
  210. package/src/engine/connect-settings.ts +23 -0
  211. package/src/engine/connect.ts +184 -0
  212. package/src/engine/coupons.ts +5 -0
  213. package/src/engine/credit-notes.ts +6 -0
  214. package/src/engine/customers.ts +35 -0
  215. package/src/engine/display.ts +19 -0
  216. package/src/engine/disputes.ts +9 -0
  217. package/src/engine/entitlements.ts +5 -0
  218. package/src/engine/ephemeral-keys.ts +3 -0
  219. package/src/engine/files.ts +41 -0
  220. package/src/engine/front.ts +31 -0
  221. package/src/engine/invoices.ts +71 -0
  222. package/src/engine/issuing.ts +61 -0
  223. package/src/engine/ledger.ts +50 -0
  224. package/src/engine/oauth.ts +19 -0
  225. package/src/engine/onboarding.ts +54 -0
  226. package/src/{stripe-params.ts → engine/params.ts} +8 -56
  227. package/src/engine/payment-intents.ts +33 -0
  228. package/src/engine/payment-links.ts +6 -0
  229. package/src/engine/payment-methods.ts +25 -0
  230. package/src/engine/plans.ts +42 -0
  231. package/src/engine/platform.ts +47 -0
  232. package/src/engine/products.ts +3 -0
  233. package/src/engine/radar.ts +6 -0
  234. package/src/engine/refunds.ts +29 -0
  235. package/src/engine/renewals.ts +73 -0
  236. package/src/engine/setup-intents.ts +5 -0
  237. package/src/{stripe-twin.ts → engine/stripe.ts} +28 -323
  238. package/src/engine/subscription-schedules.ts +10 -0
  239. package/src/engine/subscriptions.ts +40 -0
  240. package/src/engine/tax.ts +39 -0
  241. package/src/engine/terminal.ts +23 -0
  242. package/src/engine/test-clocks.ts +11 -0
  243. package/src/engine/tokens.ts +22 -0
  244. package/src/engine/transfers.ts +23 -0
  245. package/src/engine/treasury.ts +47 -0
  246. package/src/{stripe-version.ts → engine/version.ts} +4 -3
  247. package/src/engine/webhook-endpoints.ts +14 -0
  248. package/src/engine/webhooks.ts +72 -0
  249. package/src/fetch.ts +27 -0
  250. package/src/generated/surface.gen.json +1 -1
  251. package/src/generated/ui.gen.json +1 -1
  252. package/src/index.ts +8 -116
  253. package/src/manifest.ts +234 -786
  254. package/src/screens/{portal.tsx → billing-portal.tsx} +32 -37
  255. package/src/screens/checkout.tsx +33 -95
  256. package/src/screens/connect-oauth.tsx +64 -88
  257. package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +38 -82
  258. package/src/screens/dashboard.tsx +198 -0
  259. package/src/screens/shared.tsx +84 -0
  260. package/src/screens/stripe-js.ts +170 -0
  261. package/src/semantics/account_links.ts +21 -0
  262. package/src/semantics/accounts.ts +43 -0
  263. package/src/semantics/{apps-secrets.ts → apps.ts} +17 -32
  264. package/src/semantics/around.ts +20 -0
  265. package/src/semantics/balance.ts +6 -207
  266. package/src/semantics/balance_transactions.ts +20 -0
  267. package/src/semantics/billing_portal.ts +51 -0
  268. package/src/semantics/charges.ts +12 -211
  269. package/src/semantics/checkout.ts +13 -268
  270. package/src/semantics/clock.ts +26 -0
  271. package/src/semantics/coupons.ts +5 -67
  272. package/src/semantics/customers.ts +29 -392
  273. package/src/semantics/disputes.ts +9 -59
  274. package/src/semantics/doors.ts +80 -0
  275. package/src/semantics/entitlements.ts +9 -56
  276. package/src/semantics/events.ts +44 -0
  277. package/src/semantics/index.ts +111 -0
  278. package/src/semantics/invoice_payments.ts +18 -0
  279. package/src/semantics/invoiceitems.ts +59 -0
  280. package/src/semantics/invoices.ts +109 -443
  281. package/src/semantics/payment_intents.ts +83 -0
  282. package/src/semantics/payment_methods.ts +43 -0
  283. package/src/semantics/payouts.ts +23 -0
  284. package/src/semantics/prices.ts +42 -0
  285. package/src/semantics/products.ts +18 -142
  286. package/src/semantics/promotion_codes.ts +39 -0
  287. package/src/semantics/radar.ts +8 -77
  288. package/src/semantics/refunds.ts +9 -212
  289. package/src/semantics/setup_intents.ts +45 -0
  290. package/src/semantics/shared.ts +3092 -106
  291. package/src/semantics/states.ts +883 -0
  292. package/src/semantics/subscription_items.ts +54 -0
  293. package/src/semantics/subscription_schedules.ts +66 -0
  294. package/src/semantics/subscriptions.ts +49 -432
  295. package/src/semantics/{test-clocks.ts → test_helpers.ts} +23 -47
  296. package/src/semantics/transfers.ts +11 -131
  297. package/src/server.ts +8 -0
  298. package/client/dashboard-api.ts +0 -286
  299. package/client/stripe-mirror.css +0 -277
  300. package/client/stripe-mirror.tsx +0 -1559
  301. package/dist/client/dashboard-api.d.ts +0 -107
  302. package/dist/client/dashboard-api.js +0 -238
  303. package/dist/client/dashboard-api.ts +0 -286
  304. package/dist/client/stripe-mirror.bundle.js +0 -236
  305. package/dist/client/stripe-mirror.css +0 -277
  306. package/dist/client/stripe-mirror.d.ts +0 -134
  307. package/dist/client/stripe-mirror.js +0 -859
  308. package/dist/client/stripe-mirror.tsx +0 -1559
  309. package/dist/src/screens/connect-settings.js +0 -103
  310. package/dist/src/screens/consent-skin.d.ts +0 -4
  311. package/dist/src/screens/consent-skin.js +0 -18
  312. package/dist/src/screens/financial-connections.d.ts +0 -5
  313. package/dist/src/screens/financial-connections.js +0 -90
  314. package/dist/src/screens/identity.d.ts +0 -5
  315. package/dist/src/screens/identity.js +0 -86
  316. package/dist/src/screens/onboarding.d.ts +0 -13
  317. package/dist/src/screens/portal.d.ts +0 -5
  318. package/dist/src/screens/public-details.d.ts +0 -5
  319. package/dist/src/screens/public-details.js +0 -90
  320. package/dist/src/semantics/after-payment.d.ts +0 -22
  321. package/dist/src/semantics/after-payment.js +0 -99
  322. package/dist/src/semantics/apps-secrets.d.ts +0 -2
  323. package/dist/src/semantics/billing.d.ts +0 -2
  324. package/dist/src/semantics/billing.js +0 -220
  325. package/dist/src/semantics/connect.d.ts +0 -5
  326. package/dist/src/semantics/connect.js +0 -493
  327. package/dist/src/semantics/credit-notes.d.ts +0 -2
  328. package/dist/src/semantics/credit-notes.js +0 -172
  329. package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
  330. package/dist/src/semantics/ephemeral-keys.js +0 -34
  331. package/dist/src/semantics/files.d.ts +0 -2
  332. package/dist/src/semantics/files.js +0 -125
  333. package/dist/src/semantics/issuing.d.ts +0 -13
  334. package/dist/src/semantics/issuing.js +0 -575
  335. package/dist/src/semantics/ledger.d.ts +0 -59
  336. package/dist/src/semantics/ledger.js +0 -200
  337. package/dist/src/semantics/payment-intents.d.ts +0 -18
  338. package/dist/src/semantics/payment-intents.js +0 -452
  339. package/dist/src/semantics/payment-links.d.ts +0 -2
  340. package/dist/src/semantics/payment-links.js +0 -133
  341. package/dist/src/semantics/payment-methods.d.ts +0 -20
  342. package/dist/src/semantics/payment-methods.js +0 -140
  343. package/dist/src/semantics/plans.d.ts +0 -5
  344. package/dist/src/semantics/plans.js +0 -121
  345. package/dist/src/semantics/platform.d.ts +0 -9
  346. package/dist/src/semantics/platform.js +0 -206
  347. package/dist/src/semantics/renewals.d.ts +0 -47
  348. package/dist/src/semantics/renewals.js +0 -281
  349. package/dist/src/semantics/setup-intents.d.ts +0 -2
  350. package/dist/src/semantics/subscription-schedules.d.ts +0 -2
  351. package/dist/src/semantics/subscription-schedules.js +0 -119
  352. package/dist/src/semantics/tax.d.ts +0 -2
  353. package/dist/src/semantics/tax.js +0 -197
  354. package/dist/src/semantics/terminal.d.ts +0 -5
  355. package/dist/src/semantics/terminal.js +0 -182
  356. package/dist/src/semantics/test-clocks.d.ts +0 -6
  357. package/dist/src/semantics/tokens.d.ts +0 -4
  358. package/dist/src/semantics/tokens.js +0 -44
  359. package/dist/src/semantics/treasury.d.ts +0 -2
  360. package/dist/src/semantics/treasury.js +0 -377
  361. package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
  362. package/dist/src/semantics/webhook-endpoints.js +0 -85
  363. package/dist/src/stripe-budget.d.ts +0 -55
  364. package/dist/src/stripe-budget.js +0 -155
  365. package/dist/src/stripe-capabilities.d.ts +0 -3
  366. package/dist/src/stripe-capabilities.js +0 -5836
  367. package/dist/src/stripe-conformance.d.ts +0 -43
  368. package/dist/src/stripe-conformance.js +0 -105
  369. package/dist/src/stripe-connector.d.ts +0 -161
  370. package/dist/src/stripe-connector.js +0 -414
  371. package/dist/src/stripe-emit.d.ts +0 -2
  372. package/dist/src/stripe-emit.js +0 -145
  373. package/dist/src/stripe-events.d.ts +0 -93
  374. package/dist/src/stripe-events.js +0 -392
  375. package/dist/src/stripe-js.d.ts +0 -4
  376. package/dist/src/stripe-js.js +0 -70
  377. package/dist/src/stripe-mirror-ui.d.ts +0 -15
  378. package/dist/src/stripe-mirror-ui.js +0 -87
  379. package/dist/src/stripe-perform-harness.d.ts +0 -9
  380. package/dist/src/stripe-perform-harness.js +0 -26
  381. package/dist/src/stripe-server.d.ts +0 -33
  382. package/dist/src/stripe-server.js +0 -429
  383. package/dist/src/stripe-shared.d.ts +0 -109
  384. package/dist/src/stripe-shared.js +0 -276
  385. package/dist/src/stripe-ui-conformance.d.ts +0 -5
  386. package/dist/src/stripe-ui-conformance.js +0 -79
  387. package/dist/src/stripe-ui-structure.d.ts +0 -3
  388. package/dist/src/stripe-ui-structure.js +0 -168
  389. package/dist/test-fixtures/stripe-known-deviations.json +0 -110
  390. package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
  391. package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
  392. package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
  393. package/dist/test-fixtures/stripe-schemas.json +0 -3813
  394. package/src/screens/connect-settings.tsx +0 -121
  395. package/src/screens/consent-skin.ts +0 -20
  396. package/src/screens/financial-connections.tsx +0 -101
  397. package/src/screens/identity.tsx +0 -96
  398. package/src/screens/public-details.tsx +0 -105
  399. package/src/semantics/after-payment.ts +0 -118
  400. package/src/semantics/billing.ts +0 -216
  401. package/src/semantics/connect.ts +0 -487
  402. package/src/semantics/credit-notes.ts +0 -168
  403. package/src/semantics/ephemeral-keys.ts +0 -34
  404. package/src/semantics/files.ts +0 -143
  405. package/src/semantics/issuing.ts +0 -590
  406. package/src/semantics/ledger.ts +0 -253
  407. package/src/semantics/payment-intents.ts +0 -463
  408. package/src/semantics/payment-links.ts +0 -148
  409. package/src/semantics/payment-methods.ts +0 -145
  410. package/src/semantics/plans.ts +0 -131
  411. package/src/semantics/platform.ts +0 -220
  412. package/src/semantics/renewals.ts +0 -279
  413. package/src/semantics/setup-intents.ts +0 -87
  414. package/src/semantics/subscription-schedules.ts +0 -129
  415. package/src/semantics/tax.ts +0 -220
  416. package/src/semantics/terminal.ts +0 -195
  417. package/src/semantics/tokens.ts +0 -52
  418. package/src/semantics/treasury.ts +0 -383
  419. package/src/semantics/webhook-endpoints.ts +0 -87
  420. package/src/stripe-budget.ts +0 -181
  421. package/src/stripe-capabilities.ts +0 -5651
  422. package/src/stripe-conformance.ts +0 -124
  423. package/src/stripe-connector.ts +0 -462
  424. package/src/stripe-emit.ts +0 -157
  425. package/src/stripe-events.ts +0 -434
  426. package/src/stripe-js.ts +0 -70
  427. package/src/stripe-mirror-ui.ts +0 -90
  428. package/src/stripe-perform-harness.ts +0 -29
  429. package/src/stripe-server.ts +0 -393
  430. package/src/stripe-shared.ts +0 -297
  431. package/src/stripe-ui-conformance.ts +0 -88
  432. package/src/stripe-ui-structure.ts +0 -182
  433. package/test-fixtures/stripe-known-deviations.json +0 -110
  434. package/test-fixtures/stripe-openapi-operations.json +0 -4717
  435. package/test-fixtures/stripe-schemas.json +0 -3813
  436. /package/dist/src/{screens → engine}/industries.d.ts +0 -0
  437. /package/dist/src/{screens → engine}/industries.js +0 -0
  438. /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
  439. /package/dist/src/{semantics → engine}/test-cards.js +0 -0
  440. /package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -0
  441. /package/src/{screens → engine}/industries.ts +0 -0
  442. /package/src/{semantics → engine}/test-cards.ts +0 -0
@@ -1,166 +1,40 @@
1
- // Invoice and InvoiceItem semantics. The machine in ../manifest.ts says which moves of an invoice's
2
- // `status` are legal (finalize, send, pay, mark_uncollectible, void) and what Stripe answers
3
- // otherwise; these handlers compute each move, keep a draft's lines and totals in step with its
4
- // items, and mint the PaymentIntent a finalized invoice collects through. The invoice list and the
5
- // item list are the derived core's.
6
- import type { Semantics, SemanticsContext } from '@volter/world-core';
7
- import { addBillingInterval, applyCouponDiscount, asBool, cardError, declineFor, mintClientSecret, paginate, priceAmount, subscriptionItemPairs } from '../stripe-twin.ts';
8
- import { mintCharge } from './payment-intents.ts';
9
- import { spendOnceCoupon } from './renewals.ts';
10
- import { at, created, expanded, fail, finder, list, newest, path, search, send, type Row } from './shared.ts';
1
+ // Stripe's invoices 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 type { Row } from '../engine/common.ts';
5
+ import { collectsThroughIntent, draft, INV, linesOf, prorationLines, sumLines, transitions } from '../engine/invoices.ts';
6
+ import { addBillingInterval, applyCouponDiscount, asBool, cardError, declineFor, nowUnix, paginate, priceAmount, subscriptionItemPairs } from '../engine/stripe.ts';
7
+ import { activatedByPayment, at, created, editLines, expanded, fail, finalizedFields, finder, invoiceMissing, invoicesLoad, mintCharge, newest, nothingToPayWith, openPaymentIntent, path, pullPendingItem, search, send, spendOnceCoupon } from './shared.ts';
11
8
 
12
- const INV = 'invoice';
13
- const invoiceMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such invoice: '${id}'`, 404, 'resource_missing');
14
- const linesOf = (inv: Row): Row[] => (inv.lines as { data?: Row[] } | undefined)?.data ?? [];
15
- const sumLines = (lines: Row[]): number => lines.reduce((s, l) => s + (Number(l.amount) || 0), 0);
16
-
17
- /** Write a draft's line list and the totals derived from it. */
18
- async function writeLines(ctx: SemanticsContext, invoiceId: string, next: Row[]): Promise<Row> {
19
- const subtotal = sumLines(next);
20
- const lines = { object: 'list', data: next, has_more: false, total_count: next.length, url: `/v1/invoices/${invoiceId}/lines` };
21
- return ctx.write(INV, invoiceId, { lines, subtotal, total: subtotal, amount_due: subtotal, amount_remaining: subtotal }, 'invoice.update');
22
- }
23
-
24
- /** Paying an invoice with nothing to pay it: "If not set, defaults to the subscription's default payment method, if any,
25
- * or to the default payment method in the customer's invoice settings" (docs.stripe.com/api/invoices/object,
26
- * default_payment_method), and none of them is set. */
27
- function nothingToPayWith(ctx: SemanticsContext): Response {
28
- return fail(ctx, 'This customer has no attached payment source or default payment method. Please consider adding a default payment method.', 400, 'missing');
29
- }
30
-
31
- /** A subscription waiting on the invoice paid through the invoice's own pay (incomplete on its first, past_due on a
32
- * failed renewal) is active again. */
33
- async function activatedByPayment(ctx: SemanticsContext, subId: string): Promise<void> {
34
- await ctx.write('subscription', subId, { status: 'active' }, 'subscription.update');
35
- }
36
-
37
- /** A draft's line no invoice item made (a subscription's) stays as it is when an item changes. */
38
- function unlinkedLine(line: Row): Row {
39
- return line;
40
- }
41
-
42
- /** A preview's proration for a change at proration_date. Where the documentation stops and the twin decides: half the
43
- * period's charge credited as unused time and half charged as remaining time. */
44
- function prorationLines(out: Row[], subtotal: number, currency: string, now: number, period: number): void {
45
- const half = Math.round(subtotal / 2);
46
- out.unshift({ id: 'il_twin_proration_unused', object: 'line_item', type: 'invoiceitem', amount: -half, currency, proration: true, period: { start: now, end: period }, description: 'Unused time (proration credit)' });
47
- out.push({ id: 'il_twin_proration_remaining', object: 'line_item', type: 'invoiceitem', amount: half, currency, proration: true, period: { start: now, end: period }, description: 'Remaining time (proration charge)' });
9
+ // only a draft is deleted; a finalized invoice is voided instead
10
+ export async function DeleteInvoicesInvoice(ctx: HandlerContext): Promise<Response> {
11
+ const loaded = invoicesLoad(ctx, 'DeleteInvoicesInvoice');
12
+ if ('answer' in loaded) return loaded.answer;
13
+ const id = String(loaded.inv.id);
14
+ await ctx.write(INV, id, { deleted: true }, 'invoice.deleted');
15
+ return ctx.reply({ id, object: 'invoice', deleted: true });
48
16
  }
49
17
 
50
- /** Re-derive a draft's lines from the items still on it: an item that changed or was deleted moves the invoice with it. */
51
- async function resyncDraftLines(ctx: SemanticsContext, invoiceId: string): Promise<void> {
52
- const inv = ctx.get(INV, invoiceId);
53
- if (!inv || inv.status !== 'draft') return;
54
- const next: Row[] = [];
55
- for (const line of linesOf(inv)) {
56
- const itemId = typeof line.invoice_item === 'string' ? line.invoice_item : '';
57
- if (!itemId) next.push(unlinkedLine(line));
58
- const item = itemId ? ctx.get('invoiceitem', itemId) : undefined;
59
- if (item) next.push({ ...line, amount: Number(item.amount) || 0, description: (item.description as string) ?? null });
60
- }
61
- await writeLines(ctx, invoiceId, next);
18
+ export async function GetInvoicesInvoice(ctx: HandlerContext): Promise<Response> {
19
+ const id = at(ctx, 'invoice');
20
+ const inv = ctx.get(INV, id);
21
+ return inv ? ctx.reply(expanded(ctx, INV, inv)) : invoiceMissing(ctx, id);
62
22
  }
63
23
 
64
- // ── invoice items: a price-based item takes its amount from the price; one naming a draft invoice
65
- // lands on its lines at once ──
66
-
67
- /** An item priced by a Price: its unit amount times the quantity, in its currency (docs.stripe.com/api/invoiceitems/create#create_invoiceitem-pricing). */
68
- function pricedItem(ctx: SemanticsContext, priceRef: string, qty: number): { amount: number; currency: string | undefined } | Response {
69
- const price = ctx.get('price', priceRef);
70
- if (!price) return fail(ctx, `No such price: '${priceRef}'`, 400, 'resource_missing');
71
- return { amount: priceAmount(price, qty), currency: typeof price.currency === 'string' ? price.currency : undefined };
24
+ // the invoice's lines, a page at a time
25
+ export async function GetInvoicesInvoiceLines(ctx: HandlerContext): Promise<Response> {
26
+ const id = at(ctx, 'invoice');
27
+ const inv = ctx.get(INV, id);
28
+ if (!inv) return invoiceMissing(ctx, id);
29
+ const { page, hasMore } = paginate(linesOf(inv), ctx.params);
30
+ return ctx.reply({ object: 'list', url: path(ctx), has_more: hasMore, data: page.map((l) => ({ invoice: id, ...l })) });
72
31
  }
73
32
 
74
- const createItem: Semantics = async (ctx) => {
75
- const params = ctx.params;
76
- const now = Number(ctx.now());
77
- const qty = Number(params.quantity) || 1;
78
- // the served version names the price in `pricing[price]` ("The pricing information for the invoice item", the served
79
- // spec); an older version sends `price`. It is kept as the item's price, which the answer renders as `pricing`
80
- // (stripe-version.ts)
81
- const { pricing: pricingParam, ...stored } = params;
82
- const given = (pricingParam as Row | undefined)?.price;
83
- const priceRef = typeof given === 'string' && given ? given : typeof params.price === 'string' ? params.price : '';
84
- const priced = priceRef ? pricedItem(ctx, priceRef, qty) : undefined;
85
- if (priced instanceof Response) return priced;
86
- const resolvedAmount = priced?.amount;
87
- let resolvedCurrency = priced?.currency;
88
- // only a draft invoice takes new items
89
- const invoiceRef = typeof params.invoice === 'string' ? params.invoice : '';
90
- let target: Row | undefined;
91
- if (invoiceRef) {
92
- target = ctx.get(INV, invoiceRef);
93
- if (!target) return invoiceMissing(ctx, invoiceRef);
94
- if (target.status !== 'draft') return fail(ctx, `This invoice cannot have invoice items added because it has status ${String(target.status)}.`, 400, 'invoice_not_editable');
95
- if (resolvedCurrency === undefined && typeof target.currency === 'string') resolvedCurrency = target.currency;
96
- }
97
- // an invoice item is dated by `date`, not `created`; its currency is the price's, else the invoice's
98
- const item = await created(ctx, 'invoiceitem', { ...stored, ...(priceRef ? { price: priceRef } : {}) }, {
99
- livemode: false, discountable: true, proration: false, quantity: qty, quantity_decimal: String(qty),
100
- period: { start: now, end: now }, currency: resolvedCurrency ?? 'usd',
101
- ...(resolvedAmount !== undefined ? { amount: resolvedAmount } : {}),
102
- }, { timeField: 'date' });
103
- if (target) {
104
- await writeLines(ctx, invoiceRef, [...linesOf(target), {
105
- id: `il_${String(item.id)}`, object: 'line_item', type: 'invoiceitem', amount: Number(item.amount) || 0, currency: String(target.currency ?? item.currency ?? 'usd'),
106
- quantity: qty, proration: false, invoice_item: item.id, price: item.price ?? null,
107
- period: item.period ?? { start: now, end: now }, description: (item.description as string) ?? null,
108
- }]);
109
- }
110
- return ctx.reply(item);
111
- };
112
-
113
- const itemMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such invoiceitem: '${id}'`, 404, 'resource_missing');
114
-
115
- const retrieveItem: Semantics = async (ctx) => {
116
- const it = ctx.get('invoiceitem', at(ctx, 'invoiceitem'));
117
- return it ? ctx.reply(it) : itemMissing(ctx, at(ctx, 'invoiceitem'));
118
- };
119
-
120
- const updateItem: Semantics = async (ctx) => {
121
- const id = at(ctx, 'invoiceitem');
122
- const it = ctx.get('invoiceitem', id);
123
- if (!it) return itemMissing(ctx, id);
124
- const updated = await ctx.write('invoiceitem', id, ctx.params, 'invoiceitem.update');
125
- if (typeof it.invoice === 'string') await resyncDraftLines(ctx, it.invoice);
126
- return ctx.reply(updated);
127
- };
128
-
129
- // an item is deleted only while its invoice is still editable
130
- const deleteItem: Semantics = async (ctx) => {
131
- const id = at(ctx, 'invoiceitem');
132
- const it = ctx.get('invoiceitem', id);
133
- if (!it) return itemMissing(ctx, id);
134
- const onInvoice = typeof it.invoice === 'string' ? ctx.get(INV, it.invoice) : undefined;
135
- if (onInvoice && onInvoice.status !== 'draft') return fail(ctx, 'You cannot delete an invoice item that is attached to an invoice that is no longer editable.', 400, 'invoice_not_editable');
136
- await ctx.write('invoiceitem', id, { deleted: true }, 'invoiceitem.delete');
137
- if (typeof it.invoice === 'string') await resyncDraftLines(ctx, it.invoice);
138
- return ctx.reply({ id, object: 'invoiceitem', deleted: true });
139
- };
140
-
141
- // ── invoices ──
142
-
143
- /** A draft invoice's fields before anything is on it: what a new invoice and a preview start from. */
144
- const draft = (now: number): Row => ({
145
- status: 'draft', livemode: false, currency: 'usd', collection_method: 'charge_automatically',
146
- auto_advance: false, attempt_count: 0, attempted: false,
147
- amount_due: 0, amount_paid: 0, amount_remaining: 0, amount_overpaid: 0, amount_paid_off_stripe: 0, amount_shipping: 0,
148
- subtotal: 0, total: 0, starting_balance: 0, post_payment_credit_notes_amount: 0, pre_payment_credit_notes_amount: 0,
149
- period_start: now, period_end: now, default_tax_rates: [], discounts: [],
150
- lines: { object: 'list', data: [], has_more: false, total_count: 0 },
151
- automatic_tax: { enabled: false, liability: null, status: null },
152
- status_transitions: { finalized_at: null, marked_uncollectible_at: null, paid_at: null, voided_at: null },
153
- issuer: { type: 'self' },
154
- payment_settings: { default_mandate: null, payment_method_options: null, payment_method_types: null },
155
- // a draft has no PaymentIntent: finalize mints it
156
- payment_intent: null,
157
- });
158
-
159
33
  // a new invoice pulls in the customer's pending items only when asked: pending_invoice_items_behavior "Defaults to
160
34
  // `exclude` if the parameter is omitted" (docs.stripe.com/api/invoices/create)
161
- const create: Semantics = async (ctx) => {
35
+ export async function PostInvoices(ctx: HandlerContext): Promise<Response> {
162
36
  const params = ctx.params;
163
- const now = Number(ctx.now());
37
+ const now = Number(nowUnix(ctx.occurredAt));
164
38
  // the lines list names where it can be read, "The URL where this list can be accessed" (served spec, invoice.lines.url)
165
39
  const newId = ctx.mint(INV);
166
40
  // a send_invoice invoice is due `days_until_due` days "from when the invoice is created" (the same page); the customer's
@@ -177,254 +51,18 @@ const create: Semantics = async (ctx) => {
177
51
  const pending = customerId ? newest(ctx, 'invoiceitem', 'date').filter((it) => it.customer === customerId && !it.invoice).reverse() : [];
178
52
  for (const item of pending) await pullPendingItem(ctx, invoiceId, inv, item, now);
179
53
  return ctx.reply(pending.length > 0 ? (ctx.get(INV, invoiceId) ?? inv) : inv);
180
- };
181
-
182
- /** A draft invoice another object makes (an accepted quote): its customer's, with an invoice item per line the
183
- * object gives, and the object named as its parent. Answers the invoice's id. */
184
- export async function draftInvoiceFor(ctx: SemanticsContext, customer: string, currency: string, parent: Row, lines: Array<{ price: unknown; quantity: number; amount: number; description: unknown }>): Promise<string> {
185
- const now = Number(ctx.now());
186
- const id = ctx.mint(INV);
187
- const holder = ctx.get('customer', customer);
188
- const inv = await created(ctx, INV, { id, customer, currency, parent }, { ...draft(now), currency, customer_email: holder?.email ?? null, customer_name: holder?.name ?? null, lines: { object: 'list', data: [], has_more: false, total_count: 0, url: `/v1/invoices/${id}/lines` } });
189
- for (const l of lines) {
190
- const price = typeof l.price === 'string' ? l.price : l.price && typeof l.price === 'object' ? String((l.price as Row).id) : null;
191
- const item = await created(ctx, 'invoiceitem', { customer, currency, amount: l.amount, quantity: l.quantity, ...(price ? { price } : {}), description: l.description ?? null }, { date: now, livemode: false, metadata: {}, discountable: true, proration: false, period: { start: now, end: now } });
192
- await pullPendingItem(ctx, id, inv, item, now);
193
- }
194
- return id;
195
- }
196
-
197
- /** A customer's pending invoice item, made before the invoice, pulled onto it (docs.stripe.com/api/invoices/create#create_invoice-pending_invoice_items_behavior). */
198
- async function pullPendingItem(ctx: SemanticsContext, invoiceId: string, inv: Row, item: Row, now: number): Promise<void> {
199
- const current = ctx.get(INV, invoiceId) ?? inv;
200
- await writeLines(ctx, invoiceId, [...linesOf(current), {
201
- id: `il_${String(item.id)}`, object: 'line_item', type: 'invoiceitem', amount: Number(item.amount) || 0, currency: String(current.currency ?? item.currency ?? 'usd'),
202
- quantity: Number(item.quantity) || 1, proration: false, invoice_item: item.id, price: item.price ?? null,
203
- period: item.period ?? { start: now, end: now }, description: (item.description as string) ?? null,
204
- discountable: item.discountable ?? true, discounts: [], livemode: false, metadata: item.metadata ?? {}, tax_amounts: [], tax_rates: [],
205
- }]);
206
- await ctx.write('invoiceitem', String(item.id), { invoice: invoiceId }, 'invoiceitem.update');
207
- }
208
-
209
- /** The invoice the path names, and the machine's refusal when this operation may not move it. */
210
- function load(ctx: SemanticsContext, operationId: string): { inv: Row } | { answer: Response } {
211
- const id = at(ctx, 'invoice');
212
- const inv = ctx.get(INV, id);
213
- if (!inv) return { answer: invoiceMissing(ctx, id) };
214
- const refused = ctx.legal(INV, 'status', operationId, inv.status, undefined, id);
215
- return refused ? { answer: ctx.refuse(refused) } : { inv };
216
- }
217
-
218
- /** The PaymentIntent a charge_automatically invoice with a balance gets when it is finalized; it
219
- * waits for confirmation when the invoice already names a default payment method. */
220
- async function openPaymentIntent(ctx: SemanticsContext, piId: string, inv: Row, invoiceId: string): Promise<void> {
221
- const defaultPaymentMethod = typeof inv.default_payment_method === 'string' ? inv.default_payment_method : undefined;
222
- await created(ctx, 'payment_intent', {
223
- id: piId, amount: Number(inv.amount_due) || 0, currency: String(inv.currency ?? 'usd'), customer: inv.customer, invoice: invoiceId,
224
- ...(defaultPaymentMethod ? { payment_method: defaultPaymentMethod } : {}),
225
- }, {
226
- status: defaultPaymentMethod ? 'requires_confirmation' : 'requires_payment_method',
227
- client_secret: mintClientSecret(piId), livemode: false,
228
- capture_method: 'automatic', amount_capturable: 0, amount_received: 0, next_action: null,
229
- automatic_payment_methods: null, payment_method_types: ['card', 'link'], payment_method_options: {},
230
- });
231
- }
232
-
233
- /** Whether leaving draft mints a PaymentIntent: only a charge_automatically invoice with a balance. */
234
- // "Finalizing an invoice does the following: ... It creates an incomplete payment intent for the invoice"
235
- // (docs.stripe.com/invoicing/integration/workflow-transitions), whichever its collection method
236
- const collectsThroughIntent = (inv: Row): boolean => inv.status === 'draft' && (Number(inv.amount_due) || 0) > 0;
237
-
238
- const transitions = (inv: Row): object => (inv.status_transitions as object) ?? {};
239
-
240
- /** What finalizing assigns: the invoice's `number`, the customer's invoice_prefix and a sequence
241
- * (https://docs.stripe.com/api/invoices/object#invoice_object-number), and for send_invoice a
242
- * `due_date` days_until_due after finalization (#invoice_object-due_date). */
243
- /** A customer's invoice_prefix, "The prefix for the customer used to generate unique invoice numbers"
244
- * (docs.stripe.com/api/customers/object): the twin's, from the customer's id. */
245
- export const invoicePrefix = (customerId: string): string => customerId.replace(/[^a-z0-9]/gi, '').slice(-8).toUpperCase() || 'TWIN';
246
-
247
- export function finalizedFields(ctx: SemanticsContext, inv: Row, now: number): Row {
248
- const customerId = typeof inv.customer === 'string' ? inv.customer : '';
249
- const customer = customerId ? ctx.get('customer', customerId) : undefined;
250
- const prefix = typeof customer?.invoice_prefix === 'string' && customer.invoice_prefix
251
- ? customer.invoice_prefix
252
- : invoicePrefix(customerId);
253
- const numbered = ctx.rows(INV).filter((i) => i.customer === customerId && typeof i.number === 'string' && i.id !== inv.id).length;
254
- const fields: Row = { number: typeof inv.number === 'string' && inv.number ? inv.number : `${prefix}-${String(numbered + 1).padStart(4, '0')}` };
255
- const days = Number(inv.days_until_due);
256
- if (inv.collection_method === 'send_invoice' && inv.due_date == null && Number.isFinite(days) && days > 0) fields.due_date = now + days * 86400;
257
- return fields;
258
- }
259
-
260
- const finalize: Semantics = async (ctx) => {
261
- const loaded = load(ctx, 'PostInvoicesInvoiceFinalize');
262
- if ('answer' in loaded) return loaded.answer;
263
- const inv = loaded.inv;
264
- const id = String(inv.id);
265
- const piId = collectsThroughIntent(inv) ? ctx.mint('payment_intent') : null;
266
- const body = await ctx.write(INV, id, { ...finalizedFields(ctx, inv, Number(ctx.now())), status: 'open', status_transitions: { ...transitions(inv), finalized_at: ctx.now() }, payment_intent: piId }, 'invoice.finalize');
267
- // the customer's next_invoice_sequence, "The suffix of the customer's next invoice number" (docs.stripe.com/api/customers/object)
268
- const customerId = typeof inv.customer === 'string' ? inv.customer : '';
269
- if (customerId && ctx.get('customer', customerId)) await ctx.write('customer', customerId, { next_invoice_sequence: ctx.rows(INV).filter((i) => i.customer === customerId && typeof i.number === 'string').length + 1 }, 'customer.invoice_sequence');
270
- if (piId) await openPaymentIntent(ctx, piId, inv, id);
271
- // a subscription's invoice finalized spends the once coupon it took (renewals.ts spendOnceCoupon)
272
- await spendOnceCoupon(ctx, inv);
273
- return ctx.reply(expanded(ctx, INV, body));
274
- };
275
-
276
- // sending a draft finalizes it; any other invoice keeps its status and records the send
277
- const sendInvoice: Semantics = async (ctx) => {
278
- const loaded = load(ctx, 'PostInvoicesInvoiceSend');
279
- if ('answer' in loaded) return loaded.answer;
280
- const inv = loaded.inv;
281
- const id = String(inv.id);
282
- const becameOpen = inv.status === 'draft';
283
- const piId = collectsThroughIntent(inv) ? ctx.mint('payment_intent') : null;
284
- const now = ctx.now();
285
- const body = await ctx.write(INV, id, {
286
- status: becameOpen ? 'open' : (inv.status as string), _sent: true, _sent_at: now,
287
- status_transitions: { ...transitions(inv), ...(becameOpen ? { finalized_at: now } : {}) },
288
- ...(becameOpen ? { payment_intent: piId } : {}),
289
- }, 'invoice.sent');
290
- if (piId) await openPaymentIntent(ctx, piId, inv, id);
291
- if (becameOpen) await spendOnceCoupon(ctx, inv);
292
- return ctx.reply(expanded(ctx, INV, body));
293
- };
294
-
295
- // paying charges a payment method (the one passed, the invoice's default, or the customer's) unless
296
- // the payment is recorded out of band; a declining card answers 402 and leaves the invoice open. A
297
- // paid invoice settles the PaymentIntent finalize opened, and names the charge that took the money.
298
- const pay: Semantics = async (ctx) => {
299
- const id = at(ctx, 'invoice');
300
- const inv = ctx.get(INV, id);
301
- if (!inv) return invoiceMissing(ctx, id);
302
- const refused = ctx.legal(INV, 'status', 'PostInvoicesInvoicePay', inv.status, undefined, id);
303
- if (refused) return ctx.refuse(refused);
304
- const params = ctx.params;
305
- const paidOutOfBand = params.paid_out_of_band !== undefined && asBool(params.paid_out_of_band);
306
- const payer = ctx.get('customer', String(inv.customer ?? ''));
307
- const customerDefault = (payer?.invoice_settings as Row | undefined)?.default_payment_method || payer?.default_source;
308
- if (!paidOutOfBand && !params.payment_method && !params.source && !inv.default_payment_method && !inv.default_source && !customerDefault) return nothingToPayWith(ctx);
309
- // what it charges: the method passed, else the invoice's default, its subscription's, then its customer's
310
- // (docs.stripe.com/api/invoices/pay#pay_invoice-payment_method)
311
- const subscription = typeof inv.subscription === 'string' ? ctx.get('subscription', inv.subscription) : undefined;
312
- const decline = declineFor(finder(ctx), params, { payment_method: inv.default_payment_method ?? subscription?.default_payment_method ?? customerDefault });
313
- const intent = typeof inv.payment_intent === 'string' ? ctx.get('payment_intent', inv.payment_intent) : undefined;
314
- if (decline) return send(ctx, cardError(decline, intent ? { payment_intent: intent } : {}));
315
- const amountPaid = Number(inv.total ?? inv.amount_due) || 0;
316
- let body = await ctx.write(INV, id, {
317
- // paid outside Stripe, the invoice counts it as such: `amount_paid_off_stripe`, "Amount, in cents (or local
318
- // equivalent), that was paid on the invoice outside of Stripe" (served spec, invoice), the only place the served
319
- // version shows it now that basil renders no `paid_out_of_band` (stripe-version.ts)
320
- status: 'paid', paid: true, paid_out_of_band: paidOutOfBand, amount_paid: amountPaid, amount_remaining: 0,
321
- ...(paidOutOfBand ? { amount_paid_off_stripe: amountPaid } : {}),
322
- status_transitions: { ...transitions(inv), paid_at: ctx.now() },
323
- }, 'invoice.pay');
324
- if (!paidOutOfBand && typeof inv.payment_intent === 'string' && inv.payment_intent) {
325
- const pi = ctx.get('payment_intent', inv.payment_intent);
326
- if (pi && pi.status !== 'succeeded' && !ctx.legal('payment_intent', 'status', 'PostInvoicesInvoicePay', pi.status)) {
327
- const chargeId = await mintCharge(ctx, inv.payment_intent, { ...pi, invoice: id, ...(typeof params.payment_method === 'string' ? { payment_method: params.payment_method } : {}) }, amountPaid);
328
- await ctx.write('payment_intent', inv.payment_intent, { status: 'succeeded', amount_received: amountPaid, amount_capturable: 0, next_action: null, latest_charge: chargeId }, 'payment_intent.succeeded');
329
- body = await ctx.write(INV, id, { charge: chargeId }, 'invoice.update');
330
- }
331
- }
332
- // a subscription waiting on this invoice (incomplete on its first, past_due on a failed renewal) is active once it is paid
333
- const subId = typeof inv.subscription === 'string' ? inv.subscription : undefined;
334
- const sub = subId ? ctx.get('subscription', subId) : undefined;
335
- if (subId && sub && (sub.status === 'incomplete' || sub.status === 'past_due') && !ctx.legal('subscription', 'status', 'PostInvoicesInvoicePay', sub.status, 'active', subId)) await activatedByPayment(ctx, subId);
336
- return ctx.reply(expanded(ctx, INV, body));
337
- };
338
-
339
- // only a draft is deleted; a finalized invoice is voided instead
340
- const deleteDraft: Semantics = async (ctx) => {
341
- const id = at(ctx, 'invoice');
342
- const inv = ctx.get(INV, id);
343
- if (!inv) return invoiceMissing(ctx, id);
344
- if (inv.status !== 'draft') return fail(ctx, 'You can only delete draft invoices.', 400, 'invoice_not_editable');
345
- await ctx.write(INV, id, { deleted: true }, 'invoice.deleted');
346
- return ctx.reply({ id, object: 'invoice', deleted: true });
347
- };
348
-
349
- const markUncollectible: Semantics = async (ctx) => {
350
- const loaded = load(ctx, 'PostInvoicesInvoiceMarkUncollectible');
351
- if ('answer' in loaded) return loaded.answer;
352
- const inv = loaded.inv;
353
- const body = await ctx.write(INV, String(inv.id), { status: 'uncollectible', status_transitions: { ...transitions(inv), marked_uncollectible_at: ctx.now() } }, 'invoice.marked_uncollectible');
354
- return ctx.reply(expanded(ctx, INV, body));
355
- };
356
-
357
- const voidInvoice: Semantics = async (ctx) => {
358
- const loaded = load(ctx, 'PostInvoicesInvoiceVoid');
359
- if ('answer' in loaded) return loaded.answer;
360
- const inv = loaded.inv;
361
- const body = await ctx.write(INV, String(inv.id), { status: 'void', status_transitions: { ...transitions(inv), voided_at: ctx.now() } }, 'invoice.void');
362
- return ctx.reply(expanded(ctx, INV, body));
363
- };
364
-
365
- // the invoice's lines, a page at a time
366
- const lines: Semantics = async (ctx) => {
367
- const id = at(ctx, 'invoice');
368
- const inv = ctx.get(INV, id);
369
- if (!inv) return invoiceMissing(ctx, id);
370
- const { page, hasMore } = paginate(linesOf(inv), ctx.params);
371
- return ctx.reply({ object: 'list', url: path(ctx), has_more: hasMore, data: page.map((l) => ({ invoice: id, ...l })) });
372
- };
373
-
374
- // add_lines, update_lines and remove_lines edit a draft's lines in bulk and re-derive its totals
375
- function editLines(kind: 'add' | 'update' | 'remove'): Semantics {
376
- return async (ctx) => {
377
- const id = at(ctx, 'invoice');
378
- const inv = ctx.get(INV, id);
379
- if (!inv) return invoiceMissing(ctx, id);
380
- if (inv.status !== 'draft') return fail(ctx, `You can only add/update/remove lines on a draft invoice (this one is ${String(inv.status)}).`, 400, 'invoice_not_editable');
381
- const cur = linesOf(inv);
382
- let next = [...cur];
383
- const raw = ctx.params.lines;
384
- const entries = Array.isArray(raw) ? (raw as Row[]) : raw && typeof raw === 'object' ? [raw as Row] : [];
385
- if (entries.length === 0) return fail(ctx, 'Missing required param: lines.', 400, 'parameter_missing');
386
- const currency = String(inv.currency ?? 'usd');
387
- if (kind === 'add') {
388
- for (let i = 0; i < entries.length; i++) {
389
- const e = entries[i]!;
390
- const priceRef = typeof e.price === 'string' ? e.price : '';
391
- const price = priceRef ? ctx.get('price', priceRef) : undefined;
392
- if (priceRef && !price) return fail(ctx, `No such price: '${priceRef}'`, 400, 'resource_missing');
393
- const quantity = e.quantity !== undefined ? Math.max(1, Math.trunc(Number(e.quantity) || 1)) : 1;
394
- const unit = Math.trunc(Number(e.amount) || 0);
395
- next.push({ id: `il_twin_${id}_${cur.length + i + 1}`, object: 'line_item', amount: price ? priceAmount(price, quantity) : unit, currency, quantity, price: price ?? (priceRef || null), description: typeof e.description === 'string' ? e.description : null, proration: false });
396
- }
397
- } else if (kind === 'update') {
398
- for (const e of entries) {
399
- const lid = typeof e.id === 'string' ? e.id : '';
400
- next = next.map((l) => (l.id === lid ? { ...l, ...(e.amount !== undefined ? { amount: Math.trunc(Number(e.amount) || 0) } : {}), ...(e.quantity !== undefined ? { quantity: Math.max(1, Math.trunc(Number(e.quantity) || 1)) } : {}), ...(e.description !== undefined ? { description: e.description } : {}) } : l));
401
- }
402
- } else {
403
- const remove = new Set(entries.map((e) => (typeof e.id === 'string' ? e.id : '')).filter(Boolean));
404
- next = next.filter((l) => !remove.has(String(l.id)));
405
- }
406
- return ctx.reply(await writeLines(ctx, id, next));
407
- };
408
54
  }
409
55
 
410
- const searchInvoices: Semantics = async (ctx) => search(ctx, INV);
411
-
412
- const retrieve: Semantics = async (ctx) => {
413
- const id = at(ctx, 'invoice');
414
- const inv = ctx.get(INV, id);
415
- return inv ? ctx.reply(expanded(ctx, INV, inv)) : invoiceMissing(ctx, id);
416
- };
417
-
418
56
  /**
419
57
  * A preview of the next invoice for a customer or subscription (create_preview), computed and never
420
58
  * stored: the subscription's items at their prices, the customer's pending items, the subscription's
421
59
  * discount, and with `subscription_details.proration_date` a credit for the unused period against a
422
60
  * charge for the new one (split evenly: there is no clock here to measure the remaining fraction).
423
61
  */
424
- const createPreview: Semantics = async (ctx) => {
62
+ export async function PostInvoicesCreatePreview(ctx: HandlerContext): Promise<Response> {
425
63
  const params = ctx.params;
426
64
  const prorationDate = (params.subscription_details as Row | undefined)?.proration_date;
427
- const now = Number(ctx.now());
65
+ const now = Number(nowUnix(ctx.occurredAt));
428
66
  let subId = typeof params.subscription === 'string' ? params.subscription : '';
429
67
  let customer = typeof params.customer === 'string' ? params.customer : '';
430
68
  let sub: Row | undefined;
@@ -486,60 +124,88 @@ const createPreview: Semantics = async (ctx) => {
486
124
  total_discount_amounts: discountAmount > 0 ? [{ amount: discountAmount, discount: subDiscount?.id ?? null }] : [],
487
125
  lines: { object: 'list', url: '/v1/invoices/create_preview/lines', has_more: false, total_count: out.length, data: out },
488
126
  });
489
- };
127
+ }
490
128
 
491
- /** An invoice's payment as Stripe answers it (docs.stripe.com/api/invoice-payment/object): the PaymentIntent that
492
- * collects the invoice, open until the invoice is paid. Where the documentation stops and the twin decides: an
493
- * invoice has one payment, its default, and the payment's id is the invoice's with `inpay_`. */
494
- function invoicePayment(inv: Row): Row | undefined {
495
- if (typeof inv.payment_intent !== 'string') return undefined;
496
- const paid = inv.status === 'paid';
497
- const transitions = (inv.status_transitions as Row | undefined) ?? {};
498
- return {
499
- id: `inpay_${String(inv.id).replace(/^in_/, '')}`, object: 'invoice_payment', invoice: inv.id, is_default: true, livemode: false,
500
- amount_requested: Number(inv.amount_due ?? 0), amount_paid: paid ? Number(inv.amount_paid ?? inv.amount_due ?? 0) : null, currency: inv.currency,
501
- created: inv.created, payment: { type: 'payment_intent', payment_intent: inv.payment_intent },
502
- status: paid ? 'paid' : inv.status === 'void' ? 'canceled' : 'open',
503
- status_transitions: { paid_at: paid ? (transitions.paid_at ?? null) : null, canceled_at: inv.status === 'void' ? (transitions.voided_at ?? null) : null },
504
- };
129
+ export async function PostInvoicesInvoiceFinalize(ctx: HandlerContext): Promise<Response> {
130
+ const loaded = invoicesLoad(ctx, 'PostInvoicesInvoiceFinalize');
131
+ if ('answer' in loaded) return loaded.answer;
132
+ const inv = loaded.inv;
133
+ const id = String(inv.id);
134
+ const piId = collectsThroughIntent(inv) ? ctx.mint('payment_intent') : null;
135
+ const body = await ctx.write(INV, id, { ...finalizedFields(ctx, inv, Number(nowUnix(ctx.occurredAt))), status: 'open', status_transitions: { ...transitions(inv), finalized_at: nowUnix(ctx.occurredAt) }, payment_intent: piId }, 'invoice.finalize');
136
+ if (piId) await openPaymentIntent(ctx, piId, inv, id);
137
+ // a subscription's invoice finalized spends the once coupon it took (renewals.ts spendOnceCoupon)
138
+ await spendOnceCoupon(ctx, inv);
139
+ return ctx.reply(expanded(ctx, INV, body));
505
140
  }
506
141
 
507
- const invoicePayments: Semantics = async (ctx) => {
508
- const p = ctx.params;
509
- const wanted = (p.payment as Row | undefined)?.payment_intent;
510
- const items = newest(ctx, INV)
511
- .filter((inv) => typeof p.invoice !== 'string' || inv.id === p.invoice)
512
- .map(invoicePayment).filter((x): x is Row => !!x)
513
- .filter((x) => typeof wanted !== 'string' || (x.payment as Row).payment_intent === wanted)
514
- .filter((x) => typeof p.status !== 'string' || x.status === p.status);
515
- return list(ctx, 'invoice_payment', items);
516
- };
142
+ // paying charges a payment method (the one passed, the invoice's default, or the customer's) unless
143
+ // the payment is recorded out of band; a declining card answers 402 and leaves the invoice open. A
144
+ // paid invoice settles the PaymentIntent finalize opened, and names the charge that took the money.
145
+ export async function PostInvoicesInvoicePay(ctx: HandlerContext): Promise<Response> {
146
+ const id = at(ctx, 'invoice');
147
+ const inv = ctx.get(INV, id);
148
+ if (!inv) return invoiceMissing(ctx, id);
149
+ const refused = ctx.legal(INV, 'status', 'PostInvoicesInvoicePay', inv.status, undefined, id);
150
+ if (refused) return ctx.refuse(refused);
151
+ const params = ctx.params;
152
+ const paidOutOfBand = params.paid_out_of_band !== undefined && asBool(params.paid_out_of_band);
153
+ const payer = ctx.get('customer', String(inv.customer ?? ''));
154
+ const customerDefault = (payer?.invoice_settings as Row | undefined)?.default_payment_method || payer?.default_source;
155
+ if (!paidOutOfBand && !params.payment_method && !params.source && !inv.default_payment_method && !inv.default_source && !customerDefault) return nothingToPayWith(ctx);
156
+ // what it charges: the method passed, else the invoice's default, its subscription's, then its customer's
157
+ // (docs.stripe.com/api/invoices/pay#pay_invoice-payment_method)
158
+ const subscription = typeof inv.subscription === 'string' ? ctx.get('subscription', inv.subscription) : undefined;
159
+ const decline = declineFor(finder(ctx), params, { payment_method: inv.default_payment_method ?? subscription?.default_payment_method ?? customerDefault });
160
+ const intent = typeof inv.payment_intent === 'string' ? ctx.get('payment_intent', inv.payment_intent) : undefined;
161
+ if (decline) return send(ctx, cardError(decline, intent ? { payment_intent: intent } : {}));
162
+ const amountPaid = Number(inv.total ?? inv.amount_due) || 0;
163
+ let body = await ctx.write(INV, id, {
164
+ // paid outside Stripe, the invoice counts it as such: `amount_paid_off_stripe`, "Amount, in cents (or local
165
+ // equivalent), that was paid on the invoice outside of Stripe" (served spec, invoice), the only place the served
166
+ // version shows it now that basil renders no `paid_out_of_band` (../engine/version.ts)
167
+ status: 'paid', paid: true, paid_out_of_band: paidOutOfBand, amount_paid: amountPaid, amount_remaining: 0,
168
+ ...(paidOutOfBand ? { amount_paid_off_stripe: amountPaid } : {}),
169
+ status_transitions: { ...transitions(inv), paid_at: nowUnix(ctx.occurredAt) },
170
+ }, 'invoice.pay');
171
+ if (!paidOutOfBand && typeof inv.payment_intent === 'string' && inv.payment_intent) {
172
+ const pi = ctx.get('payment_intent', inv.payment_intent);
173
+ if (pi && pi.status !== 'succeeded' && !ctx.legal('payment_intent', 'status', 'PostInvoicesInvoicePay', pi.status)) {
174
+ const chargeId = await mintCharge(ctx, inv.payment_intent, { ...pi, invoice: id, ...(typeof params.payment_method === 'string' ? { payment_method: params.payment_method } : {}) }, amountPaid);
175
+ await ctx.write('payment_intent', inv.payment_intent, { status: 'succeeded', amount_received: amountPaid, amount_capturable: 0, next_action: null, latest_charge: chargeId }, 'payment_intent.succeeded');
176
+ body = await ctx.write(INV, id, { charge: chargeId }, 'invoice.update');
177
+ }
178
+ }
179
+ // a subscription waiting on this invoice (incomplete on its first, past_due on a failed renewal) is active once it is paid
180
+ const subId = typeof inv.subscription === 'string' ? inv.subscription : undefined;
181
+ const sub = subId ? ctx.get('subscription', subId) : undefined;
182
+ if (subId && sub && (sub.status === 'incomplete' || sub.status === 'past_due') && !ctx.legal('subscription', 'status', 'PostInvoicesInvoicePay', sub.status, 'active', subId)) await activatedByPayment(ctx, subId);
183
+ return ctx.reply(expanded(ctx, INV, body));
184
+ }
517
185
 
518
- const invoicePaymentOne: Semantics = async (ctx) => {
519
- const id = at(ctx, 'invoice_payment');
520
- const found = newest(ctx, INV).map(invoicePayment).find((x) => x?.id === id);
521
- return found ? ctx.reply(found) : fail(ctx, `No such invoice payment: '${id}'`, 404, 'resource_missing');
522
- };
186
+ // sending a draft finalizes it; any other invoice keeps its status and records the send
187
+ export async function PostInvoicesInvoiceSend(ctx: HandlerContext): Promise<Response> {
188
+ const loaded = invoicesLoad(ctx, 'PostInvoicesInvoiceSend');
189
+ if ('answer' in loaded) return loaded.answer;
190
+ const inv = loaded.inv;
191
+ const id = String(inv.id);
192
+ const becameOpen = inv.status === 'draft';
193
+ const piId = collectsThroughIntent(inv) ? ctx.mint('payment_intent') : null;
194
+ const now = nowUnix(ctx.occurredAt);
195
+ const body = await ctx.write(INV, id, {
196
+ status: becameOpen ? 'open' : (inv.status as string), _sent: true, _sent_at: now,
197
+ status_transitions: { ...transitions(inv), ...(becameOpen ? { finalized_at: now } : {}) },
198
+ ...(becameOpen ? { payment_intent: piId } : {}),
199
+ }, 'invoice.sent');
200
+ if (piId) await openPaymentIntent(ctx, piId, inv, id);
201
+ if (becameOpen) await spendOnceCoupon(ctx, inv);
202
+ return ctx.reply(expanded(ctx, INV, body));
203
+ }
523
204
 
524
- export const invoices: Record<string, Semantics> = {
525
- GetInvoicePayments: invoicePayments,
526
- GetInvoicePaymentsInvoicePayment: invoicePaymentOne,
527
- PostInvoiceitems: createItem,
528
- GetInvoiceitemsInvoiceitem: retrieveItem,
529
- PostInvoiceitemsInvoiceitem: updateItem,
530
- DeleteInvoiceitemsInvoiceitem: deleteItem,
531
- PostInvoices: create,
532
- PostInvoicesCreatePreview: createPreview,
533
- PostInvoicesInvoiceFinalize: finalize,
534
- PostInvoicesInvoiceSend: sendInvoice,
535
- PostInvoicesInvoicePay: pay,
536
- PostInvoicesInvoiceMarkUncollectible: markUncollectible,
537
- PostInvoicesInvoiceVoid: voidInvoice,
538
- DeleteInvoicesInvoice: deleteDraft,
539
- GetInvoicesInvoiceLines: lines,
540
- PostInvoicesInvoiceAddLines: editLines('add'),
541
- PostInvoicesInvoiceUpdateLines: editLines('update'),
542
- PostInvoicesInvoiceRemoveLines: editLines('remove'),
543
- GetInvoicesSearch: searchInvoices,
544
- GetInvoicesInvoice: retrieve,
545
- };
205
+ export async function PostInvoicesInvoiceVoid(ctx: HandlerContext): Promise<Response> {
206
+ const loaded = invoicesLoad(ctx, 'PostInvoicesInvoiceVoid');
207
+ if ('answer' in loaded) return loaded.answer;
208
+ const inv = loaded.inv;
209
+ const body = await ctx.write(INV, String(inv.id), { status: 'void', status_transitions: { ...transitions(inv), voided_at: nowUnix(ctx.occurredAt) } }, 'invoice.void');
210
+ return ctx.reply(expanded(ctx, INV, body));
211
+ }