Repository navigation
feat(billing): take back affiliate commission when a referred payment is refunded - #4280
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
|
Warning Review limit reached
This review includes 24 billable files and costs up to $6.00.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 10 minutes for your next included review. View limit details
|
95f4aee to
2ac14fe
Compare
23866e7 to
05315db
Compare
|
@claude review |
There was a problem hiding this comment.
An organization admin can view or raise the monthly spending cap at claude.ai/admin-settings/usage, on the Claude Code Review row under Services. The cap resets at the start of the next billing period.
Once the cap resets or is raised, someone with write access to this repository can comment @claude review on this pull request to trigger a review.
2ac14fe to
ec5e58f
Compare
05315db to
d76364a
Compare
ec5e58f to
4067df0
Compare
d76364a to
8490638
Compare
4067df0 to
a68301b
Compare
8490638 to
67c3b38
Compare
a68301b to
da3b26b
Compare
67c3b38 to
340549c
Compare
9ebe380 to
5df23d0
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4280 +/- ##
==========================================
+ Coverage 94.93% 95.13% +0.19%
==========================================
Files 2155 2048 -107
Lines 62826 60091 -2735
Branches 15726 15189 -537
==========================================
- Hits 59646 57167 -2479
+ Misses 2860 2620 -240
+ Partials 320 304 -16
*This pull request uses carry forward flags. Click here to find out more.
🚀 New features to boost your workflow:
|
5df23d0 to
a2cf1d7
Compare
c9943fb to
79a941d
Compare
… is refunded A refund of a referred card payment, or a dispute the payer wins, now takes the matching part of the affiliate's commission back out of their Console credit. charge.refunded queues the SyncAffiliateCommission job for card payments when the delivery records a new refund amount. A new charge.dispute.closed route records dispute_lost_at on the payment when the dispute is lost and queues the same job. The payer's own balance is untouched by a dispute, as before. The sync now reconciles every commission it finds or grants. The commission should be 5% of what the payer still paid (zero after a lost dispute), rounded down, and the job takes back the difference from what was already reversed: it updates amount_refunded on the commission row (status refunded once fully reversed) and reduces the affiliate's allowance, floored at zero. Repeated syncs take back nothing twice. A payment refunded before its first sync is granted and then reversed in the same run. Reversal ignores the flag and applies to revoked affiliates. - add stripe_transactions.dispute_lost_at (migration 0076) - RefillService.reduceWalletBalance returns the shortfall it could not cover, logged with AFFILIATE_COMMISSION_REVERSED - skip the grant with reason affiliate_abuse_locked when the affiliate's wallet is locked for abuse - track affiliate_commission_reversed in Amplitude Ops: add charge.dispute.closed to the Stripe webhook endpoint's events. Refs CON-1192
…e top-up A payment refunded or lost to a dispute before its commission sync ran used to be granted in full and then reduced in the same job. If the reduction failed, the row rolled back while the top-up stayed on chain, and the retry granted it again. The grant now records the gross commission with the refunded share already marked as taken back, and tops up only what is left. Nothing is granted when nothing is left. The sync job now says what queued it. Only a settlement may grant; a refund or a dispute only takes back a commission that already exists, so a skipped grant stays skipped and a refund that beats the settlement job leaves the grant to it. - take back each refund's share of the stored commission, capped at the commission, so a later rate change cannot over-reverse - lock the wallet row in RefillService.reduceWalletBalance, as topUpWallet does, so a reversal and a grant on one wallet serialize - round the reported shortfall up to whole cents Refs CON-1192
…rsed total Main now debits a refund once per transaction and refunded total, from a connection of its own. The reversal passes the commission row and its reversed totals as that key, so a redelivered reversal never debits the affiliate twice. The commission row is locked FOR NO KEY UPDATE, because the refund debit references it from that other connection and would otherwise wait forever on the sync holding the lock. A refund now queues a commission sync, so the refund debit integration spec registers its handler.
79a941d to
0950fbf
Compare
Why
Closes CON-1192. Without this, someone could pay, collect the affiliate commission and then get a refund, and repeat it.
What
applyRefund) and a lost dispute (newcharge.dispute.closedhandler, newdispute_lost_atcolumn, migration 0129) queue the sameSyncAffiliateCommissionjob. The job takes back the matching share of the commission:floor(commission * netPaid / amount)is kept, the rest is reversed, and the total reversed never exceeds the commission.affiliate_commission_reversed.reduceWalletBalancenow locks the wallet it reduces (the team's or the member's own), astopUpWalletdoes, so a reversal and a grant on the same wallet don't overwrite each other.FOR NO KEY UPDATE, because that debit references it from another connection.Ops before turning
affiliate_programon: addcharge.dispute.closedto the Stripe webhook endpoint's events.Testing
Unit specs for partial and full refunds, a lost dispute, a won dispute, a repeated sync, a revoked affiliate, the flag off, a refund before the grant, the abuse lock and rounding. Integration specs through the real job worker. Functional webhook specs for a 40% refund (200 of 500 taken back) and a lost dispute (fully reversed, payer untouched). Mutation score on changed lines: 100%.