How to handle Stripe affiliate commissions on refunds, upgrades and downgrades

How a Stripe affiliate commission should react to partial refunds, upgrades, downgrades and cancellations, with the invoice arithmetic worked out.

Muzahid Maruf — Founder of TrackRev.io

Muzahid MarufUpdated

Affiliate programs · 8 min read
On this page
  1. 01How should a Stripe affiliate commission react to each event?
  2. 02How does refund math work with tax on the invoice?
  3. 03How do upgrades and downgrades change the invoice?
  4. 04What happens on annual switches, cancellations and failed renewals?
  5. 05How do you test these cases with Stripe test clocks?
  6. 06How do you keep channel revenue honest after refunds and upgrades?
  7. 07How do other affiliate tools handle these events?

Explore with AI

Opens this article inside the chosen assistant with a ready-made prompt.

A Stripe affiliate commission is a percentage of the cash a customer paid and kept.

Stripe Billing records the payment on invoice.paid, so create the commission there, base it on the pre-tax amount, and shrink it in proportion on charge.refunded, which covers partial refunds too.

With cash as the base, upgrades, downgrades and coupons adjust it through Stripe's own invoice math, and cancellation only stops future commissions.

The worked examples below cover a refund that cuts a $60.00 commission to $40.00 and an upgrade from $99 to $299 that pays $125.87 across 90 days.

The numbers rest on Stripe's 2026 documentation for 8 webhook events, and the last section shows where TrackRev differs.

Key takeaways

  • Create the commission on invoice.paid from the pre-tax cash paid, so upgrades, coupons and downgrade credits flow through Stripe's own invoice amounts.
  • One event, charge.refunded, covers full and partial refunds. Scale the commission by the share of the charge the customer kept: a $110.00 refund on a $330.00 charge turns a $60.00 commission into $40.00.
  • A downgrade sends no refund event. Stripe credits the next invoice, so a plan-price commission overpays by 20% of the credit, which is $13.33 on a $66.67 credit.

How should a Stripe affiliate commission react to each event?

EventWhat Stripe says it meansCommission action
invoice.paidPayment succeededCreate the commission
charge.refundedRefunded, partially or fullyRecompute from the running amount_refunded
refund.failedA refund failedRestore the reversed commission
customer.subscription.updatedPlan, coupon or renewal changeOnly for per-plan rates or caps
customer.subscription.deletedSubscription endedStop future commissions
invoice.payment_failedPayment attempt failedNothing: no paid invoice, no commission
charge.dispute.created, charge.dispute.closedDispute opened or closedReverse on open, restore on a win
checkout.session.completedCheckout finishedDo not pay on it

The webhook types that touch a commission, with meanings paraphrased from Stripe's event type reference.

Eight Stripe event types touch a commission, and the table sets the action for each. Of the 8 rows, 4 change the amount (invoice.paid, charge.refunded, refund.failed and the dispute pair) and 2 trigger nothing (invoice.payment_failed and checkout.session.completed).

The trigger is invoice.paid, which means Stripe collected the money, and checkout completion is not that signal. checkout.session.completed can fire before money moves in Stripe Checkout. Stripe's fulfillment guide gives ACH Direct Debit as an example, with checkout.session.async_payment_succeeded following later.

In Stripe Checkout, pay on invoice.paid for subscriptions and payment_intent.succeeded for one-time payments.

The refund trigger is charge.refunded, the event Stripe describes as covering partial refunds. The charge.refund.updated event fires only on selected payment methods, so a handler built on it alone can miss them.

Stripe's webhook documentation adds that an endpoint can receive the same event twice and that live deliveries retry for up to 3 days, so recompute from current totals and key each write on an ID.

The guide to affiliate refund clawbacks covers replays, disputes and holds.

How does refund math work with tax on the invoice?

Refund math with tax means scaling the commission by the share of the charge the customer kept.

Tax belongs to the tax authority, so a commission on the gross charge overpays the affiliate by the tax rate, while a refund is gross because it returns the tax too.

Take a $300.00 invoice plus $30.00 of sales tax from Stripe Tax, a 20% commission of $60.00 on the pre-tax amount, and two refunds against the $330.00 charge.

RefundRefunded so farLeft after reversing 20% of each refundLeft after scaling by what the customer kept
$110.00$110.00$38.00$40.00
$220.00$330.00-$6.00$0.00

The scaled figure is $60.00 x (1 - refunded / $330.00).

Scaling needs no tax lookup, because the commission was already pre-tax. Reversing 20% of each refund takes back $22.00 and $44.00, which is $6.00 more than the $60.00 credited.

The scaled method assumes a refund returns the $300.00 pre-tax amount and the $30.00 tax in the original ratio. Floor the result at $0.00 either way.

Node: recompute from the charge on charge.refunded
async function onChargeRefunded(event) {  const charge = event.data.object; // amount_refunded is the running total  const commission = await findCommissionByCharge(charge.id);  if (!commission) return; // not an affiliate sale   const kept = 1 - charge.amount_refunded / charge.amount;  const next = Math.max(0, Math.round(commission.original_cents * kept));   // event.id is the idempotency key: a repeat delivery changes nothing  if (next !== commission.current_cents) {    await setCommission(commission.id, next, event.id);  }}

How do upgrades and downgrades change the invoice?

A proration is a credit or debit line that Stripe adds to an invoice when a plan changes.

By default, create_prorations adds a credit line for unused time on the old price and a debit line for the remaining time on the new one, and bills neither at once.

Setting always_invoice bills an upgrade immediately, per the subscription update reference. A downgrade sends no refund: the proration guide says negative prorations are not automatically refunded, and the customer balance guide lists them as credit against the next invoice.

With no charge.refunded event, the affiliate keeps the larger $299 plan's commission.

Take one customer: a $99 plan upgraded to $299 on day 15 of a 30-day cycle, then dropped back to $99 on day 50.

ChangeCredit for old planDebit for new planNet
Day 15, $99 to $299, 15 days left-$49.50$149.50$100.00
Day 50, $299 to $99, 10 days left-$99.67$33.00-$66.67

Stripe prorates to the second, so live invoices differ by a few cents from these 2 rows.

DayStripe eventInvoiceCash commission at 20%Plan-price commission at 20%
0Signup on $99$99.00$19.80$19.80
15Upgrade, prorations queuedNone$0.00$0.00
30Renewal with the $100.00 proration$399.00$79.80$59.80
50Downgrade, credit queuedNone$0.00$0.00
60Renewal after the $66.67 credit$32.33$6.47$19.80
90Renewal$99.00$19.80$19.80
Total$629.33$125.87$119.20

Default proration settings, tax ignored.

Should a commission follow cash or the plan price?

A commission should follow cash, not the plan price. With always_invoice on the upgrade, the $100.00 is invoiced on day 15 and the day 30 invoice is $299.00, so the commission total stays $125.87 and only the timing moves.

The plan-price rule is $20.00 short on the upgrade and pays $13.33 on credited money at the downgrade, so across 90 days the cash rule pays $125.87 and the plan-price rule $119.20, a $6.67 gap from two errors pulling in opposite directions.

in our experience building TrackRev I would pay on cash, because Stripe has already done the proration arithmetic and a plan-price rule makes you redo it. In your own code the cash figure is amount_paid less the invoice's tax.

To exclude proration, read parent.subscription_item_details.proration on each invoice line.

When does the subscription event matter?

The customer.subscription.updated event is the Stripe webhook that matters when a rule depends on the plan. A percentage of cash needs no plan lookup, but per-plan rates, a tier bonus or a cap such as the first 12 months do.

For example, a 30% rate on the $299 plan and 20% on the $99 plan needs the price ID from this event.

Stripe's subscription webhook guide says customer.subscription.updated also fires for renewals, coupons and added invoice items, so diff previous_attributes against the new object, which per the event object reference holds the whole array when part of one changes.

Node: detect a plan change on customer.subscription.updated
function planChange(event) {  const before = event.data.previous_attributes?.items?.data?.[0]?.price?.id;  const after = event.data.object.items.data[0].price.id;  // renewals and coupons arrive here too, with no price change  return before && before !== after ? { before, after } : null;}

What happens on annual switches, cancellations and failed renewals?

An annual switch in Stripe Billing bills the new price at once. Stripe's price change guide says that when two prices have different billing intervals, the new price bills from the day of the change.

Take a customer on $99 a month who moves to $990 a year on day 10 of 30.

LineAmount
Credit for 20 unused days of the $99 plan-$66.00
Annual plan$990.00
Invoice$924.00
Commission at 20%$184.80

No invoice follows for 12 months, so a rule that pays 12 months of recurring commission is met by that one payment. Count the window in months of service, because counting invoices pays the annual customer 12 times over.

The customer.subscription.deleted event reverses nothing, because only money returned to the customer undoes a paid invoice. A declined renewal needs no hold in Stripe Billing when commissions are created on invoice.paid, and Stripe marks the subscription past_due while it retries.

How do you test these cases with Stripe test clocks?

A Stripe test clock is a sandbox clock that moves a customer through billing cycles in minutes, so the 6 tests below can cover 90 days of invoices on a $99 plan.

Stripe's test clock simulations advance time in a Stripe sandbox by up to 2 billing intervals per step, so a monthly subscription jumps up to 2 months, and the Run simulation button is disabled for a customer attached to more than 3 subscriptions.

TestExpected result
Subscribe at $99 a month through a test affiliate linkOne commission of $19.80
Upgrade to $299 on day 15, once with always_invoice and once with the defaultBoth runs total $79.80 by day 30
Downgrade on day 50 and advance to day 60Commission of $6.47 and no refund event
Refund $110.00, then $220.00, on a $330.00 chargeLedger lands on $0.00
Refund a charge from an earlier cycleCommission reverses even though newer charges exist
Resend a refund event from the Stripe DashboardLedger does not change

How do you keep channel revenue honest after refunds and upgrades?

Honest channel revenue means debiting every refund from the channel that earned the sale.

Debit a refund from the channel that earned the sale, using the model that credited it, whether last-touch, first-touch or linear, so a linear sale splits its refund across the same touches.

Credit an upgrade from the source stored against the customer, because a plan change in the Stripe Customer Portal happens on a Stripe-hosted page you cannot tag, as Stripe Customer Portal attribution explains.

Normalize annual plans to monthly figures first. Moving from $99 a month to $990 a year books $891 of expansion if counted raw, while the monthly equivalent falls from $99 to $82.50.

TrackRev copies the original click's credit onto each renewal, rescaled to the charge amount, so expansion revenue stays with the channel that brought the customer.

How do other affiliate tools handle these events?

Affiliate tools handle refunds in 2 ways: proportional recalculation, as Rewardful does, or a negative commission keyed on an event ID, as FirstPromoter does.

Rewardful's Stripe integration article says it commissions the amount the customer actually paid, after discounts and account balance debits, and recalculates from the refunded amount on full and partial refunds.

FirstPromoter's refund endpoint creates a negative commission, requires an event ID, and answers a repeat with a 409. In our comparison, Rewardful documents proportional recalculation on partial refunds, while TrackRev reverses the whole commission on a refund of any size.

FirstPromoter needs one event ID per refund, so a replay gets an HTTP 409 instead of a second debit. The Rewardful and TrackRev comparison goes further.

Sources: help.rewardful.com/en · docs.firstpromoter.com/api-reference-v2

What does TrackRev do with each case?

TrackRev is SaaS affiliate software for indie hackers and small SaaS teams, and we built TrackRev's Stripe sync to read charges, not subscription events, using a restricted API key.

Our own sync runs hourly, and an optional webhook for charge.succeeded, charge.refunded and checkout.session.completed runs it at once. The Stripe integration page lists the key permissions.

CaseWhat TrackRev does
Paid chargeEach paid charge from a partner link or coupon earns its own commission, renewals included on a recurring program
Commission baseThe charge amount Stripe reports, tax included: 20% of a $330.00 charge is $66.00
Upgrade or downgradeThe commission follows the charge: the $399.00 and $32.33 renewals above earn $79.80 and $6.47 at 20%, with no per-plan rate
First-sale-only programA $299 renewal earns $0.00 after the first sale
Refund of any sizeA $110.00 refund on that $330.00 charge reverses all $66.00, at every multi-tier level
Commission already paidMarked refunded with no negative balance, so subtract it from the next payout
DisputeFollows the refund status Stripe reports, so check the ledger when a dispute ends without a refund
Hold periodPer program, 0 to 180 days, default 0

TrackRev affiliate program behavior with a Stripe connection.

TrackRev reverses the whole commission on any refund and has no per-plan rates, so pick a tool that documents proportional reversal, as Rewardful does, if you need it.

The affiliate program needs a paid plan from Indie at $29 a month, with no cap on the commission it tracks and 0% fees. The Free plan covers link tracking only, with 50 links and 1,000 tracked events a month.

The pricing page, the affiliate program page and affiliate tracking for recurring subscriptions cover the rest.

Found this useful? Share it.

PostLinkedIn

Frequently asked questions

Muzahid Maruf — Founder of TrackRev.io

Written by

Muzahid Maruf

Founder, TrackRev.io & Contant.io

Muzahid Maruf founded TrackRev.io, SaaS affiliate software with no limit on tracked revenue, and Contant.io. He writes about affiliate programs.

Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth

Run your affiliate program without a revenue cap.

Affiliate software for indie hackers and small SaaS teams: $29 a month flat, 0% fees, link tracking and revenue attribution built in. Free to start, no card.