Ecommerce Logic

By the end of this lesson you'll build the business logic behind a real online shop — a cart that handles money safely, correct discount-and-tax maths, and a checkout that creates an order and adjusts stock atomically, the way production systems actually do it.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

1️⃣ The Cart: Add, Update, Remove

A cart is a temporary collection of items before the customer commits to buying. It needs four jobs: add a product (and merge if it's already there), update a quantity, remove a line, and report the subtotal . The single most important decision is how you store the price. Use integer cents — store $29.99 as 2999 — and only divide by 100 when you print. (More on why in the next section; for now just notice the prices are whole numbers.) Keying the lines by product id means adding the same product twice merges into one line automatically.

Notice how update(2, 3) set the keyboard line to exactly 3, and adding mouse id 1 twice produced a single line of quantity 2. remove(99) on a product that isn't in the cart simply does nothing — defensive code that won't crash on bad input.

2️⃣ Discounts, Coupons & Tax

Here is the rule that catches everyone: discount first, then tax . A coupon comes off the subtotal, and tax is charged on what's left (the discounted amount). Round to a whole cent once per step — not on every line — or tiny errors accumulate. The two helpers below model the two coupon kinds you'll meet: a percent coupon (15% off) and a flat coupon ($10 off), capped so it can never make the total negative.

Each money step does its own single round() back to an integer. The %% in printf prints one literal % sign — handy when your label contains a percentage.

3️⃣ Checkout: One Transaction, No Overselling

Checkout is where money and stock both move, so it must be all-or-nothing . A transaction gives you that: beginTransaction() opens it, commit() makes every write permanent together, and rollBack() undoes the lot if any step throws. The clever bit is the stock decrement: UPDATE ... SET stock = stock - ? WHERE id = ? AND stock = ? . That stock = ? guard means if two shoppers race for the last unit, the database lets only one update match — the other changes zero rows, and you treat that as out-of-stock.

The order is created as pending , every line is written, stock is decremented with the race guard, and only then is the order flipped to paid and committed. If anything fails — a sold-out item, a dropped connection — rollBack() leaves the database exactly as it was. That is the difference between a real shop and a toy one.

4️⃣ Your Turn: Get the Money Right

Time to do it yourself. The script below computes a total but the three money steps have blanks. Fill in each ___ using the 👉 hint, keeping the discount → taxable → tax order, then run it and check the Output panel.

One more. This one is the stock guard — the single comparison that stops you overselling. Replace the ___ so a sale only goes through when there's enough on hand.

📋 Quick Reference — E-Commerce Logic

No code is filled in this time — just a brief and an outline. Build the state machine yourself, run it on onecompiler.com/php or your own machine, then check it against the expected output in the comments. Enforcing legal transitions is exactly how real order systems stop an order jumping from "pending" straight to "delivered".

Practice quiz

Why store money as integer cents instead of a float like 29.99?

  • Floats take more memory
  • Integers are easier to type
  • Floats drift (0.1 + 0.2 isn't exactly 0.3); integers stay exact
  • Floats can't be negative

Answer: Floats drift (0.1 + 0.2 isn't exactly 0.3); integers stay exact. Floats can't represent most decimals exactly, so errors compound across a cart. Store $29.99 as 2999 and divide by 100 only to display.

How is $29.99 stored as integer cents?

  • 2999
  • 29.99
  • 30
  • 299

Answer: 2999. Multiply by 100: $29.99 becomes the integer 2999 cents. You divide by 100 only when formatting for display.

In the lesson's Cart, what happens when you add the same product id twice?

  • A duplicate line is created
  • The second add is ignored
  • An error is thrown
  • The two are merged into one line and the quantity increases

Answer: The two are merged into one line and the quantity increases. Lines are keyed by product id, so adding the same id again just bumps that line's qty (e.g. add mouse twice -> qty 2).

What is the correct order for discount and tax?

  • Tax first, then apply the discount
  • Discount first, then tax the discounted amount
  • Apply both to the original subtotal independently
  • Tax and discount can be done in any order

Answer: Discount first, then tax the discounted amount. In most regions the coupon comes off the subtotal, then tax is charged on what's left. Wrong order makes every receipt a few cents off.

Subtotal 12000c, a 25% coupon, then 10% tax — what is the total?

  • $99.00
  • $108.00
  • $90.00
  • $99.90

Answer: $99.00. Discount = 25% of 12000 = 3000; taxable = 9000; tax = 10% of 9000 = 900; total = 9900c = $99.00.

How should you round money during a multi-step calculation?

  • Round on every single line
  • Never round until the very end of everything
  • Round once per step (e.g. once for discount, once for tax)
  • Round each digit separately

Answer: Round once per step (e.g. once for discount, once for tax). Each money step does its own single round() back to an integer; rounding on every line lets tiny errors accumulate.

Why must checkout run inside a database transaction?

  • Transactions make queries faster
  • The order, line items, and stock changes must all commit together or all roll back
  • It hashes the payment
  • It is required to connect to MySQL

Answer: The order, line items, and stock changes must all commit together or all roll back. Checkout writes rows that only make sense together; a transaction is all-or-nothing so a crash never leaves a half-built order.

Which SQL guard prevents two shoppers overselling the last unit?

  • SELECT stock FROM products WHERE id = ?
  • DELETE FROM products WHERE stock = 0
  • UPDATE products SET stock = 0
  • UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?

Answer: UPDATE products SET stock = stock - ? WHERE id = ? AND stock >= ?. The atomic 'AND stock >= ?' guard means only one racing request matches and changes a row; the other gets 0 rows and is treated as out of stock.

After the guarded stock UPDATE, what does rowCount() === 0 mean?

  • The sale succeeded
  • Not enough stock — bail out and roll back
  • The product was deleted
  • The connection dropped

Answer: Not enough stock — bail out and roll back. Zero rows changed means the stock >= qty condition failed, so the item is out of stock and you throw to trigger the rollback.

Why model order states as a small state machine?

  • To make the database smaller
  • To avoid using a status column
  • To enforce legal transitions so an order can't jump from pending straight to delivered
  • To speed up checkout

Answer: To enforce legal transitions so an order can't jump from pending straight to delivered. Orders move pending -> paid -> processing -> shipped -> delivered; mapping each state to its allowed next states blocks illegal hops.