Kelick Tech Hub

Build, learn & keep up — tech, Linux & sports news included.

C++ E-commerce Backend Project

Advanced•2 weeks•system design, OOP, data modelling

Products, carts, orders, and payments — the engine behind an online shop.

Share on X

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

Feature checklist

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

Compile and run

g++ -std=c++17 -o store store.cpp

Sample 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.
> 0

Try it in your browser

No browser demo is included for this project. Build the C++ version below — that is the real program.

Related Advanced projects

← All 60 projects · Classic site (Learn & Sandbox)

Download Fake GPS