@volter/twin-stripe 2.0.38 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (402) hide show
  1. package/README.md +112 -207
  2. package/dist/generated/pack-facts.json +94 -0
  3. package/dist/journeys/coverage.json +596 -0
  4. package/dist/journeys/customer-life.json +6108 -0
  5. package/dist/journeys/customer-life.outline.md +371 -0
  6. package/dist/journeys/decisions.json +677 -0
  7. package/dist/journeys/demand.json +706 -0
  8. package/dist/journeys/score.json +853 -0
  9. package/dist/journeys/unreachable.json +102 -0
  10. package/dist/journeys/vendor-examples.json +8010 -0
  11. package/dist/spec/SOURCE.md +18 -0
  12. package/dist/spec/doc-examples.json +445 -0
  13. package/dist/spec/fixtures3.json.gz +0 -0
  14. package/dist/spec/openapi.json.gz +0 -0
  15. package/dist/spec/patches.json +34 -0
  16. package/dist/spec/sources.json +306 -0
  17. package/dist/src/cli.d.ts +0 -1
  18. package/dist/src/cli.js +11 -31
  19. package/dist/src/engine/after-payment.d.ts +8 -0
  20. package/dist/src/engine/after-payment.js +27 -0
  21. package/dist/src/engine/apps-secrets.d.ts +1 -0
  22. package/dist/src/engine/apps-secrets.js +3 -0
  23. package/dist/src/engine/balance.d.ts +5 -0
  24. package/dist/src/engine/balance.js +15 -0
  25. package/dist/src/engine/checkout.d.ts +24 -0
  26. package/dist/src/engine/checkout.js +30 -0
  27. package/dist/src/engine/common.d.ts +19 -0
  28. package/dist/src/engine/common.js +34 -0
  29. package/dist/src/engine/connect.d.ts +45 -0
  30. package/dist/src/engine/connect.js +108 -0
  31. package/dist/src/engine/customers.d.ts +4 -0
  32. package/dist/src/engine/customers.js +8 -0
  33. package/dist/src/engine/display.d.ts +11 -0
  34. package/dist/src/engine/display.js +13 -0
  35. package/dist/src/engine/files.d.ts +11 -0
  36. package/dist/src/engine/files.js +23 -0
  37. package/dist/src/engine/invoices.d.ts +21 -0
  38. package/dist/src/engine/invoices.js +84 -0
  39. package/dist/src/engine/issuing.d.ts +33 -0
  40. package/dist/src/engine/issuing.js +54 -0
  41. package/dist/src/engine/ledger.d.ts +8 -0
  42. package/dist/src/engine/ledger.js +8 -0
  43. package/dist/src/engine/oauth.d.ts +4 -0
  44. package/dist/src/engine/oauth.js +24 -0
  45. package/dist/src/engine/onboarding.d.ts +13 -0
  46. package/dist/src/engine/onboarding.js +42 -0
  47. package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
  48. package/dist/src/{stripe-params.js → engine/params.js} +7 -60
  49. package/dist/src/engine/payment-intents.d.ts +10 -0
  50. package/dist/src/engine/payment-intents.js +27 -0
  51. package/dist/src/engine/payment-methods.d.ts +16 -0
  52. package/dist/src/engine/payment-methods.js +22 -0
  53. package/dist/src/engine/radar.d.ts +2 -0
  54. package/dist/src/engine/radar.js +5 -0
  55. package/dist/src/engine/refunds.d.ts +1 -0
  56. package/dist/src/engine/refunds.js +3 -0
  57. package/dist/src/engine/renewals.d.ts +36 -0
  58. package/dist/src/engine/renewals.js +65 -0
  59. package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +12 -45
  60. package/dist/src/{stripe-twin.js → engine/stripe.js} +53 -387
  61. package/dist/src/engine/subscriptions.d.ts +11 -0
  62. package/dist/src/engine/subscriptions.js +30 -0
  63. package/dist/src/engine/transfers.d.ts +2 -0
  64. package/dist/src/engine/transfers.js +1 -0
  65. package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -2
  66. package/dist/src/{stripe-version.js → engine/version.js} +21 -9
  67. package/dist/src/engine/webhook-endpoints.d.ts +1 -0
  68. package/dist/src/engine/webhook-endpoints.js +12 -0
  69. package/dist/src/engine/webhooks.d.ts +31 -0
  70. package/dist/src/engine/webhooks.js +68 -0
  71. package/dist/src/fetch.d.ts +2 -0
  72. package/dist/src/fetch.js +26 -0
  73. package/dist/src/generated/surface.gen.json +1 -1
  74. package/dist/src/generated/ui.gen.json +1 -1
  75. package/dist/src/index.d.ts +5 -14
  76. package/dist/src/index.js +10 -75
  77. package/dist/src/manifest.js +478 -876
  78. package/dist/src/screens/billing-portal.d.ts +3 -0
  79. package/dist/src/screens/{portal.js → billing-portal.js} +32 -58
  80. package/dist/src/screens/checkout-js.d.ts +2 -0
  81. package/dist/src/screens/checkout-js.js +84 -0
  82. package/dist/src/screens/checkout.d.ts +2 -31
  83. package/dist/src/screens/checkout.js +32 -121
  84. package/dist/src/screens/connect-onboarding.d.ts +3 -0
  85. package/dist/src/screens/{onboarding.js → connect-onboarding.js} +38 -104
  86. package/dist/src/screens/dashboard.d.ts +3 -0
  87. package/dist/src/screens/dashboard.js +116 -0
  88. package/dist/src/screens/shared.d.ts +27 -0
  89. package/dist/src/screens/shared.js +87 -0
  90. package/dist/src/screens/stripe-js.d.ts +3 -0
  91. package/dist/src/screens/stripe-js.js +95 -0
  92. package/dist/src/semantics/account.d.ts +2 -0
  93. package/dist/src/semantics/account.js +8 -0
  94. package/dist/src/semantics/account_links.d.ts +2 -0
  95. package/dist/src/semantics/account_links.js +22 -0
  96. package/dist/src/semantics/accounts.d.ts +4 -0
  97. package/dist/src/semantics/accounts.js +40 -0
  98. package/dist/src/semantics/apps.d.ts +4 -0
  99. package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
  100. package/dist/src/semantics/around.d.ts +2 -0
  101. package/dist/src/semantics/around.js +13 -0
  102. package/dist/src/semantics/balance.d.ts +2 -11
  103. package/dist/src/semantics/balance.js +3 -194
  104. package/dist/src/semantics/balance_transactions.d.ts +2 -0
  105. package/dist/src/semantics/balance_transactions.js +15 -0
  106. package/dist/src/semantics/billing_portal.d.ts +4 -0
  107. package/dist/src/semantics/billing_portal.js +36 -0
  108. package/dist/src/semantics/charges.d.ts +3 -28
  109. package/dist/src/semantics/charges.js +5 -202
  110. package/dist/src/semantics/checkout.d.ts +3 -20
  111. package/dist/src/semantics/checkout.js +21 -271
  112. package/dist/src/semantics/clock.d.ts +2 -0
  113. package/dist/src/semantics/clock.js +15 -0
  114. package/dist/src/semantics/coupons.d.ts +2 -6
  115. package/dist/src/semantics/coupons.js +2 -60
  116. package/dist/src/semantics/customers.d.ts +5 -6
  117. package/dist/src/semantics/customers.js +47 -413
  118. package/dist/src/semantics/doors.d.ts +23 -0
  119. package/dist/src/semantics/doors.js +73 -0
  120. package/dist/src/semantics/entitlements.d.ts +0 -2
  121. package/dist/src/semantics/entitlements.js +4 -95
  122. package/dist/src/semantics/events.d.ts +11 -0
  123. package/dist/src/semantics/events.js +31 -0
  124. package/dist/src/semantics/index.d.ts +2 -0
  125. package/dist/src/semantics/index.js +85 -0
  126. package/dist/src/semantics/invoice_payments.d.ts +2 -0
  127. package/dist/src/semantics/invoice_payments.js +12 -0
  128. package/dist/src/semantics/invoices.d.ts +11 -18
  129. package/dist/src/semantics/invoices.js +121 -440
  130. package/dist/src/semantics/issuing.d.ts +6 -13
  131. package/dist/src/semantics/issuing.js +58 -556
  132. package/dist/src/semantics/payment_intents.d.ts +3 -0
  133. package/dist/src/semantics/payment_intents.js +71 -0
  134. package/dist/src/semantics/payment_methods.d.ts +3 -0
  135. package/dist/src/semantics/payment_methods.js +15 -0
  136. package/dist/src/semantics/payouts.d.ts +3 -0
  137. package/dist/src/semantics/payouts.js +28 -0
  138. package/dist/src/semantics/prices.d.ts +4 -0
  139. package/dist/src/semantics/prices.js +37 -0
  140. package/dist/src/semantics/products.d.ts +4 -2
  141. package/dist/src/semantics/products.js +12 -138
  142. package/dist/src/semantics/promotion_codes.d.ts +3 -0
  143. package/dist/src/semantics/promotion_codes.js +35 -0
  144. package/dist/src/semantics/radar.d.ts +2 -2
  145. package/dist/src/semantics/radar.js +5 -74
  146. package/dist/src/semantics/refunds.d.ts +2 -9
  147. package/dist/src/semantics/refunds.js +3 -193
  148. package/dist/src/semantics/shared.d.ts +558 -55
  149. package/dist/src/semantics/shared.js +2070 -114
  150. package/dist/src/semantics/states.d.ts +462 -0
  151. package/dist/src/semantics/states.js +634 -0
  152. package/dist/src/semantics/subscriptions.d.ts +6 -11
  153. package/dist/src/semantics/subscriptions.js +37 -530
  154. package/dist/src/semantics/test_helpers.d.ts +3 -0
  155. package/dist/src/semantics/test_helpers.js +125 -0
  156. package/dist/src/semantics/transfers.d.ts +2 -2
  157. package/dist/src/semantics/transfers.js +6 -101
  158. package/dist/src/semantics/webhook_endpoints.d.ts +2 -0
  159. package/dist/src/semantics/webhook_endpoints.js +26 -0
  160. package/dist/src/server.d.ts +7 -0
  161. package/dist/src/server.js +7 -0
  162. package/generated/pack-facts.json +94 -0
  163. package/journeys/coverage.json +596 -0
  164. package/journeys/customer-life.json +6108 -0
  165. package/journeys/customer-life.outline.md +371 -0
  166. package/journeys/decisions.json +677 -0
  167. package/journeys/demand.json +706 -0
  168. package/journeys/score.json +853 -0
  169. package/journeys/unreachable.json +102 -0
  170. package/journeys/vendor-examples.json +8010 -0
  171. package/package.json +11 -20
  172. package/spec/SOURCE.md +18 -0
  173. package/spec/doc-examples.json +445 -0
  174. package/spec/fixtures3.json.gz +0 -0
  175. package/spec/openapi.json.gz +0 -0
  176. package/spec/patches.json +34 -0
  177. package/spec/sources.json +306 -0
  178. package/src/cli.ts +11 -28
  179. package/src/engine/after-payment.ts +29 -0
  180. package/src/engine/apps-secrets.ts +4 -0
  181. package/src/engine/balance.ts +32 -0
  182. package/src/engine/checkout.ts +39 -0
  183. package/src/engine/common.ts +49 -0
  184. package/src/engine/connect.ts +106 -0
  185. package/src/engine/customers.ts +10 -0
  186. package/src/engine/display.ts +18 -0
  187. package/src/engine/files.ts +28 -0
  188. package/src/engine/invoices.ts +102 -0
  189. package/src/engine/issuing.ts +66 -0
  190. package/src/engine/ledger.ts +48 -0
  191. package/src/engine/oauth.ts +18 -0
  192. package/src/engine/onboarding.ts +53 -0
  193. package/src/{stripe-params.ts → engine/params.ts} +7 -56
  194. package/src/engine/payment-intents.ts +32 -0
  195. package/src/engine/payment-methods.ts +24 -0
  196. package/src/engine/radar.ts +6 -0
  197. package/src/engine/refunds.ts +3 -0
  198. package/src/engine/renewals.ts +72 -0
  199. package/src/{stripe-twin.ts → engine/stripe.ts} +57 -400
  200. package/src/engine/subscriptions.ts +37 -0
  201. package/src/engine/transfers.ts +22 -0
  202. package/src/{stripe-version.ts → engine/version.ts} +19 -10
  203. package/src/engine/webhook-endpoints.ts +14 -0
  204. package/src/engine/webhooks.ts +70 -0
  205. package/src/fetch.ts +27 -0
  206. package/src/generated/surface.gen.json +1 -1
  207. package/src/generated/ui.gen.json +1 -1
  208. package/src/index.ts +10 -116
  209. package/src/manifest.ts +480 -904
  210. package/src/screens/{portal.tsx → billing-portal.tsx} +35 -42
  211. package/src/screens/checkout-js.ts +80 -0
  212. package/src/screens/checkout.tsx +32 -96
  213. package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +39 -85
  214. package/src/screens/dashboard.tsx +178 -0
  215. package/src/screens/shared.tsx +84 -0
  216. package/src/screens/stripe-js.ts +102 -0
  217. package/src/semantics/account.ts +10 -0
  218. package/src/semantics/account_links.ts +20 -0
  219. package/src/semantics/accounts.ts +44 -0
  220. package/src/semantics/{apps-secrets.ts → apps.ts} +17 -33
  221. package/src/semantics/around.ts +17 -0
  222. package/src/semantics/balance.ts +6 -208
  223. package/src/semantics/balance_transactions.ts +18 -0
  224. package/src/semantics/billing_portal.ts +45 -0
  225. package/src/semantics/charges.ts +8 -212
  226. package/src/semantics/checkout.ts +25 -276
  227. package/src/semantics/clock.ts +19 -0
  228. package/src/semantics/coupons.ts +5 -68
  229. package/src/semantics/customers.ts +48 -414
  230. package/src/semantics/doors.ts +79 -0
  231. package/src/semantics/entitlements.ts +3 -94
  232. package/src/semantics/events.ts +37 -0
  233. package/src/semantics/index.ts +89 -0
  234. package/src/semantics/invoice_payments.ts +16 -0
  235. package/src/semantics/invoices.ts +112 -441
  236. package/src/semantics/issuing.ts +57 -573
  237. package/src/semantics/payment_intents.ts +76 -0
  238. package/src/semantics/payment_methods.ts +20 -0
  239. package/src/semantics/payouts.ts +29 -0
  240. package/src/semantics/prices.ts +41 -0
  241. package/src/semantics/products.ts +12 -146
  242. package/src/semantics/promotion_codes.ts +38 -0
  243. package/src/semantics/radar.ts +8 -79
  244. package/src/semantics/refunds.ts +4 -216
  245. package/src/semantics/shared.ts +2089 -135
  246. package/src/semantics/states.ts +662 -0
  247. package/src/semantics/subscriptions.ts +39 -542
  248. package/src/semantics/test_helpers.ts +119 -0
  249. package/src/semantics/transfers.ts +10 -132
  250. package/src/semantics/webhook_endpoints.ts +27 -0
  251. package/src/server.ts +7 -0
  252. package/client/dashboard-api.ts +0 -286
  253. package/client/stripe-mirror.css +0 -277
  254. package/client/stripe-mirror.tsx +0 -1559
  255. package/dist/client/dashboard-api.d.ts +0 -107
  256. package/dist/client/dashboard-api.js +0 -238
  257. package/dist/client/dashboard-api.ts +0 -286
  258. package/dist/client/stripe-mirror.bundle.js +0 -236
  259. package/dist/client/stripe-mirror.css +0 -277
  260. package/dist/client/stripe-mirror.d.ts +0 -134
  261. package/dist/client/stripe-mirror.js +0 -859
  262. package/dist/client/stripe-mirror.tsx +0 -1559
  263. package/dist/src/screens/connect-oauth.d.ts +0 -27
  264. package/dist/src/screens/connect-oauth.js +0 -414
  265. package/dist/src/screens/connect-settings.d.ts +0 -22
  266. package/dist/src/screens/connect-settings.js +0 -103
  267. package/dist/src/screens/consent-skin.d.ts +0 -4
  268. package/dist/src/screens/consent-skin.js +0 -18
  269. package/dist/src/screens/financial-connections.d.ts +0 -5
  270. package/dist/src/screens/financial-connections.js +0 -90
  271. package/dist/src/screens/identity.d.ts +0 -5
  272. package/dist/src/screens/identity.js +0 -86
  273. package/dist/src/screens/onboarding.d.ts +0 -13
  274. package/dist/src/screens/portal.d.ts +0 -5
  275. package/dist/src/screens/public-details.d.ts +0 -5
  276. package/dist/src/screens/public-details.js +0 -90
  277. package/dist/src/semantics/after-payment.d.ts +0 -22
  278. package/dist/src/semantics/after-payment.js +0 -99
  279. package/dist/src/semantics/apps-secrets.d.ts +0 -2
  280. package/dist/src/semantics/billing.d.ts +0 -2
  281. package/dist/src/semantics/billing.js +0 -220
  282. package/dist/src/semantics/connect.d.ts +0 -5
  283. package/dist/src/semantics/connect.js +0 -493
  284. package/dist/src/semantics/credit-notes.d.ts +0 -2
  285. package/dist/src/semantics/credit-notes.js +0 -172
  286. package/dist/src/semantics/disputes.d.ts +0 -2
  287. package/dist/src/semantics/disputes.js +0 -51
  288. package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
  289. package/dist/src/semantics/ephemeral-keys.js +0 -34
  290. package/dist/src/semantics/files.d.ts +0 -2
  291. package/dist/src/semantics/files.js +0 -125
  292. package/dist/src/semantics/ledger.d.ts +0 -59
  293. package/dist/src/semantics/ledger.js +0 -200
  294. package/dist/src/semantics/payment-intents.d.ts +0 -18
  295. package/dist/src/semantics/payment-intents.js +0 -452
  296. package/dist/src/semantics/payment-links.d.ts +0 -2
  297. package/dist/src/semantics/payment-links.js +0 -133
  298. package/dist/src/semantics/payment-methods.d.ts +0 -20
  299. package/dist/src/semantics/payment-methods.js +0 -140
  300. package/dist/src/semantics/plans.d.ts +0 -5
  301. package/dist/src/semantics/plans.js +0 -121
  302. package/dist/src/semantics/platform.d.ts +0 -9
  303. package/dist/src/semantics/platform.js +0 -206
  304. package/dist/src/semantics/renewals.d.ts +0 -47
  305. package/dist/src/semantics/renewals.js +0 -281
  306. package/dist/src/semantics/setup-intents.d.ts +0 -2
  307. package/dist/src/semantics/setup-intents.js +0 -84
  308. package/dist/src/semantics/subscription-schedules.d.ts +0 -2
  309. package/dist/src/semantics/subscription-schedules.js +0 -119
  310. package/dist/src/semantics/tax.d.ts +0 -2
  311. package/dist/src/semantics/tax.js +0 -197
  312. package/dist/src/semantics/terminal.d.ts +0 -5
  313. package/dist/src/semantics/terminal.js +0 -182
  314. package/dist/src/semantics/test-clocks.d.ts +0 -6
  315. package/dist/src/semantics/test-clocks.js +0 -73
  316. package/dist/src/semantics/tokens.d.ts +0 -4
  317. package/dist/src/semantics/tokens.js +0 -44
  318. package/dist/src/semantics/treasury.d.ts +0 -2
  319. package/dist/src/semantics/treasury.js +0 -377
  320. package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
  321. package/dist/src/semantics/webhook-endpoints.js +0 -85
  322. package/dist/src/stripe-budget.d.ts +0 -55
  323. package/dist/src/stripe-budget.js +0 -155
  324. package/dist/src/stripe-capabilities.d.ts +0 -3
  325. package/dist/src/stripe-capabilities.js +0 -5836
  326. package/dist/src/stripe-conformance.d.ts +0 -43
  327. package/dist/src/stripe-conformance.js +0 -105
  328. package/dist/src/stripe-connector.d.ts +0 -161
  329. package/dist/src/stripe-connector.js +0 -414
  330. package/dist/src/stripe-emit.d.ts +0 -2
  331. package/dist/src/stripe-emit.js +0 -145
  332. package/dist/src/stripe-events.d.ts +0 -93
  333. package/dist/src/stripe-events.js +0 -392
  334. package/dist/src/stripe-js.d.ts +0 -4
  335. package/dist/src/stripe-js.js +0 -70
  336. package/dist/src/stripe-mirror-ui.d.ts +0 -15
  337. package/dist/src/stripe-mirror-ui.js +0 -87
  338. package/dist/src/stripe-perform-harness.d.ts +0 -9
  339. package/dist/src/stripe-perform-harness.js +0 -26
  340. package/dist/src/stripe-server.d.ts +0 -33
  341. package/dist/src/stripe-server.js +0 -429
  342. package/dist/src/stripe-shared.d.ts +0 -109
  343. package/dist/src/stripe-shared.js +0 -276
  344. package/dist/src/stripe-ui-conformance.d.ts +0 -5
  345. package/dist/src/stripe-ui-conformance.js +0 -79
  346. package/dist/src/stripe-ui-structure.d.ts +0 -3
  347. package/dist/src/stripe-ui-structure.js +0 -168
  348. package/dist/test-fixtures/stripe-known-deviations.json +0 -110
  349. package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
  350. package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
  351. package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
  352. package/dist/test-fixtures/stripe-schemas.json +0 -3813
  353. package/src/screens/connect-oauth.tsx +0 -400
  354. package/src/screens/connect-settings.tsx +0 -121
  355. package/src/screens/consent-skin.ts +0 -20
  356. package/src/screens/financial-connections.tsx +0 -101
  357. package/src/screens/identity.tsx +0 -96
  358. package/src/screens/public-details.tsx +0 -105
  359. package/src/semantics/after-payment.ts +0 -118
  360. package/src/semantics/billing.ts +0 -216
  361. package/src/semantics/connect.ts +0 -487
  362. package/src/semantics/credit-notes.ts +0 -168
  363. package/src/semantics/disputes.ts +0 -62
  364. package/src/semantics/ephemeral-keys.ts +0 -34
  365. package/src/semantics/files.ts +0 -143
  366. package/src/semantics/ledger.ts +0 -253
  367. package/src/semantics/payment-intents.ts +0 -463
  368. package/src/semantics/payment-links.ts +0 -148
  369. package/src/semantics/payment-methods.ts +0 -145
  370. package/src/semantics/plans.ts +0 -131
  371. package/src/semantics/platform.ts +0 -220
  372. package/src/semantics/renewals.ts +0 -279
  373. package/src/semantics/setup-intents.ts +0 -87
  374. package/src/semantics/subscription-schedules.ts +0 -129
  375. package/src/semantics/tax.ts +0 -220
  376. package/src/semantics/terminal.ts +0 -195
  377. package/src/semantics/test-clocks.ts +0 -77
  378. package/src/semantics/tokens.ts +0 -52
  379. package/src/semantics/treasury.ts +0 -383
  380. package/src/semantics/webhook-endpoints.ts +0 -87
  381. package/src/stripe-budget.ts +0 -181
  382. package/src/stripe-capabilities.ts +0 -5651
  383. package/src/stripe-conformance.ts +0 -124
  384. package/src/stripe-connector.ts +0 -462
  385. package/src/stripe-emit.ts +0 -157
  386. package/src/stripe-events.ts +0 -434
  387. package/src/stripe-js.ts +0 -70
  388. package/src/stripe-mirror-ui.ts +0 -90
  389. package/src/stripe-perform-harness.ts +0 -29
  390. package/src/stripe-server.ts +0 -393
  391. package/src/stripe-shared.ts +0 -297
  392. package/src/stripe-ui-conformance.ts +0 -88
  393. package/src/stripe-ui-structure.ts +0 -182
  394. package/test-fixtures/stripe-known-deviations.json +0 -110
  395. package/test-fixtures/stripe-openapi-operations.json +0 -4717
  396. package/test-fixtures/stripe-schemas.json +0 -3813
  397. /package/dist/src/{screens → engine}/industries.d.ts +0 -0
  398. /package/dist/src/{screens → engine}/industries.js +0 -0
  399. /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
  400. /package/dist/src/{semantics → engine}/test-cards.js +0 -0
  401. /package/src/{screens → engine}/industries.ts +0 -0
  402. /package/src/{semantics → engine}/test-cards.ts +0 -0
@@ -23,25 +23,23 @@
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';
33
-
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';
34
34
  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
35
 
37
36
  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
37
 
39
38
  /** 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());
39
+ const usable = (ctx: HandlerContext, link: Row): boolean => link.used !== true && Number(link.expires_at) > nowUnix(ctx.occurredAt);
41
40
 
42
41
  // the field asks, with no industry chosen until the person chooses one (an unanswered field leaves the MCC due)
43
42
  const INDUSTRIES = [{ value: '', label: 'Select your industry' }, ...MCCS.map(([value, label]) => ({ value, label }))];
44
- const OFFERED = new Set(MCCS.map(([mcc]) => mcc));
45
43
  // the fifty states and the District of Columbia, by their USPS codes (the address's `state`, ISO 3166-2 without its
46
44
  // country, docs.stripe.com/api/accounts/object)
47
45
  const STATES = ([
@@ -55,49 +53,6 @@ const STATES = ([
55
53
  ['WA', 'Washington'], ['WV', 'West Virginia'], ['WI', 'Wisconsin'], ['WY', 'Wyoming'],
56
54
  ] as const).map(([value, label]) => ({ value, label }));
57
55
 
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
56
  const field = (id: string, label: string, v: Form, extra: Partial<PaymentField> = {}): PaymentField => ({ id, label, value: v[id] ?? '', ...extra });
102
57
  const addressFields = (prefix: string, v: Form): PaymentField[] => [
103
58
  field(`${prefix}line1`, 'Address', v, { autoComplete: 'address-line1' }), field(`${prefix}city`, 'City', v),
@@ -107,7 +62,7 @@ const addressFields = (prefix: string, v: Form): PaymentField[] => [
107
62
  /** The industry the field shows: what the form sent, else what the account already holds, when the page offers it. */
108
63
  const industryOf = (mcc: unknown): string => (typeof mcc === 'string' && OFFERED.has(mcc.trim()) ? mcc.trim() : '');
109
64
 
110
- function page(ctx: SemanticsContext, sessionUrl: string, link: Row, kind: Kind, values: Form = {}, error?: string): Response {
65
+ function page(ctx: HandlerContext, sessionUrl: string, link: Row, kind: Kind, values: Form = {}, error?: string): Response {
111
66
  const v = values;
112
67
  const business: PaymentField[] = [
113
68
  field('mcc', 'Industry', v, { options: INDUSTRIES, value: industryOf(v.mcc ?? (ctx.get('account', String(link.account))?.business_profile as Row | undefined)?.mcc) }),
@@ -157,19 +112,19 @@ function unmodelled(account: Row): Response {
157
112
  }
158
113
 
159
114
  /** 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 {
115
+ function incomplete(ctx: HandlerContext, sessionUrl: string, link: Row, kind: Kind, v: Form, missing: string[]): Response {
161
116
  return page(ctx, sessionUrl, link, kind, v, `Still required: ${missing.join(', ')}.`);
162
117
  }
163
118
 
164
119
  /** The owner submits everything: it is stored on the account, nothing is due, and charges, payouts and the requested
165
120
  * capabilities are enabled. */
166
- async function submit(ctx: SemanticsContext, link: Row, kind: Kind, v: Form, ip: string): Promise<void> {
121
+ async function submit(ctx: HandlerContext, link: Row, kind: Kind, v: Form, ip: string): Promise<void> {
167
122
  const id = String(link.account);
168
123
  const account = ctx.get('account', id);
169
124
  if (!account) return;
170
125
  const caps: Row = { ...((account.capabilities as Row | undefined) ?? {}) };
171
126
  for (const [cap, status] of Object.entries(caps)) {
172
- if (status !== 'active' && !ctx.legal('capability', 'status', OPERATION.id, String(status), 'active', cap, 'vendor')) caps[cap] = 'active';
127
+ if (status !== 'active') caps[cap] = 'active';
173
128
  }
174
129
  const due = currentlyDue(kind, v);
175
130
  const requirements = { ...((account.requirements as Row | undefined) ?? {}), currently_due: due, eventually_due: due, past_due: [], disabled_reason: null, current_deadline: null };
@@ -178,6 +133,7 @@ async function submit(ctx: SemanticsContext, link: Row, kind: Kind, v: Form, ip:
178
133
  const person = { first_name: v.first_name, last_name: v.last_name, email: v.email, phone: v.phone, dob: { day, month, year }, address: addressOf(''), ssn_last_4_provided: true, id_number_provided: false };
179
134
  const company = kind === 'company';
180
135
  const settings = (account.settings as Row | undefined) ?? {};
136
+ const individual = company ? null : await created(ctx, 'person', { ...person, account: id }, { relationship: { representative: true }, metadata: {} });
181
137
  await ctx.write('account', id, {
182
138
  details_submitted: true, charges_enabled: due.length === 0, payouts_enabled: due.length === 0, capabilities: caps, requirements,
183
139
  business_type: kind,
@@ -185,8 +141,8 @@ async function submit(ctx: SemanticsContext, link: Row, kind: Kind, v: Form, ip:
185
141
  settings: { ...settings, payments: { ...((settings.payments as Row | undefined) ?? {}), statement_descriptor: v.statement_descriptor } },
186
142
  ...(company
187
143
  ? { 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
- : { individual: { ...person, id: `person_${id}` } }),
189
- tos_acceptance: { date: Number(ctx.now()), ip, user_agent: null },
144
+ : { individual: individual!.id }),
145
+ tos_acceptance: { date: nowUnix(ctx.occurredAt), ip, user_agent: null },
190
146
  }, 'account.updated');
191
147
  if (company) {
192
148
  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 +161,7 @@ const SESSIONS = 'https://connect.stripe.com/setup/s/';
205
161
 
206
162
  /** Visiting an Account Link consumes it and sends the owner to their onboarding session; a used or expired one sends
207
163
  * them to refresh_url for a fresh link. */
208
- async function visit(ctx: SemanticsContext, id: string): Promise<Response> {
164
+ async function visit(ctx: HandlerContext, id: string): Promise<Response> {
209
165
  const link = ctx.get(LINK, id);
210
166
  if (!link) return gone();
211
167
  if (!usable(ctx, link)) return seeOther(String(link.refresh_url));
@@ -215,29 +171,27 @@ async function visit(ctx: SemanticsContext, id: string): Promise<Response> {
215
171
  }
216
172
 
217
173
  /** 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
- };
174
+ export async function screen(ctx: HandlerContext): Promise<Response> {
175
+ const request = ctx.call.request;
176
+ const path = new URL(request.url).pathname;
177
+ const entry = /^\/setup\/(acctlink_[A-Za-z0-9_]+)\/?$/.exec(path);
178
+ const at = /^\/setup\/s\/(onbs_[A-Za-z0-9_]+)\/?$/.exec(path);
179
+ if (entry && request.method === 'GET') return visit(ctx, entry[1]!);
180
+ if (!at || (request.method !== 'GET' && request.method !== 'POST')) return notFound();
181
+ const sid = at[1]!;
182
+ const form = request.method === 'POST' ? await formOf(request.clone()) : {};
183
+ // the session the visited link opened, until it is submitted
184
+ const stored = ctx.rowsRaw(LINK).find((l) => l._session === sid && l._submitted !== true);
185
+ const link = stored ? ctx.get(LINK, String(stored.id)) : undefined;
186
+ if (!stored || !link) return gone();
187
+ const sessionUrl = `${SESSIONS}${sid}`;
188
+ const account = ctx.get('account', String(link.account)) ?? {};
189
+ const kind = kindOf(account);
190
+ if (!kind) return unmodelled(account);
191
+ if (request.method === 'GET') return page(ctx, sessionUrl, link, kind);
192
+ const missing = currentlyDue(kind, form);
193
+ if (missing.length) return incomplete(ctx, sessionUrl, link, kind, form, missing);
194
+ await ctx.write(LINK, String(stored.id), { _submitted: true }, 'account_link.submitted');
195
+ await submit(ctx, link, kind, form, request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '127.0.0.1');
196
+ return seeOther(String(link.return_url));
243
197
  }
@@ -0,0 +1,178 @@
1
+ // THE DASHBOARD — dashboard.stripe.com's pages an operator sets what Stripe's API cannot (docs/contributing/architecture.md,
2
+ // "Screens": a workspace): Public details (/settings/public) and the Connect OAuth settings
3
+ // (/settings/connect/onboarding-options/oauth), Radar lists and test Issuing funding.
4
+ // The operator controls prepare the cited real application calls; other Dashboard pages answer Stripe's 404.
5
+ //
6
+ // THE DASHBOARD'S PUBLIC DETAILS — a workspace page (docs/contributing/architecture.md, "Screens") of the Dashboard
7
+ // (manifest screen `dashboard`), at dashboard.stripe.com/settings/public: Settings → Business → Public details, where
8
+ // the operator sets what their customers see ("Public Details → Business Settings",
9
+ // support.stripe.com/questions/update-account-name-shown-in-the-dashboard). Its business name is the account's
10
+ // `business_profile.name`, "The customer-facing business name" (docs.stripe.com/api/accounts/object), and it is the
11
+ // name Checkout shows: "You can change a Checkout page's name by modifying the Business name field"
12
+ // (docs.stripe.com/payments/checkout/customization/appearance). Stripe's API has no call that changes the platform's
13
+ // own account (POST /v1/accounts/{account} updates a connected one), so this page is the only way to the act, and
14
+ // saving it writes the platform account's stored record (../engine/connect.ts), which GET /v1/account,
15
+ // Checkout and the customer portal then read. The Dashboard's account name (`settings.dashboard.display_name`,
16
+ // Settings → Account) is a separate, internal setting, "for internal use only" (the same support article), and this
17
+ // page does not touch it. Authored from plain markup under Stripe's type; nothing of Stripe's page is copied.
18
+ //
19
+ // Where the documentation stops and the twin decides: the page holds the business name only (the real page's
20
+ // support details, website and statement descriptor are not modelled here); the name is required and at most 5000
21
+ // characters (the spec's bound on business_profile.name), which the browser holds the field to and the page checks
22
+ // again for a client that is not a browser; no sign-in guards the page. The form posts to the page's own address
23
+ // and the save redirects to its own path. Saving is an update of an account that exists (the first save included, though
24
+ // the twin stores the platform's record only then), so every save is sent as account.updated.
25
+ //
26
+ // THE DASHBOARD'S CONNECT OAUTH SETTINGS — a workspace page (docs/contributing/architecture.md, "Screens") of the
27
+ // Dashboard (manifest screen `dashboard`), at dashboard.stripe.com/settings/connect/onboarding-options/oauth, where a
28
+ // platform starts an OAuth integration: "Enable onboarding accounts with OAuth", "Copy your client_id, a unique
29
+ // identifier for your platform that's generated by Stripe", and "Set your redirect_uri ... You must specify all redirect
30
+ // URLs in your platform settings. If you don't include the redirect_uri parameter in your request, Stripe defaults to
31
+ // using the first address you've configured" (docs.stripe.com/connect/oauth-standard-accounts). The test client_id
32
+ // "allows you to: Set your redirect_uri to a non-HTTPS URL; Set your redirect_uri to localhost"
33
+ // (docs.stripe.com/connect/testing#using-oauth). Stripe's API has no call that reads or changes these settings (GET
34
+ // /v1/account does not carry the client_id), so this page is the only way to them, and connect.stripe.com/oauth/authorize
35
+ // (./connect-oauth.tsx) reads what it saves. Authored from plain markup under Stripe's type; nothing of Stripe's page is
36
+ // copied.
37
+ //
38
+ // Where the documentation stops and the twin decides: the World has one platform, and its test client_id is
39
+ // `ca_twin_self` (PLATFORM_CLIENT_ID; the platform account is acct_twin_self), the same in every World, shown on the
40
+ // page in an element a runner reads (data-testid="connect-client-id"). OAuth starts disabled. The page is served at
41
+ // the docs' address, at /test/… (the docs' test-mode link) and at /settings/connect (the address Cal.com's setup
42
+ // guide gives). The redirect URIs are one form field, one per line (or comma-separated, as the reference describes
43
+ // them), saved whole: each an absolute http or https URL without a fragment; http and localhost are allowed, as the
44
+ // test client_id allows them. No sign-in guards the page. The form posts to the
45
+ // page's own address and the save redirects to it.
46
+ import type { HandlerContext } from '@volter/world-core';
47
+ import { flowPage } from '@volter/world-ui';
48
+ import { VL } from '../engine/radar.ts';
49
+ import { nowUnix } from '../engine/stripe.ts';
50
+ import type { Row } from '../engine/common.ts';
51
+ import { platformAccountDefault } from '../engine/connect.ts';
52
+ import { publicBusinessName } from '../engine/display.ts';
53
+ import { PLATFORM_ACCOUNT_ID } from '../engine/stripe.ts';
54
+ import { formOf, notFound } from './shared.tsx';
55
+ import { created } from '../semantics/shared.ts';
56
+ const redirect = (path: string): Response => new Response(null, { status: 303, headers: { location: path } });
57
+ const invalid = (message: string): Response => flowPage({ css: [], title: 'Stripe', status: 400, body: <main><p role="alert">{message}</p></main> });
58
+
59
+ const PUBLIC_DETAILS_PATH = '/settings/public';
60
+ const MAX_NAME = 5000;
61
+
62
+ const DETAILS_CSS = `
63
+ * { box-sizing: border-box; }
64
+ body { margin: 0; background: #f6f8fa; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; font-size: 14px; }
65
+ .pd { max-width: 640px; margin: 48px auto; padding: 0 16px; }
66
+ .pd h1 { font-size: 28px; margin: 16px 0 4px; }
67
+ .pd-lead { color: #697386; margin: 0 0 24px; }
68
+ .pd-card { background: #ffffff; border: 1px solid #e3e8ee; border-radius: 8px; padding: 24px; }
69
+ .pd-card label { display: block; font-weight: 600; margin-bottom: 6px; }
70
+ .pd-card small { display: block; color: #697386; margin-bottom: 8px; }
71
+ .pd-card input { width: 100%; padding: 8px 10px; border: 1px solid #c1c9d2; border-radius: 6px; font: inherit; }
72
+ .pd-actions { margin-top: 20px; display: flex; justify-content: flex-end; }
73
+ .pd-actions button { background: #635bff; color: #ffffff; border: 0; border-radius: 6px; padding: 8px 16px; font: inherit; font-weight: 600; cursor: pointer; }
74
+ .pd-notice { padding: 10px 12px; border-radius: 6px; margin-bottom: 16px; }
75
+ .pd-error { background: #fff0f3; color: #df1b41; }
76
+ .pd-saved { background: #e7f8ef; color: #0e6245; }
77
+ `;
78
+
79
+ function detailsPage(account: Row | undefined, opts: { value?: string; error?: string; saved?: boolean } = {}): Response {
80
+ const named = (account?.business_profile as Row | undefined)?.name;
81
+ const value = opts.value ?? (typeof named === 'string' ? named : '');
82
+ return flowPage({
83
+ title: 'Public details – Stripe',
84
+ css: [DETAILS_CSS],
85
+ ...(opts.error ? { status: 400 } : {}),
86
+ body: (
87
+ <main className="pd">
88
+ <h1>Public details</h1>
89
+ <p className="pd-lead">What your customers see on Checkout, the customer portal, receipts and invoices. Currently shown: <strong data-testid="public-business-name">{publicBusinessName(account)}</strong></p>
90
+ {opts.error ? <p className="pd-notice pd-error" role="alert">{opts.error}</p> : null}
91
+ {opts.saved ? <p className="pd-notice pd-saved" role="status">Your public details were saved.</p> : null}
92
+ <form className="pd-card" method="post">
93
+ <label htmlFor="business_name">Business name</label>
94
+ <small>The name customers see when they pay you.</small>
95
+ <input id="business_name" name="business_name" autoComplete="organization" defaultValue={value} required maxLength={MAX_NAME} />
96
+ <div className="pd-actions"><button type="submit">Save</button></div>
97
+ </form>
98
+ </main>
99
+ ),
100
+ });
101
+ }
102
+
103
+ /** Save the business name on the platform's own account: the stored record, made from Stripe's default the first
104
+ * time anything is saved, and otherwise changed in place (its other public details kept). */
105
+ async function save(ctx: HandlerContext, name: string): Promise<void> {
106
+ const held = ctx.get('account', PLATFORM_ACCOUNT_ID);
107
+ const profile = { business_profile: { ...(((held ?? platformAccountDefault()).business_profile as Row | undefined) ?? {}), name } };
108
+ await (held ? ctx.write('account', PLATFORM_ACCOUNT_ID, profile, 'account.update') : created(ctx, 'account', { ...platformAccountDefault(), ...profile }, {}, { operation: 'account.update' }));
109
+ }
110
+
111
+ /** What the page says of a name no browser sends (the field is required and bounded), for a client that sends it. */
112
+ function nameRefused(name: string): string | undefined {
113
+ return !name ? 'Enter your business name.' : name.length > MAX_NAME ? `Your business name must be at most ${MAX_NAME} characters.` : undefined;
114
+ }
115
+
116
+ async function publicDetails(ctx: HandlerContext): Promise<Response> {
117
+ const request = ctx.call.request;
118
+ const url = new URL(request.url);
119
+ if (request.method !== 'GET' && request.method !== 'POST') return notFound();
120
+ const form = request.method === 'POST' ? await formOf(request.clone()) : {};
121
+ if (request.method === 'GET') return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { saved: url.searchParams.has('saved') });
122
+ const name = (form.business_name ?? '').trim();
123
+ const refused = nameRefused(name);
124
+ if (refused) return detailsPage(ctx.get('account', PLATFORM_ACCOUNT_ID), { value: form.business_name ?? '', error: refused });
125
+ await save(ctx, name);
126
+ // a query-only reference: the page's own path, wherever it is mounted and with or without its trailing slash
127
+ return new Response(null, { status: 303, headers: { location: '?saved=1' } });
128
+ }
129
+
130
+ /** Dashboard settings, Radar list creation and test Issuing funding; other pages answer Stripe's 404. */
131
+ export async function screen(ctx: HandlerContext): Promise<Response> {
132
+ const path = new URL(ctx.call.request.url).pathname.replace(/\/+$/, '');
133
+ // source: https://docs.stripe.com/radar/lists "Use the Dashboard or the API to create lists."
134
+ // source: https://docs.stripe.com/radar/lists "Enter a name for the list"
135
+ // source: https://docs.stripe.com/radar/lists "Select the type of list to create."
136
+ // source: https://docs.stripe.com/radar/lists "Click Add to save your new list."
137
+ // Where the documentation stops: it names Dashboard controls, not their browser request routes or form wire.
138
+ // This authored page uses /radar/lists for GET and POST, /radar/lists/{id} for its redirect/detail address,
139
+ // and a 303 redirect after saving. The HTML field names name, alias and item_type are the twin's choices;
140
+ // item_type values are the pinned API's enums, not published Dashboard form values. The alias fallback's
141
+ // lowercase/underscore algorithm is also authored; Stripe says it generates an alias but gives no algorithm.
142
+ if (/^\/radar\/lists(?:\/[^/]+)?$/.test(path)) {
143
+ if (ctx.call.request.method === 'POST') {
144
+ const form = await formOf(ctx.call.request);
145
+ const types = ['string', 'case_sensitive_string', 'card_fingerprint', 'card_bin', 'customer_id', 'email', 'ip_address', 'country', 'sepa_debit_fingerprint', 'us_bank_account_fingerprint'];
146
+ if (!form.name?.trim() || !types.includes(form.item_type ?? '')) return invalid('Enter a name and select a list type.');
147
+ const alias = form.alias?.trim() || form.name.toLowerCase().replace(/[^a-z0-9]+/g, '_');
148
+ // Where the page stops: an operator has no login identity in the World; created_by is its synthetic operator.
149
+ const list = await created(ctx, VL, { name: form.name.trim(), alias, item_type: form.item_type }, {
150
+ created_by: 'twin', livemode: false, metadata: {},
151
+ list_items: { object: 'list', data: [], has_more: false, url: '/v1/radar/value_list_items' },
152
+ });
153
+ await ctx.write(VL, String(list.id), { list_items: { object: 'list', data: [], has_more: false, url: `/v1/radar/value_list_items?value_list=${list.id}` } }, 'radar.value_list.updated');
154
+ return redirect(`/radar/lists/${list.id}`);
155
+ }
156
+ return flowPage({ css: [], title: 'Lists – Radar – Stripe', body: <main><h1>Radar Lists</h1><form method="post" action="/radar/lists"><h2>New list</h2><label>Name<input name="name" required /></label><label>Alias<input name="alias" /></label><label>List type<select name="item_type"><option value="customer_id">Customer ID</option><option value="email">Email</option><option value="card_fingerprint">Card fingerprint</option><option value="ip_address">IP address</option><option value="string">String</option></select></label><button>Add</button></form><ul>{ctx.rows(VL).map(row => <li key={String(row.id)}>{String(row.name)} ({String(row.alias)})</li>)}</ul></main> });
157
+ }
158
+ // source: https://docs.stripe.com/issuing/testing "Before you create test transactions, you must add test funds to the Issuing balance on your account."
159
+ // source: https://docs.stripe.com/issuing/testing "You can create test top-ups in the Dashboard, or with the Top-ups API"
160
+ // Where the documentation stops: it states the Dashboard funding act, not a browser request path or form wire.
161
+ // The authored GET/POST address and 303 redirect are /test/issuing/balance. The form name amount, its positive
162
+ // integer validation and its units (USD cents for this US sandbox) are the twin's choices, not published form
163
+ // fields. Its POST records the balance change in the vendor ledger, with source=null (permitted by the pinned
164
+ // schema) and immediate availability for this sandbox control; the page states neither that source nor timing.
165
+ // No private Topup object is seeded or answered, and the uncalled Top-ups API remains the gap.
166
+ if (path === '/test/issuing/balance') {
167
+ if (ctx.call.request.method === 'POST') {
168
+ const form = await formOf(ctx.call.request);
169
+ const amount = Number(form.amount);
170
+ if (!Number.isInteger(amount) || amount <= 0) return invalid('Enter a positive amount in cents.');
171
+ await created(ctx, 'balance_transaction', {}, { amount, currency: 'usd', fee: 0, net: amount, type: 'topup', reporting_category: 'topup', source: null, status: 'available', balance_type: 'issuing', available_on: Number(nowUnix(ctx.occurredAt)), fee_details: [] });
172
+ return redirect('/test/issuing/balance');
173
+ }
174
+ return flowPage({ css: [], title: 'Test Issuing balance – Stripe', body: <main><h1>Test Issuing balance</h1><form method="post"><label>Amount<input name="amount" type="number" min="1" required /></label><button>Add test funds</button></form></main> });
175
+ }
176
+ return path === PUBLIC_DETAILS_PATH ? publicDetails(ctx) : notFound();
177
+ }
178
+
@@ -0,0 +1,84 @@
1
+ // What Stripe's screens share (docs/contributing/architecture.md, "Pack layout": screens/shared.tsx): the skin over
2
+ // @volter/world-ui's consent piece for the flows where a person agrees to something (connected-account onboarding,
3
+ // identity verification, linking a bank), a page's form, and the card a customer types on Checkout and the portal.
4
+ export const STRIPE_CONSENT_SKIN = `
5
+ body { background: #f6f9fc; color: #1a1f36; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, sans-serif; }
6
+ .consent-badge { background: #ffffff; border: 1px solid #e3e8ee; color: #1a1f36; }
7
+ .consent-vendor { background: #635bff; border-color: #635bff; }
8
+ .consent-link { border-color: #c1c9d2; }
9
+ .consent-box { background: #ffffff; border-color: #e3e8ee; }
10
+ .consent-who small, .consent-permission summary small, .consent-note { color: #697386; }
11
+ .consent-actions { border-color: #e3e8ee; }
12
+ .consent-deny { background: #ffffff; border-color: #c1c9d2; color: #1a1f36; }
13
+ .consent-allow { background: #635bff; border-color: #635bff; color: #ffffff; }
14
+ `;
15
+
16
+ /** The form fields a page's POST carries, as a browser posts them. */
17
+ export async function formOf(request: Request): Promise<Record<string, string>> {
18
+ return Object.fromEntries(new URLSearchParams(await request.text()));
19
+ }
20
+
21
+ export const seeOther = (location: string): Response => new Response(null, { status: 302, headers: { location } });
22
+
23
+ export const DECLINES: Record<string, string> = {
24
+ '4000000000000002': 'Your card was declined.',
25
+ '4000000000009995': 'Your card has insufficient funds.',
26
+ '4000000000000069': 'Your card has expired.',
27
+ '4000000000000127': "Your card's security code is incorrect.",
28
+ };
29
+
30
+ export const luhn = (digits: string): boolean => {
31
+ let sum = 0;
32
+ for (let i = 0; i < digits.length; i++) {
33
+ let d = Number(digits[digits.length - 1 - i]);
34
+ if (i % 2 === 1) { d *= 2; if (d > 9) d -= 9; }
35
+ sum += d;
36
+ }
37
+ return digits.length >= 12 && sum % 10 === 0;
38
+ };
39
+
40
+ export const money = (minor: unknown, currency: unknown): string => {
41
+ const c = typeof currency === 'string' ? currency.toUpperCase() : 'USD';
42
+ const zeroDecimal = ['JPY', 'KRW', 'VND', 'CLP', 'ISK', 'UGX', 'XAF', 'XOF'].includes(c);
43
+ return new Intl.NumberFormat('en-US', { style: 'currency', currency: c }).format((Number(minor) || 0) / (zeroDecimal ? 1 : 100));
44
+ };
45
+
46
+ export const COUNTRIES = [['US', 'United States'], ['GB', 'United Kingdom'], ['CA', 'Canada'], ['DE', 'Germany'], ['FR', 'France'], ['AU', 'Australia'], ['JP', 'Japan']].map(([value, label]) => ({ value: value!, label: label! }));
47
+
48
+ /** Cards that are saved but decline when charged. */
49
+ export const DECLINES_WHEN_CHARGED: Record<string, string> = { '4000000000000341': 'Your card was declined.' };
50
+
51
+ /** The expiry as the customer typed it. Stripe's field formats itself as it is typed: the digits `1230` read `12 / 30`,
52
+ * and a first digit above 1 is a month on its own (`5` reads `05 / `), so a person types the digits straight through
53
+ * (Checkout's own field, seen on checkout.stripe.com; docs.stripe.com/payments/checkout names no format to type). The
54
+ * page's script (@volter/world-ui Payment, `format: 'card-expiry'`) shows it formatted; this reads what is posted either
55
+ * way: `MM / YY`, `MM/YYYY`, or the bare digits `MMYY`, `MYY` (`530`) and `MMYYYY`; a month past 12 is read, and refused
56
+ * as invalid by cardAnswer. */
57
+ export function parseExpiry(raw: string): { month: number; year: number } | undefined {
58
+ const split = /^\s*(\d{1,2})\s*\/\s*(\d{2}|\d{4})\s*$/.exec(raw);
59
+ const digits = raw.replace(/\s/g, '');
60
+ const bare = split ? undefined : /^(\d{2})(\d{2}|\d{4})$/.exec(digits) ?? /^([2-9])(\d{2})$/.exec(digits);
61
+ const m = split ?? bare;
62
+ if (!m) return undefined;
63
+ return { month: Number(m[1]), year: m[2]!.length === 2 ? 2000 + Number(m[2]) : Number(m[2]) };
64
+ }
65
+
66
+ /** The card's answer: undefined when it pays, else what Checkout says under the button. */
67
+ export function cardAnswer(v: Record<string, string>, nowSeconds: number, chargesNow: boolean): string | undefined {
68
+ const digits = (v.cardNumber ?? '').replace(/\D/g, '');
69
+ if (!digits) return 'Your card number is incomplete.';
70
+ if (DECLINES[digits]) return DECLINES[digits];
71
+ if (chargesNow && DECLINES_WHEN_CHARGED[digits]) return DECLINES_WHEN_CHARGED[digits];
72
+ if (!luhn(digits)) return 'Your card number is invalid.';
73
+ const exp = parseExpiry(v.cardExpiry ?? '');
74
+ if (!exp) return "Your card's expiration date is incomplete.";
75
+ const { month, year } = exp;
76
+ if (month < 1 || month > 12) return "Your card's expiration date is invalid.";
77
+ const now = new Date(nowSeconds * 1000);
78
+ if (year < now.getUTCFullYear() || (year === now.getUTCFullYear() && month < now.getUTCMonth() + 1)) return "Your card's expiration year is in the past.";
79
+ if (!/^\d{3,4}$/.test(v.cardCvc ?? '')) return "Your card's security code is incomplete.";
80
+ return undefined;
81
+ }
82
+
83
+ /** Stripe's answer for a page of one of its hosts the twin does not serve. */
84
+ export const notFound = (): Response => new Response('Not Found', { status: 404, headers: { 'content-type': 'text/plain; charset=utf-8' } });
@@ -0,0 +1,102 @@
1
+ // Stripe.js 7.3.1 is Dub’s pinned browser SDK (apps/web/package.json:49 and pnpm-lock.yaml).
2
+ // Its loader requests a release-train script, reads window.Stripe and registers its wrapper.
3
+ // The shipped upgrade button calls redirectToCheckout({ sessionId }); hosted-checkout.d.ts declares that
4
+ // navigation and its promise that settles only on failure. Other SDK flows have no demanded caller.
5
+ import type { HandlerContext } from '@volter/world-core';
6
+ import { notFound } from './shared.tsx';
7
+
8
+ /** The release train a script path names: `/v3`, `/v3/` or `/v3/stripe.js` → 3, `/<train>/stripe.js` → the train. */
9
+ function stripeJsTrain(pathname: string): 3 | string | undefined {
10
+ return /^\/v3(\/|\/stripe\.js)?$/.test(pathname) ? 3 : /^\/([a-z]+)\/stripe\.js$/.exec(pathname)?.[1];
11
+ }
12
+
13
+ // source: archive:https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.6.0.tgz#sha256=13e61419e9076ab440cc636e34d3ea5df3fc62bbf2d6a80d3fd718e8cd47f0a2!/package/dist/stripe-js/checkout.d.ts "loadActions: () => Promise<StripeCheckoutLoadActionsResult>"
14
+ function script(version: 3 | string): string {
15
+ return `(function () {
16
+ var version = ${JSON.stringify(version)};
17
+ function gap(name) {
18
+ return function () { throw new Error("Stripe.js (Volter twin): stripe." + name + " is not modelled by this twin yet"); };
19
+ }
20
+ function initCheckout(options) {
21
+ var session, root, fields = {}, listeners = {};
22
+ function emit(name, value) { (listeners[name] || []).forEach(function (fn) { fn(value); }); }
23
+ function call(action, values) {
24
+ var secret = options.clientSecret;
25
+ var id = secret.split("_secret_")[0];
26
+ return fetch("https://api.stripe.com/v1/payment_pages/" + encodeURIComponent(id) + "/" + action, {
27
+ method: "POST", headers: { "content-type": "application/json" },
28
+ body: JSON.stringify(Object.assign({ client_secret: secret }, values || {}))
29
+ }).then(function (r) { return r.json(); }).then(function (result) {
30
+ if (result.type === "success") { session = result.session; emit("change", session); }
31
+ return result;
32
+ });
33
+ }
34
+ var actions = {
35
+ getSession: function () { return session; },
36
+ applyPromotionCode: function (code) { return call("apply_promotion_code", { promotion_code: code }); },
37
+ removePromotionCode: function () { return call("remove_promotion_code"); },
38
+ confirm: function (params) {
39
+ return call("confirm", Object.assign({}, params || {}, { payment_details: fields })).then(function (result) {
40
+ if (result.type === "success" && (!params || params.redirect !== "if_required") && result.return_url) window.location.assign((params && params.returnUrl) || result.return_url);
41
+ return result;
42
+ });
43
+ }
44
+ };
45
+ var elementListeners = {};
46
+ function elementEmit(name, value) { (elementListeners[name] || []).forEach(function (fn) { fn(value); }); }
47
+ var element = {
48
+ on: function (name, fn) { (elementListeners[name] || (elementListeners[name] = [])).push(fn); return element; },
49
+ off: function (name, fn) { elementListeners[name] = (elementListeners[name] || []).filter(function (f) { return f !== fn; }); return element; },
50
+ mount: function (target) {
51
+ root = typeof target === "string" ? document.querySelector(target) : target;
52
+ [["cardNumber", "Card number", "cc-number"], ["cardExpiry", "Expiration", "cc-exp"], ["cardCvc", "CVC", "cc-csc"], ["billingName", "Cardholder name", "cc-name"]].forEach(function (field) {
53
+ var label = document.createElement("label"), input = document.createElement("input");
54
+ label.textContent = field[1]; input.name = field[0]; input.autocomplete = field[2];
55
+ input.addEventListener("input", function () { fields[field[0]] = input.value; elementEmit("change", { empty: !fields.cardNumber, complete: !!(fields.cardNumber && fields.cardExpiry && fields.cardCvc), value: { type: "card" } }); });
56
+ label.appendChild(input); root.appendChild(label);
57
+ });
58
+ Promise.resolve().then(function () { elementEmit("ready", element); });
59
+ },
60
+ unmount: function () { if (root) root.replaceChildren(); root = null; },
61
+ destroy: function () { element.unmount(); elementListeners = {}; },
62
+ update: function () {}
63
+ };
64
+ return {
65
+ on: function (name, fn) { (listeners[name] || (listeners[name] = [])).push(fn); },
66
+ loadActions: function () { return call("init").then(function (r) { return r.type === "success" ? { type: "success", actions: actions } : r; }); },
67
+ createPaymentElement: function () { return element; }, getPaymentElement: function () { return element; },
68
+ changeAppearance: function () {}, loadFonts: function () {}
69
+ };
70
+ }
71
+ function Stripe(publishableKey) {
72
+ if (typeof publishableKey !== "string" || !publishableKey) throw new Error("Stripe.js (Volter twin): Stripe() needs a publishable key");
73
+ var known = {
74
+ initCheckout: initCheckout,
75
+ _registerWrapper: function () {},
76
+ redirectToCheckout: function (options) {
77
+ if (!options || typeof options.sessionId !== "string" || !options.sessionId) return gap("redirectToCheckout without a sessionId")();
78
+ window.location.assign("https://checkout.stripe.com/c/pay/" + encodeURIComponent(options.sessionId));
79
+ return new Promise(function () {});
80
+ }
81
+ };
82
+ return new Proxy(known, {
83
+ get: function (target, name) {
84
+ if (name in target) return target[name];
85
+ if (typeof name === "symbol" || name === "then") return undefined;
86
+ return gap(String(name));
87
+ }
88
+ });
89
+ }
90
+ Stripe.version = version;
91
+ window.Stripe = Stripe;
92
+ })();
93
+ `;
94
+ }
95
+
96
+ /** js.stripe.com's script for a GET of its path; Stripe's 404 for anything else there. */
97
+ export async function screen(ctx: HandlerContext): Promise<Response> {
98
+ const request = ctx.call.request;
99
+ const train = request.method === 'GET' || request.method === 'HEAD' ? stripeJsTrain(new URL(request.url).pathname) : undefined;
100
+ if (train === undefined) return notFound();
101
+ return new Response(script(train), { headers: { 'content-type': 'application/javascript; charset=utf-8', 'access-control-allow-origin': '*' } });
102
+ }
@@ -0,0 +1,10 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ import { PLATFORM_ACCOUNT_ID } from '../engine/stripe.ts';
3
+ import { actingAccount, accountMissing } from './shared.ts';
4
+
5
+ // source: spec:GetAccount "Retrieves the details of an account."
6
+ export async function GetAccount(ctx: HandlerContext): Promise<Response> {
7
+ const id = actingAccount(ctx) ?? PLATFORM_ACCOUNT_ID;
8
+ const account = ctx.get('account', id);
9
+ return account ? ctx.reply(ctx.expand('account', account)) : accountMissing(ctx, id);
10
+ }
@@ -0,0 +1,20 @@
1
+ // Stripe's account_links operations (docs/contributing/architecture.md, "What an author writes"): each handler over the
2
+ // handler contract's context, computing with the engine (../engine/).
3
+ import type { HandlerContext } from '@volter/world-core';
4
+ import { nowUnix } from '../engine/stripe.ts';
5
+ import { accountMissing, fail } from './shared.ts';
6
+ // ── hosted onboarding links and embedded-component sessions ──
7
+ export async function PostAccountLinks(ctx: HandlerContext): Promise<Response> {
8
+ const account = typeof ctx.params.account === 'string' ? ctx.params.account : '';
9
+ if (!account) return fail(ctx, 'Missing required param: account.', 400, 'parameter_missing');
10
+ if (!ctx.get('account', account)) return accountMissing(ctx, account, 400);
11
+ const linkType = typeof ctx.params.type === 'string' ? ctx.params.type : '';
12
+ if (linkType !== 'account_onboarding' && linkType !== 'account_update') return fail(ctx, 'Invalid account link type: must be account_onboarding or account_update.', 400, 'parameter_invalid_string_enum');
13
+ // the owner is sent back to return_url when done, and to refresh_url for a new link once this one is used or expired
14
+ for (const k of ['refresh_url', 'return_url']) if (typeof ctx.params[k] !== 'string' || !ctx.params[k]) return fail(ctx, `Missing required param: ${k}.`, 400, 'parameter_missing');
15
+ const now = Number(nowUnix(ctx.occurredAt));
16
+ const id = ctx.mint('account_link');
17
+ const link = { object: 'account_link', created: now, expires_at: now + 300, url: `https://connect.stripe.com/setup/${id}` };
18
+ await ctx.write('account_link', id, { ...link, account, type: linkType, refresh_url: ctx.params.refresh_url, return_url: ctx.params.return_url, used: false }, 'account_link.create');
19
+ return ctx.reply(link);
20
+ }