SaaS

Billing migration for a B2B subscription product

Built by Rafael Bautista

Moved a hand-rolled billing system onto a proper provider without changing a single customer's invoice total.

  • Builder email verified

    Verification

Type
Professional
Category
SaaS
Published
Jul 25, 2026
Updated
Jul 25, 2026
Built withNode.jsTypeScriptPrismaPostgreSQLStripe
TopicsFintechProductivity

About this project

The problem

The product had grown its own billing code over six years — proration, annual plans, seat changes mid-cycle, a dozen grandfathered discounts — and it was wrong often enough that support had a standing process for issuing credits. Every new pricing idea from the commercial team took weeks because the logic was tangled through the application. The company wanted to move to a billing provider, but the hard requirement was that no existing customer's invoice could change by a single peso, including the ones on deals nobody could find paperwork for.

My role

Backend engineer and de facto migration owner. I was not the tech lead, but I volunteered for the discovery work and ended up owning the plan, which is how these things usually go.

What I owned

I owned the reconstruction of the existing pricing rules — reading six years of billing code and, where the code disagreed with itself, deciding which branch was the real one. I built the shadow-billing harness that ran the new provider's calculation against every active subscription nightly and diffed the totals against what we actually charged. I wrote the migration itself, in cohorts, and the rollback path for each cohort. The grandfathered plan mapping was mine too, which meant a lot of conversations with the account managers who remembered the deals.

Technical & product decisions

I refused to migrate anyone until the shadow run had been clean for thirty consecutive days for their cohort, which pushed the timeline out by about two months and was the single decision I would defend hardest. We also chose to model grandfathered discounts as explicit coupon objects rather than custom price points, because a coupon is visible on the invoice and a custom price is not — support can now explain a bill without opening a database. I kept our own subscription table as the source of truth for entitlements and treated the provider as the billing engine only, rather than letting it own access control; that separation cost some duplication and meant an outage there never locks a customer out of the product.

Constraints

Zero tolerance on invoice differences, which drove the whole schedule. We also could not pause the commercial team — they kept selling on new terms throughout, so the mapping was a moving target. There was no staging environment with realistic data, so the shadow harness ran in production against real subscriptions and wrote nowhere, which took some convincing to get approved.

Result & impact

We migrated about 1,400 active subscriptions in nine cohorts over ten weeks with no invoice discrepancies and no rollbacks. Billing-related support tickets fell by roughly seventy percent in the quarter after the last cohort, mostly because proration stopped being wrong. The commercial team shipped two new plan structures in the following quarter without any backend work, which had previously been a multi-week ask. The shadow harness is still running as a regression check.

Who else worked on it

Two account managers reconstructed the grandfathered deals from memory and old email, which was genuinely the hardest input to get. Our finance controller signed off each cohort's diff before we moved it. A frontend engineer rebuilt the billing settings screens against the new model.