Designing Tiered Pricing That Can't Be Gamed
Early-bird rates, founding discounts, sibling pricing — dynamic pricing logic is easy to add to a form and easy to exploit if it's only enforced on the front end. This piece explains why client-side pricing is a security problem, not just a design pattern, and how to build pricing logic that holds up in production.
The Problem With Front-End Price Calculation
When you calculate price in JavaScript and pass the result to your payment provider, you are trusting the client. Anyone with browser DevTools can inspect the Paystack (or Stripe, or Flutterwave) initialisation call, find the amount field, and change it before the popup opens. They pay whatever they want.
This is not a theoretical attack. It takes two minutes with F12 open.
The front end can display a price. It should never be the source of truth for what is charged.
Rule 1: Price Is Always Calculated Server-Side
The server recalculates the correct price independently when payment is about to be initiated — querying the database, applying all relevant logic, and setting the amount on the payment provider's API. If you're using Paystack's inline JS, the amount must still be fetched from your server immediately before the popup opens, not computed in the browser.
Rule 2: Verify Before Saving
After payment is completed, your callback receives a transaction reference. Call the payment provider's verification API server-side before saving any record. This confirms the transaction happened, the amount paid matches what you expected, and the status is genuinely "success." Don't save a registration or confirm a booking based on a client-side callback alone.
A Founding Rate: Worked Example
For the AI Navigators Bootcamp, the first three paid registrations per cohort get a founding rate (₦395,000 vs ₦450,000). The rules: only verified Paystack transactions count, the count is per cohort, and the rate locks at payment time.
Implementation: immediately before opening the Paystack popup, the server queries the DB for confirmed paid registrations in the selected cohort. If count < 3, founding price is set. This query runs on every checkout — no cached state can drift out of sync.
Sibling Discounts and Product Decisions
Sibling discounts require the same discipline. We also made a deliberate product decision: no instalment plans when enrolling two children. Holding two seats in an 8-seat cohort on a 50% deposit is too much capacity risk. The server enforces full payment regardless of what the front end sends. This is product design and security working together.
The Secure Flow
- User selects options; price is displayed for UX purposes only
- On checkout, front end calls server to fetch the current correct price
- Server queries DB, applies all rules, returns price
- Front end uses server-returned amount to initialise Paystack popup
- After payment, reference is sent to server
- Server verifies with Paystack API
- Server confirms amount matches what it set
- Only then is any record saved
Display price on the client. Calculate price on the server. Verify payment on the server. Never trust an amount that came from a browser.
Have a question about this? Talk to us — we're happy to go deeper.