Case Study

Subscription media platform: from manual signups to automatic delivery

Every new subscriber used to mean manual work. Confirm payment by hand, create the account by hand, send access details by hand, remember to chase the renewal. We automated the whole chain.

Media catalogue with subscription checkout panel showing payment options

Public catalogue with the subscription checkout panel.

Payment rails
Stripe and FPX
Fulfilment
Automatic
Status
Live in production

The problem

The business worked, but only because someone was manually holding it together:

The bottleneck was not traffic. It was that every single subscriber required a human to finish the job.

What we built

Catalogue and checkout

Fulfilment chain

Both payment rails converge on the same fulfilment path. Once payment is confirmed, by webhook or by the payment poller, the system provisions access, records the expiry date, and notifies the customer automatically. Nobody touches it.

Customer side

Operations side

The parts that were actually hard

Two payment rails, one truth. Stripe confirms instantly by webhook. Bank transfers confirm minutes or hours later, and sometimes not at all. The system had to treat a single order as potentially pending across two different confirmation mechanisms without ever double provisioning. The fix was an idempotent fulfilment step keyed on the order reference: it does not matter which rail fires first, or whether both fire, the customer is provisioned exactly once.

Expiry without a cron dependency. Access has to end on a date. Rather than trusting a scheduled job to run, access checks compare the current time against the stored expiry on every request. Renewal extends the expiry. This means the system stays correct even if a scheduled task is late or missed, which it will be eventually.

Notifications that actually arrive. Customers were paying and then waiting for a message that silently failed. Delivery is now retried, and failures are logged rather than swallowed. A payment that succeeds but never gets delivered is the worst possible outcome, so it gets the most defensive code.

Architecture choices

Cloudflare Pages for the front end, Workers for the API and webhooks, KV for state. The same reasoning as the ordering system: no server to patch, no database to administer, and a hosting cost that rounds to zero at this scale. Payment secrets live as Worker environment bindings, never in the client bundle.

The public pages carry a strict content security policy. Card data never touches our code at any point, Stripe hosts the payment fields directly, which keeps the compliance scope small.

What I would do differently

The customer portal started as a read only status page and grew features over time. If I rebuilt it, I would design the portal as the primary interface from day one and treat the status page as a lightweight view of it, rather than maintaining two surfaces that show overlapping information.

Client and brand are not named here. The platform is described by capability, not by product.