BenefEx
A discounts marketplace serving corporate employees and ordinary consumers from one merchant network, where the same offer is visible, restricted or absent depending on who is looking at it.
Context
BenefEx is a discounts and benefits marketplace connecting users, merchants and offers. It serves two audiences on one merchant network: corporate employees, whose employer grants them exclusive access to selected offers, and individual consumers, who reach offers independently of any corporate programme. Merchants supply the offers, discounts, promotions, services and products the marketplace carries, and honour them at their own counters. Customers work from a Flutter application, and the platform is run from a React administration dashboard. Around the transaction sit the mechanics that make people keep the application installed: a running total of what they have saved, transaction history, points, rewards and gift vouchers.
The problem
A deals marketplace is two-sided and neither side arrives first. Merchants fund discounts to bring in customers who would not otherwise have come, users show up only if the offers are worth having, and each is waiting on the other. Carrying corporate employees and individual consumers on one catalogue then adds a dimension a single-audience platform never faces: an employer grants its staff access to offers nobody else sees, so the same catalogue has to render differently for every viewer, and every query against it becomes a question about who is asking. Redemption is where platforms of this kind actually break. A discount is real only when a merchant honours it, the thing being presented at the counter is a code on a screen, and whatever makes that code trustworthy has to survive both a queue and a screenshot.
The solution
One marketplace, an entitlement check on the path to the catalogue rather than bolted on beside it, and a redemption that puts the merchant in the verifying seat. Customers discover merchants and offers through categories, promotions and deals, and at the counter they present a code which the merchant scans to complete the redemption: the customer holds the claim, the merchant proves it, which is the right way round for a discount somebody else is funding. Employees reach their employer’s offers by joining with a company invite or access code, which ties an account to an organisation without the platform needing an HR feed. Around it sits the value the product accumulates: original value, discount value, final amount and a running lifetime saving, alongside transaction history, points, rewards and gift vouchers, all of which are redeemable for real value rather than being a score. The administration dashboard is where the platform itself is run: merchants, offers, corporate access and everything that needs resolving after the fact.
Architecture
A Laravel 8 backend over MySQL, with Redis in front of the reads that repeat and Firebase carrying push, on AWS in Docker. Customers are on Flutter and Dart; the administration dashboard is React and TypeScript, which suits a surface that sits entirely behind a login and never needs to be indexed. Behind the API the interesting arrangement is that entitlement sits on the path to the catalogue rather than beside it: resolving who is asking, an individual or an employee of a particular company, happens before the offers they may see are assembled, which is the only arrangement a query written later cannot bypass. Redemptions hang off that, and points, rewards and vouchers hang off redemptions, because value is earned by transacting rather than granted directly. Nothing in the diagram is drawn as unconfirmed.
Read this diagram as text
- Customer app
- Flutter and Dart, and the only surface a customer ever sees: merchants, offers, the code presented at the counter, and the running savings total that justifies keeping it installed.
- Admin dashboard
- React and TypeScript, behind a login and never indexed. Merchants, offers, corporate access, and everything that needs resolving after the fact.
- Laravel 8 API
- Single entry point for both surfaces, on AWS in Docker.
- Entitlement
- Resolves who is asking before the catalogue is assembled: an individual, or an employee who joined their employer’s programme with an invite or access code.
- Redemptions & savings
- What was redeemed, where, and what it saved: original value, discount value, final amount and a running lifetime total. Completed by the merchant scanning the code the customer presents.
- Redis
- Fronts the reads that repeat, on a catalogue whose contents depend on who is asking, which makes the cache key the entitlement rather than the offer list.
- Firebase
- Cloud Messaging. On a marketplace of time-bound offers this is how a catalogue that changes becomes one anybody acts on.
- Offers & merchants
- The merchant network and the offers, discounts and promotions it carries, assembled per viewer rather than served whole.
- Points & vouchers
- Points, rewards and gift vouchers, earned by transacting and redeemable for real value, which makes them an obligation on the books rather than a score.
- Customer app → Laravel 8 API (discover, redeem, track)
- Admin dashboard → Laravel 8 API (merchants, offers, corporate access)
- Laravel 8 API → Entitlement (who is asking)
- Entitlement → Offers & merchants (the catalogue this viewer may see)
- Laravel 8 API → Redemptions & savings (merchant scans the code)
- Redemptions & savings → Points & vouchers (points and vouchers earned)
- Laravel 8 API → Redis (repeated reads)
- Laravel 8 API → Firebase (offer alerts)
What makes it interesting
The parts a system like this is actually judged on.
- Entitlement on the query path is what the dual audience actually costs. The same catalogue renders differently for an individual, an employee of company A and an employee of company B, so resolving the viewer has to happen before the catalogue is assembled rather than filtering results afterwards. Anything else leaves a query somewhere that forgets to ask, and the failure mode is not a cosmetic one: it is showing one employer the offers negotiated by another.
- The boundary between an employee and everybody else is an access code, which is worth stating plainly rather than dressing up. Joining a company programme means entering an invite or code that ties the account to that organisation. It needs no HR integration and no identity provider, which is why it works at all for employers who will not grant either, and the cost is that the credential is shareable: a code passed to somebody outside the company is a corporate discount leaving the company. The control is commercial rather than cryptographic, and the honest version of this system says so.
- Entitlement and caching pull against each other, which is why Redis is more interesting here than on an ordinary catalogue. Caching is easy when every viewer gets the same answer and hard when the answer is per-viewer by definition, so the cache has to be keyed on the entitlement that produced it rather than on the offer list, and an employer changing its agreement has to invalidate exactly the right slice. Cache one response an audience wide and the platform shows people offers they are not entitled to.
- The merchant scans the customer, which settles the trust question in the only direction that survives scrutiny. The customer presents a code and the merchant verifies it, so the party giving up margin is the party confirming the claim, and the platform is not asking a shop to take a stranger’s screen on faith. What it does not remove is replay: a code on a screen can be photographed, so whatever makes it single-use and short-lived is the entire defence, and it has to do that without failing a customer standing at a till behind four other people.
- Points are redeemable for real value, which makes them a liability rather than a loyalty score. Anything convertible into a voucher is money in a costume: it attracts the people who look for ways to mint it, it has to survive refunds and reversals clawing it back, and it sits on somebody’s books as an obligation until it is spent. That is a materially different system from one where points are a badge, and the distinction is invisible in the interface and decisive underneath it.
- Savings are derived state the user is asked to trust. Original value, discount value, final amount and a running lifetime total are the number that justifies keeping the application installed, and a running total has to survive refunds and reversals to stay honest. Compute it at redemption and a reversal has to unwind it; aggregate it afterwards and it can drift quietly. Either way the number is a promise, and a promise nobody audits is one that eventually stops being true.
- A deal nobody hears about is not a deal, which makes push part of the product rather than a nicety. Offers are time-bound and often local, so Firebase is the mechanism by which a catalogue that changes becomes a catalogue anyone acts on. It also cuts the other way: a benefits application that notifies too eagerly is uninstalled, and the offers most worth sending are the ones restricted to a single employer.
Engineering challenges
- Per-viewer entitlement on the core query path, where forgetting to ask leaks one employer’s offers to another
- An access code as the corporate boundary, which is shareable by design and commercial rather than cryptographic
- Caching a catalogue whose contents depend on who is asking
- A QR code that must be single-use and screenshot-proof while still clearing a queue
- Points redeemable for real value, which is a liability and a fraud target rather than a score
- A running savings total the user trusts, which must survive refunds and reversals
- Two-sided cold start: users need offers and merchants need users, and neither arrives first
Integrations
- Firebase: push notification delivery to the customer application, which on a marketplace of time-bound offers is how the catalogue reaches anyone
Technology
- Laravel 8
- MySQL
- Redis
- React
- TypeScript
- Flutter
- Dart
- Firebase
- AWS
- Docker