Discover the State
What must it remember? Items, owner, maybe currency — and the challenge to every field: do you need the product name in the cart, or can it be loaded from the catalog? Derived against stored; UI state, JSON, domain object, rows.
A cart is "a temporary collection of products the user intends to buy". Before a class or a table, ask what that collection has to remember — items, an owner, possibly a currency; for each item a product id and a quantity — and write the candidates down as a list you can challenge, not as code.
Do you need to store the product name inside the cart, or can it be loaded from the catalog? Every candidate field gets that question, and the answer is a trade-off — duplication and staleness against a lookup per render — written down, not a preference.
The total is computed, not stored: a stored total is a second source of truth that has to be kept in sync on every change. The price is derived for a cart and snapshotted for an order — and that difference is the difference between a cart and an order.
You cannot tell whether an item needs a quantity by staring at the class. Write the examples: Cart = [] → add Laptop → [Laptop × 1] → add Laptop → [Laptop × 2]. The second example is where quantity becomes a field and "two rows for the same product" becomes wrong.
The same cart exists four times: as UI state in a component, as JSON on the wire, as a domain object on the server, as cart and cart_items rows in a database. Knowing one concept in four representations — and what each one adds or loses — is what makes "the cart" survive a change of layer.