@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
@@ -16,20 +16,15 @@
16
16
  // (the account's default) enables cancellation at period end, updating the payment method and invoice history;
17
17
  // changing the plan from the portal is not offered yet. A new card is saved without a charge, so a card that
18
18
  // declines only when charged (4000000000000341) is saved and declines when the invoice is paid.
19
- import { semanticsContext, type SemanticsContext } from '@volter/world-core';
20
- import { flowPage, Portal, PORTAL_CSS, type PaymentField, type PortalItem } from '@volter/world-ui';
21
- import surface from '../generated/surface.gen.json' with { type: 'json' };
22
- import { manifest } from '../manifest.ts';
23
- import { payerOf, payOpenInvoice } from '../semantics/renewals.ts';
24
- import { afterSuccessOf } from '../semantics/after-payment.ts';
25
- import { created, type Row } from '../semantics/shared.ts';
26
- import { publicBusinessName } from '../stripe-shared.ts';
27
- import { declineFor, paymentMethodSubObject, PLATFORM_ACCOUNT_ID } from '../stripe-twin.ts';
28
- import { cardAnswer, COUNTRIES, money } from './checkout.tsx';
29
- import { formOf } from './consent-skin.ts';
30
-
19
+ import type { HandlerContext } from '@volter/world-core';
20
+ import { flowPage, type PaymentField, Portal, PORTAL_CSS, type PortalItem } from '@volter/world-ui';
21
+ import type { Row } from '../engine/common.ts';
22
+ import { publicBusinessName } from '../engine/display.ts';
23
+ import { declineFor, nowUnix, paymentMethodSubObject, PLATFORM_ACCOUNT_ID } from '../engine/stripe.ts';
24
+ import { cardAnswer, COUNTRIES, formOf, money, notFound } from './shared.tsx';
25
+ import { resourceView, afterSuccessOf, created, payerOf, payOpenInvoice } from '../semantics/shared.ts';
31
26
  const SUB = 'subscription';
32
- const PORTAL_OPERATION = (surface.operations as Array<{ id: string; method: string; path: string; class: string }>).find((o) => o.id === 'PostBillingPortalSessions')!;
27
+ const PORTAL_OPERATION = { id: 'PostBillingPortalSessions' };
33
28
 
34
29
  // Stripe's skin: its type, its purple accent, the merchant's side tinted.
35
30
  const SKIN = `
@@ -50,7 +45,7 @@ const day = (epoch: unknown): string => new Date(Number(epoch) * 1000).toLocaleD
50
45
  const gone = (): Response => flowPage({ title: 'Billing', status: 404, css: [PORTAL_CSS, SKIN], body: <main className="portal-main"><h1>This link has expired.</h1></main> });
51
46
 
52
47
  type Features = { cancel: { enabled: boolean; mode: 'at_period_end' | 'immediately' }; invoices: boolean; paymentMethod: boolean };
53
- function featuresOf(ctx: SemanticsContext, session: Row): Features {
48
+ function featuresOf(ctx: HandlerContext, session: Row): Features {
54
49
  const f = (ctx.get('billing_portal.configuration', String(session.configuration))?.features ?? undefined) as Row | undefined;
55
50
  const cancel = (f?.subscription_cancel ?? {}) as Row;
56
51
  const invoices = (f?.invoice_history ?? {}) as Row;
@@ -62,7 +57,7 @@ function featuresOf(ctx: SemanticsContext, session: Row): Features {
62
57
  };
63
58
  }
64
59
 
65
- function planOf(ctx: SemanticsContext, sub: Row): { name: string; price: string } {
60
+ function planOf(ctx: HandlerContext, sub: Row): { name: string; price: string } {
66
61
  const item = (((sub.items as { data?: Row[] } | undefined)?.data) ?? [])[0];
67
62
  const price = (typeof item?.price === 'object' ? item.price : ctx.get('price', String(item?.price ?? ''))) as Row | undefined;
68
63
  const product = typeof price?.product === 'string' ? ctx.get('product', price.product) : (price?.product as Row | undefined);
@@ -73,10 +68,10 @@ function planOf(ctx: SemanticsContext, sub: Row): { name: string; price: string
73
68
  };
74
69
  }
75
70
 
76
- function page(ctx: SemanticsContext, id: string, session: Row, problem?: { error: string; values: Record<string, string> }): Response {
71
+ function page(ctx: HandlerContext, id: string, session: Row, problem?: { error: string; values: Record<string, string> }): Response {
77
72
  const features = featuresOf(ctx, session);
78
73
  const customer = ctx.get('customer', String(session.customer)) ?? {};
79
- const subs = ctx.rows(SUB).filter((s) => s.customer === session.customer && s.status !== 'canceled' && s.status !== 'incomplete_expired');
74
+ const subs = ctx.rows(SUB).map((s) => ctx.expand(SUB, s)).filter((s) => s.customer === session.customer && s.status !== 'canceled' && s.status !== 'incomplete_expired');
80
75
  const plans: PortalItem[] = subs.map((s) => {
81
76
  const plan = planOf(ctx, s);
82
77
  const ending = s.cancel_at_period_end === true;
@@ -106,7 +101,7 @@ function page(ctx: SemanticsContext, id: string, session: Row, problem?: { error
106
101
  }))
107
102
  : [];
108
103
  const back = typeof session.return_url === 'string' ? session.return_url : undefined;
109
- // the portal shows the public business name (stripe-shared.ts, publicBusinessName) of the session's on_behalf_of
104
+ // the portal shows the public business name (../engine/display.ts, publicBusinessName) of the session's on_behalf_of
110
105
  // account ("Use the Accounts API to modify the on_behalf_of account's branding settings, which the portal displays",
111
106
  // docs.stripe.com/api/customer_portal/sessions/object), else of the account it was made on (Stripe-Account), else the
112
107
  // platform's
@@ -143,8 +138,8 @@ function page(ctx: SemanticsContext, id: string, session: Row, problem?: { error
143
138
  }
144
139
 
145
140
  /** The customer's move on one of their subscriptions, then back to the portal. */
146
- async function move(ctx: SemanticsContext, id: string, session: Row, subscription: string, kind: 'cancel' | 'renew'): Promise<Response> {
147
- const sub = ctx.get(SUB, subscription);
141
+ async function move(ctx: HandlerContext, id: string, session: Row, subscription: string, kind: 'cancel' | 'renew'): Promise<Response> {
142
+ const sub = resourceView(ctx, SUB, subscription);
148
143
  if (!sub || sub.customer !== session.customer || sub.status === 'canceled') return gone();
149
144
  const back = new Response(null, { status: 303, headers: { location: `/p/session/${id}` } });
150
145
  if (kind === 'renew') {
@@ -153,7 +148,7 @@ async function move(ctx: SemanticsContext, id: string, session: Row, subscriptio
153
148
  }
154
149
  const features = featuresOf(ctx, session);
155
150
  if (!features.cancel.enabled) return gone();
156
- const now = Number(ctx.now());
151
+ const now = nowUnix(ctx.occurredAt);
157
152
  const details = { comment: null, feedback: null, reason: 'cancellation_requested' };
158
153
  if (features.cancel.mode === 'at_period_end') {
159
154
  await ctx.write(SUB, subscription, { cancel_at_period_end: true, cancel_at: sub.current_period_end ?? null, canceled_at: now, cancellation_details: details }, 'subscription.update');
@@ -166,9 +161,9 @@ async function move(ctx: SemanticsContext, id: string, session: Row, subscriptio
166
161
  }
167
162
 
168
163
  /** The customer's new card: saved on them (no charge) as the default their invoices and subscriptions charge. */
169
- async function updatePaymentMethod(ctx: SemanticsContext, id: string, session: Row, v: Record<string, string>): Promise<Response> {
164
+ async function updatePaymentMethod(ctx: HandlerContext, id: string, session: Row, v: Record<string, string>): Promise<Response> {
170
165
  if (!featuresOf(ctx, session).paymentMethod) return gone();
171
- const refused = cardAnswer(v, Number(ctx.now()), false);
166
+ const refused = cardAnswer(v, nowUnix(ctx.occurredAt), false);
172
167
  if (refused) return page(ctx, id, session, { error: refused, values: v });
173
168
  const customer = String(session.customer);
174
169
  const [month, year] = (v.cardExpiry ?? '').split('/').map((x) => Number(x.trim()));
@@ -181,17 +176,17 @@ async function updatePaymentMethod(ctx: SemanticsContext, id: string, session: R
181
176
  });
182
177
  const holder = ctx.get('customer', customer) ?? {};
183
178
  await ctx.write('customer', customer, { invoice_settings: { ...((holder.invoice_settings as Row | undefined) ?? {}), default_payment_method: pm.id } }, 'customer.update');
184
- for (const sub of ctx.rows(SUB).filter((s) => s.customer === customer && s.status !== 'canceled' && s.status !== 'incomplete_expired')) {
179
+ for (const sub of ctx.rows(SUB).map((s) => ctx.expand(SUB, s)).filter((s) => s.customer === customer && s.status !== 'canceled' && s.status !== 'incomplete_expired')) {
185
180
  await ctx.write(SUB, String(sub.id), { default_payment_method: pm.id }, 'subscription.update');
186
181
  }
187
182
  return new Response(null, { status: 303, headers: { location: `/p/session/${id}` } });
188
183
  }
189
184
 
190
185
  /** The customer pays one of their open invoices with the card on file. */
191
- async function payInvoice(ctx: SemanticsContext, id: string, session: Row, invoiceId: string): Promise<Response> {
186
+ async function payInvoice(ctx: HandlerContext, id: string, session: Row, invoiceId: string): Promise<Response> {
192
187
  const inv = ctx.get('invoice', invoiceId);
193
188
  if (!inv || inv.customer !== session.customer || inv.status !== 'open') return gone();
194
- const sub = typeof inv.subscription === 'string' ? ctx.get(SUB, inv.subscription) : undefined;
189
+ const sub = typeof inv.subscription === 'string' ? resourceView(ctx, SUB, inv.subscription) : undefined;
195
190
  const holder = ctx.get('customer', String(session.customer)) ?? {};
196
191
  const pm = (sub ? payerOf(ctx, sub) : undefined) ?? ((holder.invoice_settings as Row | undefined)?.default_payment_method as string | undefined);
197
192
  if (!pm) return page(ctx, id, session, { error: 'Add a payment method to pay this invoice.', values: {} });
@@ -201,20 +196,18 @@ async function payInvoice(ctx: SemanticsContext, id: string, session: Row, invoi
201
196
  }
202
197
 
203
198
  /** billing.stripe.com's portal pages, or undefined for any other request. */
204
- export function stripePortalFlow(scope: { root?: string; clock?: () => string }): (request: Request) => Promise<Response | undefined> {
205
- return async (request) => {
206
- const m = /^\/p\/session\/(bps_[A-Za-z0-9_]+)(?:\/(cancel|renew|payment_method|pay))?\/?$/.exec(new URL(request.url).pathname);
207
- if (!m) return undefined;
208
- const [, id, kind] = m as unknown as [string, string, 'cancel' | 'renew' | 'payment_method' | 'pay' | undefined];
209
- if ((kind && request.method !== 'POST') || (!kind && request.method !== 'GET')) return undefined;
210
- // the form is read before the context, which reads the request's body itself
211
- const form = kind ? await formOf(request.clone()) : {};
212
- const ctx = await semanticsContext(manifest, request, PORTAL_OPERATION, scope);
213
- const session = ctx.get('billing_portal.session', id);
214
- if (!session) return gone();
215
- if (!kind) return page(ctx, id, session);
216
- if (kind === 'payment_method') return updatePaymentMethod(ctx, id, session, form);
217
- if (kind === 'pay') return payInvoice(ctx, id, session, form.invoice ?? '');
218
- return move(ctx, id, session, form.subscription ?? '', kind);
219
- };
199
+ export async function screen(ctx: HandlerContext): Promise<Response> {
200
+ const request = ctx.call.request;
201
+ const m = /^\/p\/session\/(bps_[A-Za-z0-9_]+)(?:\/(cancel|renew|payment_method|pay))?\/?$/.exec(new URL(request.url).pathname);
202
+ if (!m) return notFound();
203
+ const [, id, kind] = m as unknown as [string, string, 'cancel' | 'renew' | 'payment_method' | 'pay' | undefined];
204
+ if ((kind && request.method !== 'POST') || (!kind && request.method !== 'GET')) return notFound();
205
+ // the form is read before the context, which reads the request's body itself
206
+ const form = kind ? await formOf(request.clone()) : {};
207
+ const session = ctx.get('billing_portal.session', id);
208
+ if (!session) return gone();
209
+ if (!kind) return page(ctx, id, session);
210
+ if (kind === 'payment_method') return updatePaymentMethod(ctx, id, session, form);
211
+ if (kind === 'pay') return payInvoice(ctx, id, session, form.invoice ?? '');
212
+ return move(ctx, id, session, form.subscription ?? '', kind);
220
213
  }
@@ -0,0 +1,80 @@
1
+ import type { HandlerContext } from '@volter/world-core';
2
+ import type { Row } from '../engine/common.ts';
3
+ import { newCustomer } from '../engine/customers.ts';
4
+ import { CS } from '../engine/checkout.ts';
5
+ import { applyCouponDiscount, nowUnix, resolveTrial } from '../engine/stripe.ts';
6
+ import { cardAnswer, parseExpiry } from './shared.tsx';
7
+ import { completeSession, discountOf, created } from '../semantics/shared.ts';
8
+
9
+ function amount(value: number, currency: string): Row {
10
+ return { amount: new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(value / 100), minorUnitsAmount: value };
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 "export interface StripeCheckoutSession"
14
+ // The SDK publishes its contract, not its private HTTP wire. This authored payment_pages wire uses the same session store and completion as hosted Checkout.
15
+ function view(ctx: HandlerContext, session: Row): Row {
16
+ const currency = String(session.currency ?? 'usd');
17
+ const lines = ((session.line_items as Row)?.data ?? []) as Row[];
18
+ const stored = ctx.row(CS, String(session.id)) ?? session;
19
+ const trial = session.mode === 'subscription' ? resolveTrial((stored._subscription_data as Row) ?? {}, nowUnix(ctx.occurredAt)) : undefined;
20
+ const customer = typeof session.customer === 'string' ? ctx.get('customer', session.customer) : undefined;
21
+ const discount = stored._discount as Row | undefined;
22
+ const promotion = typeof discount?.promotion_code === 'string' ? ctx.get('promotion_code', discount.promotion_code) : undefined;
23
+ const zero = amount(0, currency);
24
+ return {
25
+ id: session.id, billingAddress: null, businessName: null, canConfirm: session.status === 'open', currency,
26
+ currencyOptions: null, discountAmounts: promotion ? [{ ...amount(Number((session.total_details as Row)?.amount_discount ?? 0), currency), promotionCode: promotion.code }] : [],
27
+ email: customer?.email ?? session.customer_email ?? null, lastPaymentError: null,
28
+ lineItems: lines.map(line => ({ id: line.id, name: line.description, quantity: line.quantity, unitAmount: amount(Number(line.amount_subtotal) / Number(line.quantity || 1), currency), total: amount(Number(line.amount_total), currency) })),
29
+ livemode: false, minorUnitsAmountDivisor: 100, phoneNumber: null,
30
+ recurring: session.mode === 'subscription' ? { trial: trial ? { trialEnd: trial.end, trialPeriodDays: Math.ceil((trial.end - trial.start) / 86400) } : null } : null,
31
+ savedPaymentMethods: [], shipping: null, shippingAddress: null, shippingOptions: [],
32
+ status: session.status === 'complete' ? { type: 'complete', paymentStatus: session.payment_status } : { type: session.status },
33
+ tax: { status: 'ready' }, taxAmounts: [], taxIdInfo: null,
34
+ total: { appliedBalance: zero, balanceAppliedToNextInvoice: false, discount: amount(Number((session.total_details as Row)?.amount_discount ?? 0), currency), shippingRate: zero, subtotal: amount(Number(session.amount_subtotal ?? 0), currency), taxExclusive: zero, taxInclusive: zero, total: amount(trial ? 0 : Number(session.amount_total ?? 0), currency) },
35
+ };
36
+ }
37
+
38
+ const failed = (message: string, code: string | null = null): Response => Response.json({ type: 'error', error: { message, code } });
39
+
40
+ export async function screen(ctx: HandlerContext): Promise<Response> {
41
+ const path = new URL(ctx.call.request.url).pathname;
42
+ const id = path.split('/')[3]!;
43
+ const action = path.split('/')[4];
44
+ const params = ctx.params;
45
+ const session = ctx.get(CS, id);
46
+ // The private client secret is the browser's authority for this session; it never accepts a secret API key.
47
+ if (!session || session.ui_mode !== 'custom' || params.client_secret !== session.client_secret) return failed('Invalid Checkout Session client secret.');
48
+ if (action === 'init') return Response.json({ type: 'success', session: view(ctx, session) });
49
+ if (session.status !== 'open') return failed('This Checkout Session is no longer active.');
50
+ if (action === 'apply_promotion_code' || action === 'remove_promotion_code') {
51
+ if (action === 'apply_promotion_code' && session.allow_promotion_codes !== true) return failed('Promotion codes are not enabled for this Checkout Session.');
52
+ const promotion = action === 'apply_promotion_code' ? ctx.rows('promotion_code').find(p => p.code === params.promotion_code && p.active === true) : undefined;
53
+ if (action === 'apply_promotion_code' && !promotion) return failed('This promotion code is invalid.', 'invalidPromotionCode');
54
+ const discount = promotion ? discountOf(ctx, { discounts: [{ promotion_code: promotion.id }] }, nowUnix(ctx.occurredAt)) : undefined;
55
+ if (discount instanceof Response) return failed('This promotion code is invalid.', 'invalidPromotionCode');
56
+ const off = discount ? applyCouponDiscount(Number(session.amount_subtotal), discount.coupon) : 0;
57
+ const fields = { amount_total: Number(session.amount_subtotal) - off, total_details: { ...(session.total_details as Row), amount_discount: off }, discounts: discount ? [{ coupon: discount.coupon.id, promotion_code: promotion!.id }] : [], _discount: discount ? { coupon: discount.coupon.id, promotion_code: promotion!.id } : null };
58
+ const updated = await ctx.write(CS, id, fields, 'checkout.session.update');
59
+ return Response.json({ type: 'success', session: view(ctx, updated) });
60
+ }
61
+ if (action !== 'confirm') return failed('Unrecognized Checkout action.');
62
+ const values = (params.payment_details ?? {}) as Record<string, string>;
63
+ const stored = ctx.row(CS, id) ?? session;
64
+ const trial = session.mode === 'subscription' ? resolveTrial((stored._subscription_data as Row) ?? {}, nowUnix(ctx.occurredAt)) : undefined;
65
+ const refusal = cardAnswer(values, nowUnix(ctx.occurredAt), !trial);
66
+ if (refusal) return failed(refusal, 'paymentFailed');
67
+ const customer = typeof session.customer === 'string' ? ctx.get('customer', session.customer) : undefined;
68
+ const email = String(customer?.email ?? params.email ?? session.customer_email ?? '');
69
+ if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) return failed('Your email address is incomplete.');
70
+ let existing = session;
71
+ if (!customer && session.mode === 'subscription') {
72
+ const cid = ctx.mint('customer');
73
+ const made = await created(ctx, 'customer', { id: cid, email }, { livemode: false, ...newCustomer(cid) });
74
+ existing = { ...session, customer: made.id };
75
+ }
76
+ const expiry = parseExpiry(values.cardExpiry)!;
77
+ const completed = await completeSession(ctx, id, existing, { customer: existing.customer, customer_details: { email, name: values.billingName ?? null, address: null, phone: null, tax_exempt: 'none', tax_ids: [] } }, { number: values.cardNumber.replace(/\D/g, ''), exp_month: expiry.month, exp_year: expiry.year });
78
+ if (completed instanceof Response) return failed('This Checkout Session is no longer active.');
79
+ return Response.json({ type: 'success', session: view(ctx, completed), return_url: session.return_url });
80
+ }
@@ -15,41 +15,17 @@
15
15
  // only sets the card up: setup mode, or a subscription's trial);
16
16
  // any other number that passes the Luhn check pays, and a card that asks for authentication pays without
17
17
  // the challenge. A subscription session with no customer makes one from the email, as Stripe does.
18
- import { semanticsContext, type SemanticsContext } from '@volter/world-core';
18
+ import type { HandlerContext } from '@volter/world-core';
19
19
  import { flowPage, Payment, PAYMENT_CSS, type PaymentField } from '@volter/world-ui';
20
- import surface from '../generated/surface.gen.json' with { type: 'json' };
21
- import { manifest } from '../manifest.ts';
22
- import { completeSession, isOneTime } from '../semantics/checkout.ts';
23
- import { newCustomer } from '../semantics/customers.ts';
24
- import { created, type Row } from '../semantics/shared.ts';
25
- import { publicBusinessName } from '../stripe-shared.ts';
26
- import { applyCouponDiscount, PLATFORM_ACCOUNT_ID, resolveTrial } from '../stripe-twin.ts';
27
- import { formOf } from './consent-skin.ts';
28
-
20
+ import { isOneTime } from '../engine/checkout.ts';
21
+ import { newCustomer } from '../engine/customers.ts';
22
+ import type { Row } from '../engine/common.ts';
23
+ import { publicBusinessName } from '../engine/display.ts';
24
+ import { applyCouponDiscount, nowUnix, PLATFORM_ACCOUNT_ID, resolveTrial } from '../engine/stripe.ts';
25
+ import { cardAnswer, COUNTRIES, formOf, money, notFound, parseExpiry } from './shared.tsx';
26
+ import { completeSession, created } from '../semantics/shared.ts';
29
27
  const CS = 'checkout.session';
30
- const COMPLETE_OPERATION = (surface.operations as Array<{ id: string; method: string; path: string; class: string }>).find((o) => o.id === 'PostCheckoutSessionsSession')!;
31
-
32
- const DECLINES: Record<string, string> = {
33
- '4000000000000002': 'Your card was declined.',
34
- '4000000000009995': 'Your card has insufficient funds.',
35
- '4000000000000069': 'Your card has expired.',
36
- '4000000000000127': "Your card's security code is incorrect.",
37
- };
38
- const luhn = (digits: string): boolean => {
39
- let sum = 0;
40
- for (let i = 0; i < digits.length; i++) {
41
- let d = Number(digits[digits.length - 1 - i]);
42
- if (i % 2 === 1) { d *= 2; if (d > 9) d -= 9; }
43
- sum += d;
44
- }
45
- return digits.length >= 12 && sum % 10 === 0;
46
- };
47
-
48
- export const money = (minor: unknown, currency: unknown): string => {
49
- const c = typeof currency === 'string' ? currency.toUpperCase() : 'USD';
50
- const zeroDecimal = ['JPY', 'KRW', 'VND', 'CLP', 'ISK', 'UGX', 'XAF', 'XOF'].includes(c);
51
- return new Intl.NumberFormat('en-US', { style: 'currency', currency: c }).format((Number(minor) || 0) / (zeroDecimal ? 1 : 100));
52
- };
28
+ const COMPLETE_OPERATION = { id: 'PostCheckoutSessionsSession' };
53
29
 
54
30
  // Stripe's skin: its type, its blue submit, the summary on a tinted half.
55
31
  const SKIN = `
@@ -61,8 +37,6 @@ body { color: #1a1a1a; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI
61
37
  .pay-submit { background: #0074d4; color: #ffffff; }
62
38
  `;
63
39
 
64
- 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! }));
65
-
66
40
  /** A subscription session's trial, when its subscription_data gives one: `trial_period_days`, or `trial_end`
67
41
  * (docs.stripe.com/payments/checkout/free-trials), as resolveTrial makes the subscription's at completion. */
68
42
  function trialOf(session: Row, now: number): { start: number; end: number } | undefined {
@@ -103,7 +77,7 @@ function trialSummary(session: Row, trial: { start: number; end: number }): { le
103
77
 
104
78
  /** What each period after the trial charges, as the subscription's invoices will: its recurring line items (a one-time
105
79
  * item is on the first invoice only, semantics/checkout.ts isOneTime), under the session's coupon unless the coupon is `once`, which the trial's own $0
106
- * invoice takes (semantics/renewals.ts draftSubscriptionInvoice). */
80
+ * invoice takes (../engine/renewals.ts draftSubscriptionInvoice). */
107
81
  function afterTrial(session: Row): number {
108
82
  const items = (((session.line_items as { data?: Row[] } | undefined)?.data) ?? []).filter((it, i) => !isOneTime(it, inlineOf(session, i)));
109
83
  const subtotal = items.reduce((sum, it) => sum + (Number(it.amount_subtotal) || 0), 0);
@@ -167,52 +141,17 @@ function page(session: Row, id: string, now: number, values: Record<string, stri
167
141
  const gone = (): Response =>
168
142
  flowPage({ title: 'Checkout', status: 404, css: [PAYMENT_CSS, SKIN], body: <main className="pay-form"><h1>This Checkout Session is no longer active.</h1></main> });
169
143
 
170
- /** Cards that are saved but decline when charged. */
171
- const DECLINES_WHEN_CHARGED: Record<string, string> = { '4000000000000341': 'Your card was declined.' };
172
-
173
- /** The expiry as the customer typed it. Stripe's field formats itself as it is typed: the digits `1230` read `12 / 30`,
174
- * and a first digit above 1 is a month on its own (`5` reads `05 / `), so a person types the digits straight through
175
- * (Checkout's own field, seen on checkout.stripe.com; docs.stripe.com/payments/checkout names no format to type). The
176
- * page's script (@volter/world-ui Payment, `format: 'card-expiry'`) shows it formatted; this reads what is posted either
177
- * way: `MM / YY`, `MM/YYYY`, or the bare digits `MMYY`, `MYY` (`530`) and `MMYYYY`; a month past 12 is read, and refused
178
- * as invalid by cardAnswer. */
179
- export function parseExpiry(raw: string): { month: number; year: number } | undefined {
180
- const split = /^\s*(\d{1,2})\s*\/\s*(\d{2}|\d{4})\s*$/.exec(raw);
181
- const digits = raw.replace(/\s/g, '');
182
- const bare = split ? undefined : /^(\d{2})(\d{2}|\d{4})$/.exec(digits) ?? /^([2-9])(\d{2})$/.exec(digits);
183
- const m = split ?? bare;
184
- if (!m) return undefined;
185
- return { month: Number(m[1]), year: m[2]!.length === 2 ? 2000 + Number(m[2]) : Number(m[2]) };
186
- }
187
-
188
- /** The card's answer: undefined when it pays, else what Checkout says under the button. */
189
- export function cardAnswer(v: Record<string, string>, nowSeconds: number, chargesNow: boolean): string | undefined {
190
- const digits = (v.cardNumber ?? '').replace(/\D/g, '');
191
- if (!digits) return 'Your card number is incomplete.';
192
- if (DECLINES[digits]) return DECLINES[digits];
193
- if (chargesNow && DECLINES_WHEN_CHARGED[digits]) return DECLINES_WHEN_CHARGED[digits];
194
- if (!luhn(digits)) return 'Your card number is invalid.';
195
- const exp = parseExpiry(v.cardExpiry ?? '');
196
- if (!exp) return "Your card's expiration date is incomplete.";
197
- const { month, year } = exp;
198
- if (month < 1 || month > 12) return "Your card's expiration date is invalid.";
199
- const now = new Date(nowSeconds * 1000);
200
- if (year < now.getUTCFullYear() || (year === now.getUTCFullYear() && month < now.getUTCMonth() + 1)) return "Your card's expiration year is in the past.";
201
- if (!/^\d{3,4}$/.test(v.cardCvc ?? '')) return "Your card's security code is incomplete.";
202
- return undefined;
203
- }
204
-
205
- async function pay(ctx: SemanticsContext, id: string, session: Row, v: Record<string, string>): Promise<Response> {
144
+ async function pay(ctx: HandlerContext, id: string, session: Row, v: Record<string, string>): Promise<Response> {
206
145
  const onFile = customerEmailOf(session);
207
146
  const email = onFile ?? (typeof session.customer_email === 'string' ? session.customer_email : (v.email ?? '').trim());
208
147
  if (session.mode !== 'setup' || !session.customer) {
209
- if (!EMAIL.test(email)) return page(session, id, Number(ctx.now()), v, 'Your email address is incomplete.');
148
+ if (!EMAIL.test(email)) return page(session, id, nowUnix(ctx.occurredAt), v, 'Your email address is incomplete.');
210
149
  }
211
150
  // a trial charges nothing now unless a one-time price is on the first invoice
212
151
  const oneTimeDue = (((session.line_items as { data?: Row[] } | undefined)?.data) ?? []).some((it, i) => isOneTime(it, inlineOf(session, i)));
213
- const chargesNow = session.mode === 'payment' || (session.mode === 'subscription' && (!trialOf(session, Number(ctx.now())) || oneTimeDue));
214
- const refused = cardAnswer(v, Number(ctx.now()), chargesNow);
215
- if (refused) return page(session, id, Number(ctx.now()), v, refused);
152
+ const chargesNow = session.mode === 'payment' || (session.mode === 'subscription' && (!trialOf(session, nowUnix(ctx.occurredAt)) || oneTimeDue));
153
+ const refused = cardAnswer(v, nowUnix(ctx.occurredAt), chargesNow);
154
+ if (refused) return page(session, id, nowUnix(ctx.occurredAt), v, refused);
216
155
  let existing = session;
217
156
  // Checkout always makes a customer for a subscription; a payment makes one only when the session asks
218
157
  if (!session.customer && (session.mode === 'subscription' || session.customer_creation === 'always')) {
@@ -233,35 +172,32 @@ async function pay(ctx: SemanticsContext, id: string, session: Row, v: Record<st
233
172
  return new Response(null, { status: 303, headers: { location: to } });
234
173
  }
235
174
 
236
- /** checkout.stripe.com's pages, or undefined for any other request. */
237
175
  /** The name the page shows: the session's own `branding_settings.display_name` where the create set one, else the
238
- * public business name (stripe-shared.ts, publicBusinessName) of the account whose brand Checkout uses: "Checkout uses
176
+ * public business name (../engine/display.ts, publicBusinessName) of the account whose brand Checkout uses: "Checkout uses
239
177
  * the brand settings of the connected account for destination charges with on_behalf_of and for platforms performing
240
178
  * direct charges" (a session made with Stripe-Account), and otherwise the platform's; "If you omit a field in
241
179
  * branding_settings, Checkout applies the default value from the Dashboard"
242
180
  * (docs.stripe.com/payments/checkout/customization/appearance). */
243
- export function merchantOf(ctx: SemanticsContext, stored: Row): string {
181
+ function merchantOf(ctx: HandlerContext, stored: Row): string {
244
182
  const brand = (stored._branding_settings as Row | undefined)?.display_name;
245
183
  const behalf = (stored._payment_intent_data as Row | undefined)?.on_behalf_of ?? (stored._subscription_data as Row | undefined)?.on_behalf_of;
246
184
  const account = typeof behalf === 'string' ? behalf : typeof stored._brand_account === 'string' ? stored._brand_account : PLATFORM_ACCOUNT_ID;
247
185
  return typeof brand === 'string' && brand.trim() ? brand : publicBusinessName(ctx.get('account', account));
248
186
  }
249
187
 
250
- export function stripeCheckoutFlow(scope: { root?: string; clock?: () => string }): (request: Request) => Promise<Response | undefined> {
251
- return async (request) => {
252
- const m = /^\/c\/pay\/(cs_[A-Za-z0-9_]+)\/?$/.exec(new URL(request.url).pathname);
253
- if (!m || (request.method !== 'GET' && request.method !== 'POST')) return undefined;
254
- const id = m[1]!;
255
- // the form is read before the context, which reads the request's body itself
256
- const values = request.method === 'POST' ? await formOf(request.clone()) : {};
257
- const ctx = await semanticsContext(manifest, request, COMPLETE_OPERATION, scope);
258
- const session = ctx.get(CS, id);
259
- if (!session) return gone();
260
- // an ended session's page is gone; paying on one is the customer's move the machine refuses
261
- if (session.status !== 'open' && (request.method === 'GET' || ctx.legal(CS, 'status', COMPLETE_OPERATION.id, String(session.status), 'complete', id, 'external'))) return gone();
262
- const stored = ctx.row(CS, id) ?? {};
263
- const coupon = (stored._discount as Row | undefined)?.coupon;
264
- const full = { ...session, _subscription_data: stored._subscription_data, _price_data: stored._price_data, _merchant: merchantOf(ctx, stored), _coupon: typeof coupon === 'string' ? ctx.get('coupon', coupon) : undefined, _customer: typeof session.customer === 'string' ? ctx.get('customer', session.customer) : undefined };
265
- return request.method === 'GET' ? page(full, id, Number(ctx.now())) : pay(ctx, id, full, values);
266
- };
188
+ export async function screen(ctx: HandlerContext): Promise<Response> {
189
+ const request = ctx.call.request;
190
+ const m = /^\/c\/pay\/(cs_[A-Za-z0-9_]+)\/?$/.exec(new URL(request.url).pathname);
191
+ if (!m || (request.method !== 'GET' && request.method !== 'POST')) return notFound();
192
+ const id = m[1]!;
193
+ // the form is read before the context, which reads the request's body itself
194
+ const values = request.method === 'POST' ? await formOf(request.clone()) : {};
195
+ const session = ctx.get(CS, id);
196
+ if (!session) return gone();
197
+ // an ended session's page is gone; paying on one is the customer's move the machine refuses
198
+ if (session.status !== 'open' && (request.method === 'GET' || ctx.legal(CS, 'status', COMPLETE_OPERATION.id, String(session.status), 'complete', id, 'external'))) return gone();
199
+ const stored = ctx.row(CS, id) ?? {};
200
+ const coupon = (stored._discount as Row | undefined)?.coupon;
201
+ const full = { ...session, _subscription_data: stored._subscription_data, _price_data: stored._price_data, _merchant: merchantOf(ctx, stored), _coupon: typeof coupon === 'string' ? ctx.get('coupon', coupon) : undefined, _customer: typeof session.customer === 'string' ? ctx.get('customer', session.customer) : undefined };
202
+ return request.method === 'GET' ? page(full, id, nowUnix(ctx.occurredAt)) : pay(ctx, id, full, values);
267
203
  }