An online shopping system, from basket to doorstep

Published . By the DiagramDesk team.

A web shop looks like a catalogue with a till attached. Model it that way and three things break the first time the real world touches it: a price changes, a customer moves house, and someone wants to buy without making an account. The diagrams below are built to survive all three.

ActorsContextDataClassesCheckoutMistakes

Who uses it: four kinds of people and two other systems

Start with the actors, because the rest of the model depends on them. A shopper who has not signed in can browse, fill a basket and pay. A registered customer can do all of that and more: look up past orders, track a parcel, ask to send something back. That "and more" is exactly what actor generalisation expresses, the hollow-headed arrow from Registered customer to Shopper. It saves drawing the shopping use cases twice.

Two actors are not people. The payment provider takes the money, and the courier carries the parcel and reports when it arrives. Both sit on the right, the usual side for systems the shop depends on.

A registered customer inherits everything a shopper can do.Online shop«include»«extend»Browse productsAdd to basketCheck outTrack an orderRequest a returnManage productsand stockDispatch an orderTake paymentApply a discountcodeConfirm deliveryShopperRegisteredcustomerShop staffPayment providerCourier
A registered customer inherits everything a shopper can do.Open in the editor

Check out includes Take payment, because no checkout finishes without it. Apply a discount code extends Check out, because most checkouts never use one: the extension happens at one point, on one condition, and the base use case reads perfectly well without it. If a diagram has the discount code «include»d, a marker will ask whether every order really has a code.

The context diagram: one circle, everything around it

A context diagram, the top level of a set of data flow diagrams, draws the whole system as a single process numbered 0 and shows only what crosses its edge. It is the right first DFD for a shop because it settles the boundary before anyone argues about the inside.

Only flows that cross the shop's boundary appear at this level.basket and addressconfirmation, trackingproducts, stockorders to packpayment requestpayment resultparcel detailsdelivery statusCustomerShop staff0Online shopPayment providerCourier
Only flows that cross the shop's boundary appear at this level.Open in the editor

The interesting line is the one that is not there. The customer types their card number into the payment provider's page, so the number flows from the customer to the provider and never through the shop. A context diagram leaves out flows between two outside parties, which makes that design choice visible: the shop receives a payment result, not a card.

The data: what an order has to remember

The ER model is where the three real-world problems are solved, and each one is solved the same way: the order keeps its own copy of the facts as they were when it was placed.

  • Prices change. Order line has its own unit_price. Product's price is today's price; the line's is what this customer agreed to pay. Without it, a price rise would silently rewrite every past invoice.
  • People move. The delivery address is copied onto the order at checkout. If it were only a link to the customer's current address, editing a profile would re-route orders already in the courier's van, at least on paper.
  • Guests buy things. The line from Customer to Order has a ring at the Customer end: an order belongs to zero or one customer. A guest order keeps an email address of its own instead.
Basket and order are separate: most baskets never become orders.placesownsholdsis incontainsis sold ingroupsis paid byCUSTOMERint customer_id PKstring email UKstring nameORDERint order_id PKint customer_id FK "empty for a guest"string emailstring ship_to "copied at checkout"datetime placed_atstring statusBASKETint basket_id PKint customer_id FKdatetime updated_atBASKET_ITEMint basket_id PK, FKint product_id PK, FKint quantityPRODUCTint product_id PKint category_id FKstring sku UKdecimal priceint stockORDER_LINEint order_id PK, FKint line_no PKint product_id FKint quantitydecimal unit_price "price when ordered"CATEGORYint category_id PKstring namePAYMENTint payment_id PKint order_id FKstring provider_refdecimal amountstring outcome
Basket and order are separate: most baskets never become orders.Open in the editor

Payment is its own entity rather than a "paid" column, because an order can have several: a declined attempt, then a successful one, and later perhaps a refund. Each keeps the provider's reference, which is what you quote when a customer disputes a charge. Order line and basket item both use composite keys, and it is worth saying why in a write-up: a line is identified by its order and its position, and a basket holds a product at most once, with a quantity. Those keys are why three lines are solid, identifying relationships: Order to Order line, and Basket and Product to Basket item. Every other line is dashed, because the parent's key is not part of the child's: an order has its own number, and an order line points at its product without the product being part of its key.

Classes: the basket makes the order

In the class diagram the basket and the order become objects with behaviour, and the method that joins them is Basket.checkOut(address), which returns a new Order. That dashed arrow is a dependency (the basket creates orders but does not keep hold of them), not an association.

OrderStatus is an enumeration: the status can only be one of five named values.1 · holds · 0..*0..* · of · 11 · contains · 1..*0..* · of · 11 · is paid by · 0..*createsBasket+addItem(product, qty)+total() Money+checkOut(address) OrderBasketItem+quantity: intProduct+sku: String+price: Money+stock: int+reserve(qty) boolOrder+placedAt: DateTime+status: OrderStatus+markPaid(payment)+cancel()OrderLine+quantity: int+unitPrice: Money+lineTotal() MoneyPayment+providerRef: String+amount: Money+succeeded: bool«enumeration» OrderStatusAwaitingPaymentPaidDispatchedDeliveredCancelled
OrderStatus is an enumeration: the status can only be one of five named values.Open in the editor

The status values are worth listing in the diagram rather than leaving as a string, because they are also the states an order moves through: awaiting payment, paid, dispatched, delivered, or cancelled. If your course asks for a state diagram as well, those five are its states, and the methods on Order (markPaid, cancel) are its transitions. Composition again marks ownership: order lines do not exist without their order, and basket items do not exist without their basket.

The checkout sequence

Checkout is the scenario examiners ask for most often in this system, because it crosses every part of it. Two details in the order of messages carry the design. The order is created before the customer pays, in an "awaiting payment" state, so a failed payment still leaves a record of what was attempted. And the stock is reserved first and released if the payment fails, so two customers cannot both buy the last one.

The payment result arrives as its own message, not as a reply the shop waits for.alt [Paid]Check out basketReserve each itemReservedCreate order, awaiting paymentRequest payment for the totalPayment pageCard detailsPayment resultMark order paidOrder confirmationRelease the itemsPayment failed, try againCustomerShopStockPayment provider[Declined]
The payment result arrives as its own message, not as a reply the shop waits for.Open in the editor

The payment result is drawn with an open, asynchronous arrowhead from the provider to the shop. It does not come back along the customer's browser, because the customer returning from the payment page is no proof of payment: they can close the tab or lose their connection on the way. The shop marks the order paid when the provider itself says so.

What costs marks

  1. The basket as an order with a status of "basket". Abandoned baskets then count as orders, and every report has to filter them out.
  2. Price only on Product. The first price change corrupts order history.
  3. A mandatory customer on every order, which quietly rules out guest checkout the assignment asked for.
  4. "Paid: yes/no" on Order. A retry, a partial refund or a chargeback has nowhere to go.
  5. The card number drawn flowing into the shop's own stores in the DFD, when the design sends it to the payment provider.
  6. Discount code as an «include», which claims every checkout uses one.

Your own brief may differ in ways that change the answer: a marketplace with many sellers needs a Seller entity and splits one order into several shipments; a digital shop has no courier at all. Draw the version your brief describes, and use this one to check your reasoning.