How to get Stripe revenue for a date range with the Balance Transactions API
Part of Stripe MRR on your Mac, without opening the dashboard
Which Stripe endpoint gives revenue for a date range?
Balance transactions, not charges. Most people reach for
/v1/charges because it is the obvious one, and it is wrong in two ways that
only show up once real money is moving.
A charge is an intent that succeeded. A balance transaction is money that actually arrived in your Stripe balance, and it is where fees, refunds, disputes, payouts and adjustments all live. If you sum charges you will miss every refund and every dispute, and you will have no idea what Stripe took.
The call
async function revenue(stripe, from, to) {
let gross = 0;
let fees = 0;
for await (const txn of stripe.balanceTransactions.list({
created: { gte: Math.floor(from / 1000), lt: Math.floor(to / 1000) },
type: "charge",
limit: 100,
})) {
gross += txn.amount;
fees += txn.fee;
}
return { gross, fees, net: gross - fees };
}
created takes Unix seconds, not milliseconds, and passing milliseconds
returns an empty list rather than an error. That is thirty minutes of your
life if you have not met it before.
The types you have to choose between
type is the parameter that decides what your number means.
| Type | What it is |
|---|---|
charge |
A successful payment |
payment |
The same thing, on accounts using certain payment methods |
refund |
Money returned, as a negative amount |
adjustment |
A dispute, usually, also negative |
payout |
Money leaving for your bank. Not revenue |
stripe_fee |
Monthly billing fees, separate from per-charge fees |
Omitting type entirely sums all of them, which nets payouts against income
and produces a figure close to zero. That is a surprisingly common bug.
Note that charge and payment are different types for historical reasons
and which one your account produces depends on how the payment was taken. An
account taking both and filtering on only charge undercounts. Either list
both types or omit the filter and bucket by type yourself.
Gross, net, and which one to report
A balance transaction carries amount and fee. Gross is the sum of
amount. Net is gross minus fees, and it is the number that arrives in your
bank.
Report gross, and report refunds separately. A single net figure hides the thing you need to see: a month that takes fifteen thousand and refunds three is a different business from one that takes twelve and refunds none, and netting them calls the two identical.
Currency, and the mistake that silently corrupts totals
Every amount is in minor units of txn.currency. Not dollars, cents. Not
yen-with-decimals, yen. Zero-decimal currencies like JPY and KRW have no
minor unit at all, so 1000 means 1000 yen and dividing by 100 is wrong.
Never hold money in a float. 0.1 + 0.2 is not 0.3 and a year of
summing cents into a double drifts. Integers and a currency code, always.
An account taking several currencies has several balances, and summing them without converting produces a number that means nothing.
Timezones
created is UTC. Stripe's own dashboard reports in the timezone set on your
account, so a daily figure computed in UTC will disagree with the dashboard
for every charge near midnight.
Pick one and write it down. Disagreeing with the dashboard is fine. Not knowing why you disagree is not.
Or let the notch do it
Notch Metrics reads balance transactions and reports Stripe revenue in the MacBook notch, bucketed hourly, daily, weekly or monthly, with a sparkline and a comparison against the previous window. Gross, before Stripe fees, with refunds reported separately rather than netted off.
Connect Stripe with a read-only restricted key. What the revenue widget counts, or everything else Stripe reports.
- stripe
- revenue
- api