Back to BlogIndustry Guides

Building a Subscription and Recurring-Order E-Commerce Platform

Rupak Amin

Founder & Lead Engineer, RAITHub

7 min read

A subscription e-commerce platform charges the same customer on a schedule, so the hard part is not the first sale but every renewal: proration on plan changes, pausing and skipping a delivery, retrying a failed payment, and cancelling cleanly. Build it around a subscription state machine and a billing engine driven by idempotent webhooks, and test the renewal, failed-payment and cancellation paths as carefully as the first checkout.

If you would rather have it built and tested for you, see how RAITHub would build this below. First, the model and the test plan.

Why is a subscription store harder than a normal store?

A normal store takes a payment once. A subscription store commits to charging again and again, each time with a card that may now be expired, a plan that may have changed mid-cycle, and a customer who may have paused, skipped or cancelled since the last charge. The revenue is recurring, so a small bug recurs too: a proration that is a few cents off, or a failed-payment retry that gives up too soon, compounds every month across every subscriber. The billing-model choices behind this are in SaaS billing models explained and usage-based billing.

What is the data model?

Model the subscription as a state machine, not a boolean "active" flag. The states and the events that move between them are what you will test.

StateMeansMoves to
trialingIn a free trial, no charge yetactive, cancelled
activePaid and charging on schedulepaused, past_due, cancelled
pausedDeliveries and billing on holdactive, cancelled
past_dueA charge failed; retries are runningactive (recovered), cancelled (exhausted)
cancelledEnded; no further charges— (terminal)

Store money as integers, record every charge and refund in an append-only ledger, and keep an audit log of state changes (designing a SaaS audit log). The ledger is what lets you answer "why was this customer charged this amount" months later.

How should the billing engine work?

Drive it from signed, idempotent webhooks, not from a timer that charges cards directly. When a renewal succeeds, fails or is retried, the provider sends an event; your handler moves the subscription's state and records the ledger entry. The handler must be safe to run twice, because a provider can deliver the same event more than once. A webhook returning HTTP 200 does not prove the work happened, which is a common silent failure (when a webhook returns 200 but the subscription is not updated).

// src/billing/apply-event.ts  (idempotent by event id)
export async function applyEvent(event) {
  return db.transaction(async (tx) => {
    const seen = await tx.processedEvent.findUnique({ where: { id: event.id } })
    if (seen) return // already applied; safe to replay
    await tx.processedEvent.create({ data: { id: event.id } })
    switch (event.type) {
      case 'invoice.paid':       return markActive(tx, event)
      case 'invoice.payment_failed': return startDunning(tx, event)
      case 'customer.subscription.deleted': return markCancelled(tx, event)
    }
  })
}

Stripe's API supports idempotency keys so a retried request returns the original result instead of charging again (Stripe API, idempotent requests), and Stripe's Smart Retries and dunning settings govern how failed payments are retried before a subscription is cancelled (Stripe, Smart Retries).

What does the test plan cover?

Shape it like any SaaS (the test pyramid for a SaaS), weighted to the recurring paths:

  • Unit: proration on upgrade and downgrade mid-cycle, trial end, tax, currency rounding, the state-machine transitions.
  • API: each webhook applied once even when replayed; pause, resume and skip; a failed charge starting dunning and a recovered charge ending it; a cancellation stopping all further charges.
  • End-to-end: subscribe with a test card, change plan, pause and resume, and cancel.
// tests/api/dunning.spec.ts
import { test, expect } from '@playwright/test'
import { signedEvent, subState } from './helpers'

test('a failed charge moves to past_due, a later success recovers', async ({ request }) => {
  await request.post('/api/webhooks/stripe', signedEvent('invoice.payment_failed', 'sub_1'))
  expect(await subState('sub_1')).toBe('past_due')
  await request.post('/api/webhooks/stripe', signedEvent('invoice.paid', 'sub_1'))
  expect(await subState('sub_1')).toBe('active')
})

Buy, build or hire?

OptionWhat you getChoose this when
A subscriptions app on a store platformRecurring billing, pauses and dunning handledA standard product on a mainstream platform and the app's rules fit yours
A billing provider's subscription productRenewals, retries and proration from the providerYou build the store but let the provider own billing logic
A custom subscription platformFull control of the model, pauses, skips and dunningYour schedule, bundling or local payment rules are the business
A managed QA team on your buildRenewal, dunning and cancellation suites gated in CIYou have the platform but the recurring paths are untested

How long does a first version take?

A focused first release with one billing cycle, trials, pause and cancel typically takes 4 to 6 weeks at a fixed scope. Proration on plan changes, dunning tuning, skips and gifting are backend-heavy and sit in the 6 to 12-week range. The main risk of building it yourself is testing only the subscribe-and-renew happy path, while dunning, proration and replayed webhooks, where recurring revenue actually leaks, stay untested.

Why RAITHub for this

RAITHub builds Stripe billing with idempotent webhooks as part of its SaaS development service, and the recurring-money discipline is proven: PropDesk collects rent through Stripe with 1,024 tests, and Sundor Skin enforces credit limits and tier pricing in the database with 530+ tests. For the subscription-store angle specifically, see adding Stripe billing to a SaaS and the eCommerce industry page.

When you don't need us

  • You sell a standard product on a mainstream platform. A subscriptions app is cheaper than a custom build.
  • Your billing is a provider's standard subscription product, untouched. The provider tests the renewal logic; test your store around it.
  • You need a certified PCI or SOC 2 attestation. RAITHub is not certified and issues no attestation.

How RAITHub would build this

  • Scope: a subscription state machine, a ledger-backed billing engine driven by idempotent webhooks, pause/skip/resume, dunning on failed payments, and server-authoritative price and stock on every renewal.
  • Timeline: a first release in 4 to 6 weeks at fixed scope; proration, dunning tuning and gifting in the 6 to 12-week range, in phases.
  • What you receive: the renewal, dunning and cancellation test suites gated in CI, an append-only billing ledger and audit log, runbooks, IP assigned to you and an NDA as standard.
  • Ways to buy it: a fixed-scope first release, then a dedicated monthly team, or a QA plan if you only need the recurring-path test suite.
  • Next step: a free 15-minute technical audit, then a fixed written quote.

See the QA as a Service page, or book the free 15-minute audit.

Documentation checked on 10 October 2026.

Frequently asked questions

What is a subscription e-commerce platform?

A store that charges the same customer on a schedule for recurring deliveries or access, rather than once per order. The engineering centres on renewals, proration, pausing and skipping, failed-payment dunning and clean cancellation, not just the first checkout.

How do you handle failed subscription payments?

Move the subscription to a past_due state and run retries (dunning) before cancelling, driven by signed webhook events your handler applies idempotently. A recovered charge returns it to active; an exhausted retry schedule cancels it. Test both the recovery and the give-up paths.

Should I build subscriptions or use a billing provider?

Use a provider's subscription product when your rules are standard; it tests the renewal logic for you. Build custom when your schedule, bundling, credit or local payment rules are central to the business and an off-the-shelf app cannot express them.

How do you test recurring billing?

Unit-test proration and the state machine, API-test that each webhook applies exactly once and that failed charges start dunning and recovered charges end it, and run end-to-end tests for subscribe, change plan, pause, resume and cancel with a test card.

How long does it take to build one?

A first release with one cycle, trials, pause and cancel typically takes 4 to 6 weeks at a fixed scope. Proration, dunning tuning, skips and gifting are backend-heavy and sit in the 6 to 12-week range, delivered in phases.

subscription ecommercerecurring order platformsubscription billingdunning testingstripe subscriptionsrecurring revenue

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.