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.

playbookbillingsaassubscriptionsno-codeqa
monito

How to test subscription billing changes: proration, downgrades, and the invoice

playbookbillingsaassubscriptions
September 4, 2026

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:

  1. 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."
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

Go to https://staging.yourapp.com and log in as
billing-test@example.com / Password123!

This account is on the "Starter" plan ($10/mo). Upgrade it to the
"Pro" plan ($20/mo) from the billing/plan settings page.

Before confirming, report exactly what the app tells the customer they
will be charged now. Then confirm the upgrade and report:
1. The amount actually charged or shown as due (quote the number).
2. Whether the charge looks like a PRORATED difference for the
   remainder of this billing period, or a full new period's price.
3. Whether the plan/tier indicator now shows "Pro" everywhere it
   should (settings, header, any usage meter).

Flag it if the confirmation screen and the resulting invoice/charge
disagree, if a mid-cycle upgrade billed a full $20 instead of a
prorated amount, or if the tier updated in one place but not another.
Capture the billing screen, the invoice/receipt, and all console and
network activity.

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

Go to https://staging.yourapp.com and log in as
billing-test@example.com / Password123! (currently on the "Pro" plan,
which allows up to 10 projects; the "Starter" plan allows 3).

Before downgrading, ensure the account is using MORE than the Starter
limit — e.g. it has 6 projects.

Now downgrade from "Pro" to "Starter" and report:
1. Whether the downgrade takes effect IMMEDIATELY or at the end of the
   current billing period — quote whatever the app tells the customer.
2. Whether a credit or refund for unused Pro time is mentioned, and
   what the next invoice is shown as.
3. After the downgrade is in effect, whether the account is still able
   to use its 6 projects, is blocked from creating a 4th, is asked to
   remove projects down to 3, or silently keeps all 6.

Flag any case where billing changed but the plan LIMITS did not follow,
where an immediate downgrade gave no credit, or where the app's
description of when the change takes effect doesn't match what actually
happens. Capture the billing screen and the projects/usage screen.

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

On https://staging.yourapp.com, logged in as
billing-test@example.com / Password123!, stress the timing edges of
plan changes.

1. If your test billing clock lets you, set the account near the END of
   its current billing period (e.g. one day before renewal). Perform a
   mid-cycle upgrade and report the prorated amount — a tiny remaining
   window should produce a small proration, not a full period.
2. Regardless of the clock, perform three plan changes in quick
   succession with no deliberate pause: upgrade to Pro, downgrade to
   Starter, upgrade to Pro again.
3. After the rapid sequence settles, report the final plan, the final
   set of charges/credits shown on the invoice history, and whether the
   totals reconcile or whether any charge appears duplicated, orphaned,
   or contradictory.

Flag any last-day proration that bills a full period, any duplicated or
missing charge after the rapid sequence, and any final state where the
displayed plan and the billing history disagree. Capture the invoice
history and all network requests.

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

On https://staging.yourapp.com, logged in as
billing-test@example.com / Password123!, verify the customer-facing
billing history is legible and reconciles.

After performing a mid-cycle upgrade, open the billing/invoices/receipts
page the customer would see and check:
1. The proration appears as understandable line items — a credit for
   unused time on the old plan and a charge for the new plan — not a
   single unexplained number.
2. Each amount shown reconciles: the credits and debits add up to the
   total that was actually charged.
3. Dates and plan names on the invoice match the change that was made
   (correct old plan, correct new plan, correct effective date).

Report any invoice line that is missing, unlabeled, or that doesn't add
up to the charged total, and any mismatch between the invoice and the
plan change performed. Quote the line items and the total. Capture the
invoice page.

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.

Go to https://staging.yourapp.com and log in as
billing-test@example.com / Password123!, currently on the "Pro" plan
(allows 10 projects) with 6 projects in use. The "Starter" plan allows
3 projects.

Downgrade from Pro to Starter. Report whether the change takes effect
immediately or at period end and whatever credit or next-invoice amount
the app shows. Then, once the downgrade is in effect, try to create a
new project. Confirm the app enforces the Starter limit — it should
block creation beyond 3, or clearly prompt the customer to reduce usage,
not silently allow all 6+ to continue. Flag any case where billing
changed but the plan limits did not. Capture the billing screen, the
projects screen, and all console and network errors.

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.

All Posts