How to test subscription billing changes: proration, downgrades, and the invoice
Upgrades, downgrades, and mid-cycle plan changes are where SaaS billing quietly leaks money. Here's how to test subscription billing changes end to end — proration, plan-limit enforcement, and invoice history — in plain English, no scripts.
How to test subscription billing changes: proration, downgrades, and the invoice
The upgrade button works. You clicked it once, the plan changed, the dashboard showed the new tier, and you moved on. That's the demo, and it's also the last time anyone tested the part of your product that touches money directly. Because the bug in subscription billing isn't "the upgrade failed" — it's the mid-cycle upgrade that charged the full month instead of the prorated difference, the downgrade that took effect immediately instead of at period end and gave away three weeks of the higher tier for free, the plan limit that didn't tighten after someone dropped to the cheaper plan, and the invoice history that shows a customer a charge they can't reconcile.
Subscription billing is a state machine wearing a pricing table. Every plan change has to reconcile three things at once: what the customer sees on screen, what the billing provider actually charges, and what the product lets them do afterward. The happy path exercises none of the seams between them. This is the playbook for testing subscription billing changes end to end, in plain-English prompts you point at staging — and it deliberately doesn't overlap with testing the initial checkout and charge, which is the card going through the first time. This is everything that happens when the plan changes after that.
What "testing a billing change" actually has to cover
Applying a new plan and seeing the tier update is one assertion. The machinery underneath it is where the money is:
- Mid-cycle upgrades and the proration math. When a customer moves to a more expensive plan partway through a period, they should be charged the difference, not a full new period. Stripe's own documentation is blunt about how fiddly this is: "The most complex aspect of changing existing subscriptions are prorations, where the customer is charged a percentage of a subscription's cost to reflect partial use." Their worked example: upgrading a $10/mo plan to $20/mo halfway through a cycle credits $5 of unused time and debits $10 of the new plan, for a net charge of $5. The test is whether the number your customer sees matches that logic — not whether the upgrade "worked."
- Downgrades: immediate vs. at period end. A downgrade is a product decision disguised as a button. Does the cheaper plan take effect right now (and issue a credit for the unused expensive time), or at the end of the current period (so the customer keeps what they paid for)? Both are valid; shipping the wrong one silently is the bug. Stripe distinguishes these explicitly — canceling or changing "at period end" creates no proration, while an immediate change does.
- Plan-limit enforcement after the change. This is the one everyone forgets. A customer on the $20 plan has 10 seats; they downgrade to the $10 plan that allows 3. What happens to seats 4 through 10? The billing changed instantly; did the entitlement follow? A downgrade that bills correctly but leaves the old limits in place is revenue you're giving away, and an upgrade whose new limits don't unlock is a support ticket during the customer's happiest moment.
- The boundary and timing edges. A change made on the last day of a cycle, or the first day right after a renewal, is where proration arithmetic goes strange — the prorated window is a sliver, and rounding, off-by-one-day, and "which period does this belong to" bugs surface here and nowhere else.
- Rapid repeated changes. Upgrade, then downgrade, then upgrade again within a minute. Each change recomputes credits and debits against the current state; do them fast and you're testing whether the system races itself into a wrong balance or a duplicated charge.
- The customer-facing invoice and history. Whatever the provider computed, the customer reads it as a line on an invoice. Does the billing/portal page show the proration credit and debit as separate, legible lines? Does the history reconcile to what was actually charged? "I don't understand this charge" is a churn event, and it starts with an invoice that doesn't explain itself.
Most teams test item 1's happy sibling — click upgrade, tier changes — ship, and meet items 2 through 6 as billing disputes. "A customer downgraded and kept enterprise features for a month" is not a bug you find by clicking upgrade once. It's a bug you find by making the change and then checking what the product does next, every time — which is exactly the discipline that lets an agent catch what a script waved through.
One honest limit before the prompts
A browser agent tests billing changes the way your customer experiences them: it drives the real upgrade/downgrade flow, reads the screen, and watches the network requests your app makes. What it does not do is recompute proration independently — it is not a second billing engine, and it won't tell you "Stripe's math is wrong." It asserts on what your product presents and enforces: the amount shown on the confirmation, the line items on the invoice page, the tier badge, and whether the feature that should now be locked is actually locked. For the underlying arithmetic, preview the proration through your provider — Stripe lets you "preview a proration to view the amount before applying the changes" — and let the agent verify the customer-facing result matches it. That division of labor is the honest version of this test, and it's the one worth automating, because the presentation-and-entitlement layer is exactly where the money leaks and exactly what unit tests on the billing service never see.
Setup: two plans, known limits, a test clock
Billing testing needs a fixture you control: a staging Project pointed at your billing provider's test mode, at least two plans with different feature limits, and a test account you can move between them. Pass the account credentials through your Test Scenario config rather than the prompt body — the same secret-handling pattern the signup-flow playbook uses. Substitute your own URLs, plan names, and limits throughout.
Prompt 1: the mid-cycle upgrade and its prorated charge
The pre-confirmation report in the first paragraph is the highest-value line. A checkout that says "you'll be charged $5 today" and then charges $20 is a specific, screenshot-provable bug — and it's invisible to any test that only checks the final tier. The agent reads the number the customer is promised and the number that lands, so "the confirmation lied" comes back as two separately-recorded facts instead of one green check.
Prompt 2: the downgrade, and what happens to the limits
Step 3 is the test nobody writes. Billing and entitlement are usually two different systems — the payment provider knows the plan changed, but the application has to re-read that and tighten the limits, and the code path for "you're now over your new limit" is one almost no one exercises on purpose. An agent that downgrades an over-limit account and then actively tries to exceed the new limit turns "I think downgrades enforce limits" into "the account kept all 6 projects and let me create a 7th on the Starter plan." That's the same state-must-follow-the-change property that multi-step forms live or die on, applied to money.
Prompt 3: the boundary and the rapid-change race
Step 2 is where race conditions hide. Each change credits the old plan and debits the new one against whatever the current state is; fire them faster than the system settles and you can get a double debit, a credit that references a plan the customer never actually paid for, or a final plan that doesn't match the last click. Stripe's docs even walk through how a credit can be calculated "based on the current price, even though the customer never paid" that rate under some configurations — which is exactly the kind of counterintuitive result you want surfaced in staging, by a test that does the messy thing on purpose, not by a customer doing it by accident.
Prompt 4: the invoice the customer actually reads
This is the check that prevents support tickets rather than bugs. A proration can be arithmetically perfect and still unreadable — one line that says "$4.17" with no explanation is technically correct and completely useless to a customer trying to reconcile a charge. The agent reads the rendered invoice the way a confused customer would and reports whether it explains itself, which is a property no assertion on the raw provider API can capture.
Reading the results
Each run comes back as a Monito Session, not a pass/fail bit: a screenshot timeline of every billing screen, confirmation, and invoice; the network log with the actual requests your app fired to change the subscription and their responses; console output; and the agent's reasoning wherever it judged whether a total reconciled or a limit held. For billing specifically, the two documents to read are the confirmation-vs-invoice screenshots — because the classic bug is a promised amount that doesn't match the charged one — and the network log, which shows whether the plan-change request even carried the parameters you expected. The network events are captured with their method, URL, and status, so an upgrade that silently 500'd on the entitlement update while the UI showed success comes back as the failure it actually was.
None of the prompts names a button ID, a plan-card selector, or a DOM path — only the rule being tested. Billing pages get redesigned constantly; a test pinned to today's markup rots on the first restyle, while a prompt that describes "upgrade mid-cycle and check the prorated charge" survives it. That durability is the whole argument for describing intent over recording clicks.
The one to run right now
If you run a single check today, run the downgrade-and-limits test — it catches the two most expensive billing-change bugs at once: a downgrade that doesn't credit correctly, and entitlements that don't follow the plan down.
Save it as a Test Scenario, pin it to your staging Project, and wire it into CI against every deploy that touches billing, plans, or entitlements — the surfaces where a change breaks quietly and expensively. A full run is typically 8–13 credits — roughly $0.08–$0.13 — and your first run is free. Point it at test-mode billing, run the downgrade, and try to break your own limit. If the account keeps its enterprise features on the starter plan, you'd rather learn it from an agent than from your revenue report.