experiment · shipped
Coupon Application Engine
A framework-agnostic coupon auto-apply engine, built as a plain TypeScript state machine
the constraint
Coupon auto-application has to run inside browser-extension content scripts on arbitrary store checkouts, so the engine can’t assume Vue, React, or any other reactivity layer. Whatever we built had to work in a content script, in a popup, and in whatever UI framework the extension around it happened to use. The engine is plain TypeScript that works in any JavaScript browser environment
what we built
The base class extends EventEmitter and uses its patterns for everything observable. Application runs as a state machine: a coupon run moves from validating and waking through starting, processing and restoring, to a terminal success or failure, with pause and stop available throughout
- unavailable
- stop
- pause
- init
- validating
- waking
- available
- created
- starting
- process
- restoring
- finishing
- fail
- success
Fourteen statuses cover the whole lifecycle, which sounds excessive until a run gets interrupted halfway through a checkout that has just reloaded itself
reading a checkout we don't control
This is the interesting part. Checkout pages expose totals and coupon fields in wildly different ways, and no selector strategy survives contact with more than a handful of stores. So the engine doesn’t hardcode any of it: values are read through config-driven data retrievers
- checkout page
- retriever
- engine
Adding a store then means writing configuration rather than writing code, and a store that changes its checkout is a configuration fix rather than a release. That decision is what made per-store coverage a data problem instead of an engineering one
the public API
Consumers subscribe to engine events and read plain properties off the instance:
- on(‘UPDATE:status’)
- subscribe to lifecycle changes
- cachedCoupons
- the applied coupons
- total
- purchase cost after all discounts
- result
- best coupon and savings
- status
- where the run currently is
- processIndex
- index of the coupon being tried, typically rendered as a progress bar
what survived
The engine shipped as part of our extension systems and is still the basis of the coupon integration module in Coupon Genius. It has run in more than one of our extension projects, and Reactive Coupon Integration is the Vue layer built on top of it — the cost of keeping the engine framework-free, paid once, in a wrapper instead of in the engine
The engine’s lineage goes back to a coupon auto-applier we built for a client in 2019 — per-store config files even then
It is not the newest layer of our extension tooling any more; the things we build now abstract this differently. That is the normal shape of infrastructure that worked: it stopped being the interesting part and became the part underneath