Capstone 5 — E-Commerce System¶
Scope: a term's work · Prerequisites: Modules 1–15
1. Problem statement¶
An online shop sells physical and digital goods. A customer browses a catalogue, builds a basket, applies offers, pays, and receives an order that may later be partially cancelled.
The interesting part is pricing that composes. A price may be affected by a percentage discount, a bulk rule, a bundle offer, a loyalty tier, a shipping band and a tax rule — and the rules combine, do not always commute, and change every season. The same product also behaves differently if it is digital (no shipping, no stock) or physical (stock, shipping, returns).
Orders must be persisted and must survive being reopened: an order is a financial record, and its prices are the ones agreed at the time of purchase, not today's.
2. Actors¶
| Actor | What they do | What the system owes them |
|---|---|---|
| Customer | Browses, adds to a basket, applies an offer code, orders, returns | Prices that are explained line by line, and an order that says what it will cost before it is placed |
| Shop manager | Adds products, sets offers and stock | Rules that can be reconfigured without recompiling |
| Warehouse clerk | Packs, ships, and records stock movements | An order state that matches reality, and stock that is not oversold |
| Accountant | Reads order totals and tax | Totals that reconcile and never change after the order is placed |
3. Use cases¶
- Add products: a physical product with stock, and a digital one without.
- Build a basket: add, change quantity, remove.
- Price a basket: subtotal, each discount applied and shown, shipping, tax, and a total.
- Apply an offer code, and refuse it with a reason if it does not apply.
- Place an order, which reserves stock for physical goods and fixes the prices.
- Cancel or return part of an order, with the refund computed from the order's own prices.
- List a customer's orders, and re-print an order exactly as placed.
- Move an order through its states: placed, paid, packed, shipped, delivered, cancelled.
- Save and load everything; refuse a corrupted file with a line number and no partial state.
4. Suggested classes¶
| Class | Responsibility, in one sentence | Notes |
|---|---|---|
Money |
An exact amount in pence | A value type; no double |
Product |
A sellable identity: name, price, category | Abstract: not everything has stock |
PhysicalProduct |
A product with stock and a shipping weight | Derived from Product |
DigitalProduct |
A product with no stock and no shipping | Derived from Product |
Basket |
A customer's current selection, priced on demand | Owns basket lines |
BasketLine |
A product and a quantity | Value-like |
PricingRule |
Transforms a running total, and knows whether it applies | An interface: the thing that composes |
ShippingPolicy |
Turns a basket into a shipping charge | Injected |
TaxPolicy |
Turns a basket and a region into tax | Injected |
PricingPipeline |
Applies the rules in order and produces an itemised total | The explanation the customer sees |
Order |
A placed basket, with fixed prices and a state | Owns its lines; never reprices |
OrderState |
What is allowed in this state | A state machine, or an enum with guarded transitions |
Inventory |
Stock levels and reservations | Enforces "never negative" |
Customer |
An identity, an address, a loyalty tier, a basket and orders | Owns its own basket |
The class worth the most marks is PricingPipeline. Pricing that composes is the requirement that
distinguishes this project from a shopping list, and it is where a switch on offer type turns into a
chain of small policies. The receipt the customer sees is the pipeline's explanation of how it got from
subtotal to total, and if you cannot print that explanation, the pricing is not designed.
5. Relationships¶
Customer ◆── Basket composition
Customer ◆── vector<Order> composition
Basket ◆── vector<BasketLine> composition
BasketLine ────> Product association
Order ◆── vector<OrderLine> composition, with prices frozen at placement
Order ────> Customer association
PricingPipeline ◆── vector<PricingRule> composition: the pipeline owns its rules
Product <|── PhysicalProduct inheritance for substitution
Product <|── DigitalProduct
Order ────> OrderState association to the current state; transitions guarded
Inventory ◇── PhysicalProduct aggregation: the inventory indexes products it does not own
6. Design constraints¶
- Prices are frozen when an order is placed. Re-printing an old order must produce the original
amounts even after a price change — the classic requirement that decides whether
Orderstores a reference to a product or a copy of the line as sold. - Pricing rules compose and are applied in a documented order, and the order is justified: tax after discounts, shipping before tax, and so on. Write the order down; a rule that silently depends on another's position is a bug waiting for a reconfiguration.
- A refused offer code says why: expired, wrong category, minimum spend not met, already applied.
- Stock is never oversold, and a reservation is released if an order is cancelled — with the invariant owned in one place.
- Order states have guarded transitions: you may not ship a cancelled order, and the refusal names the state.
- Money in integer pence, and every displayed total equal to the sum of the lines shown — a self-check worth running in a test.
- Format: documented text format with a version marker and an escape rule; an order's lines preserved in order; a later-version file refused, not misread.
7. Milestones¶
| # | Milestone | Done when |
|---|---|---|
| 1 | Design | Diagram; the pricing rule order written down and justified; where prices are frozen stated |
| 2 | Catalogue and basket | Products of two kinds, a basket that prices itself, and an itemised total |
| 3 | Pricing pipeline | Rules compose; an offer code applies or is refused with a reason; two configurations exist |
| 4 | Orders and stock | Placing an order freezes prices and reserves stock; cancellation releases it; no oversell is possible |
| 5 | Persistence | Documented format; round trip preserving orders and prices; three hand-edited corruptions rejected with line numbers |
| 6 | Tests and report | Invariants, boundaries and failures asserted; a deliberate bug caught; the reversal named with its cost |
8. Minimum requirements¶
- [ ] Two kinds of product, differing in stock and shipping — with the difference in types, not in
ifs - [ ] A basket that can be priced, with an itemised breakdown: subtotal, each applied rule, shipping, tax
- [ ] At least three composable pricing rules, applied in a documented order
- [ ] Offer codes that apply or are refused with a specific reason
- [ ] Orders that freeze their prices, and a re-print that is identical after a price change — demonstrated
- [ ] Stock that is never negative, with reservations released on cancellation
- [ ] Order states with guarded transitions, and refusals that name the current state
- [ ] Persistence with a version marker and escape rule; a round trip preserving orders, including an empty order list
- [ ] Three hand-edited corruptions rejected with line numbers, leaving the loaded state untouched
- [ ] At least two patterns, each justified by a named requirement; one declined, with the reason
- [ ] Tests with assertions covering invariants, boundaries and failure paths
- [ ] No warnings with
-Wall -Wextra -Werror
9. Advanced requirements¶
- [ ] Bundle offers ("three for two") as a rule that composes with the others — and a demonstration that adding it required no edits to existing rules
- [ ] Loyalty tiers as another policy, with the tier change counted in files
- [ ] Partial returns that refund exactly the lines returned, computed from the order's frozen prices
- [ ] Tax by region, with a second region added without editing the order logic
- [ ] Stock movements as an append-only log, with the current level derived from it
- [ ] A second way to place an order (a scripted batch file) reusing the domain unchanged
- [ ] Order export in CSV behind the same interface as the console report
10. Testing requirements¶
- Invariants: the displayed total equals the sum of the lines shown; stock never negative; an order's prices never change; a basket's lines always refer to existing products
- Boundaries: an empty basket; a quantity of one and of the maximum; an offer exactly at its minimum spend; a return of every line; a stock level of exactly the requested quantity
- Failure paths: an expired offer code; a discount for the wrong category; ordering more than stock; shipping a cancelled order; loading a truncated file
- Round trip: orders and prices preserved exactly, including an order with a single line and an empty order list
- One deliberate bug (for example, applying tax before the discount), caught by a test that asserts an itemised total, with the failing assertion kept
11. Extension ideas¶
- Wish lists and price-drop notifications, with the observer's lifetime question answered.
- A recommendation rule based on purchase history, and a measurement of how expensive it is in your data structures.
- Suppliers and reordering when stock falls below a threshold — a policy, and a notification.
- Gift cards as a payment method, and the design decision about whether a gift card is money.
- Multiple warehouses, with stock split across them and a rule for which one ships.
- A price history per product, and the ability to price an old basket as of a date — which is where your data structures are tested hardest.
12. Evaluation checklist¶
- [ ] The diagram matches the declarations, including multiplicities
- [ ] The pricing rule order is documented and justified, not incidental
- [ ] The itemised total equals the sum of the lines — checked by a test, not by eye
- [ ] An offer code's refusal names a specific reason, and each reason is tested
- [ ] An old order re-prints identically after a price change — demonstrated
- [ ] Stock cannot go negative — demonstrated by a test that tries
- [ ] Adding a pricing rule required no edits to existing rules, and the file count is in the report
- [ ] The round trip preserves order lines and their order, including an empty list — output pasted
- [ ] Three corruptions rejected with line numbers, leaving the loaded state untouched
- [ ] The suite fails when a deliberate bug is introduced
- [ ] One pattern considered and declined, in writing
- [ ] The report names the decision I would reverse and the cost of reversing it
Related: Capstone options · Projects and the rubric · Module 16 — Capstone · Module 15 — Patterns