@fantastic.dev/better-stripe 0.7.0 → 0.8.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 (343) hide show
  1. package/CHANGELOG.md +23 -18
  2. package/README.md +122 -90
  3. package/dist/client/billing/checkout.d.ts +1 -1
  4. package/dist/client/billing/checkout.d.ts.map +1 -1
  5. package/dist/client/billing/checkout.js +7 -2
  6. package/dist/client/billing/checkout.js.map +1 -1
  7. package/dist/client/connect/paymentMethods.d.ts.map +1 -1
  8. package/dist/client/connect/paymentMethods.js.map +1 -1
  9. package/dist/client/connect/refundActor.d.ts.map +1 -1
  10. package/dist/client/connect/refundActor.js.map +1 -1
  11. package/dist/client/connect/refunds.d.ts.map +1 -1
  12. package/dist/client/connect/refunds.js.map +1 -1
  13. package/dist/client/connect/transfers.d.ts.map +1 -1
  14. package/dist/client/connect/transfers.js +15 -4
  15. package/dist/client/connect/transfers.js.map +1 -1
  16. package/dist/client/core/accountDefaults.d.ts +6 -0
  17. package/dist/client/core/accountDefaults.d.ts.map +1 -0
  18. package/dist/client/core/accountDefaults.js +15 -0
  19. package/dist/client/core/accountDefaults.js.map +1 -0
  20. package/dist/client/core/accounts.d.ts +1 -5
  21. package/dist/client/core/accounts.d.ts.map +1 -1
  22. package/dist/client/core/accounts.js +2 -14
  23. package/dist/client/core/accounts.js.map +1 -1
  24. package/dist/client/core/fees.d.ts.map +1 -1
  25. package/dist/client/core/fees.js +7 -2
  26. package/dist/client/core/fees.js.map +1 -1
  27. package/dist/client/index.d.ts +1 -1
  28. package/dist/client/index.d.ts.map +1 -1
  29. package/dist/client/products/prices.d.ts.map +1 -1
  30. package/dist/client/products/prices.js.map +1 -1
  31. package/dist/client/products/products.d.ts.map +1 -1
  32. package/dist/client/products/products.js +3 -1
  33. package/dist/client/products/products.js.map +1 -1
  34. package/dist/client/utils/owner.d.ts.map +1 -1
  35. package/dist/client/utils/owner.js +3 -1
  36. package/dist/client/utils/owner.js.map +1 -1
  37. package/dist/client/utils/stripeDashboardUrl.d.ts.map +1 -1
  38. package/dist/client/utils/stripeDashboardUrl.js.map +1 -1
  39. package/dist/client/webhooks/helpers.d.ts +10 -0
  40. package/dist/client/webhooks/helpers.d.ts.map +1 -1
  41. package/dist/client/webhooks/helpers.js +18 -0
  42. package/dist/client/webhooks/helpers.js.map +1 -1
  43. package/dist/client/webhooks/processors.d.ts.map +1 -1
  44. package/dist/client/webhooks/processors.js +1 -19
  45. package/dist/client/webhooks/processors.js.map +1 -1
  46. package/dist/component/connect/mutations.d.ts.map +1 -1
  47. package/dist/component/connect/mutations.js +4 -33
  48. package/dist/component/connect/mutations.js.map +1 -1
  49. package/dist/component/connect/schema.d.ts.map +1 -1
  50. package/dist/component/connect/schema.js.map +1 -1
  51. package/dist/component/lib/paymentSeller.d.ts +3 -0
  52. package/dist/component/lib/paymentSeller.d.ts.map +1 -0
  53. package/dist/component/lib/paymentSeller.js +10 -0
  54. package/dist/component/lib/paymentSeller.js.map +1 -0
  55. package/dist/component/lib/reversals.d.ts +10 -0
  56. package/dist/component/lib/reversals.d.ts.map +1 -1
  57. package/dist/component/lib/reversals.js +17 -0
  58. package/dist/component/lib/reversals.js.map +1 -1
  59. package/dist/react/components/ConnectProvider.d.ts +1 -1
  60. package/dist/react/components/ConnectProvider.d.ts.map +1 -1
  61. package/dist/react/components/ConnectProvider.js +18 -4
  62. package/dist/react/components/ConnectProvider.js.map +1 -1
  63. package/dist/react/components/ConnectStatusBadge.d.ts.map +1 -1
  64. package/dist/react/components/ConnectStatusBadge.js.map +1 -1
  65. package/dist/react/components/EmbeddedDisputes.d.ts +1 -1
  66. package/dist/react/components/EmbeddedDisputes.d.ts.map +1 -1
  67. package/dist/react/components/EmbeddedDisputes.js +1 -1
  68. package/dist/react/components/EmbeddedDisputes.js.map +1 -1
  69. package/dist/react/components/SplitBreakdown.d.ts.map +1 -1
  70. package/dist/react/components/SplitBreakdown.js +12 -2
  71. package/dist/react/components/SplitBreakdown.js.map +1 -1
  72. package/dist/react/hooks/useCheckoutSession.d.ts.map +1 -1
  73. package/dist/react/hooks/useCheckoutSession.js.map +1 -1
  74. package/dist/react/hooks/useConfirmPayment.d.ts.map +1 -1
  75. package/dist/react/hooks/useConfirmPayment.js.map +1 -1
  76. package/dist/react/index.d.ts +1 -0
  77. package/dist/react/index.d.ts.map +1 -1
  78. package/dist/react/index.js +2 -0
  79. package/dist/react/index.js.map +1 -1
  80. package/dist/react/workflows/Account.d.ts +67 -0
  81. package/dist/react/workflows/Account.d.ts.map +1 -0
  82. package/dist/react/workflows/Account.js +74 -0
  83. package/dist/react/workflows/Account.js.map +1 -0
  84. package/dist/react/workflows/Billing.d.ts +60 -0
  85. package/dist/react/workflows/Billing.d.ts.map +1 -0
  86. package/dist/react/workflows/Billing.js +84 -0
  87. package/dist/react/workflows/Billing.js.map +1 -0
  88. package/dist/react/workflows/BillingPlans.d.ts +45 -0
  89. package/dist/react/workflows/BillingPlans.d.ts.map +1 -0
  90. package/dist/react/workflows/BillingPlans.js +51 -0
  91. package/dist/react/workflows/BillingPlans.js.map +1 -0
  92. package/dist/react/workflows/Checkout.d.ts +46 -0
  93. package/dist/react/workflows/Checkout.d.ts.map +1 -0
  94. package/dist/react/workflows/Checkout.js +61 -0
  95. package/dist/react/workflows/Checkout.js.map +1 -0
  96. package/dist/react/workflows/CheckoutChooser.d.ts +20 -0
  97. package/dist/react/workflows/CheckoutChooser.d.ts.map +1 -0
  98. package/dist/react/workflows/CheckoutChooser.js +24 -0
  99. package/dist/react/workflows/CheckoutChooser.js.map +1 -0
  100. package/dist/react/workflows/CheckoutForms.d.ts +26 -0
  101. package/dist/react/workflows/CheckoutForms.d.ts.map +1 -0
  102. package/dist/react/workflows/CheckoutForms.js +126 -0
  103. package/dist/react/workflows/CheckoutForms.js.map +1 -0
  104. package/dist/react/workflows/ClipboardButton.d.ts +17 -0
  105. package/dist/react/workflows/ClipboardButton.d.ts.map +1 -0
  106. package/dist/react/workflows/ClipboardButton.js +35 -0
  107. package/dist/react/workflows/ClipboardButton.js.map +1 -0
  108. package/dist/react/workflows/Collection.d.ts +30 -0
  109. package/dist/react/workflows/Collection.d.ts.map +1 -0
  110. package/dist/react/workflows/Collection.js +25 -0
  111. package/dist/react/workflows/Collection.js.map +1 -0
  112. package/dist/react/workflows/Collections.d.ts +56 -0
  113. package/dist/react/workflows/Collections.d.ts.map +1 -0
  114. package/dist/react/workflows/Collections.js +111 -0
  115. package/dist/react/workflows/Collections.js.map +1 -0
  116. package/dist/react/workflows/Connect.d.ts +19 -0
  117. package/dist/react/workflows/Connect.d.ts.map +1 -0
  118. package/dist/react/workflows/Connect.js +43 -0
  119. package/dist/react/workflows/Connect.js.map +1 -0
  120. package/dist/react/workflows/Costs.d.ts +48 -0
  121. package/dist/react/workflows/Costs.d.ts.map +1 -0
  122. package/dist/react/workflows/Costs.js +66 -0
  123. package/dist/react/workflows/Costs.js.map +1 -0
  124. package/dist/react/workflows/Disputes.d.ts +36 -0
  125. package/dist/react/workflows/Disputes.d.ts.map +1 -0
  126. package/dist/react/workflows/Disputes.js +74 -0
  127. package/dist/react/workflows/Disputes.js.map +1 -0
  128. package/dist/react/workflows/Earnings.d.ts +42 -0
  129. package/dist/react/workflows/Earnings.d.ts.map +1 -0
  130. package/dist/react/workflows/Earnings.js +38 -0
  131. package/dist/react/workflows/Earnings.js.map +1 -0
  132. package/dist/react/workflows/Financials.d.ts +43 -0
  133. package/dist/react/workflows/Financials.d.ts.map +1 -0
  134. package/dist/react/workflows/Financials.js +57 -0
  135. package/dist/react/workflows/Financials.js.map +1 -0
  136. package/dist/react/workflows/IntegrationSwitch.d.ts +23 -0
  137. package/dist/react/workflows/IntegrationSwitch.d.ts.map +1 -0
  138. package/dist/react/workflows/IntegrationSwitch.js +31 -0
  139. package/dist/react/workflows/IntegrationSwitch.js.map +1 -0
  140. package/dist/react/workflows/Onboarding.d.ts +56 -0
  141. package/dist/react/workflows/Onboarding.d.ts.map +1 -0
  142. package/dist/react/workflows/Onboarding.js +86 -0
  143. package/dist/react/workflows/Onboarding.js.map +1 -0
  144. package/dist/react/workflows/Overview.d.ts +51 -0
  145. package/dist/react/workflows/Overview.d.ts.map +1 -0
  146. package/dist/react/workflows/Overview.js +63 -0
  147. package/dist/react/workflows/Overview.js.map +1 -0
  148. package/dist/react/workflows/PaymentMethods.d.ts +35 -0
  149. package/dist/react/workflows/PaymentMethods.d.ts.map +1 -0
  150. package/dist/react/workflows/PaymentMethods.js +63 -0
  151. package/dist/react/workflows/PaymentMethods.js.map +1 -0
  152. package/dist/react/workflows/PaymentReceipt.d.ts +20 -0
  153. package/dist/react/workflows/PaymentReceipt.d.ts.map +1 -0
  154. package/dist/react/workflows/PaymentReceipt.js +24 -0
  155. package/dist/react/workflows/PaymentReceipt.js.map +1 -0
  156. package/dist/react/workflows/Payouts.d.ts +47 -0
  157. package/dist/react/workflows/Payouts.d.ts.map +1 -0
  158. package/dist/react/workflows/Payouts.js +87 -0
  159. package/dist/react/workflows/Payouts.js.map +1 -0
  160. package/dist/react/workflows/Products.d.ts +92 -0
  161. package/dist/react/workflows/Products.d.ts.map +1 -0
  162. package/dist/react/workflows/Products.js +179 -0
  163. package/dist/react/workflows/Products.js.map +1 -0
  164. package/dist/react/workflows/ReferralLinks.d.ts +26 -0
  165. package/dist/react/workflows/ReferralLinks.d.ts.map +1 -0
  166. package/dist/react/workflows/ReferralLinks.js +30 -0
  167. package/dist/react/workflows/ReferralLinks.js.map +1 -0
  168. package/dist/react/workflows/Refunds.d.ts +72 -0
  169. package/dist/react/workflows/Refunds.d.ts.map +1 -0
  170. package/dist/react/workflows/Refunds.js +95 -0
  171. package/dist/react/workflows/Refunds.js.map +1 -0
  172. package/dist/react/workflows/Setup.d.ts +50 -0
  173. package/dist/react/workflows/Setup.d.ts.map +1 -0
  174. package/dist/react/workflows/Setup.js +59 -0
  175. package/dist/react/workflows/Setup.js.map +1 -0
  176. package/dist/react/workflows/Storefront.d.ts +53 -0
  177. package/dist/react/workflows/Storefront.d.ts.map +1 -0
  178. package/dist/react/workflows/Storefront.js +44 -0
  179. package/dist/react/workflows/Storefront.js.map +1 -0
  180. package/dist/react/workflows/SubscriptionOverview.d.ts +13 -0
  181. package/dist/react/workflows/SubscriptionOverview.d.ts.map +1 -0
  182. package/dist/react/workflows/SubscriptionOverview.js +26 -0
  183. package/dist/react/workflows/SubscriptionOverview.js.map +1 -0
  184. package/dist/react/workflows/Subscriptions.d.ts +92 -0
  185. package/dist/react/workflows/Subscriptions.d.ts.map +1 -0
  186. package/dist/react/workflows/Subscriptions.js +149 -0
  187. package/dist/react/workflows/Subscriptions.js.map +1 -0
  188. package/dist/react/workflows/Tasks.d.ts +40 -0
  189. package/dist/react/workflows/Tasks.d.ts.map +1 -0
  190. package/dist/react/workflows/Tasks.js +113 -0
  191. package/dist/react/workflows/Tasks.js.map +1 -0
  192. package/dist/react/workflows/checkout-options.d.ts +37 -0
  193. package/dist/react/workflows/checkout-options.d.ts.map +1 -0
  194. package/dist/react/workflows/checkout-options.js +43 -0
  195. package/dist/react/workflows/checkout-options.js.map +1 -0
  196. package/dist/react/workflows/current-subscription.d.ts +11 -0
  197. package/dist/react/workflows/current-subscription.d.ts.map +1 -0
  198. package/dist/react/workflows/current-subscription.js +14 -0
  199. package/dist/react/workflows/current-subscription.js.map +1 -0
  200. package/dist/react/workflows/filters.d.ts +17 -0
  201. package/dist/react/workflows/filters.d.ts.map +1 -0
  202. package/dist/react/workflows/filters.js +23 -0
  203. package/dist/react/workflows/filters.js.map +1 -0
  204. package/dist/react/workflows/index.d.ts +70 -0
  205. package/dist/react/workflows/index.d.ts.map +1 -0
  206. package/dist/react/workflows/index.js +38 -0
  207. package/dist/react/workflows/index.js.map +1 -0
  208. package/dist/react/workflows/useWorkflowAction.d.ts +11 -0
  209. package/dist/react/workflows/useWorkflowAction.d.ts.map +1 -0
  210. package/dist/react/workflows/useWorkflowAction.js +68 -0
  211. package/dist/react/workflows/useWorkflowAction.js.map +1 -0
  212. package/dist/react/workflows/useWorkflowResource.d.ts +9 -0
  213. package/dist/react/workflows/useWorkflowResource.d.ts.map +1 -0
  214. package/dist/react/workflows/useWorkflowResource.js +67 -0
  215. package/dist/react/workflows/useWorkflowResource.js.map +1 -0
  216. package/docs/react-workflows-audit.md +66 -0
  217. package/docs/react-workflows.md +231 -0
  218. package/package.json +19 -3
  219. package/src/client/billing/checkout-ui-modes.test.ts +49 -0
  220. package/src/client/billing/checkout.ts +8 -3
  221. package/src/client/billing/invoices.test.ts +12 -6
  222. package/src/client/connect/paymentMethods.test.ts +3 -1
  223. package/src/client/connect/paymentMethods.ts +4 -1
  224. package/src/client/connect/refundActor.ts +1 -2
  225. package/src/client/connect/refunds.test.ts +35 -10
  226. package/src/client/connect/refunds.ts +4 -1
  227. package/src/client/connect/transfers.test.ts +66 -12
  228. package/src/client/connect/transfers.ts +32 -8
  229. package/src/client/core/accountDefaults.ts +15 -0
  230. package/src/client/core/accounts.ts +14 -17
  231. package/src/client/core/config.test.ts +2 -1
  232. package/src/client/core/descriptors.test.ts +6 -4
  233. package/src/client/core/fees.test.ts +20 -28
  234. package/src/client/core/fees.ts +9 -2
  235. package/src/client/index.test.ts +22 -22
  236. package/src/client/index.ts +1 -1
  237. package/src/client/products/prices.test.ts +8 -2
  238. package/src/client/products/prices.ts +4 -1
  239. package/src/client/products/products.test.ts +61 -20
  240. package/src/client/products/products.ts +3 -1
  241. package/src/client/utils/data.test.ts +3 -1
  242. package/src/client/utils/owner.test.ts +4 -1
  243. package/src/client/utils/owner.ts +5 -1
  244. package/src/client/utils/stripeDashboardUrl.ts +1 -4
  245. package/src/client/webhooks/handler.test.ts +16 -19
  246. package/src/client/webhooks/helpers.test.ts +26 -13
  247. package/src/client/webhooks/helpers.ts +23 -0
  248. package/src/client/webhooks/hooks.test.ts +1 -2
  249. package/src/client/webhooks/processors.test.ts +18 -34
  250. package/src/client/webhooks/processors.ts +1 -23
  251. package/src/client/webhooks.test.ts +1 -2
  252. package/src/component/_generated/api.ts +2 -0
  253. package/src/component/connect/mutations.ts +5 -46
  254. package/src/component/connect/schema.ts +4 -3
  255. package/src/component/connect.test.ts +1 -5
  256. package/src/component/lib/paymentSeller.ts +15 -0
  257. package/src/component/lib/reversals.ts +30 -2
  258. package/src/component/subscription-scoping.test.ts +5 -1
  259. package/src/component/triggers.test.ts +7 -4
  260. package/src/react/components/BuyerBillingView.render.test.tsx +7 -2
  261. package/src/react/components/ConnectProvider.live.test.tsx +61 -0
  262. package/src/react/components/ConnectProvider.tsx +18 -4
  263. package/src/react/components/ConnectStatusBadge.tsx +1 -4
  264. package/src/react/components/DisputeDetail.render.test.tsx +5 -1
  265. package/src/react/components/EarningsSummary.render.test.tsx +2 -6
  266. package/src/react/components/EmbeddedDisputes.render.test.tsx +7 -5
  267. package/src/react/components/EmbeddedDisputes.tsx +4 -1
  268. package/src/react/components/EvidenceForm.render.test.tsx +2 -8
  269. package/src/react/components/SplitBreakdown.render.test.tsx +28 -5
  270. package/src/react/components/SplitBreakdown.tsx +12 -2
  271. package/src/react/hooks/useCheckoutSession.ts +1 -2
  272. package/src/react/hooks/useConfirmPayment.ts +1 -2
  273. package/src/react/hooks/useEarnings.test.ts +43 -8
  274. package/src/react/hooks/useStripeMode.test.ts +5 -8
  275. package/src/react/index.ts +3 -0
  276. package/src/react/workflows/Account.test.tsx +31 -0
  277. package/src/react/workflows/Account.tsx +183 -0
  278. package/src/react/workflows/Billing.test.tsx +45 -0
  279. package/src/react/workflows/Billing.tsx +213 -0
  280. package/src/react/workflows/BillingPlans.test.tsx +31 -0
  281. package/src/react/workflows/BillingPlans.tsx +130 -0
  282. package/src/react/workflows/Checkout.test.tsx +118 -0
  283. package/src/react/workflows/Checkout.tsx +135 -0
  284. package/src/react/workflows/CheckoutChooser.test.tsx +44 -0
  285. package/src/react/workflows/CheckoutChooser.tsx +77 -0
  286. package/src/react/workflows/CheckoutForm.test.tsx +66 -0
  287. package/src/react/workflows/CheckoutForms.test.tsx +123 -0
  288. package/src/react/workflows/CheckoutForms.tsx +247 -0
  289. package/src/react/workflows/ClipboardButton.test.tsx +34 -0
  290. package/src/react/workflows/ClipboardButton.tsx +67 -0
  291. package/src/react/workflows/Collection.tsx +76 -0
  292. package/src/react/workflows/Collections.test.tsx +86 -0
  293. package/src/react/workflows/Collections.tsx +213 -0
  294. package/src/react/workflows/Connect.test.tsx +50 -0
  295. package/src/react/workflows/Connect.tsx +107 -0
  296. package/src/react/workflows/Costs.test.tsx +41 -0
  297. package/src/react/workflows/Costs.tsx +133 -0
  298. package/src/react/workflows/Disputes.test.tsx +25 -0
  299. package/src/react/workflows/Disputes.tsx +164 -0
  300. package/src/react/workflows/Earnings.test.ts +19 -0
  301. package/src/react/workflows/Earnings.tsx +84 -0
  302. package/src/react/workflows/Financials.test.tsx +35 -0
  303. package/src/react/workflows/Financials.tsx +117 -0
  304. package/src/react/workflows/IntegrationSwitch.test.tsx +36 -0
  305. package/src/react/workflows/IntegrationSwitch.tsx +63 -0
  306. package/src/react/workflows/Onboarding.test.tsx +75 -0
  307. package/src/react/workflows/Onboarding.tsx +171 -0
  308. package/src/react/workflows/Overview.test.tsx +57 -0
  309. package/src/react/workflows/Overview.tsx +123 -0
  310. package/src/react/workflows/PaymentMethods.test.tsx +60 -0
  311. package/src/react/workflows/PaymentMethods.tsx +117 -0
  312. package/src/react/workflows/PaymentReceipt.test.tsx +24 -0
  313. package/src/react/workflows/PaymentReceipt.tsx +46 -0
  314. package/src/react/workflows/Payouts.test.tsx +82 -0
  315. package/src/react/workflows/Payouts.tsx +212 -0
  316. package/src/react/workflows/Products.test.tsx +90 -0
  317. package/src/react/workflows/Products.tsx +377 -0
  318. package/src/react/workflows/ReferralLinks.test.tsx +39 -0
  319. package/src/react/workflows/ReferralLinks.tsx +75 -0
  320. package/src/react/workflows/Refunds.test.tsx +75 -0
  321. package/src/react/workflows/Refunds.tsx +223 -0
  322. package/src/react/workflows/Setup.test.tsx +25 -0
  323. package/src/react/workflows/Setup.tsx +166 -0
  324. package/src/react/workflows/Storefront.test.tsx +82 -0
  325. package/src/react/workflows/Storefront.tsx +138 -0
  326. package/src/react/workflows/SubscriptionOverview.test.tsx +27 -0
  327. package/src/react/workflows/SubscriptionOverview.tsx +48 -0
  328. package/src/react/workflows/Subscriptions.test.tsx +61 -0
  329. package/src/react/workflows/Subscriptions.tsx +282 -0
  330. package/src/react/workflows/Tasks.test.tsx +23 -0
  331. package/src/react/workflows/Tasks.tsx +199 -0
  332. package/src/react/workflows/checkout-options.ts +42 -0
  333. package/src/react/workflows/current-subscription.test.ts +29 -0
  334. package/src/react/workflows/current-subscription.ts +25 -0
  335. package/src/react/workflows/filters.test.ts +12 -0
  336. package/src/react/workflows/filters.ts +22 -0
  337. package/src/react/workflows/index.ts +238 -0
  338. package/src/react/workflows/native-interactions.test.tsx +73 -0
  339. package/src/react/workflows/public-workflows.test.tsx +216 -0
  340. package/src/react/workflows/useWorkflowAction.test.tsx +54 -0
  341. package/src/react/workflows/useWorkflowAction.ts +75 -0
  342. package/src/react/workflows/useWorkflowResource.test.tsx +41 -0
  343. package/src/react/workflows/useWorkflowResource.ts +78 -0
@@ -0,0 +1,164 @@
1
+ "use client";
2
+ import { useState, type ReactNode } from "react";
3
+ import {
4
+ DisputesList,
5
+ type DisputesListRow,
6
+ } from "../components/DisputesList.js";
7
+ import { DisputeDetail } from "../components/DisputeDetail.js";
8
+ import {
9
+ EvidenceForm,
10
+ type EvidenceFormUpdateArgs,
11
+ } from "../components/EvidenceForm.js";
12
+ import type { DisputeWithCountdown } from "../hooks/useDisputeWithCountdown.js";
13
+ import { useWorkflowAction } from "./useWorkflowAction.js";
14
+ import { Connect, type ConnectProps } from "./Connect.js";
15
+ export type DisputeResponseOptions = {
16
+ scopeKey: string;
17
+ acceptDispute?: (stripeDisputeId: string) => Promise<unknown>;
18
+ onSelectionChange?: (id: string | undefined) => void;
19
+ };
20
+ export function useDisputeResponse(props: DisputeResponseOptions) {
21
+ const initial = {
22
+ scope: props.scopeKey,
23
+ selectedId: undefined as string | undefined,
24
+ confirming: false,
25
+ };
26
+ const [state, setState] = useState(initial);
27
+ if (state.scope !== props.scopeKey) setState(initial);
28
+ const action = useWorkflowAction(
29
+ async () => {
30
+ if (!state.selectedId || !state.confirming)
31
+ throw new Error("Confirm accepting this dispute first.");
32
+ if (!props.acceptDispute)
33
+ throw new Error("Dispute acceptance is not configured.");
34
+ await props.acceptDispute(state.selectedId);
35
+ return true;
36
+ },
37
+ `${props.scopeKey}:${state.selectedId ?? ""}`,
38
+ );
39
+ return {
40
+ selectedId: state.selectedId,
41
+ setSelectedId: (selectedId: string | undefined) => {
42
+ setState((previous) => ({ ...previous, selectedId, confirming: false }));
43
+ props.onSelectionChange?.(selectedId);
44
+ action.reset();
45
+ },
46
+ acceptStatus: action.isPending
47
+ ? ("submitting" as const)
48
+ : action.result
49
+ ? ("accepted" as const)
50
+ : action.error
51
+ ? ("error" as const)
52
+ : state.confirming
53
+ ? ("confirming" as const)
54
+ : ("idle" as const),
55
+ acceptError: action.error,
56
+ isPending: action.isPending,
57
+ requestAccept: () => {
58
+ action.reset();
59
+ setState((previous) => ({ ...previous, confirming: true }));
60
+ },
61
+ cancelAccept: () => {
62
+ action.reset();
63
+ setState((previous) => ({ ...previous, confirming: false }));
64
+ },
65
+ accept: action.run,
66
+ };
67
+ }
68
+ export type DisputesRenderProps = ReturnType<typeof useDisputeResponse> & {
69
+ disputes: DisputesListRow[] | undefined;
70
+ detail: DisputeWithCountdown | null | undefined;
71
+ };
72
+ export type HeadlessDisputesProps = DisputeResponseOptions & {
73
+ presentation?: "headless";
74
+ disputes: DisputesListRow[] | undefined;
75
+ detail?: DisputeWithCountdown | null;
76
+ onUpdateEvidence: (args: EvidenceFormUpdateArgs) => Promise<unknown>;
77
+ children?: (state: DisputesRenderProps) => ReactNode;
78
+ };
79
+ export type DisputesProps =
80
+ | HeadlessDisputesProps
81
+ | (Omit<ConnectProps, "component"> & { presentation: "embedded" });
82
+ export function Disputes(props: DisputesProps) {
83
+ return props.presentation === "embedded" ? (
84
+ <Connect {...props} component="disputes" />
85
+ ) : (
86
+ <DisputesBody key={props.scopeKey} {...props} />
87
+ );
88
+ }
89
+ function DisputesBody(props: HeadlessDisputesProps) {
90
+ const state = useDisputeResponse(props);
91
+ const detail =
92
+ props.detail?.stripeDisputeId === state.selectedId
93
+ ? props.detail
94
+ : undefined;
95
+ if (props.children)
96
+ return (
97
+ <>{props.children({ ...state, disputes: props.disputes, detail })}</>
98
+ );
99
+ return (
100
+ <section data-better-stripe="disputes">
101
+ <h2>Disputes</h2>
102
+ <DisputesList
103
+ disputes={props.disputes}
104
+ isLoading={props.disputes === undefined}
105
+ renderRow={(row) => (
106
+ <button
107
+ key={row.dispute.stripeDisputeId}
108
+ type="button"
109
+ aria-pressed={state.selectedId === row.dispute.stripeDisputeId}
110
+ onClick={() => state.setSelectedId(row.dispute.stripeDisputeId)}
111
+ >
112
+ {row.dispute.stripeDisputeId} · {row.dispute.status} {row.badgeText}
113
+ </button>
114
+ )}
115
+ />
116
+ {!state.selectedId ? (
117
+ <p>Select a dispute to respond.</p>
118
+ ) : (
119
+ <>
120
+ <DisputeDetail dispute={detail} isLoading={!detail} />
121
+ {props.acceptDispute &&
122
+ (state.acceptStatus === "confirming" ? (
123
+ <div>
124
+ <p>
125
+ Accepting permanently closes this dispute. This cannot be
126
+ undone.
127
+ </p>
128
+ <button
129
+ type="button"
130
+ disabled={state.isPending}
131
+ onClick={() => {
132
+ void state.accept();
133
+ }}
134
+ >
135
+ Yes, accept dispute
136
+ </button>
137
+ <button type="button" onClick={state.cancelAccept}>
138
+ Cancel
139
+ </button>
140
+ </div>
141
+ ) : (
142
+ <button
143
+ type="button"
144
+ disabled={state.isPending || state.acceptStatus === "accepted"}
145
+ onClick={state.requestAccept}
146
+ >
147
+ {state.isPending
148
+ ? "Accepting…"
149
+ : state.acceptStatus === "accepted"
150
+ ? "Dispute accepted"
151
+ : "Accept dispute"}
152
+ </button>
153
+ ))}
154
+ {state.acceptError && <p role="alert">{state.acceptError}</p>}
155
+ <EvidenceForm
156
+ key={state.selectedId}
157
+ stripeDisputeId={state.selectedId}
158
+ onUpdateDispute={props.onUpdateEvidence}
159
+ />
160
+ </>
161
+ )}
162
+ </section>
163
+ );
164
+ }
@@ -0,0 +1,19 @@
1
+ import { expect, it } from "vitest";
2
+ import { earningsFromBalance } from "./Earnings.js";
3
+ it("keeps currencies separate and includes only completed payouts in recipient totals", () => {
4
+ const result = earningsFromBalance(
5
+ { available: 1200, pending: 300, currency: "usd" },
6
+ [
7
+ { amount: 500, status: "paid", currency: "usd" },
8
+ { amount: 9000, status: "paid", currency: "eur" },
9
+ { amount: 200, status: "pending", currency: "usd" },
10
+ ],
11
+ );
12
+ expect(result).toEqual({
13
+ gross: 2000,
14
+ net: 2000,
15
+ reversed: 0,
16
+ paidOut: 500,
17
+ currency: "usd",
18
+ });
19
+ });
@@ -0,0 +1,84 @@
1
+ "use client";
2
+ import type { ReactNode } from "react";
3
+ import {
4
+ EarningsSummary,
5
+ type EarningsSummaryProps,
6
+ } from "../components/EarningsSummary.js";
7
+ import { formatPrice } from "../lib/price-helpers.js";
8
+ import { Connect, type ConnectProps } from "./Connect.js";
9
+ export type BalanceSnapshot = {
10
+ available: number;
11
+ pending: number;
12
+ currency: string;
13
+ };
14
+ export type StoreEarnings = {
15
+ gross: number;
16
+ reversed: number;
17
+ net: number;
18
+ paidOut: number;
19
+ currency: string;
20
+ };
21
+ /** Recipient funds held plus already paid out, not gross charge revenue. Destination-charge balance display only. */
22
+ export function earningsFromBalance(
23
+ balance: BalanceSnapshot | null,
24
+ payouts: { amount: number; status: string; currency: string }[],
25
+ ): StoreEarnings {
26
+ const currency = balance?.currency ?? payouts[0]?.currency ?? "usd";
27
+ const paidOut = payouts
28
+ .filter((row) => row.currency === currency && row.status === "paid")
29
+ .reduce((sum, row) => sum + row.amount, 0);
30
+ const gross = (balance ? balance.available + balance.pending : 0) + paidOut;
31
+ return { gross, reversed: 0, net: gross, paidOut, currency };
32
+ }
33
+ export type EarningsMetric = {
34
+ key: "gross" | "reversed" | "net" | "paidOut";
35
+ label: string;
36
+ amount: number;
37
+ formattedValue: string;
38
+ };
39
+ export type EarningsRenderProps = {
40
+ metrics: EarningsMetric[];
41
+ isLoading: boolean;
42
+ currency: string;
43
+ payouts: EarningsSummaryProps["payouts"];
44
+ };
45
+ export type HeadlessEarningsProps = Omit<EarningsSummaryProps, "children"> & {
46
+ presentation?: "headless";
47
+ children?: (state: EarningsRenderProps) => ReactNode;
48
+ };
49
+ export type EarningsProps =
50
+ | HeadlessEarningsProps
51
+ | (Omit<ConnectProps, "component"> & { presentation: "embedded" });
52
+ export function Earnings(props: EarningsProps) {
53
+ if (props.presentation === "embedded")
54
+ return <Connect {...props} component="balances" />;
55
+ const { children, ...summary } = props;
56
+ const currency = props.currency ?? "usd";
57
+ const metrics: EarningsMetric[] = (
58
+ [
59
+ ["gross", "Gross"],
60
+ ["reversed", "Reversals & adjustments"],
61
+ ["net", "Net"],
62
+ ["paidOut", "Paid out"],
63
+ ] as const
64
+ ).map(([key, label]) => ({
65
+ key,
66
+ label: props.labels?.[key] ?? label,
67
+ amount: props[key],
68
+ formattedValue: formatPrice(props[key], currency, props.locale),
69
+ }));
70
+ return (
71
+ <>
72
+ {children ? (
73
+ children({
74
+ metrics,
75
+ isLoading: props.isLoading ?? false,
76
+ currency,
77
+ payouts: props.payouts,
78
+ })
79
+ ) : (
80
+ <EarningsSummary {...summary} />
81
+ )}
82
+ </>
83
+ );
84
+ }
@@ -0,0 +1,35 @@
1
+ // @vitest-environment jsdom
2
+ import { cleanup, render, screen } from "@testing-library/react";
3
+ import { afterEach, expect, it } from "vitest";
4
+ import { ChargeSummary, PaymentDetails } from "./Financials.js";
5
+ afterEach(cleanup);
6
+ it("displays server-authoritative destination-charge amounts and reconciliation status", () => {
7
+ render(
8
+ <ChargeSummary
9
+ gross={10000}
10
+ fee={900}
11
+ net={9100}
12
+ currency="usd"
13
+ reconciles={false}
14
+ />,
15
+ );
16
+ expect(screen.getByText("$91.00")).toBeTruthy();
17
+ expect(screen.getByRole("alert").textContent).toContain("does not reconcile");
18
+ });
19
+ it("shows recorded payment refund state, including a failed refund", () => {
20
+ const { rerender } = render(<PaymentDetails payment={undefined} />);
21
+ expect(screen.getByRole("status")).toBeTruthy();
22
+ rerender(
23
+ <PaymentDetails
24
+ payment={{
25
+ amount: 1000,
26
+ currency: "usd",
27
+ status: "succeeded",
28
+ refundedAmount: 200,
29
+ refundStatus: "failed",
30
+ }}
31
+ />,
32
+ );
33
+ expect(screen.getByText("$2.00")).toBeTruthy();
34
+ expect(screen.getByText("failed")).toBeTruthy();
35
+ });
@@ -0,0 +1,117 @@
1
+ "use client";
2
+ import type { ReactNode } from "react";
3
+ import { formatPrice } from "../lib/price-helpers.js";
4
+ export type ChargeSummaryProps = {
5
+ gross: number;
6
+ fee: number;
7
+ net: number;
8
+ currency: string;
9
+ reconciles: boolean;
10
+ feeLabel?: string;
11
+ children?: (state: {
12
+ metrics: {
13
+ key: "gross" | "fee" | "net";
14
+ label: string;
15
+ amount: number;
16
+ formattedAmount: string;
17
+ }[];
18
+ reconciles: boolean;
19
+ }) => ReactNode;
20
+ };
21
+ /** Displays a server-calculated charge allocation, without applying a platform fee policy. */
22
+ export function ChargeSummary(props: ChargeSummaryProps) {
23
+ const metrics = (
24
+ [
25
+ ["gross", "Charged"],
26
+ ["fee", props.feeLabel ?? "Platform fee"],
27
+ ["net", "Seller payout"],
28
+ ] as const
29
+ ).map(([key, label]) => ({
30
+ key,
31
+ label,
32
+ amount: props[key],
33
+ formattedAmount: formatPrice(props[key], props.currency),
34
+ }));
35
+ if (props.children)
36
+ return <>{props.children({ metrics, reconciles: props.reconciles })}</>;
37
+ return (
38
+ <section data-better-stripe="charge-summary">
39
+ <h2>Charge breakdown</h2>
40
+ <dl>
41
+ {metrics.map((metric) => (
42
+ <div key={metric.key}>
43
+ <dt>{metric.label}</dt>
44
+ <dd>{metric.formattedAmount}</dd>
45
+ </div>
46
+ ))}
47
+ </dl>
48
+ <p role={props.reconciles ? "status" : "alert"}>
49
+ {props.reconciles
50
+ ? "Fee and seller payout reconcile to the charge amount."
51
+ : "Breakdown does not reconcile to the charge amount."}
52
+ </p>
53
+ </section>
54
+ );
55
+ }
56
+ export type PaymentDetailsPayment = {
57
+ amount: number;
58
+ currency: string;
59
+ status: string;
60
+ refundedAmount?: number;
61
+ refundStatus?: string;
62
+ };
63
+ export type PaymentDetailsState = {
64
+ payment: PaymentDetailsPayment | null | undefined;
65
+ isLoading: boolean;
66
+ isEmpty: boolean;
67
+ metrics: {
68
+ key: "charged" | "refunded" | "status" | "refundStatus";
69
+ label: string;
70
+ value: string;
71
+ }[];
72
+ };
73
+ export type PaymentDetailsProps = {
74
+ payment: PaymentDetailsPayment | null | undefined;
75
+ children?: (state: PaymentDetailsState) => ReactNode;
76
+ };
77
+ export function PaymentDetails({ payment, children }: PaymentDetailsProps) {
78
+ const metrics: PaymentDetailsState["metrics"] = payment
79
+ ? [
80
+ {
81
+ key: "charged",
82
+ label: "Charged",
83
+ value: formatPrice(payment.amount, payment.currency),
84
+ },
85
+ {
86
+ key: "refunded",
87
+ label: "Refunded",
88
+ value: formatPrice(payment.refundedAmount ?? 0, payment.currency),
89
+ },
90
+ { key: "status", label: "Status", value: payment.status },
91
+ {
92
+ key: "refundStatus",
93
+ label: "Refund state",
94
+ value: payment.refundStatus ?? "none",
95
+ },
96
+ ]
97
+ : [];
98
+ const state = {
99
+ payment,
100
+ metrics,
101
+ isLoading: payment === undefined,
102
+ isEmpty: payment === null,
103
+ };
104
+ if (children) return <>{children(state)}</>;
105
+ if (state.isLoading) return <p role="status">Loading payment…</p>;
106
+ if (state.isEmpty) return <p>No payment found.</p>;
107
+ return (
108
+ <dl data-better-stripe="payment-details">
109
+ {metrics.map((metric) => (
110
+ <div key={metric.key}>
111
+ <dt>{metric.label}</dt>
112
+ <dd>{metric.value}</dd>
113
+ </div>
114
+ ))}
115
+ </dl>
116
+ );
117
+ }
@@ -0,0 +1,36 @@
1
+ // @vitest-environment jsdom
2
+ import { cleanup, render, screen } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { afterEach, expect, it, vi } from "vitest";
5
+ import { IntegrationSwitch } from "./IntegrationSwitch.js";
6
+ afterEach(cleanup);
7
+ it("mounts only the selected integration and falls back when a requested mode is unsupported", async () => {
8
+ const mount = vi.fn();
9
+ function Embedded() {
10
+ mount();
11
+ return <p>Stripe component</p>;
12
+ }
13
+ render(
14
+ <IntegrationSwitch
15
+ value="hosted"
16
+ views={{ headless: <p>API controls</p>, embedded: <Embedded /> }}
17
+ />,
18
+ );
19
+ expect(screen.getByText("API controls")).toBeTruthy();
20
+ expect(mount).not.toHaveBeenCalled();
21
+ });
22
+ it("allows an uncontrolled choice without requiring a router", async () => {
23
+ render(
24
+ <IntegrationSwitch
25
+ views={{
26
+ headless: <p>API controls</p>,
27
+ embedded: <p>Stripe component</p>,
28
+ }}
29
+ />,
30
+ );
31
+ await userEvent.click(
32
+ screen.getByRole("button", { name: "Stripe embedded" }),
33
+ );
34
+ expect(screen.getByText("Stripe component")).toBeTruthy();
35
+ expect(screen.queryByText("API controls")).toBeNull();
36
+ });
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import { useState, type ReactNode } from "react";
3
+ export type IntegrationMode = "headless" | "components" | "embedded" | "hosted";
4
+ const labels: Record<IntegrationMode, string> = {
5
+ headless: "Headless",
6
+ components: "Components",
7
+ embedded: "Stripe embedded",
8
+ hosted: "Stripe hosted",
9
+ };
10
+ export type IntegrationSwitchState = {
11
+ value: IntegrationMode;
12
+ select: (mode: IntegrationMode) => void;
13
+ options: { value: IntegrationMode; label: string }[];
14
+ content: ReactNode;
15
+ };
16
+ export type IntegrationSwitchProps = {
17
+ views: Partial<Record<IntegrationMode, ReactNode>> & { headless: ReactNode };
18
+ value?: IntegrationMode;
19
+ defaultValue?: IntegrationMode;
20
+ onValueChange?: (mode: IntegrationMode) => void;
21
+ children?: (state: IntegrationSwitchState) => ReactNode;
22
+ };
23
+ /** Only the active integration is mounted. No router or styling dependencies. */
24
+ export function IntegrationSwitch({
25
+ views,
26
+ value,
27
+ defaultValue = "headless",
28
+ onValueChange,
29
+ children,
30
+ }: IntegrationSwitchProps) {
31
+ const [internal, setInternal] = useState(defaultValue);
32
+ const options = (Object.keys(labels) as IntegrationMode[])
33
+ .filter((mode) => views[mode] !== undefined && views[mode] !== null)
34
+ .map((value) => ({ value, label: labels[value] }));
35
+ const requested = value ?? internal;
36
+ const selected = options.some((option) => option.value === requested)
37
+ ? requested
38
+ : "headless";
39
+ const select = (mode: IntegrationMode) => {
40
+ if (!options.some((option) => option.value === mode)) return;
41
+ setInternal(mode);
42
+ onValueChange?.(mode);
43
+ };
44
+ const state = { value: selected, select, options, content: views[selected] };
45
+ if (children) return <>{children(state)}</>;
46
+ return (
47
+ <div data-better-stripe="integration-switch">
48
+ <div role="group" aria-label="Integration examples">
49
+ {options.map((option) => (
50
+ <button
51
+ key={option.value}
52
+ type="button"
53
+ aria-pressed={selected === option.value}
54
+ onClick={() => select(option.value)}
55
+ >
56
+ {option.label}
57
+ </button>
58
+ ))}
59
+ </div>
60
+ {state.content}
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,75 @@
1
+ // @vitest-environment jsdom
2
+ import { act, cleanup, renderHook } from "@testing-library/react";
3
+ import { afterEach, expect, it, vi } from "vitest";
4
+ import { useOnboarding } from "./Onboarding.js";
5
+ afterEach(cleanup);
6
+ it("creates an account for the selected country and exposes a hosted link failure", async () => {
7
+ const createAccount = vi
8
+ .fn()
9
+ .mockResolvedValue({ onboardingUrl: "https://connect.stripe.com/setup" });
10
+ const continueOnboarding = vi
11
+ .fn()
12
+ .mockRejectedValue(new Error("Account restricted"));
13
+ const navigate = vi.fn();
14
+ const { result } = renderHook(() =>
15
+ useOnboarding({
16
+ scopeKey: "seller",
17
+ createAccount,
18
+ continueOnboarding,
19
+ onNavigate: navigate,
20
+ }),
21
+ );
22
+ act(() => result.current.setSelectedCountry("GB"));
23
+ await act(async () => {
24
+ await result.current.create();
25
+ });
26
+ expect(createAccount).toHaveBeenCalledWith("GB");
27
+ expect(navigate).toHaveBeenCalledWith("https://connect.stripe.com/setup");
28
+ await act(async () => {
29
+ await result.current.continue();
30
+ });
31
+ expect(result.current.linkError).toBe("Account restricted");
32
+ });
33
+
34
+ it("keeps onboarding link failures associated with the active operation", async () => {
35
+ let fail!: (error: Error) => void;
36
+ const createAccount = vi.fn();
37
+ const { result } = renderHook(() =>
38
+ useOnboarding({
39
+ scopeKey: "seller",
40
+ createAccount,
41
+ continueOnboarding: () =>
42
+ new Promise<{ url: string }>((_, reject) => {
43
+ fail = reject;
44
+ }),
45
+ }),
46
+ );
47
+ let pending!: Promise<unknown>;
48
+ act(() => {
49
+ pending = result.current.continue();
50
+ });
51
+ await act(async () => {
52
+ await result.current.create();
53
+ });
54
+ expect(result.current.isLinking).toBe(true);
55
+ expect(createAccount).not.toHaveBeenCalled();
56
+ await act(async () => {
57
+ fail(new Error("Link unavailable"));
58
+ await pending;
59
+ });
60
+ expect(result.current.linkError).toBe("Link unavailable");
61
+ expect(result.current.createError).toBeNull();
62
+ });
63
+ it("offers the shared example countries in the native onboarding UI", async () => {
64
+ const { render, screen } = await import("@testing-library/react");
65
+ const { Onboarding } = await import("./Onboarding.js");
66
+ render(
67
+ <Onboarding
68
+ scopeKey="store"
69
+ account={null}
70
+ createAccount={async () => ({})}
71
+ continueOnboarding={async () => ({})}
72
+ />,
73
+ );
74
+ expect(screen.getByRole("option", { name: "United Kingdom" })).toBeTruthy();
75
+ });