@volter/twin-stripe 2.0.38 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/README.md +82 -207
  2. package/dist/generated/pack-facts.json +94 -0
  3. package/dist/spec/SOURCE.md +14 -0
  4. package/dist/spec/fixtures3.json.gz +0 -0
  5. package/dist/spec/openapi.json.gz +0 -0
  6. package/dist/spec/patches.json +34 -0
  7. package/dist/spec/sources.json +378 -0
  8. package/dist/src/cli.js +12 -30
  9. package/dist/src/engine/after-payment.d.ts +9 -0
  10. package/dist/src/engine/after-payment.js +28 -0
  11. package/dist/src/engine/apps-secrets.d.ts +1 -0
  12. package/dist/src/engine/apps-secrets.js +3 -0
  13. package/dist/src/engine/balance.d.ts +5 -0
  14. package/dist/src/engine/balance.js +15 -0
  15. package/dist/src/engine/billing.d.ts +5 -0
  16. package/dist/src/engine/billing.js +6 -0
  17. package/dist/src/engine/charges.d.ts +6 -0
  18. package/dist/src/engine/charges.js +13 -0
  19. package/dist/src/engine/checkout.d.ts +24 -0
  20. package/dist/src/engine/checkout.js +30 -0
  21. package/dist/src/engine/common.d.ts +19 -0
  22. package/dist/src/engine/common.js +32 -0
  23. package/dist/src/{screens → engine}/connect-settings.d.ts +1 -7
  24. package/dist/src/engine/connect-settings.js +26 -0
  25. package/dist/src/engine/connect.d.ts +66 -0
  26. package/dist/src/engine/connect.js +189 -0
  27. package/dist/src/engine/coupons.d.ts +0 -0
  28. package/dist/src/engine/coupons.js +5 -0
  29. package/dist/src/engine/credit-notes.d.ts +1 -0
  30. package/dist/src/engine/credit-notes.js +5 -0
  31. package/dist/src/engine/customers.d.ts +12 -0
  32. package/dist/src/engine/customers.js +30 -0
  33. package/dist/src/engine/display.d.ts +14 -0
  34. package/dist/src/engine/display.js +16 -0
  35. package/dist/src/engine/disputes.d.ts +0 -0
  36. package/dist/src/engine/disputes.js +9 -0
  37. package/dist/src/engine/entitlements.d.ts +1 -0
  38. package/dist/src/engine/entitlements.js +4 -0
  39. package/dist/src/engine/ephemeral-keys.d.ts +0 -0
  40. package/dist/src/engine/ephemeral-keys.js +4 -0
  41. package/dist/src/engine/files.d.ts +18 -0
  42. package/dist/src/engine/files.js +33 -0
  43. package/dist/src/engine/front.d.ts +5 -0
  44. package/dist/src/engine/front.js +25 -0
  45. package/dist/src/engine/invoices.d.ts +24 -0
  46. package/dist/src/engine/invoices.js +57 -0
  47. package/dist/src/engine/issuing.d.ts +35 -0
  48. package/dist/src/engine/issuing.js +45 -0
  49. package/dist/src/engine/ledger.d.ts +9 -0
  50. package/dist/src/engine/ledger.js +9 -0
  51. package/dist/src/engine/oauth.d.ts +4 -0
  52. package/dist/src/engine/oauth.js +25 -0
  53. package/dist/src/engine/onboarding.d.ts +13 -0
  54. package/dist/src/engine/onboarding.js +42 -0
  55. package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
  56. package/dist/src/{stripe-params.js → engine/params.js} +7 -60
  57. package/dist/src/engine/payment-intents.d.ts +10 -0
  58. package/dist/src/engine/payment-intents.js +27 -0
  59. package/dist/src/engine/payment-links.d.ts +1 -0
  60. package/dist/src/engine/payment-links.js +5 -0
  61. package/dist/src/engine/payment-methods.d.ts +16 -0
  62. package/dist/src/engine/payment-methods.js +22 -0
  63. package/dist/src/engine/plans.d.ts +6 -0
  64. package/dist/src/engine/plans.js +26 -0
  65. package/dist/src/engine/platform.d.ts +39 -0
  66. package/dist/src/engine/platform.js +34 -0
  67. package/dist/src/engine/products.d.ts +0 -0
  68. package/dist/src/engine/products.js +4 -0
  69. package/dist/src/engine/radar.d.ts +2 -0
  70. package/dist/src/engine/radar.js +5 -0
  71. package/dist/src/engine/refunds.d.ts +1 -0
  72. package/dist/src/engine/refunds.js +28 -0
  73. package/dist/src/engine/renewals.d.ts +36 -0
  74. package/dist/src/engine/renewals.js +65 -0
  75. package/dist/src/engine/setup-intents.d.ts +1 -0
  76. package/dist/src/engine/setup-intents.js +4 -0
  77. package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +11 -39
  78. package/dist/src/{stripe-twin.js → engine/stripe.js} +25 -310
  79. package/dist/src/engine/subscription-schedules.d.ts +3 -0
  80. package/dist/src/engine/subscription-schedules.js +1 -0
  81. package/dist/src/engine/subscriptions.d.ts +12 -0
  82. package/dist/src/engine/subscriptions.js +31 -0
  83. package/dist/src/engine/tax.d.ts +7 -0
  84. package/dist/src/engine/tax.js +28 -0
  85. package/dist/src/engine/terminal.d.ts +4 -0
  86. package/dist/src/engine/terminal.js +20 -0
  87. package/dist/src/engine/test-clocks.d.ts +5 -0
  88. package/dist/src/engine/test-clocks.js +9 -0
  89. package/dist/src/engine/tokens.d.ts +7 -0
  90. package/dist/src/engine/tokens.js +20 -0
  91. package/dist/src/engine/transfers.d.ts +2 -0
  92. package/dist/src/engine/transfers.js +1 -0
  93. package/dist/src/engine/treasury.d.ts +23 -0
  94. package/dist/src/engine/treasury.js +45 -0
  95. package/dist/src/{stripe-version.js → engine/version.js} +4 -3
  96. package/dist/src/engine/webhook-endpoints.d.ts +1 -0
  97. package/dist/src/engine/webhook-endpoints.js +12 -0
  98. package/dist/src/engine/webhooks.d.ts +31 -0
  99. package/dist/src/engine/webhooks.js +68 -0
  100. package/dist/src/fetch.d.ts +2 -0
  101. package/dist/src/fetch.js +26 -0
  102. package/dist/src/generated/surface.gen.json +1 -1
  103. package/dist/src/generated/ui.gen.json +1 -1
  104. package/dist/src/index.d.ts +5 -14
  105. package/dist/src/index.js +8 -75
  106. package/dist/src/manifest.js +232 -758
  107. package/dist/src/screens/billing-portal.d.ts +3 -0
  108. package/dist/src/screens/{portal.js → billing-portal.js} +28 -54
  109. package/dist/src/screens/checkout.d.ts +2 -31
  110. package/dist/src/screens/checkout.js +32 -121
  111. package/dist/src/screens/connect-oauth.d.ts +3 -27
  112. package/dist/src/screens/connect-oauth.js +51 -132
  113. package/dist/src/screens/connect-onboarding.d.ts +3 -0
  114. package/dist/src/screens/{onboarding.js → connect-onboarding.js} +36 -102
  115. package/dist/src/screens/dashboard.d.ts +3 -0
  116. package/dist/src/screens/dashboard.js +117 -0
  117. package/dist/src/screens/shared.d.ts +27 -0
  118. package/dist/src/screens/shared.js +87 -0
  119. package/dist/src/screens/stripe-js.d.ts +3 -0
  120. package/dist/src/screens/stripe-js.js +135 -0
  121. package/dist/src/semantics/account_links.d.ts +2 -0
  122. package/dist/src/semantics/account_links.js +22 -0
  123. package/dist/src/semantics/accounts.d.ts +4 -0
  124. package/dist/src/semantics/accounts.js +38 -0
  125. package/dist/src/semantics/apps.d.ts +4 -0
  126. package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
  127. package/dist/src/semantics/around.d.ts +2 -0
  128. package/dist/src/semantics/around.js +16 -0
  129. package/dist/src/semantics/balance.d.ts +2 -11
  130. package/dist/src/semantics/balance.js +3 -194
  131. package/dist/src/semantics/balance_transactions.d.ts +2 -0
  132. package/dist/src/semantics/balance_transactions.js +15 -0
  133. package/dist/src/semantics/billing_portal.d.ts +5 -0
  134. package/dist/src/semantics/billing_portal.js +42 -0
  135. package/dist/src/semantics/charges.d.ts +3 -28
  136. package/dist/src/semantics/charges.js +5 -202
  137. package/dist/src/semantics/checkout.d.ts +3 -20
  138. package/dist/src/semantics/checkout.js +8 -261
  139. package/dist/src/semantics/clock.d.ts +2 -0
  140. package/dist/src/semantics/clock.js +21 -0
  141. package/dist/src/semantics/coupons.d.ts +2 -6
  142. package/dist/src/semantics/coupons.js +2 -60
  143. package/dist/src/semantics/customers.d.ts +6 -6
  144. package/dist/src/semantics/customers.js +27 -387
  145. package/dist/src/semantics/disputes.d.ts +2 -2
  146. package/dist/src/semantics/disputes.js +3 -49
  147. package/dist/src/semantics/doors.d.ts +23 -0
  148. package/dist/src/semantics/doors.js +73 -0
  149. package/dist/src/semantics/entitlements.d.ts +2 -2
  150. package/dist/src/semantics/entitlements.js +5 -50
  151. package/dist/src/semantics/events.d.ts +12 -0
  152. package/dist/src/semantics/events.js +36 -0
  153. package/dist/src/semantics/index.d.ts +2 -0
  154. package/dist/src/semantics/index.js +107 -0
  155. package/dist/src/semantics/invoice_payments.d.ts +2 -0
  156. package/dist/src/semantics/invoice_payments.js +12 -0
  157. package/dist/src/semantics/invoiceitems.d.ts +3 -0
  158. package/dist/src/semantics/invoiceitems.js +59 -0
  159. package/dist/src/semantics/invoices.d.ts +16 -18
  160. package/dist/src/semantics/invoices.js +118 -444
  161. package/dist/src/semantics/payment_intents.d.ts +4 -0
  162. package/dist/src/semantics/payment_intents.js +79 -0
  163. package/dist/src/semantics/payment_methods.d.ts +5 -0
  164. package/dist/src/semantics/payment_methods.js +39 -0
  165. package/dist/src/semantics/payouts.d.ts +2 -0
  166. package/dist/src/semantics/payouts.js +21 -0
  167. package/dist/src/semantics/prices.d.ts +4 -0
  168. package/dist/src/semantics/prices.js +37 -0
  169. package/dist/src/semantics/products.d.ts +6 -2
  170. package/dist/src/semantics/products.js +15 -130
  171. package/dist/src/semantics/promotion_codes.d.ts +3 -0
  172. package/dist/src/semantics/promotion_codes.js +35 -0
  173. package/dist/src/semantics/radar.d.ts +2 -2
  174. package/dist/src/semantics/radar.js +5 -74
  175. package/dist/src/semantics/refunds.d.ts +3 -9
  176. package/dist/src/semantics/refunds.js +6 -192
  177. package/dist/src/semantics/setup_intents.d.ts +3 -0
  178. package/dist/src/semantics/{setup-intents.js → setup_intents.js} +10 -53
  179. package/dist/src/semantics/shared.d.ts +822 -47
  180. package/dist/src/semantics/shared.js +3044 -78
  181. package/dist/src/semantics/states.d.ts +531 -0
  182. package/dist/src/semantics/states.js +852 -0
  183. package/dist/src/semantics/subscription_items.d.ts +5 -0
  184. package/dist/src/semantics/subscription_items.js +55 -0
  185. package/dist/src/semantics/subscription_schedules.d.ts +4 -0
  186. package/dist/src/semantics/subscription_schedules.js +60 -0
  187. package/dist/src/semantics/subscriptions.d.ts +7 -11
  188. package/dist/src/semantics/subscriptions.js +44 -415
  189. package/dist/src/semantics/test_helpers.d.ts +6 -0
  190. package/dist/src/semantics/{test-clocks.js → test_helpers.js} +14 -40
  191. package/dist/src/semantics/transfers.d.ts +2 -2
  192. package/dist/src/semantics/transfers.js +6 -101
  193. package/dist/src/server.d.ts +7 -0
  194. package/dist/src/server.js +7 -0
  195. package/generated/pack-facts.json +94 -0
  196. package/package.json +10 -20
  197. package/spec/SOURCE.md +14 -0
  198. package/spec/fixtures3.json.gz +0 -0
  199. package/spec/openapi.json.gz +0 -0
  200. package/spec/patches.json +34 -0
  201. package/spec/sources.json +378 -0
  202. package/src/cli.ts +12 -26
  203. package/src/engine/after-payment.ts +31 -0
  204. package/src/engine/apps-secrets.ts +4 -0
  205. package/src/engine/balance.ts +33 -0
  206. package/src/engine/billing.ts +16 -0
  207. package/src/engine/charges.ts +19 -0
  208. package/src/engine/checkout.ts +40 -0
  209. package/src/engine/common.ts +47 -0
  210. package/src/engine/connect-settings.ts +23 -0
  211. package/src/engine/connect.ts +184 -0
  212. package/src/engine/coupons.ts +5 -0
  213. package/src/engine/credit-notes.ts +6 -0
  214. package/src/engine/customers.ts +35 -0
  215. package/src/engine/display.ts +19 -0
  216. package/src/engine/disputes.ts +9 -0
  217. package/src/engine/entitlements.ts +5 -0
  218. package/src/engine/ephemeral-keys.ts +3 -0
  219. package/src/engine/files.ts +41 -0
  220. package/src/engine/front.ts +31 -0
  221. package/src/engine/invoices.ts +71 -0
  222. package/src/engine/issuing.ts +61 -0
  223. package/src/engine/ledger.ts +50 -0
  224. package/src/engine/oauth.ts +19 -0
  225. package/src/engine/onboarding.ts +54 -0
  226. package/src/{stripe-params.ts → engine/params.ts} +8 -56
  227. package/src/engine/payment-intents.ts +33 -0
  228. package/src/engine/payment-links.ts +6 -0
  229. package/src/engine/payment-methods.ts +25 -0
  230. package/src/engine/plans.ts +42 -0
  231. package/src/engine/platform.ts +47 -0
  232. package/src/engine/products.ts +3 -0
  233. package/src/engine/radar.ts +6 -0
  234. package/src/engine/refunds.ts +29 -0
  235. package/src/engine/renewals.ts +73 -0
  236. package/src/engine/setup-intents.ts +5 -0
  237. package/src/{stripe-twin.ts → engine/stripe.ts} +28 -323
  238. package/src/engine/subscription-schedules.ts +10 -0
  239. package/src/engine/subscriptions.ts +40 -0
  240. package/src/engine/tax.ts +39 -0
  241. package/src/engine/terminal.ts +23 -0
  242. package/src/engine/test-clocks.ts +11 -0
  243. package/src/engine/tokens.ts +22 -0
  244. package/src/engine/transfers.ts +23 -0
  245. package/src/engine/treasury.ts +47 -0
  246. package/src/{stripe-version.ts → engine/version.ts} +4 -3
  247. package/src/engine/webhook-endpoints.ts +14 -0
  248. package/src/engine/webhooks.ts +72 -0
  249. package/src/fetch.ts +27 -0
  250. package/src/generated/surface.gen.json +1 -1
  251. package/src/generated/ui.gen.json +1 -1
  252. package/src/index.ts +8 -116
  253. package/src/manifest.ts +234 -786
  254. package/src/screens/{portal.tsx → billing-portal.tsx} +32 -37
  255. package/src/screens/checkout.tsx +33 -95
  256. package/src/screens/connect-oauth.tsx +64 -88
  257. package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +38 -82
  258. package/src/screens/dashboard.tsx +198 -0
  259. package/src/screens/shared.tsx +84 -0
  260. package/src/screens/stripe-js.ts +170 -0
  261. package/src/semantics/account_links.ts +21 -0
  262. package/src/semantics/accounts.ts +43 -0
  263. package/src/semantics/{apps-secrets.ts → apps.ts} +17 -32
  264. package/src/semantics/around.ts +20 -0
  265. package/src/semantics/balance.ts +6 -207
  266. package/src/semantics/balance_transactions.ts +20 -0
  267. package/src/semantics/billing_portal.ts +51 -0
  268. package/src/semantics/charges.ts +12 -211
  269. package/src/semantics/checkout.ts +13 -268
  270. package/src/semantics/clock.ts +26 -0
  271. package/src/semantics/coupons.ts +5 -67
  272. package/src/semantics/customers.ts +29 -392
  273. package/src/semantics/disputes.ts +9 -59
  274. package/src/semantics/doors.ts +80 -0
  275. package/src/semantics/entitlements.ts +9 -56
  276. package/src/semantics/events.ts +44 -0
  277. package/src/semantics/index.ts +111 -0
  278. package/src/semantics/invoice_payments.ts +18 -0
  279. package/src/semantics/invoiceitems.ts +59 -0
  280. package/src/semantics/invoices.ts +109 -443
  281. package/src/semantics/payment_intents.ts +83 -0
  282. package/src/semantics/payment_methods.ts +43 -0
  283. package/src/semantics/payouts.ts +23 -0
  284. package/src/semantics/prices.ts +42 -0
  285. package/src/semantics/products.ts +18 -142
  286. package/src/semantics/promotion_codes.ts +39 -0
  287. package/src/semantics/radar.ts +8 -77
  288. package/src/semantics/refunds.ts +9 -212
  289. package/src/semantics/setup_intents.ts +45 -0
  290. package/src/semantics/shared.ts +3092 -106
  291. package/src/semantics/states.ts +883 -0
  292. package/src/semantics/subscription_items.ts +54 -0
  293. package/src/semantics/subscription_schedules.ts +66 -0
  294. package/src/semantics/subscriptions.ts +49 -432
  295. package/src/semantics/{test-clocks.ts → test_helpers.ts} +23 -47
  296. package/src/semantics/transfers.ts +11 -131
  297. package/src/server.ts +8 -0
  298. package/client/dashboard-api.ts +0 -286
  299. package/client/stripe-mirror.css +0 -277
  300. package/client/stripe-mirror.tsx +0 -1559
  301. package/dist/client/dashboard-api.d.ts +0 -107
  302. package/dist/client/dashboard-api.js +0 -238
  303. package/dist/client/dashboard-api.ts +0 -286
  304. package/dist/client/stripe-mirror.bundle.js +0 -236
  305. package/dist/client/stripe-mirror.css +0 -277
  306. package/dist/client/stripe-mirror.d.ts +0 -134
  307. package/dist/client/stripe-mirror.js +0 -859
  308. package/dist/client/stripe-mirror.tsx +0 -1559
  309. package/dist/src/screens/connect-settings.js +0 -103
  310. package/dist/src/screens/consent-skin.d.ts +0 -4
  311. package/dist/src/screens/consent-skin.js +0 -18
  312. package/dist/src/screens/financial-connections.d.ts +0 -5
  313. package/dist/src/screens/financial-connections.js +0 -90
  314. package/dist/src/screens/identity.d.ts +0 -5
  315. package/dist/src/screens/identity.js +0 -86
  316. package/dist/src/screens/onboarding.d.ts +0 -13
  317. package/dist/src/screens/portal.d.ts +0 -5
  318. package/dist/src/screens/public-details.d.ts +0 -5
  319. package/dist/src/screens/public-details.js +0 -90
  320. package/dist/src/semantics/after-payment.d.ts +0 -22
  321. package/dist/src/semantics/after-payment.js +0 -99
  322. package/dist/src/semantics/apps-secrets.d.ts +0 -2
  323. package/dist/src/semantics/billing.d.ts +0 -2
  324. package/dist/src/semantics/billing.js +0 -220
  325. package/dist/src/semantics/connect.d.ts +0 -5
  326. package/dist/src/semantics/connect.js +0 -493
  327. package/dist/src/semantics/credit-notes.d.ts +0 -2
  328. package/dist/src/semantics/credit-notes.js +0 -172
  329. package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
  330. package/dist/src/semantics/ephemeral-keys.js +0 -34
  331. package/dist/src/semantics/files.d.ts +0 -2
  332. package/dist/src/semantics/files.js +0 -125
  333. package/dist/src/semantics/issuing.d.ts +0 -13
  334. package/dist/src/semantics/issuing.js +0 -575
  335. package/dist/src/semantics/ledger.d.ts +0 -59
  336. package/dist/src/semantics/ledger.js +0 -200
  337. package/dist/src/semantics/payment-intents.d.ts +0 -18
  338. package/dist/src/semantics/payment-intents.js +0 -452
  339. package/dist/src/semantics/payment-links.d.ts +0 -2
  340. package/dist/src/semantics/payment-links.js +0 -133
  341. package/dist/src/semantics/payment-methods.d.ts +0 -20
  342. package/dist/src/semantics/payment-methods.js +0 -140
  343. package/dist/src/semantics/plans.d.ts +0 -5
  344. package/dist/src/semantics/plans.js +0 -121
  345. package/dist/src/semantics/platform.d.ts +0 -9
  346. package/dist/src/semantics/platform.js +0 -206
  347. package/dist/src/semantics/renewals.d.ts +0 -47
  348. package/dist/src/semantics/renewals.js +0 -281
  349. package/dist/src/semantics/setup-intents.d.ts +0 -2
  350. package/dist/src/semantics/subscription-schedules.d.ts +0 -2
  351. package/dist/src/semantics/subscription-schedules.js +0 -119
  352. package/dist/src/semantics/tax.d.ts +0 -2
  353. package/dist/src/semantics/tax.js +0 -197
  354. package/dist/src/semantics/terminal.d.ts +0 -5
  355. package/dist/src/semantics/terminal.js +0 -182
  356. package/dist/src/semantics/test-clocks.d.ts +0 -6
  357. package/dist/src/semantics/tokens.d.ts +0 -4
  358. package/dist/src/semantics/tokens.js +0 -44
  359. package/dist/src/semantics/treasury.d.ts +0 -2
  360. package/dist/src/semantics/treasury.js +0 -377
  361. package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
  362. package/dist/src/semantics/webhook-endpoints.js +0 -85
  363. package/dist/src/stripe-budget.d.ts +0 -55
  364. package/dist/src/stripe-budget.js +0 -155
  365. package/dist/src/stripe-capabilities.d.ts +0 -3
  366. package/dist/src/stripe-capabilities.js +0 -5836
  367. package/dist/src/stripe-conformance.d.ts +0 -43
  368. package/dist/src/stripe-conformance.js +0 -105
  369. package/dist/src/stripe-connector.d.ts +0 -161
  370. package/dist/src/stripe-connector.js +0 -414
  371. package/dist/src/stripe-emit.d.ts +0 -2
  372. package/dist/src/stripe-emit.js +0 -145
  373. package/dist/src/stripe-events.d.ts +0 -93
  374. package/dist/src/stripe-events.js +0 -392
  375. package/dist/src/stripe-js.d.ts +0 -4
  376. package/dist/src/stripe-js.js +0 -70
  377. package/dist/src/stripe-mirror-ui.d.ts +0 -15
  378. package/dist/src/stripe-mirror-ui.js +0 -87
  379. package/dist/src/stripe-perform-harness.d.ts +0 -9
  380. package/dist/src/stripe-perform-harness.js +0 -26
  381. package/dist/src/stripe-server.d.ts +0 -33
  382. package/dist/src/stripe-server.js +0 -429
  383. package/dist/src/stripe-shared.d.ts +0 -109
  384. package/dist/src/stripe-shared.js +0 -276
  385. package/dist/src/stripe-ui-conformance.d.ts +0 -5
  386. package/dist/src/stripe-ui-conformance.js +0 -79
  387. package/dist/src/stripe-ui-structure.d.ts +0 -3
  388. package/dist/src/stripe-ui-structure.js +0 -168
  389. package/dist/test-fixtures/stripe-known-deviations.json +0 -110
  390. package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
  391. package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
  392. package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
  393. package/dist/test-fixtures/stripe-schemas.json +0 -3813
  394. package/src/screens/connect-settings.tsx +0 -121
  395. package/src/screens/consent-skin.ts +0 -20
  396. package/src/screens/financial-connections.tsx +0 -101
  397. package/src/screens/identity.tsx +0 -96
  398. package/src/screens/public-details.tsx +0 -105
  399. package/src/semantics/after-payment.ts +0 -118
  400. package/src/semantics/billing.ts +0 -216
  401. package/src/semantics/connect.ts +0 -487
  402. package/src/semantics/credit-notes.ts +0 -168
  403. package/src/semantics/ephemeral-keys.ts +0 -34
  404. package/src/semantics/files.ts +0 -143
  405. package/src/semantics/issuing.ts +0 -590
  406. package/src/semantics/ledger.ts +0 -253
  407. package/src/semantics/payment-intents.ts +0 -463
  408. package/src/semantics/payment-links.ts +0 -148
  409. package/src/semantics/payment-methods.ts +0 -145
  410. package/src/semantics/plans.ts +0 -131
  411. package/src/semantics/platform.ts +0 -220
  412. package/src/semantics/renewals.ts +0 -279
  413. package/src/semantics/setup-intents.ts +0 -87
  414. package/src/semantics/subscription-schedules.ts +0 -129
  415. package/src/semantics/tax.ts +0 -220
  416. package/src/semantics/terminal.ts +0 -195
  417. package/src/semantics/tokens.ts +0 -52
  418. package/src/semantics/treasury.ts +0 -383
  419. package/src/semantics/webhook-endpoints.ts +0 -87
  420. package/src/stripe-budget.ts +0 -181
  421. package/src/stripe-capabilities.ts +0 -5651
  422. package/src/stripe-conformance.ts +0 -124
  423. package/src/stripe-connector.ts +0 -462
  424. package/src/stripe-emit.ts +0 -157
  425. package/src/stripe-events.ts +0 -434
  426. package/src/stripe-js.ts +0 -70
  427. package/src/stripe-mirror-ui.ts +0 -90
  428. package/src/stripe-perform-harness.ts +0 -29
  429. package/src/stripe-server.ts +0 -393
  430. package/src/stripe-shared.ts +0 -297
  431. package/src/stripe-ui-conformance.ts +0 -88
  432. package/src/stripe-ui-structure.ts +0 -182
  433. package/test-fixtures/stripe-known-deviations.json +0 -110
  434. package/test-fixtures/stripe-openapi-operations.json +0 -4717
  435. package/test-fixtures/stripe-schemas.json +0 -3813
  436. /package/dist/src/{screens → engine}/industries.d.ts +0 -0
  437. /package/dist/src/{screens → engine}/industries.js +0 -0
  438. /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
  439. /package/dist/src/{semantics → engine}/test-cards.js +0 -0
  440. /package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -0
  441. /package/src/{screens → engine}/industries.ts +0 -0
  442. /package/src/{semantics → engine}/test-cards.ts +0 -0
@@ -9,7 +9,7 @@
9
9
  // connection. The page is @volter/world-ui's consent piece under Stripe's skin; nothing of Stripe's page is copied.
10
10
  //
11
11
  // What the documentation says and the twin keeps:
12
- // - authorize: `client_id` is the platform's (its Connect OAuth settings, ./connect-settings.tsx, where OAuth is enabled
12
+ // - authorize: `client_id` is the platform's (its Connect OAuth settings, ./dashboard.tsx, where OAuth is enabled
13
13
  // and the redirect URIs are registered); `response_type` "The only option at the moment is code"; `redirect_uri`, "If
14
14
  // provided, this must exactly match one of the ... redirect_uri values in your application settings" and "Defaults to
15
15
  // the redirect_uri in your application settings" ("the first URI configured"); `scope` read_write or read_only,
@@ -26,13 +26,13 @@
26
26
  // unsupported_grant_type and invalid_scope as `{ error, error_description }`.
27
27
  // - deauthorize: client_id and stripe_user_id, answered `{ stripe_user_id }`; errors invalid_request and invalid_client
28
28
  // ("stripe_user_id doesn't exist or isn't connected to your application"); afterwards "the account can't be accessed by
29
- // your platform ... through the API" (stripe-server.ts refuses the Stripe-Account header for it, account_invalid).
29
+ // your platform ... through the API" (../engine/front.ts refuses the Stripe-Account header for it, account_invalid).
30
30
  // - events: account.application.authorized when the person connects and account.application.deauthorized when the
31
31
  // connection is revoked, each the connected account's (top-level `account`, Connect endpoints), carrying the
32
32
  // `application` object ({ id: the client_id, object: 'application', name }).
33
33
  //
34
34
  // Where the documentation stops and the twin decides:
35
- // - the twin is a test-mode Stripe: its client_id is a test one (./connect-settings.tsx PLATFORM_CLIENT_ID), redirect
35
+ // - the twin is a test-mode Stripe: its client_id is a test one (./dashboard.tsx PLATFORM_CLIENT_ID), redirect
36
36
  // URIs may be http and localhost ("Your test client_id allows you to ... Set your redirect_uri to a non-HTTPS URL /
37
37
  // to localhost"), a code, token and connection are livemode false, and a live secret key (sk_live_) is refused as the
38
38
  // documented mode mismatch (invalid_grant at token, invalid_client at deauthorize).
@@ -50,50 +50,34 @@
50
50
  // key (sk_…/rk_…) 401 invalid_request too. The token and deauthorize wording other than the one quoted above is the
51
51
  // twin's. An expired code is invalid_grant "Authorization code expired: {code}"; a reused one invalid_grant "This
52
52
  // authorization code has already been used. All tokens issued with this code have been revoked." and revokes.
53
- // - tokens are deterministic, not secret: access_token `sk_test_oauth_{account}_{n}`, refresh_token `rt_twin_{n}`,
54
- // stripe_publishable_key `pk_test_oauth_{account}`; a refresh answers the same refresh_token (`rt_twin_{n}` of the code
55
- // `ac_twin_{n}` that made the connection). The access_token and publishable key are deprecated by Stripe (use the
56
- // Stripe-Account header) and act as the account (stripe-server.ts actingAsOAuthKey); a read_only one is not held to
53
+ // - tokens are drawn from secrets the World holds (ctx.secret), so none is computable from the account's id:
54
+ // access_token `sk_test_oauth_{account}_{secret of its generation}`, refresh_token `rt_{secret of the code}`,
55
+ // stripe_publishable_key `pk_test_oauth_{account}` (publishable, so public); a refresh answers the same refresh_token. The access_token and publishable key are deprecated by Stripe (use the
56
+ // Stripe-Account header) and act as the account (../engine/front.ts actingAsOAuthKey); a read_only one is not held to
57
57
  // reads (manifest todo `stripe.connect.oauth_scope_enforcement`).
58
58
  // - concurrency: a code's number is taken with its write in one move; a code redeemed twice at once lets one exchange
59
59
  // win, and whichever of the two finishes last revokes the connection, so a double exchange always ends revoked.
60
- import { semanticsContext, type SemanticsContext } from '@volter/world-core';
60
+ import type { HandlerContext } from '@volter/world-core';
61
61
  import { Consent, CONSENT_CSS, flowPage } from '@volter/world-ui';
62
- import surface from '../generated/surface.gen.json' with { type: 'json' };
63
- import { manifest } from '../manifest.ts';
64
- import { created, type Row } from '../semantics/shared.ts';
65
- import { OAUTH_CONNECTIONS, publicBusinessName } from '../stripe-shared.ts';
66
- import { accountSettings, afterStripeWrite, PLATFORM_ACCOUNT_ID } from '../stripe-twin.ts';
67
- import { formOf, seeOther, STRIPE_CONSENT_SKIN } from './consent-skin.ts';
68
- import { connectOAuthSettings, PLATFORM_CLIENT_ID, SETTINGS_URL } from './connect-settings.tsx';
62
+ import type { Row } from '../engine/common.ts';
63
+ import { PLATFORM_CLIENT_ID, SETTINGS_URL } from '../engine/connect-settings.ts';
64
+ import { OAUTH_CONNECTIONS, publicBusinessName } from '../engine/display.ts';
65
+ import { redactKey } from '../engine/oauth.ts';
66
+ import { accountSettings, nowUnix, PLATFORM_ACCOUNT_ID } from '../engine/stripe.ts';
67
+ import { formOf, notFound, seeOther, STRIPE_CONSENT_SKIN } from './shared.tsx';
68
+ import { connectOAuthSettings, created, decide } from '../semantics/shared.ts';
69
69
 
70
- const OPERATION = (surface.operations as Array<{ id: string; method: string; path: string; class: string }>).find((o) => o.id === 'PostAccountsAccount')!;
71
70
  /** A code "expires in 5 minutes". */
72
- export const CODE_TTL_SECONDS = 300;
71
+ const CODE_TTL_SECONDS = 300;
73
72
  const CODES = '_oauth_code';
74
73
  /** One connection per connected account (the twin's one platform): its scope, tokens, and whether it was revoked. */
75
- export const CONNECTIONS = OAUTH_CONNECTIONS;
74
+ const CONNECTIONS = OAUTH_CONNECTIONS;
76
75
  const SCOPES = ['read_write', 'read_only'] as const;
77
76
  type Scope = (typeof SCOPES)[number];
78
77
 
79
- type Scoped = { root?: string; clock?: () => string };
80
78
  const json = (body: Row, status = 200): Response => Response.json(body, { status, headers: { 'request-id': 'req_twin', 'cache-control': 'no-store' } });
81
79
  const oauthError = (error: string, description: string, status = 400, extra: Row = {}): Response => json({ error, error_description: description, ...extra }, status);
82
80
 
83
- /** Whether a connected account's OAuth connection to the platform was revoked (deauthorized, or its code reused). */
84
- export function connectionRevoked(ctx: SemanticsContext, account: string): boolean {
85
- return ctx.rowsRaw(CONNECTIONS).some((c) => c.id === account && c.revoked === true);
86
- }
87
-
88
- /** The connected account an OAuth key acts as — the connection's current access_token (sk_test_oauth_…) or its
89
- * stripe_publishable_key (pk_test_oauth_…) — with the scope it was issued; undefined for any other key, and null for an
90
- * OAuth-shaped key no live connection holds (revoked, replaced by a refresh, or never issued). */
91
- export function oauthKeyAccount(ctx: SemanticsContext, key: string): { account: string; scope: string } | null | undefined {
92
- if (!/^(sk|pk)_test_oauth_/.test(key)) return undefined;
93
- const held = ctx.rowsRaw(CONNECTIONS).find((c) => c.revoked !== true && (c.access_token === key || c.publishable_key === key));
94
- return held ? { account: String(held.id), scope: String(key.startsWith('pk_') ? held.scope : held.access_scope ?? held.scope) } : null;
95
- }
96
-
97
81
  // ── authorize ──
98
82
 
99
83
  /** A query parameter, or undefined when absent (an empty value is absent too, as a browser form sends one). */
@@ -102,7 +86,7 @@ const param = (q: URLSearchParams, k: string): string | undefined => { const v =
102
86
  type Authorize = { redirect: string; scope: Scope; state: string | undefined };
103
87
 
104
88
  /** The authorize request checked as Stripe checks it, or the JSON error it answers. */
105
- function checked(ctx: SemanticsContext, q: URLSearchParams): Authorize | Response {
89
+ function checked(ctx: HandlerContext, q: URLSearchParams): Authorize | Response {
106
90
  const state = param(q, 'state');
107
91
  const fail = (error: string, description: string) => oauthError(error, description, 400, state !== undefined ? { state } : {});
108
92
  const client = param(q, 'client_id');
@@ -137,7 +121,7 @@ function withParams(uri: string, params: Array<[string, string | undefined]>): s
137
121
  /** The prefill the request carries, invalid values silently dropped (docs.stripe.com/connect/oauth-reference). Only
138
122
  * what the new account's object shows the platform, or the page shows the person, is read: the person's birth date and
139
123
  * the business address are the account application's, which the twin does not model (the test-mode skip). */
140
- export function prefill(q: URLSearchParams): Record<string, string> {
124
+ function prefill(q: URLSearchParams): Record<string, string> {
141
125
  const v = (k: string) => param(q, `stripe_user[${k}]`)?.trim() || undefined;
142
126
  const out: Record<string, string> = {};
143
127
  const keep = (k: string, ok: (s: string) => boolean = () => true) => { const s = v(k); if (s !== undefined && ok(s)) out[k] = s; };
@@ -160,7 +144,7 @@ const COUNTRY_CURRENCY: Record<string, string> = { US: 'usd', CA: 'cad', GB: 'gb
160
144
  const BUSINESS_TYPE: Record<string, string> = { sole_prop: 'individual', corporation: 'company', llc: 'company', partnership: 'company', non_profit: 'non_profit' };
161
145
 
162
146
  /** The Standard account the skipped form creates, from the prefill. */
163
- async function createAccount(ctx: SemanticsContext, p: Record<string, string>): Promise<string> {
147
+ async function createAccount(ctx: HandlerContext, p: Record<string, string>): Promise<string> {
164
148
  const country = p.country ?? 'US';
165
149
  const id = ctx.mint('account');
166
150
  const none = { alternatives: [], current_deadline: null, currently_due: [], disabled_reason: null, errors: [], eventually_due: [], past_due: [], pending_verification: [] };
@@ -180,20 +164,17 @@ async function createAccount(ctx: SemanticsContext, p: Record<string, string>):
180
164
  }
181
165
 
182
166
  /** The platform's application as the account.application.* events carry it. */
183
- function application(ctx: SemanticsContext): Row {
167
+ function application(ctx: HandlerContext): Row {
184
168
  return { id: PLATFORM_CLIENT_ID, object: 'application', name: publicBusinessName(ctx.get('account', PLATFORM_ACCOUNT_ID)) };
185
169
  }
186
170
 
187
- /** Send one of the account.application.* events as the connected account's. */
188
- async function applicationEvent(ctx: SemanticsContext, type: 'account.application.authorized' | 'account.application.deauthorized', account: string): Promise<void> {
189
- await afterStripeWrite('application', type, application(ctx), ctx.root, ctx.occurredAt, undefined, account);
171
+ /** Send one of the account.application.* events as the connected account's: the notice it is sent for (the kernel
172
+ * sends it, ../semantics/events.ts renders it). */
173
+ async function applicationEvent(ctx: HandlerContext, type: 'account.application.authorized' | 'account.application.deauthorized', account: string): Promise<void> {
174
+ await ctx.write('_application_notice', `${account}:${type}:${ctx.occurredAt}`, { account, application: application(ctx) }, type);
190
175
  }
191
176
 
192
- /** A code's number: `ac_twin_{n}`, the refresh token its exchange mints being `rt_twin_{n}`, so the one follows the
193
- * other and neither is counted separately. */
194
- const codeNumber = (code: string): string => code.replace(/^ac_twin_/, '');
195
-
196
- function page(ctx: SemanticsContext, action: string, a: Authorize, p: Record<string, string>): Response {
177
+ function page(ctx: HandlerContext, action: string, a: Authorize, p: Record<string, string>): Response {
197
178
  const name = publicBusinessName(ctx.get('account', PLATFORM_ACCOUNT_ID));
198
179
  const who = [p.email, [p.first_name, p.last_name].filter(Boolean).join(' '), p.business_name].filter((s) => s).join(' · ');
199
180
  return flowPage({
@@ -221,22 +202,20 @@ function page(ctx: SemanticsContext, action: string, a: Authorize, p: Record<str
221
202
  }
222
203
 
223
204
  /** The person connects: a new Standard account, its connection's code, the authorized event, and the redirect. */
224
- async function connect(ctx: SemanticsContext, a: Authorize, p: Record<string, string>): Promise<Response> {
205
+ async function connect(ctx: HandlerContext, a: Authorize, p: Record<string, string>): Promise<Response> {
225
206
  const account = await createAccount(ctx, p);
226
- // the code's number is taken and its row written in one move, so two people connecting at once get two codes
227
- const fields = { client_id: PLATFORM_CLIENT_ID, account, scope: a.scope, redirect_uri: a.redirect, expires_at: Number(ctx.now()) + CODE_TTL_SECONDS, used: false, livemode: false };
228
- const code = await ctx.atomically((rows) => {
229
- const id = `ac_twin_${rows(CODES).length + 1}`;
230
- return { value: id, write: { resource: CODES, id, fields, operation: 'oauth_code.issue' } };
231
- });
207
+ // the code's row id is minted and its value drawn from the World's secret; the row keeps the value's SHA-256 alone
208
+ const id = ctx.mint(CODES);
209
+ const code = `ac_${(await ctx.secret(`stripe-oauth-code:${id}`)).replace(/[^A-Za-z0-9]/g, '').slice(0, 24)}`;
210
+ await ctx.record(CODES, { client_id: PLATFORM_CLIENT_ID, account, scope: a.scope, redirect_uri: a.redirect, expires_at: nowUnix(ctx.occurredAt) + CODE_TTL_SECONDS, used: false, livemode: false, sha256: ctx.crypto.sha256(code) }, id);
232
211
  await applicationEvent(ctx, 'account.application.authorized', account);
233
212
  return seeOther(withParams(a.redirect, [['scope', a.scope], ['code', code], ['state', a.state]]));
234
213
  }
235
214
 
236
- async function authorize(request: Request, scope: Scoped): Promise<Response> {
215
+ async function authorize(ctx: HandlerContext): Promise<Response> {
216
+ const request = ctx.call.request;
237
217
  const url = new URL(request.url);
238
218
  const form = request.method === 'POST' ? await formOf(request.clone()) : {};
239
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
240
219
  const a = checked(ctx, url.searchParams);
241
220
  if (a instanceof Response) return a;
242
221
  const p = prefill(url.searchParams);
@@ -267,13 +246,6 @@ function secretKey(request: Request, params: Record<string, string>): string | u
267
246
  return params.client_secret || undefined;
268
247
  }
269
248
 
270
- /** A key shown the way Stripe's errors show one: its prefix and its last four. */
271
- export const redactKey = (key: string): string => {
272
- const prefix = /^(sk|rk|pk)_(test|live)_/.exec(key)?.[0] ?? '';
273
- const rest = key.slice(prefix.length);
274
- return rest.length > 4 ? `${prefix}${'*'.repeat(rest.length - 4)}${rest.slice(-4)}` : `${prefix}${'*'.repeat(rest.length)}`;
275
- };
276
-
277
249
  /** The refusal of a request without a usable secret key, or undefined when it has one (answered with whether it is live). */
278
250
  function keyRefused(key: string | undefined): Response | undefined {
279
251
  if (!key) return oauthError('invalid_request', 'No API key provided. Provide your secret key in the Authorization header, using Bearer auth (e.g. \'Authorization: Bearer YOUR_SECRET_KEY\').', 401);
@@ -286,26 +258,29 @@ function keyRefused(key: string | undefined): Response | undefined {
286
258
  const live = (key: string): boolean => /^(sk|rk)_live_/.test(key);
287
259
 
288
260
  /** A new access token for a connection: its generation counted from the ones it had. */
289
- const accessToken = (account: string, n: number): string => `sk_test_oauth_${account}_${n}`;
261
+ /** A connection's access token of one generation: its account, then a digest of the connection's secret and the
262
+ * generation (the secret read before the atomic move that numbers the generation). */
263
+ // p3: keyed by the connection's secret, drawn from ctx.secret
264
+ const accessToken = (ctx: Pick<HandlerContext, 'crypto'>, secret: string, account: string, n: number): string => `sk_test_oauth_${account}_${ctx.crypto.digest('sha256', `${secret}:${n}`, 'base64url').replace(/[-_]/g, '').slice(0, 24)}`;
290
265
 
291
266
  /** Revoke an account's connection, once: the check and the write are one move, and only the move that revoked it sends
292
267
  * account.application.deauthorized. Answers whether this call revoked it. */
293
- async function revoke(ctx: SemanticsContext, account: string): Promise<boolean> {
294
- const revoked = await ctx.atomically((rows) => {
268
+ async function revoke(ctx: HandlerContext, account: string): Promise<boolean> {
269
+ const revoked = await decide(ctx, (rows) => {
295
270
  const held = rows(CONNECTIONS).find((c) => c.id === account);
296
271
  if (!held || held.revoked === true) return { value: false };
297
- return { value: true, write: { resource: CONNECTIONS, id: account, fields: { revoked: true, access_token: null, revoked_at: Number(ctx.now()) }, operation: 'oauth_connection.revoke' } };
272
+ return { value: true, write: { resource: CONNECTIONS, id: account, fields: { revoked: true, access_token: null, revoked_at: nowUnix(ctx.occurredAt) }, operation: 'oauth_connection.revoke' } };
298
273
  });
299
274
  if (revoked) await applicationEvent(ctx, 'account.application.deauthorized', account);
300
275
  return revoked;
301
276
  }
302
277
 
303
- async function token(request: Request, scope: Scoped): Promise<Response> {
278
+ async function token(ctx: HandlerContext): Promise<Response> {
279
+ const request = ctx.call.request;
304
280
  const params = await bodyOf(request);
305
281
  const key = secretKey(request, params);
306
282
  const refused = keyRefused(key);
307
283
  if (refused) return refused;
308
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
309
284
  const grant = params.grant_type;
310
285
  if (!grant) return oauthError('invalid_request', 'No grant type specified');
311
286
  if (grant !== 'authorization_code' && grant !== 'refresh_token') return oauthError('unsupported_grant_type', `Unsupported grant type: ${grant}. The only currently supported types are authorization_code and refresh_token.`);
@@ -315,13 +290,13 @@ async function token(request: Request, scope: Scoped): Promise<Response> {
315
290
  if (live(key!)) return oauthError('invalid_grant', `Authorization code provided does not belong to the livemode of the API key: ${code} is a test mode code, but a live mode key was used.`);
316
291
  // a code is spent once: the read and the spend are one move, so two exchanges at once cannot both succeed
317
292
  type Redeemed = { outcome: 'missing' } | { outcome: 'used' | 'expired' | 'ok'; row: Row };
318
- const found = await ctx.atomically<Redeemed>((rows) => {
319
- const row = rows(CODES).find((r) => r.id === code);
293
+ const found = await decide<Redeemed>(ctx, (rows) => {
294
+ const row = rows(CODES).find((r) => r.sha256 === ctx.crypto.sha256(code));
320
295
  if (!row) return { value: { outcome: 'missing' } };
321
296
  // a second use is marked in the same move, so the exchange that spent it sees it after writing its connection
322
- if (row.used === true) return { value: { outcome: 'used', row }, write: { resource: CODES, id: code, fields: { reused: true }, operation: 'oauth_code.reuse' } };
323
- if (Number(ctx.now()) >= Number(row.expires_at)) return { value: { outcome: 'expired', row } };
324
- return { value: { outcome: 'ok', row }, write: { resource: CODES, id: code, fields: { used: true, used_at: Number(ctx.now()) }, operation: 'oauth_code.redeem' } };
297
+ if (row.used === true) return { value: { outcome: 'used', row }, write: { resource: CODES, id: String(row.id), fields: { reused: true }, operation: 'oauth_code.reuse' } };
298
+ if (nowUnix(ctx.occurredAt) >= Number(row.expires_at)) return { value: { outcome: 'expired', row } };
299
+ return { value: { outcome: 'ok', row }, write: { resource: CODES, id: String(row.id), fields: { used: true, used_at: nowUnix(ctx.occurredAt) }, operation: 'oauth_code.redeem' } };
325
300
  });
326
301
  if (found.outcome === 'missing') return oauthError('invalid_grant', `Authorization code does not exist: ${code}`);
327
302
  const account = String(found.row.account);
@@ -332,11 +307,12 @@ async function token(request: Request, scope: Scoped): Promise<Response> {
332
307
  }
333
308
  if (found.outcome === 'expired') return oauthError('invalid_grant', `Authorization code expired: ${code}`);
334
309
  const granted = String(found.row.scope) as Scope;
335
- const refresh = `rt_twin_${codeNumber(code)}`;
336
- const connection = { client_id: PLATFORM_CLIENT_ID, account, scope: granted, code, refresh_token: refresh, access_token: accessToken(account, 1), access_scope: granted, generation: 1, publishable_key: `pk_test_oauth_${account}`, revoked: false, livemode: false };
310
+ const refresh = `rt_${(await ctx.secret(`stripe-oauth-refresh:${String(found.row.id)}`)).replace(/[-_]/g, '').slice(0, 32)}`;
311
+ const secret = await ctx.secret(`stripe-oauth-access:${account}`);
312
+ const connection = { client_id: PLATFORM_CLIENT_ID, account, scope: granted, code: found.row.id, refresh_token: refresh, access_token: accessToken(ctx, secret, account, 1), access_scope: granted, generation: 1, publishable_key: `pk_test_oauth_${account}`, revoked: false, livemode: false };
337
313
  await ctx.record(CONNECTIONS, connection, account);
338
314
  // the code used again while this exchange was writing: that use found no connection to revoke, so this one does
339
- if (ctx.rowsRaw(CODES).some((r) => r.id === code && r.reused === true)) {
315
+ if (ctx.rowsRaw(CODES).some((r) => r.id === found.row.id && r.reused === true)) {
340
316
  await revoke(ctx, account);
341
317
  return oauthError('invalid_grant', 'This authorization code has already been used. All tokens issued with this code have been revoked.');
342
318
  }
@@ -354,11 +330,12 @@ async function token(request: Request, scope: Scoped): Promise<Response> {
354
330
  // "Any existing access token with the same scope and mode ... is revoked": the connection holds the one now issued; the
355
331
  // generation is read and written in one move, so two refreshes at once issue two tokens, and a refresh never lands
356
332
  // on a connection revoked meanwhile
357
- const renewed = await ctx.atomically((rows) => {
333
+ const secret = await ctx.secret(`stripe-oauth-access:${account}`);
334
+ const renewed = await decide(ctx, (rows) => {
358
335
  const now = rows(CONNECTIONS).find((c) => c.id === account && c.revoked !== true);
359
336
  if (!now) return { value: undefined };
360
337
  const generation = Number(now.generation ?? 1) + 1;
361
- const fields = { access_token: accessToken(account, generation), access_scope: asked, generation };
338
+ const fields = { access_token: accessToken(ctx, secret, account, generation), access_scope: asked, generation };
362
339
  return { value: { ...now, ...fields }, write: { resource: CONNECTIONS, id: account, fields, operation: 'oauth_connection.refresh' } };
363
340
  });
364
341
  if (!renewed) return oauthError('invalid_grant', `Refresh token does not exist: ${refresh}`);
@@ -370,12 +347,12 @@ function tokenBody(c: Row, scope: Scope): Row {
370
347
  return { access_token: c.access_token, livemode: false, refresh_token: c.refresh_token, scope, stripe_publishable_key: c.publishable_key, stripe_user_id: c.account ?? c.id, token_type: 'bearer' };
371
348
  }
372
349
 
373
- async function deauthorize(request: Request, scope: Scoped): Promise<Response> {
350
+ async function deauthorize(ctx: HandlerContext): Promise<Response> {
351
+ const request = ctx.call.request;
374
352
  const params = await bodyOf(request);
375
353
  const key = secretKey(request, params);
376
354
  const refused = keyRefused(key);
377
355
  if (refused) return refused;
378
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
379
356
  const client = params.client_id;
380
357
  const account = params.stripe_user_id;
381
358
  if (!client) return oauthError('invalid_request', 'No client_id provided');
@@ -388,13 +365,12 @@ async function deauthorize(request: Request, scope: Scoped): Promise<Response> {
388
365
  return json({ stripe_user_id: account });
389
366
  }
390
367
 
391
- /** connect.stripe.com's OAuth endpoints, or undefined for any other request. */
392
- export function stripeConnectOAuthFlow(scope: Scoped): (request: Request) => Promise<Response | undefined> {
393
- return async (request) => {
394
- const path = new URL(request.url).pathname.replace(/\/+$/, '');
395
- if (path === '/oauth/authorize' && (request.method === 'GET' || request.method === 'POST')) return authorize(request, scope);
396
- if (path === '/oauth/token' && request.method === 'POST') return token(request, scope);
397
- if (path === '/oauth/deauthorize' && request.method === 'POST') return deauthorize(request, scope);
398
- return undefined;
399
- };
368
+ /** connect.stripe.com's OAuth endpoints; Stripe's 404 for anything else there. */
369
+ export async function screen(ctx: HandlerContext): Promise<Response> {
370
+ const request = ctx.call.request;
371
+ const path = new URL(request.url).pathname.replace(/\/+$/, '');
372
+ if (path === '/oauth/authorize' && (request.method === 'GET' || request.method === 'POST')) return authorize(ctx);
373
+ if (path === '/oauth/token' && request.method === 'POST') return token(ctx);
374
+ if (path === '/oauth/deauthorize' && request.method === 'POST') return deauthorize(ctx);
375
+ return notFound();
400
376
  }
@@ -23,25 +23,25 @@
23
23
  // merchant category codes as its list gives them (./industries.ts), and an industry the page does not offer leaves
24
24
  // business_profile.mcc due; the states are the fifty and the District of Columbia; a request with no forwarded address
25
25
  // records 127.0.0.1. An update link (account_update) shows the same page.
26
- import { semanticsContext, type SemanticsContext } from '@volter/world-core';
26
+ import type { HandlerContext } from '@volter/world-core';
27
27
  import { CONSENT_CSS, flowPage, Payment, PAYMENT_CSS, type PaymentField } from '@volter/world-ui';
28
- import surface from '../generated/surface.gen.json' with { type: 'json' };
29
- import { manifest } from '../manifest.ts';
30
- import { created, syncExternals, type Row } from '../semantics/shared.ts';
31
- import { formOf, seeOther, STRIPE_CONSENT_SKIN } from './consent-skin.ts';
32
- import { INDUSTRIES as MCCS } from './industries.ts';
28
+ import type { Row } from '../engine/common.ts';
29
+ import { nowUnix } from '../engine/stripe.ts';
30
+ import { formOf, notFound, seeOther, STRIPE_CONSENT_SKIN } from './shared.tsx';
31
+ import { INDUSTRIES as MCCS } from '../engine/industries.ts';
32
+ import { currentlyDue, type Form, type Kind, kindOf, OFFERED } from '../engine/onboarding.ts';
33
+ import { created, syncExternals } from '../semantics/shared.ts';
33
34
 
34
35
  const LINK = 'account_link';
35
- const OPERATION = (surface.operations as Array<{ id: string; method: string; path: string; class: string }>).find((o) => o.id === 'PostAccountsAccount')!;
36
+ const OPERATION = { id: 'PostAccountsAccount' };
36
37
 
37
38
  const gone = (): Response => flowPage({ title: 'Stripe', status: 404, css: [CONSENT_CSS, STRIPE_CONSENT_SKIN], body: <main className="consent"><h1 className="consent-heading">This link is invalid.</h1></main> });
38
39
 
39
40
  /** A link that can still be used: not used and not past its expiry. */
40
- const usable = (ctx: SemanticsContext, link: Row): boolean => link.used !== true && Number(link.expires_at) > Number(ctx.now());
41
+ const usable = (ctx: HandlerContext, link: Row): boolean => link.used !== true && Number(link.expires_at) > nowUnix(ctx.occurredAt);
41
42
 
42
43
  // the field asks, with no industry chosen until the person chooses one (an unanswered field leaves the MCC due)
43
44
  const INDUSTRIES = [{ value: '', label: 'Select your industry' }, ...MCCS.map(([value, label]) => ({ value, label }))];
44
- const OFFERED = new Set(MCCS.map(([mcc]) => mcc));
45
45
  // the fifty states and the District of Columbia, by their USPS codes (the address's `state`, ISO 3166-2 without its
46
46
  // country, docs.stripe.com/api/accounts/object)
47
47
  const STATES = ([
@@ -55,49 +55,6 @@ const STATES = ([
55
55
  ['WA', 'Washington'], ['WV', 'West Virginia'], ['WI', 'Wisconsin'], ['WY', 'Wyoming'],
56
56
  ] as const).map(([value, label]) => ({ value, label }));
57
57
 
58
- type Form = Record<string, string>;
59
- type Kind = 'company' | 'individual';
60
- const has = (v: Form, k: string): boolean => (v[k] ?? '').trim() !== '';
61
- const dobPart = (v: Form, i: number): boolean => /^\d{4}-\d{2}-\d{2}$/.test(v.dob ?? '') && Number((v.dob ?? '').split('-')[i]) > 0;
62
-
63
- /** Each requirement Stripe names, the form's answer that meets it, and which of the two sets it belongs to. */
64
- const REQUIREMENTS: Array<[string, (v: Form) => boolean, Kind[]]> = [
65
- ['business_profile.mcc', (v) => OFFERED.has((v.mcc ?? '').trim()), ['company', 'individual']],
66
- ['business_profile.url', (v) => has(v, 'url'), ['company', 'individual']],
67
- ['settings.payments.statement_descriptor', (v) => has(v, 'statement_descriptor'), ['company', 'individual']],
68
- ['company.name', (v) => has(v, 'company_name'), ['company']],
69
- ['company.tax_id', (v) => has(v, 'company_tax_id'), ['company']],
70
- ['company.phone', (v) => has(v, 'company_phone'), ['company']],
71
- ['company.address.line1', (v) => has(v, 'company_line1'), ['company']],
72
- ['company.address.city', (v) => has(v, 'company_city'), ['company']],
73
- ['company.address.state', (v) => has(v, 'company_state'), ['company']],
74
- ['company.address.postal_code', (v) => has(v, 'company_postal_code'), ['company']],
75
- ['company.owners_provided', (v) => v.owners_provided === 'provided', ['company']],
76
- ['owners.first_name', (v) => has(v, 'owner_first_name'), ['company']],
77
- ['owners.last_name', (v) => has(v, 'owner_last_name'), ['company']],
78
- ['owners.email', (v) => has(v, 'owner_email'), ['company']],
79
- ...(['first_name', 'last_name', 'email', 'phone'] as const).flatMap((f): Array<[string, (v: Form) => boolean, Kind[]]> => [[`representative.${f}`, (v) => has(v, f), ['company']], [`individual.${f}`, (v) => has(v, f), ['individual']]]),
80
- ...(['day', 'month', 'year'] as const).flatMap((f, i): Array<[string, (v: Form) => boolean, Kind[]]> => [[`representative.dob.${f}`, (v) => dobPart(v, 2 - i), ['company']], [`individual.dob.${f}`, (v) => dobPart(v, 2 - i), ['individual']]]),
81
- ...(['line1', 'city', 'state', 'postal_code'] as const).flatMap((f): Array<[string, (v: Form) => boolean, Kind[]]> => [[`representative.address.${f}`, (v) => has(v, f), ['company']], [`individual.address.${f}`, (v) => has(v, f), ['individual']]]),
82
- ['representative.relationship.title', (v) => has(v, 'title'), ['company']],
83
- ['representative.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['company']],
84
- ['individual.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['individual']],
85
- ['tos_acceptance.date', (v) => v.tos === 'accepted', ['company', 'individual']],
86
- ['tos_acceptance.ip', (v) => v.tos === 'accepted', ['company', 'individual']],
87
- ['external_account', (v) => has(v, 'routing_number') && has(v, 'account_number'), ['company', 'individual']],
88
- ];
89
-
90
- /** The set an account onboards against: the two US sets the twin models, or none. */
91
- export function kindOf(account: Row): Kind | undefined {
92
- const type = account.business_type ?? 'individual';
93
- return (account.country ?? 'US') === 'US' && (type === 'company' || type === 'individual') ? type : undefined;
94
- }
95
-
96
- /** What an account of this kind still owes, given the form's answers (none: all of it). */
97
- export function currentlyDue(kind: Kind, v: Form = {}): string[] {
98
- return REQUIREMENTS.filter(([, met, kinds]) => kinds.includes(kind) && !met(v)).map(([req]) => req);
99
- }
100
-
101
58
  const field = (id: string, label: string, v: Form, extra: Partial<PaymentField> = {}): PaymentField => ({ id, label, value: v[id] ?? '', ...extra });
102
59
  const addressFields = (prefix: string, v: Form): PaymentField[] => [
103
60
  field(`${prefix}line1`, 'Address', v, { autoComplete: 'address-line1' }), field(`${prefix}city`, 'City', v),
@@ -107,7 +64,7 @@ const addressFields = (prefix: string, v: Form): PaymentField[] => [
107
64
  /** The industry the field shows: what the form sent, else what the account already holds, when the page offers it. */
108
65
  const industryOf = (mcc: unknown): string => (typeof mcc === 'string' && OFFERED.has(mcc.trim()) ? mcc.trim() : '');
109
66
 
110
- function page(ctx: SemanticsContext, sessionUrl: string, link: Row, kind: Kind, values: Form = {}, error?: string): Response {
67
+ function page(ctx: HandlerContext, sessionUrl: string, link: Row, kind: Kind, values: Form = {}, error?: string): Response {
111
68
  const v = values;
112
69
  const business: PaymentField[] = [
113
70
  field('mcc', 'Industry', v, { options: INDUSTRIES, value: industryOf(v.mcc ?? (ctx.get('account', String(link.account))?.business_profile as Row | undefined)?.mcc) }),
@@ -157,13 +114,13 @@ function unmodelled(account: Row): Response {
157
114
  }
158
115
 
159
116
  /** A submit that leaves something out is refused on the page with what is missing, the owner's answers kept. */
160
- function incomplete(ctx: SemanticsContext, sessionUrl: string, link: Row, kind: Kind, v: Form, missing: string[]): Response {
117
+ function incomplete(ctx: HandlerContext, sessionUrl: string, link: Row, kind: Kind, v: Form, missing: string[]): Response {
161
118
  return page(ctx, sessionUrl, link, kind, v, `Still required: ${missing.join(', ')}.`);
162
119
  }
163
120
 
164
121
  /** The owner submits everything: it is stored on the account, nothing is due, and charges, payouts and the requested
165
122
  * capabilities are enabled. */
166
- async function submit(ctx: SemanticsContext, link: Row, kind: Kind, v: Form, ip: string): Promise<void> {
123
+ async function submit(ctx: HandlerContext, link: Row, kind: Kind, v: Form, ip: string): Promise<void> {
167
124
  const id = String(link.account);
168
125
  const account = ctx.get('account', id);
169
126
  if (!account) return;
@@ -186,7 +143,7 @@ async function submit(ctx: SemanticsContext, link: Row, kind: Kind, v: Form, ip:
186
143
  ...(company
187
144
  ? { company: { name: v.company_name, phone: v.company_phone, address: addressOf('company_'), tax_id_provided: true, owners_provided: true, directors_provided: false, executives_provided: false } }
188
145
  : { individual: { ...person, id: `person_${id}` } }),
189
- tos_acceptance: { date: Number(ctx.now()), ip, user_agent: null },
146
+ tos_acceptance: { date: nowUnix(ctx.occurredAt), ip, user_agent: null },
190
147
  }, 'account.updated');
191
148
  if (company) {
192
149
  await created(ctx, 'person', { account: id }, { ...person, relationship: { representative: true, executive: true, director: false, owner: false, percent_ownership: null, title: v.title }, metadata: {} });
@@ -205,7 +162,7 @@ const SESSIONS = 'https://connect.stripe.com/setup/s/';
205
162
 
206
163
  /** Visiting an Account Link consumes it and sends the owner to their onboarding session; a used or expired one sends
207
164
  * them to refresh_url for a fresh link. */
208
- async function visit(ctx: SemanticsContext, id: string): Promise<Response> {
165
+ async function visit(ctx: HandlerContext, id: string): Promise<Response> {
209
166
  const link = ctx.get(LINK, id);
210
167
  if (!link) return gone();
211
168
  if (!usable(ctx, link)) return seeOther(String(link.refresh_url));
@@ -215,29 +172,28 @@ async function visit(ctx: SemanticsContext, id: string): Promise<Response> {
215
172
  }
216
173
 
217
174
  /** connect.stripe.com's onboarding pages, or undefined for any other request. */
218
- export function stripeOnboardingFlow(scope: { root?: string; clock?: () => string }): (request: Request) => Promise<Response | undefined> {
219
- return async (request) => {
220
- const path = new URL(request.url).pathname;
221
- const entry = /^\/setup\/(acctlink_[A-Za-z0-9_]+)\/?$/.exec(path);
222
- const at = /^\/setup\/s\/(onbs_[A-Za-z0-9_]+)\/?$/.exec(path);
223
- if (entry && request.method === 'GET') return visit(await semanticsContext(manifest, request, OPERATION, scope), entry[1]!);
224
- if (!at || (request.method !== 'GET' && request.method !== 'POST')) return undefined;
225
- const sid = at[1]!;
226
- const form = request.method === 'POST' ? await formOf(request.clone()) : {};
227
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
228
- // the session the visited link opened, until it is submitted
229
- const stored = ctx.rowsRaw(LINK).find((l) => l._session === sid && l._submitted !== true);
230
- const link = stored ? ctx.get(LINK, String(stored.id)) : undefined;
231
- if (!stored || !link) return gone();
232
- const sessionUrl = `${SESSIONS}${sid}`;
233
- const account = ctx.get('account', String(link.account)) ?? {};
234
- const kind = kindOf(account);
235
- if (!kind) return unmodelled(account);
236
- if (request.method === 'GET') return page(ctx, sessionUrl, link, kind);
237
- const missing = currentlyDue(kind, form);
238
- if (missing.length) return incomplete(ctx, sessionUrl, link, kind, form, missing);
239
- await ctx.write(LINK, String(stored.id), { _submitted: true }, 'account_link.submitted');
240
- await submit(ctx, link, kind, form, request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1');
241
- return seeOther(String(link.return_url));
242
- };
175
+ export async function screen(ctx: HandlerContext): Promise<Response> {
176
+ const request = ctx.call.request;
177
+ const path = new URL(request.url).pathname;
178
+ const entry = /^\/setup\/(acctlink_[A-Za-z0-9_]+)\/?$/.exec(path);
179
+ const at = /^\/setup\/s\/(onbs_[A-Za-z0-9_]+)\/?$/.exec(path);
180
+ if (entry && request.method === 'GET') return visit(ctx, entry[1]!);
181
+ if (!at || (request.method !== 'GET' && request.method !== 'POST')) return notFound();
182
+ const sid = at[1]!;
183
+ const form = request.method === 'POST' ? await formOf(request.clone()) : {};
184
+ // the session the visited link opened, until it is submitted
185
+ const stored = ctx.rowsRaw(LINK).find((l) => l._session === sid && l._submitted !== true);
186
+ const link = stored ? ctx.get(LINK, String(stored.id)) : undefined;
187
+ if (!stored || !link) return gone();
188
+ const sessionUrl = `${SESSIONS}${sid}`;
189
+ const account = ctx.get('account', String(link.account)) ?? {};
190
+ const kind = kindOf(account);
191
+ if (!kind) return unmodelled(account);
192
+ if (request.method === 'GET') return page(ctx, sessionUrl, link, kind);
193
+ const missing = currentlyDue(kind, form);
194
+ if (missing.length) return incomplete(ctx, sessionUrl, link, kind, form, missing);
195
+ await ctx.write(LINK, String(stored.id), { _submitted: true }, 'account_link.submitted');
196
+ await submit(ctx, link, kind, form, request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1');
197
+ return seeOther(String(link.return_url));
243
198
  }
199
+