@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,11 +1,6 @@
1
- import type { Semantics, SemanticsContext } from '@volter/world-core';
2
- import { type Row } from './shared.js';
3
- /** A `trial_end` Stripe refuses: the parameter is `"now" | timestamp` (docs.stripe.com/api/subscriptions/update), and a
4
- * timestamp that is not in the future answers "Invalid timestamp: must be an integer Unix timestamp in the future."
5
- * (Stripe's own message, as its SDKs' users report it: github.com/stripe/stripe-dotnet/issues/805,
6
- * github.com/stripe/stripe-ruby/issues/682). Where the documentation stops and the twin decides: a value that is no
7
- * number at all, and one more than two years on, answer that same message. */
8
- export declare function trialEndRefused(ctx: SemanticsContext, value: unknown, now: number): Response | undefined;
9
- /** A new subscription's items, each its own subscription_item as Stripe keeps it (/v1/subscription_items/{id}). */
10
- export declare function storeItems(ctx: SemanticsContext, subId: string, items: Row[]): Promise<void>;
11
- export declare const subscriptions: Record<string, Semantics>;
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ export declare function DeleteSubscriptionsSubscriptionExposedId(ctx: HandlerContext): Promise<Response>;
3
+ export declare function GetSubscriptions(ctx: HandlerContext): Promise<Response>;
4
+ export declare function GetSubscriptionsSubscriptionExposedId(ctx: HandlerContext): Promise<Response>;
5
+ export declare function PostSubscriptionsSubscriptionExposedId(ctx: HandlerContext): Promise<Response>;
6
+ export declare function DeleteSubscriptionsSubscriptionExposedIdDiscount(ctx: HandlerContext): Promise<Response>;
@@ -1,326 +1,46 @@
1
- import { addBillingInterval, applyCouponDiscount, asBool, buildDiscount, buildSubscriptionItemsList, cardError, declineFor, mintClientSecret, priceAmount, resolveSubscriptionBillingInterval, resolveTrial, subscriptionCouponParam, subscriptionHasPrice, subscriptionItemEntries, subscriptionItemPrice, } from "../stripe-twin.js";
2
- import { finalizedFields } from "./invoices.js";
3
- import { mintCharge } from "./payment-intents.js";
4
- import { advanceBilling, clockNow, collectSubscriptionInvoice, draftSubscriptionInvoice, itemQuantity, keptAfter, payerOf, spendOnceCoupon } from "./renewals.js";
5
- import { at, created, deletedDiscount, expanded, fail, finder, list, newest, redeemCoupon, search, send, where } from "./shared.js";
6
- const SUB = 'subscription';
7
- const subMissing = (ctx, id) => fail(ctx, `No such subscription: '${id}'`, 404, 'resource_missing');
8
- const create = async (ctx) => {
9
- const params = ctx.params;
10
- const customer = typeof params.customer === 'string' ? params.customer : '';
11
- if (!customer)
12
- return fail(ctx, 'Missing required param: customer.', 400, 'parameter_missing');
13
- if (!ctx.get('customer', customer))
14
- return fail(ctx, `No such customer: '${customer}'`, 404, 'resource_missing');
15
- const now = clockNow(ctx, customer);
16
- const badTrial = trialEndRefused(ctx, params.trial_end, now);
17
- if (badTrial)
18
- return badTrial;
19
- // a coupon (or discounts[0][coupon]) must exist; it becomes the subscription's discount
20
- const couponId = subscriptionCouponParam(params);
21
- let discounts = [];
22
- if (couponId) {
23
- const coupon = ctx.get('coupon', couponId);
24
- if (!coupon)
25
- return fail(ctx, `No such coupon: '${couponId}'`, 400, 'resource_missing');
26
- discounts = [buildDiscount(coupon, customer, now)];
27
- }
28
- // a trial is the current period; otherwise one billing interval of the first item's price. The
29
- // period sits top-level: this twin's default API version predates basil moving it onto items.
30
- const trial = resolveTrial(params, now);
31
- const itemEntries = subscriptionItemEntries(params.items);
32
- const { interval, interval_count } = resolveSubscriptionBillingInterval(itemEntries, finder(ctx));
33
- const periodStart = trial ? trial.start : now;
34
- const periodEnd = trial ? trial.end : addBillingInterval(now, interval, interval_count);
35
- const subId = typeof params.id === 'string' && params.id ? params.id : ctx.mint(SUB);
36
- const itemsList = buildSubscriptionItemsList(itemEntries, subId, now, finder(ctx));
37
- const itemsData = itemsList.data;
38
- const currency = typeof params.currency === 'string' && params.currency ? params.currency : 'usd';
39
- // Stripe opens a subscription's first invoice at once (a trialing one has nothing due yet) and, by
40
- // payment_behavior (docs.stripe.com/api/subscriptions/create#create_subscription-payment_behavior):
41
- // allow_incomplete (the default) charges the default payment method now, and without one, or on a decline,
42
- // leaves the subscription incomplete with the invoice open; error_if_incomplete refuses the create instead;
43
- // default_incomplete never charges, leaving a PaymentIntent the frontend confirms.
44
- const behavior = typeof params.payment_behavior === 'string' ? params.payment_behavior : 'allow_incomplete';
45
- const needsFirstInvoice = !trial;
46
- const holder = ctx.get('customer', customer) ?? {};
47
- const payWith = behavior === 'default_incomplete' ? undefined
48
- : (typeof params.default_payment_method === 'string' ? params.default_payment_method : undefined) ?? holder.invoice_settings?.default_payment_method ?? undefined;
49
- const declined = payWith ? declineFor(finder(ctx), { payment_method: payWith }) : undefined;
50
- let subtotal = 0;
51
- for (const it of itemsData) {
52
- const price = it.price && typeof it.price === 'object' ? it.price : undefined;
53
- subtotal += priceAmount(price, itemQuantity(it));
54
- }
55
- const coupon = discounts[0]?.coupon;
56
- const discountAmount = applyCouponDiscount(subtotal, coupon);
57
- const invoiceTotal = Math.max(0, subtotal - discountAmount);
58
- // a $0 first invoice is paid at once and needs no PaymentIntent
59
- const isFree = invoiceTotal <= 0;
60
- const pays = needsFirstInvoice && !isFree && !!payWith && !declined;
61
- if (needsFirstInvoice && !isFree && !pays && behavior === 'error_if_incomplete')
62
- return firstPaymentRefused(ctx, declined);
63
- // minted first so the subscription names its latest_invoice in its own create (a trial's $0 invoice too)
64
- const invoiceId = needsFirstInvoice ? ctx.mint('invoice') : null;
65
- const trialInvoiceId = needsFirstInvoice ? null : ctx.mint('invoice');
66
- const piId = needsFirstInvoice && !isFree ? ctx.mint('payment_intent') : null;
67
- const { coupon: _c, discounts: _ds, trial_period_days: _tpd, trial_end: _te, items: _items, id: _id, ...rest } = params;
68
- const sub = await created(ctx, SUB, { ...rest, id: subId }, {
69
- status: trial ? 'trialing' : needsFirstInvoice && !isFree && !pays ? 'incomplete' : 'active',
70
- livemode: false, currency, collection_method: 'charge_automatically',
71
- // a trial's end is the billing anchor: "The `billing_cycle_anchor` will be updated to the `trial_end` value"
72
- // (docs.stripe.com/api/subscriptions/create#create_subscription-trial_end)
73
- cancel_at_period_end: false, start_date: now, billing_cycle_anchor: trial ? trial.end : now, metadata: {},
74
- discounts: discounts.map((d) => ({ ...d, subscription: subId })), billing_schedules: [],
75
- ...(trial ? { trial_start: trial.start, trial_end: trial.end } : { trial_start: null, trial_end: null }),
76
- current_period_start: periodStart, current_period_end: periodEnd,
77
- items: itemsList,
78
- latest_invoice: invoiceId ?? trialInvoiceId,
79
- automatic_tax: { enabled: false, liability: null },
80
- billing_mode: { type: 'classic' },
81
- invoice_settings: { issuer: { type: 'self' } },
82
- });
83
- await storeItems(ctx, subId, itemsData);
84
- if (discounts.length)
85
- await redeemCoupon(ctx, couponId);
86
- // a customer billed for the first time takes the subscription's currency
87
- if (!holder.currency)
88
- await ctx.write('customer', customer, { currency }, 'customer.update');
89
- if (!invoiceId) {
90
- // a trial's subscription still opens an invoice at once: "An immediate invoice is still created, but the amount is 0"
91
- // (docs.stripe.com/billing/subscriptions/trials/free-trials); finalizing it spends a once coupon (renewals.ts)
92
- const trialInvoice = await draftSubscriptionInvoice(ctx, ctx.get(SUB, subId) ?? sub, { start: periodStart, end: periodEnd, reason: 'subscription_create', trial: true, ...(trialInvoiceId ? { id: trialInvoiceId } : {}) });
93
- await collectSubscriptionInvoice(ctx, trialInvoice, now, 'api');
94
- return ctx.reply(expanded(ctx, SUB, ctx.get(SUB, subId) ?? sub));
95
- }
96
- // the default payment method pays the first invoice now: its charge (with its ledger entry) comes first
97
- const chargeId = pays && piId ? await mintCharge(ctx, piId, { currency, customer, payment_method: payWith, invoice: invoiceId }, invoiceTotal) : null;
98
- const invoiceLines = itemsData.map((it, i) => {
99
- const price = it.price && typeof it.price === 'object' ? it.price : undefined;
100
- const quantity = itemQuantity(it);
101
- return {
102
- id: `il_twin_${invoiceId}_${i + 1}`, object: 'line_item', type: 'subscription',
103
- amount: priceAmount(price, quantity), currency, quantity, proration: false,
104
- price: it.price ?? null, subscription: subId, invoice_item: null, subscription_item: it.id ?? null,
105
- period: { start: periodStart, end: periodEnd }, description: null,
106
- };
107
- });
108
- await created(ctx, 'invoice', {
109
- id: invoiceId,
110
- ...finalizedFields(ctx, { id: invoiceId, customer }, now),
111
- status: isFree || pays ? 'paid' : 'open', livemode: false, currency, collection_method: 'charge_automatically',
112
- auto_advance: !(isFree || pays), attempt_count: payWith && !isFree ? 1 : 0, attempted: !isFree && behavior !== 'default_incomplete',
113
- customer, subscription: subId, billing_reason: 'subscription_create', charge: chargeId,
114
- amount_due: invoiceTotal, amount_paid: isFree || pays ? invoiceTotal : 0, amount_remaining: isFree || pays ? 0 : invoiceTotal,
115
- amount_overpaid: 0, amount_paid_off_stripe: 0, amount_shipping: 0,
116
- subtotal, total: invoiceTotal, starting_balance: 0,
117
- post_payment_credit_notes_amount: 0, pre_payment_credit_notes_amount: 0,
118
- period_start: periodStart, period_end: periodEnd,
119
- default_tax_rates: [], discounts: discounts[0] ? [discounts[0]] : [],
120
- lines: { object: 'list', data: invoiceLines, has_more: false, total_count: invoiceLines.length, url: `/v1/invoices/${invoiceId}/lines` },
121
- automatic_tax: { enabled: false, liability: null, status: null },
122
- status_transitions: { finalized_at: now, marked_uncollectible_at: null, paid_at: isFree || pays ? now : null, voided_at: null },
123
- issuer: { type: 'self' },
124
- payment_settings: { default_mandate: null, payment_method_options: null, payment_method_types: null },
125
- payment_intent: piId,
126
- }, {});
127
- if (piId) {
128
- // paid by the default payment method, or waiting on one (declined, absent, or collected client-side)
129
- await created(ctx, 'payment_intent', { id: piId, amount: invoiceTotal, currency, customer, invoice: invoiceId, ...(payWith ? { payment_method: pays ? payWith : null } : {}) }, {
130
- status: pays ? 'succeeded' : 'requires_payment_method', client_secret: mintClientSecret(piId), livemode: false,
131
- capture_method: 'automatic', amount_capturable: 0, amount_received: pays ? invoiceTotal : 0, next_action: null,
132
- latest_charge: chargeId,
133
- last_payment_error: declined ? { type: 'card_error', code: declined.code, decline_code: declined.decline_code ?? null, message: declined.message, payment_method: { id: payWith } } : null,
134
- automatic_payment_methods: null, payment_method_types: ['card', 'link'], payment_method_options: {},
135
- });
136
- }
137
- // the first invoice is finalized: the `once` coupon it took is spent (renewals.ts spendOnceCoupon)
138
- await spendOnceCoupon(ctx, ctx.get('invoice', invoiceId) ?? {});
139
- return ctx.reply(expanded(ctx, SUB, ctx.get(SUB, subId) ?? sub));
140
- };
141
- const listSubscriptions = async (ctx) => {
1
+ import { keptAfter } from "../engine/renewals.js";
2
+ import { asBool, nowUnix, subscriptionCouponParam, subscriptionHasPrice, subscriptionItemEntries } from "../engine/stripe.js";
3
+ import { pauseCollection, SUB } from "../engine/subscriptions.js";
4
+ import { resourceView, advanceBilling, applyItemChanges, applyTrialEnd, at, clockNow, collectSubscriptionInvoice, expanded, itemChangesRefused, list, newest, redeemCoupon, replacedDiscounts, subMissing, trialEndBlocked, trialEndRefused, where } from "./shared.js";
5
+ import { deletedDiscount } from "../engine/common.js";
6
+ import { fail } from "./shared.js";
7
+ // canceling keeps the subscription, now canceled, stamped with when it was canceled and ended
8
+ // (https://docs.stripe.com/api/subscriptions/object#subscription_object-canceled_at, #subscription_object-ended_at)
9
+ export async function DeleteSubscriptionsSubscriptionExposedId(ctx) {
10
+ const id = at(ctx, 'subscription_exposed_id');
11
+ const sub = resourceView(ctx, SUB, id);
12
+ if (!sub)
13
+ return subMissing(ctx, id);
14
+ const refused = ctx.legal(SUB, 'status', 'DeleteSubscriptionsSubscriptionExposedId', sub.status, undefined, id);
15
+ if (refused)
16
+ return ctx.refuse(refused);
17
+ const now = nowUnix(ctx.occurredAt);
18
+ // why it was canceled: the request's `cancellation_details` ("Details about why this subscription was cancelled"), and
19
+ // the reason Stripe records for a cancel asked through the API, as the cancel page's example answers it:
20
+ // `"reason": "cancellation_requested"` (docs.stripe.com/api/subscriptions/cancel)
21
+ const given = ctx.params.cancellation_details && typeof ctx.params.cancellation_details === 'object' ? ctx.params.cancellation_details : {};
22
+ const cancellation_details = { comment: given.comment ?? null, feedback: given.feedback ?? null, reason: 'cancellation_requested' };
23
+ return ctx.reply(ctx.expand(SUB, await ctx.write(SUB, id, { status: 'canceled', canceled_at: now, ended_at: now, cancellation_details }, 'subscription.cancel')));
24
+ }
25
+ export async function GetSubscriptions(ctx) {
142
26
  await advanceBilling(ctx);
143
27
  return list(ctx, SUB, where(ctx, newest(ctx, SUB), {
144
28
  customer: (s, v) => s.customer === v,
145
29
  status: (s, v) => (v === 'all' ? true : s.status === v),
146
30
  price: (s, v) => subscriptionHasPrice(s, String(v)),
147
31
  }));
148
- };
149
- const searchSubscriptions = async (ctx) => {
150
- await advanceBilling(ctx);
151
- return search(ctx, SUB);
152
- };
153
- const retrieveSubscription = async (ctx) => {
32
+ }
33
+ export async function GetSubscriptionsSubscriptionExposedId(ctx) {
154
34
  const id = at(ctx, 'subscription_exposed_id');
155
35
  await advanceBilling(ctx, (sub) => sub.id === id);
156
- const sub = ctx.get(SUB, id);
36
+ const sub = resourceView(ctx, SUB, id);
157
37
  return sub ? ctx.reply(expanded(ctx, SUB, sub)) : subMissing(ctx, id);
158
- };
159
- // pause_collection pauses billing (the subscription stays active; the paused event fires) and
160
- // clearing it resumes; cancel_at_period_end schedules or withdraws a cancel; a coupon replaces the
161
- // discount and an empty one clears it
162
- /** pause_collection pauses billing (the paused event fires) and clearing it resumes
163
- * (docs.stripe.com/billing/subscriptions/pause-payment); answers the event, or nothing for a value it does not take. */
164
- function pauseCollection(pc, fields) {
165
- const isClear = pc === '' || pc === null || (typeof pc === 'object' && pc !== null && Object.keys(pc).length === 0);
166
- if (isClear) {
167
- fields.pause_collection = null;
168
- return 'customer.subscription.resumed';
169
- }
170
- if (!pc || typeof pc !== 'object')
171
- return undefined;
172
- const p = pc;
173
- fields.pause_collection = { behavior: typeof p.behavior === 'string' ? p.behavior : 'void', resumes_at: p.resumes_at !== undefined ? Number(p.resumes_at) : null };
174
- return 'customer.subscription.paused';
175
- }
176
- /** A coupon that replaces the subscription's discount, or an empty one that clears it (docs.stripe.com/api/subscriptions/update#update_subscription-discounts). */
177
- function replacedDiscounts(ctx, couponId, sub) {
178
- if (couponId === '')
179
- return [];
180
- const coupon = ctx.get('coupon', couponId);
181
- return coupon ? [buildDiscount(coupon, String(sub.customer ?? ''), Number(ctx.now()), { subscription: String(sub.id) })] : fail(ctx, `No such coupon: '${couponId}'`, 400, 'resource_missing');
182
- }
183
- /** The longest trial Stripe takes: `trial_end` "Can be at most two years from `billing_cycle_anchor`"
184
- * (docs.stripe.com/api/subscriptions/update#update_subscription-trial_end); "The trial period must be 730 days (2 years)
185
- * or less" (docs.stripe.com/billing/subscriptions/trials/free-trials). */
186
- const MAX_TRIAL = 730 * 86400;
187
- /** A `trial_end` Stripe refuses: the parameter is `"now" | timestamp` (docs.stripe.com/api/subscriptions/update), and a
188
- * timestamp that is not in the future answers "Invalid timestamp: must be an integer Unix timestamp in the future."
189
- * (Stripe's own message, as its SDKs' users report it: github.com/stripe/stripe-dotnet/issues/805,
190
- * github.com/stripe/stripe-ruby/issues/682). Where the documentation stops and the twin decides: a value that is no
191
- * number at all, and one more than two years on, answer that same message. */
192
- export function trialEndRefused(ctx, value, now) {
193
- const t = typeof value === 'number' ? value : typeof value === 'string' && /^\d+$/.test(value.trim()) ? Number(value) : NaN;
194
- return value === undefined || value === 'now' || (Number.isInteger(t) && t > now && t <= now + MAX_TRIAL) ? undefined : invalidTrialEnd(ctx);
195
- }
196
- function invalidTrialEnd(ctx) {
197
- return ctx.refuse({ status: 400, param: 'trial_end', message: 'Invalid timestamp: must be an integer Unix timestamp in the future.' });
198
- }
199
- /** An update's `trial_end` on the subscription, into `fields`.
200
- *
201
- * `now` on a trialing subscription ends its trial at once: "The special value `now` can be provided to end the customer's
202
- * trial immediately" (docs.stripe.com/api/subscriptions/update#update_subscription-trial_end; the trials guide's "To end a
203
- * trial early … setting the `trial_end` value to … **now** to end immediately", docs.stripe.com/billing/subscriptions/
204
- * trials/free-trials). The update resets the billing date and charges at once: "Switching prices does not normally change
205
- * the billing date or generate an immediate charge unless … A trial starts or ends. In these cases, we … immediately
206
- * charge the customer using the new price, and reset the billing date" (docs.stripe.com/api/subscriptions/update), and
207
- * "Stripe immediately attempts payment when a subscription's billing cycle anchor is reset … When billing is performed
208
- * immediately, but the required payment fails, the subscription change request succeeds and the subscription transitions
209
- * to `past_due`" (docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment). So a new period starts now,
210
- * its invoice is made (`invoice.created`) and charged to the subscription's default payment method, else the
211
- * customer's (paid: `invoice.paid`, the subscription `active`; declined or none: the invoice open, `past_due`), and the
212
- * subscription's own write sends `customer.subscription.updated`. `payment_behavior` (the same page): `error_if_incomplete`
213
- * answers 402 and leaves the subscription as it was, `default_incomplete` leaves the invoice open without an attempt.
214
- *
215
- * A future timestamp moves the trial's end, and the period and billing anchor with it: "The `billing_cycle_anchor` will be
216
- * updated to the `trial_end` value" (the same parameter); on a subscription out of its trial it starts a new one ("You can
217
- * add a new trial on a non-trialing subscription by updating the subscription while specifying trial_end", and for
218
- * classic billing mode "the `trial_start` field remains set to the start of the first trial", docs.stripe.com/billing/
219
- * subscriptions/trials/free-trials).
220
- *
221
- * Where the documentation stops and the twin decides: the invoice's billing_reason is `subscription_update` ("A
222
- * subscription was updated", docs.stripe.com/api/invoices/object#invoice_object-billing_reason); `now` on a subscription
223
- * that is not trialing has no trial to end and changes nothing; the credit Stripe prorates for the unused time when a new
224
- * trial starts mid-period is not made (as `proration_behavior=none` would have it); `pending_if_incomplete` answers as
225
- * `allow_incomplete` (no pending update is modelled). Answers a refusal, or the invoice to collect once the subscription is written. */
226
- async function applyTrialEnd(ctx, sub, fields, now) {
227
- const value = ctx.params.trial_end;
228
- delete fields.trial_end;
229
- const id = String(sub.id);
230
- if (value !== 'now' || sub.status !== 'trialing')
231
- return value === 'now' ? undefined : movedTrial(sub, fields, Number(value), now);
232
- // the period's invoice bills the items as this update leaves them, under its discount
233
- const next = { ...sub, ...fields, items: ctx.get(SUB, id)?.items ?? sub.items };
234
- const { interval, interval_count } = resolveSubscriptionBillingInterval((next.items?.data) ?? [], finder(ctx));
235
- const period = { start: now, end: addBillingInterval(now, interval, interval_count) };
236
- const invoice = await draftSubscriptionInvoice(ctx, next, { ...period, reason: 'subscription_update' });
237
- Object.assign(fields, { trial_end: now, billing_cycle_anchor: now, current_period_start: period.start, current_period_end: period.end, latest_invoice: invoice });
238
- const payer = trialPayer(ctx, sub);
239
- return { invoice, ...(payer ? { payer } : {}), attempt: ctx.params.payment_behavior !== 'default_incomplete' };
240
- }
241
- /** A trial moved to end at `end`, or a new one to `end` on a subscription out of its trial (applyTrialEnd). */
242
- function movedTrial(sub, fields, end, now) {
243
- Object.assign(fields, {
244
- status: 'trialing', trial_end: end, trial_start: sub.trial_start ?? now, billing_cycle_anchor: end,
245
- current_period_end: end, ...(sub.status !== 'trialing' ? { current_period_start: now } : {}),
246
- });
247
- return undefined;
248
- }
249
- /** What ending the trial charges: the update's own default payment method, else the subscription's, else its customer's. */
250
- function trialPayer(ctx, sub) {
251
- const given = ctx.params.default_payment_method;
252
- return typeof given === 'string' && given ? given : payerOf(ctx, sub);
253
38
  }
254
- /** What an update's `trial_end` refuses before anything is written: a new trial the machine does not allow from the
255
- * subscription's status, or, under `error_if_incomplete`, ending a trial that cannot be paid at once ("If payment fails,
256
- * return an HTTP `402` status code and don't update the subscription", docs.stripe.com/api/subscriptions/update). */
257
- function trialEndBlocked(ctx, sub, discounts) {
258
- if (ctx.params.trial_end !== 'now')
259
- return newTrialRefused(ctx, sub);
260
- if (sub.status !== 'trialing')
261
- return undefined;
262
- const charge = trialEndCharge(ctx, sub, discounts);
263
- if (ctx.params.payment_behavior === 'error_if_incomplete' && charge.due > 0 && !charge.pays)
264
- return firstPaymentRefused(ctx, charge.declined);
265
- // the move ending the trial makes (paid or nothing due: active; else past_due) is the machine's, asked before
266
- // anything is written
267
- const refused = ctx.legal(SUB, 'status', ctx.call.operation.id, 'trialing', charge.due <= 0 || charge.pays ? 'active' : 'past_due', String(sub.id));
268
- return refused ? ctx.refuse(refused) : undefined;
269
- }
270
- /** A price named by id, or given as an object. */
271
- function priceRow(ctx, price) {
272
- return typeof price === 'string' ? ctx.get('price', price) : price && typeof price === 'object' ? price : undefined;
273
- }
274
- /** One period of the subscription's items as an update's `items` would leave them, before any discount: an entry naming
275
- * an item changes its price and quantity (a new price resets the quantity to 1 unless one is given) or deletes it, and
276
- * one naming none adds an item (applyItemChanges). */
277
- function dueAfter(ctx, sub, entries) {
278
- const lines = new Map();
279
- for (const it of (sub.items?.data) ?? [])
280
- lines.set(String(it.id), { price: priceRow(ctx, it.price), quantity: itemQuantity(it) });
281
- entries.forEach((e, i) => entryAfter(ctx, lines, e, i));
282
- return [...lines.values()].reduce((sum, l) => sum + priceAmount(l.price, l.quantity), 0);
283
- }
284
- /** One entry of an update's `items` on the lines it would leave (dueAfter). */
285
- function entryAfter(ctx, lines, e, i) {
286
- // an inline price bills as the Price inlinePrice makes of it
287
- const d = e.price_data && typeof e.price_data === 'object' ? e.price_data : undefined;
288
- const inline = d ? (d.unit_amount_decimal !== undefined ? { unit_amount_decimal: String(d.unit_amount_decimal) } : { unit_amount: Math.trunc(Number(d.unit_amount) || 0) }) : undefined;
289
- const price = typeof e.price === 'string' && e.price ? priceRow(ctx, e.price) : inline;
290
- const named = typeof e.id === 'string' && e.id;
291
- const key = named ? String(e.id) : `new_${i}`;
292
- // only a named item is deleted; an entry naming none adds one, as applyItemChanges does
293
- if (named && asBool(e.deleted)) {
294
- lines.delete(key);
295
- return;
296
- }
297
- const was = lines.get(key);
298
- const quantity = e.quantity !== undefined ? Math.max(0, Math.trunc(Number(e.quantity) || 0)) : price && was && price.id !== was.price?.id ? 1 : was?.quantity ?? 1;
299
- lines.set(key, { price: price ?? was?.price, quantity });
300
- }
301
- /** A new trial on a subscription whose status the machine does not let start one. */
302
- function newTrialRefused(ctx, sub) {
303
- const refused = sub.status === 'trialing' ? undefined : ctx.legal(SUB, 'status', ctx.call.operation.id, sub.status, 'trialing', String(sub.id));
304
- return refused ? ctx.refuse(refused) : undefined;
305
- }
306
- /** What ending the trial charges, judged before anything is written: the period of the items as this update leaves them
307
- * (dueAfter) under the discount it leaves, and whether the payer pays it (an attempt made, a payer, no decline). */
308
- function trialEndCharge(ctx, sub, discounts) {
309
- const subtotal = dueAfter(ctx, sub, subscriptionItemEntries(ctx.params.items));
310
- // the discount the subscription carries after this update takes this invoice (renewals.ts draftSubscriptionInvoice)
311
- const discount = (discounts ?? sub.discounts ?? [])[0];
312
- const coupon = discount && typeof discount === 'object' ? discount.coupon : undefined;
313
- const due = subtotal - (coupon ? applyCouponDiscount(subtotal, coupon) : 0);
314
- const payer = trialPayer(ctx, sub);
315
- const attempt = ctx.params.payment_behavior !== 'default_incomplete';
316
- const declined = payer && attempt ? declineFor(finder(ctx), { payment_method: payer }) : undefined;
317
- return { due, pays: attempt && !!payer && !declined, ...(declined ? { declined } : {}) };
318
- }
319
- const update = async (ctx) => {
39
+ export async function PostSubscriptionsSubscriptionExposedId(ctx) {
320
40
  const id = at(ctx, 'subscription_exposed_id');
321
41
  // a period that has already ended renews (or a trial that has, ends) before the update applies (renewals.ts)
322
42
  await advanceBilling(ctx, (s) => s.id === id);
323
- const sub = ctx.get(SUB, id);
43
+ const sub = resourceView(ctx, SUB, id);
324
44
  if (!sub)
325
45
  return subMissing(ctx, id);
326
46
  const params = ctx.params;
@@ -362,7 +82,7 @@ const update = async (ctx) => {
362
82
  // scheduled cancel follows a period this update moves
363
83
  if ('cancel_at_period_end' in params) {
364
84
  fields.cancel_at_period_end = asBool(params.cancel_at_period_end);
365
- Object.assign(fields, fields.cancel_at_period_end ? { cancel_at: fields.current_period_end ?? sub.current_period_end ?? null, canceled_at: Number(ctx.now()) } : { cancel_at: null, canceled_at: null });
85
+ Object.assign(fields, fields.cancel_at_period_end ? { cancel_at: fields.current_period_end ?? sub.current_period_end ?? null, canceled_at: Number(nowUnix(ctx.occurredAt)) } : { cancel_at: null, canceled_at: null });
366
86
  }
367
87
  else if (sub.cancel_at_period_end === true && fields.current_period_end !== undefined)
368
88
  fields.cancel_at = fields.current_period_end;
@@ -375,231 +95,18 @@ const update = async (ctx) => {
375
95
  if (fields.discounts !== undefined)
376
96
  fields.discounts = keptAfter(fields.discounts, ctx.get('invoice', trial.invoice) ?? {});
377
97
  }
378
- return ctx.reply(await ctx.write(SUB, id, fields, op));
379
- };
380
- /** A new item named with neither `price` nor `price_data`: "One of `price` or `price_data` is required"
381
- * (docs.stripe.com/api/subscriptions/update). */
382
- function noItemPrice(ctx) {
383
- return fail(ctx, 'Missing required param: items[price].', 400, 'parameter_missing');
384
- }
385
- /** A subscription asked to fail when its first invoice cannot be paid at once (payment_behavior=error_if_incomplete):
386
- * the card's decline, or no payment method to charge (docs.stripe.com/api/subscriptions/create#create_subscription-payment_behavior). */
387
- function firstPaymentRefused(ctx, declined) {
388
- return declined ? send(ctx, cardError(declined)) : fail(ctx, 'This customer has no attached payment source or default payment method.', 400, 'resource_missing');
389
- }
390
- /** An update's `items`: "A list of up to 20 subscription items, each with an attached price"
391
- * (docs.stripe.com/api/subscriptions/update): an entry naming an item's `id` changes that item (its price, quantity,
392
- * metadata) or, with `deleted` ("A flag that, if set to `true`, will delete the specified item"), removes it; "If you
393
- * omit `id`, the API adds a new subscription item rather than updating the existing one". Each takes `price` or
394
- * `price_data`, "Data used to generate a new Price object inline. One of `price` or `price_data` is required", and
395
- * "When changing a subscription item's price, `quantity` is set to 1 unless a `quantity` parameter is provided". An
396
- * inline price is made as the manage-prices guide says: "By default, prices created with `price_data` are effectively
397
- * archived (they're marked as `active=false`)" (docs.stripe.com/products-prices/manage-prices).
398
- * Where the documentation stops and the twin decides: the update is all or nothing, every entry checked before any is
399
- * written (Stripe answers one error for the request); the refusals are worded by the twin. */
400
- async function applyItemChanges(ctx, subId, entries) {
401
- // every entry is checked first: a refusal leaves the subscription as it was
402
- const refused = itemChangesRefused(ctx, subId, entries);
403
- if (refused)
404
- return refused;
405
- return writeItemChanges(ctx, subId, entries);
406
- }
407
- /** The refusal of an update's `items`, checked entry by entry before any is written (applyItemChanges). */
408
- function itemChangesRefused(ctx, subId, entries) {
409
- for (const entry of entries) {
410
- const priceRef = typeof entry.price === 'string' && entry.price ? entry.price : undefined;
411
- if (priceRef && !ctx.get('price', priceRef))
412
- return fail(ctx, `No such price: '${priceRef}'`, 400, 'resource_missing');
413
- const inline = !priceRef && entry.price_data && typeof entry.price_data === 'object' ? entry.price_data : undefined;
414
- if (inline) {
415
- const refused = refuseInlinePrice(ctx, inline);
416
- if (refused)
417
- return refused;
418
- }
419
- if (typeof entry.id === 'string' && entry.id) {
420
- const si = ctx.get('subscription_item', entry.id);
421
- if (!si || si.subscription !== subId)
422
- return fail(ctx, `No such subscription_item: '${entry.id}'`, 400, 'resource_missing');
423
- }
424
- else if (!priceRef && !inline)
425
- return noItemPrice(ctx);
426
- }
427
- return undefined;
428
- }
429
- /** An update's checked `items`, written. */
430
- async function writeItemChanges(ctx, subId, entries) {
431
- for (const entry of entries) {
432
- const inline = typeof entry.price === 'string' && entry.price ? undefined : entry.price_data && typeof entry.price_data === 'object' ? entry.price_data : undefined;
433
- const priceRef = typeof entry.price === 'string' && entry.price ? entry.price : inline ? await inlinePrice(ctx, inline) : undefined;
434
- // only a price that changes resets the quantity; one naming the item's own price leaves it
435
- const was = typeof entry.id === 'string' && entry.id ? ctx.get('subscription_item', entry.id)?.price : undefined;
436
- const wasId = typeof was === 'string' ? was : was && typeof was === 'object' ? String(was.id ?? '') : undefined;
437
- const quantity = entry.quantity !== undefined ? { quantity: Math.max(0, Math.trunc(Number(entry.quantity) || 0)) } : priceRef && priceRef !== wasId ? { quantity: 1 } : {};
438
- const metadata = entry.metadata && typeof entry.metadata === 'object' ? { metadata: entry.metadata } : {};
439
- if (typeof entry.id === 'string' && entry.id) {
440
- if (asBool(entry.deleted)) {
441
- await ctx.write('subscription_item', entry.id, { deleted: true }, 'subscription_item.delete');
442
- continue;
443
- }
444
- await ctx.write('subscription_item', entry.id, { ...(priceRef ? subscriptionItemPrice(priceRef, finder(ctx)) : {}), ...quantity, ...metadata }, 'subscription_item.update');
445
- }
446
- else {
447
- await created(ctx, 'subscription_item', { subscription: subId, ...subscriptionItemPrice(priceRef, finder(ctx)), quantity: 1, ...quantity, ...metadata }, { livemode: false, metadata: {}, discounts: [], tax_rates: [], billing_thresholds: null });
448
- }
449
- }
450
- await syncItems(ctx, subId);
451
- return undefined;
452
- }
453
- /** A subscription item's `price_data` Stripe would refuse: its required `currency`, `product` (an existing one) and
454
- * `recurring[interval]`, and "Only one of `unit_amount` and `unit_amount_decimal` can be set". */
455
- function refuseInlinePrice(ctx, d) {
456
- for (const name of ['currency', 'product'])
457
- if (typeof d[name] !== 'string' || !d[name])
458
- return fail(ctx, `Missing required param: items[price_data][${name}].`, 400, 'parameter_missing');
459
- if (!ctx.get('product', String(d.product)))
460
- return fail(ctx, `No such product: '${String(d.product)}'`, 400, 'resource_missing');
461
- const interval = d.recurring?.interval;
462
- if (!['day', 'week', 'month', 'year'].includes(String(interval)))
463
- return fail(ctx, 'Missing required param: items[price_data][recurring][interval].', 400, 'parameter_missing');
464
- if (d.unit_amount !== undefined && d.unit_amount_decimal !== undefined)
465
- return fail(ctx, 'Only one of `unit_amount` and `unit_amount_decimal` can be set.', 400, 'parameter_invalid');
466
- return undefined;
467
- }
468
- /** The inline Price a `price_data` generates: recurring, archived (active=false). Answers its id. */
469
- async function inlinePrice(ctx, d) {
470
- const r = d.recurring;
471
- const decimal = d.unit_amount_decimal !== undefined ? String(d.unit_amount_decimal) : String(Math.trunc(Number(d.unit_amount) || 0));
472
- const price = await created(ctx, 'price', {
473
- product: d.product, currency: String(d.currency).toLowerCase(),
474
- // a decimal amount that is not a whole number of the smallest unit has no integer unit_amount (as semantics/plans.ts)
475
- unit_amount: d.unit_amount_decimal !== undefined ? (Number.isInteger(Number(d.unit_amount_decimal)) ? Number(d.unit_amount_decimal) : null) : Math.trunc(Number(d.unit_amount) || 0), unit_amount_decimal: decimal,
476
- recurring: { interval: r.interval, interval_count: Math.max(1, Math.trunc(Number(r.interval_count ?? 1)) || 1), usage_type: 'licensed', trial_period_days: null, meter: null },
477
- tax_behavior: typeof d.tax_behavior === 'string' ? d.tax_behavior : 'unspecified',
478
- }, { active: false, livemode: false, billing_scheme: 'per_unit', type: 'recurring', metadata: {}, lookup_key: null, nickname: null });
479
- return String(price.id);
98
+ return ctx.reply(ctx.expand(SUB, await ctx.write(SUB, id, fields, op)));
480
99
  }
481
- const deleteDiscount = async (ctx) => {
100
+ export async function DeleteSubscriptionsSubscriptionExposedIdDiscount(ctx) {
482
101
  const id = at(ctx, 'subscription_exposed_id');
483
- const sub = ctx.get(SUB, id);
102
+ const sub = resourceView(ctx, SUB, id);
484
103
  if (!sub)
485
104
  return subMissing(ctx, id);
105
+ // source: spec:DeleteSubscriptionsSubscriptionExposedIdDiscount "Removes the currently applied discount on a subscription."
486
106
  const current = (sub.discounts ?? [])[0] ?? sub.discount;
107
+ // Where the documentation stops and the twin decides: a subscription with no discount answers its absence as a missing resource.
487
108
  if (!current)
488
109
  return fail(ctx, `No active discount for subscription: '${id}'`, 404, 'resource_missing');
489
110
  await ctx.write(SUB, id, { discounts: [] }, 'subscription.update');
490
- return ctx.reply(deletedDiscount(typeof current === 'string' ? { id: current } : current, ctx.now()));
491
- };
492
- // canceling keeps the subscription, now canceled, stamped with when it was canceled and ended
493
- // (https://docs.stripe.com/api/subscriptions/object#subscription_object-canceled_at, #subscription_object-ended_at)
494
- const cancel = async (ctx) => {
495
- const id = at(ctx, 'subscription_exposed_id');
496
- const sub = ctx.get(SUB, id);
497
- if (!sub)
498
- return subMissing(ctx, id);
499
- const refused = ctx.legal(SUB, 'status', 'DeleteSubscriptionsSubscriptionExposedId', sub.status, undefined, id);
500
- if (refused)
501
- return ctx.refuse(refused);
502
- const now = ctx.now();
503
- // why it was canceled: the request's `cancellation_details` ("Details about why this subscription was cancelled"), and
504
- // the reason Stripe records for a cancel asked through the API, as the cancel page's example answers it:
505
- // `"reason": "cancellation_requested"` (docs.stripe.com/api/subscriptions/cancel)
506
- const given = ctx.params.cancellation_details && typeof ctx.params.cancellation_details === 'object' ? ctx.params.cancellation_details : {};
507
- const cancellation_details = { comment: given.comment ?? null, feedback: given.feedback ?? null, reason: 'cancellation_requested' };
508
- return ctx.reply(await ctx.write(SUB, id, { status: 'canceled', canceled_at: now, ended_at: now, cancellation_details }, 'subscription.cancel'));
509
- };
510
- // ── items: each write keeps the parent subscription's items.data in step, rebuilt from the live items ──
511
- /** A new subscription's items, each its own subscription_item as Stripe keeps it (/v1/subscription_items/{id}). */
512
- export async function storeItems(ctx, subId, items) {
513
- for (const item of items) {
514
- if (typeof item.id !== 'string' || ctx.get('subscription_item', item.id))
515
- continue;
516
- await ctx.write('subscription_item', item.id, { ...item, subscription: subId }, 'subscription_item.create');
517
- }
111
+ return ctx.reply(deletedDiscount(typeof current === 'string' ? { id: current } : { ...current, subscription: id }, nowUnix(ctx.occurredAt)));
518
112
  }
519
- async function syncItems(ctx, subId) {
520
- if (!subId)
521
- return;
522
- const items = ctx.rows('subscription_item').filter((r) => r.subscription === subId);
523
- await ctx.write(SUB, subId, { items: { object: 'list', data: items, has_more: false, total_count: items.length, url: `/v1/subscription_items?subscription=${subId}` } }, 'subscription.items_synced');
524
- }
525
- /** A subscription item with its billing period: its subscription's (basil moved the period onto the items). */
526
- const withPeriod = (ctx, si) => {
527
- const sub = ctx.get(SUB, String(si.subscription));
528
- return { ...si, current_period_start: si.current_period_start ?? sub?.current_period_start ?? null, current_period_end: si.current_period_end ?? sub?.current_period_end ?? null };
529
- };
530
- const createItem = async (ctx) => {
531
- const params = ctx.params;
532
- const subId = typeof params.subscription === 'string' ? params.subscription : '';
533
- if (!subId)
534
- return fail(ctx, 'Missing required param: subscription.', 400, 'parameter_missing');
535
- if (!ctx.get(SUB, subId))
536
- return subMissing(ctx, subId);
537
- const priceRef = typeof params.price === 'string' ? params.price : '';
538
- if (!priceRef && !(params.price_data && typeof params.price_data === 'object'))
539
- return fail(ctx, 'Missing required param: price.', 400, 'parameter_missing');
540
- if (priceRef && !ctx.get('price', priceRef))
541
- return fail(ctx, `No such price: '${priceRef}'`, 400, 'resource_missing');
542
- const quantity = params.quantity !== undefined ? Math.max(0, Math.trunc(Number(params.quantity) || 0)) : 1;
543
- // "metadata: Set of key-value pairs that you can attach to an object" (docs.stripe.com/api/subscription_items/create)
544
- const metadata = params.metadata && typeof params.metadata === 'object' ? { metadata: params.metadata } : {};
545
- const body = await created(ctx, 'subscription_item', { subscription: subId, ...(priceRef ? subscriptionItemPrice(priceRef, finder(ctx)) : { price: null, plan: null }), quantity, ...metadata }, { livemode: false, metadata: {} });
546
- await syncItems(ctx, subId);
547
- return ctx.reply(withPeriod(ctx, body));
548
- };
549
- const listItems = async (ctx) => {
550
- const subId = typeof ctx.params.subscription === 'string' ? ctx.params.subscription : '';
551
- if (!subId)
552
- return fail(ctx, 'Missing required param: subscription.', 400, 'parameter_missing');
553
- if (!ctx.get(SUB, subId))
554
- return subMissing(ctx, subId);
555
- return list(ctx, 'subscription_item', newest(ctx, 'subscription_item').filter((si) => si.subscription === subId).map((si) => withPeriod(ctx, si)));
556
- };
557
- const ITEM_ATTRIBUTES = new Set(['billing_thresholds', 'discounts', 'metadata', 'price', 'quantity', 'tax_rates']);
558
- const itemMissing = (ctx, id) => fail(ctx, `No such subscription_item: '${id}'`, 404, 'resource_missing');
559
- const updateItem = async (ctx) => {
560
- const id = at(ctx, 'item');
561
- const si = ctx.get('subscription_item', id);
562
- if (!si)
563
- return itemMissing(ctx, id);
564
- // only the item's own attributes are kept; the update's instructions (off_session, payment_behavior, price_data,
565
- // proration_behavior, proration_date) are not attributes of the Subscription Item object
566
- // (docs.stripe.com/api/subscription_items/object lists none of them)
567
- const fields = Object.fromEntries(Object.entries(ctx.params).filter(([k]) => ITEM_ATTRIBUTES.has(k)));
568
- if (ctx.params.quantity !== undefined)
569
- fields.quantity = Math.max(0, Math.trunc(Number(ctx.params.quantity) || 0));
570
- if (typeof ctx.params.price === 'string' && ctx.params.price && !ctx.get('price', ctx.params.price))
571
- return fail(ctx, `No such price: '${ctx.params.price}'`, 400, 'resource_missing');
572
- // the item carries its Price and plan, as the subscription's own items do (subscriptionItemPrice)
573
- if (typeof ctx.params.price === 'string' && ctx.params.price)
574
- Object.assign(fields, subscriptionItemPrice(ctx.params.price, finder(ctx)));
575
- const out = await ctx.write('subscription_item', id, fields, 'subscription_item.update');
576
- await syncItems(ctx, String(si.subscription));
577
- return ctx.reply(withPeriod(ctx, out));
578
- };
579
- const retrieveItem = async (ctx) => {
580
- const si = ctx.get('subscription_item', at(ctx, 'item'));
581
- return si ? ctx.reply(withPeriod(ctx, si)) : itemMissing(ctx, at(ctx, 'item'));
582
- };
583
- const deleteItem = async (ctx) => {
584
- const id = at(ctx, 'item');
585
- const si = ctx.get('subscription_item', id);
586
- if (!si)
587
- return itemMissing(ctx, id);
588
- await ctx.write('subscription_item', id, { deleted: true }, 'subscription_item.delete');
589
- await syncItems(ctx, String(si.subscription));
590
- return ctx.reply({ id, object: 'subscription_item', deleted: true });
591
- };
592
- export const subscriptions = {
593
- PostSubscriptions: create,
594
- GetSubscriptions: listSubscriptions,
595
- GetSubscriptionsSearch: searchSubscriptions,
596
- GetSubscriptionsSubscriptionExposedId: retrieveSubscription,
597
- PostSubscriptionsSubscriptionExposedId: update,
598
- DeleteSubscriptionsSubscriptionExposedIdDiscount: deleteDiscount,
599
- DeleteSubscriptionsSubscriptionExposedId: cancel,
600
- PostSubscriptionItems: createItem,
601
- GetSubscriptionItems: listItems,
602
- PostSubscriptionItemsItem: updateItem,
603
- GetSubscriptionItemsItem: retrieveItem,
604
- DeleteSubscriptionItemsItem: deleteItem,
605
- };
@@ -0,0 +1,3 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ export declare function PostTestHelpersIssuingAuthorizations(ctx: HandlerContext): Promise<Response>;
3
+ export declare function PostTestHelpersIssuingAuthorizationsAuthorizationCapture(ctx: HandlerContext): Promise<Response>;