Revenue Page
Invoices tell you what you billed. Orders tell you what was bought. Neither tells you what actually landed in the bank this month, or how much of it will land again next month. The Revenue page does, from one screen, without a spreadsheet.
What It Answers
Four questions, in the order most owners ask them:
- How much came in? Collected revenue for the period, split by where it came from — invoices, one-time orders, subscription renewals — with refunds shown against it and a comparison to the previous period.
- What is still owed? Outstanding and overdue invoice balances, an aging breakdown, and your largest open invoices.
- What is recurring? Monthly and annual recurring revenue, the subscriptions renewing in the next 30 days, and any that are cancelled or ending.
- Who pays you? Revenue by client and by product for the period.
Underneath is a list of the most recent payments, a CSV export, and a button that pulls your payment history from Stripe.
New in v4.108.0: The Revenue page, the payment ledger behind it, and the Stripe backfill shipped together. Nothing appears until you turn the page on (below).
Turning It On
The page is part of the Invoices feature and needs a page of its own in the portal:
- In wp-admin, go to Pages → Add New, title it "Revenue", choose the Revenue template, and publish it
- In the portal, open Settings → Features, find the Revenue card under Billing & Invoices, choose that page in the Revenue Page dropdown, and save
- Add the page to your staff menu the same way you added Invoices
New installs get the page and a menu entry from the setup wizard. Existing installs make it once, as above. The page only works while Invoices is enabled.
Choosing a Period
The period picker sits in the page toolbar, top right, next to Export CSV. The resolved period reads beside the page title, for example "Revenue · Last 12 months".
| Option | Covers |
|---|---|
| This month | The first of the month to today |
| Last month | The previous calendar month |
| This quarter | The current calendar quarter |
| Year to date | January 1 to today |
| Last 12 months | The trailing twelve calendar months, including this one |
| Custom | Any From and To dates; both are required |
Every panel re-queries when the period changes. The "vs prior period" comparison on the Collected figure uses the same length of time immediately before the period — last month against the month before it, a custom ten-day range against the ten days before it.
If the portal has recorded payments in more than one currency, a Currency picker appears beside the period. The page never adds different currencies together; you look at one at a time.
Reading the Page
The top strip. Four figures for the period:
- Collected — everything that came in, refunds subtracted, with the change against the prior period
- Outstanding — the balance of every invoice that is sent, viewed, overdue or partially paid
- Overdue — the part of Outstanding that is past its due date, shown in red when it is not zero
- MRR — monthly recurring revenue from active subscriptions, with the active count and the annual figure (ARR) beneath it
Collected over time. A stacked bar for each month — at least twelve, so a short period still has context — split into invoices, orders and subscription renewals, with refunds as a negative series.
Receivables aging. A donut of open invoice balances by how late they are: current, 1–30, 31–60, 61–90 and 90+ days past due. An invoice with no due date counts as current. Beside it, Largest open invoices lists the ten biggest, each linking to the invoice, with the days overdue in red.
Recurring. MRR, ARR and the active subscription count, then two lists: Renewing in the next 30 days and Cancelled / ending. Each row links to the client. MRR normalises every plan to a monthly figure — a quarterly plan counts a third of its price, a yearly plan a twelfth — and includes subscriptions that are active, in trial, or past due.
Revenue by client and Revenue by product. The top ten in each, with everything else folded into "Other". Clicking a client's bar opens that client. Payments that could not be tied to a client show as "Unassigned"; invoice payments with no product show as "Invoiced services".
Recent payments. Twenty per page: date, source, client, a link to the invoice or order, the payment method, and the amount. Refunds are red and negative.
The Payment Ledger
Everything on the page is read from one table of payments the portal keeps for itself. A row is written when:
- An invoice is paid through the portal's Stripe checkout, or marked paid by hand (with whatever method you chose — check, ACH, cash and so on)
- A one-time order completes
- Stripe reports a subscription renewal (
invoice.paid) - Stripe reports a refund (
refund.created/refund.updated)
Those last two are the point. Until now, renewals were never stored anywhere in the portal — Stripe charged the card and the portal only re-granted access — so anything built from invoices and orders alone counted a subscription's first payment and missed every renewal after it. The ledger records them as they happen.
The ledger is for reporting. The invoice or order itself is still what decides whether something is paid; if a ledger row ever fails to write, the payment goes through anyway and the failure is logged.
Backfilling From Stripe
The ledger fills in on its own from the moment you update. To include what happened before, pull the history from Stripe:
- On the Revenue page, click Backfill from Stripe at the bottom of Recent payments (the empty page shows the same button front and centre)
- Watch the progress line — it reports each phase as it runs
- When it finishes, every panel reloads
The backfill reads your paid invoices and completed orders from the portal, then paid invoices, successful payments and refunds from Stripe. It only reads; nothing is written to Stripe. Running it again is safe — a payment it has already recorded is skipped, so the totals never double. If Stripe is unreachable part-way through, the button turns into Resume backfill and picks up where it stopped.
Operators can run the same thing from the command line with wp cove-ledger backfill; it accepts --cursor to resume and exits non-zero if any rows were rejected.
Exporting to CSV
Export CSV downloads the ledger for the selected period and currency, one row per payment or refund: date, source, kind, client, product, amount, currency, method, Stripe reference, the invoice number or order, and any note. Amounts are in dollars with two decimals; refunds are negative. The file is UTF-8 with a byte-order mark, so it opens cleanly in Excel and Numbers.
Text that begins with a character a spreadsheet would treat as a formula (=, +, -, @) is prefixed with an apostrophe so a client name can't run as a calculation.
Who Can See It
The page is staff-only. A user needs both View invoices and Manage invoices; a client user who has been given billing access can see their own invoices and orders, but never the agency-wide Revenue page.
Two of the buttons have their own permissions:
- Export CSV needs Export billing data (administrators and system admins by default)
- Backfill from Stripe needs Manage system settings — it talks to your Stripe account
Notes & Gotchas
Stripe webhooks. Renewals arrive through the invoice.paid event, which most installs already send. Refunds need refund.created and refund.updated added to your webhook endpoint's event list in the Stripe dashboard; without them refunds never reach the ledger.
Payments land in the month of their payment date. If an invoice payment was recorded with the wrong date, correct it in the invoice editor and the payment moves to the right month. See Correcting a Payment.
Refunds land in the month they were refunded, not the month of the original payment — the way an accountant would see the cash.
A subscription's first payment is counted once, even though Stripe reports it twice (as the order and as the subscription's first invoice). Whichever arrives first is kept and the other joins it.
- The MRR figure comes from the portal's own subscription records, which update as Stripe sends subscription events. If a subscription was created before the portal started recording plans, its amount may show as zero until its next renewal.
- Figures are cached for a minute; a payment or an invoice status change refreshes them immediately.
- The Revenue page and the Orders list can disagree by design: Orders sums what was ordered, including manual orders that have not been paid through the portal; Revenue sums what was collected.