@volter/twin-stripe 2.0.37 → 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,34 +1,51 @@
1
- // Subscription and SubscriptionItem semantics. A subscription is created with its items, its
2
- // billing period and, under payment_behavior=default_incomplete, its first invoice and that
3
- // invoice's PaymentIntent; canceling it is the machine's move in ../manifest.ts. Retrieve of a
4
- // subscription and of an item are the derived core's.
5
- import type { Semantics, SemanticsContext } from '@volter/world-core';
6
- import {
7
- addBillingInterval,
8
- applyCouponDiscount,
9
- asBool,
10
- buildDiscount,
11
- buildSubscriptionItemsList,
12
- cardError,
13
- declineFor,
14
- mintClientSecret,
15
- priceAmount,
16
- resolveSubscriptionBillingInterval,
17
- resolveTrial,
18
- subscriptionCouponParam,
19
- subscriptionHasPrice,
20
- subscriptionItemEntries,
21
- subscriptionItemPrice,
22
- } from '../stripe-twin.ts';
23
- import { finalizedFields } from './invoices.ts';
24
- import { mintCharge } from './payment-intents.ts';
25
- import { advanceBilling, clockNow, collectSubscriptionInvoice, draftSubscriptionInvoice, itemQuantity, keptAfter, payerOf, spendOnceCoupon } from './renewals.ts';
26
- import { at, created, deletedDiscount, expanded, fail, finder, list, newest, redeemCoupon, search, send, where, type Row } from './shared.ts';
1
+ // Stripe's subscriptions 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 { deletedDiscount, type Row } from '../engine/common.ts';
5
+ import { itemQuantity, keptAfter } from '../engine/renewals.ts';
6
+ import { addBillingInterval, applyCouponDiscount, asBool, buildDiscount, buildSubscriptionItemsList, declineFor, mintClientSecret, nowUnix, priceAmount, resolveSubscriptionBillingInterval, resolveTrial, subscriptionCouponParam, subscriptionHasPrice, subscriptionItemEntries } from '../engine/stripe.ts';
7
+ import { pauseCollection, SUB } from '../engine/subscriptions.ts';
8
+ import { advanceBilling, applyItemChanges, applyTrialEnd, at, clockNow, collectSubscriptionInvoice, created, draftSubscriptionInvoice, expanded, fail, finalizedFields, finder, firstPaymentRefused, itemChangesRefused, list, mintCharge, newest, redeemCoupon, replacedDiscounts, search, spendOnceCoupon, storeItems, subMissing, trialEndBlocked, trialEndRefused, where } from './shared.ts';
27
9
 
28
- const SUB = 'subscription';
29
- const subMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such subscription: '${id}'`, 404, 'resource_missing');
10
+ // canceling keeps the subscription, now canceled, stamped with when it was canceled and ended
11
+ // (https://docs.stripe.com/api/subscriptions/object#subscription_object-canceled_at, #subscription_object-ended_at)
12
+ export async function DeleteSubscriptionsSubscriptionExposedId(ctx: HandlerContext): Promise<Response> {
13
+ const id = at(ctx, 'subscription_exposed_id');
14
+ const sub = ctx.get(SUB, id);
15
+ if (!sub) return subMissing(ctx, id);
16
+ const refused = ctx.legal(SUB, 'status', 'DeleteSubscriptionsSubscriptionExposedId', sub.status, undefined, id);
17
+ if (refused) return ctx.refuse(refused);
18
+ const now = nowUnix(ctx.occurredAt);
19
+ // why it was canceled: the request's `cancellation_details` ("Details about why this subscription was cancelled"), and
20
+ // the reason Stripe records for a cancel asked through the API, as the cancel page's example answers it:
21
+ // `"reason": "cancellation_requested"` (docs.stripe.com/api/subscriptions/cancel)
22
+ const given = ctx.params.cancellation_details && typeof ctx.params.cancellation_details === 'object' ? (ctx.params.cancellation_details as Row) : {};
23
+ const cancellation_details = { comment: given.comment ?? null, feedback: given.feedback ?? null, reason: 'cancellation_requested' };
24
+ return ctx.reply(await ctx.write(SUB, id, { status: 'canceled', canceled_at: now, ended_at: now, cancellation_details }, 'subscription.cancel'));
25
+ }
26
+
27
+ export async function GetSubscriptions(ctx: HandlerContext): Promise<Response> {
28
+ await advanceBilling(ctx);
29
+ return list(ctx, SUB, where(ctx, newest(ctx, SUB), {
30
+ customer: (s, v) => s.customer === v,
31
+ status: (s, v) => (v === 'all' ? true : s.status === v),
32
+ price: (s, v) => subscriptionHasPrice(s, String(v)),
33
+ }));
34
+ }
35
+
36
+ export async function GetSubscriptionsSearch(ctx: HandlerContext): Promise<Response> {
37
+ await advanceBilling(ctx);
38
+ return search(ctx, SUB);
39
+ }
40
+
41
+ export async function GetSubscriptionsSubscriptionExposedId(ctx: HandlerContext): Promise<Response> {
42
+ const id = at(ctx, 'subscription_exposed_id');
43
+ await advanceBilling(ctx, (sub) => sub.id === id);
44
+ const sub = ctx.get(SUB, id);
45
+ return sub ? ctx.reply(expanded(ctx, SUB, sub)) : subMissing(ctx, id);
46
+ }
30
47
 
31
- const create: Semantics = async (ctx) => {
48
+ export async function PostSubscriptions(ctx: HandlerContext): Promise<Response> {
32
49
  const params = ctx.params;
33
50
  const customer = typeof params.customer === 'string' ? params.customer : '';
34
51
  if (!customer) return fail(ctx, 'Missing required param: customer.', 400, 'parameter_missing');
@@ -146,7 +163,7 @@ const create: Semantics = async (ctx) => {
146
163
  if (piId) {
147
164
  // paid by the default payment method, or waiting on one (declined, absent, or collected client-side)
148
165
  await created(ctx, 'payment_intent', { id: piId, amount: invoiceTotal, currency, customer, invoice: invoiceId, ...(payWith ? { payment_method: pays ? payWith : null } : {}) }, {
149
- status: pays ? 'succeeded' : 'requires_payment_method', client_secret: mintClientSecret(piId), livemode: false,
166
+ status: pays ? 'succeeded' : 'requires_payment_method', client_secret: mintClientSecret(piId, await ctx.secret(`client-secret:${piId}`)), livemode: false,
150
167
  capture_method: 'automatic', amount_capturable: 0, amount_received: pays ? invoiceTotal : 0, next_action: null,
151
168
  latest_charge: chargeId,
152
169
  last_payment_error: declined ? { type: 'card_error', code: declined.code, decline_code: declined.decline_code ?? null, message: declined.message, payment_method: { id: payWith } } : null,
@@ -156,194 +173,9 @@ const create: Semantics = async (ctx) => {
156
173
  // the first invoice is finalized: the `once` coupon it took is spent (renewals.ts spendOnceCoupon)
157
174
  await spendOnceCoupon(ctx, ctx.get('invoice', invoiceId) ?? {});
158
175
  return ctx.reply(expanded(ctx, SUB, ctx.get(SUB, subId) ?? sub));
159
- };
160
-
161
- const listSubscriptions: Semantics = async (ctx) => {
162
- await advanceBilling(ctx);
163
- return list(ctx, SUB, where(ctx, newest(ctx, SUB), {
164
- customer: (s, v) => s.customer === v,
165
- status: (s, v) => (v === 'all' ? true : s.status === v),
166
- price: (s, v) => subscriptionHasPrice(s, String(v)),
167
- }));
168
- };
169
-
170
- const searchSubscriptions: Semantics = async (ctx) => {
171
- await advanceBilling(ctx);
172
- return search(ctx, SUB);
173
- };
174
-
175
- const retrieveSubscription: Semantics = async (ctx) => {
176
- const id = at(ctx, 'subscription_exposed_id');
177
- await advanceBilling(ctx, (sub) => sub.id === id);
178
- const sub = ctx.get(SUB, id);
179
- return sub ? ctx.reply(expanded(ctx, SUB, sub)) : subMissing(ctx, id);
180
- };
181
-
182
- // pause_collection pauses billing (the subscription stays active; the paused event fires) and
183
- // clearing it resumes; cancel_at_period_end schedules or withdraws a cancel; a coupon replaces the
184
- // discount and an empty one clears it
185
- /** pause_collection pauses billing (the paused event fires) and clearing it resumes
186
- * (docs.stripe.com/billing/subscriptions/pause-payment); answers the event, or nothing for a value it does not take. */
187
- function pauseCollection(pc: unknown, fields: Row): string | undefined {
188
- const isClear = pc === '' || pc === null || (typeof pc === 'object' && pc !== null && Object.keys(pc as object).length === 0);
189
- if (isClear) {
190
- fields.pause_collection = null;
191
- return 'customer.subscription.resumed';
192
- }
193
- if (!pc || typeof pc !== 'object') return undefined;
194
- const p = pc as Row;
195
- fields.pause_collection = { behavior: typeof p.behavior === 'string' ? p.behavior : 'void', resumes_at: p.resumes_at !== undefined ? Number(p.resumes_at) : null };
196
- return 'customer.subscription.paused';
197
- }
198
-
199
- /** A coupon that replaces the subscription's discount, or an empty one that clears it (docs.stripe.com/api/subscriptions/update#update_subscription-discounts). */
200
- function replacedDiscounts(ctx: SemanticsContext, couponId: string, sub: Row): Row[] | Response {
201
- if (couponId === '') return [];
202
- const coupon = ctx.get('coupon', couponId);
203
- return coupon ? [buildDiscount(coupon, String(sub.customer ?? ''), Number(ctx.now()), { subscription: String(sub.id) })] : fail(ctx, `No such coupon: '${couponId}'`, 400, 'resource_missing');
204
- }
205
-
206
- /** The longest trial Stripe takes: `trial_end` "Can be at most two years from `billing_cycle_anchor`"
207
- * (docs.stripe.com/api/subscriptions/update#update_subscription-trial_end); "The trial period must be 730 days (2 years)
208
- * or less" (docs.stripe.com/billing/subscriptions/trials/free-trials). */
209
- const MAX_TRIAL = 730 * 86400;
210
-
211
- /** A `trial_end` Stripe refuses: the parameter is `"now" | timestamp` (docs.stripe.com/api/subscriptions/update), and a
212
- * timestamp that is not in the future answers "Invalid timestamp: must be an integer Unix timestamp in the future."
213
- * (Stripe's own message, as its SDKs' users report it: github.com/stripe/stripe-dotnet/issues/805,
214
- * github.com/stripe/stripe-ruby/issues/682). Where the documentation stops and the twin decides: a value that is no
215
- * number at all, and one more than two years on, answer that same message. */
216
- export function trialEndRefused(ctx: SemanticsContext, value: unknown, now: number): Response | undefined {
217
- const t = typeof value === 'number' ? value : typeof value === 'string' && /^\d+$/.test(value.trim()) ? Number(value) : NaN;
218
- return value === undefined || value === 'now' || (Number.isInteger(t) && t > now && t <= now + MAX_TRIAL) ? undefined : invalidTrialEnd(ctx);
219
- }
220
-
221
- function invalidTrialEnd(ctx: SemanticsContext): Response {
222
- return ctx.refuse({ status: 400, param: 'trial_end', message: 'Invalid timestamp: must be an integer Unix timestamp in the future.' });
223
- }
224
-
225
- /** An update's `trial_end` on the subscription, into `fields`.
226
- *
227
- * `now` on a trialing subscription ends its trial at once: "The special value `now` can be provided to end the customer's
228
- * trial immediately" (docs.stripe.com/api/subscriptions/update#update_subscription-trial_end; the trials guide's "To end a
229
- * trial early … setting the `trial_end` value to … **now** to end immediately", docs.stripe.com/billing/subscriptions/
230
- * trials/free-trials). The update resets the billing date and charges at once: "Switching prices does not normally change
231
- * the billing date or generate an immediate charge unless … A trial starts or ends. In these cases, we … immediately
232
- * charge the customer using the new price, and reset the billing date" (docs.stripe.com/api/subscriptions/update), and
233
- * "Stripe immediately attempts payment when a subscription's billing cycle anchor is reset … When billing is performed
234
- * immediately, but the required payment fails, the subscription change request succeeds and the subscription transitions
235
- * to `past_due`" (docs.stripe.com/billing/subscriptions/upgrade-downgrade#immediate-payment). So a new period starts now,
236
- * its invoice is made (`invoice.created`) and charged to the subscription's default payment method, else the
237
- * customer's (paid: `invoice.paid`, the subscription `active`; declined or none: the invoice open, `past_due`), and the
238
- * subscription's own write sends `customer.subscription.updated`. `payment_behavior` (the same page): `error_if_incomplete`
239
- * answers 402 and leaves the subscription as it was, `default_incomplete` leaves the invoice open without an attempt.
240
- *
241
- * A future timestamp moves the trial's end, and the period and billing anchor with it: "The `billing_cycle_anchor` will be
242
- * updated to the `trial_end` value" (the same parameter); on a subscription out of its trial it starts a new one ("You can
243
- * add a new trial on a non-trialing subscription by updating the subscription while specifying trial_end", and for
244
- * classic billing mode "the `trial_start` field remains set to the start of the first trial", docs.stripe.com/billing/
245
- * subscriptions/trials/free-trials).
246
- *
247
- * Where the documentation stops and the twin decides: the invoice's billing_reason is `subscription_update` ("A
248
- * subscription was updated", docs.stripe.com/api/invoices/object#invoice_object-billing_reason); `now` on a subscription
249
- * that is not trialing has no trial to end and changes nothing; the credit Stripe prorates for the unused time when a new
250
- * trial starts mid-period is not made (as `proration_behavior=none` would have it); `pending_if_incomplete` answers as
251
- * `allow_incomplete` (no pending update is modelled). Answers a refusal, or the invoice to collect once the subscription is written. */
252
- async function applyTrialEnd(ctx: SemanticsContext, sub: Row, fields: Row, now: number): Promise<{ invoice: string; payer?: string; attempt: boolean } | undefined> {
253
- const value = ctx.params.trial_end;
254
- delete fields.trial_end;
255
- const id = String(sub.id);
256
- if (value !== 'now' || sub.status !== 'trialing') return value === 'now' ? undefined : movedTrial(sub, fields, Number(value), now);
257
- // the period's invoice bills the items as this update leaves them, under its discount
258
- const next: Row = { ...sub, ...fields, items: ctx.get(SUB, id)?.items ?? sub.items };
259
- const { interval, interval_count } = resolveSubscriptionBillingInterval(((next.items as { data?: Row[] } | undefined)?.data) ?? [], finder(ctx));
260
- const period = { start: now, end: addBillingInterval(now, interval, interval_count) };
261
- const invoice = await draftSubscriptionInvoice(ctx, next, { ...period, reason: 'subscription_update' });
262
- Object.assign(fields, { trial_end: now, billing_cycle_anchor: now, current_period_start: period.start, current_period_end: period.end, latest_invoice: invoice });
263
- const payer = trialPayer(ctx, sub);
264
- return { invoice, ...(payer ? { payer } : {}), attempt: ctx.params.payment_behavior !== 'default_incomplete' };
265
- }
266
-
267
- /** A trial moved to end at `end`, or a new one to `end` on a subscription out of its trial (applyTrialEnd). */
268
- function movedTrial(sub: Row, fields: Row, end: number, now: number): undefined {
269
- Object.assign(fields, {
270
- status: 'trialing', trial_end: end, trial_start: sub.trial_start ?? now, billing_cycle_anchor: end,
271
- current_period_end: end, ...(sub.status !== 'trialing' ? { current_period_start: now } : {}),
272
- });
273
- return undefined;
274
- }
275
-
276
- /** What ending the trial charges: the update's own default payment method, else the subscription's, else its customer's. */
277
- function trialPayer(ctx: SemanticsContext, sub: Row): string | undefined {
278
- const given = ctx.params.default_payment_method;
279
- return typeof given === 'string' && given ? given : payerOf(ctx, sub);
280
- }
281
-
282
- /** What an update's `trial_end` refuses before anything is written: a new trial the machine does not allow from the
283
- * subscription's status, or, under `error_if_incomplete`, ending a trial that cannot be paid at once ("If payment fails,
284
- * return an HTTP `402` status code and don't update the subscription", docs.stripe.com/api/subscriptions/update). */
285
- function trialEndBlocked(ctx: SemanticsContext, sub: Row, discounts: Row[] | undefined): Response | undefined {
286
- if (ctx.params.trial_end !== 'now') return newTrialRefused(ctx, sub);
287
- if (sub.status !== 'trialing') return undefined;
288
- const charge = trialEndCharge(ctx, sub, discounts);
289
- if (ctx.params.payment_behavior === 'error_if_incomplete' && charge.due > 0 && !charge.pays) return firstPaymentRefused(ctx, charge.declined);
290
- // the move ending the trial makes (paid or nothing due: active; else past_due) is the machine's, asked before
291
- // anything is written
292
- const refused = ctx.legal(SUB, 'status', ctx.call.operation.id, 'trialing', charge.due <= 0 || charge.pays ? 'active' : 'past_due', String(sub.id));
293
- return refused ? ctx.refuse(refused) : undefined;
294
- }
295
-
296
- /** A price named by id, or given as an object. */
297
- function priceRow(ctx: SemanticsContext, price: unknown): Row | undefined {
298
- return typeof price === 'string' ? ctx.get('price', price) : price && typeof price === 'object' ? (price as Row) : undefined;
299
- }
300
-
301
- /** One period of the subscription's items as an update's `items` would leave them, before any discount: an entry naming
302
- * an item changes its price and quantity (a new price resets the quantity to 1 unless one is given) or deletes it, and
303
- * one naming none adds an item (applyItemChanges). */
304
- function dueAfter(ctx: SemanticsContext, sub: Row, entries: Row[]): number {
305
- const lines = new Map<string, { price: Row | undefined; quantity: number }>();
306
- for (const it of ((sub.items as { data?: Row[] } | undefined)?.data) ?? []) lines.set(String(it.id), { price: priceRow(ctx, it.price), quantity: itemQuantity(it) });
307
- entries.forEach((e, i) => entryAfter(ctx, lines, e, i));
308
- return [...lines.values()].reduce((sum, l) => sum + priceAmount(l.price, l.quantity), 0);
309
- }
310
-
311
- /** One entry of an update's `items` on the lines it would leave (dueAfter). */
312
- function entryAfter(ctx: SemanticsContext, lines: Map<string, { price: Row | undefined; quantity: number }>, e: Row, i: number): void {
313
- // an inline price bills as the Price inlinePrice makes of it
314
- const d = e.price_data && typeof e.price_data === 'object' ? (e.price_data as Row) : undefined;
315
- const inline: Row | undefined = d ? (d.unit_amount_decimal !== undefined ? { unit_amount_decimal: String(d.unit_amount_decimal) } : { unit_amount: Math.trunc(Number(d.unit_amount) || 0) }) : undefined;
316
- const price = typeof e.price === 'string' && e.price ? priceRow(ctx, e.price) : inline;
317
- const named = typeof e.id === 'string' && e.id;
318
- const key = named ? String(e.id) : `new_${i}`;
319
- // only a named item is deleted; an entry naming none adds one, as applyItemChanges does
320
- if (named && asBool(e.deleted)) { lines.delete(key); return; }
321
- const was = lines.get(key);
322
- 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;
323
- lines.set(key, { price: price ?? was?.price, quantity });
324
- }
325
-
326
- /** A new trial on a subscription whose status the machine does not let start one. */
327
- function newTrialRefused(ctx: SemanticsContext, sub: Row): Response | undefined {
328
- const refused = sub.status === 'trialing' ? undefined : ctx.legal(SUB, 'status', ctx.call.operation.id, sub.status, 'trialing', String(sub.id));
329
- return refused ? ctx.refuse(refused) : undefined;
330
- }
331
-
332
- /** What ending the trial charges, judged before anything is written: the period of the items as this update leaves them
333
- * (dueAfter) under the discount it leaves, and whether the payer pays it (an attempt made, a payer, no decline). */
334
- function trialEndCharge(ctx: SemanticsContext, sub: Row, discounts: Row[] | undefined): { due: number; pays: boolean; declined?: ReturnType<typeof declineFor> } {
335
- const subtotal = dueAfter(ctx, sub, subscriptionItemEntries(ctx.params.items));
336
- // the discount the subscription carries after this update takes this invoice (renewals.ts draftSubscriptionInvoice)
337
- const discount = (discounts ?? (sub.discounts as Row[] | undefined) ?? [])[0];
338
- const coupon = discount && typeof discount === 'object' ? (discount.coupon as Row | undefined) : undefined;
339
- const due = subtotal - (coupon ? applyCouponDiscount(subtotal, coupon) : 0);
340
- const payer = trialPayer(ctx, sub);
341
- const attempt = ctx.params.payment_behavior !== 'default_incomplete';
342
- const declined = payer && attempt ? declineFor(finder(ctx), { payment_method: payer }) : undefined;
343
- return { due, pays: attempt && !!payer && !declined, ...(declined ? { declined } : {}) };
344
176
  }
345
177
 
346
- const update: Semantics = async (ctx) => {
178
+ export async function PostSubscriptionsSubscriptionExposedId(ctx: HandlerContext): Promise<Response> {
347
179
  const id = at(ctx, 'subscription_exposed_id');
348
180
  // a period that has already ended renews (or a trial that has, ends) before the update applies (renewals.ts)
349
181
  await advanceBilling(ctx, (s) => s.id === id);
@@ -381,7 +213,7 @@ const update: Semantics = async (ctx) => {
381
213
  // scheduled cancel follows a period this update moves
382
214
  if ('cancel_at_period_end' in params) {
383
215
  fields.cancel_at_period_end = asBool(params.cancel_at_period_end);
384
- 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 });
216
+ 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 });
385
217
  } else if (sub.cancel_at_period_end === true && fields.current_period_end !== undefined) fields.cancel_at = fields.current_period_end;
386
218
  if (trial?.invoice) {
387
219
  // the trial's end is charged at once: paid (or nothing due), the subscription is active; not, past_due
@@ -392,219 +224,4 @@ const update: Semantics = async (ctx) => {
392
224
  if (fields.discounts !== undefined) fields.discounts = keptAfter(fields.discounts as Row[], ctx.get('invoice', trial.invoice) ?? {});
393
225
  }
394
226
  return ctx.reply(await ctx.write(SUB, id, fields, op));
395
- };
396
-
397
- /** A new item named with neither `price` nor `price_data`: "One of `price` or `price_data` is required"
398
- * (docs.stripe.com/api/subscriptions/update). */
399
- function noItemPrice(ctx: SemanticsContext): Response {
400
- return fail(ctx, 'Missing required param: items[price].', 400, 'parameter_missing');
401
- }
402
-
403
- /** A subscription asked to fail when its first invoice cannot be paid at once (payment_behavior=error_if_incomplete):
404
- * the card's decline, or no payment method to charge (docs.stripe.com/api/subscriptions/create#create_subscription-payment_behavior). */
405
- function firstPaymentRefused(ctx: SemanticsContext, declined: Parameters<typeof cardError>[0] | null | undefined): Response {
406
- return declined ? send(ctx, cardError(declined)) : fail(ctx, 'This customer has no attached payment source or default payment method.', 400, 'resource_missing');
407
- }
408
-
409
- /** An update's `items`: "A list of up to 20 subscription items, each with an attached price"
410
- * (docs.stripe.com/api/subscriptions/update): an entry naming an item's `id` changes that item (its price, quantity,
411
- * metadata) or, with `deleted` ("A flag that, if set to `true`, will delete the specified item"), removes it; "If you
412
- * omit `id`, the API adds a new subscription item rather than updating the existing one". Each takes `price` or
413
- * `price_data`, "Data used to generate a new Price object inline. One of `price` or `price_data` is required", and
414
- * "When changing a subscription item's price, `quantity` is set to 1 unless a `quantity` parameter is provided". An
415
- * inline price is made as the manage-prices guide says: "By default, prices created with `price_data` are effectively
416
- * archived (they're marked as `active=false`)" (docs.stripe.com/products-prices/manage-prices).
417
- * Where the documentation stops and the twin decides: the update is all or nothing, every entry checked before any is
418
- * written (Stripe answers one error for the request); the refusals are worded by the twin. */
419
- async function applyItemChanges(ctx: SemanticsContext, subId: string, entries: Row[]): Promise<Response | undefined> {
420
- // every entry is checked first: a refusal leaves the subscription as it was
421
- const refused = itemChangesRefused(ctx, subId, entries);
422
- if (refused) return refused;
423
- return writeItemChanges(ctx, subId, entries);
424
- }
425
-
426
- /** The refusal of an update's `items`, checked entry by entry before any is written (applyItemChanges). */
427
- function itemChangesRefused(ctx: SemanticsContext, subId: string, entries: Row[]): Response | undefined {
428
- for (const entry of entries) {
429
- const priceRef = typeof entry.price === 'string' && entry.price ? entry.price : undefined;
430
- if (priceRef && !ctx.get('price', priceRef)) return fail(ctx, `No such price: '${priceRef}'`, 400, 'resource_missing');
431
- const inline = !priceRef && entry.price_data && typeof entry.price_data === 'object' ? (entry.price_data as Row) : undefined;
432
- if (inline) {
433
- const refused = refuseInlinePrice(ctx, inline);
434
- if (refused) return refused;
435
- }
436
- if (typeof entry.id === 'string' && entry.id) {
437
- const si = ctx.get('subscription_item', entry.id);
438
- if (!si || si.subscription !== subId) return fail(ctx, `No such subscription_item: '${entry.id}'`, 400, 'resource_missing');
439
- } else if (!priceRef && !inline) return noItemPrice(ctx);
440
- }
441
- return undefined;
442
- }
443
-
444
- /** An update's checked `items`, written. */
445
- async function writeItemChanges(ctx: SemanticsContext, subId: string, entries: Row[]): Promise<undefined> {
446
- for (const entry of entries) {
447
- const inline = typeof entry.price === 'string' && entry.price ? undefined : entry.price_data && typeof entry.price_data === 'object' ? (entry.price_data as Row) : undefined;
448
- const priceRef = typeof entry.price === 'string' && entry.price ? entry.price : inline ? await inlinePrice(ctx, inline) : undefined;
449
- // only a price that changes resets the quantity; one naming the item's own price leaves it
450
- const was = typeof entry.id === 'string' && entry.id ? ctx.get('subscription_item', entry.id)?.price : undefined;
451
- const wasId = typeof was === 'string' ? was : was && typeof was === 'object' ? String((was as Row).id ?? '') : undefined;
452
- const quantity = entry.quantity !== undefined ? { quantity: Math.max(0, Math.trunc(Number(entry.quantity) || 0)) } : priceRef && priceRef !== wasId ? { quantity: 1 } : {};
453
- const metadata = entry.metadata && typeof entry.metadata === 'object' ? { metadata: entry.metadata } : {};
454
- if (typeof entry.id === 'string' && entry.id) {
455
- if (asBool(entry.deleted)) { await ctx.write('subscription_item', entry.id, { deleted: true }, 'subscription_item.delete'); continue; }
456
- await ctx.write('subscription_item', entry.id, { ...(priceRef ? subscriptionItemPrice(priceRef, finder(ctx)) : {}), ...quantity, ...metadata }, 'subscription_item.update');
457
- } else {
458
- await created(ctx, 'subscription_item', { subscription: subId, ...subscriptionItemPrice(priceRef, finder(ctx)), quantity: 1, ...quantity, ...metadata }, { livemode: false, metadata: {}, discounts: [], tax_rates: [], billing_thresholds: null });
459
- }
460
- }
461
- await syncItems(ctx, subId);
462
- return undefined;
463
- }
464
-
465
- /** A subscription item's `price_data` Stripe would refuse: its required `currency`, `product` (an existing one) and
466
- * `recurring[interval]`, and "Only one of `unit_amount` and `unit_amount_decimal` can be set". */
467
- function refuseInlinePrice(ctx: SemanticsContext, d: Row): Response | undefined {
468
- for (const name of ['currency', 'product']) if (typeof d[name] !== 'string' || !d[name]) return fail(ctx, `Missing required param: items[price_data][${name}].`, 400, 'parameter_missing');
469
- if (!ctx.get('product', String(d.product))) return fail(ctx, `No such product: '${String(d.product)}'`, 400, 'resource_missing');
470
- const interval = (d.recurring as Row | undefined)?.interval;
471
- if (!['day', 'week', 'month', 'year'].includes(String(interval))) return fail(ctx, 'Missing required param: items[price_data][recurring][interval].', 400, 'parameter_missing');
472
- if (d.unit_amount !== undefined && d.unit_amount_decimal !== undefined) return fail(ctx, 'Only one of `unit_amount` and `unit_amount_decimal` can be set.', 400, 'parameter_invalid');
473
- return undefined;
474
- }
475
-
476
- /** The inline Price a `price_data` generates: recurring, archived (active=false). Answers its id. */
477
- async function inlinePrice(ctx: SemanticsContext, d: Row): Promise<string> {
478
- const r = d.recurring as Row;
479
- const decimal = d.unit_amount_decimal !== undefined ? String(d.unit_amount_decimal) : String(Math.trunc(Number(d.unit_amount) || 0));
480
- const price = await created(ctx, 'price', {
481
- product: d.product, currency: String(d.currency).toLowerCase(),
482
- // a decimal amount that is not a whole number of the smallest unit has no integer unit_amount (as semantics/plans.ts)
483
- 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,
484
- 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 },
485
- tax_behavior: typeof d.tax_behavior === 'string' ? d.tax_behavior : 'unspecified',
486
- }, { active: false, livemode: false, billing_scheme: 'per_unit', type: 'recurring', metadata: {}, lookup_key: null, nickname: null });
487
- return String(price.id);
488
- }
489
-
490
- const deleteDiscount: Semantics = async (ctx) => {
491
- const id = at(ctx, 'subscription_exposed_id');
492
- const sub = ctx.get(SUB, id);
493
- if (!sub) return subMissing(ctx, id);
494
- const current = ((sub.discounts as unknown[] | undefined) ?? [])[0] ?? sub.discount;
495
- if (!current) return fail(ctx, `No active discount for subscription: '${id}'`, 404, 'resource_missing');
496
- await ctx.write(SUB, id, { discounts: [] }, 'subscription.update');
497
- return ctx.reply(deletedDiscount(typeof current === 'string' ? { id: current } : (current as Row), ctx.now()));
498
- };
499
-
500
- // canceling keeps the subscription, now canceled, stamped with when it was canceled and ended
501
- // (https://docs.stripe.com/api/subscriptions/object#subscription_object-canceled_at, #subscription_object-ended_at)
502
- const cancel: Semantics = async (ctx) => {
503
- const id = at(ctx, 'subscription_exposed_id');
504
- const sub = ctx.get(SUB, id);
505
- if (!sub) return subMissing(ctx, id);
506
- const refused = ctx.legal(SUB, 'status', 'DeleteSubscriptionsSubscriptionExposedId', sub.status, undefined, id);
507
- if (refused) return ctx.refuse(refused);
508
- const now = ctx.now();
509
- // why it was canceled: the request's `cancellation_details` ("Details about why this subscription was cancelled"), and
510
- // the reason Stripe records for a cancel asked through the API, as the cancel page's example answers it:
511
- // `"reason": "cancellation_requested"` (docs.stripe.com/api/subscriptions/cancel)
512
- const given = ctx.params.cancellation_details && typeof ctx.params.cancellation_details === 'object' ? (ctx.params.cancellation_details as Row) : {};
513
- const cancellation_details = { comment: given.comment ?? null, feedback: given.feedback ?? null, reason: 'cancellation_requested' };
514
- return ctx.reply(await ctx.write(SUB, id, { status: 'canceled', canceled_at: now, ended_at: now, cancellation_details }, 'subscription.cancel'));
515
- };
516
-
517
- // ── items: each write keeps the parent subscription's items.data in step, rebuilt from the live items ──
518
-
519
- /** A new subscription's items, each its own subscription_item as Stripe keeps it (/v1/subscription_items/{id}). */
520
- export async function storeItems(ctx: SemanticsContext, subId: string, items: Row[]): Promise<void> {
521
- for (const item of items) {
522
- if (typeof item.id !== 'string' || ctx.get('subscription_item', item.id)) continue;
523
- await ctx.write('subscription_item', item.id, { ...item, subscription: subId }, 'subscription_item.create');
524
- }
525
- }
526
-
527
- async function syncItems(ctx: SemanticsContext, subId: string): Promise<void> {
528
- if (!subId) return;
529
- const items = ctx.rows('subscription_item').filter((r) => r.subscription === subId);
530
- 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');
531
227
  }
532
-
533
- /** A subscription item with its billing period: its subscription's (basil moved the period onto the items). */
534
- const withPeriod = (ctx: SemanticsContext, si: Row): Row => {
535
- const sub = ctx.get(SUB, String(si.subscription));
536
- 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 };
537
- };
538
-
539
- const createItem: Semantics = async (ctx) => {
540
- const params = ctx.params;
541
- const subId = typeof params.subscription === 'string' ? params.subscription : '';
542
- if (!subId) return fail(ctx, 'Missing required param: subscription.', 400, 'parameter_missing');
543
- if (!ctx.get(SUB, subId)) return subMissing(ctx, subId);
544
- const priceRef = typeof params.price === 'string' ? params.price : '';
545
- if (!priceRef && !(params.price_data && typeof params.price_data === 'object')) return fail(ctx, 'Missing required param: price.', 400, 'parameter_missing');
546
- if (priceRef && !ctx.get('price', priceRef)) return fail(ctx, `No such price: '${priceRef}'`, 400, 'resource_missing');
547
- const quantity = params.quantity !== undefined ? Math.max(0, Math.trunc(Number(params.quantity) || 0)) : 1;
548
- // "metadata: Set of key-value pairs that you can attach to an object" (docs.stripe.com/api/subscription_items/create)
549
- const metadata = params.metadata && typeof params.metadata === 'object' ? { metadata: params.metadata } : {};
550
- const body = await created(ctx, 'subscription_item', { subscription: subId, ...(priceRef ? subscriptionItemPrice(priceRef, finder(ctx)) : { price: null, plan: null }), quantity, ...metadata }, { livemode: false, metadata: {} });
551
- await syncItems(ctx, subId);
552
- return ctx.reply(withPeriod(ctx, body));
553
- };
554
-
555
- const listItems: Semantics = async (ctx) => {
556
- const subId = typeof ctx.params.subscription === 'string' ? ctx.params.subscription : '';
557
- if (!subId) return fail(ctx, 'Missing required param: subscription.', 400, 'parameter_missing');
558
- if (!ctx.get(SUB, subId)) return subMissing(ctx, subId);
559
- return list(ctx, 'subscription_item', newest(ctx, 'subscription_item').filter((si) => si.subscription === subId).map((si) => withPeriod(ctx, si)));
560
- };
561
-
562
- const ITEM_ATTRIBUTES = new Set(['billing_thresholds', 'discounts', 'metadata', 'price', 'quantity', 'tax_rates']);
563
-
564
- const itemMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such subscription_item: '${id}'`, 404, 'resource_missing');
565
-
566
- const updateItem: Semantics = async (ctx) => {
567
- const id = at(ctx, 'item');
568
- const si = ctx.get('subscription_item', id);
569
- if (!si) return itemMissing(ctx, id);
570
- // only the item's own attributes are kept; the update's instructions (off_session, payment_behavior, price_data,
571
- // proration_behavior, proration_date) are not attributes of the Subscription Item object
572
- // (docs.stripe.com/api/subscription_items/object lists none of them)
573
- const fields: Row = Object.fromEntries(Object.entries(ctx.params).filter(([k]) => ITEM_ATTRIBUTES.has(k)));
574
- if (ctx.params.quantity !== undefined) fields.quantity = Math.max(0, Math.trunc(Number(ctx.params.quantity) || 0));
575
- if (typeof ctx.params.price === 'string' && ctx.params.price && !ctx.get('price', ctx.params.price)) return fail(ctx, `No such price: '${ctx.params.price}'`, 400, 'resource_missing');
576
- // the item carries its Price and plan, as the subscription's own items do (subscriptionItemPrice)
577
- if (typeof ctx.params.price === 'string' && ctx.params.price) Object.assign(fields, subscriptionItemPrice(ctx.params.price, finder(ctx)));
578
- const out = await ctx.write('subscription_item', id, fields, 'subscription_item.update');
579
- await syncItems(ctx, String(si.subscription));
580
- return ctx.reply(withPeriod(ctx, out));
581
- };
582
-
583
- const retrieveItem: Semantics = async (ctx) => {
584
- const si = ctx.get('subscription_item', at(ctx, 'item'));
585
- return si ? ctx.reply(withPeriod(ctx, si)) : itemMissing(ctx, at(ctx, 'item'));
586
- };
587
-
588
- const deleteItem: Semantics = async (ctx) => {
589
- const id = at(ctx, 'item');
590
- const si = ctx.get('subscription_item', id);
591
- if (!si) return itemMissing(ctx, id);
592
- await ctx.write('subscription_item', id, { deleted: true }, 'subscription_item.delete');
593
- await syncItems(ctx, String(si.subscription));
594
- return ctx.reply({ id, object: 'subscription_item', deleted: true });
595
- };
596
-
597
- export const subscriptions: Record<string, Semantics> = {
598
- PostSubscriptions: create,
599
- GetSubscriptions: listSubscriptions,
600
- GetSubscriptionsSearch: searchSubscriptions,
601
- GetSubscriptionsSubscriptionExposedId: retrieveSubscription,
602
- PostSubscriptionsSubscriptionExposedId: update,
603
- DeleteSubscriptionsSubscriptionExposedIdDiscount: deleteDiscount,
604
- DeleteSubscriptionsSubscriptionExposedId: cancel,
605
- PostSubscriptionItems: createItem,
606
- GetSubscriptionItems: listItems,
607
- PostSubscriptionItemsItem: updateItem,
608
- GetSubscriptionItemsItem: retrieveItem,
609
- DeleteSubscriptionItemsItem: deleteItem,
610
- };
@@ -1,27 +1,21 @@
1
- // Test clock semantics. A test clock is Stripe's own door for a transition no API verb makes: the
2
- // passage of time. Advancing it moves the subscriptions of the clock's customers as a real clock tick does:
3
- // trials that ended end, and periods that ended renew (./renewals.ts advanceBilling). List, retrieve and delete are
4
- // the derived core's.
5
- import type { Semantics, SemanticsContext } from '@volter/world-core';
6
- import { at, created, fail } from './shared.ts';
7
- import { advanceBilling } from './renewals.ts';
8
-
9
- const CLOCK = 'test_helpers.test_clock';
10
-
11
- const create: Semantics = async (ctx) => {
12
- if (ctx.params.frozen_time === undefined) return fail(ctx, 'Missing required param: frozen_time.', 400, 'parameter_missing');
13
- const frozen = Math.trunc(Number(ctx.params.frozen_time) || 0);
14
- // "Simulations are automatically deleted 30 days after you create them" (docs.stripe.com/billing/testing/test-clocks/
15
- // api-advanced-usage): from its creation on the account's clock, not its frozen time
16
- return ctx.reply(await created(ctx, CLOCK, { name: ctx.params.name ?? null }, { frozen_time: frozen, status: 'ready', status_details: {}, livemode: false, deletes_after: Number(ctx.now()) + 30 * 24 * 3600 }));
17
- };
1
+ // Stripe's test_helpers 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 { AUTH, CARD, cardId, CH, historyEntry, holderId, merchantData, TXN } from '../engine/issuing.ts';
6
+ import { isValidApiVersion, nowUnix, spendingControlsViolation, TWIN_API_VERSION } from '../engine/stripe.ts';
7
+ import { READER } from '../engine/terminal.ts';
8
+ import { CLOCK } from '../engine/test-clocks.ts';
9
+ import { FA, TX } from '../engine/treasury.ts';
10
+ import { stripeEventMatches } from '../engine/webhooks.ts';
11
+ import { answeredByEndpoint, approvedAtOnce, askForAuthorization, at, authMissing, balanceOf, capture, cashBalance, created, customerMissing, fail, holdForCapture, issuingRefusal, mintCharge, nothingToPresent, readerMissing, received, reconcile, review, withCard } from './shared.ts';
18
12
 
19
13
  /** A test clock deleted with what lives on it: "Deleting the simulation also deletes the test customers associated
20
14
  * with the clock and cancels their subscriptions" (docs.stripe.com/billing/testing/test-clocks/api-advanced-usage). */
21
- const remove: Semantics = async (ctx) => {
15
+ export async function DeleteTestHelpersTestClocksTestClock(ctx: HandlerContext): Promise<Response> {
22
16
  const id = at(ctx, 'test_clock');
23
17
  if (!ctx.get(CLOCK, id)) return fail(ctx, `No such test clock: '${id}'`, 404, 'resource_missing');
24
- const now = Number(ctx.now());
18
+ const now = Number(nowUnix(ctx.occurredAt));
25
19
  for (const c of ctx.rows('customer').filter((c) => c.test_clock === id)) {
26
20
  for (const sub of ctx.rows('subscription').filter((s) => s.customer === c.id && s.status !== 'canceled')) {
27
21
  ctx.legal('subscription', 'status', 'DeleteTestHelpersTestClocksTestClock', String(sub.status), 'canceled', String(sub.id));
@@ -31,9 +25,17 @@ const remove: Semantics = async (ctx) => {
31
25
  }
32
26
  await ctx.write(CLOCK, id, { deleted: true }, 'test_clock.deleted');
33
27
  return ctx.reply({ id, object: 'test_helpers.test_clock', deleted: true });
34
- };
28
+ }
29
+
30
+ export async function PostTestHelpersTestClocks(ctx: HandlerContext): Promise<Response> {
31
+ if (ctx.params.frozen_time === undefined) return fail(ctx, 'Missing required param: frozen_time.', 400, 'parameter_missing');
32
+ const frozen = Math.trunc(Number(ctx.params.frozen_time) || 0);
33
+ // "Simulations are automatically deleted 30 days after you create them" (docs.stripe.com/billing/testing/test-clocks/
34
+ // api-advanced-usage): from its creation on the account's clock, not its frozen time
35
+ return ctx.reply(await created(ctx, CLOCK, { name: ctx.params.name ?? null }, { frozen_time: frozen, status: 'ready', status_details: {}, livemode: false, deletes_after: Number(nowUnix(ctx.occurredAt)) + 30 * 24 * 3600 }));
36
+ }
35
37
 
36
- const advance: Semantics = async (ctx) => {
38
+ export async function PostTestHelpersTestClocksTestClockAdvance(ctx: HandlerContext): Promise<Response> {
37
39
  const id = at(ctx, 'test_clock');
38
40
  const clock = ctx.get(CLOCK, id);
39
41
  if (!clock) return fail(ctx, `No such test clock: '${id}'`, 404, 'resource_missing');
@@ -47,31 +49,5 @@ const advance: Semantics = async (ctx) => {
47
49
  // and finishClockAdvances completes it ADVANCE_SECONDS of World time later; an advance asked meanwhile is refused
48
50
  // test_clock_not_ready (the machine's refusal).
49
51
  return ctx.reply(await ctx.write(CLOCK, id, { status: 'advancing', status_details: { advancing: { target_frozen_time: to } } }, 'test_clock.advancing'));
50
- };
51
-
52
- /** How long an advance takes: "it takes a few seconds to advance to the specified time"
53
- * (docs.stripe.com/billing/testing/test-clocks/api-advanced-usage). Where the documentation stops and the twin decides:
54
- * five seconds of World time from the advance. */
55
- const ADVANCE_SECONDS = 5;
56
-
57
- /** Each advancing clock whose advance has taken its time reaches its target, caught up before anything is answered:
58
- * its frozen_time moves, it is ready, and its customers' trials and periods that ended by then end
59
- * (./renewals.ts advanceBilling). The advance's own write is when it began (the kernel's updatedAt). */
60
- export async function finishClockAdvances(ctx: SemanticsContext): Promise<void> {
61
- const now = Number(ctx.now());
62
- for (const clock of ctx.rowsRaw(CLOCK)) {
63
- if (clock.status !== 'advancing') continue;
64
- if (Date.parse(String(clock.updatedAt)) / 1000 + ADVANCE_SECONDS > now) continue;
65
- const id = String(clock.id);
66
- const target = Number((clock.status_details as { advancing?: { target_frozen_time?: number } } | undefined)?.advancing?.target_frozen_time);
67
- ctx.legal(CLOCK, 'status', ctx.call.operation.id, 'advancing', 'ready', id, 'vendor');
68
- await ctx.write(CLOCK, id, { frozen_time: target, status: 'ready', status_details: {} }, 'test_clock.ready');
69
- await advanceBilling(ctx, (sub) => typeof sub.customer === 'string' && ctx.get('customer', sub.customer)?.test_clock === id);
70
- }
71
52
  }
72
53
 
73
- export const testClocks: Record<string, Semantics> = {
74
- PostTestHelpersTestClocks: create,
75
- PostTestHelpersTestClocksTestClockAdvance: advance,
76
- DeleteTestHelpersTestClocksTestClock: remove,
77
- };