@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
@@ -1,20 +1,8 @@
1
- // Coupon and PromotionCode semantics. A coupon is exactly one of percent_off or amount_off (with
2
- // its currency) over a duration; a promotion code is a customer-facing code for an existing
3
- // coupon, active until switched off (the machine in ../manifest.ts). Coupon list, retrieve, update
4
- // and delete and promotion-code retrieve and update (`active`, the machine) are the derived core's.
5
- import type { Semantics, SemanticsContext } from '@volter/world-core';
6
- import { asBool } from '../stripe-twin.ts';
7
- import { at, at_, created, fail, list, newest, where, type Row } from './shared.ts';
8
-
9
- /** A coupon for a fixed amount off, in its currency (docs.stripe.com/api/coupons/create#create_coupon-amount_off). */
10
- function amountOffOf(ctx: SemanticsContext, params: Record<string, unknown>): number | Response {
11
- const amountOff = Number(params.amount_off);
12
- if (!Number.isInteger(amountOff) || amountOff <= 0) return fail(ctx, 'Invalid integer: amount_off must be a positive integer.', 400, 'parameter_invalid_integer');
13
- if (params.currency === undefined || params.currency === '') return fail(ctx, 'Missing required param: currency.', 400, 'parameter_missing');
14
- return amountOff;
15
- }
16
-
17
- const createCoupon: Semantics = async (ctx) => {
1
+ // Stripe's coupons 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 { amountOffOf, created, fail } from './shared.ts';
5
+ export async function PostCoupons(ctx: HandlerContext): Promise<Response> {
18
6
  const params = ctx.params;
19
7
  const hasPct = params.percent_off !== undefined;
20
8
  const hasAmt = params.amount_off !== undefined;
@@ -43,55 +31,4 @@ const createCoupon: Semantics = async (ctx) => {
43
31
  applies_to: null,
44
32
  }),
45
33
  );
46
- };
47
-
48
- // a code the caller does not give is derived from the promotion code's id
49
- const createPromotionCode: Semantics = async (ctx) => {
50
- // what it promotes: promotion[type]=coupon and promotion[coupon] (2025-09-30.clover,
51
- // docs.stripe.com/changelog/clover/2025-09-30/polymorphic-coupon), or the top-level coupon a caller pinned to an
52
- // earlier version sends; the twin keeps the coupon's id on the code for its rules
53
- const { promotion, coupon: legacy, ...params } = ctx.params;
54
- const promo = promotion && typeof promotion === 'object' ? (promotion as Record<string, unknown>) : undefined;
55
- if (promo && promo.type !== 'coupon') return fail(ctx, 'Invalid promotion[type]: must be coupon.', 400, 'parameter_invalid_string_enum');
56
- const coupon = typeof promo?.coupon === 'string' ? promo.coupon : typeof legacy === 'string' ? legacy : '';
57
- if (!coupon) return fail(ctx, promo ? 'Missing required param: promotion[coupon].' : 'Missing required param: promotion.', 400, 'parameter_missing');
58
- if (!ctx.get('coupon', coupon)) return fail(ctx, `No such coupon: '${coupon}'`, 400, 'resource_missing');
59
- const id = ctx.mint('promotion_code');
60
- const code = typeof params.code === 'string' && params.code ? params.code : `TWIN${id.toUpperCase().replace(/[^A-Z0-9]/g, '')}`;
61
- return ctx.reply(
62
- await created(ctx, 'promotion_code', { ...params, coupon, promotion: { type: 'coupon', coupon }, id, code }, {
63
- active: params.active !== undefined ? asBool(params.active) : true,
64
- customer: typeof params.customer === 'string' ? params.customer : null,
65
- expires_at: params.expires_at !== undefined ? Number(params.expires_at) : null,
66
- max_redemptions: params.max_redemptions !== undefined ? Number(params.max_redemptions) : null,
67
- times_redeemed: 0, livemode: false, metadata: {}, restrictions: { first_time_transaction: false, minimum_amount: null, minimum_amount_currency: null },
68
- }),
69
- );
70
- };
71
-
72
- /** Time's lapsing of coupons, caught up to the World's clock: a valid coupon whose redeem_by has passed is no longer
73
- * valid from that moment (manifest.ts). Where the documentation stops and the twin decides: the lapse sends no event
74
- * (the events page names none for it). */
75
- export async function lapseCoupons(ctx: SemanticsContext): Promise<void> {
76
- const now = Number(ctx.now());
77
- for (const c of ctx.rowsRaw('coupon')) {
78
- if (c.valid !== true || typeof c.redeem_by !== 'number' || c.redeem_by > now) continue;
79
- const t = await at_(ctx)(c.redeem_by);
80
- t.legal('coupon', 'valid', ctx.call.operation.id, 'true', 'false', String(c.id), 'time');
81
- await t.write('coupon', String(c.id), { valid: false }, 'coupon.lapsed');
82
- }
83
34
  }
84
-
85
- const listPromotionCodes: Semantics = async (ctx) =>
86
- list(ctx, 'promotion_code', where(ctx, newest(ctx, 'promotion_code'), {
87
- code: (p, v) => p.code === v,
88
- active: (p, v) => asBool(p.active) === asBool(v),
89
- coupon: (p, v) => p.coupon === v,
90
- customer: (p, v) => p.customer === v,
91
- }));
92
-
93
- export const coupons: Record<string, Semantics> = {
94
- PostCoupons: createCoupon,
95
- PostPromotionCodes: createPromotionCode,
96
- GetPromotionCodes: listPromotionCodes,
97
- };
@@ -1,432 +1,66 @@
1
- // Customer semantics: the customer and what hangs off it (tax ids, the credit-balance ledger, the
2
- // cash balance, legacy sources, attached payment methods) and the top-level Source object. List,
3
- // update and delete of a customer, retrieve of a Source and the flat tax-id list are the derived core's.
4
- import type { Semantics, SemanticsContext } from '@volter/world-core';
5
- import { buildDiscount } from '../stripe-twin.ts';
6
- import { invoicePrefix } from './invoices.ts';
7
- import { mintCharge } from './payment-intents.ts';
8
- import { attachable, attachPaymentMethod } from './payment-methods.ts';
9
- import { at, created, deletedDiscount, expanded, fail, list, newest, redeemCoupon, search, where, type Row } from './shared.ts';
10
-
11
- const customerMissing = (ctx: SemanticsContext, id: string): Response => fail(ctx, `No such customer: '${id}'`, 404, 'resource_missing');
12
-
13
- /** The discount a customer's `coupon` makes, a parameter of API versions before the served one. */
14
- function customerDiscount(ctx: SemanticsContext, couponId: string): Row | Response {
15
- const coupon = ctx.get('coupon', couponId);
16
- return coupon ? buildDiscount(coupon, '', Number(ctx.now())) : fail(ctx, `No such coupon: '${couponId}'`, 400, 'resource_missing');
17
- }
18
-
19
- /** What a new customer answers before any invoice: its invoice_prefix and a next_invoice_sequence of 1, a zero balance,
20
- * no metadata (docs.stripe.com/api/customers/object). */
21
- export const newCustomer = (id: string): Row => ({ invoice_prefix: invoicePrefix(id), next_invoice_sequence: 1, balance: 0, metadata: {}, invoice_settings: {} });
22
-
23
- const create: Semantics = async (ctx) => {
24
- // a `coupon` on create materializes the discount object on the customer (the coupon must exist)
25
- const couponId = typeof ctx.params.coupon === 'string' ? ctx.params.coupon : '';
26
- const discount = couponId ? customerDiscount(ctx, couponId) : null;
27
- if (discount instanceof Response) return discount;
28
- // `payment_method` attaches that method to the new customer; it is not a field of the customer
29
- // (docs.stripe.com/api/customers/create#create_customer-payment_method)
30
- const pmId = typeof ctx.params.payment_method === 'string' ? ctx.params.payment_method : '';
31
- if (pmId && !attachable(ctx, pmId)) return fail(ctx, `No such PaymentMethod: '${pmId}'`, 400, 'resource_missing');
32
- const { coupon: _c, payment_method: _pm, ...rest } = ctx.params;
33
- const id = ctx.mint('customer');
34
- const customer = await created(ctx, 'customer', { ...rest, id }, { livemode: false, discount: discount ? { ...discount, customer: id } : null, ...newCustomer(id) });
35
- if (discount) await redeemCoupon(ctx, couponId);
36
- if (pmId) await attachPaymentMethod(ctx, pmId, String(customer.id));
37
- return ctx.reply(customer);
38
- };
39
-
40
- const searchCustomers: Semantics = async (ctx) => search(ctx, 'customer');
41
-
1
+ // Stripe's customers 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 { newCustomer } from '../engine/customers.ts';
5
+ import { nowUnix } from '../engine/stripe.ts';
6
+ import { at, attachable, attachPaymentMethod, created, customerMissing, expanded, fail, search } from './shared.ts';
42
7
  // "If it’s for a deleted Customer, a subset of the customer’s information is returned, including a `deleted` property
43
8
  // that’s set to true" (docs.stripe.com/api/customers/retrieve)
44
- const retrieve: Semantics = async (ctx) => {
9
+ export async function GetCustomersCustomer(ctx: HandlerContext): Promise<Response> {
45
10
  const id = at(ctx, 'customer');
46
11
  const row = ctx.row('customer', id, { withDeleted: true });
47
12
  if (!row) return customerMissing(ctx, id);
48
13
  if (row.deleted === true) return ctx.reply({ id, object: 'customer', deleted: true });
49
14
  return ctx.reply(expanded(ctx, 'customer', ctx.get('customer', id)!));
50
- };
51
-
52
- // the payment methods attached to a customer (customers.listPaymentMethods), optionally by `type`
53
- const paymentMethods: Semantics = async (ctx) => {
54
- const customer = at(ctx, 'customer');
55
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
56
- const items = where(ctx, newest(ctx, 'payment_method').filter((m) => m.customer === customer), { type: (m, v) => m.type === v });
57
- return list(ctx, 'payment_method', items);
58
- };
59
-
60
- const paymentMethod: Semantics = async (ctx) => {
61
- const customer = at(ctx, 'customer');
62
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
63
- const m = ctx.get('payment_method', at(ctx, 'payment_method'));
64
- return m && m.customer === customer ? ctx.reply(m) : fail(ctx, `No such PaymentMethod: '${at(ctx, 'payment_method')}'`, 404, 'resource_missing');
65
- };
66
-
67
- // removing the customer's discount answers the deleted-discount stub
68
- const deleteDiscount: Semantics = async (ctx) => {
69
- const id = at(ctx, 'customer');
70
- const customer = ctx.get('customer', id);
71
- if (!customer) return customerMissing(ctx, id);
72
- const discount = customer.discount as Row | null | undefined;
73
- if (!discount) return fail(ctx, `No active discount for customer: '${id}'`, 404, 'resource_missing');
74
- await ctx.write('customer', id, { discount: null }, 'customer.update');
75
- return ctx.reply(deletedDiscount(discount, ctx.now()));
76
- };
77
-
78
- // ── tax ids: a customer's, keyed under it; `type` and `value` are required ──
79
-
80
- // An EU VAT number is its country's prefix and that country's format (the European Commission's VIES formats,
81
- // ec.europa.eu/taxation_customs/vies); Stripe refuses one that does not fit as tax_id_invalid
82
- // (docs.stripe.com/error-codes#tax-id-invalid). The message's wording is the twin's.
83
- const EU_VAT: Record<string, RegExp> = {
84
- AT: /^U\d{8}$/, BE: /^[01]\d{9}$/, BG: /^\d{9,10}$/, CY: /^\d{8}[A-Z]$/, CZ: /^\d{8,10}$/, DE: /^\d{9}$/, DK: /^\d{8}$/,
85
- EE: /^\d{9}$/, EL: /^\d{9}$/, ES: /^[A-Z0-9]\d{7}[A-Z0-9]$/, FI: /^\d{8}$/, FR: /^[A-Z0-9]{2}\d{9}$/, HR: /^\d{11}$/,
86
- HU: /^\d{8}$/, IE: /^(\d{7}[A-W][A-I]?|\d[A-Z+*]\d{5}[A-W])$/, IT: /^\d{11}$/, LT: /^(\d{9}|\d{12})$/, LU: /^\d{8}$/,
87
- LV: /^\d{11}$/, MT: /^\d{8}$/, NL: /^\d{9}B\d{2}$/, PL: /^\d{10}$/, PT: /^\d{9}$/, RO: /^\d{2,10}$/, SE: /^\d{12}$/,
88
- SI: /^\d{8}$/, SK: /^\d{10}$/, XI: /^(\d{9}|\d{12}|GD\d{3}|HA\d{3})$/,
89
- };
90
- const euVatCountry = (value: string): string | undefined => {
91
- const v = value.replace(/[\s.-]/g, '').toUpperCase();
92
- const prefix = v.slice(0, 2);
93
- const format = EU_VAT[prefix];
94
- return format && format.test(v.slice(2)) ? (prefix === 'EL' ? 'GR' : prefix) : undefined;
95
- };
96
-
97
- /** The tax ID types Stripe checks against a government register: "Stripe automatically validates all Australian
98
- * Business Numbers (ABNs)", "all European Value-Added-Tax (EU VAT) numbers" (VIES) and "all UK Value-Added-Tax (GB VAT)
99
- * numbers" (HMRC); a verification's status "is pending, verified, unverified, or unavailable", and "When automatic
100
- * validation is unavailable, you must manually verify the tax ID" (docs.stripe.com/billing/customer/tax-ids). Any other
101
- * type answers unavailable; one of these waits pending. */
102
- const AUTO_VALIDATED = new Set(['au_abn', 'eu_vat', 'gb_vat']);
103
-
104
- /** A tax ID for its owner: the account itself, or one of its customers. */
105
- async function makeTaxId(ctx: SemanticsContext, customer: string | undefined): Promise<Response> {
106
- const { owner: _owner, ...params } = ctx.params;
107
- if (params.type === undefined || params.type === '') return fail(ctx, 'Missing required param: type.', 400, 'parameter_missing');
108
- if (params.value === undefined || params.value === '') return fail(ctx, 'Missing required param: value.', 400, 'parameter_missing');
109
- const vatCountry = params.type === 'eu_vat' ? euVatCountry(String(params.value)) : undefined;
110
- if (params.type === 'eu_vat' && !vatCountry) return ctx.refuse({ status: 400, code: 'tax_id_invalid', param: 'value', message: `Invalid value for eu_vat.` });
111
- return ctx.reply(
112
- await created(ctx, 'tax_id', { ...params, ...(customer ? { customer } : {}) }, {
113
- livemode: false,
114
- country: vatCountry ?? (typeof params.country === 'string' ? params.country : null),
115
- owner: customer ? { type: 'customer', customer } : { type: 'self' },
116
- verification: { status: AUTO_VALIDATED.has(String(params.type)) ? 'pending' : 'unavailable', verified_address: null, verified_name: null },
117
- }),
118
- );
119
- }
120
-
121
- const createTaxId: Semantics = async (ctx) => {
122
- const customer = at(ctx, 'customer');
123
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
124
- return makeTaxId(ctx, customer);
125
- };
126
-
127
- /** The owner a top-level tax-ID call names: the account itself unless `owner[type]=customer` names a customer
128
- * (docs.stripe.com/api/tax_ids/list: owner defaults to self). */
129
- function ownerOf(ctx: SemanticsContext): { customer?: string } | Response {
130
- const owner = (ctx.params.owner ?? {}) as Row;
131
- const type = String(owner.type ?? 'self');
132
- if (type === 'self') return {};
133
- if (type !== 'customer') return fail(ctx, `Invalid owner[type]: must be one of self or customer.`, 400, 'parameter_invalid_string_enum');
134
- const customer = typeof owner.customer === 'string' ? owner.customer : '';
135
- if (!customer) return fail(ctx, 'Missing required param: owner[customer].', 400, 'parameter_missing');
136
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
137
- return { customer };
138
15
  }
139
16
 
140
- const createOwnedTaxId: Semantics = async (ctx) => {
141
- const owner = ownerOf(ctx);
142
- return owner instanceof Response ? owner : makeTaxId(ctx, owner.customer);
143
- };
144
-
145
- const listOwnedTaxIds: Semantics = async (ctx) => {
146
- const owner = ownerOf(ctx);
147
- if (owner instanceof Response) return owner;
148
- return list(ctx, 'tax_id', newest(ctx, 'tax_id').filter((t) => (owner.customer ? t.customer === owner.customer : !t.customer)));
149
- };
150
-
151
- const taxIds: Semantics = async (ctx) => {
152
- const customer = at(ctx, 'customer');
153
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
154
- return list(ctx, 'tax_id', newest(ctx, 'tax_id').filter((t) => t.customer === customer));
155
- };
156
-
157
- const taxId: Semantics = async (ctx) => {
158
- const t = ctx.get('tax_id', at(ctx, 'id'));
159
- return t && t.customer === at(ctx, 'customer') ? ctx.reply(t) : fail(ctx, `No such tax_id: '${at(ctx, 'id')}'`, 404, 'resource_missing');
160
- };
161
-
162
- const deleteTaxId: Semantics = async (ctx) => {
163
- const id = at(ctx, 'id');
164
- const t = ctx.get('tax_id', id);
165
- if (!t || t.customer !== at(ctx, 'customer')) return fail(ctx, `No such tax_id: '${id}'`, 404, 'resource_missing');
166
- await ctx.write('tax_id', id, { deleted: true }, 'tax_id.delete');
167
- return ctx.reply({ id, object: 'tax_id', deleted: true });
168
- };
169
-
170
- // ── the credit-balance ledger: a negative amount is a credit; `ending_balance` is the running sum,
171
- // mirrored onto the customer's `balance` ──
172
-
173
- const createBalanceTransaction: Semantics = async (ctx) => {
174
- const customer = at(ctx, 'customer');
175
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
176
- const params = ctx.params;
177
- if (params.amount === undefined) return fail(ctx, 'Missing required param: amount.', 400, 'parameter_missing');
178
- const amount = Number(params.amount);
179
- if (!Number.isInteger(amount)) return fail(ctx, 'Invalid integer: amount.', 400, 'parameter_invalid_integer');
180
- if (params.currency === undefined || params.currency === '') return fail(ctx, 'Missing required param: currency.', 400, 'parameter_missing');
181
- const prior = ctx.rows('customer_balance_transaction').filter((t) => t.customer === customer).reduce((s, t) => s + (Number(t.amount) || 0), 0);
182
- const ending = prior + amount;
183
- const body = await created(ctx, 'customer_balance_transaction', { ...params, customer, amount }, {
184
- ending_balance: ending, type: 'adjustment', livemode: false, metadata: {},
185
- description: params.description ?? null, credit_note: null, invoice: null, checkout_session: null,
186
- });
187
- // keyed by the new transaction so two adjustments at one instant are two writes, the last winning
188
- await ctx.write('customer', customer, { balance: ending }, `customer.balance_adjusted:${String(body.id)}`);
189
- return ctx.reply(body);
190
- };
191
-
192
- const balanceTransactions: Semantics = async (ctx) => {
193
- const customer = at(ctx, 'customer');
194
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
195
- return list(ctx, 'customer_balance_transaction', newest(ctx, 'customer_balance_transaction').filter((t) => t.customer === customer));
196
- };
197
-
198
- const balanceTransaction: Semantics = async (ctx) => {
199
- const t = ctx.get('customer_balance_transaction', at(ctx, 'transaction'));
200
- return t && t.customer === at(ctx, 'customer') ? ctx.reply(t) : fail(ctx, `No such balance transaction: '${at(ctx, 'transaction')}'`, 404, 'resource_missing');
201
- };
202
-
203
- // ── the cash balance: per-currency sums of the funding ledger, and the reconciliation setting ──
204
-
205
- function cashBalance(ctx: SemanticsContext, customer: string): Row {
206
- const byCurrency: Record<string, number> = {};
207
- for (const t of ctx.rows('customer_cash_balance_transaction')) {
208
- if (t.customer !== customer) continue;
209
- const cur = String(t.currency ?? 'usd');
210
- byCurrency[cur] = (byCurrency[cur] ?? 0) + (Number(t.net_amount) || 0);
211
- }
212
- const cust = ctx.row('customer', customer);
213
- const mode = typeof cust?._cash_balance_mode === 'string' ? cust._cash_balance_mode : 'automatic';
214
- return {
215
- object: 'cash_balance', customer,
216
- available: Object.keys(byCurrency).length ? byCurrency : null,
217
- settings: { reconciliation_mode: mode, using_merchant_default: mode === 'merchant_default' },
218
- livemode: false,
219
- };
17
+ export async function GetCustomersSearch(ctx: HandlerContext): Promise<Response> {
18
+ return search(ctx, 'customer');
220
19
  }
221
20
 
222
- const getCashBalance: Semantics = async (ctx) => {
223
- const customer = at(ctx, 'customer');
224
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
225
- return ctx.reply(cashBalance(ctx, customer));
226
- };
227
-
228
- const updateCashBalance: Semantics = async (ctx) => {
229
- const customer = at(ctx, 'customer');
230
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
231
- const settings = ctx.params.settings && typeof ctx.params.settings === 'object' ? (ctx.params.settings as Row) : {};
232
- const mode = typeof settings.reconciliation_mode === 'string' ? settings.reconciliation_mode : 'automatic';
233
- if (!['automatic', 'manual', 'merchant_default'].includes(mode)) return fail(ctx, 'Invalid settings[reconciliation_mode].', 400, 'parameter_invalid_string_enum');
234
- await ctx.write('customer', customer, { _cash_balance_mode: mode }, 'cash_balance.updated');
235
- return ctx.reply(cashBalance(ctx, customer));
236
- };
237
-
238
- const cashBalanceTransactions: Semantics = async (ctx) => {
239
- const customer = at(ctx, 'customer');
240
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
241
- return list(ctx, 'customer_cash_balance_transaction', newest(ctx, 'customer_cash_balance_transaction').filter((t) => t.customer === customer));
242
- };
243
-
244
- /** Automatic reconciliation (docs.stripe.com/payments/customer-balance/reconciliation): funds that arrive pay the
245
- * customer's open invoices that take bank transfers (payment_method_types customer_balance), the one whose number
246
- * the transfer's reference names first, then the oldest. Where the documentation stops and the twin decides: an
247
- * invoice is paid only when the balance covers all of what remains; a smaller balance waits for more funds. */
248
- async function reconcile(ctx: SemanticsContext, customer: string, currency: string, reference: string | null): Promise<void> {
249
- const takesTransfers = (inv: Row): boolean => ((inv.payment_settings as Row | undefined)?.payment_method_types as unknown[] | undefined)?.includes('customer_balance') === true;
250
- const open = ctx.rows('invoice').filter((inv) => inv.customer === customer && inv.status === 'open' && String(inv.currency ?? 'usd') === currency && takesTransfers(inv))
251
- .sort((a, b) => (reference && a.number === reference ? -1 : reference && b.number === reference ? 1 : Number(a.created) - Number(b.created)));
252
- for (const inv of open) {
253
- const available = Number(((cashBalance(ctx, customer).available as Row | null) ?? {})[currency] ?? 0);
254
- const amount = Number(inv.amount_remaining ?? inv.amount_due) || 0;
255
- if (amount <= 0 || available < amount) continue;
256
- const id = String(inv.id);
257
- if (ctx.legal('invoice', 'status', 'PostTestHelpersCustomersCustomerFundCashBalance', inv.status, 'paid', id)) continue;
258
- // the payment the balance makes: the invoice's own PaymentIntent (every invoice finalized with an amount due has one,
259
- // semantics/invoices.ts), paid from the cash balance, its charge, and the ledger's entry
260
- const piId = String(inv.payment_intent);
261
- await created(ctx, 'customer_cash_balance_transaction', { customer }, { currency, type: 'applied_to_payment', net_amount: -amount, ending_balance: available - amount, applied_to_payment: { payment_intent: piId }, livemode: false });
262
- const charge = await mintCharge(ctx, piId, { currency, customer, invoice: id }, amount);
263
- await ctx.write('payment_intent', piId, { status: 'succeeded', amount_received: amount, latest_charge: charge }, 'payment_intent.succeeded');
264
- await ctx.write('invoice', id, {
265
- status: 'paid', paid: true, amount_paid: amount, amount_remaining: 0, payment_intent: piId, charge,
266
- status_transitions: { ...((inv.status_transitions as Row | undefined) ?? {}), paid_at: ctx.now() },
267
- }, 'invoice.paid');
21
+ export async function PostCustomers(ctx: HandlerContext): Promise<Response> {
22
+ // `payment_method` attaches that method to the new customer; it is not a field of the customer
23
+ // (docs.stripe.com/api/customers/create#create_customer-payment_method)
24
+ const pmId = typeof ctx.params.payment_method === 'string' ? ctx.params.payment_method : '';
25
+ if (pmId && !attachable(ctx, pmId)) return fail(ctx, `No such PaymentMethod: '${pmId}'`, 400, 'resource_missing');
26
+ const given = ctx.params;
27
+ const { coupon: _c, payment_method: _pm, ...rest } = given;
28
+ const id = ctx.mint('customer');
29
+ const customer = await created(ctx, 'customer', { ...rest, id }, { livemode: false, discount: null, ...newCustomer(id) });
30
+ // source: https://docs.stripe.com/billing/customer/balance "Represents the starting value of the customer invoice balance when a customer is created using the API with a non-zero invoice balance."
31
+ if (Number(customer.balance ?? 0) !== 0) {
32
+ await created(ctx, 'customer_balance_transaction', {
33
+ customer: id, amount: Number(customer.balance), ending_balance: Number(customer.balance),
34
+ currency: customer.currency ?? 'usd', type: 'initial', description: null, metadata: {},
35
+ livemode: false, invoice: null, credit_note: null, checkout_session: null, customer_account: null,
36
+ }, {});
268
37
  }
38
+ if (pmId) await attachPaymentMethod(ctx, pmId, String(customer.id));
39
+ return ctx.reply(customer);
269
40
  }
270
41
 
271
- // test mode's stand-in for a bank transfer arriving (docs.stripe.com/payments/customer-balance/funding#test):
272
- // the customer's cash balance is funded by `amount`, and in automatic reconciliation the funds pay what waits on them
273
- const fundCashBalance: Semantics = async (ctx) => {
274
- const customer = at(ctx, 'customer');
275
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
276
- const amount = Math.trunc(Number(ctx.params.amount) || 0);
277
- if (amount <= 0) return fail(ctx, 'Missing required param: amount.', 400, 'parameter_missing');
278
- const currency = typeof ctx.params.currency === 'string' ? ctx.params.currency : '';
279
- if (!currency) return fail(ctx, 'Missing required param: currency.', 400, 'parameter_missing');
280
- const before = Number(((cashBalance(ctx, customer).available as Row | null) ?? {})[currency] ?? 0);
281
- const reference = typeof ctx.params.reference === 'string' ? ctx.params.reference : null;
282
- const funded = await created(ctx, 'customer_cash_balance_transaction', { customer }, {
283
- currency, type: 'funded', net_amount: amount, ending_balance: before + amount, livemode: false,
284
- funded: { bank_transfer: { reference, type: 'us_bank_transfer', us_bank_transfer: { network: 'ach', sender_name: null } } },
285
- });
286
- if ((cashBalance(ctx, customer).settings as Row).reconciliation_mode !== 'manual') await reconcile(ctx, customer, currency, reference);
287
- return ctx.reply(funded);
288
- };
289
-
290
- const cashBalanceTransaction: Semantics = async (ctx) => {
291
- const t = ctx.get('customer_cash_balance_transaction', at(ctx, 'transaction'));
292
- return t && t.customer === at(ctx, 'customer') ? ctx.reply(t) : fail(ctx, `No such cash balance transaction: '${at(ctx, 'transaction')}'`, 404, 'resource_missing');
293
- };
294
-
295
- // ── legacy sources: attaching a token mints a card with only the safe fields; the first becomes
296
- // the customer's default_source, and detaching the default promotes the next ──
297
-
298
- const attachSource: Semantics = async (ctx) => {
299
- const customer = at(ctx, 'customer');
300
- const c = ctx.get('customer', customer);
301
- if (!c) return customerMissing(ctx, customer);
302
- const src = typeof ctx.params.source === 'string' ? ctx.params.source : '';
303
- if (!src) return fail(ctx, 'Missing required param: source.', 400, 'parameter_missing');
304
- // a card token becomes the card it carries, the token spent: "you can only use tokens once" (the served spec's
305
- // token.used); any other source reference keeps the twin's stand-in card
306
- const token = src.startsWith('tok_') ? ctx.get('token', src) : undefined;
307
- if (token?.used === true) return fail(ctx, `You cannot use a Stripe token more than once: ${src}.`, 400, 'token_already_used');
308
- const tokenCard = token?.card && typeof token.card === 'object' ? (token.card as Row) : undefined;
309
- const last4 = String(4242 + ctx.rows('source').length + 1).slice(-4);
310
- const body = await created(ctx, 'source', { customer, ...(tokenCard ? { id: String(tokenCard.id) } : {}) }, tokenCard
311
- ? { ...tokenCard, customer, _source_token: src }
312
- : { object: 'card', last4, brand: 'Visa', exp_month: 12, exp_year: 2030, funding: 'credit', country: 'US', metadata: {}, _source_token: src });
313
- if (token) await ctx.write('token', src, { used: true }, 'token.used');
314
- const existing = ctx.rows('source').filter((s) => s.customer === customer);
315
- if (!c.default_source || existing.length === 1) await ctx.write('customer', customer, { default_source: body.id }, 'customer.updated');
316
- return ctx.reply(body);
317
- };
318
-
319
- const sources: Semantics = async (ctx) => {
320
- const customer = at(ctx, 'customer');
321
- if (!ctx.get('customer', customer)) return customerMissing(ctx, customer);
322
- const items = newest(ctx, 'source').filter((s) => s.customer === customer);
323
- return list(ctx, 'source', typeof ctx.params.object === 'string' ? items.filter((s) => s.object === ctx.params.object) : items);
324
- };
325
-
326
- /** The customer's source the path names, or undefined. */
327
- const customerSource = (ctx: SemanticsContext): Row | undefined => {
328
- const s = ctx.get('source', at(ctx, 'id'));
329
- return s && s.customer === at(ctx, 'customer') ? s : undefined;
330
- };
331
- const sourceMissing = (ctx: SemanticsContext): Response => fail(ctx, `No such source: '${at(ctx, 'id')}'`, 404, 'resource_missing');
332
-
333
- const source: Semantics = async (ctx) => {
334
- const s = customerSource(ctx);
335
- return s ? ctx.reply(s) : sourceMissing(ctx);
336
- };
42
+ // ── the credit-balance ledger: a negative amount is a credit; `ending_balance` is the running sum,
43
+ // mirrored onto the customer's `balance` ──
337
44
 
338
- const updateSource: Semantics = async (ctx) => {
339
- if (!customerSource(ctx)) return sourceMissing(ctx);
340
- return ctx.reply(await ctx.write('source', at(ctx, 'id'), ctx.params, 'source.updated'));
341
- };
342
45
 
343
- const detachSource: Semantics = async (ctx) => {
344
- const customer = at(ctx, 'customer');
345
- const id = at(ctx, 'id');
346
- const c = ctx.get('customer', customer);
347
- const s = customerSource(ctx);
348
- if (!c || !s) return sourceMissing(ctx);
349
- await ctx.write('source', id, { deleted: true, customer: null }, 'source.detached');
350
- if (c.default_source === id) {
351
- const remaining = ctx.rows('source').filter((x) => x.customer === customer && x.id !== id);
352
- await ctx.write('customer', customer, { default_source: remaining[0]?.id ?? null }, 'customer.updated');
46
+ // source: spec:PostCustomersCustomer "Updates the specified customer by setting the values of the parameters passed."
47
+ export async function PostCustomersCustomer(ctx: HandlerContext): Promise<Response> {
48
+ const id = at(ctx, 'customer');
49
+ const customer = ctx.get('customer', id);
50
+ if (!customer) return customerMissing(ctx, id);
51
+ const { customer: _pathCustomer, ...fields } = ctx.params;
52
+ // source: https://docs.stripe.com/billing/customer/balance "All modifications to the invoice balance are recorded as Transactions"
53
+ // source: https://docs.stripe.com/billing/customer/balance "All Transactions created with the API or in the Dashboard have a type value of adjustment"
54
+ if (typeof fields.balance === 'number' && fields.balance !== Number(customer.balance ?? 0)) {
55
+ const balance = fields.balance;
56
+ await created(ctx, 'customer_balance_transaction', {
57
+ customer: id, amount: balance - Number(customer.balance ?? 0), ending_balance: balance,
58
+ // Stripe inherits the customer's billing currency. Where no invoice has set it, this World's invoice currency is USD.
59
+ currency: customer.currency ?? 'usd', type: 'adjustment',
60
+ description: null, metadata: {}, livemode: false,
61
+ invoice: null, credit_note: null, checkout_session: null, customer_account: null,
62
+ created: nowUnix(ctx.occurredAt),
63
+ }, {});
353
64
  }
354
- return ctx.reply({ id, object: s.object ?? 'card', deleted: true });
355
- };
356
-
357
- // ── the top-level Source: `type` is required; a card is chargeable at once, anything else pending ──
358
-
359
- const createSource: Semantics = async (ctx) => {
360
- const params = ctx.params;
361
- const srcType = typeof params.type === 'string' ? params.type : '';
362
- if (!srcType) return fail(ctx, 'Missing required param: type.', 400, 'parameter_missing');
363
- return ctx.reply(
364
- await created(ctx, 'source', { customer: null }, {
365
- object: 'source',
366
- status: srcType === 'card' ? 'chargeable' : 'pending',
367
- flow: srcType === 'card' ? 'none' : 'receiver',
368
- usage: (params.usage as string) ?? 'reusable',
369
- currency: typeof params.currency === 'string' ? params.currency : null,
370
- livemode: false,
371
- type: srcType,
372
- client_secret: `src_client_secret_twin_${ctx.rows('source').length + 1}`,
373
- owner: params.owner && typeof params.owner === 'object' ? params.owner : { name: null, email: null, address: null, phone: null, verified_name: null, verified_email: null, verified_address: null, verified_phone: null },
374
- metadata: params.metadata && typeof params.metadata === 'object' ? params.metadata : {},
375
- statement_descriptor: null,
376
- ...(srcType === 'ach_credit_transfer' ? achCreditTransfer(ctx) : {}),
377
- }),
378
- );
379
- };
380
-
381
- /** An ACH credit transfer source's account for the customer to wire to, and its receiver flow, as the sources create
382
- * page's example answers one (docs.stripe.com/api/sources/create): a test account number, "TEST BANK", routing
383
- * 110000000 and SWIFT TSTEZ122; the receiver's address "routing-account" with nothing yet charged, received or
384
- * returned, refund attributes by email and missing. Where the documentation stops and the twin decides: the account
385
- * number is the source's own. */
386
- function achCreditTransfer(ctx: SemanticsContext): Row {
387
- const account = `test_${String(ctx.rows('source').length + 1).padStart(12, '0')}`;
388
- return {
389
- ach_credit_transfer: { account_number: account, bank_name: 'TEST BANK', fingerprint: `twin_acct_${account}`, refund_account_holder_name: null, refund_account_holder_type: null, refund_routing_number: null, routing_number: '110000000', swift_code: 'TSTEZ122' },
390
- receiver: { address: `110000000-${account}`, amount_charged: 0, amount_received: 0, amount_returned: 0, refund_attributes_method: 'email', refund_attributes_status: 'missing' },
391
- };
65
+ return ctx.reply(ctx.expand('customer', await ctx.write('customer', id, fields, 'customer.update')));
392
66
  }
393
-
394
- // a Source update changes only its metadata and owner
395
- const updateTopSource: Semantics = async (ctx) => {
396
- const id = at(ctx, 'source');
397
- if (!ctx.get('source', id)) return fail(ctx, `No such source: '${id}'`, 404, 'resource_missing');
398
- const patch: Row = {};
399
- if (ctx.params.metadata !== undefined) patch.metadata = ctx.params.metadata;
400
- if (ctx.params.owner !== undefined) patch.owner = ctx.params.owner;
401
- return ctx.reply(await ctx.write('source', id, patch, 'source.updated'));
402
- };
403
-
404
- export const customers: Record<string, Semantics> = {
405
- PostCustomers: create,
406
- GetCustomersCustomer: retrieve,
407
- GetCustomersSearch: searchCustomers,
408
- GetCustomersCustomerPaymentMethods: paymentMethods,
409
- GetCustomersCustomerPaymentMethodsPaymentMethod: paymentMethod,
410
- DeleteCustomersCustomerDiscount: deleteDiscount,
411
- PostCustomersCustomerTaxIds: createTaxId,
412
- PostTaxIds: createOwnedTaxId,
413
- GetTaxIds: listOwnedTaxIds,
414
- GetCustomersCustomerTaxIds: taxIds,
415
- GetCustomersCustomerTaxIdsId: taxId,
416
- DeleteCustomersCustomerTaxIdsId: deleteTaxId,
417
- PostCustomersCustomerBalanceTransactions: createBalanceTransaction,
418
- GetCustomersCustomerBalanceTransactions: balanceTransactions,
419
- GetCustomersCustomerBalanceTransactionsTransaction: balanceTransaction,
420
- GetCustomersCustomerCashBalance: getCashBalance,
421
- PostCustomersCustomerCashBalance: updateCashBalance,
422
- GetCustomersCustomerCashBalanceTransactions: cashBalanceTransactions,
423
- GetCustomersCustomerCashBalanceTransactionsTransaction: cashBalanceTransaction,
424
- PostTestHelpersCustomersCustomerFundCashBalance: fundCashBalance,
425
- PostCustomersCustomerSources: attachSource,
426
- GetCustomersCustomerSources: sources,
427
- GetCustomersCustomerSourcesId: source,
428
- PostCustomersCustomerSourcesId: updateSource,
429
- DeleteCustomersCustomerSourcesId: detachSource,
430
- PostSources: createSource,
431
- PostSourcesSource: updateTopSource,
432
- };