A subtle bug shows up in almost every homegrown affiliate system: the commission gets computed from the subscription's list price instead of what the customer actually paid. Those two numbers match only in the simplest case. Add a coupon, a mid-cycle upgrade, or an annual discount and you start paying affiliates on revenue you never collected.
The rule I follow: commission is a function of the invoice, not the plan.
Where the naive version breaks
// tempting, and wrong
const commission = plan.unit_amount * commissionRate;
That ignores three things Stripe already computed for you:
- Coupons and discounts. A 50%-off promo means the customer paid half, but
plan.unit_amountstill reports the full price. - Proration. When someone upgrades mid-cycle, Stripe issues a prorated charge: part credit for the unused old plan, part charge for the new one. The plan price matches neither.
- Tax. If you commission on net revenue, the tax line should not be in the base.
Read it off the invoice instead
Handle invoice.payment_succeeded and compute from the amount that actually settled:
// amounts are in the smallest currency unit (cents)
const netPaid = invoice.total_excluding_tax;
const commission = Math.round(netPaid * commissionRate);
total_excluding_tax is already post-discount and post-proration, so coupons and upgrades fall out for free. If you only pay commission on some products in a mixed invoice, sum the qualifying line items and subtract each line's allocated discount from line.discount_amounts instead of trusting the plan price.
Two things that bite later
Idempotency. Stripe retries webhooks, so key the commission insert on the invoice id with a unique constraint. Otherwise a retry double-pays the affiliate.
create unique index on commissions (invoice_id);
Clawbacks. A commission is a liability until the refund window closes. When charge.refunded or a lost dispute fires, reverse the matching commission rather than deleting it, so your ledger keeps the history of what happened.
The short version: let Stripe do the arithmetic. Every discount, proration, and credit already lives in the invoice total, so anchor commission there and most of the edge cases disappear on their own.
(I build Referralful, affiliate software for SaaS on Stripe. This is the exact math it runs so you don't have to: https://referralful.com/?utm_source=dev.to&utm_medium=article&utm_campaign=content)
Top comments (0)