@fantastic.dev/better-stripe 0.23.1 → 0.24.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 (287) hide show
  1. package/CHANGELOG.md +26 -2
  2. package/README.md +40 -5
  3. package/dist/client/affiliateRecovery/disputes.d.ts +18 -0
  4. package/dist/client/affiliateRecovery/disputes.d.ts.map +1 -0
  5. package/dist/client/affiliateRecovery/disputes.js +33 -0
  6. package/dist/client/affiliateRecovery/disputes.js.map +1 -0
  7. package/dist/client/affiliateRecovery/engine.d.ts +29 -0
  8. package/dist/client/affiliateRecovery/engine.d.ts.map +1 -0
  9. package/dist/client/affiliateRecovery/engine.js +116 -0
  10. package/dist/client/affiliateRecovery/engine.js.map +1 -0
  11. package/dist/client/affiliateRecovery/payoutGate.d.ts +24 -0
  12. package/dist/client/affiliateRecovery/payoutGate.d.ts.map +1 -0
  13. package/dist/client/affiliateRecovery/payoutGate.js +30 -0
  14. package/dist/client/affiliateRecovery/payoutGate.js.map +1 -0
  15. package/dist/client/affiliateRecovery/provider.d.ts +14 -0
  16. package/dist/client/affiliateRecovery/provider.d.ts.map +1 -0
  17. package/dist/client/affiliateRecovery/provider.js +96 -0
  18. package/dist/client/affiliateRecovery/provider.js.map +1 -0
  19. package/dist/client/affiliateRecovery/review.d.ts +28 -0
  20. package/dist/client/affiliateRecovery/review.d.ts.map +1 -0
  21. package/dist/client/affiliateRecovery/review.js +86 -0
  22. package/dist/client/affiliateRecovery/review.js.map +1 -0
  23. package/dist/client/affiliateRecovery/validators.d.ts +402 -0
  24. package/dist/client/affiliateRecovery/validators.d.ts.map +1 -0
  25. package/dist/client/affiliateRecovery/validators.js +27 -0
  26. package/dist/client/affiliateRecovery/validators.js.map +1 -0
  27. package/dist/client/affiliateRecovery.d.ts +131 -0
  28. package/dist/client/affiliateRecovery.d.ts.map +1 -0
  29. package/dist/client/affiliateRecovery.js +69 -0
  30. package/dist/client/affiliateRecovery.js.map +1 -0
  31. package/dist/client/connect/commissionReceipts.d.ts +4 -0
  32. package/dist/client/connect/commissionReceipts.d.ts.map +1 -0
  33. package/dist/client/connect/commissionReceipts.js +17 -0
  34. package/dist/client/connect/commissionReceipts.js.map +1 -0
  35. package/dist/client/connect/payoutAdoptionTestHelpers.d.ts +536 -2
  36. package/dist/client/connect/payoutAdoptionTestHelpers.d.ts.map +1 -1
  37. package/dist/client/connect/payoutSync.d.ts +32 -0
  38. package/dist/client/connect/payoutSync.d.ts.map +1 -0
  39. package/dist/client/connect/payoutSync.js +48 -0
  40. package/dist/client/connect/payoutSync.js.map +1 -0
  41. package/dist/client/connect/payoutSyncTestHelpers.d.ts +1509 -0
  42. package/dist/client/connect/payoutSyncTestHelpers.d.ts.map +1 -0
  43. package/dist/client/connect/payoutSyncTestHelpers.js +62 -0
  44. package/dist/client/connect/payoutSyncTestHelpers.js.map +1 -0
  45. package/dist/client/connect/payoutSyncValidation.d.ts +16 -0
  46. package/dist/client/connect/payoutSyncValidation.d.ts.map +1 -0
  47. package/dist/client/connect/payoutSyncValidation.js +54 -0
  48. package/dist/client/connect/payoutSyncValidation.js.map +1 -0
  49. package/dist/client/connect/payouts.d.ts +3 -1
  50. package/dist/client/connect/payouts.d.ts.map +1 -1
  51. package/dist/client/connect/payouts.js +45 -12
  52. package/dist/client/connect/payouts.js.map +1 -1
  53. package/dist/client/connect/refundPrincipal.d.ts +4 -0
  54. package/dist/client/connect/refundPrincipal.d.ts.map +1 -0
  55. package/dist/client/connect/refundPrincipal.js +21 -0
  56. package/dist/client/connect/refundPrincipal.js.map +1 -0
  57. package/dist/client/connect/reversalExecution.d.ts +47 -0
  58. package/dist/client/connect/reversalExecution.d.ts.map +1 -0
  59. package/dist/client/connect/reversalExecution.js +84 -0
  60. package/dist/client/connect/reversalExecution.js.map +1 -0
  61. package/dist/client/connect/transfers.d.ts +8 -4
  62. package/dist/client/connect/transfers.d.ts.map +1 -1
  63. package/dist/client/connect/transfers.js +52 -57
  64. package/dist/client/connect/transfers.js.map +1 -1
  65. package/dist/client/index.d.ts +29 -9
  66. package/dist/client/index.d.ts.map +1 -1
  67. package/dist/client/index.js +9 -6
  68. package/dist/client/index.js.map +1 -1
  69. package/dist/client/readContracts.d.ts +8 -0
  70. package/dist/client/readContracts.d.ts.map +1 -1
  71. package/dist/client/readContracts.js +7 -0
  72. package/dist/client/readContracts.js.map +1 -1
  73. package/dist/client/settlement/execution.d.ts.map +1 -1
  74. package/dist/client/settlement/execution.js +30 -3
  75. package/dist/client/settlement/execution.js.map +1 -1
  76. package/dist/client/settlement/reconciliation.d.ts.map +1 -1
  77. package/dist/client/settlement/reconciliation.js +16 -2
  78. package/dist/client/settlement/reconciliation.js.map +1 -1
  79. package/dist/client/webhooks/disputeRecovery.d.ts +4 -0
  80. package/dist/client/webhooks/disputeRecovery.d.ts.map +1 -0
  81. package/dist/client/webhooks/disputeRecovery.js +40 -0
  82. package/dist/client/webhooks/disputeRecovery.js.map +1 -0
  83. package/dist/client/webhooks/processors.d.ts.map +1 -1
  84. package/dist/client/webhooks/processors.js +14 -29
  85. package/dist/client/webhooks/processors.js.map +1 -1
  86. package/dist/component/_generated/component.d.ts +560 -0
  87. package/dist/component/_generated/component.d.ts.map +1 -1
  88. package/dist/component/affiliateEarnings/mutations.d.ts +22 -0
  89. package/dist/component/affiliateEarnings/mutations.d.ts.map +1 -0
  90. package/dist/component/affiliateEarnings/mutations.js +160 -0
  91. package/dist/component/affiliateEarnings/mutations.js.map +1 -0
  92. package/dist/component/affiliatePayouts/mutations.d.ts +25 -0
  93. package/dist/component/affiliatePayouts/mutations.d.ts.map +1 -0
  94. package/dist/component/affiliatePayouts/mutations.js +104 -0
  95. package/dist/component/affiliatePayouts/mutations.js.map +1 -0
  96. package/dist/component/affiliatePayouts/queries.d.ts +23 -0
  97. package/dist/component/affiliatePayouts/queries.d.ts.map +1 -0
  98. package/dist/component/affiliatePayouts/queries.js +23 -0
  99. package/dist/component/affiliatePayouts/queries.js.map +1 -0
  100. package/dist/component/affiliatePayouts/recipientGuard.d.ts +12 -0
  101. package/dist/component/affiliatePayouts/recipientGuard.d.ts.map +1 -0
  102. package/dist/component/affiliatePayouts/recipientGuard.js +26 -0
  103. package/dist/component/affiliatePayouts/recipientGuard.js.map +1 -0
  104. package/dist/component/affiliateRecovery/accounting.d.ts +28 -0
  105. package/dist/component/affiliateRecovery/accounting.d.ts.map +1 -0
  106. package/dist/component/affiliateRecovery/accounting.js +71 -0
  107. package/dist/component/affiliateRecovery/accounting.js.map +1 -0
  108. package/dist/component/affiliateRecovery/mutations.d.ts +65 -0
  109. package/dist/component/affiliateRecovery/mutations.d.ts.map +1 -0
  110. package/dist/component/affiliateRecovery/mutations.js +250 -0
  111. package/dist/component/affiliateRecovery/mutations.js.map +1 -0
  112. package/dist/component/affiliateRecovery/queries.d.ts +305 -0
  113. package/dist/component/affiliateRecovery/queries.d.ts.map +1 -0
  114. package/dist/component/affiliateRecovery/queries.js +113 -0
  115. package/dist/component/affiliateRecovery/queries.js.map +1 -0
  116. package/dist/component/affiliateRecovery/schema.d.ts +171 -0
  117. package/dist/component/affiliateRecovery/schema.d.ts.map +1 -0
  118. package/dist/component/affiliateRecovery/schema.js +71 -0
  119. package/dist/component/affiliateRecovery/schema.js.map +1 -0
  120. package/dist/component/connect/mutations.d.ts +36 -23
  121. package/dist/component/connect/mutations.d.ts.map +1 -1
  122. package/dist/component/connect/mutations.js +11 -151
  123. package/dist/component/connect/mutations.js.map +1 -1
  124. package/dist/component/connect/payoutWrites.d.ts +21 -0
  125. package/dist/component/connect/payoutWrites.d.ts.map +1 -0
  126. package/dist/component/connect/payoutWrites.js +19 -0
  127. package/dist/component/connect/payoutWrites.js.map +1 -0
  128. package/dist/component/connect/queries.d.ts +10 -0
  129. package/dist/component/connect/queries.d.ts.map +1 -1
  130. package/dist/component/connect/queries.js +3 -40
  131. package/dist/component/connect/queries.js.map +1 -1
  132. package/dist/component/connect/reversalClaims.d.ts +169 -0
  133. package/dist/component/connect/reversalClaims.d.ts.map +1 -0
  134. package/dist/component/connect/reversalClaims.js +176 -0
  135. package/dist/component/connect/reversalClaims.js.map +1 -0
  136. package/dist/component/connect/reversalDiagnostics.d.ts +59 -0
  137. package/dist/component/connect/reversalDiagnostics.d.ts.map +1 -0
  138. package/dist/component/connect/reversalDiagnostics.js +50 -0
  139. package/dist/component/connect/reversalDiagnostics.js.map +1 -0
  140. package/dist/component/connect/reversalReceipts.d.ts +27 -0
  141. package/dist/component/connect/reversalReceipts.d.ts.map +1 -0
  142. package/dist/component/connect/reversalReceipts.js +85 -0
  143. package/dist/component/connect/reversalReceipts.js.map +1 -0
  144. package/dist/component/connect/schema.d.ts +31 -1
  145. package/dist/component/connect/schema.d.ts.map +1 -1
  146. package/dist/component/connect/schema.js +3 -1
  147. package/dist/component/connect/schema.js.map +1 -1
  148. package/dist/component/connect/validators.d.ts +49 -1
  149. package/dist/component/connect/validators.d.ts.map +1 -1
  150. package/dist/component/connect/validators.js +2 -0
  151. package/dist/component/connect/validators.js.map +1 -1
  152. package/dist/component/earnings/triggers.d.ts +454 -2
  153. package/dist/component/earnings/triggers.d.ts.map +1 -1
  154. package/dist/component/payoutRelease/mutations.d.ts.map +1 -1
  155. package/dist/component/payoutRelease/mutations.js +6 -0
  156. package/dist/component/payoutRelease/mutations.js.map +1 -1
  157. package/dist/component/payoutRelease/queries.d.ts.map +1 -1
  158. package/dist/component/payoutRelease/queries.js +3 -1
  159. package/dist/component/payoutRelease/queries.js.map +1 -1
  160. package/dist/component/payoutSync/helpers.d.ts +44 -0
  161. package/dist/component/payoutSync/helpers.d.ts.map +1 -0
  162. package/dist/component/payoutSync/helpers.js +60 -0
  163. package/dist/component/payoutSync/helpers.js.map +1 -0
  164. package/dist/component/payoutSync/mutations.d.ts +51 -0
  165. package/dist/component/payoutSync/mutations.d.ts.map +1 -0
  166. package/dist/component/payoutSync/mutations.js +88 -0
  167. package/dist/component/payoutSync/mutations.js.map +1 -0
  168. package/dist/component/payoutSync/queries.d.ts +16 -0
  169. package/dist/component/payoutSync/queries.d.ts.map +1 -0
  170. package/dist/component/payoutSync/queries.js +11 -0
  171. package/dist/component/payoutSync/queries.js.map +1 -0
  172. package/dist/component/payoutSync/schema.d.ts +46 -0
  173. package/dist/component/payoutSync/schema.d.ts.map +1 -0
  174. package/dist/component/payoutSync/schema.js +22 -0
  175. package/dist/component/payoutSync/schema.js.map +1 -0
  176. package/dist/component/payoutSync/validators.d.ts +137 -0
  177. package/dist/component/payoutSync/validators.d.ts.map +1 -0
  178. package/dist/component/payoutSync/validators.js +19 -0
  179. package/dist/component/payoutSync/validators.js.map +1 -0
  180. package/dist/component/schema.d.ts +268 -1
  181. package/dist/component/schema.js +11 -0
  182. package/dist/component/schema.js.map +1 -1
  183. package/dist/component/settlement/clawbackSchema.d.ts +161 -0
  184. package/dist/component/settlement/clawbackSchema.d.ts.map +1 -0
  185. package/dist/component/settlement/clawbackSchema.js +73 -0
  186. package/dist/component/settlement/clawbackSchema.js.map +1 -0
  187. package/dist/component/settlement/clawbacks.d.ts +55 -0
  188. package/dist/component/settlement/clawbacks.d.ts.map +1 -0
  189. package/dist/component/settlement/clawbacks.js +146 -0
  190. package/dist/component/settlement/clawbacks.js.map +1 -0
  191. package/dist/component/settlement/mutations.d.ts +16 -0
  192. package/dist/component/settlement/mutations.d.ts.map +1 -1
  193. package/dist/component/settlement/mutations.js +48 -0
  194. package/dist/component/settlement/mutations.js.map +1 -1
  195. package/dist/component/settlement/queries.d.ts +53 -0
  196. package/dist/component/settlement/queries.d.ts.map +1 -1
  197. package/dist/component/settlement/queries.js +26 -0
  198. package/dist/component/settlement/queries.js.map +1 -1
  199. package/dist/react/workflows/AffiliateRecovery.d.ts +46 -0
  200. package/dist/react/workflows/AffiliateRecovery.d.ts.map +1 -0
  201. package/dist/react/workflows/AffiliateRecovery.js +84 -0
  202. package/dist/react/workflows/AffiliateRecovery.js.map +1 -0
  203. package/dist/react/workflows/index.d.ts +2 -0
  204. package/dist/react/workflows/index.d.ts.map +1 -1
  205. package/dist/react/workflows/index.js +1 -0
  206. package/dist/react/workflows/index.js.map +1 -1
  207. package/dist/testing/affiliate-recovery.d.ts +10 -0
  208. package/dist/testing/affiliate-recovery.d.ts.map +1 -0
  209. package/dist/testing/affiliate-recovery.js +126 -0
  210. package/dist/testing/affiliate-recovery.js.map +1 -0
  211. package/dist/testing/index.d.ts +2 -0
  212. package/dist/testing/index.d.ts.map +1 -1
  213. package/dist/testing/index.js +1 -0
  214. package/dist/testing/index.js.map +1 -1
  215. package/docs/affiliate-clawbacks.md +306 -0
  216. package/docs/read-contracts.md +40 -5
  217. package/docs/settlement.md +17 -0
  218. package/package.json +2 -1
  219. package/src/client/affiliateRecovery/disputes.ts +62 -0
  220. package/src/client/affiliateRecovery/engine.ts +196 -0
  221. package/src/client/affiliateRecovery/payoutGate.ts +65 -0
  222. package/src/client/affiliateRecovery/provider.ts +127 -0
  223. package/src/client/affiliateRecovery/review.ts +117 -0
  224. package/src/client/affiliateRecovery/validators.ts +38 -0
  225. package/src/client/affiliateRecovery.ts +173 -0
  226. package/src/client/connect/commissionReceipts.test.ts +157 -0
  227. package/src/client/connect/commissionReceipts.ts +23 -0
  228. package/src/client/connect/payoutSync.test.ts +313 -0
  229. package/src/client/connect/payoutSync.ts +93 -0
  230. package/src/client/connect/payoutSyncTestHelpers.ts +82 -0
  231. package/src/client/connect/payoutSyncValidation.ts +74 -0
  232. package/src/client/connect/payouts.ts +75 -15
  233. package/src/client/connect/refundPrincipal.test.ts +46 -0
  234. package/src/client/connect/refundPrincipal.ts +26 -0
  235. package/src/client/connect/reversalExecution.ts +130 -0
  236. package/src/client/connect/transfers.test.ts +3 -3
  237. package/src/client/connect/transfers.ts +93 -92
  238. package/src/client/index.ts +20 -18
  239. package/src/client/readContracts.ts +12 -0
  240. package/src/client/settlement/execution.test.ts +5 -0
  241. package/src/client/settlement/execution.ts +50 -3
  242. package/src/client/settlement/reconciliation.ts +20 -2
  243. package/src/client/webhooks/disputeRecovery.ts +74 -0
  244. package/src/client/webhooks/processors.test.ts +36 -53
  245. package/src/client/webhooks/processors.ts +18 -31
  246. package/src/component/_generated/api.ts +34 -0
  247. package/src/component/_generated/component.ts +696 -1
  248. package/src/component/affiliate-clawbacks.test.ts +370 -0
  249. package/src/component/affiliate-component-pagination.test.ts +83 -0
  250. package/src/component/affiliate-direct-payout.test.ts +160 -0
  251. package/src/component/affiliate-recovery-lifecycle.test.ts +587 -0
  252. package/src/component/affiliateEarnings/mutations.ts +196 -0
  253. package/src/component/affiliatePayouts/mutations.ts +121 -0
  254. package/src/component/affiliatePayouts/queries.ts +28 -0
  255. package/src/component/affiliatePayouts/recipientGuard.ts +42 -0
  256. package/src/component/affiliateRecovery/accounting.ts +111 -0
  257. package/src/component/affiliateRecovery/mutations.ts +275 -0
  258. package/src/component/affiliateRecovery/queries.ts +138 -0
  259. package/src/component/affiliateRecovery/schema.ts +86 -0
  260. package/src/component/connect/mutations.ts +13 -190
  261. package/src/component/connect/payoutWrites.ts +25 -0
  262. package/src/component/connect/queries.ts +4 -47
  263. package/src/component/connect/reversalClaims.ts +232 -0
  264. package/src/component/connect/reversalDiagnostics.ts +61 -0
  265. package/src/component/connect/reversalReceipts.ts +147 -0
  266. package/src/component/connect/schema.ts +3 -3
  267. package/src/component/connect/validators.ts +2 -0
  268. package/src/component/payout-recovery-integration.test.ts +172 -0
  269. package/src/component/payout-sync.test.ts +117 -0
  270. package/src/component/payoutRelease/mutations.ts +9 -0
  271. package/src/component/payoutRelease/queries.ts +3 -1
  272. package/src/component/payoutSync/helpers.ts +86 -0
  273. package/src/component/payoutSync/mutations.ts +102 -0
  274. package/src/component/payoutSync/queries.ts +10 -0
  275. package/src/component/payoutSync/schema.ts +25 -0
  276. package/src/component/payoutSync/validators.ts +21 -0
  277. package/src/component/schema.ts +20 -0
  278. package/src/component/settlement/clawbackSchema.ts +78 -0
  279. package/src/component/settlement/clawbacks.ts +233 -0
  280. package/src/component/settlement/mutations.ts +54 -0
  281. package/src/component/settlement/queries.ts +35 -0
  282. package/src/component/summary-arithmetic.test.ts +9 -4
  283. package/src/react/workflows/AffiliateRecovery.test.tsx +106 -0
  284. package/src/react/workflows/AffiliateRecovery.tsx +273 -0
  285. package/src/react/workflows/index.ts +9 -0
  286. package/src/testing/affiliate-recovery.ts +137 -0
  287. package/src/testing/index.ts +2 -0
@@ -96,6 +96,13 @@ type MockReversal = {
96
96
 
97
97
  function makeStripe() {
98
98
  return {
99
+ refunds: {
100
+ list: vi.fn(() =>
101
+ (async function* () {
102
+ yield { amount: 5000, status: "succeeded" };
103
+ })(),
104
+ ),
105
+ },
99
106
  products: { retrieve: vi.fn() },
100
107
  subscriptions: {
101
108
  retrieve: vi.fn(),
@@ -1396,7 +1403,7 @@ describe("processEvent — split transfer engine (BTS-22)", () => {
1396
1403
  expect(data.statementDescriptor).toBe("ACME STORE");
1397
1404
  });
1398
1405
 
1399
- it("[BTS-26] still processes the dispute when the charge lookup fails (descriptor is best-effort)", async () => {
1406
+ it("records the dispute but retries recovery when the charge basis is unavailable", async () => {
1400
1407
  const stripe = makeStripe();
1401
1408
  stripe.charges.retrieve.mockRejectedValue(new Error("stripe down"));
1402
1409
  const ctx = makeCtx({
@@ -1411,26 +1418,28 @@ describe("processEvent — split transfer engine (BTS-22)", () => {
1411
1418
  });
1412
1419
  const whCtx = makeWhCtx({ stripe, ctx });
1413
1420
 
1414
- await processEvent(
1415
- whCtx,
1416
- event("charge.dispute.created", {
1417
- id: "dp_bf",
1418
- charge: "ch_1",
1419
- amount: 9000,
1420
- currency: "usd",
1421
- status: "needs_response",
1422
- reason: "fraudulent",
1423
- is_charge_refundable: true,
1424
- evidence_details: { due_by: 1700000000 },
1425
- metadata: {},
1426
- }),
1427
- );
1421
+ await expect(
1422
+ processEvent(
1423
+ whCtx,
1424
+ event("charge.dispute.created", {
1425
+ id: "dp_bf",
1426
+ charge: "ch_1",
1427
+ amount: 9000,
1428
+ currency: "usd",
1429
+ status: "needs_response",
1430
+ reason: "fraudulent",
1431
+ is_charge_refundable: true,
1432
+ evidence_details: { due_by: 1700000000 },
1433
+ metadata: {},
1434
+ }),
1435
+ ),
1436
+ ).rejects.toThrow("stripe down");
1428
1437
 
1429
- // Dispute still recorded (without a descriptor) and clawback still ran.
1438
+ // Record the dispute, but require an authoritative basis before recovery.
1430
1439
  const { path, data } = dispatchedPayload(whCtx.ctx);
1431
1440
  expect(path).toBe("betterStripe/connect/mutations/upsertDispute");
1432
1441
  expect(data.statementDescriptor).toBeUndefined();
1433
- expect(stripe.transfers.createReversal).toHaveBeenCalledTimes(1);
1442
+ expect(stripe.transfers.createReversal).not.toHaveBeenCalled();
1434
1443
  });
1435
1444
 
1436
1445
  it("[BTS-27] claws back a single-recipient transfer on dispute", async () => {
@@ -2338,7 +2347,6 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2338
2347
  : {}),
2339
2348
  },
2340
2349
  ];
2341
-
2342
2350
  const refundEvent = (overrides: Record<string, unknown> = {}) =>
2343
2351
  event("refund.created", {
2344
2352
  id: "re_1",
@@ -2350,7 +2358,6 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2350
2358
  metadata: {},
2351
2359
  ...overrides,
2352
2360
  });
2353
-
2354
2361
  it("reverses split-sale transfers pro-rata on a succeeded partial refund", async () => {
2355
2362
  const ctx = makeCtx({ query: ledgerLegs() });
2356
2363
  const stripe = makeStripe();
@@ -2360,10 +2367,7 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2360
2367
  amount_refunded: 5000,
2361
2368
  });
2362
2369
  const whCtx = makeWhCtx({ ctx, stripe });
2363
-
2364
2370
  await processEvent(whCtx, refundEvent());
2365
-
2366
- // 50% refunded → each leg reversed to 50% of its original amount.
2367
2371
  expect(stripe.transfers.createReversal).toHaveBeenCalledTimes(2);
2368
2372
  expect(stripe.transfers.createReversal).toHaveBeenCalledWith(
2369
2373
  "tr_s",
@@ -2376,19 +2380,21 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2376
2380
  { idempotencyKey: "bs_rev_re_1_tr_a" },
2377
2381
  );
2378
2382
  });
2379
-
2380
2383
  it("reverses the full transfer amounts on a full refund", async () => {
2381
2384
  const ctx = makeCtx({ query: ledgerLegs() });
2382
2385
  const stripe = makeStripe();
2386
+ stripe.refunds.list.mockImplementation(() =>
2387
+ (async function* () {
2388
+ yield { amount: 10000, status: "succeeded" };
2389
+ })(),
2390
+ );
2383
2391
  stripe.charges.retrieve.mockResolvedValue({
2384
2392
  id: "ch_1",
2385
2393
  amount: 10000,
2386
2394
  amount_refunded: 10000,
2387
2395
  });
2388
2396
  const whCtx = makeWhCtx({ ctx, stripe });
2389
-
2390
2397
  await processEvent(whCtx, refundEvent({ amount: 10000 }));
2391
-
2392
2398
  expect(stripe.transfers.createReversal).toHaveBeenCalledWith(
2393
2399
  "tr_s",
2394
2400
  { amount: 7000 },
@@ -2400,23 +2406,23 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2400
2406
  { idempotencyKey: "bs_rev_re_1_tr_a" },
2401
2407
  );
2402
2408
  });
2403
-
2404
2409
  it("a second partial refund reverses only the delta up to the new cumulative target", async () => {
2405
- // First refund (5000) already clawed back 3500/500 and was recorded.
2406
2410
  const ctx = makeCtx({
2407
2411
  query: ledgerLegs({ reversedStore: 3500, reversedAff: 500 }),
2408
2412
  });
2409
2413
  const stripe = makeStripe();
2414
+ stripe.refunds.list.mockImplementation(() =>
2415
+ (async function* () {
2416
+ yield { amount: 7000, status: "succeeded" };
2417
+ })(),
2418
+ );
2410
2419
  stripe.charges.retrieve.mockResolvedValue({
2411
2420
  id: "ch_1",
2412
2421
  amount: 10000,
2413
2422
  amount_refunded: 7000, // cumulative: 5000 + this refund's 2000
2414
2423
  });
2415
2424
  const whCtx = makeWhCtx({ ctx, stripe });
2416
-
2417
2425
  await processEvent(whCtx, refundEvent({ id: "re_2", amount: 2000 }));
2418
-
2419
- // Target 70% of each leg minus what's already reversed: 4900-3500, 700-500.
2420
2426
  expect(stripe.transfers.createReversal).toHaveBeenCalledWith(
2421
2427
  "tr_s",
2422
2428
  { amount: 1400 },
@@ -2428,7 +2434,6 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2428
2434
  { idempotencyKey: "bs_rev_re_2_tr_a" },
2429
2435
  );
2430
2436
  });
2431
-
2432
2437
  it("is idempotent — a redelivered refund event whose reversals are recorded reverses nothing", async () => {
2433
2438
  const ctx = makeCtx({
2434
2439
  query: ledgerLegs({ reversedStore: 3500, reversedAff: 500 }),
@@ -2440,36 +2445,24 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2440
2445
  amount_refunded: 5000,
2441
2446
  });
2442
2447
  const whCtx = makeWhCtx({ ctx, stripe });
2443
-
2444
2448
  await processEvent(whCtx, refundEvent());
2445
-
2446
- // Cumulative target (50%) already met on both legs — no delta, no calls.
2447
2449
  expect(stripe.transfers.createReversal).not.toHaveBeenCalled();
2448
2450
  });
2449
-
2450
2451
  it("skips clawback quietly for a refund on a non-split sale", async () => {
2451
2452
  const ctx = makeCtx({ query: [] });
2452
2453
  const stripe = makeStripe();
2453
2454
  const whCtx = makeWhCtx({ ctx, stripe });
2454
-
2455
2455
  await processEvent(whCtx, refundEvent());
2456
-
2457
- // No ledger legs → not a split sale (Stripe-native reverse_transfer covers
2458
- // destination charges). No charge lookup, no reversals, no throw.
2459
2456
  expect(stripe.charges.retrieve).not.toHaveBeenCalled();
2460
2457
  expect(stripe.transfers.createReversal).not.toHaveBeenCalled();
2461
2458
  });
2462
-
2463
2459
  it("does not claw back for a pending refund", async () => {
2464
2460
  const ctx = makeCtx({ query: ledgerLegs() });
2465
2461
  const stripe = makeStripe();
2466
2462
  const whCtx = makeWhCtx({ ctx, stripe });
2467
-
2468
2463
  await processEvent(whCtx, refundEvent({ status: "pending" }));
2469
-
2470
2464
  expect(stripe.transfers.createReversal).not.toHaveBeenCalled();
2471
2465
  });
2472
-
2473
2466
  it("propagates a reversal failure so the event is marked failed and Stripe retries", async () => {
2474
2467
  const ctx = makeCtx({ query: ledgerLegs() });
2475
2468
  const stripe = makeStripe();
@@ -2480,12 +2473,10 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2480
2473
  });
2481
2474
  stripe.transfers.createReversal.mockRejectedValue(new Error("stripe down"));
2482
2475
  const whCtx = makeWhCtx({ ctx, stripe });
2483
-
2484
2476
  await expect(processEvent(whCtx, refundEvent())).rejects.toThrow(
2485
2477
  "stripe down",
2486
2478
  );
2487
2479
  });
2488
-
2489
2480
  it("application_fee.refunded sets absolute fee totals on the linked payment", async () => {
2490
2481
  const stripe = makeStripe();
2491
2482
  stripe.charges.retrieve.mockResolvedValue({
@@ -2493,7 +2484,6 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2493
2484
  payment_intent: "pi_1",
2494
2485
  });
2495
2486
  const whCtx = makeWhCtx({ stripe });
2496
-
2497
2487
  const feeEvent = event("application_fee.refunded", {
2498
2488
  id: "fee_1",
2499
2489
  charge: "ch_1",
@@ -2502,7 +2492,6 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2502
2492
  currency: "usd",
2503
2493
  });
2504
2494
  await processEvent(whCtx, feeEvent);
2505
-
2506
2495
  const calls = whCtx.ctx.runMutation.mock.calls;
2507
2496
  const feeCall = calls.find(
2508
2497
  (c) =>
@@ -2510,15 +2499,11 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2510
2499
  "betterStripe/connect/mutations/recordPaymentFeeRefund",
2511
2500
  );
2512
2501
  expect(feeCall).toBeDefined();
2513
- // Absolute cumulative values straight from Stripe — never incremented, so
2514
- // a redelivery (or a createRefund-triggered duplicate) can't double-count.
2515
2502
  expect(feeCall![1]).toEqual({
2516
2503
  stripePaymentIntentId: "pi_1",
2517
2504
  feeCollectedAmount: 160,
2518
2505
  feeRefundedAmount: 160,
2519
2506
  });
2520
-
2521
- // Redelivery writes the identical absolute state.
2522
2507
  await processEvent(whCtx, feeEvent);
2523
2508
  const feeCalls = whCtx.ctx.runMutation.mock.calls.filter(
2524
2509
  (c) =>
@@ -2528,7 +2513,6 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2528
2513
  expect(feeCalls).toHaveLength(2);
2529
2514
  expect(feeCalls[1][1]).toEqual(feeCalls[0][1]);
2530
2515
  });
2531
-
2532
2516
  it("application_fee.refunded skips quietly when the charge has no payment intent", async () => {
2533
2517
  const stripe = makeStripe();
2534
2518
  stripe.charges.retrieve.mockResolvedValue({
@@ -2536,7 +2520,6 @@ describe("processEvent — refunds: split clawback + fee-refund denormalization
2536
2520
  payment_intent: null,
2537
2521
  });
2538
2522
  const whCtx = makeWhCtx({ stripe });
2539
-
2540
2523
  await expect(
2541
2524
  processEvent(
2542
2525
  whCtx,
@@ -1,3 +1,5 @@
1
+ import { reconcileDisputeMoney } from "./disputeRecovery.js";
2
+ import { successfulRefundPrincipal } from "../connect/refundPrincipal.js";
1
3
  import { subscriptionRouting } from "../billing/subscriptionRouting.js";
2
4
  import { cancelsAtPeriodEnd } from "../billing/subscriptionCancellation.js";
3
5
  import type Stripe from "stripe";
@@ -5,8 +7,6 @@ import type Stripe from "stripe";
5
7
  import type { SplitRecipient } from "../../component/lib/fees.js";
6
8
  import {
7
9
  createSplitTransfers,
8
- reinstateTransfers,
9
- reverseTransfers,
10
10
  reverseTransfersForRefund,
11
11
  } from "../connect/transfers.js";
12
12
  import { validateStatementDescriptorSuffix } from "../core/descriptors.js";
@@ -989,15 +989,24 @@ async function handleRefundEvent(
989
989
  componentRef(whCtx.component, "connect/queries/listTransfersByCharge"),
990
990
  { sourceChargeId: chargeId },
991
991
  )) ?? []) as unknown[];
992
- if (legs.length === 0) return; // not a split sale
992
+ if (legs.length === 0) {
993
+ const obligations = (await whCtx.ctx.runQuery(
994
+ componentRef(
995
+ whCtx.component,
996
+ "affiliateRecovery/queries/chargeObligations",
997
+ ),
998
+ { sourceChargeId: chargeId },
999
+ )) as unknown[] | null;
1000
+ if (!obligations?.length) return;
1001
+ }
993
1002
 
994
- // Fresh charge read for the authoritative cumulative amount_refunded.
1003
+ // Fetch succeeded refunds: charge.amount_refunded can include pending funds.
995
1004
  const charge = await whCtx.stripe.charges.retrieve(chargeId);
996
1005
  await reverseTransfersForRefund(whCtx.stripe, whCtx.component, whCtx.ctx, {
997
1006
  sourceChargeId: chargeId,
998
1007
  refundId: refund.id,
999
1008
  chargeAmount: charge.amount,
1000
- amountRefunded: charge.amount_refunded ?? refund.amount,
1009
+ amountRefunded: await successfulRefundPrincipal(whCtx.stripe, chargeId),
1001
1010
  });
1002
1011
  }
1003
1012
 
@@ -1054,9 +1063,11 @@ async function handleDisputeEvent(
1054
1063
  // best-effort: this is optional metadata, so a transient charge-lookup failure
1055
1064
  // must never abort the critical dispute side effects (upsert, clawback, cancel).
1056
1065
  let statementDescriptor: string | undefined;
1066
+ let originalCharge: Stripe.Charge | undefined;
1057
1067
  if (lastEvent === "created" && chargeId) {
1058
1068
  try {
1059
- const charge = await whCtx.stripe.charges.retrieve(chargeId);
1069
+ const charge = (originalCharge =
1070
+ await whCtx.stripe.charges.retrieve(chargeId));
1060
1071
  statementDescriptor =
1061
1072
  charge?.calculated_statement_descriptor ??
1062
1073
  charge?.statement_descriptor ??
@@ -1082,31 +1093,7 @@ async function handleDisputeEvent(
1082
1093
  metadata: dispute.metadata ?? undefined,
1083
1094
  });
1084
1095
 
1085
- // Clawback (BTS-27): when a dispute is first opened, reverse the transfers
1086
- // funded by the disputed charge pro-rata to the disputed amount, so recipients
1087
- // bear their share (losses_collector=application leaves the platform with the
1088
- // rest). Idempotent via the dispute id; reinstated by BTS-29 if the dispute is
1089
- // won. Only on `created` — other dispute events don't move funds here.
1090
- if (lastEvent === "created" && chargeId) {
1091
- await reverseTransfers(whCtx.stripe, whCtx.component, whCtx.ctx, {
1092
- sourceChargeId: chargeId,
1093
- amount: dispute.amount,
1094
- operationId: dispute.id,
1095
- });
1096
- }
1097
-
1098
- // Reinstatement (BTS-29): when the dispute is won (or funds are explicitly
1099
- // reinstated), pay recipients back the amounts clawed back on `created`.
1100
- const isWon =
1101
- lastEvent === "funds_reinstated" ||
1102
- (lastEvent === "closed" && dispute.status === "won");
1103
- if (isWon && chargeId) {
1104
- await reinstateTransfers(whCtx.stripe, whCtx.component, whCtx.ctx, {
1105
- sourceChargeId: chargeId,
1106
- operationId: dispute.id,
1107
- currency: dispute.currency,
1108
- });
1109
- }
1096
+ await reconcileDisputeMoney(whCtx, dispute, lastEvent, originalCharge);
1110
1097
 
1111
1098
  // Auto-cancel the disputed subscription (BTS-28, default on). Resolve the
1112
1099
  // subscription from the dispute's PaymentIntent (invoice payment → invoice →
@@ -8,11 +8,22 @@
8
8
  * @module
9
9
  */
10
10
 
11
+ import type * as affiliateEarnings_mutations from "../affiliateEarnings/mutations.js";
12
+ import type * as affiliatePayouts_mutations from "../affiliatePayouts/mutations.js";
13
+ import type * as affiliatePayouts_queries from "../affiliatePayouts/queries.js";
14
+ import type * as affiliatePayouts_recipientGuard from "../affiliatePayouts/recipientGuard.js";
15
+ import type * as affiliateRecovery_accounting from "../affiliateRecovery/accounting.js";
16
+ import type * as affiliateRecovery_mutations from "../affiliateRecovery/mutations.js";
17
+ import type * as affiliateRecovery_queries from "../affiliateRecovery/queries.js";
11
18
  import type * as billing_mutations from "../billing/mutations.js";
12
19
  import type * as billing_queries from "../billing/queries.js";
13
20
  import type * as billing_validators from "../billing/validators.js";
14
21
  import type * as connect_mutations from "../connect/mutations.js";
22
+ import type * as connect_payoutWrites from "../connect/payoutWrites.js";
15
23
  import type * as connect_queries from "../connect/queries.js";
24
+ import type * as connect_reversalClaims from "../connect/reversalClaims.js";
25
+ import type * as connect_reversalDiagnostics from "../connect/reversalDiagnostics.js";
26
+ import type * as connect_reversalReceipts from "../connect/reversalReceipts.js";
16
27
  import type * as connect_validators from "../connect/validators.js";
17
28
  import type * as core_mutations from "../core/mutations.js";
18
29
  import type * as core_queries from "../core/queries.js";
@@ -45,10 +56,16 @@ import type * as payoutRelease_helpers from "../payoutRelease/helpers.js";
45
56
  import type * as payoutRelease_mutations from "../payoutRelease/mutations.js";
46
57
  import type * as payoutRelease_observation from "../payoutRelease/observation.js";
47
58
  import type * as payoutRelease_queries from "../payoutRelease/queries.js";
59
+ import type * as payoutSync_helpers from "../payoutSync/helpers.js";
60
+ import type * as payoutSync_mutations from "../payoutSync/mutations.js";
61
+ import type * as payoutSync_queries from "../payoutSync/queries.js";
62
+ import type * as payoutSync_validators from "../payoutSync/validators.js";
48
63
  import type * as products_mutations from "../products/mutations.js";
49
64
  import type * as products_queries from "../products/queries.js";
50
65
  import type * as products_validators from "../products/validators.js";
51
66
  import type * as refundHistory_queries from "../refundHistory/queries.js";
67
+ import type * as settlement_clawbackSchema from "../settlement/clawbackSchema.js";
68
+ import type * as settlement_clawbacks from "../settlement/clawbacks.js";
52
69
  import type * as settlement_mutations from "../settlement/mutations.js";
53
70
  import type * as settlement_queries from "../settlement/queries.js";
54
71
  import type * as webhooks_mutations from "../webhooks/mutations.js";
@@ -63,11 +80,22 @@ import type {
63
80
  import { anyApi, componentsGeneric } from "convex/server";
64
81
 
65
82
  const fullApi: ApiFromModules<{
83
+ "affiliateEarnings/mutations": typeof affiliateEarnings_mutations;
84
+ "affiliatePayouts/mutations": typeof affiliatePayouts_mutations;
85
+ "affiliatePayouts/queries": typeof affiliatePayouts_queries;
86
+ "affiliatePayouts/recipientGuard": typeof affiliatePayouts_recipientGuard;
87
+ "affiliateRecovery/accounting": typeof affiliateRecovery_accounting;
88
+ "affiliateRecovery/mutations": typeof affiliateRecovery_mutations;
89
+ "affiliateRecovery/queries": typeof affiliateRecovery_queries;
66
90
  "billing/mutations": typeof billing_mutations;
67
91
  "billing/queries": typeof billing_queries;
68
92
  "billing/validators": typeof billing_validators;
69
93
  "connect/mutations": typeof connect_mutations;
94
+ "connect/payoutWrites": typeof connect_payoutWrites;
70
95
  "connect/queries": typeof connect_queries;
96
+ "connect/reversalClaims": typeof connect_reversalClaims;
97
+ "connect/reversalDiagnostics": typeof connect_reversalDiagnostics;
98
+ "connect/reversalReceipts": typeof connect_reversalReceipts;
71
99
  "connect/validators": typeof connect_validators;
72
100
  "core/mutations": typeof core_mutations;
73
101
  "core/queries": typeof core_queries;
@@ -100,10 +128,16 @@ const fullApi: ApiFromModules<{
100
128
  "payoutRelease/mutations": typeof payoutRelease_mutations;
101
129
  "payoutRelease/observation": typeof payoutRelease_observation;
102
130
  "payoutRelease/queries": typeof payoutRelease_queries;
131
+ "payoutSync/helpers": typeof payoutSync_helpers;
132
+ "payoutSync/mutations": typeof payoutSync_mutations;
133
+ "payoutSync/queries": typeof payoutSync_queries;
134
+ "payoutSync/validators": typeof payoutSync_validators;
103
135
  "products/mutations": typeof products_mutations;
104
136
  "products/queries": typeof products_queries;
105
137
  "products/validators": typeof products_validators;
106
138
  "refundHistory/queries": typeof refundHistory_queries;
139
+ "settlement/clawbackSchema": typeof settlement_clawbackSchema;
140
+ "settlement/clawbacks": typeof settlement_clawbacks;
107
141
  "settlement/mutations": typeof settlement_mutations;
108
142
  "settlement/queries": typeof settlement_queries;
109
143
  "webhooks/mutations": typeof webhooks_mutations;