@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,94 +1,19 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // STRIPE CONNECT OAUTH (Standard accounts) — a hosted flow (docs/contributing/architecture.md, "Screens") and the two
3
- // connect.stripe.com endpoints behind it, as docs.stripe.com/connect/oauth-reference and
4
- // docs.stripe.com/connect/oauth-standard-accounts describe them. A platform sends a person to
5
- // GET connect.stripe.com/oauth/authorize with its client_id; the person connects (in test mode Stripe lets them "Force-skip
6
- // the account form", docs.stripe.com/connect/testing#using-oauth) or denies; Stripe sends them back to the redirect_uri
7
- // with a `code`, the `scope` granted and the `state` (or `error=access_denied`); the platform's server turns the code into
8
- // the connection with POST connect.stripe.com/oauth/token, authenticated by its secret key, and learns the account id
9
- // (`stripe_user_id`) it then acts as with the Stripe-Account header. POST connect.stripe.com/oauth/deauthorize revokes the
10
- // connection. The page is @volter/world-ui's consent piece under Stripe's skin; nothing of Stripe's page is copied.
11
- //
12
- // What the documentation says and the twin keeps:
13
- // - authorize: `client_id` is the platform's (its Connect OAuth settings, ./connect-settings.tsx, where OAuth is enabled
14
- // and the redirect URIs are registered); `response_type` "The only option at the moment is code"; `redirect_uri`, "If
15
- // provided, this must exactly match one of the ... redirect_uri values in your application settings" and "Defaults to
16
- // the redirect_uri in your application settings" ("the first URI configured"); `scope` read_write or read_only,
17
- // "Defaults to read_only"; `state` passed back. `stripe_user[...]` prefills the new account, and "Any parameters with
18
- // invalid values are silently ignored". Errors: "the user's browser won't be redirected except in the case of
19
- // access_denied. Instead, errors will be returned in a JSON dictionary" of error, error_description and state, with
20
- // the codes invalid_scope, invalid_redirect_uri, invalid_request (Missing response_type) and unsupported_response_type.
21
- // Denied: `error=access_denied&error_description=The%20user%20denied%20your%20request`. Success: code, scope, state.
22
- // - token: the code "can only be used once and expires in 5 minutes"; "Consuming an authorization code more than once
23
- // revokes the account connection"; grant_type authorization_code or refresh_token; a refresh `scope` of "equal or lesser
24
- // scope" and "Any existing access token with the same scope and mode ... is revoked"; the response's scope,
25
- // stripe_user_id, livemode, token_type "bearer", access_token, stripe_publishable_key and refresh_token; errors
26
- // invalid_request, invalid_grant ("Authorization code does not exist: {AUTHORIZATION_CODE}", verbatim from the guide),
27
- // unsupported_grant_type and invalid_scope as `{ error, error_description }`.
28
- // - deauthorize: client_id and stripe_user_id, answered `{ stripe_user_id }`; errors invalid_request and invalid_client
29
- // ("stripe_user_id doesn't exist or isn't connected to your application"); afterwards "the account can't be accessed by
30
- // your platform ... through the API" (stripe-server.ts refuses the Stripe-Account header for it, account_invalid).
31
- // - events: account.application.authorized when the person connects and account.application.deauthorized when the
32
- // connection is revoked, each the connected account's (top-level `account`, Connect endpoints), carrying the
33
- // `application` object ({ id: the client_id, object: 'application', name }).
34
- //
35
- // Where the documentation stops and the twin decides:
36
- // - the twin is a test-mode Stripe: its client_id is a test one (./connect-settings.tsx PLATFORM_CLIENT_ID), redirect
37
- // URIs may be http and localhost ("Your test client_id allows you to ... Set your redirect_uri to a non-HTTPS URL /
38
- // to localhost"), a code, token and connection are livemode false, and a live secret key (sk_live_) is refused as the
39
- // documented mode mismatch (invalid_grant at token, invalid_client at deauthorize).
40
- // - the only way through the page is the test-mode skip: it creates a NEW Standard account from the prefill and connects
41
- // it; logging in to connect an existing Stripe account is not modelled. A skipped test account can take payments at
42
- // once (charges and payouts enabled, card_payments and transfers active, nothing due), and controls itself (controller
43
- // type `account`), since the platform did not create it.
44
- // - an unknown client_id answers invalid_client "No application matches the supplied client identifier", and a
45
- // platform that has not enabled OAuth invalid_client naming its settings page; a missing client_id is invalid_request.
46
- // The JSON errors are HTTP 400; the order of the checks is client_id, OAuth enabled, redirect_uri, response_type,
47
- // scope. With no redirect URI registered and none given, invalid_redirect_uri. read_only is accepted from any platform
48
- // (the docs limit it to extensions; the twin does not tell a platform from an extension).
49
- // - the secret key may come as Bearer, as Basic's user, or as the `client_secret` parameter (the reference's curl uses
50
- // `-u`, stripe-node a Bearer header); without one the answer is 401 invalid_request, and a key that is not a secret
51
- // key (sk_…/rk_…) 401 invalid_request too. The token and deauthorize wording other than the one quoted above is the
52
- // twin's. An expired code is invalid_grant "Authorization code expired: {code}"; a reused one invalid_grant "This
53
- // authorization code has already been used. All tokens issued with this code have been revoked." and revokes.
54
- // - tokens are deterministic, not secret: access_token `sk_test_oauth_{account}_{n}`, refresh_token `rt_twin_{n}`,
55
- // stripe_publishable_key `pk_test_oauth_{account}`; a refresh answers the same refresh_token (`rt_twin_{n}` of the code
56
- // `ac_twin_{n}` that made the connection). The access_token and publishable key are deprecated by Stripe (use the
57
- // Stripe-Account header) and act as the account (stripe-server.ts actingAsOAuthKey); a read_only one is not held to
58
- // reads (manifest todo `stripe.connect.oauth_scope_enforcement`).
59
- // - concurrency: a code's number is taken with its write in one move; a code redeemed twice at once lets one exchange
60
- // win, and whichever of the two finishes last revokes the connection, so a double exchange always ends revoked.
61
- import { semanticsContext } from '@volter/world-core';
62
2
  import { Consent, CONSENT_CSS, flowPage } from '@volter/world-ui';
63
- import surface from '../generated/surface.gen.json' with { type: 'json' };
64
- import { manifest } from "../manifest.js";
65
- import { created } from "../semantics/shared.js";
66
- import { OAUTH_CONNECTIONS, publicBusinessName } from "../stripe-shared.js";
67
- import { accountSettings, afterStripeWrite, PLATFORM_ACCOUNT_ID } from "../stripe-twin.js";
68
- import { formOf, seeOther, STRIPE_CONSENT_SKIN } from "./consent-skin.js";
69
- import { connectOAuthSettings, PLATFORM_CLIENT_ID, SETTINGS_URL } from "./connect-settings.js";
70
- const OPERATION = surface.operations.find((o) => o.id === 'PostAccountsAccount');
3
+ import { PLATFORM_CLIENT_ID, SETTINGS_URL } from "../engine/connect-settings.js";
4
+ import { OAUTH_CONNECTIONS, publicBusinessName } from "../engine/display.js";
5
+ import { redactKey } from "../engine/oauth.js";
6
+ import { accountSettings, nowUnix, PLATFORM_ACCOUNT_ID } from "../engine/stripe.js";
7
+ import { formOf, notFound, seeOther, STRIPE_CONSENT_SKIN } from "./shared.js";
8
+ import { connectOAuthSettings, created, decide } from "../semantics/shared.js";
71
9
  /** A code "expires in 5 minutes". */
72
- export const CODE_TTL_SECONDS = 300;
10
+ const CODE_TTL_SECONDS = 300;
73
11
  const CODES = '_oauth_code';
74
12
  /** One connection per connected account (the twin's one platform): its scope, tokens, and whether it was revoked. */
75
- export const CONNECTIONS = OAUTH_CONNECTIONS;
13
+ const CONNECTIONS = OAUTH_CONNECTIONS;
76
14
  const SCOPES = ['read_write', 'read_only'];
77
15
  const json = (body, status = 200) => Response.json(body, { status, headers: { 'request-id': 'req_twin', 'cache-control': 'no-store' } });
78
16
  const oauthError = (error, description, status = 400, extra = {}) => json({ error, error_description: description, ...extra }, status);
79
- /** Whether a connected account's OAuth connection to the platform was revoked (deauthorized, or its code reused). */
80
- export function connectionRevoked(ctx, account) {
81
- return ctx.rowsRaw(CONNECTIONS).some((c) => c.id === account && c.revoked === true);
82
- }
83
- /** The connected account an OAuth key acts as — the connection's current access_token (sk_test_oauth_…) or its
84
- * stripe_publishable_key (pk_test_oauth_…) — with the scope it was issued; undefined for any other key, and null for an
85
- * OAuth-shaped key no live connection holds (revoked, replaced by a refresh, or never issued). */
86
- export function oauthKeyAccount(ctx, key) {
87
- if (!/^(sk|pk)_test_oauth_/.test(key))
88
- return undefined;
89
- const held = ctx.rowsRaw(CONNECTIONS).find((c) => c.revoked !== true && (c.access_token === key || c.publishable_key === key));
90
- return held ? { account: String(held.id), scope: String(key.startsWith('pk_') ? held.scope : held.access_scope ?? held.scope) } : null;
91
- }
92
17
  // ── authorize ──
93
18
  /** A query parameter, or undefined when absent (an empty value is absent too, as a browser form sends one). */
94
19
  const param = (q, k) => { const v = q.get(k); return v === null || v === '' ? undefined : v; };
@@ -135,7 +60,7 @@ function withParams(uri, params) {
135
60
  /** The prefill the request carries, invalid values silently dropped (docs.stripe.com/connect/oauth-reference). Only
136
61
  * what the new account's object shows the platform, or the page shows the person, is read: the person's birth date and
137
62
  * the business address are the account application's, which the twin does not model (the test-mode skip). */
138
- export function prefill(q) {
63
+ function prefill(q) {
139
64
  const v = (k) => param(q, `stripe_user[${k}]`)?.trim() || undefined;
140
65
  const out = {};
141
66
  const keep = (k, ok = () => true) => { const s = v(k); if (s !== undefined && ok(s))
@@ -179,13 +104,11 @@ async function createAccount(ctx, p) {
179
104
  function application(ctx) {
180
105
  return { id: PLATFORM_CLIENT_ID, object: 'application', name: publicBusinessName(ctx.get('account', PLATFORM_ACCOUNT_ID)) };
181
106
  }
182
- /** Send one of the account.application.* events as the connected account's. */
107
+ /** Send one of the account.application.* events as the connected account's: the notice it is sent for (the kernel
108
+ * sends it, ../semantics/events.ts renders it). */
183
109
  async function applicationEvent(ctx, type, account) {
184
- await afterStripeWrite('application', type, application(ctx), ctx.root, ctx.occurredAt, undefined, account);
110
+ await ctx.write('_application_notice', `${account}:${type}:${ctx.occurredAt}`, { account, application: application(ctx) }, type);
185
111
  }
186
- /** A code's number: `ac_twin_{n}`, the refresh token its exchange mints being `rt_twin_{n}`, so the one follows the
187
- * other and neither is counted separately. */
188
- const codeNumber = (code) => code.replace(/^ac_twin_/, '');
189
112
  function page(ctx, action, a, p) {
190
113
  const name = publicBusinessName(ctx.get('account', PLATFORM_ACCOUNT_ID));
191
114
  const who = [p.email, [p.first_name, p.last_name].filter(Boolean).join(' '), p.business_name].filter((s) => s).join(' · ');
@@ -203,19 +126,17 @@ function page(ctx, action, a, p) {
203
126
  /** The person connects: a new Standard account, its connection's code, the authorized event, and the redirect. */
204
127
  async function connect(ctx, a, p) {
205
128
  const account = await createAccount(ctx, p);
206
- // the code's number is taken and its row written in one move, so two people connecting at once get two codes
207
- 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 };
208
- const code = await ctx.atomically((rows) => {
209
- const id = `ac_twin_${rows(CODES).length + 1}`;
210
- return { value: id, write: { resource: CODES, id, fields, operation: 'oauth_code.issue' } };
211
- });
129
+ // 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
130
+ const id = ctx.mint(CODES);
131
+ const code = `ac_${(await ctx.secret(`stripe-oauth-code:${id}`)).replace(/[^A-Za-z0-9]/g, '').slice(0, 24)}`;
132
+ 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);
212
133
  await applicationEvent(ctx, 'account.application.authorized', account);
213
134
  return seeOther(withParams(a.redirect, [['scope', a.scope], ['code', code], ['state', a.state]]));
214
135
  }
215
- async function authorize(request, scope) {
136
+ async function authorize(ctx) {
137
+ const request = ctx.call.request;
216
138
  const url = new URL(request.url);
217
139
  const form = request.method === 'POST' ? await formOf(request.clone()) : {};
218
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
219
140
  const a = checked(ctx, url.searchParams);
220
141
  if (a instanceof Response)
221
142
  return a;
@@ -259,12 +180,6 @@ function secretKey(request, params) {
259
180
  }
260
181
  return params.client_secret || undefined;
261
182
  }
262
- /** A key shown the way Stripe's errors show one: its prefix and its last four. */
263
- export const redactKey = (key) => {
264
- const prefix = /^(sk|rk|pk)_(test|live)_/.exec(key)?.[0] ?? '';
265
- const rest = key.slice(prefix.length);
266
- return rest.length > 4 ? `${prefix}${'*'.repeat(rest.length - 4)}${rest.slice(-4)}` : `${prefix}${'*'.repeat(rest.length)}`;
267
- };
268
183
  /** The refusal of a request without a usable secret key, or undefined when it has one (answered with whether it is live). */
269
184
  function keyRefused(key) {
270
185
  if (!key)
@@ -279,27 +194,30 @@ function keyRefused(key) {
279
194
  }
280
195
  const live = (key) => /^(sk|rk)_live_/.test(key);
281
196
  /** A new access token for a connection: its generation counted from the ones it had. */
282
- const accessToken = (account, n) => `sk_test_oauth_${account}_${n}`;
197
+ /** A connection's access token of one generation: its account, then a digest of the connection's secret and the
198
+ * generation (the secret read before the atomic move that numbers the generation). */
199
+ // p3: keyed by the connection's secret, drawn from ctx.secret
200
+ const accessToken = (ctx, secret, account, n) => `sk_test_oauth_${account}_${ctx.crypto.digest('sha256', `${secret}:${n}`, 'base64url').replace(/[-_]/g, '').slice(0, 24)}`;
283
201
  /** Revoke an account's connection, once: the check and the write are one move, and only the move that revoked it sends
284
202
  * account.application.deauthorized. Answers whether this call revoked it. */
285
203
  async function revoke(ctx, account) {
286
- const revoked = await ctx.atomically((rows) => {
204
+ const revoked = await decide(ctx, (rows) => {
287
205
  const held = rows(CONNECTIONS).find((c) => c.id === account);
288
206
  if (!held || held.revoked === true)
289
207
  return { value: false };
290
- return { value: true, write: { resource: CONNECTIONS, id: account, fields: { revoked: true, access_token: null, revoked_at: Number(ctx.now()) }, operation: 'oauth_connection.revoke' } };
208
+ return { value: true, write: { resource: CONNECTIONS, id: account, fields: { revoked: true, access_token: null, revoked_at: nowUnix(ctx.occurredAt) }, operation: 'oauth_connection.revoke' } };
291
209
  });
292
210
  if (revoked)
293
211
  await applicationEvent(ctx, 'account.application.deauthorized', account);
294
212
  return revoked;
295
213
  }
296
- async function token(request, scope) {
214
+ async function token(ctx) {
215
+ const request = ctx.call.request;
297
216
  const params = await bodyOf(request);
298
217
  const key = secretKey(request, params);
299
218
  const refused = keyRefused(key);
300
219
  if (refused)
301
220
  return refused;
302
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
303
221
  const grant = params.grant_type;
304
222
  if (!grant)
305
223
  return oauthError('invalid_request', 'No grant type specified');
@@ -311,16 +229,16 @@ async function token(request, scope) {
311
229
  return oauthError('invalid_request', 'No authorization code provided');
312
230
  if (live(key))
313
231
  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.`);
314
- const found = await ctx.atomically((rows) => {
315
- const row = rows(CODES).find((r) => r.id === code);
232
+ const found = await decide(ctx, (rows) => {
233
+ const row = rows(CODES).find((r) => r.sha256 === ctx.crypto.sha256(code));
316
234
  if (!row)
317
235
  return { value: { outcome: 'missing' } };
318
236
  // a second use is marked in the same move, so the exchange that spent it sees it after writing its connection
319
237
  if (row.used === true)
320
- return { value: { outcome: 'used', row }, write: { resource: CODES, id: code, fields: { reused: true }, operation: 'oauth_code.reuse' } };
321
- if (Number(ctx.now()) >= Number(row.expires_at))
238
+ return { value: { outcome: 'used', row }, write: { resource: CODES, id: String(row.id), fields: { reused: true }, operation: 'oauth_code.reuse' } };
239
+ if (nowUnix(ctx.occurredAt) >= Number(row.expires_at))
322
240
  return { value: { outcome: 'expired', row } };
323
- return { value: { outcome: 'ok', row }, write: { resource: CODES, id: code, fields: { used: true, used_at: Number(ctx.now()) }, operation: 'oauth_code.redeem' } };
241
+ return { value: { outcome: 'ok', row }, write: { resource: CODES, id: String(row.id), fields: { used: true, used_at: nowUnix(ctx.occurredAt) }, operation: 'oauth_code.redeem' } };
324
242
  });
325
243
  if (found.outcome === 'missing')
326
244
  return oauthError('invalid_grant', `Authorization code does not exist: ${code}`);
@@ -333,11 +251,12 @@ async function token(request, scope) {
333
251
  if (found.outcome === 'expired')
334
252
  return oauthError('invalid_grant', `Authorization code expired: ${code}`);
335
253
  const granted = String(found.row.scope);
336
- const refresh = `rt_twin_${codeNumber(code)}`;
337
- 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 };
254
+ const refresh = `rt_${(await ctx.secret(`stripe-oauth-refresh:${String(found.row.id)}`)).replace(/[-_]/g, '').slice(0, 32)}`;
255
+ const secret = await ctx.secret(`stripe-oauth-access:${account}`);
256
+ 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 };
338
257
  await ctx.record(CONNECTIONS, connection, account);
339
258
  // the code used again while this exchange was writing: that use found no connection to revoke, so this one does
340
- if (ctx.rowsRaw(CODES).some((r) => r.id === code && r.reused === true)) {
259
+ if (ctx.rowsRaw(CODES).some((r) => r.id === found.row.id && r.reused === true)) {
341
260
  await revoke(ctx, account);
342
261
  return oauthError('invalid_grant', 'This authorization code has already been used. All tokens issued with this code have been revoked.');
343
262
  }
@@ -360,12 +279,13 @@ async function token(request, scope) {
360
279
  // "Any existing access token with the same scope and mode ... is revoked": the connection holds the one now issued; the
361
280
  // generation is read and written in one move, so two refreshes at once issue two tokens, and a refresh never lands
362
281
  // on a connection revoked meanwhile
363
- const renewed = await ctx.atomically((rows) => {
282
+ const secret = await ctx.secret(`stripe-oauth-access:${account}`);
283
+ const renewed = await decide(ctx, (rows) => {
364
284
  const now = rows(CONNECTIONS).find((c) => c.id === account && c.revoked !== true);
365
285
  if (!now)
366
286
  return { value: undefined };
367
287
  const generation = Number(now.generation ?? 1) + 1;
368
- const fields = { access_token: accessToken(account, generation), access_scope: asked, generation };
288
+ const fields = { access_token: accessToken(ctx, secret, account, generation), access_scope: asked, generation };
369
289
  return { value: { ...now, ...fields }, write: { resource: CONNECTIONS, id: account, fields, operation: 'oauth_connection.refresh' } };
370
290
  });
371
291
  if (!renewed)
@@ -376,13 +296,13 @@ async function token(request, scope) {
376
296
  function tokenBody(c, scope) {
377
297
  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' };
378
298
  }
379
- async function deauthorize(request, scope) {
299
+ async function deauthorize(ctx) {
300
+ const request = ctx.call.request;
380
301
  const params = await bodyOf(request);
381
302
  const key = secretKey(request, params);
382
303
  const refused = keyRefused(key);
383
304
  if (refused)
384
305
  return refused;
385
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
386
306
  const client = params.client_id;
387
307
  const account = params.stripe_user_id;
388
308
  if (!client)
@@ -399,16 +319,15 @@ async function deauthorize(request, scope) {
399
319
  return oauthError('invalid_client', `This application is not connected to stripe account ${account}, or that account does not exist.`);
400
320
  return json({ stripe_user_id: account });
401
321
  }
402
- /** connect.stripe.com's OAuth endpoints, or undefined for any other request. */
403
- export function stripeConnectOAuthFlow(scope) {
404
- return async (request) => {
405
- const path = new URL(request.url).pathname.replace(/\/+$/, '');
406
- if (path === '/oauth/authorize' && (request.method === 'GET' || request.method === 'POST'))
407
- return authorize(request, scope);
408
- if (path === '/oauth/token' && request.method === 'POST')
409
- return token(request, scope);
410
- if (path === '/oauth/deauthorize' && request.method === 'POST')
411
- return deauthorize(request, scope);
412
- return undefined;
413
- };
322
+ /** connect.stripe.com's OAuth endpoints; Stripe's 404 for anything else there. */
323
+ export async function screen(ctx) {
324
+ const request = ctx.call.request;
325
+ const path = new URL(request.url).pathname.replace(/\/+$/, '');
326
+ if (path === '/oauth/authorize' && (request.method === 'GET' || request.method === 'POST'))
327
+ return authorize(ctx);
328
+ if (path === '/oauth/token' && request.method === 'POST')
329
+ return token(ctx);
330
+ if (path === '/oauth/deauthorize' && request.method === 'POST')
331
+ return deauthorize(ctx);
332
+ return notFound();
414
333
  }
@@ -0,0 +1,3 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ /** connect.stripe.com's onboarding pages, or undefined for any other request. */
3
+ export declare function screen(ctx: HandlerContext): Promise<Response>;
@@ -1,44 +1,17 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // STRIPE CONNECT HOSTED ONBOARDING — a hosted flow (docs/contributing/architecture.md, "Screens"): a platform
3
- // creates an Account Link for a connected account and sends the account's owner to its `url`. The link is a
4
- // single-use entry point: visiting it takes the owner to their onboarding session, and visiting it again, or after it
5
- // expires, sends them to refresh_url, where the platform makes a new one (docs.stripe.com/connect/hosted-onboarding).
6
- // At the session's own address they give Stripe what it requires, and Stripe sends them to return_url. Submitting is
7
- // the owner's move, not an API call. Authored from @volter/world-ui's payment piece; nothing of Stripe's page is copied.
8
- //
9
- // The page asks for exactly what Stripe requires before a US Express account with card_payments and transfers can
10
- // take card payments (docs.stripe.com/connect/required-verification-information; the set is the one Stripe's
11
- // requirements endpoint answers, docs.stripe.com/_endpoint/get-requirements-for-setups with apiVersion=v1,
12
- // platformCountry=US, accountCountry=US, dashboardType=express, tosType=full, capabilities card_payments and transfers):
13
- // thirty requirements for a company (the business profile, the statement descriptor, the company with its tax id,
14
- // phone and address, its owners, its representative with their title and address, the agreement and the payout bank
15
- // account) and eighteen for an individual. A submit that leaves any out is refused with what is missing. The account's
16
- // requirements.currently_due lists what is still missing, and charges and payouts are enabled, and its requested
17
- // capabilities become active (Stripe's move on the capability machine), only once none is. The agreement is the
18
- // page's own checkbox, recorded as tos_acceptance with the request's address and the time.
19
- //
20
- // Where the documentation stops and the twin decides: only these two US sets are modelled; an account elsewhere, or
21
- // of another business type, is told its requirements are not modelled. Test mode verifies at once whatever is
22
- // submitted (docs.stripe.com/connect/testing lists the values that pass: date of birth 1901-01-01, SSN last four
23
- // 0000, tax id 000000000, routing 110000000, account 000123456789); the industries offered are Stripe's supported
24
- // merchant category codes as its list gives them (./industries.ts), and an industry the page does not offer leaves
25
- // business_profile.mcc due; the states are the fifty and the District of Columbia; a request with no forwarded address
26
- // records 127.0.0.1. An update link (account_update) shows the same page.
27
- import { semanticsContext } from '@volter/world-core';
28
2
  import { CONSENT_CSS, flowPage, Payment, PAYMENT_CSS } from '@volter/world-ui';
29
- import surface from '../generated/surface.gen.json' with { type: 'json' };
30
- import { manifest } from "../manifest.js";
3
+ import { nowUnix } from "../engine/stripe.js";
4
+ import { formOf, notFound, seeOther, STRIPE_CONSENT_SKIN } from "./shared.js";
5
+ import { INDUSTRIES as MCCS } from "../engine/industries.js";
6
+ import { currentlyDue, kindOf, OFFERED } from "../engine/onboarding.js";
31
7
  import { created, syncExternals } from "../semantics/shared.js";
32
- import { formOf, seeOther, STRIPE_CONSENT_SKIN } from "./consent-skin.js";
33
- import { INDUSTRIES as MCCS } from "./industries.js";
34
8
  const LINK = 'account_link';
35
- const OPERATION = surface.operations.find((o) => o.id === 'PostAccountsAccount');
9
+ const OPERATION = { id: 'PostAccountsAccount' };
36
10
  const gone = () => flowPage({ title: 'Stripe', status: 404, css: [CONSENT_CSS, STRIPE_CONSENT_SKIN], body: _jsx("main", { className: "consent", children: _jsx("h1", { className: "consent-heading", children: "This link is invalid." }) }) });
37
11
  /** A link that can still be used: not used and not past its expiry. */
38
- const usable = (ctx, link) => link.used !== true && Number(link.expires_at) > Number(ctx.now());
12
+ const usable = (ctx, link) => link.used !== true && Number(link.expires_at) > nowUnix(ctx.occurredAt);
39
13
  // the field asks, with no industry chosen until the person chooses one (an unanswered field leaves the MCC due)
40
14
  const INDUSTRIES = [{ value: '', label: 'Select your industry' }, ...MCCS.map(([value, label]) => ({ value, label }))];
41
- const OFFERED = new Set(MCCS.map(([mcc]) => mcc));
42
15
  // the fifty states and the District of Columbia, by their USPS codes (the address's `state`, ISO 3166-2 without its
43
16
  // country, docs.stripe.com/api/accounts/object)
44
17
  const STATES = [
@@ -51,43 +24,6 @@ const STATES = [
51
24
  ['SC', 'South Carolina'], ['SD', 'South Dakota'], ['TN', 'Tennessee'], ['TX', 'Texas'], ['UT', 'Utah'], ['VT', 'Vermont'], ['VA', 'Virginia'],
52
25
  ['WA', 'Washington'], ['WV', 'West Virginia'], ['WI', 'Wisconsin'], ['WY', 'Wyoming'],
53
26
  ].map(([value, label]) => ({ value, label }));
54
- const has = (v, k) => (v[k] ?? '').trim() !== '';
55
- const dobPart = (v, i) => /^\d{4}-\d{2}-\d{2}$/.test(v.dob ?? '') && Number((v.dob ?? '').split('-')[i]) > 0;
56
- /** Each requirement Stripe names, the form's answer that meets it, and which of the two sets it belongs to. */
57
- const REQUIREMENTS = [
58
- ['business_profile.mcc', (v) => OFFERED.has((v.mcc ?? '').trim()), ['company', 'individual']],
59
- ['business_profile.url', (v) => has(v, 'url'), ['company', 'individual']],
60
- ['settings.payments.statement_descriptor', (v) => has(v, 'statement_descriptor'), ['company', 'individual']],
61
- ['company.name', (v) => has(v, 'company_name'), ['company']],
62
- ['company.tax_id', (v) => has(v, 'company_tax_id'), ['company']],
63
- ['company.phone', (v) => has(v, 'company_phone'), ['company']],
64
- ['company.address.line1', (v) => has(v, 'company_line1'), ['company']],
65
- ['company.address.city', (v) => has(v, 'company_city'), ['company']],
66
- ['company.address.state', (v) => has(v, 'company_state'), ['company']],
67
- ['company.address.postal_code', (v) => has(v, 'company_postal_code'), ['company']],
68
- ['company.owners_provided', (v) => v.owners_provided === 'provided', ['company']],
69
- ['owners.first_name', (v) => has(v, 'owner_first_name'), ['company']],
70
- ['owners.last_name', (v) => has(v, 'owner_last_name'), ['company']],
71
- ['owners.email', (v) => has(v, 'owner_email'), ['company']],
72
- ...['first_name', 'last_name', 'email', 'phone'].flatMap((f) => [[`representative.${f}`, (v) => has(v, f), ['company']], [`individual.${f}`, (v) => has(v, f), ['individual']]]),
73
- ...['day', 'month', 'year'].flatMap((f, i) => [[`representative.dob.${f}`, (v) => dobPart(v, 2 - i), ['company']], [`individual.dob.${f}`, (v) => dobPart(v, 2 - i), ['individual']]]),
74
- ...['line1', 'city', 'state', 'postal_code'].flatMap((f) => [[`representative.address.${f}`, (v) => has(v, f), ['company']], [`individual.address.${f}`, (v) => has(v, f), ['individual']]]),
75
- ['representative.relationship.title', (v) => has(v, 'title'), ['company']],
76
- ['representative.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['company']],
77
- ['individual.ssn_last_4', (v) => /^\d{4}$/.test(v.ssn_last_4 ?? ''), ['individual']],
78
- ['tos_acceptance.date', (v) => v.tos === 'accepted', ['company', 'individual']],
79
- ['tos_acceptance.ip', (v) => v.tos === 'accepted', ['company', 'individual']],
80
- ['external_account', (v) => has(v, 'routing_number') && has(v, 'account_number'), ['company', 'individual']],
81
- ];
82
- /** The set an account onboards against: the two US sets the twin models, or none. */
83
- export function kindOf(account) {
84
- const type = account.business_type ?? 'individual';
85
- return (account.country ?? 'US') === 'US' && (type === 'company' || type === 'individual') ? type : undefined;
86
- }
87
- /** What an account of this kind still owes, given the form's answers (none: all of it). */
88
- export function currentlyDue(kind, v = {}) {
89
- return REQUIREMENTS.filter(([, met, kinds]) => kinds.includes(kind) && !met(v)).map(([req]) => req);
90
- }
91
27
  const field = (id, label, v, extra = {}) => ({ id, label, value: v[id] ?? '', ...extra });
92
28
  const addressFields = (prefix, v) => [
93
29
  field(`${prefix}line1`, 'Address', v, { autoComplete: 'address-line1' }), field(`${prefix}city`, 'City', v),
@@ -163,7 +99,7 @@ async function submit(ctx, link, kind, v, ip) {
163
99
  ...(company
164
100
  ? { 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 } }
165
101
  : { individual: { ...person, id: `person_${id}` } }),
166
- tos_acceptance: { date: Number(ctx.now()), ip, user_agent: null },
102
+ tos_acceptance: { date: nowUnix(ctx.occurredAt), ip, user_agent: null },
167
103
  }, 'account.updated');
168
104
  if (company) {
169
105
  await created(ctx, 'person', { account: id }, { ...person, relationship: { representative: true, executive: true, director: false, owner: false, percent_ownership: null, title: v.title }, metadata: {} });
@@ -191,35 +127,33 @@ async function visit(ctx, id) {
191
127
  return seeOther(`${SESSIONS}${session}`);
192
128
  }
193
129
  /** connect.stripe.com's onboarding pages, or undefined for any other request. */
194
- export function stripeOnboardingFlow(scope) {
195
- return async (request) => {
196
- const path = new URL(request.url).pathname;
197
- const entry = /^\/setup\/(acctlink_[A-Za-z0-9_]+)\/?$/.exec(path);
198
- const at = /^\/setup\/s\/(onbs_[A-Za-z0-9_]+)\/?$/.exec(path);
199
- if (entry && request.method === 'GET')
200
- return visit(await semanticsContext(manifest, request, OPERATION, scope), entry[1]);
201
- if (!at || (request.method !== 'GET' && request.method !== 'POST'))
202
- return undefined;
203
- const sid = at[1];
204
- const form = request.method === 'POST' ? await formOf(request.clone()) : {};
205
- const ctx = await semanticsContext(manifest, request, OPERATION, scope);
206
- // the session the visited link opened, until it is submitted
207
- const stored = ctx.rowsRaw(LINK).find((l) => l._session === sid && l._submitted !== true);
208
- const link = stored ? ctx.get(LINK, String(stored.id)) : undefined;
209
- if (!stored || !link)
210
- return gone();
211
- const sessionUrl = `${SESSIONS}${sid}`;
212
- const account = ctx.get('account', String(link.account)) ?? {};
213
- const kind = kindOf(account);
214
- if (!kind)
215
- return unmodelled(account);
216
- if (request.method === 'GET')
217
- return page(ctx, sessionUrl, link, kind);
218
- const missing = currentlyDue(kind, form);
219
- if (missing.length)
220
- return incomplete(ctx, sessionUrl, link, kind, form, missing);
221
- await ctx.write(LINK, String(stored.id), { _submitted: true }, 'account_link.submitted');
222
- await submit(ctx, link, kind, form, request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1');
223
- return seeOther(String(link.return_url));
224
- };
130
+ export async function screen(ctx) {
131
+ const request = ctx.call.request;
132
+ const path = new URL(request.url).pathname;
133
+ const entry = /^\/setup\/(acctlink_[A-Za-z0-9_]+)\/?$/.exec(path);
134
+ const at = /^\/setup\/s\/(onbs_[A-Za-z0-9_]+)\/?$/.exec(path);
135
+ if (entry && request.method === 'GET')
136
+ return visit(ctx, entry[1]);
137
+ if (!at || (request.method !== 'GET' && request.method !== 'POST'))
138
+ return notFound();
139
+ const sid = at[1];
140
+ const form = request.method === 'POST' ? await formOf(request.clone()) : {};
141
+ // the session the visited link opened, until it is submitted
142
+ const stored = ctx.rowsRaw(LINK).find((l) => l._session === sid && l._submitted !== true);
143
+ const link = stored ? ctx.get(LINK, String(stored.id)) : undefined;
144
+ if (!stored || !link)
145
+ return gone();
146
+ const sessionUrl = `${SESSIONS}${sid}`;
147
+ const account = ctx.get('account', String(link.account)) ?? {};
148
+ const kind = kindOf(account);
149
+ if (!kind)
150
+ return unmodelled(account);
151
+ if (request.method === 'GET')
152
+ return page(ctx, sessionUrl, link, kind);
153
+ const missing = currentlyDue(kind, form);
154
+ if (missing.length)
155
+ return incomplete(ctx, sessionUrl, link, kind, form, missing);
156
+ await ctx.write(LINK, String(stored.id), { _submitted: true }, 'account_link.submitted');
157
+ await submit(ctx, link, kind, form, request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1');
158
+ return seeOther(String(link.return_url));
225
159
  }
@@ -0,0 +1,3 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ /** dashboard.stripe.com's pages: Public details and the Connect OAuth settings; Stripe's 404 for any other. */
3
+ export declare function screen(ctx: HandlerContext): Promise<Response>;
@@ -0,0 +1,117 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { flowPage } from '@volter/world-ui';
3
+ import { platformAccountDefault } from "../engine/connect.js";
4
+ import { CONNECT_SETTINGS_PATH, PLATFORM_CLIENT_ID, redirectUris, SETTINGS } from "../engine/connect-settings.js";
5
+ import { publicBusinessName } from "../engine/display.js";
6
+ import { PLATFORM_ACCOUNT_ID } from "../engine/stripe.js";
7
+ import { formOf, notFound } from "./shared.js";
8
+ import { connectOAuthSettings, created } from "../semantics/shared.js";
9
+ const PUBLIC_DETAILS_PATH = '/settings/public';
10
+ const MAX_NAME = 5000;
11
+ const SETTINGS_CSS = `
12
+ * { box-sizing: border-box; }
13
+ body { margin: 0; background: #f6f8fa; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; font-size: 14px; }
14
+ .co { max-width: 680px; margin: 48px auto; padding: 0 16px; }
15
+ .co h1 { font-size: 28px; margin: 16px 0 4px; }
16
+ .co-lead { color: #697386; margin: 0 0 24px; }
17
+ .co-card { background: #ffffff; border: 1px solid #e3e8ee; border-radius: 8px; padding: 24px; margin-bottom: 16px; }
18
+ .co-card h2 { font-size: 16px; margin: 0 0 12px; }
19
+ .co-card label { display: block; font-weight: 600; margin-bottom: 6px; }
20
+ .co-card small { display: block; color: #697386; margin-bottom: 8px; }
21
+ .co-card textarea { width: 100%; min-height: 96px; padding: 8px 10px; border: 1px solid #c1c9d2; border-radius: 6px; font: 13px ui-monospace, Menlo, monospace; }
22
+ .co-id { font: 14px ui-monospace, Menlo, monospace; background: #f6f8fa; border: 1px solid #e3e8ee; border-radius: 6px; padding: 6px 10px; display: inline-block; }
23
+ .co-toggle { display: flex; gap: 8px; align-items: center; font-weight: 600; }
24
+ .co-actions { display: flex; justify-content: flex-end; }
25
+ .co-actions button { background: #635bff; color: #ffffff; border: 0; border-radius: 6px; padding: 8px 16px; font: inherit; font-weight: 600; cursor: pointer; }
26
+ .co-notice { padding: 10px 12px; border-radius: 6px; margin-bottom: 16px; }
27
+ .co-error { background: #fff0f3; color: #df1b41; }
28
+ .co-saved { background: #e7f8ef; color: #0e6245; }
29
+ .co-uris { margin: 0; padding-left: 18px; }
30
+ `;
31
+ function settingsPage(s, opts = {}) {
32
+ const enabled = opts.enabled ?? s.oauth_enabled;
33
+ return flowPage({
34
+ title: 'OAuth – Connect settings – Stripe',
35
+ css: [SETTINGS_CSS],
36
+ ...(opts.error ? { status: 400 } : {}),
37
+ body: (_jsxs("main", { className: "co", children: [_jsx("h1", { children: "OAuth" }), _jsx("p", { className: "co-lead", children: "Onboard Standard accounts by sending them to Stripe's OAuth flow. Test mode." }), opts.error ? _jsx("p", { className: "co-notice co-error", role: "alert", children: opts.error }) : null, opts.saved ? _jsx("p", { className: "co-notice co-saved", role: "status", children: "Your OAuth settings were saved." }) : null, _jsxs("section", { className: "co-card", children: [_jsx("h2", { children: "Test mode client ID" }), _jsx("small", { children: "The client_id your platform sends to connect.stripe.com/oauth/authorize." }), _jsx("code", { className: "co-id", "data-testid": "connect-client-id", children: PLATFORM_CLIENT_ID }), _jsx("p", { "data-testid": "connect-oauth-status", children: s.oauth_enabled ? 'OAuth is enabled.' : 'OAuth is disabled.' }), s.redirect_uris.length ? _jsx("ul", { className: "co-uris", children: s.redirect_uris.map((u) => _jsx("li", { "data-testid": "connect-redirect-uri", children: u }, u)) }) : _jsx("p", { children: "No redirect URIs." })] }), _jsxs("form", { className: "co-card", method: "post", children: [_jsxs("label", { className: "co-toggle", htmlFor: "oauth_enabled", children: [_jsx("input", { type: "checkbox", id: "oauth_enabled", name: "oauth_enabled", value: "on", defaultChecked: enabled }), " Enable OAuth for Standard accounts"] }), _jsx("label", { htmlFor: "redirect_uris", children: "Redirect URIs" }), _jsx("small", { children: "One per line. The first is used when a request names none. http and localhost are allowed in test mode." }), _jsx("textarea", { id: "redirect_uris", name: "redirect_uris", defaultValue: opts.value ?? s.redirect_uris.join('\n') }), _jsx("div", { className: "co-actions", children: _jsx("button", { type: "submit", children: "Save" }) })] })] })),
38
+ });
39
+ }
40
+ const DETAILS_CSS = `
41
+ * { box-sizing: border-box; }
42
+ body { margin: 0; background: #f6f8fa; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; font-size: 14px; }
43
+ .pd { max-width: 640px; margin: 48px auto; padding: 0 16px; }
44
+ .pd h1 { font-size: 28px; margin: 16px 0 4px; }
45
+ .pd-lead { color: #697386; margin: 0 0 24px; }
46
+ .pd-card { background: #ffffff; border: 1px solid #e3e8ee; border-radius: 8px; padding: 24px; }
47
+ .pd-card label { display: block; font-weight: 600; margin-bottom: 6px; }
48
+ .pd-card small { display: block; color: #697386; margin-bottom: 8px; }
49
+ .pd-card input { width: 100%; padding: 8px 10px; border: 1px solid #c1c9d2; border-radius: 6px; font: inherit; }
50
+ .pd-actions { margin-top: 20px; display: flex; justify-content: flex-end; }
51
+ .pd-actions button { background: #635bff; color: #ffffff; border: 0; border-radius: 6px; padding: 8px 16px; font: inherit; font-weight: 600; cursor: pointer; }
52
+ .pd-notice { padding: 10px 12px; border-radius: 6px; margin-bottom: 16px; }
53
+ .pd-error { background: #fff0f3; color: #df1b41; }
54
+ .pd-saved { background: #e7f8ef; color: #0e6245; }
55
+ `;
56
+ function detailsPage(account, opts = {}) {
57
+ const named = account?.business_profile?.name;
58
+ const value = opts.value ?? (typeof named === 'string' ? named : '');
59
+ return flowPage({
60
+ title: 'Public details – Stripe',
61
+ css: [DETAILS_CSS],
62
+ ...(opts.error ? { status: 400 } : {}),
63
+ body: (_jsxs("main", { className: "pd", children: [_jsx("h1", { children: "Public details" }), _jsxs("p", { className: "pd-lead", children: ["What your customers see on Checkout, the customer portal, receipts and invoices. Currently shown: ", _jsx("strong", { "data-testid": "public-business-name", children: publicBusinessName(account) })] }), opts.error ? _jsx("p", { className: "pd-notice pd-error", role: "alert", children: opts.error }) : null, opts.saved ? _jsx("p", { className: "pd-notice pd-saved", role: "status", children: "Your public details were saved." }) : null, _jsxs("form", { className: "pd-card", method: "post", children: [_jsx("label", { htmlFor: "business_name", children: "Business name" }), _jsx("small", { children: "The name customers see when they pay you." }), _jsx("input", { id: "business_name", name: "business_name", autoComplete: "organization", defaultValue: value, required: true, maxLength: MAX_NAME }), _jsx("div", { className: "pd-actions", children: _jsx("button", { type: "submit", children: "Save" }) })] })] })),
64
+ });
65
+ }
66
+ /** Save the business name on the platform's own account: the stored record, made from Stripe's default the first
67
+ * time anything is saved, and otherwise changed in place (its other public details kept). */
68
+ async function save(ctx, name) {
69
+ const held = ctx.get('account', PLATFORM_ACCOUNT_ID);
70
+ const profile = { business_profile: { ...((held ?? platformAccountDefault()).business_profile ?? {}), name } };
71
+ await (held ? ctx.write('account', PLATFORM_ACCOUNT_ID, profile, 'account.update') : created(ctx, 'account', { ...platformAccountDefault(), ...profile }, {}, { operation: 'account.update' }));
72
+ }
73
+ /** What the page says of a name no browser sends (the field is required and bounded), for a client that sends it. */
74
+ function nameRefused(name) {
75
+ return !name ? 'Enter your business name.' : name.length > MAX_NAME ? `Your business name must be at most ${MAX_NAME} characters.` : undefined;
76
+ }
77
+ async function settings(ctx) {
78
+ const request = ctx.call.request;
79
+ const url = new URL(request.url);
80
+ if (request.method !== 'GET' && request.method !== 'POST')
81
+ return notFound();
82
+ const form = request.method === 'POST' ? await formOf(request.clone()) : {};
83
+ const held = connectOAuthSettings(ctx);
84
+ if (request.method === 'GET')
85
+ return settingsPage(held, { saved: url.searchParams.has('saved') });
86
+ const enabled = form.oauth_enabled === 'on' || form.oauth_enabled === 'true';
87
+ const parsed = redirectUris(form.redirect_uris ?? '');
88
+ if ('refused' in parsed)
89
+ return settingsPage(held, { value: form.redirect_uris ?? '', enabled, error: parsed.refused });
90
+ await ctx.record(SETTINGS, { oauth_enabled: enabled, redirect_uris: parsed.uris }, PLATFORM_CLIENT_ID);
91
+ return new Response(null, { status: 303, headers: { location: '?saved=1' } });
92
+ }
93
+ async function publicDetails(ctx) {
94
+ const request = ctx.call.request;
95
+ const url = new URL(request.url);
96
+ if (request.method !== 'GET' && request.method !== 'POST')
97
+ return notFound();
98
+ const form = request.method === 'POST' ? await formOf(request.clone()) : {};
99
+ if (request.method === 'GET')
100
+ return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { saved: url.searchParams.has('saved') });
101
+ const name = (form.business_name ?? '').trim();
102
+ const refused = nameRefused(name);
103
+ if (refused)
104
+ return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { value: form.business_name ?? '', error: refused });
105
+ await save(ctx, name);
106
+ // a query-only reference: the page's own path, wherever it is mounted and with or without its trailing slash
107
+ return new Response(null, { status: 303, headers: { location: '?saved=1' } });
108
+ }
109
+ /** dashboard.stripe.com's pages: Public details and the Connect OAuth settings; Stripe's 404 for any other. */
110
+ export async function screen(ctx) {
111
+ const path = new URL(ctx.call.request.url).pathname.replace(/\/+$/, '');
112
+ if (path === PUBLIC_DETAILS_PATH)
113
+ return publicDetails(ctx);
114
+ if (CONNECT_SETTINGS_PATH.test(path))
115
+ return settings(ctx);
116
+ return notFound();
117
+ }