Skip to content

← Work

Reset

Reset is a subscription product with paying members, and it needed to be rebuilt across the stack. The part that made it difficult was not the new build — it was that the rebuild reached into the data structures behind users and their Stripe subscriptions, which are the structures that decide whether someone who is already being charged still has what they paid for.

I led the full-stack rebuild and the live migration that came with it. Continuity for active subscribers was the constraint the work was organized around, not a detail handled at the end of it.

At a glance

  • Role: Led the full-stack rebuild and the live subscription migration; owned the Stripe integration and the user and subscription data structures.
  • Stage: Live subscription product, rebuilt and migrated while subscribers were active.
  • Core constraint: Rebuild the data structures behind users and Stripe subscriptions without interrupting active billing.
  • Strongest evidence: A live migration that kept existing Stripe subscriptions in place, server-side billing operations, and one user identity shared across RevenueCat, Klaviyo, and AppsFlyer.

Context

A rebuild is a straightforward piece of engineering when nothing is live. Reset was live. Subscriptions were mid-cycle and Stripe would keep charging on its own schedule regardless of what the product was doing with its own data model, so the migration had to fit around active billing rather than pause it.

That made the failure mode specific rather than general. The risk was never a broken page. It was a member still being charged who can no longer reach the thing they are being charged for, which does not raise an error anywhere — it arrives as a support message. The constraints followed from that:

  • Active subscriptions had to remain the same subscriptions, not replacements created in a new shape.
  • Payment-method management had to keep working against those subscriptions on the other side of the move.
  • Revenue, lifecycle, and attribution systems all resolved against user identity, so identity could not be reinvented in isolation from them.

My role

I led the rebuild across the stack: the React web application, the Firebase authentication and data layer, and the Node and Express REST API behind them. I owned the technical direction of the migration itself, including the decision to treat subscriber continuity as the requirement the rebuild had to satisfy rather than a cutover problem.

The subscription and billing side was mine specifically — the Stripe integration for subscriptions and payment-method management, and the rebuild of the user and subscription data structures that the migration had to carry. The revenue, lifecycle, and attribution integrations were built against that same identity.

Architecture

React carries the web application, with Chakra UI and Styled Components for the interface. Firebase holds authentication, user data, and storage. A Node and Express REST API on Cloud Functions handles the server-side work: subscription operations against Stripe, the events that follow them, and the integrations that listen for those events.

Stripe stays the billing authority. The rebuild changed how the product describes a subscription; it did not change the agreement Stripe is charging against. Subscription state and payment methods are operated against Stripe rather than mirrored into the product's own store as a second source of truth. That distinction matters more than usual during a migration, because a copied billing state is a thing that can quietly drift while the subscription it describes is still live.

Identity is what everything else hangs from. RevenueCat revenue events, Klaviyo lifecycle email, and AppsFlyer routing and attribution all resolve against the same user. That left user identity as the part of the rebuild with the least freedom: the structures around it could change, but the person they pointed at had to stay the same person across billing, lifecycle, and analytics.

Stack

Web — React · Chakra UI · Styled Components

Backend and data — Node · Express REST API · Firebase Cloud Functions · Firebase Authentication, data, and storage

Billing — Stripe · RevenueCat

Lifecycle and attribution — Klaviyo · AppsFlyer

Engineering challenges

Rebuilding the data behind subscriptions that were still billing

The problem. The rebuild required new structures for users and for the Stripe subscriptions attached to them. Those subscriptions were live. People were being charged on them while the work was underway, and they expected their access to be uninterrupted by it.

Why it was hard. A subscription is not a row you can rewrite at your convenience. It is an ongoing agreement held in an external system on its own schedule, joined to a user record on yours, and changing the representation of that join puts every active member briefly dependent on two descriptions of the same relationship agreeing. Nothing throws when a subscription lands against the wrong user or against no user at all. It simply becomes a person paying for something they cannot open.

The decision. Organize the rebuild around the billing relationships that already existed rather than around the new model, and keep Stripe as the authority throughout so the migration moved the product's representation of a subscription instead of the subscription itself.

Implementation. The migration ran against live accounts. User records and the Stripe subscription structures attached to them were rebuilt into the new model together, with the subscriptions themselves left in place at Stripe rather than canceled and recreated in the new shape.

Outcome. Active subscribers kept their subscriptions through the rebuild. The billing relationships people were already paying on carried into the new structures instead of being rebuilt around them.

Payment methods that had to work on the other side

The problem. Updating a card is the most routine thing a subscriber does and the first thing they do when a payment fails. It had to keep working from the rebuilt structures, against subscriptions that were created before those structures existed.

Why it was hard. Payment-method management reaches straight into billing. It is not a product-side update; it changes what Stripe will charge at the next renewal. If a rebuilt user record resolves to the wrong Stripe customer, or to none, the customer's card change either does nothing or applies to a subscription that is not theirs, and both of those look completely normal in the interface.

The decision. Keep card data and subscription state out of the product's own store. The application holds the reference to the Stripe customer and their subscription; every subscription and payment-method operation is performed against Stripe and read back from it.

Implementation. Subscription and payment-method operations run server-side through the Express API rather than from the client, so the server resolves a signed-in user to their Stripe customer before any billing operation happens. RevenueCat revenue events and Klaviyo lifecycle email hang off that same resolved identity, so a subscription change is reflected the same way everywhere downstream of it.

Outcome. Payment-method and subscription management operate against the same Stripe relationships the migration preserved, with no second copy of billing state inside the product that could disagree with them.

Outcome

The rebuild was completed and the migration ran against live accounts, with active subscribers keeping their subscriptions through it. The user and subscription structures the product depends on were genuinely rebuilt in the process rather than carried forward untouched to avoid the risk.

What made that possible was where authority was allowed to sit. Stripe held the subscriptions and the rebuild changed only the product's description of them. Firebase held identity, and the revenue, lifecycle, and attribution systems resolved against it instead of maintaining their own idea of who a subscriber was.

The measure of work like this is that the people on the other side of it do not have to do anything. Members kept the subscriptions they were already paying for, on a platform that had been rebuilt underneath them.