@volter/twin-stripe 2.0.38 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/README.md +82 -207
  2. package/dist/generated/pack-facts.json +94 -0
  3. package/dist/spec/SOURCE.md +14 -0
  4. package/dist/spec/fixtures3.json.gz +0 -0
  5. package/dist/spec/openapi.json.gz +0 -0
  6. package/dist/spec/patches.json +34 -0
  7. package/dist/spec/sources.json +378 -0
  8. package/dist/src/cli.js +12 -30
  9. package/dist/src/engine/after-payment.d.ts +9 -0
  10. package/dist/src/engine/after-payment.js +28 -0
  11. package/dist/src/engine/apps-secrets.d.ts +1 -0
  12. package/dist/src/engine/apps-secrets.js +3 -0
  13. package/dist/src/engine/balance.d.ts +5 -0
  14. package/dist/src/engine/balance.js +15 -0
  15. package/dist/src/engine/billing.d.ts +5 -0
  16. package/dist/src/engine/billing.js +6 -0
  17. package/dist/src/engine/charges.d.ts +6 -0
  18. package/dist/src/engine/charges.js +13 -0
  19. package/dist/src/engine/checkout.d.ts +24 -0
  20. package/dist/src/engine/checkout.js +30 -0
  21. package/dist/src/engine/common.d.ts +19 -0
  22. package/dist/src/engine/common.js +32 -0
  23. package/dist/src/{screens → engine}/connect-settings.d.ts +1 -7
  24. package/dist/src/engine/connect-settings.js +26 -0
  25. package/dist/src/engine/connect.d.ts +66 -0
  26. package/dist/src/engine/connect.js +189 -0
  27. package/dist/src/engine/coupons.d.ts +0 -0
  28. package/dist/src/engine/coupons.js +5 -0
  29. package/dist/src/engine/credit-notes.d.ts +1 -0
  30. package/dist/src/engine/credit-notes.js +5 -0
  31. package/dist/src/engine/customers.d.ts +12 -0
  32. package/dist/src/engine/customers.js +30 -0
  33. package/dist/src/engine/display.d.ts +14 -0
  34. package/dist/src/engine/display.js +16 -0
  35. package/dist/src/engine/disputes.d.ts +0 -0
  36. package/dist/src/engine/disputes.js +9 -0
  37. package/dist/src/engine/entitlements.d.ts +1 -0
  38. package/dist/src/engine/entitlements.js +4 -0
  39. package/dist/src/engine/ephemeral-keys.d.ts +0 -0
  40. package/dist/src/engine/ephemeral-keys.js +4 -0
  41. package/dist/src/engine/files.d.ts +18 -0
  42. package/dist/src/engine/files.js +33 -0
  43. package/dist/src/engine/front.d.ts +5 -0
  44. package/dist/src/engine/front.js +25 -0
  45. package/dist/src/engine/invoices.d.ts +24 -0
  46. package/dist/src/engine/invoices.js +57 -0
  47. package/dist/src/engine/issuing.d.ts +35 -0
  48. package/dist/src/engine/issuing.js +45 -0
  49. package/dist/src/engine/ledger.d.ts +9 -0
  50. package/dist/src/engine/ledger.js +9 -0
  51. package/dist/src/engine/oauth.d.ts +4 -0
  52. package/dist/src/engine/oauth.js +25 -0
  53. package/dist/src/engine/onboarding.d.ts +13 -0
  54. package/dist/src/engine/onboarding.js +42 -0
  55. package/dist/src/{stripe-params.d.ts → engine/params.d.ts} +20 -3
  56. package/dist/src/{stripe-params.js → engine/params.js} +7 -60
  57. package/dist/src/engine/payment-intents.d.ts +10 -0
  58. package/dist/src/engine/payment-intents.js +27 -0
  59. package/dist/src/engine/payment-links.d.ts +1 -0
  60. package/dist/src/engine/payment-links.js +5 -0
  61. package/dist/src/engine/payment-methods.d.ts +16 -0
  62. package/dist/src/engine/payment-methods.js +22 -0
  63. package/dist/src/engine/plans.d.ts +6 -0
  64. package/dist/src/engine/plans.js +26 -0
  65. package/dist/src/engine/platform.d.ts +39 -0
  66. package/dist/src/engine/platform.js +34 -0
  67. package/dist/src/engine/products.d.ts +0 -0
  68. package/dist/src/engine/products.js +4 -0
  69. package/dist/src/engine/radar.d.ts +2 -0
  70. package/dist/src/engine/radar.js +5 -0
  71. package/dist/src/engine/refunds.d.ts +1 -0
  72. package/dist/src/engine/refunds.js +28 -0
  73. package/dist/src/engine/renewals.d.ts +36 -0
  74. package/dist/src/engine/renewals.js +65 -0
  75. package/dist/src/engine/setup-intents.d.ts +1 -0
  76. package/dist/src/engine/setup-intents.js +4 -0
  77. package/dist/src/{stripe-twin.d.ts → engine/stripe.d.ts} +11 -39
  78. package/dist/src/{stripe-twin.js → engine/stripe.js} +25 -310
  79. package/dist/src/engine/subscription-schedules.d.ts +3 -0
  80. package/dist/src/engine/subscription-schedules.js +1 -0
  81. package/dist/src/engine/subscriptions.d.ts +12 -0
  82. package/dist/src/engine/subscriptions.js +31 -0
  83. package/dist/src/engine/tax.d.ts +7 -0
  84. package/dist/src/engine/tax.js +28 -0
  85. package/dist/src/engine/terminal.d.ts +4 -0
  86. package/dist/src/engine/terminal.js +20 -0
  87. package/dist/src/engine/test-clocks.d.ts +5 -0
  88. package/dist/src/engine/test-clocks.js +9 -0
  89. package/dist/src/engine/tokens.d.ts +7 -0
  90. package/dist/src/engine/tokens.js +20 -0
  91. package/dist/src/engine/transfers.d.ts +2 -0
  92. package/dist/src/engine/transfers.js +1 -0
  93. package/dist/src/engine/treasury.d.ts +23 -0
  94. package/dist/src/engine/treasury.js +45 -0
  95. package/dist/src/{stripe-version.js → engine/version.js} +4 -3
  96. package/dist/src/engine/webhook-endpoints.d.ts +1 -0
  97. package/dist/src/engine/webhook-endpoints.js +12 -0
  98. package/dist/src/engine/webhooks.d.ts +31 -0
  99. package/dist/src/engine/webhooks.js +68 -0
  100. package/dist/src/fetch.d.ts +2 -0
  101. package/dist/src/fetch.js +26 -0
  102. package/dist/src/generated/surface.gen.json +1 -1
  103. package/dist/src/generated/ui.gen.json +1 -1
  104. package/dist/src/index.d.ts +5 -14
  105. package/dist/src/index.js +8 -75
  106. package/dist/src/manifest.js +232 -758
  107. package/dist/src/screens/billing-portal.d.ts +3 -0
  108. package/dist/src/screens/{portal.js → billing-portal.js} +28 -54
  109. package/dist/src/screens/checkout.d.ts +2 -31
  110. package/dist/src/screens/checkout.js +32 -121
  111. package/dist/src/screens/connect-oauth.d.ts +3 -27
  112. package/dist/src/screens/connect-oauth.js +51 -132
  113. package/dist/src/screens/connect-onboarding.d.ts +3 -0
  114. package/dist/src/screens/{onboarding.js → connect-onboarding.js} +36 -102
  115. package/dist/src/screens/dashboard.d.ts +3 -0
  116. package/dist/src/screens/dashboard.js +117 -0
  117. package/dist/src/screens/shared.d.ts +27 -0
  118. package/dist/src/screens/shared.js +87 -0
  119. package/dist/src/screens/stripe-js.d.ts +3 -0
  120. package/dist/src/screens/stripe-js.js +135 -0
  121. package/dist/src/semantics/account_links.d.ts +2 -0
  122. package/dist/src/semantics/account_links.js +22 -0
  123. package/dist/src/semantics/accounts.d.ts +4 -0
  124. package/dist/src/semantics/accounts.js +38 -0
  125. package/dist/src/semantics/apps.d.ts +4 -0
  126. package/dist/src/semantics/{apps-secrets.js → apps.js} +14 -26
  127. package/dist/src/semantics/around.d.ts +2 -0
  128. package/dist/src/semantics/around.js +16 -0
  129. package/dist/src/semantics/balance.d.ts +2 -11
  130. package/dist/src/semantics/balance.js +3 -194
  131. package/dist/src/semantics/balance_transactions.d.ts +2 -0
  132. package/dist/src/semantics/balance_transactions.js +15 -0
  133. package/dist/src/semantics/billing_portal.d.ts +5 -0
  134. package/dist/src/semantics/billing_portal.js +42 -0
  135. package/dist/src/semantics/charges.d.ts +3 -28
  136. package/dist/src/semantics/charges.js +5 -202
  137. package/dist/src/semantics/checkout.d.ts +3 -20
  138. package/dist/src/semantics/checkout.js +8 -261
  139. package/dist/src/semantics/clock.d.ts +2 -0
  140. package/dist/src/semantics/clock.js +21 -0
  141. package/dist/src/semantics/coupons.d.ts +2 -6
  142. package/dist/src/semantics/coupons.js +2 -60
  143. package/dist/src/semantics/customers.d.ts +6 -6
  144. package/dist/src/semantics/customers.js +27 -387
  145. package/dist/src/semantics/disputes.d.ts +2 -2
  146. package/dist/src/semantics/disputes.js +3 -49
  147. package/dist/src/semantics/doors.d.ts +23 -0
  148. package/dist/src/semantics/doors.js +73 -0
  149. package/dist/src/semantics/entitlements.d.ts +2 -2
  150. package/dist/src/semantics/entitlements.js +5 -50
  151. package/dist/src/semantics/events.d.ts +12 -0
  152. package/dist/src/semantics/events.js +36 -0
  153. package/dist/src/semantics/index.d.ts +2 -0
  154. package/dist/src/semantics/index.js +107 -0
  155. package/dist/src/semantics/invoice_payments.d.ts +2 -0
  156. package/dist/src/semantics/invoice_payments.js +12 -0
  157. package/dist/src/semantics/invoiceitems.d.ts +3 -0
  158. package/dist/src/semantics/invoiceitems.js +59 -0
  159. package/dist/src/semantics/invoices.d.ts +16 -18
  160. package/dist/src/semantics/invoices.js +118 -444
  161. package/dist/src/semantics/payment_intents.d.ts +4 -0
  162. package/dist/src/semantics/payment_intents.js +79 -0
  163. package/dist/src/semantics/payment_methods.d.ts +5 -0
  164. package/dist/src/semantics/payment_methods.js +39 -0
  165. package/dist/src/semantics/payouts.d.ts +2 -0
  166. package/dist/src/semantics/payouts.js +21 -0
  167. package/dist/src/semantics/prices.d.ts +4 -0
  168. package/dist/src/semantics/prices.js +37 -0
  169. package/dist/src/semantics/products.d.ts +6 -2
  170. package/dist/src/semantics/products.js +15 -130
  171. package/dist/src/semantics/promotion_codes.d.ts +3 -0
  172. package/dist/src/semantics/promotion_codes.js +35 -0
  173. package/dist/src/semantics/radar.d.ts +2 -2
  174. package/dist/src/semantics/radar.js +5 -74
  175. package/dist/src/semantics/refunds.d.ts +3 -9
  176. package/dist/src/semantics/refunds.js +6 -192
  177. package/dist/src/semantics/setup_intents.d.ts +3 -0
  178. package/dist/src/semantics/{setup-intents.js → setup_intents.js} +10 -53
  179. package/dist/src/semantics/shared.d.ts +822 -47
  180. package/dist/src/semantics/shared.js +3044 -78
  181. package/dist/src/semantics/states.d.ts +531 -0
  182. package/dist/src/semantics/states.js +852 -0
  183. package/dist/src/semantics/subscription_items.d.ts +5 -0
  184. package/dist/src/semantics/subscription_items.js +55 -0
  185. package/dist/src/semantics/subscription_schedules.d.ts +4 -0
  186. package/dist/src/semantics/subscription_schedules.js +60 -0
  187. package/dist/src/semantics/subscriptions.d.ts +7 -11
  188. package/dist/src/semantics/subscriptions.js +44 -415
  189. package/dist/src/semantics/test_helpers.d.ts +6 -0
  190. package/dist/src/semantics/{test-clocks.js → test_helpers.js} +14 -40
  191. package/dist/src/semantics/transfers.d.ts +2 -2
  192. package/dist/src/semantics/transfers.js +6 -101
  193. package/dist/src/server.d.ts +7 -0
  194. package/dist/src/server.js +7 -0
  195. package/generated/pack-facts.json +94 -0
  196. package/package.json +10 -20
  197. package/spec/SOURCE.md +14 -0
  198. package/spec/fixtures3.json.gz +0 -0
  199. package/spec/openapi.json.gz +0 -0
  200. package/spec/patches.json +34 -0
  201. package/spec/sources.json +378 -0
  202. package/src/cli.ts +12 -26
  203. package/src/engine/after-payment.ts +31 -0
  204. package/src/engine/apps-secrets.ts +4 -0
  205. package/src/engine/balance.ts +33 -0
  206. package/src/engine/billing.ts +16 -0
  207. package/src/engine/charges.ts +19 -0
  208. package/src/engine/checkout.ts +40 -0
  209. package/src/engine/common.ts +47 -0
  210. package/src/engine/connect-settings.ts +23 -0
  211. package/src/engine/connect.ts +184 -0
  212. package/src/engine/coupons.ts +5 -0
  213. package/src/engine/credit-notes.ts +6 -0
  214. package/src/engine/customers.ts +35 -0
  215. package/src/engine/display.ts +19 -0
  216. package/src/engine/disputes.ts +9 -0
  217. package/src/engine/entitlements.ts +5 -0
  218. package/src/engine/ephemeral-keys.ts +3 -0
  219. package/src/engine/files.ts +41 -0
  220. package/src/engine/front.ts +31 -0
  221. package/src/engine/invoices.ts +71 -0
  222. package/src/engine/issuing.ts +61 -0
  223. package/src/engine/ledger.ts +50 -0
  224. package/src/engine/oauth.ts +19 -0
  225. package/src/engine/onboarding.ts +54 -0
  226. package/src/{stripe-params.ts → engine/params.ts} +8 -56
  227. package/src/engine/payment-intents.ts +33 -0
  228. package/src/engine/payment-links.ts +6 -0
  229. package/src/engine/payment-methods.ts +25 -0
  230. package/src/engine/plans.ts +42 -0
  231. package/src/engine/platform.ts +47 -0
  232. package/src/engine/products.ts +3 -0
  233. package/src/engine/radar.ts +6 -0
  234. package/src/engine/refunds.ts +29 -0
  235. package/src/engine/renewals.ts +73 -0
  236. package/src/engine/setup-intents.ts +5 -0
  237. package/src/{stripe-twin.ts → engine/stripe.ts} +28 -323
  238. package/src/engine/subscription-schedules.ts +10 -0
  239. package/src/engine/subscriptions.ts +40 -0
  240. package/src/engine/tax.ts +39 -0
  241. package/src/engine/terminal.ts +23 -0
  242. package/src/engine/test-clocks.ts +11 -0
  243. package/src/engine/tokens.ts +22 -0
  244. package/src/engine/transfers.ts +23 -0
  245. package/src/engine/treasury.ts +47 -0
  246. package/src/{stripe-version.ts → engine/version.ts} +4 -3
  247. package/src/engine/webhook-endpoints.ts +14 -0
  248. package/src/engine/webhooks.ts +72 -0
  249. package/src/fetch.ts +27 -0
  250. package/src/generated/surface.gen.json +1 -1
  251. package/src/generated/ui.gen.json +1 -1
  252. package/src/index.ts +8 -116
  253. package/src/manifest.ts +234 -786
  254. package/src/screens/{portal.tsx → billing-portal.tsx} +32 -37
  255. package/src/screens/checkout.tsx +33 -95
  256. package/src/screens/connect-oauth.tsx +64 -88
  257. package/src/screens/{onboarding.tsx → connect-onboarding.tsx} +38 -82
  258. package/src/screens/dashboard.tsx +198 -0
  259. package/src/screens/shared.tsx +84 -0
  260. package/src/screens/stripe-js.ts +170 -0
  261. package/src/semantics/account_links.ts +21 -0
  262. package/src/semantics/accounts.ts +43 -0
  263. package/src/semantics/{apps-secrets.ts → apps.ts} +17 -32
  264. package/src/semantics/around.ts +20 -0
  265. package/src/semantics/balance.ts +6 -207
  266. package/src/semantics/balance_transactions.ts +20 -0
  267. package/src/semantics/billing_portal.ts +51 -0
  268. package/src/semantics/charges.ts +12 -211
  269. package/src/semantics/checkout.ts +13 -268
  270. package/src/semantics/clock.ts +26 -0
  271. package/src/semantics/coupons.ts +5 -67
  272. package/src/semantics/customers.ts +29 -392
  273. package/src/semantics/disputes.ts +9 -59
  274. package/src/semantics/doors.ts +80 -0
  275. package/src/semantics/entitlements.ts +9 -56
  276. package/src/semantics/events.ts +44 -0
  277. package/src/semantics/index.ts +111 -0
  278. package/src/semantics/invoice_payments.ts +18 -0
  279. package/src/semantics/invoiceitems.ts +59 -0
  280. package/src/semantics/invoices.ts +109 -443
  281. package/src/semantics/payment_intents.ts +83 -0
  282. package/src/semantics/payment_methods.ts +43 -0
  283. package/src/semantics/payouts.ts +23 -0
  284. package/src/semantics/prices.ts +42 -0
  285. package/src/semantics/products.ts +18 -142
  286. package/src/semantics/promotion_codes.ts +39 -0
  287. package/src/semantics/radar.ts +8 -77
  288. package/src/semantics/refunds.ts +9 -212
  289. package/src/semantics/setup_intents.ts +45 -0
  290. package/src/semantics/shared.ts +3092 -106
  291. package/src/semantics/states.ts +883 -0
  292. package/src/semantics/subscription_items.ts +54 -0
  293. package/src/semantics/subscription_schedules.ts +66 -0
  294. package/src/semantics/subscriptions.ts +49 -432
  295. package/src/semantics/{test-clocks.ts → test_helpers.ts} +23 -47
  296. package/src/semantics/transfers.ts +11 -131
  297. package/src/server.ts +8 -0
  298. package/client/dashboard-api.ts +0 -286
  299. package/client/stripe-mirror.css +0 -277
  300. package/client/stripe-mirror.tsx +0 -1559
  301. package/dist/client/dashboard-api.d.ts +0 -107
  302. package/dist/client/dashboard-api.js +0 -238
  303. package/dist/client/dashboard-api.ts +0 -286
  304. package/dist/client/stripe-mirror.bundle.js +0 -236
  305. package/dist/client/stripe-mirror.css +0 -277
  306. package/dist/client/stripe-mirror.d.ts +0 -134
  307. package/dist/client/stripe-mirror.js +0 -859
  308. package/dist/client/stripe-mirror.tsx +0 -1559
  309. package/dist/src/screens/connect-settings.js +0 -103
  310. package/dist/src/screens/consent-skin.d.ts +0 -4
  311. package/dist/src/screens/consent-skin.js +0 -18
  312. package/dist/src/screens/financial-connections.d.ts +0 -5
  313. package/dist/src/screens/financial-connections.js +0 -90
  314. package/dist/src/screens/identity.d.ts +0 -5
  315. package/dist/src/screens/identity.js +0 -86
  316. package/dist/src/screens/onboarding.d.ts +0 -13
  317. package/dist/src/screens/portal.d.ts +0 -5
  318. package/dist/src/screens/public-details.d.ts +0 -5
  319. package/dist/src/screens/public-details.js +0 -90
  320. package/dist/src/semantics/after-payment.d.ts +0 -22
  321. package/dist/src/semantics/after-payment.js +0 -99
  322. package/dist/src/semantics/apps-secrets.d.ts +0 -2
  323. package/dist/src/semantics/billing.d.ts +0 -2
  324. package/dist/src/semantics/billing.js +0 -220
  325. package/dist/src/semantics/connect.d.ts +0 -5
  326. package/dist/src/semantics/connect.js +0 -493
  327. package/dist/src/semantics/credit-notes.d.ts +0 -2
  328. package/dist/src/semantics/credit-notes.js +0 -172
  329. package/dist/src/semantics/ephemeral-keys.d.ts +0 -2
  330. package/dist/src/semantics/ephemeral-keys.js +0 -34
  331. package/dist/src/semantics/files.d.ts +0 -2
  332. package/dist/src/semantics/files.js +0 -125
  333. package/dist/src/semantics/issuing.d.ts +0 -13
  334. package/dist/src/semantics/issuing.js +0 -575
  335. package/dist/src/semantics/ledger.d.ts +0 -59
  336. package/dist/src/semantics/ledger.js +0 -200
  337. package/dist/src/semantics/payment-intents.d.ts +0 -18
  338. package/dist/src/semantics/payment-intents.js +0 -452
  339. package/dist/src/semantics/payment-links.d.ts +0 -2
  340. package/dist/src/semantics/payment-links.js +0 -133
  341. package/dist/src/semantics/payment-methods.d.ts +0 -20
  342. package/dist/src/semantics/payment-methods.js +0 -140
  343. package/dist/src/semantics/plans.d.ts +0 -5
  344. package/dist/src/semantics/plans.js +0 -121
  345. package/dist/src/semantics/platform.d.ts +0 -9
  346. package/dist/src/semantics/platform.js +0 -206
  347. package/dist/src/semantics/renewals.d.ts +0 -47
  348. package/dist/src/semantics/renewals.js +0 -281
  349. package/dist/src/semantics/setup-intents.d.ts +0 -2
  350. package/dist/src/semantics/subscription-schedules.d.ts +0 -2
  351. package/dist/src/semantics/subscription-schedules.js +0 -119
  352. package/dist/src/semantics/tax.d.ts +0 -2
  353. package/dist/src/semantics/tax.js +0 -197
  354. package/dist/src/semantics/terminal.d.ts +0 -5
  355. package/dist/src/semantics/terminal.js +0 -182
  356. package/dist/src/semantics/test-clocks.d.ts +0 -6
  357. package/dist/src/semantics/tokens.d.ts +0 -4
  358. package/dist/src/semantics/tokens.js +0 -44
  359. package/dist/src/semantics/treasury.d.ts +0 -2
  360. package/dist/src/semantics/treasury.js +0 -377
  361. package/dist/src/semantics/webhook-endpoints.d.ts +0 -3
  362. package/dist/src/semantics/webhook-endpoints.js +0 -85
  363. package/dist/src/stripe-budget.d.ts +0 -55
  364. package/dist/src/stripe-budget.js +0 -155
  365. package/dist/src/stripe-capabilities.d.ts +0 -3
  366. package/dist/src/stripe-capabilities.js +0 -5836
  367. package/dist/src/stripe-conformance.d.ts +0 -43
  368. package/dist/src/stripe-conformance.js +0 -105
  369. package/dist/src/stripe-connector.d.ts +0 -161
  370. package/dist/src/stripe-connector.js +0 -414
  371. package/dist/src/stripe-emit.d.ts +0 -2
  372. package/dist/src/stripe-emit.js +0 -145
  373. package/dist/src/stripe-events.d.ts +0 -93
  374. package/dist/src/stripe-events.js +0 -392
  375. package/dist/src/stripe-js.d.ts +0 -4
  376. package/dist/src/stripe-js.js +0 -70
  377. package/dist/src/stripe-mirror-ui.d.ts +0 -15
  378. package/dist/src/stripe-mirror-ui.js +0 -87
  379. package/dist/src/stripe-perform-harness.d.ts +0 -9
  380. package/dist/src/stripe-perform-harness.js +0 -26
  381. package/dist/src/stripe-server.d.ts +0 -33
  382. package/dist/src/stripe-server.js +0 -429
  383. package/dist/src/stripe-shared.d.ts +0 -109
  384. package/dist/src/stripe-shared.js +0 -276
  385. package/dist/src/stripe-ui-conformance.d.ts +0 -5
  386. package/dist/src/stripe-ui-conformance.js +0 -79
  387. package/dist/src/stripe-ui-structure.d.ts +0 -3
  388. package/dist/src/stripe-ui-structure.js +0 -168
  389. package/dist/test-fixtures/stripe-known-deviations.json +0 -110
  390. package/dist/test-fixtures/stripe-openapi-operations.SOURCE.md +0 -14
  391. package/dist/test-fixtures/stripe-openapi-operations.json +0 -4717
  392. package/dist/test-fixtures/stripe-schemas.SOURCE.md +0 -35
  393. package/dist/test-fixtures/stripe-schemas.json +0 -3813
  394. package/src/screens/connect-settings.tsx +0 -121
  395. package/src/screens/consent-skin.ts +0 -20
  396. package/src/screens/financial-connections.tsx +0 -101
  397. package/src/screens/identity.tsx +0 -96
  398. package/src/screens/public-details.tsx +0 -105
  399. package/src/semantics/after-payment.ts +0 -118
  400. package/src/semantics/billing.ts +0 -216
  401. package/src/semantics/connect.ts +0 -487
  402. package/src/semantics/credit-notes.ts +0 -168
  403. package/src/semantics/ephemeral-keys.ts +0 -34
  404. package/src/semantics/files.ts +0 -143
  405. package/src/semantics/issuing.ts +0 -590
  406. package/src/semantics/ledger.ts +0 -253
  407. package/src/semantics/payment-intents.ts +0 -463
  408. package/src/semantics/payment-links.ts +0 -148
  409. package/src/semantics/payment-methods.ts +0 -145
  410. package/src/semantics/plans.ts +0 -131
  411. package/src/semantics/platform.ts +0 -220
  412. package/src/semantics/renewals.ts +0 -279
  413. package/src/semantics/setup-intents.ts +0 -87
  414. package/src/semantics/subscription-schedules.ts +0 -129
  415. package/src/semantics/tax.ts +0 -220
  416. package/src/semantics/terminal.ts +0 -195
  417. package/src/semantics/tokens.ts +0 -52
  418. package/src/semantics/treasury.ts +0 -383
  419. package/src/semantics/webhook-endpoints.ts +0 -87
  420. package/src/stripe-budget.ts +0 -181
  421. package/src/stripe-capabilities.ts +0 -5651
  422. package/src/stripe-conformance.ts +0 -124
  423. package/src/stripe-connector.ts +0 -462
  424. package/src/stripe-emit.ts +0 -157
  425. package/src/stripe-events.ts +0 -434
  426. package/src/stripe-js.ts +0 -70
  427. package/src/stripe-mirror-ui.ts +0 -90
  428. package/src/stripe-perform-harness.ts +0 -29
  429. package/src/stripe-server.ts +0 -393
  430. package/src/stripe-shared.ts +0 -297
  431. package/src/stripe-ui-conformance.ts +0 -88
  432. package/src/stripe-ui-structure.ts +0 -182
  433. package/test-fixtures/stripe-known-deviations.json +0 -110
  434. package/test-fixtures/stripe-openapi-operations.json +0 -4717
  435. package/test-fixtures/stripe-schemas.json +0 -3813
  436. /package/dist/src/{screens → engine}/industries.d.ts +0 -0
  437. /package/dist/src/{screens → engine}/industries.js +0 -0
  438. /package/dist/src/{semantics → engine}/test-cards.d.ts +0 -0
  439. /package/dist/src/{semantics → engine}/test-cards.js +0 -0
  440. /package/dist/src/{stripe-version.d.ts → engine/version.d.ts} +0 -0
  441. /package/src/{screens → engine}/industries.ts +0 -0
  442. /package/src/{semantics → engine}/test-cards.ts +0 -0
@@ -1,859 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- // The Stripe Dashboard (manifest screen `dashboard`, dashboard.stripe.com/test/{section}): a React app
3
- // (Bun-bundled) authored as Stripe's Dashboard in test mode, reading and writing only through Stripe's API
4
- // (client/dashboard-api.ts). Its pages sit at Stripe's own paths: /test/dashboard (Home),
5
- // /test/balance/overview, /test/payments[/{pi|ch}], /test/customers[/{cus}], /test/subscriptions[/{sub}],
6
- // /test/invoices[/{in}], /test/products, /test/developers, /test/settings, and the pages under "More".
7
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
8
- import { createRoot } from 'react-dom/client';
9
- import { formatStripeAmount, formatRecurring, formatPaymentMethod, formatPaymentError, formatBalanceSummary, formatAccountFlags, accountCurrentlyDue, publicBusinessName, PLATFORM_DEFAULT_NAME, } from "../src/stripe-shared.js";
10
- import { API_BASE, list, loadBalances, loadCustomer, loadCustomers, loadDevelopers, loadHome, loadInvoices, loadPayment, loadPayments, loadProducts, loadSubscription, loadSubscriptions, retrieve, createRefund, cancelSubscription, createObject, } from "./dashboard-api.js";
11
- // ── routing: Stripe's paths, inside whatever prefix this page is served under ─────────────────────
12
- const BASE_PATH = API_BASE;
13
- export function routeOf(pathname) {
14
- const inside = pathname.startsWith(BASE_PATH) ? pathname.slice(BASE_PATH.length) : pathname;
15
- const parts = inside.split('/').filter(Boolean);
16
- return parts[0] === 'test' ? parts.slice(1) : parts;
17
- }
18
- const hrefOf = (to) => `${BASE_PATH}/test/${to.replace(/^\/+/, '')}`;
19
- // ── formatting ───────────────────────────────────────────────────────────────
20
- const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
21
- export function formatDate(ts, withTime = true) {
22
- const n = Number(ts);
23
- if (!Number.isFinite(n) || n <= 0)
24
- return '—';
25
- const d = new Date(n * 1000);
26
- const day = `${MONTHS[d.getMonth()]} ${d.getDate()}`;
27
- const sameYear = d.getFullYear() === new Date().getFullYear();
28
- if (!withTime)
29
- return `${day}, ${d.getFullYear()}`;
30
- const h = d.getHours() % 12 || 12;
31
- const time = `${h}:${String(d.getMinutes()).padStart(2, '0')} ${d.getHours() < 12 ? 'AM' : 'PM'}`;
32
- return sameYear ? `${day}, ${time}` : `${day}, ${d.getFullYear()}, ${time}`;
33
- }
34
- const money = (amount, currency) => formatStripeAmount(Number(amount) || 0, currency);
35
- const ccy = (currency) => String(currency ?? 'usd').toUpperCase();
36
- const titleCase = (s) => s.replace(/_/g, ' ').replace(/^\w/, (c) => c.toUpperCase());
37
- const customerLabel = (c) => (c ? c.name || c.email || c.id : '—');
38
- // ── icons ────────────────────────────────────────────────────────────────────
39
- const ICONS = {
40
- home: 'M8 1.5 1.5 7v7.5h4.5v-4.5h4v4.5h4.5V7L8 1.5z',
41
- balance: 'M2 4h12v9H2zM2 7h12M5 10.5h3',
42
- transactions: 'M2 5h10M9 2l3 3-3 3M14 11H4M7 8l-3 3 3 3',
43
- customers: 'M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM2.5 14.5c.6-2.8 2.8-4.5 5.5-4.5s4.9 1.7 5.5 4.5',
44
- catalog: 'M2 4.5 8 1.5l6 3v7L8 14.5l-6-3v-7zM2 4.5l6 3 6-3M8 7.5v7',
45
- connect: 'M5.5 10.5 10.5 5.5M7 3.5l1.5-1.5a3 3 0 0 1 4.5 4.5L11.5 8M9 12.5 7.5 14a3 3 0 0 1-4.5-4.5L4.5 8',
46
- payments: 'M1.5 3.5h13v9h-13zM1.5 6.5h13M4 10h2.5',
47
- billing: 'M3 1.5h10v13l-2-1.3-2 1.3-2-1.3-2 1.3-2-1.3zM5.5 5h5M5.5 8h5',
48
- reporting: 'M2 14h12M4 11V7M8 11V3M12 11V8',
49
- more: 'M3 8h.01M8 8h.01M13 8h.01',
50
- developers: 'M5 4 1.5 8 5 12M11 4l3.5 4-3.5 4',
51
- search: 'M7 12.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zM11 11l3.5 3.5',
52
- bell: 'M4 11V7a4 4 0 0 1 8 0v4l1.5 1.5h-11L4 11zM6.5 14h3',
53
- gear: 'M8 10.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM8 1v2M8 13v2M1 8h2M13 8h2M3 3l1.4 1.4M11.6 11.6 13 13M3 13l1.4-1.4M11.6 4.4 13 3',
54
- help: 'M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zM6 6a2 2 0 1 1 2.5 2c-.4.1-.5.4-.5.8V10M8 12v.01',
55
- plus: 'M8 2v12M2 8h12',
56
- check: 'M3 8.5 6.5 12 13 4.5',
57
- x: 'M4 4l8 8M12 4l-8 8',
58
- refund: 'M4 6h6.5a3 3 0 0 1 0 6H6M6.5 3.5 4 6l2.5 2.5',
59
- clock: 'M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zM8 4v4l2.5 1.5',
60
- chevronDown: 'M4 6l4 4 4-4',
61
- chevronRight: 'M6 4l4 4-4 4',
62
- dispute: 'M8 1.5 15 14H1L8 1.5zM8 6v3.5M8 11.5v.01',
63
- arrowRight: 'M2 8h11M9 4l4 4-4 4',
64
- card: 'M1.5 3.5h13v9h-13zM1.5 6.5h13',
65
- download: 'M8 2v8M4.5 6.5 8 10l3.5-3.5M2.5 13.5h11',
66
- person: 'M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM2.5 14.5c.6-2.8 2.8-4.5 5.5-4.5s4.9 1.7 5.5 4.5',
67
- };
68
- export function Icon({ name, size = 16, className }) {
69
- return (_jsx("svg", { className: `icon ${className ?? ''}`, width: size, height: size, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: ICONS[name] ?? '' }) }));
70
- }
71
- /** The card network mark Stripe shows beside a card's last four. */
72
- export function CardBrand({ brand }) {
73
- const b = String(brand ?? '').toLowerCase();
74
- if (b === 'mastercard') {
75
- return (_jsx("span", { className: "brand brand-mastercard", title: "Mastercard", children: _jsxs("svg", { width: "20", height: "12", viewBox: "0 0 20 12", "aria-hidden": "true", children: [_jsx("circle", { cx: "7", cy: "6", r: "5", fill: "#EB001B" }), _jsx("circle", { cx: "13", cy: "6", r: "5", fill: "#F79E1B", fillOpacity: "0.9" })] }) }));
76
- }
77
- if (b === 'amex')
78
- return _jsx("span", { className: "brand brand-amex", title: "American Express", children: "AMEX" });
79
- if (b === 'visa')
80
- return _jsx("span", { className: "brand brand-visa", title: "Visa", children: "VISA" });
81
- return _jsx("span", { className: "brand brand-generic", title: brand ?? 'Card', children: _jsx(Icon, { name: "card", size: 12 }) });
82
- }
83
- export function CardLabel({ pm }) {
84
- const card = pm?.card;
85
- if (!card)
86
- return _jsx("span", { className: "muted", children: pm ? formatPaymentMethod(pm) : '—' });
87
- return _jsxs("span", { className: "card-label", children: [_jsx(CardBrand, { brand: card.brand }), _jsx("span", { className: "dots", children: "\u2022\u2022\u2022\u2022" }), " ", card.last4] });
88
- }
89
- const STATUS = {
90
- succeeded: { label: 'Succeeded', tone: 'green', icon: 'check' },
91
- refunded: { label: 'Refunded', tone: 'grey', icon: 'refund' },
92
- partially_refunded: { label: 'Partial refund', tone: 'grey', icon: 'refund' },
93
- failed: { label: 'Failed', tone: 'red', icon: 'x' },
94
- incomplete: { label: 'Incomplete', tone: 'grey', icon: 'clock' },
95
- uncaptured: { label: 'Uncaptured', tone: 'blue', icon: 'clock' },
96
- canceled: { label: 'Canceled', tone: 'grey', icon: 'x' },
97
- disputed: { label: 'Disputed', tone: 'orange', icon: 'dispute' },
98
- processing: { label: 'Pending', tone: 'grey', icon: 'clock' },
99
- active: { label: 'Active', tone: 'green' },
100
- trialing: { label: 'Trialing', tone: 'blue' },
101
- past_due: { label: 'Past due', tone: 'red' },
102
- unpaid: { label: 'Unpaid', tone: 'red' },
103
- incomplete_expired: { label: 'Expired', tone: 'grey' },
104
- paused: { label: 'Paused', tone: 'grey' },
105
- paid: { label: 'Paid', tone: 'green' },
106
- open: { label: 'Open', tone: 'blue' },
107
- draft: { label: 'Draft', tone: 'grey' },
108
- void: { label: 'Void', tone: 'grey' },
109
- uncollectible: { label: 'Uncollectible', tone: 'red' },
110
- pending: { label: 'Pending', tone: 'grey' },
111
- enabled: { label: 'Enabled', tone: 'green' },
112
- disabled: { label: 'Disabled', tone: 'grey' },
113
- true: { label: 'Active', tone: 'green' },
114
- false: { label: 'Archived', tone: 'grey' },
115
- };
116
- export function StatusPill({ value, suffix }) {
117
- const key = String(value ?? '');
118
- const s = STATUS[key] ?? { label: titleCase(key || '—'), tone: 'grey' };
119
- return (_jsxs("span", { className: `pill pill-${s.tone}`, "data-status": key, children: [s.label, suffix ?? '', s.icon ? _jsx(Icon, { name: s.icon, size: 12 }) : null] }));
120
- }
121
- /** A declined payment's reason, as Stripe states it above the payment. */
122
- export function PaymentErrorBanner({ error }) {
123
- const text = formatPaymentError(error);
124
- if (!text)
125
- return null;
126
- const message = typeof error?.message === 'string' ? error.message : text;
127
- return (_jsxs("div", { className: "pay-error", role: "alert", children: [_jsx(Icon, { name: "dispute" }), _jsxs("div", { children: [_jsx("div", { className: "pay-error-label", children: "Payment error" }), _jsxs("div", { className: "pay-error-text", children: [message, " ", _jsx("span", { className: "mono muted", children: text.split(':')[0] })] })] })] }));
128
- }
129
- /** The balance's per-currency buckets: incoming (pending) and available. */
130
- export function BalanceSummary({ row }) {
131
- const buckets = formatBalanceSummary(row);
132
- if (buckets.length === 0)
133
- return null;
134
- const label = (b) => (b === 'pending' ? 'Incoming' : 'Available');
135
- return (_jsx("div", { className: "balance-summary", children: [...buckets].reverse().map((b) => (_jsxs("div", { className: `balance-bucket ${b.bucket}`, children: [_jsx("span", { className: "balance-bucket-label", children: label(b.bucket) }), _jsx("span", { className: "balance-bucket-amount", children: b.text })] }, b.bucket))) }));
136
- }
137
- /** A connected account's readiness: charges, payouts, details, and what is currently due. */
138
- export function ConnectAccountPanel({ row }) {
139
- const flags = formatAccountFlags(row);
140
- const due = accountCurrentlyDue(row);
141
- if (flags.length === 0)
142
- return null;
143
- return (_jsxs("div", { className: "connect-panel", children: [_jsx("div", { className: "connect-flags", children: flags.map((f) => (_jsxs("div", { className: `connect-flag ${f.enabled ? 'ok' : 'warn'}`, children: [_jsx("span", { className: "connect-flag-label", children: f.label }), _jsx(StatusPill, { value: f.enabled ? 'enabled' : 'disabled' })] }, f.key))) }), due.length > 0 && (_jsxs("div", { className: "connect-requirements", role: "status", children: [_jsx("span", { className: "connect-req-label", children: "Requirements currently due" }), _jsx("ul", { className: "connect-req-list", children: due.map((d) => _jsx("li", { className: "connect-req-item mono", children: d }, d)) })] }))] }));
144
- }
145
- const d = (r) => formatDate(r.created);
146
- export const SECTIONS = [
147
- { key: 'customers', label: 'Customers', path: 'customers', title: (r) => r.name || r.email || r.id, subtitle: (r) => r.email || r.id },
148
- { key: 'payment_intents', label: 'Payments', path: 'payments', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => r.description || r.id },
149
- { key: 'subscriptions', label: 'Subscriptions', path: 'subscriptions', title: (r) => r.id, subtitle: (r) => r.customer ?? '' },
150
- { key: 'invoices', label: 'Invoices', path: 'invoices', title: (r) => formatStripeAmount(r.total ?? r.amount_due, r.currency), subtitle: (r) => r.number || r.id },
151
- { key: 'products', label: 'Products', path: 'products', title: (r) => r.name || r.id, subtitle: (r) => r.description || r.id },
152
- { key: 'prices', label: 'Prices', path: 'prices', title: (r) => `${formatStripeAmount(r.unit_amount, r.currency)}${formatRecurring(r.recurring) ? ` ${formatRecurring(r.recurring)}` : ''}`, subtitle: (r) => r.product ?? r.id },
153
- { key: 'charges', label: 'Charges', path: 'charges', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => r.description || r.id },
154
- { key: 'refunds', label: 'Refunds', path: 'refunds', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => r.charge ?? r.id },
155
- { key: 'payment_methods', label: 'Payment Methods', path: 'payment-methods', title: (r) => formatPaymentMethod(r), subtitle: (r) => r.customer ?? r.id },
156
- { key: 'setup_intents', label: 'Setup Intents', path: 'setup-intents', title: (r) => r.id, subtitle: (r) => r.usage ?? '' },
157
- { key: 'invoiceitems', label: 'Invoice Items', path: 'invoice-items', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => r.description || r.customer || r.id },
158
- { key: 'credit_notes', label: 'Credit Notes', path: 'credit-notes', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => r.invoice ?? r.id },
159
- { key: 'tax_ids', label: 'Tax IDs', path: 'tax-ids', title: (r) => `${r.value ?? r.id}`, subtitle: (r) => `${r.type ?? ''}` },
160
- { key: 'balance', label: 'Balance', path: 'balance', title: (r) => r.id, subtitle: () => '' },
161
- // pages the Dashboard has, reached from the navigation
162
- { key: 'disputes', label: 'Disputes', path: 'disputes', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => `${titleCase(String(r.reason ?? ''))} · ${r.charge ?? ''}`, page: true, columns: ['Amount', 'Reason'] },
163
- { key: 'payouts', label: 'Payouts', path: 'payouts', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => `Arrives ${formatDate(r.arrival_date, false)}`, page: true, columns: ['Amount', 'Arrival'] },
164
- { key: 'balance_transactions', label: 'Balance Transactions', path: 'balance-transactions', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => `${titleCase(String(r.type ?? ''))} · ${r.source ?? r.id}`, page: true, columns: ['Amount', 'Type'] },
165
- { key: 'payment_links', label: 'Payment Links', path: 'payment-links', title: (r) => r.url || r.id, subtitle: (r) => (r.active ? 'Active' : 'Inactive'), page: true },
166
- { key: 'coupons', label: 'Coupons', path: 'coupons', title: (r) => r.name || (r.percent_off != null ? `${r.percent_off}% off` : `${formatStripeAmount(r.amount_off, r.currency)} off`), subtitle: (r) => titleCase(String(r.duration ?? '')), page: true, columns: ['Coupon', 'Duration'] },
167
- { key: 'promotion_codes', label: 'Promotion Codes', path: 'promotion-codes', title: (r) => r.code || r.id, subtitle: (r) => (typeof r.coupon === 'object' ? r.coupon?.id : r.coupon) ?? '', page: true },
168
- { key: 'quotes', label: 'Quotes', path: 'quotes', title: (r) => formatStripeAmount(r.amount_total, r.currency), subtitle: (r) => r.customer ?? r.id, page: true, columns: ['Amount', 'Customer'] },
169
- { key: 'accounts', label: 'Connected Accounts', path: 'connect/accounts', title: (r) => r.business_profile?.name || r.email || r.id, subtitle: (r) => `${titleCase(String(r.type ?? 'account'))} · ${r.country ?? ''}`, page: true },
170
- { key: 'transfers', label: 'Transfers', path: 'connect/transfers', title: (r) => formatStripeAmount(r.amount, r.currency), subtitle: (r) => `To ${r.destination ?? '—'}`, page: true, columns: ['Amount', 'Destination'] },
171
- { key: 'tax_rates', label: 'Tax Rates', path: 'tax/rates', title: (r) => `${r.display_name ?? 'Tax'} · ${r.percentage ?? 0}%`, subtitle: (r) => `${r.inclusive ? 'Inclusive' : 'Exclusive'} · ${r.jurisdiction ?? ''}`, page: true, columns: ['Tax rate', 'Type'] },
172
- { key: 'reviews', label: 'Radar Reviews', path: 'radar/reviews', title: (r) => r.charge || r.payment_intent || r.id, subtitle: (r) => (r.open ? 'Open' : titleCase(String(r.closed_reason ?? 'closed'))), page: true },
173
- { key: 'radar/value_lists', label: 'Radar Value Lists', path: 'radar/lists', title: (r) => r.name || r.alias || r.id, subtitle: (r) => `${r.item_type ?? ''} · ${r.alias ?? ''}`, page: true },
174
- { key: 'terminal/readers', label: 'Terminal Readers', path: 'terminal/readers', title: (r) => r.label || r.id, subtitle: (r) => `${titleCase(String(r.device_type ?? ''))} · ${r.status ?? ''}`, page: true },
175
- { key: 'terminal/locations', label: 'Terminal Locations', path: 'terminal/locations', title: (r) => r.display_name || r.id, subtitle: (r) => r.address?.city ?? '', page: true },
176
- { key: 'issuing/cards', label: 'Issuing Cards', path: 'issuing/cards', title: (r) => `•••• ${r.last4 ?? ''}`, subtitle: (r) => `${titleCase(String(r.type ?? ''))} · ${typeof r.cardholder === 'object' ? r.cardholder?.name ?? '' : r.cardholder ?? ''}`, page: true },
177
- { key: 'issuing/cardholders', label: 'Issuing Cardholders', path: 'issuing/cardholders', title: (r) => r.name || r.id, subtitle: (r) => r.email || titleCase(String(r.type ?? '')), page: true },
178
- { key: 'reporting/report_runs', label: 'Reports', path: 'reports', title: (r) => r.report_type || r.id, subtitle: (r) => titleCase(String(r.status ?? '')), page: true },
179
- { key: 'events', label: 'Events', path: 'events', title: (r) => r.type || r.id, subtitle: (r) => r.data?.object?.id ?? r.id, page: true, columns: ['Event', 'Object'] },
180
- { key: 'webhook_endpoints', label: 'Webhook Endpoints', path: 'webhooks', title: (r) => r.url || r.id, subtitle: (r) => `${(r.enabled_events ?? []).length} events`, page: true },
181
- ];
182
- const SECTION_BY_PATH = new Map(SECTIONS.filter((s) => s.page).map((s) => [s.path, s]));
183
- const statusOfRow = (r) => (r.status !== undefined ? r.status : r.active !== undefined ? String(r.active) : undefined);
184
- /** A Stripe list table over one resource's objects. */
185
- export function ListPane({ section, rows, query, currentId, onSelect }) {
186
- return (_jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: section.columns?.[0] ?? section.label.replace(/^(Radar|Terminal|Issuing|Tax) /, '') }), _jsx("th", { children: section.columns?.[1] ?? 'Details' }), _jsx("th", { children: "Status" }), _jsx("th", { children: "ID" }), _jsx("th", { className: "right", children: "Created" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && _jsx("tr", { className: "empty-row", children: _jsx("td", { colSpan: 5, children: _jsx(EmptyState, { title: query ? 'No results' : `No ${section.label.toLowerCase()}` }) }) }), rows.map((row, i) => (_jsxs("tr", { className: `list-row ${currentId === row.id ? 'current' : ''}`, onClick: () => onSelect(row.id), children: [_jsx("td", { className: "strong", children: section.title(row) }), _jsx("td", { className: "muted", children: section.subtitle(row) }), _jsx("td", { children: statusOfRow(row) !== undefined ? _jsx(StatusPill, { value: statusOfRow(row) }) : null }), _jsx("td", { className: "mono muted", children: row.id }), _jsx("td", { className: "right muted nowrap", children: d(row) })] }, row.id ?? i)))] })] }));
187
- }
188
- export function globalSearch(data, query, limit = 12) {
189
- const q = query.trim().toLowerCase();
190
- if (!q)
191
- return [];
192
- const hits = [];
193
- const add = (rows, kind, route, label, sub, fields) => {
194
- for (const r of rows ?? []) {
195
- if (hits.length >= limit)
196
- return;
197
- if (fields(r).some((f) => String(f ?? '').toLowerCase().includes(q)))
198
- hits.push({ to: `${route}/${r.id}`, kind, label: label(r), sub: sub(r) });
199
- }
200
- };
201
- add(data.customers, 'Customers', 'customers', (r) => r.name || r.email || r.id, (r) => r.email || r.id, (r) => [r.id, r.name, r.email, r.description]);
202
- add(data.payment_intents, 'Payments', 'payments', (r) => `${formatStripeAmount(r.amount, r.currency)} ${ccy(r.currency)}`, (r) => r.description || r.id, (r) => [r.id, r.description, r.latest_charge, (Number(r.amount) / 100).toFixed(2)]);
203
- add(data.subscriptions, 'Subscriptions', 'subscriptions', (r) => r.id, (r) => titleCase(String(r.status ?? '')), (r) => [r.id]);
204
- add(data.invoices, 'Invoices', 'invoices', (r) => r.number || r.id, (r) => `${formatStripeAmount(r.total, r.currency)} · ${titleCase(String(r.status ?? ''))}`, (r) => [r.id, r.number, r.customer_email]);
205
- add(data.products, 'Products', 'products', (r) => r.name || r.id, (r) => r.description || r.id, (r) => [r.id, r.name, r.description]);
206
- return hits;
207
- }
208
- function SearchBox({ go }) {
209
- const [q, setQ] = useState('');
210
- const [open, setOpen] = useState(false);
211
- const [data, setData] = useState({});
212
- const input = useRef(null);
213
- const load = useCallback(async () => {
214
- const keys = ['customers', 'payment_intents', 'subscriptions', 'invoices', 'products'];
215
- const lists = await Promise.all(keys.map((k) => list(`/v1/${k}${k === 'subscriptions' ? '?status=all' : ''}`).catch(() => [])));
216
- setData(Object.fromEntries(keys.map((k, i) => [k, lists[i]])));
217
- }, []);
218
- useEffect(() => {
219
- const onKey = (e) => {
220
- const typing = e.target?.closest?.('input,textarea,select');
221
- if (!typing && (e.key === '/' || ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k'))) {
222
- e.preventDefault();
223
- input.current?.focus();
224
- }
225
- if (e.key === 'Escape') {
226
- setOpen(false);
227
- input.current?.blur();
228
- }
229
- };
230
- window.addEventListener('keydown', onKey);
231
- return () => window.removeEventListener('keydown', onKey);
232
- }, []);
233
- const hits = useMemo(() => globalSearch(data, q), [data, q]);
234
- const pick = (h) => { go(h.to); setOpen(false); setQ(''); input.current?.blur(); };
235
- return (_jsxs("div", { className: "search", children: [_jsx(Icon, { name: "search", className: "search-icon" }), _jsx("input", { ref: input, className: "search-input cmdk-input", placeholder: "Search", value: q, onFocus: () => { setOpen(true); void load(); }, onBlur: () => window.setTimeout(() => setOpen(false), 150), onChange: (e) => { setQ(e.target.value); setOpen(true); }, onKeyDown: (e) => { if (e.key === 'Enter' && hits[0])
236
- pick(hits[0]); } }), open && q ? (_jsxs("div", { className: "cmdk", role: "listbox", "aria-label": "Search results", children: [hits.length === 0 && _jsxs("div", { className: "cmdk-empty", children: ["No results for \u201C", q, "\u201D"] }), hits.map((h, i) => (_jsxs(React.Fragment, { children: [i === 0 || hits[i - 1].kind !== h.kind ? _jsx("div", { className: "cmdk-group", children: h.kind }) : null, _jsxs("button", { className: "cmdk-hit", role: "option", onMouseDown: (e) => e.preventDefault(), onClick: () => pick(h), children: [_jsx("span", { className: "cmdk-hit-label", children: h.label }), _jsx("span", { className: "cmdk-hit-sub", children: h.sub })] })] }, h.to)))] })) : null] }));
237
- }
238
- const PRIMARY_NAV = [
239
- { to: 'dashboard', label: 'Home', icon: 'home' },
240
- { to: 'balance/overview', label: 'Balances', icon: 'balance', match: ['balance', 'payouts', 'balance-transactions'] },
241
- { to: 'payments', label: 'Transactions', icon: 'transactions', match: ['payments', 'disputes'] },
242
- { to: 'customers', label: 'Customers', icon: 'customers' },
243
- { to: 'products', label: 'Product catalog', icon: 'catalog', match: ['products', 'coupons', 'promotion-codes'] },
244
- ];
245
- const PRODUCT_NAV = [
246
- { label: 'Connect', icon: 'connect', items: [{ to: 'connect/accounts', label: 'Connected accounts' }, { to: 'connect/transfers', label: 'Transfers' }] },
247
- { label: 'Payments', icon: 'payments', items: [{ to: 'disputes', label: 'Disputes' }, { to: 'radar/reviews', label: 'Radar' }, { to: 'payment-links', label: 'Payment Links' }, { to: 'terminal/readers', label: 'Terminal' }] },
248
- { label: 'Billing', icon: 'billing', items: [{ to: 'subscriptions', label: 'Subscriptions' }, { to: 'invoices', label: 'Invoices' }, { to: 'quotes', label: 'Quotes' }] },
249
- { label: 'Reporting', icon: 'reporting', items: [{ to: 'reports', label: 'Reports' }] },
250
- { label: 'More', icon: 'more', items: [{ to: 'tax/rates', label: 'Tax' }, { to: 'issuing/cards', label: 'Issuing' }] },
251
- ];
252
- const isActive = (route, item) => {
253
- const here = route.join('/');
254
- return [item.to, ...(item.match ?? [])].some((m) => here === m || here.startsWith(`${m}/`) || route[0] === m);
255
- };
256
- export function SideNav({ route, go }) {
257
- const [open, setOpen] = useState({ Billing: true });
258
- const link = (item, sub = false) => (_jsxs("a", { href: hrefOf(item.to), className: `nav-item ${sub ? 'nav-sub' : ''} ${isActive(route, item) ? 'active' : ''}`, onClick: (e) => { e.preventDefault(); go(item.to); }, children: [item.icon ? _jsx(Icon, { name: item.icon }) : null, _jsx("span", { children: item.label })] }, item.to));
259
- return (_jsxs("nav", { className: "side", "aria-label": "Main", children: [_jsxs("button", { className: "account-switcher", onClick: () => go('settings'), children: [_jsx("span", { className: "account-mark", children: "T" }), _jsx("span", { className: "account-name", children: "Twin Inc." }), _jsx(Icon, { name: "chevronDown", size: 12 })] }), _jsx("div", { className: "nav-group", children: PRIMARY_NAV.map((i) => link(i)) }), _jsx("div", { className: "nav-heading", children: "Products" }), _jsx("div", { className: "nav-group", children: PRODUCT_NAV.map((g) => {
260
- const expanded = open[g.label] || g.items.some((i) => isActive(route, i));
261
- return (_jsxs("div", { className: "nav-folder", children: [_jsxs("button", { className: `nav-item ${expanded ? 'expanded' : ''}`, "aria-expanded": expanded, onClick: () => setOpen((o) => ({ ...o, [g.label]: !expanded })), children: [_jsx(Icon, { name: g.icon }), _jsx("span", { children: g.label }), _jsx(Icon, { name: expanded ? 'chevronDown' : 'chevronRight', size: 12, className: "nav-caret" })] }), expanded ? g.items.map((i) => link(i, true)) : null] }, g.label));
262
- }) }), _jsx("div", { className: "nav-foot", children: link({ to: 'developers', label: 'Developers', icon: 'developers', match: ['developers', 'events', 'webhooks'] }) })] }));
263
- }
264
- function TopBar({ go }) {
265
- return (_jsxs("header", { className: "topbar", children: [_jsx(SearchBox, { go: go }), _jsxs("div", { className: "topbar-actions", children: [_jsx("button", { className: "icon-btn", title: "Help", "aria-label": "Help", children: _jsx(Icon, { name: "help" }) }), _jsx("button", { className: "icon-btn", title: "Notifications", "aria-label": "Notifications", children: _jsx(Icon, { name: "bell" }) }), _jsx("button", { className: "icon-btn", title: "Settings", "aria-label": "Settings", onClick: () => go('settings'), children: _jsx(Icon, { name: "gear" }) }), _jsx("button", { className: "icon-btn create", title: "Create", "aria-label": "Create", onClick: () => go('customers?create=1'), children: _jsx(Icon, { name: "plus" }) })] })] }));
266
- }
267
- export function TestModeBand() {
268
- return (_jsxs("div", { className: "test-band", role: "note", children: [_jsx("span", { className: "test-band-tag", children: "Test mode" }), _jsx("span", { className: "test-band-text", children: "You\u2019re using test data. Changes you make here don\u2019t affect your live account." })] }));
269
- }
270
- // ── data per page ────────────────────────────────────────────────────────────
271
- /** Load a page's data through the API, refresh it every few seconds, and after a write. */
272
- function useApi(loader, deps) {
273
- const [state, setState] = useState({});
274
- const [tick, setTick] = useState(0);
275
- useEffect(() => {
276
- let live = true;
277
- const run = () => loader().then((data) => { if (live)
278
- setState({ data }); }, (e) => { if (live)
279
- setState((s) => ({ ...s, error: String(e?.message ?? e) })); });
280
- void run();
281
- const timer = window.setInterval(run, 4000);
282
- return () => { live = false; window.clearInterval(timer); };
283
- // eslint-disable-next-line react-hooks/exhaustive-deps
284
- }, [...deps, tick]);
285
- return { ...state, reload: () => setTick((t) => t + 1) };
286
- }
287
- function EmptyState({ title, body }) {
288
- return _jsxs("div", { className: "empty", children: [_jsx("div", { className: "empty-title", children: title }), body ? _jsx("div", { className: "empty-body", children: body }) : null] });
289
- }
290
- function Loading({ error }) {
291
- return error ? _jsx("div", { className: "page-error", role: "alert", children: error }) : _jsx("div", { className: "loading", children: "Loading\u2026" });
292
- }
293
- function PageHead({ title, actions, kicker, children }) {
294
- return (_jsxs("div", { className: "page-head", children: [kicker ? _jsx("div", { className: "kicker", children: kicker }) : null, _jsxs("div", { className: "page-head-row", children: [_jsx("h1", { children: title }), actions ? _jsx("div", { className: "page-actions", children: actions }) : null] }), children] }));
295
- }
296
- function FilterCards({ options, value, onChange }) {
297
- return (_jsx("div", { className: "filter-cards", role: "tablist", children: options.map((o) => (_jsxs("button", { role: "tab", "aria-selected": value === o.key, className: `filter-card ${value === o.key ? 'selected' : ''}`, onClick: () => onChange(o.key), children: [_jsx("span", { className: "filter-card-label", children: o.label }), _jsx("span", { className: "filter-card-count", children: o.count })] }, o.key))) }));
298
- }
299
- /** One of the toolbar's filter chips: a dashed pill that opens a list of values; a chosen value fills it. */
300
- function FilterChip({ label, value, options, onChange }) {
301
- const [open, setOpen] = useState(false);
302
- const chosen = options.find((o) => o.key === value);
303
- return (_jsxs("div", { className: "chip-wrap", children: [_jsxs("button", { className: `chip ${chosen ? 'chip-set' : ''}`, "aria-expanded": open, onClick: () => setOpen((o) => !o), children: [_jsx("span", { className: "chip-plus", onClick: (e) => { if (chosen) {
304
- e.stopPropagation();
305
- onChange(undefined);
306
- setOpen(false);
307
- } }, children: _jsx(Icon, { name: chosen ? 'x' : 'plus', size: 10 }) }), label, chosen ? _jsxs(_Fragment, { children: [_jsx("span", { className: "chip-sep", children: "|" }), _jsx("span", { className: "chip-value", children: chosen.label })] }) : null] }), open ? (_jsxs("div", { className: "popover", role: "menu", "aria-label": `Filter by ${label}`, children: [_jsxs("div", { className: "popover-title", children: ["Filter by ", label.toLowerCase()] }), options.map((o) => (_jsxs("button", { role: "menuitemradio", "aria-checked": o.key === value, className: `popover-item ${o.key === value ? 'checked' : ''}`, onClick: () => { onChange(o.key); setOpen(false); }, children: [_jsx("span", { className: "popover-radio" }), o.label] }, o.key)))] })) : null] }));
308
- }
309
- /** Download the rows the table shows as a CSV, as the Dashboard's Export does. */
310
- function exportCsv(name, header, rows) {
311
- const cell = (v) => `"${String(v).replace(/"/g, '""')}"`;
312
- const csv = [header, ...rows].map((r) => r.map(cell).join(',')).join('\n');
313
- const a = document.createElement('a');
314
- a.href = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
315
- a.download = `${name}.csv`;
316
- a.click();
317
- URL.revokeObjectURL(a.href);
318
- }
319
- function ResultCount({ n }) {
320
- return (_jsxs("div", { className: "table-foot", children: [_jsxs("span", { children: [n, " ", n === 1 ? 'result' : 'results'] }), _jsxs("span", { className: "pager", children: [_jsx("button", { className: "btn", disabled: true, children: "Previous" }), _jsx("button", { className: "btn", disabled: true, children: "Next" })] })] }));
321
- }
322
- function Section({ title, actions, children, className }) {
323
- return (_jsxs("section", { className: `section ${className ?? ''}`, children: [_jsxs("div", { className: "section-head", children: [_jsx("h2", { children: title }), actions] }), children] }));
324
- }
325
- function Props({ rows }) {
326
- return (_jsx("dl", { className: "props", children: rows.map(([k, v]) => _jsxs("div", { className: "prop", children: [_jsx("dt", { children: k }), _jsx("dd", { children: v ?? '—' })] }, k)) }));
327
- }
328
- function Amount({ amount, currency }) {
329
- return _jsxs("span", { className: "amount", children: [_jsx("span", { className: "amount-value", children: money(amount, currency) }), " ", _jsx("span", { className: "amount-ccy", children: ccy(currency) })] });
330
- }
331
- function Link({ to, go, children, className }) {
332
- return _jsx("a", { href: hrefOf(to), className: `link ${className ?? ''}`, onClick: (e) => { e.preventDefault(); e.stopPropagation(); go(to); }, children: children });
333
- }
334
- // ── Home ─────────────────────────────────────────────────────────────────────
335
- export function computeHomeMetrics(data) {
336
- const succeeded = data.payments.filter((p) => ['succeeded', 'refunded', 'partially_refunded', 'disputed'].includes(p.status));
337
- const currency = succeeded[0]?.currency ?? 'usd';
338
- const grossVolume = succeeded.reduce((s, p) => s + p.amount, 0);
339
- const refunded = succeeded.reduce((s, p) => s + p.amountRefunded, 0);
340
- const available = data.balance ? (data.balance.available ?? []).find((b) => b.currency === currency)?.amount ?? 0 : 0;
341
- const pending = data.balance ? (data.balance.pending ?? []).find((b) => b.currency === currency)?.amount ?? 0 : 0;
342
- return {
343
- currency, grossVolume, netVolume: grossVolume - refunded, available: Number(available) || 0, pending: Number(pending) || 0,
344
- succeededPayments: data.payments.filter((p) => p.status === 'succeeded').length,
345
- failedPayments: data.payments.filter((p) => p.status === 'failed').length,
346
- customers: data.customers.length,
347
- activeSubscriptions: data.subscriptions.filter((s) => s.status === 'active' || s.status === 'trialing').length,
348
- openInvoices: data.invoices.filter((i) => i.status === 'open').length,
349
- };
350
- }
351
- function Sparkline({ points, of = points.length }) {
352
- const w = 280, h = 64;
353
- const max = Math.max(1, ...points);
354
- const xy = points.map((p, i) => `${(i / Math.max(1, of - 1)) * w},${h - 4 - (p / max) * (h - 12)}`);
355
- return (_jsxs("svg", { className: "spark", viewBox: `0 0 ${w} ${h}`, preserveAspectRatio: "none", "aria-hidden": "true", children: [_jsx("polyline", { points: xy.join(' '), fill: "none", stroke: "#635BFF", strokeWidth: "2", vectorEffect: "non-scaling-stroke" }), _jsx("line", { x1: "0", x2: w, y1: h - 1, y2: h - 1, stroke: "#EBEEF1" })] }));
356
- }
357
- function cumulativeByHour(payments) {
358
- const out = new Array(24).fill(0);
359
- for (const p of payments)
360
- if (p.status !== 'failed' && p.status !== 'incomplete')
361
- out[new Date(p.created * 1000).getHours()] += p.amount;
362
- for (let i = 1; i < 24; i++)
363
- out[i] += out[i - 1];
364
- // today's line stops at the current hour, as Stripe's does
365
- return out.slice(0, new Date().getHours() + 1);
366
- }
367
- export function HomeScreen({ go }) {
368
- const { data, error } = useApi(loadHome, []);
369
- if (!data)
370
- return _jsx(Loading, { error: error });
371
- const m = computeHomeMetrics(data);
372
- const curve = cumulativeByHour(data.payments);
373
- const overview = [
374
- { key: 'gross', label: 'Gross volume', value: money(m.grossVolume, m.currency) },
375
- { key: 'net', label: 'Net volume', value: money(m.netVolume, m.currency) },
376
- { key: 'payments', label: 'Successful payments', value: String(m.succeededPayments) },
377
- { key: 'failed', label: 'Failed payments', value: String(m.failedPayments) },
378
- { key: 'customers', label: 'New customers', value: String(m.customers) },
379
- { key: 'subs', label: 'Active subscribers', value: String(m.activeSubscriptions) },
380
- ];
381
- return (_jsxs("div", { className: "home-screen", children: [_jsx(PageHead, { title: "Today" }), _jsxs("div", { className: "today", children: [_jsxs("div", { className: "today-chart", children: [_jsxs("div", { className: "today-label", children: ["Gross volume ", _jsx(Icon, { name: "chevronDown", size: 12 })] }), _jsx("div", { className: "today-value", children: money(m.grossVolume, m.currency) }), _jsx(Sparkline, { points: curve, of: 24 }), _jsxs("div", { className: "today-axis", children: [_jsx("span", { children: "12:00 AM" }), _jsx("span", { children: "11:59 PM" })] })] }), _jsxs("div", { className: "today-side", children: [_jsxs("div", { className: "today-side-block", children: [_jsxs("div", { className: "today-label", children: [ccy(m.currency), " balance"] }), _jsx("div", { className: "today-side-value", children: money(m.available + m.pending, m.currency) }), _jsx(Link, { to: "balance/overview", go: go, children: "View" })] }), _jsxs("div", { className: "today-side-block", children: [_jsx("div", { className: "today-label", children: "Payouts" }), _jsx("div", { className: "today-side-value", children: money(0, m.currency) }), _jsx(Link, { to: "payouts", go: go, children: "View" })] })] })] }), _jsx("h2", { className: "overview-title", children: "Your overview" }), _jsx("div", { className: "home-metrics", children: overview.map((c) => (_jsxs("div", { className: "metric-card", "data-metric": c.key, children: [_jsx("span", { className: "metric-label", children: c.label }), _jsx("span", { className: "metric-value", children: c.value })] }, c.key))) }), _jsx(Section, { title: "Recent payments", actions: _jsx(Link, { to: "payments", go: go, children: "View all" }), children: _jsx(PaymentsTable, { payments: data.payments.slice(0, 5), go: go, compact: true }) })] }));
382
- }
383
- const matchesFilter = (p, f) => f === 'all' || (f === 'refunded' ? p.status === 'refunded' || p.status === 'partially_refunded' : p.status === f);
384
- export function PaymentsTable({ payments, go, compact, noCustomer }) {
385
- const cols = (compact ? 6 : 8) - (noCustomer ? 1 : 0);
386
- return (_jsxs("table", { className: "table payments-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Amount" }), _jsx("th", {}), _jsx("th", { children: "Payment method" }), _jsx("th", { children: "Description" }), noCustomer ? null : _jsx("th", { children: "Customer" }), _jsx("th", { children: "Date" }), compact ? null : _jsxs(_Fragment, { children: [_jsx("th", { children: "Refunded date" }), _jsx("th", { children: "Decline reason" })] })] }) }), _jsxs("tbody", { children: [payments.length === 0 && _jsx("tr", { className: "empty-row", children: _jsx("td", { colSpan: cols, children: _jsx(EmptyState, { title: "No payments", body: compact ? undefined : 'Payments you create through the API show up here.' }) }) }), payments.map((p) => (_jsxs("tr", { className: "list-row", onClick: () => go(`payments/${p.id}`), children: [_jsx("td", { className: "nowrap", children: _jsx(Amount, { amount: p.amount, currency: p.currency }) }), _jsx("td", { children: _jsx(StatusPill, { value: p.status }) }), _jsx("td", { className: "nowrap", children: _jsx(CardLabel, { pm: p.paymentMethod }) }), _jsx("td", { className: "truncate", children: p.description ?? _jsx("span", { className: "muted", children: p.id }) }), noCustomer ? null : _jsx("td", { className: "truncate", children: p.customer ? p.customer.email || p.customer.name || p.customer.id : _jsx("span", { className: "muted", children: "\u2014" }) }), _jsx("td", { className: "nowrap muted", children: formatDate(p.created) }), compact ? null : _jsxs(_Fragment, { children: [_jsx("td", { className: "nowrap muted", children: p.refundedAt ? formatDate(p.refundedAt) : '—' }), _jsx("td", { className: "nowrap muted", children: p.declineReason ?? '—' })] })] }, p.id)))] })] }));
387
- }
388
- const DAY = 86400;
389
- const DATE_RANGES = [{ key: 'today', label: 'Today' }, { key: '7d', label: 'Last 7 days' }, { key: '4w', label: 'Last 4 weeks' }, { key: '12m', label: 'Last 12 months' }];
390
- function inRange(ts, range) {
391
- if (!range)
392
- return true;
393
- const now = Date.now() / 1000;
394
- if (range === 'today') {
395
- const d = new Date();
396
- d.setHours(0, 0, 0, 0);
397
- return ts >= d.getTime() / 1000;
398
- }
399
- return ts >= now - ({ '7d': 7, '4w': 28, '12m': 365 }[range] ?? 0) * DAY;
400
- }
401
- function PaymentsPage({ go }) {
402
- const { data, error } = useApi(loadPayments, []);
403
- const [filter, setFilter] = useState('all');
404
- const [chips, setChips] = useState({});
405
- const all = data ?? [];
406
- const payments = all.filter((p) => inRange(p.created, chips.date) && (!chips.currency || p.currency === chips.currency)
407
- && (!chips.status || p.status === chips.status) && (!chips.method || (p.paymentMethod?.card?.brand ?? p.paymentMethod?.type) === chips.method));
408
- const count = (f) => payments.filter((p) => matchesFilter(p, f)).length;
409
- const shown = payments.filter((p) => matchesFilter(p, filter));
410
- const uniq = (xs) => [...new Set(xs.filter((x) => Boolean(x)))];
411
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Transactions", children: _jsxs("div", { className: "tabs", children: [_jsx(Link, { to: "payments", go: go, className: "tab active", children: "Payments" }), _jsx(Link, { to: "disputes", go: go, className: "tab", children: "Disputes" })] }) }), _jsx(FilterCards, { value: filter, onChange: setFilter, options: [
412
- { key: 'all', label: 'All', count: payments.length }, { key: 'succeeded', label: 'Succeeded', count: count('succeeded') },
413
- { key: 'refunded', label: 'Refunded', count: count('refunded') }, { key: 'disputed', label: 'Disputed', count: count('disputed') },
414
- { key: 'failed', label: 'Failed', count: count('failed') }, { key: 'uncaptured', label: 'Uncaptured', count: count('uncaptured') },
415
- ] }), _jsxs("div", { className: "toolbar", children: [_jsx(FilterChip, { label: "Date and time", value: chips.date, options: DATE_RANGES, onChange: (date) => setChips((c) => ({ ...c, date })) }), _jsx(FilterChip, { label: "Currency", value: chips.currency, options: uniq(all.map((p) => p.currency)).map((c) => ({ key: c, label: ccy(c) })), onChange: (currency) => setChips((c) => ({ ...c, currency })) }), _jsx(FilterChip, { label: "Status", value: chips.status, options: uniq(all.map((p) => p.status)).map((k) => ({ key: k, label: STATUS[k]?.label ?? k })), onChange: (status) => setChips((c) => ({ ...c, status })) }), _jsx(FilterChip, { label: "Payment method", value: chips.method, options: uniq(all.map((p) => p.paymentMethod?.card?.brand ?? p.paymentMethod?.type)).map((b) => ({ key: b, label: b === 'amex' ? 'American Express' : titleCase(b) })), onChange: (method) => setChips((c) => ({ ...c, method })) }), _jsx("div", { className: "toolbar-right", children: _jsxs("button", { className: "btn", onClick: () => exportCsv('payments', ['id', 'Amount', 'Currency', 'Status', 'Description', 'Customer Email', 'Created'], shown.map((p) => [p.id, (p.amount / 100).toFixed(2), p.currency, p.status, p.description ?? '', p.customer?.email ?? '', new Date(p.created * 1000).toISOString()])), children: [_jsx(Icon, { name: "download", size: 14 }), " Export"] }) })] }), data ? _jsxs(_Fragment, { children: [_jsx(PaymentsTable, { payments: shown, go: go }), _jsx(ResultCount, { n: shown.length })] }) : _jsx(Loading, { error: error })] }));
416
- }
417
- const EVENT_TEXT = {
418
- 'payment_intent.created': 'Payment started',
419
- 'payment_intent.succeeded': 'Payment succeeded',
420
- 'payment_intent.payment_failed': 'Payment failed',
421
- 'payment_intent.canceled': 'Payment canceled',
422
- 'payment_intent.amount_capturable_updated': 'Payment authorized',
423
- 'payment_intent.requires_action': 'Payment requires authentication',
424
- 'charge.succeeded': 'Payment succeeded',
425
- 'charge.failed': 'Payment failed',
426
- 'charge.captured': 'Payment captured',
427
- 'charge.dispute.created': 'Payment disputed',
428
- 'refund.created': 'Refund initiated',
429
- 'refund.updated': 'Refund updated',
430
- 'customer.created': 'Customer created',
431
- 'customer.updated': 'Customer updated',
432
- 'customer.subscription.created': 'Subscription created',
433
- 'customer.subscription.updated': 'Subscription updated',
434
- 'customer.subscription.deleted': 'Subscription canceled',
435
- 'invoice.created': 'Invoice created',
436
- 'invoice.finalized': 'Invoice finalized',
437
- 'invoice.paid': 'Invoice paid',
438
- 'invoice.payment_failed': 'Invoice payment failed',
439
- };
440
- function eventText(e) {
441
- const obj = e.data?.object ?? {};
442
- if (e.type === 'charge.refunded')
443
- return `${money(obj.amount_refunded, obj.currency)} ${ccy(obj.currency)} refunded`;
444
- if (e.type === 'refund.created')
445
- return `Refund of ${money(obj.amount, obj.currency)} ${ccy(obj.currency)} initiated`;
446
- if (e.type === 'payment_intent.payment_failed')
447
- return `Payment failed${obj.last_payment_error?.message ? `: ${obj.last_payment_error.message}` : ''}`;
448
- return EVENT_TEXT[e.type] ?? e.type;
449
- }
450
- const eventTone = (type) => (/failed|dispute/.test(type) ? 'red' : /succeeded|paid|captured/.test(type) ? 'green' : /refund/.test(type) ? 'grey' : 'blue');
451
- function Timeline({ events }) {
452
- if (events.length === 0)
453
- return _jsx(EmptyState, { title: "No events" });
454
- return (_jsx("ol", { className: "timeline", children: events.map((e) => (_jsxs("li", { className: "timeline-item", children: [_jsx("span", { className: `timeline-dot dot-${eventTone(e.type)}` }), _jsxs("div", { className: "timeline-body", children: [_jsx("div", { className: "timeline-text", children: eventText(e) }), _jsx("div", { className: "timeline-time", children: formatDate(e.created) })] })] }, e.id))) }));
455
- }
456
- function PaymentPage({ id, go }) {
457
- const { data, error, reload } = useApi(() => loadPayment(id), [id]);
458
- const [refunding, setRefunding] = useState(false);
459
- if (!data)
460
- return _jsx(Loading, { error: error });
461
- return (_jsxs(_Fragment, { children: [_jsx(PaymentDetailView, { p: data, go: go, onRefund: () => setRefunding(true) }), refunding ? _jsx(RefundDialog, { payment: data, onClose: () => setRefunding(false), onDone: () => { setRefunding(false); reload(); } }) : null] }));
462
- }
463
- /** A payment's page: amount and status, who and how, the timeline of its events, the money, its refunds. */
464
- export function PaymentDetailView({ p, go, onRefund }) {
465
- const refundable = Boolean(p.charge) && (p.status === 'succeeded' || p.status === 'partially_refunded') && p.amount - p.amountRefunded > 0;
466
- const card = p.paymentMethod?.card;
467
- const net = p.amount - p.amountRefunded;
468
- return (_jsxs("div", { className: "page detail-page", children: [_jsx(PageHead, { kicker: _jsxs(_Fragment, { children: [_jsx(Icon, { name: "payments", size: 12 }), " Payment"] }), title: _jsxs(_Fragment, { children: [_jsx(Amount, { amount: p.amount, currency: p.currency }), " ", _jsx(StatusPill, { value: p.status })] }), actions: refundable ? _jsxs("button", { className: "btn", onClick: onRefund, children: [_jsx(Icon, { name: "refund", size: 14 }), " Refund"] }) : null, children: _jsxs("div", { className: "summary", children: [_jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Last update" }), _jsx("div", { children: formatDate(p.events[0]?.created ?? p.created) })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Customer" }), _jsx("div", { children: p.customer ? _jsx(Link, { to: `customers/${p.customer.id}`, go: go, children: p.customer.email || customerLabel(p.customer) }) : '—' })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Payment method" }), _jsx("div", { children: _jsx(CardLabel, { pm: p.paymentMethod }) })] }), p.declineReason ? _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Decline reason" }), _jsx("div", { children: p.declineReason })] }) : null] }) }), p.intent?.last_payment_error ? _jsx(PaymentErrorBanner, { error: p.intent.last_payment_error }) : null, _jsxs("div", { className: "detail-columns", children: [_jsxs("div", { className: "detail-main", children: [_jsx(Section, { title: "Timeline", children: _jsx(Timeline, { events: p.events }) }), _jsx(Section, { title: "Payment breakdown", children: _jsxs("dl", { className: "breakdown", children: [_jsxs("div", { children: [_jsx("dt", { children: "Payment amount" }), _jsxs("dd", { children: [money(p.amount, p.currency), " ", ccy(p.currency)] })] }), p.amountRefunded > 0 ? _jsxs("div", { children: [_jsx("dt", { children: "Refunded" }), _jsxs("dd", { children: ["\u2212", money(p.amountRefunded, p.currency), " ", ccy(p.currency)] })] }) : null, _jsxs("div", { className: "total", children: [_jsx("dt", { children: "Net amount" }), _jsxs("dd", { children: [money(p.status === 'failed' || p.status === 'incomplete' ? 0 : net, p.currency), " ", ccy(p.currency)] })] })] }) }), p.refunds.length > 0 ? (_jsx(Section, { title: "Refunds", children: _jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Amount" }), _jsx("th", {}), _jsx("th", { children: "Reason" }), _jsx("th", { children: "ID" }), _jsx("th", { className: "right", children: "Date" })] }) }), _jsx("tbody", { children: p.refunds.map((r) => (_jsxs("tr", { children: [_jsx("td", { children: _jsx(Amount, { amount: Number(r.amount) || 0, currency: r.currency }) }), _jsx("td", { children: _jsx(StatusPill, { value: r.status }) }), _jsx("td", { children: r.reason ? titleCase(r.reason) : '—' }), _jsx("td", { className: "mono muted", children: r.id }), _jsx("td", { className: "right muted nowrap", children: formatDate(r.created) })] }, r.id))) })] }) })) : null] }), _jsxs("aside", { className: "detail-side", children: [_jsx(Section, { title: "Payment details", children: _jsx(Props, { rows: [
469
- ['Statement descriptor', p.charge?.calculated_statement_descriptor ?? p.intent?.statement_descriptor ?? '—'],
470
- ['Description', p.description ?? '—'],
471
- ...(p.invoice ? [['Invoice', _jsx(Link, { to: `invoices/${p.invoice.id}`, go: go, children: p.invoice.number || p.invoice.id })]] : []),
472
- ['Payment ID', _jsx("span", { className: "mono", children: p.intent?.id ?? p.id })],
473
- ...(p.charge ? [['Charge ID', _jsx("span", { className: "mono", children: p.charge.id })]] : []),
474
- ] }) }), _jsx(Section, { title: "Payment method", children: p.paymentMethod ? (_jsx(Props, { rows: [
475
- ['ID', _jsx("span", { className: "mono", children: p.paymentMethod.id })],
476
- ['Number', card ? _jsxs("span", { className: "card-label", children: [_jsx("span", { className: "dots", children: "\u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022 \u2022\u2022\u2022\u2022" }), " ", card.last4] }) : '—'],
477
- ['Expires', card ? `${String(card.exp_month).padStart(2, '0')} / ${card.exp_year}` : '—'],
478
- ['Type', card ? `${BRANDS[card.brand] ?? titleCase(String(card.brand))} ${card.funding ?? ''} card` : titleCase(String(p.paymentMethod.type))],
479
- ['Owner', p.paymentMethod.billing_details?.name ?? p.customer?.name ?? '—'],
480
- ['Owner email', p.paymentMethod.billing_details?.email ?? p.customer?.email ?? '—'],
481
- ['Origin', card?.country ? COUNTRIES[card.country] ?? card.country : '—'],
482
- ['CVC check', card?.checks?.cvc_check ? (card.checks.cvc_check === 'pass' ? _jsxs("span", { className: "check-pass", children: [_jsx(Icon, { name: "check", size: 12 }), " Passed"] }) : titleCase(card.checks.cvc_check)) : '—'],
483
- ] })) : _jsx(EmptyState, { title: "No payment method" }) }), _jsx(Section, { title: "Metadata", children: Object.keys(p.intent?.metadata ?? p.charge?.metadata ?? {}).length ? (_jsx(Props, { rows: Object.entries(p.intent?.metadata ?? p.charge?.metadata ?? {}).map(([k, v]) => [k, String(v)]) })) : _jsx("div", { className: "muted small", children: "No metadata" }) })] })] })] }));
484
- }
485
- const BRANDS = { visa: 'Visa', mastercard: 'Mastercard', amex: 'American Express', discover: 'Discover', diners: 'Diners Club', jcb: 'JCB', unionpay: 'UnionPay' };
486
- const COUNTRIES = { US: 'United States', GB: 'United Kingdom', CA: 'Canada', DE: 'Germany', FR: 'France', AU: 'Australia', JP: 'Japan', IE: 'Ireland', NL: 'Netherlands', SG: 'Singapore' };
487
- // ── dialogs ──────────────────────────────────────────────────────────────────
488
- function Dialog({ title, onClose, children, footer, label }) {
489
- return (_jsx("div", { className: "overlay", onMouseDown: onClose, children: _jsxs("div", { className: "dialog create-modal", role: "dialog", "aria-modal": "true", "aria-label": label ?? title, onMouseDown: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "dialog-head", children: [_jsx("h2", { children: title }), _jsx("button", { className: "icon-btn", "aria-label": "Close", onClick: onClose, children: _jsx(Icon, { name: "x" }) })] }), _jsx("div", { className: "dialog-body", children: children }), _jsx("div", { className: "dialog-foot", children: footer })] }) }));
490
- }
491
- function RefundDialog({ payment, onClose, onDone }) {
492
- const remaining = payment.amount - payment.amountRefunded;
493
- const [amount, setAmount] = useState((remaining / 100).toFixed(2));
494
- const [reason, setReason] = useState('');
495
- const [busy, setBusy] = useState(false);
496
- const [err, setErr] = useState(null);
497
- const cents = Math.round(Number(amount) * 100);
498
- const submit = async () => {
499
- if (!Number.isFinite(cents) || cents <= 0) {
500
- setErr('Enter an amount greater than 0.');
501
- return;
502
- }
503
- if (cents > remaining) {
504
- setErr(`Refund cannot be more than ${money(remaining, payment.currency)}.`);
505
- return;
506
- }
507
- setBusy(true);
508
- setErr(null);
509
- try {
510
- await createRefund(payment, cents === remaining && payment.amountRefunded === 0 ? undefined : cents, reason && reason !== 'other' ? reason : undefined);
511
- onDone();
512
- }
513
- catch (e) {
514
- setErr(String(e.message ?? e));
515
- }
516
- finally {
517
- setBusy(false);
518
- }
519
- };
520
- return (_jsxs(Dialog, { title: "Refund payment", onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsx("button", { className: "btn", onClick: onClose, children: "Cancel" }), _jsx("button", { className: "btn btn-primary create-submit", disabled: busy, onClick: submit, children: busy ? 'Refunding…' : 'Refund' })] }), children: [_jsx("p", { className: "dialog-note", children: "Refunds take 5\u201310 days to appear on a customer\u2019s statement. Stripe\u2019s fees for the original payment won\u2019t be returned, but there are no additional fees for the refund." }), _jsxs("label", { className: "field create-field", children: [_jsx("span", { className: "field-label", children: "Refund" }), _jsxs("span", { className: "money-input", children: [_jsx("span", { className: "money-prefix", children: "$" }), _jsx("input", { "aria-label": "Refund amount", inputMode: "decimal", value: amount, onChange: (e) => setAmount(e.target.value) }), _jsx("span", { className: "money-suffix", children: ccy(payment.currency) })] }), payment.amountRefunded > 0 ? _jsxs("span", { className: "field-hint", children: [money(payment.amountRefunded, payment.currency), " already refunded"] }) : null] }), _jsxs("label", { className: "field create-field", children: [_jsx("span", { className: "field-label", children: "Reason" }), _jsxs("select", { "aria-label": "Reason", value: reason, onChange: (e) => setReason(e.target.value), children: [_jsx("option", { value: "", children: "Select a reason" }), _jsx("option", { value: "duplicate", children: "Duplicate" }), _jsx("option", { value: "fraudulent", children: "Fraudulent" }), _jsx("option", { value: "requested_by_customer", children: "Requested by customer" }), _jsx("option", { value: "other", children: "Other" })] })] }), err ? _jsx("div", { className: "form-error", role: "alert", children: err }) : null] }));
521
- }
522
- function CancelSubscriptionDialog({ subscription, onClose, onDone }) {
523
- const [when, setWhen] = useState('now');
524
- const [busy, setBusy] = useState(false);
525
- const [err, setErr] = useState(null);
526
- const submit = async () => {
527
- setBusy(true);
528
- setErr(null);
529
- try {
530
- await cancelSubscription(String(subscription.id), when);
531
- onDone();
532
- }
533
- catch (e) {
534
- setErr(String(e.message ?? e));
535
- }
536
- finally {
537
- setBusy(false);
538
- }
539
- };
540
- return (_jsxs(Dialog, { title: "Cancel subscription", onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsx("button", { className: "btn", onClick: onClose, children: "Don\u2019t cancel" }), _jsx("button", { className: "btn btn-danger create-submit", disabled: busy, onClick: submit, children: busy ? 'Canceling…' : 'Cancel subscription' })] }), children: [_jsxs("fieldset", { className: "radio-group", children: [_jsx("legend", { className: "field-label", children: "Cancel" }), _jsxs("label", { className: "radio", children: [_jsx("input", { type: "radio", name: "when", checked: when === 'now', onChange: () => setWhen('now') }), _jsxs("span", { children: ["Immediately", _jsx("span", { className: "radio-hint", children: "The subscription ends now and no further invoices are created." })] })] }), _jsxs("label", { className: "radio", children: [_jsx("input", { type: "radio", name: "when", checked: when === 'period_end', onChange: () => setWhen('period_end') }), _jsxs("span", { children: ["End of the current period", _jsx("span", { className: "radio-hint", children: formatDate(subscription.current_period_end, false) })] })] })] }), err ? _jsx("div", { className: "form-error", role: "alert", children: err }) : null] }));
541
- }
542
- /** The create forms the Dashboard opens from a list page: each posts the resource's create call. */
543
- export const CREATE_FORMS = {
544
- customers: {
545
- title: 'Create customer', submit: 'Add customer',
546
- fields: [{ name: 'name', label: 'Name' }, { name: 'email', label: 'Email', type: 'email' }, { name: 'description', label: 'Description' }, { name: 'phone', label: 'Phone' }],
547
- },
548
- products: {
549
- title: 'Add a product', submit: 'Add product',
550
- fields: [{ name: 'name', label: 'Name', required: true }, { name: 'description', label: 'Description' }],
551
- },
552
- };
553
- function CreateDialog({ collection, onClose, onCreated }) {
554
- const form = CREATE_FORMS[collection];
555
- const [values, setValues] = useState({});
556
- const [busy, setBusy] = useState(false);
557
- const [err, setErr] = useState(null);
558
- const submit = async () => {
559
- setBusy(true);
560
- setErr(null);
561
- try {
562
- onCreated(await createObject(collection, values));
563
- }
564
- catch (e) {
565
- setErr(String(e.message ?? e));
566
- }
567
- finally {
568
- setBusy(false);
569
- }
570
- };
571
- return (_jsxs(Dialog, { title: form.title, onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsx("button", { className: "btn", onClick: onClose, children: "Cancel" }), _jsx("button", { className: "btn btn-primary create-submit", disabled: busy, onClick: submit, children: busy ? 'Saving…' : form.submit })] }), children: [collection === 'customers' ? _jsx("div", { className: "dialog-subhead", children: "Account information" }) : null, form.fields.map((f) => (_jsxs("label", { className: "field create-field", children: [_jsxs("span", { className: "field-label", children: [f.label, f.required ? '' : _jsx("span", { className: "optional", children: " (optional)" })] }), _jsx("input", { "aria-label": f.label, type: f.type ?? 'text', placeholder: f.placeholder ?? '', value: values[f.name] ?? '', onChange: (e) => setValues((v) => ({ ...v, [f.name]: e.target.value })) })] }, f.name))), err ? _jsx("div", { className: "form-error", role: "alert", children: err }) : null] }));
572
- }
573
- const SEGMENTS = [
574
- { key: 'all', label: 'All customers', test: () => true },
575
- { key: 'first_time', label: 'First-time customers', test: (c) => c.payments === 1 },
576
- { key: 'repeat', label: 'Repeat customers', test: (c) => c.payments > 1 },
577
- { key: 'recent', label: 'Recent customers', test: (c) => Number(c.created) >= Date.now() / 1000 - 30 * DAY },
578
- { key: 'refunds', label: 'High refunds', test: (c) => c.refunds > 0 },
579
- ];
580
- function CustomersPage({ go, create }) {
581
- const { data, error, reload } = useApi(loadCustomers, []);
582
- const [creating, setCreating] = useState(create);
583
- const [segment, setSegment] = useState('all');
584
- const [created, setCreated] = useState();
585
- useEffect(() => { if (create)
586
- setCreating(true); }, [create]);
587
- const all = (data ?? []).filter((c) => inRange(Number(c.created), created));
588
- const rows = all.filter(SEGMENTS.find((s) => s.key === segment).test);
589
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Customers", actions: _jsxs("button", { className: "btn btn-primary", onClick: () => setCreating(true), children: [_jsx(Icon, { name: "plus", size: 14 }), " Create customer"] }) }), _jsx(FilterCards, { value: segment, onChange: setSegment, options: SEGMENTS.map((s) => ({ key: s.key, label: s.label, count: all.filter(s.test).length })) }), _jsxs("div", { className: "toolbar", children: [_jsx(FilterChip, { label: "Created", value: created, options: DATE_RANGES, onChange: setCreated }), _jsx("div", { className: "toolbar-right", children: _jsxs("button", { className: "btn", onClick: () => exportCsv('customers', ['id', 'Name', 'Email', 'Created', 'Total Spend', 'Payment Count', 'Refunded Volume'], rows.map((c) => [c.id, c.name ?? '', c.email ?? '', new Date(Number(c.created) * 1000).toISOString(), (c.totalSpend / 100).toFixed(2), c.payments, (c.refunds / 100).toFixed(2)])), children: [_jsx(Icon, { name: "download", size: 14 }), " Export"] }) })] }), data ? (_jsxs(_Fragment, { children: [_jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Name" }), _jsx("th", { children: "Email" }), _jsx("th", { children: "Default payment method" }), _jsx("th", { children: "Created" }), _jsx("th", { className: "right", children: "Total spend" }), _jsx("th", { className: "right", children: "Payments" }), _jsx("th", { className: "right", children: "Refunds" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && _jsx("tr", { className: "empty-row", children: _jsx("td", { colSpan: 7, children: _jsx(EmptyState, { title: "No customers", body: "Customers you create show up here." }) }) }), rows.map((c) => (_jsxs("tr", { className: "list-row", onClick: () => go(`customers/${c.id}`), children: [_jsx("td", { className: "strong", children: c.name || _jsx("span", { className: "muted", children: "\u2014" }) }), _jsx("td", { children: c.email || _jsx("span", { className: "muted", children: "\u2014" }) }), _jsx("td", { children: _jsx(CardLabel, { pm: c.defaultCard }) }), _jsx("td", { className: "muted nowrap", children: formatDate(c.created) }), _jsx("td", { className: "right nowrap", children: money(c.totalSpend, c.currency) }), _jsx("td", { className: "right", children: c.payments }), _jsx("td", { className: "right nowrap", children: money(c.refunds, c.currency) })] }, c.id)))] })] }), _jsx(ResultCount, { n: rows.length })] })) : _jsx(Loading, { error: error }), creating ? _jsx(CreateDialog, { collection: "customers", onClose: () => { setCreating(false); if (create)
590
- go('customers'); }, onCreated: (c) => { setCreating(false); reload(); go(`customers/${c.id}`); } }) : null] }));
591
- }
592
- function subscriptionProduct(sub, products) {
593
- const items = sub.items?.data ?? [];
594
- const names = items.map((it) => {
595
- const productId = typeof it.price?.product === 'string' ? it.price.product : it.price?.product?.id;
596
- const name = products.get(productId)?.name ?? it.price?.nickname ?? productId ?? 'Product';
597
- return Number(it.quantity) > 1 ? `${name} × ${it.quantity}` : name;
598
- });
599
- return names.length > 1 ? `${names[0]} and ${names.length - 1} more` : names[0] ?? '—';
600
- }
601
- function subscriptionAmount(sub) {
602
- const items = sub.items?.data ?? [];
603
- const amount = items.reduce((n, it) => n + (Number(it.price?.unit_amount) || 0) * (Number(it.quantity) || 1), 0);
604
- const interval = items[0]?.price?.recurring?.interval ?? 'month';
605
- return { amount, currency: String(items[0]?.price?.currency ?? sub.currency ?? 'usd'), interval };
606
- }
607
- export function InvoicesTable({ invoices, customers, go, noCustomer }) {
608
- return (_jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Amount" }), _jsx("th", {}), _jsx("th", { children: "Invoice number" }), noCustomer ? null : _jsx("th", { children: "Customer" }), _jsx("th", { children: "Due" }), _jsx("th", { className: "right", children: "Created" })] }) }), _jsxs("tbody", { children: [invoices.length === 0 && _jsx("tr", { className: "empty-row", children: _jsx("td", { colSpan: 6, children: _jsx(EmptyState, { title: "No invoices" }) }) }), invoices.map((inv) => {
609
- const c = customers?.get(inv.customer);
610
- return (_jsxs("tr", { className: "list-row", onClick: () => go(`invoices/${inv.id}`), children: [_jsx("td", { className: "nowrap", children: _jsx(Amount, { amount: Number(inv.total ?? inv.amount_due) || 0, currency: inv.currency }) }), _jsx("td", { children: _jsx(StatusPill, { value: inv.status }) }), _jsx("td", { className: "mono", children: inv.number || inv.id }), noCustomer ? null : _jsx("td", { className: "truncate", children: inv.customer_email || c?.email || c?.name || inv.customer }), _jsx("td", { className: "muted nowrap", children: inv.due_date ? formatDate(inv.due_date, false) : '—' }), _jsx("td", { className: "right muted nowrap", children: formatDate(inv.created) })] }, inv.id));
611
- })] })] }));
612
- }
613
- function CustomerPage({ id, go }) {
614
- const { data, error } = useApi(() => loadCustomer(id), [id]);
615
- return data ? _jsx(CustomerDetailView, { data: data, go: go }) : _jsx(Loading, { error: error });
616
- }
617
- /** A customer's page: who they are, their subscriptions, payments, saved cards, invoices and events. */
618
- export function CustomerDetailView({ data, go }) {
619
- const c = data.customer;
620
- const defaultPm = c.invoice_settings?.default_payment_method;
621
- const spend = data.payments.filter((p) => p.status !== 'failed' && p.status !== 'incomplete').reduce((n, p) => n + p.amount - p.amountRefunded, 0);
622
- return (_jsxs("div", { className: "page detail-page", children: [_jsxs(PageHead, { kicker: _jsxs(_Fragment, { children: [_jsx(Icon, { name: "person", size: 12 }), " Customer"] }), title: c.name || c.email || c.id, children: [_jsx("div", { className: "subtitle", children: c.email ?? '' }), _jsxs("div", { className: "summary", children: [_jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Customer since" }), _jsx("div", { children: formatDate(c.created, false) })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Total spend" }), _jsx("div", { children: money(spend, data.payments[0]?.currency ?? 'usd') })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Payments" }), _jsx("div", { children: data.payments.filter((p) => p.status !== 'failed' && p.status !== 'incomplete').length })] })] })] }), _jsxs("div", { className: "detail-columns", children: [_jsxs("div", { className: "detail-main", children: [_jsx(Section, { title: "Subscriptions", children: data.subscriptions.length === 0 ? _jsx("div", { className: "muted small", children: "No subscriptions" }) : (_jsx("div", { className: "sub-list", children: data.subscriptions.map((s) => {
623
- const a = subscriptionAmount(s);
624
- return (_jsxs("a", { href: hrefOf(`subscriptions/${s.id}`), className: "sub-card list-row", onClick: (e) => { e.preventDefault(); go(`subscriptions/${s.id}`); }, children: [_jsxs("div", { className: "sub-card-main", children: [_jsx("span", { className: "strong", children: subscriptionProduct(s, data.products) }), " ", _jsx(StatusPill, { value: s.status })] }), _jsxs("div", { className: "muted small", children: [money(a.amount, a.currency), " / ", a.interval, " \u00B7 ", s.status === 'canceled' ? `Canceled ${formatDate(s.canceled_at ?? s.ended_at, false)}` : s.status === 'trialing' ? `Trial ends ${formatDate(s.trial_end, false)}` : s.cancel_at_period_end ? `Cancels ${formatDate(s.current_period_end, false)}` : `Next invoice on ${formatDate(s.current_period_end, false)}`] })] }, s.id));
625
- }) })) }), _jsx(Section, { title: "Payments", className: "customer-payments", children: _jsx(PaymentsTable, { payments: data.payments, go: go, compact: true, noCustomer: true }) }), _jsx(Section, { title: "Payment methods", children: data.paymentMethods.length === 0 ? _jsx("div", { className: "muted small", children: "No payment methods" }) : (_jsx("table", { className: "table", children: _jsx("tbody", { children: data.paymentMethods.map((pm) => (_jsxs("tr", { children: [_jsxs("td", { children: [_jsx(CardLabel, { pm: pm }), " ", pm.id === defaultPm ? _jsx("span", { className: "pill pill-grey", children: "Default" }) : null] }), _jsx("td", { className: "muted", children: pm.card ? `Expires ${String(pm.card.exp_month).padStart(2, '0')} / ${pm.card.exp_year}` : '' }), _jsx("td", { className: "mono muted right", children: pm.id })] }, pm.id))) }) })) }), _jsx(Section, { title: "Invoices", children: _jsx(InvoicesTable, { invoices: data.invoices, go: go, noCustomer: true }) }), _jsx(Section, { title: "Events", children: _jsx(Timeline, { events: data.events.slice(0, 10) }) })] }), _jsxs("aside", { className: "detail-side", children: [_jsx(Section, { title: "Details", children: _jsx(Props, { rows: [
626
- ['Customer ID', _jsx("span", { className: "mono", children: c.id })],
627
- ['Customer since', formatDate(c.created, false)],
628
- ['Email', c.email ?? '—'],
629
- ['Description', c.description || '—'],
630
- ['Phone', c.phone || '—'],
631
- ['Billing details', c.address?.line1 ? `${c.address.line1}, ${c.address.city ?? ''}` : 'No billing address'],
632
- ['Currency', c.currency ? ccy(c.currency) : '—'],
633
- ] }) }), _jsx(Section, { title: "Metadata", children: Object.keys(c.metadata ?? {}).length ? _jsx(Props, { rows: Object.entries(c.metadata).map(([k, v]) => [k, String(v)]) }) : _jsx("div", { className: "muted small", children: "No metadata" }) })] })] })] }));
634
- }
635
- function SubscriptionsPage({ go }) {
636
- const { data, error } = useApi(loadSubscriptions, []);
637
- const [filter, setFilter] = useState('all');
638
- const subs = data?.subscriptions ?? [];
639
- const shown = subs.filter((s) => filter === 'all' || s.status === filter);
640
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Subscriptions" }), _jsx(FilterCards, { value: filter, onChange: setFilter, options: ['all', 'active', 'trialing', 'past_due', 'canceled'].map((k) => ({ key: k, label: k === 'all' ? 'All' : STATUS[k].label, count: k === 'all' ? subs.length : subs.filter((s) => s.status === k).length })) }), data ? (_jsxs(_Fragment, { children: [_jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Customer" }), _jsx("th", { children: "Status" }), _jsx("th", { children: "Billing" }), _jsx("th", { children: "Product" }), _jsx("th", { className: "right", children: "Amount" }), _jsx("th", { className: "right", children: "Created" })] }) }), _jsxs("tbody", { children: [shown.length === 0 && _jsx("tr", { className: "empty-row", children: _jsx("td", { colSpan: 6, children: _jsx(EmptyState, { title: "No subscriptions" }) }) }), shown.map((s) => {
641
- const c = data.customers.get(s.customer);
642
- const a = subscriptionAmount(s);
643
- return (_jsxs("tr", { className: "list-row", onClick: () => go(`subscriptions/${s.id}`), children: [_jsxs("td", { children: [_jsx("div", { className: "strong", children: c?.name || c?.email || s.customer }), c?.name && c?.email ? _jsx("div", { className: "muted small", children: c.email }) : null] }), _jsxs("td", { children: [_jsx(StatusPill, { value: s.status }), s.cancel_at_period_end && s.status !== 'canceled' ? _jsxs("span", { className: "muted small", children: [" Cancels ", formatDate(s.current_period_end, false)] }) : null] }), _jsx("td", { children: s.collection_method === 'send_invoice' ? 'Send invoice' : 'Auto' }), _jsx("td", { children: subscriptionProduct(s, data.products) }), _jsxs("td", { className: "right nowrap", children: [money(a.amount, a.currency), " / ", a.interval] }), _jsx("td", { className: "right muted nowrap", children: formatDate(s.created) })] }, s.id));
644
- })] })] }), _jsx(ResultCount, { n: shown.length })] })) : _jsx(Loading, { error: error })] }));
645
- }
646
- function SubscriptionPage({ id, go }) {
647
- const { data, error, reload } = useApi(() => loadSubscription(id), [id]);
648
- const [canceling, setCanceling] = useState(false);
649
- if (!data)
650
- return _jsx(Loading, { error: error });
651
- return (_jsxs(_Fragment, { children: [_jsx(SubscriptionDetailView, { data: data, go: go, onCancel: () => setCanceling(true) }), canceling ? _jsx(CancelSubscriptionDialog, { subscription: data.subscription, onClose: () => setCanceling(false), onDone: () => { setCanceling(false); reload(); } }) : null] }));
652
- }
653
- /** A subscription's page: whose, on what, its period and next invoice, its prices, invoices and events. */
654
- export function SubscriptionDetailView({ data, go, onCancel }) {
655
- const s = data.subscription;
656
- const c = data.customer;
657
- const a = subscriptionAmount(s);
658
- const product = subscriptionProduct(s, data.products);
659
- const live = s.status !== 'canceled' && s.status !== 'incomplete_expired';
660
- const next = s.status === 'canceled' ? `Canceled on ${formatDate(s.canceled_at ?? s.ended_at, false)}`
661
- : s.cancel_at_period_end ? `Cancels on ${formatDate(s.current_period_end, false)}`
662
- : s.status === 'trialing' ? `Trial ends ${formatDate(s.trial_end, false)} · First invoice for ${money(a.amount, a.currency)}`
663
- : `Next invoice on ${formatDate(s.current_period_end, false)} for ${money(a.amount, a.currency)}`;
664
- return (_jsxs("div", { className: "page detail-page", children: [_jsxs(PageHead, { kicker: _jsxs(_Fragment, { children: [_jsx(Icon, { name: "billing", size: 12 }), " Subscription"] }), title: _jsxs(_Fragment, { children: [customerLabel(c), " on ", product, " ", _jsx(StatusPill, { value: s.status })] }), actions: live && !s.cancel_at_period_end ? _jsx("button", { className: "btn", onClick: onCancel, children: "Cancel subscription" }) : null, children: [_jsxs("div", { className: "subtitle", children: ["Billing ", a.interval, "ly \u00B7 ", next] }), _jsxs("div", { className: "summary", children: [_jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Started" }), _jsx("div", { children: formatDate(s.start_date ?? s.created, false) })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: s.status === 'canceled' ? 'Ended' : s.cancel_at_period_end ? 'Cancels' : 'Next invoice' }), _jsx("div", { children: s.status === 'canceled' ? formatDate(s.ended_at ?? s.canceled_at, false) : s.cancel_at_period_end ? formatDate(s.current_period_end, false) : `${money(a.amount, a.currency)} on ${formatDate(s.current_period_end, false)}` })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Customer" }), _jsx("div", { children: c ? _jsx(Link, { to: `customers/${c.id}`, go: go, children: c.email || c.name }) : s.customer })] })] })] }), _jsx(Section, { title: "Subscription details", children: _jsx(Props, { rows: [
665
- ['Customer', c ? _jsx(Link, { to: `customers/${c.id}`, go: go, children: customerLabel(c) }) : s.customer],
666
- ['Created', formatDate(s.created)],
667
- ['Current period', `${formatDate(s.current_period_start, false).replace(/, \d{4}$/, '')} to ${formatDate(s.current_period_end, false)}`],
668
- ...(s.trial_end ? [['Trial', `${formatDate(s.trial_start, false)} to ${formatDate(s.trial_end, false)}`]] : []),
669
- ['ID', _jsx("span", { className: "mono", children: s.id })],
670
- ['Billing method', s.collection_method === 'send_invoice' ? 'Email invoice to the customer' : 'Charge default payment method'],
671
- ['Payment method', _jsx(CardLabel, { pm: data.paymentMethod })],
672
- ['Discounts', (s.discounts ?? []).length ? `${s.discounts.length}` : '—'],
673
- ] }) }), _jsx(Section, { title: "Pricing", children: _jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Product" }), _jsx("th", { className: "right", children: "Quantity" }), _jsx("th", { className: "right", children: "Unit price" }), _jsx("th", { className: "right", children: "Total" })] }) }), _jsx("tbody", { children: (s.items?.data ?? []).map((it) => {
674
- const productId = typeof it.price?.product === 'string' ? it.price.product : it.price?.product?.id;
675
- const unit = Number(it.price?.unit_amount) || 0;
676
- const q = Number(it.quantity) || 1;
677
- return (_jsxs("tr", { children: [_jsxs("td", { children: [_jsx("div", { className: "strong", children: data.products.get(productId)?.name ?? productId }), _jsxs("div", { className: "muted small", children: [money(unit, it.price?.currency), " / ", it.price?.recurring?.interval ?? 'month'] })] }), _jsx("td", { className: "right", children: q }), _jsx("td", { className: "right", children: money(unit, it.price?.currency) }), _jsxs("td", { className: "right", children: [money(unit * q, it.price?.currency), " ", ccy(it.price?.currency)] })] }, it.id));
678
- }) })] }) }), _jsx(Section, { title: "Invoices", children: _jsx(InvoicesTable, { invoices: data.invoices, go: go, noCustomer: true }) }), _jsx(Section, { title: "Events", children: _jsx(Timeline, { events: data.events }) })] }));
679
- }
680
- function InvoicesPage({ go }) {
681
- const { data, error } = useApi(loadInvoices, []);
682
- const [filter, setFilter] = useState('all');
683
- const invoices = data?.invoices ?? [];
684
- const shown = invoices.filter((i) => filter === 'all' || i.status === filter);
685
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Invoices" }), _jsx(FilterCards, { value: filter, onChange: setFilter, options: ['all', 'draft', 'open', 'paid', 'void', 'uncollectible'].map((k) => ({ key: k, label: k === 'all' ? 'All invoices' : STATUS[k].label, count: k === 'all' ? invoices.length : invoices.filter((i) => i.status === k).length })) }), data ? _jsxs(_Fragment, { children: [_jsx(InvoicesTable, { invoices: shown, customers: data.customers, go: go }), _jsx(ResultCount, { n: shown.length })] }) : _jsx(Loading, { error: error })] }));
686
- }
687
- function InvoicePage({ id, go }) {
688
- const { data, error } = useApi(async () => {
689
- const invoice = await retrieve(`/v1/invoices/${id}`);
690
- const customer = typeof invoice.customer === 'string' ? await retrieve(`/v1/customers/${invoice.customer}`).catch(() => undefined) : undefined;
691
- return { invoice, customer };
692
- }, [id]);
693
- if (!data)
694
- return _jsx(Loading, { error: error });
695
- const inv = data.invoice;
696
- const lines = inv.lines?.data ?? [];
697
- return (_jsxs("div", { className: "page detail-page", children: [_jsx(PageHead, { kicker: _jsxs(_Fragment, { children: [_jsx(Icon, { name: "billing", size: 12 }), " Invoice"] }), title: _jsxs(_Fragment, { children: [_jsx(Amount, { amount: Number(inv.total ?? inv.amount_due) || 0, currency: inv.currency }), " ", _jsx(StatusPill, { value: inv.status })] }), children: _jsxs("div", { className: "summary", children: [_jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Invoice number" }), _jsx("div", { className: "mono", children: inv.number || inv.id })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: "Customer" }), _jsx("div", { children: data.customer ? _jsx(Link, { to: `customers/${data.customer.id}`, go: go, children: data.customer.email || data.customer.name }) : inv.customer })] }), _jsxs("div", { className: "summary-item", children: [_jsx("div", { className: "summary-label", children: inv.status === 'paid' ? 'Paid' : 'Due' }), _jsx("div", { children: inv.status === 'paid' ? formatDate(inv.status_transitions?.paid_at) : inv.due_date ? formatDate(inv.due_date, false) : '—' })] })] }) }), _jsx(Section, { title: "Summary", children: _jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Description" }), _jsx("th", { className: "right", children: "Qty" }), _jsx("th", { className: "right", children: "Amount" })] }) }), _jsxs("tbody", { children: [lines.map((l) => _jsxs("tr", { children: [_jsx("td", { children: l.description || (l.price?.product ? `Subscription (${formatDate(l.period?.start, false)} – ${formatDate(l.period?.end, false)})` : '—') }), _jsx("td", { className: "right", children: l.quantity ?? 1 }), _jsx("td", { className: "right", children: money(l.amount, l.currency) })] }, l.id)), _jsxs("tr", { className: "table-total", children: [_jsx("td", { children: "Total" }), _jsx("td", {}), _jsx("td", { className: "right strong", children: money(inv.total, inv.currency) })] }), _jsxs("tr", { className: "table-total", children: [_jsx("td", { children: "Amount due" }), _jsx("td", {}), _jsx("td", { className: "right strong", children: money(inv.amount_remaining ?? inv.amount_due, inv.currency) })] })] })] }) }), _jsx(Section, { title: "Details", children: _jsx(Props, { rows: [
698
- ['ID', _jsx("span", { className: "mono", children: inv.id })],
699
- ['Billing', inv.collection_method === 'send_invoice' ? 'Send invoice' : 'Charge automatically'],
700
- ...(inv.subscription ? [['Subscription', _jsx(Link, { to: `subscriptions/${inv.subscription}`, go: go, children: inv.subscription })]] : []),
701
- ...(inv.payment_intent ? [['Payment', _jsx(Link, { to: `payments/${inv.payment_intent}`, go: go, children: inv.payment_intent })]] : []),
702
- ['Created', formatDate(inv.created)],
703
- ] }) })] }));
704
- }
705
- // ── Product catalog ──────────────────────────────────────────────────────────
706
- function ProductsPage({ go }) {
707
- const { data, error, reload } = useApi(loadProducts, []);
708
- const [creating, setCreating] = useState(false);
709
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Product catalog", actions: _jsxs("button", { className: "btn btn-primary", onClick: () => setCreating(true), children: [_jsx(Icon, { name: "plus", size: 14 }), " Create product"] }), children: _jsxs("div", { className: "tabs", children: [_jsx(Link, { to: "products", go: go, className: "tab active", children: "Products" }), _jsx(Link, { to: "coupons", go: go, className: "tab", children: "Coupons" })] }) }), data ? (_jsxs(_Fragment, { children: [_jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Name" }), _jsx("th", { children: "Pricing" }), _jsx("th", { children: "Created" }), _jsx("th", { className: "right", children: "Updated" })] }) }), _jsxs("tbody", { children: [data.products.length === 0 && _jsx("tr", { className: "empty-row", children: _jsx("td", { colSpan: 4, children: _jsx(EmptyState, { title: "No products" }) }) }), data.products.map((p) => {
710
- const prices = data.prices.filter((x) => x.product === p.id);
711
- const first = prices[prices.length - 1];
712
- return (_jsxs("tr", { children: [_jsx("td", { children: _jsxs("div", { className: "product-cell", children: [_jsx("span", { className: "product-thumb", children: p.images?.[0] ? _jsx("img", { src: p.images[0], alt: "" }) : _jsx(Icon, { name: "catalog" }) }), _jsxs("div", { children: [_jsx("div", { className: "strong", children: p.name }), p.description ? _jsx("div", { className: "muted small", children: p.description }) : null] })] }) }), _jsx("td", { children: first ? _jsxs(_Fragment, { children: [_jsxs("span", { children: [money(first.unit_amount, first.currency), " ", ccy(first.currency), first.recurring ? ` / ${first.recurring.interval}` : ''] }), prices.length > 1 ? _jsxs("div", { className: "muted small", children: [prices.length, " prices"] }) : null] }) : _jsx("span", { className: "muted", children: "No prices" }) }), _jsx("td", { className: "muted nowrap", children: formatDate(p.created, false) }), _jsx("td", { className: "right muted nowrap", children: formatDate(p.updated ?? p.created, false) })] }, p.id));
713
- })] })] }), _jsx(ResultCount, { n: data.products.length })] })) : _jsx(Loading, { error: error }), creating ? _jsx(CreateDialog, { collection: "products", onClose: () => setCreating(false), onCreated: () => { setCreating(false); reload(); } }) : null] }));
714
- }
715
- // ── Balances ─────────────────────────────────────────────────────────────────
716
- function BalancesPage({ go }) {
717
- const { data, error } = useApi(loadBalances, []);
718
- if (!data)
719
- return _jsx(Loading, { error: error });
720
- const buckets = (k) => (data.balance[k] ?? []);
721
- const currencies = [...new Set([...buckets('available'), ...buckets('pending')].map((b) => b.currency))];
722
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Balances" }), _jsxs(Section, { title: "Balance summary", children: [_jsx(BalanceSummary, { row: data.balance }), _jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Payments" }), _jsx("th", { className: "right", children: "Amount" })] }) }), _jsx("tbody", { children: currencies.map((c) => {
723
- const pending = buckets('pending').find((b) => b.currency === c)?.amount ?? 0;
724
- const available = buckets('available').find((b) => b.currency === c)?.amount ?? 0;
725
- return (_jsxs(React.Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { children: "Incoming" }), _jsxs("td", { className: "right", children: [money(pending, c), " ", ccy(c)] })] }), _jsxs("tr", { children: [_jsx("td", { children: "Available to pay out" }), _jsxs("td", { className: "right", children: [money(available, c), " ", ccy(c)] })] }), _jsxs("tr", { className: "table-total", children: [_jsx("td", { children: "Total" }), _jsxs("td", { className: "right strong", children: [money(pending + available, c), " ", ccy(c)] })] })] }, c));
726
- }) })] })] }), _jsx(Section, { title: "Recent activity", actions: _jsx(Link, { to: "balance-transactions", go: go, children: "View all" }), children: _jsx(ListPane, { section: SECTIONS.find((s) => s.key === 'balance_transactions'), rows: data.transactions.slice(0, 10), onSelect: () => { } }) }), _jsx(Section, { title: "Payouts", actions: _jsx(Link, { to: "payouts", go: go, children: "View all" }), children: _jsx(ListPane, { section: SECTIONS.find((s) => s.key === 'payouts'), rows: data.payouts.slice(0, 10), onSelect: () => { } }) })] }));
727
- }
728
- // ── Developers and Settings ──────────────────────────────────────────────────
729
- export function payoutScheduleText(account) {
730
- const sched = account?.settings?.payouts?.schedule;
731
- if (!sched || typeof sched !== 'object')
732
- return 'Daily';
733
- const interval = String(sched.interval ?? 'daily');
734
- if (interval === 'weekly' && sched.weekly_anchor)
735
- return `Weekly on ${titleCase(String(sched.weekly_anchor))}`;
736
- if (interval === 'monthly' && sched.monthly_anchor != null)
737
- return `Monthly on day ${sched.monthly_anchor}`;
738
- return titleCase(interval);
739
- }
740
- function DevelopersPage({ go }) {
741
- const { data, error } = useApi(loadDevelopers, []);
742
- if (!data)
743
- return _jsx(Loading, { error: error });
744
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Developers" }), _jsxs("section", { className: "section settings-block", "data-settings": "api-keys", children: [_jsx("div", { className: "section-head", children: _jsx("h2", { children: "API keys" }) }), _jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Name" }), _jsx("th", { children: "Token" }), _jsx("th", { className: "right", children: "Created" })] }) }), _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "strong", children: "Publishable key" }), _jsx("td", { className: "mono", children: "pk_test_twin" }), _jsx("td", { className: "right muted", children: "\u2014" })] }), _jsxs("tr", { children: [_jsx("td", { className: "strong", children: "Secret key" }), _jsx("td", { className: "mono", children: "sk_test_\u2022\u2022\u2022\u2022twin" }), _jsx("td", { className: "right muted", children: "\u2014" })] })] })] })] }), _jsxs("section", { className: "section settings-block", "data-settings": "webhooks", children: [_jsx("div", { className: "section-head", children: _jsx("h2", { children: "Webhooks" }) }), _jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Endpoint" }), _jsx("th", { children: "Status" }), _jsx("th", { className: "right", children: "Listening to" })] }) }), _jsxs("tbody", { children: [data.webhooks.length === 0 && _jsx("tr", { className: "empty-row", children: _jsx("td", { colSpan: 3, children: _jsx(EmptyState, { title: "No webhook endpoints" }) }) }), data.webhooks.map((w) => _jsxs("tr", { children: [_jsx("td", { className: "mono", children: w.url ?? w.id }), _jsx("td", { children: _jsx(StatusPill, { value: w.status ?? 'enabled' }) }), _jsxs("td", { className: "right muted", children: [(w.enabled_events ?? []).length, " events"] })] }, w.id))] })] })] }), _jsx(Section, { title: "Events", actions: _jsx(Link, { to: "events", go: go, children: "View all" }), children: _jsxs("table", { className: "table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "Event" }), _jsx("th", { children: "Type" }), _jsx("th", { children: "Object" }), _jsx("th", { className: "right", children: "Date" })] }) }), _jsx("tbody", { children: data.events.slice(0, 15).map((e) => (_jsxs("tr", { children: [_jsx("td", { children: eventText(e) }), _jsx("td", { className: "mono muted", children: e.type }), _jsx("td", { className: "mono muted", children: e.data?.object?.id }), _jsx("td", { className: "right muted nowrap", children: formatDate(e.created) })] }, e.id))) })] }) })] }));
745
- }
746
- function SettingsPage() {
747
- const { data, error } = useApi(loadDevelopers, []);
748
- if (!data)
749
- return _jsx(Loading, { error: error });
750
- const account = data.account;
751
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: "Settings" }), _jsxs("section", { className: "section settings-block", "data-settings": "account", children: [_jsx("div", { className: "section-head", children: _jsx("h2", { children: "Business details" }) }), _jsx(Props, { rows: [
752
- ['Account ID', _jsx("span", { className: "mono", children: account?.id ?? '—' })],
753
- ['Account name', account?.settings?.dashboard?.display_name ?? PLATFORM_DEFAULT_NAME],
754
- ['Country', account?.country ?? '—'],
755
- ['Default currency', String(account?.default_currency ?? 'usd').toUpperCase()],
756
- ] })] }), _jsxs("section", { className: "section settings-block", "data-settings": "public-details", children: [_jsxs("div", { className: "section-head", children: [_jsx("h2", { children: "Public details" }), _jsx("a", { className: "btn", href: `${API_BASE}/settings/public`, children: "Edit" })] }), _jsx(Props, { rows: [['Business name', _jsx("span", { className: "settings-public-name", children: publicBusinessName(account) })]] })] }), _jsxs("section", { className: "section settings-block", "data-settings": "payouts", children: [_jsx("div", { className: "section-head", children: _jsx("h2", { children: "Payouts" }) }), _jsx(Props, { rows: [['Payout schedule', _jsx("span", { className: "settings-payout-schedule", children: payoutScheduleText(account) })]] })] })] }));
757
- }
758
- function ResourcePage({ section, go }) {
759
- const { data, error } = useApi(() => list(`/v1/${section.key}`), [section.key]);
760
- const account = section.key === 'accounts' ? data?.[0] : undefined;
761
- const tabs = section.path.startsWith('radar/') ? ['radar/reviews', 'radar/lists']
762
- : section.path.startsWith('terminal/') ? ['terminal/readers', 'terminal/locations']
763
- : section.path.startsWith('issuing/') ? ['issuing/cards', 'issuing/cardholders']
764
- : section.path.startsWith('connect/') ? ['connect/accounts', 'connect/transfers']
765
- : section.key === 'payouts' || section.key === 'balance_transactions' ? ['payouts', 'balance-transactions'] : [];
766
- const title = section.path.startsWith('radar/') ? 'Radar' : section.path.startsWith('terminal/') ? 'Terminal' : section.path.startsWith('issuing/') ? 'Issuing'
767
- : section.path.startsWith('connect/') ? 'Connect' : section.key === 'tax_rates' ? 'Tax' : section.label;
768
- return (_jsxs("div", { className: "page", children: [_jsx(PageHead, { title: title, children: tabs.length ? _jsx("div", { className: "tabs", children: tabs.map((t) => _jsx(Link, { to: t, go: go, className: `tab ${t === section.path ? 'active' : ''}`, children: SECTION_BY_PATH.get(t).label.replace(/^(Radar|Terminal|Issuing) /, '') }, t)) }) : null }), account ? _jsx(ConnectAccountPanel, { row: account }) : null, data ? _jsxs(_Fragment, { children: [_jsx(ListPane, { section: section, rows: data, onSelect: () => { } }), _jsx(ResultCount, { n: data.length })] }) : _jsx(Loading, { error: error })] }));
769
- }
770
- // ── the app ──────────────────────────────────────────────────────────────────
771
- export function Page({ route, search, go }) {
772
- const [head, id] = route;
773
- const joined = route.join('/');
774
- if (!head || head === 'dashboard')
775
- return _jsx(HomeScreen, { go: go });
776
- if (head === 'payments')
777
- return id ? _jsx(PaymentPage, { id: id, go: go }) : _jsx(PaymentsPage, { go: go });
778
- if (head === 'customers')
779
- return id ? _jsx(CustomerPage, { id: id, go: go }) : _jsx(CustomersPage, { go: go, create: new URLSearchParams(search).has('create') });
780
- if (head === 'subscriptions')
781
- return id ? _jsx(SubscriptionPage, { id: id, go: go }) : _jsx(SubscriptionsPage, { go: go });
782
- if (head === 'invoices')
783
- return id ? _jsx(InvoicePage, { id: id, go: go }) : _jsx(InvoicesPage, { go: go });
784
- if (head === 'products')
785
- return _jsx(ProductsPage, { go: go });
786
- if (head === 'balance')
787
- return _jsx(BalancesPage, { go: go });
788
- if (head === 'developers')
789
- return _jsx(DevelopersPage, { go: go });
790
- if (head === 'settings')
791
- return _jsx(SettingsPage, {});
792
- const section = SECTION_BY_PATH.get(joined) ?? SECTION_BY_PATH.get(head);
793
- if (section)
794
- return _jsx(ResourcePage, { section: section, go: go }, section.key);
795
- return _jsx("div", { className: "page", children: _jsx(EmptyState, { title: "Page not found", body: "This page doesn\u2019t exist in the Dashboard." }) });
796
- }
797
- /** A record a World's console asks to see (`?show=<type>:<id>`, docs/contributing/architecture.md "Screens"): where the
798
- * Dashboard shows it. The console names a record, never a Stripe path. A type with no page of its own opens its list,
799
- * and one with no list stays on Home; either says so, never showing another page as if it were the record. */
800
- const SHOW_AT = { customer: (id) => `customers/${id}`, payment_intent: (id) => `payments/${id}`, charge: (id) => `payments/${id}`, subscription: (id) => `subscriptions/${id}`, invoice: (id) => `invoices/${id}` };
801
- /** The sections the Dashboard routes to, by the object type their rows are (a section's key is Stripe's plural). */
802
- const LISTED = {
803
- product: { path: 'products', label: 'Products' },
804
- // (not `accounts`: Connect lists connected accounts, never the platform's own account record)
805
- ...Object.fromEntries(SECTIONS.filter((s) => s.page && s.key !== 'accounts').map((s) => [s.key.replace(/s$/, '').replace(/\//g, '.'), { path: s.path, label: s.label }])),
806
- };
807
- function shown(search) {
808
- const asked = new URLSearchParams(search).get('show');
809
- if (!asked)
810
- return null;
811
- const at = asked.indexOf(':');
812
- const type = asked.slice(0, at);
813
- const id = asked.slice(at + 1);
814
- if (at <= 0 || !id)
815
- return null;
816
- const words = type.replace(/[._]/g, ' ');
817
- if (SHOW_AT[type])
818
- return { to: hrefOf(SHOW_AT[type](id)) };
819
- if (LISTED[type])
820
- return { to: hrefOf(LISTED[type].path), note: `These screens have no page for one ${words} yet; ${LISTED[type].label.toLowerCase()} are listed here. Look for ${id}.` };
821
- return { to: hrefOf('dashboard'), note: `These screens have no page for ${words} records yet, so ${id} can't be shown here.` };
822
- }
823
- export function App() {
824
- const [asked] = useState(() => shown(window.location.search));
825
- const [note, setNote] = useState(asked?.note ?? '');
826
- const [loc, setLoc] = useState(() => {
827
- if (asked) {
828
- window.history.replaceState(null, '', asked.to);
829
- const u = new URL(asked.to, window.location.origin);
830
- return { path: u.pathname, search: u.search };
831
- }
832
- return { path: window.location.pathname, search: window.location.search };
833
- });
834
- useEffect(() => {
835
- const onPop = () => setLoc({ path: window.location.pathname, search: window.location.search });
836
- window.addEventListener('popstate', onPop);
837
- return () => window.removeEventListener('popstate', onPop);
838
- }, []);
839
- const go = useCallback((to) => {
840
- setNote('');
841
- const href = hrefOf(to);
842
- window.history.pushState(null, '', href);
843
- const u = new URL(href, window.location.origin);
844
- setLoc({ path: u.pathname, search: u.search });
845
- window.scrollTo(0, 0);
846
- }, []);
847
- const route = routeOf(loc.path);
848
- useEffect(() => {
849
- if (route.length === 0)
850
- window.history.replaceState(null, '', hrefOf('dashboard'));
851
- const [head, id] = route;
852
- const names = { dashboard: 'Home', payments: 'Payments', customers: 'Customers', subscriptions: 'Subscriptions', invoices: 'Invoices', products: 'Product catalog', balance: 'Balances', developers: 'Developers', settings: 'Settings' };
853
- document.title = `${id ? `${id} – ` : ''}${names[head ?? 'dashboard'] ?? SECTION_BY_PATH.get(route.join('/'))?.label ?? 'Dashboard'} – Stripe`;
854
- }, [loc.path]);
855
- return (_jsxs("div", { className: "dash", children: [_jsx(TestModeBand, {}), _jsxs("div", { className: "frame", children: [_jsx(SideNav, { route: route, go: go }), _jsxs("div", { className: "main", children: [_jsx(TopBar, { go: go }), _jsxs("main", { className: "content", children: [note ? _jsxs("div", { className: "show-note", role: "status", children: [_jsx("span", { children: note }), _jsx("button", { type: "button", "aria-label": "Dismiss", onClick: () => setNote(''), children: _jsx(Icon, { name: "x", size: 12 }) })] }) : null, _jsx(Page, { route: route, search: loc.search, go: go })] })] })] })] }));
856
- }
857
- if (typeof document !== 'undefined' && document.getElementById('root')) {
858
- createRoot(document.getElementById('root')).render(_jsx(App, {}));
859
- }