C++ E-commerce Backend Project
Products, carts, orders, and payments — the engine behind an online shop.
What you will build
No website, just the brain: products with stock, user carts, checkout that reserves inventory, order lifecycle (placed → paid → shipped), and discount rules. It's a system-design project where the architecture — clean layers, no rule duplicated — matters more than any single feature.
What you will learn
- Layered design: models, services, storage
- Cart and order state machines
- Inventory reservation during checkout
- Applying discount/coupon rules in one place
- Simulated payment gateway interface
Feature checklist
- Product catalogue with stock levels
- Users with carts (add/remove items)
- Checkout: validates stock, creates an order
- Coupon codes: percentage and fixed discounts
- Order status flow with cancellation restocking
- Admin sales report
Starter code
#include <map>
#include <string>
#include <vector>
struct Product { std::string name; double price; int stock; };
struct CartLine { std::string sku; int qty; };
enum class OrderStatus { Placed, Paid, Shipped, Cancelled };
struct Order {
int id; std::string user;
std::vector<CartLine> lines;
double total; OrderStatus status = OrderStatus::Placed;
};
class Store {
std::map<std::string, Product> products_;
std::vector<Order> orders_;
int nextId_ = 1;
public:
// Checkout either fully succeeds or changes nothing.
Order* checkout(const std::string& user,
const std::vector<CartLine>& cart, double couponPct) {
for (auto& l : cart) {
auto it = products_.find(l.sku);
if (it == products_.end() || it->second.stock < l.qty)
return nullptr; // insufficient stock
}
double total = 0;
for (auto& l : cart) {
auto& p = products_[l.sku];
p.stock -= l.qty;
total += p.price * l.qty;
}
total *= (1.0 - couponPct / 100.0);
orders_.push_back({nextId_++, user, cart, total, OrderStatus::Placed});
return &orders_.back();
}
};Full walkthrough
Step 1: Lay out the domain types
Product { name, price, stock }, CartLine { sku, qty }, Order { id, user, lines, total, status } with enum class OrderStatus { Placed, Paid, Shipped, Cancelled }, and class Store owning the catalogue (std::map sku → Product), the users' carts (map user → vector<CartLine>), and the orders vector. A 'backend' is just these types plus rules — there is no UI to hide behind, which is what makes it an advanced project.
Step 2: Cart operations are inventory-neutral
addToCart checks the product EXISTS, not that stock suffices — carts are wishes; stock is enforced at checkout, because stock changes while items sit in carts. Removing lines and viewing the cart (with a running total priced from the CURRENT catalogue) complete the cart API.
Step 3: checkout(): the transaction
Validate EVERY line's stock first; if any line cannot be fulfilled, the whole checkout fails and nothing changes. Then decrement stocks, snapshot the lines WITH their prices into an Order (prices at purchase time — a later price change must never rewrite history), compute the total, apply any coupon, mark Placed, clear the cart. Validate-all-then-commit-all is the entire discipline of transactions, learned on one function.
bool Store::checkout(user, coupon) {
for (auto& line : cart[user])
if (line.qty > catalogue[line.sku].stock) return false; // nothing changes
// ... only now: decrement, snapshot, total, create order
}Step 4: Coupons: two kinds, one application point
A coupon table maps codes to either a percentage (SAVE10 = 10% off) or a fixed amount (FLAT500). Apply AFTER the subtotal, floor the total at 0, and record the discount on the order so the receipt explains itself. Unknown code → error message, checkout continues without it only if the user confirms — silent coupon failure is how stores lose trust.
Step 5: Order status flow with restocking cancellation
Orders move Placed → Paid → Shipped; a Cancel from Placed or Paid returns every line's quantity to stock (restock by reversing the checkout decrements), while a Shipped order cannot cancel. Status transitions are a tiny state machine: enumerate the legal moves and refuse the rest explicitly.
Step 6: The admin sales report
Aggregate over non-cancelled orders: revenue total, units per product, top seller, order count by status. It reuses the order list as the single source of truth — no separate sales counters to drift (you can recite this principle in your sleep by now, and it is correct every time).
Step 7: Test the failure stories, not the success story
Cart 3 of an item with stock 2 → checkout fails, stock and cart both untouched. Buy 2 of 2 → stock 0; a second buyer cannot. Apply SAVE10 to a 24000 cart → 21600. Cancel a Placed order → stock restored exactly. Pay, ship, then attempt cancel → refused. Each test defends a rule; together they ARE the specification.
Common mistakes
- Decrementing stock line-by-line during validation, so a checkout that fails on line 3 has already eaten lines 1 and 2.
- Storing the cart total on the order instead of snapshotted line prices, so a catalogue price change rewrites past receipts.
- Letting a Shipped order cancel and restock, resurrecting inventory that is physically on a truck.
Compile and run
g++ -std=c++17 -o store store.cppSample run
$ ./store
Catalogue: KEYB-01 Mechanical Keyboard 15000 (stock 5) · MSE-02 Wireless Mouse 4500 (stock 2)
User ada — 1) Add to cart 2) Checkout 3) My orders 0) Exit
> 1
SKU and qty:
> KEYB-01, 1
Added. Cart total: 15000.00
> 1
SKU and qty:
> MSE-02, 2
Added. Cart total: 24000.00
> 2
Coupon code (empty for none):
> SAVE10
Subtotal 24000.00 · SAVE10 -2400.00 · TOTAL 21600.00
Order #501 placed. Stock now: KEYB-01 x4, MSE-02 x0
> 1
SKU and qty:
> MSE-02, 1
> 2
Checkout refused: MSE-02 has 0 in stock. Nothing was charged or decremented.
> 0Try it in your browser
No browser demo is included for this project. Build the C++ version below — that is the real program.