購物車是電商裡看起來最簡單、其實暗坑不少的一塊。它不就是「把商品暫存起來」嗎?是,但「暫存」這兩個字底下藏著幾個真的要想清楚的決定:訪客的車放哪、登入後怎麼跟會員的車合併、還有一個我想特別講的——車裡的價格,要當下算還是存快照?

訪客與會員:雙軌是必須的

第一個決定:沒登入的人也要能加購物車。你不能逼客人先註冊才能逛——那會流失一票隨手加車的人。

所以購物車天生是雙軌的:訪客用 session 識別(一組 session key)、會員綁在帳號上。 兩套並存。而真正的邊界問題出在「合併」——訪客加了一車東西,然後登入了,這時候要把訪客車併進會員車。

合併沒那麼單純:訪客車裡的商品,登入的瞬間可能已經有些庫存不足了;會員車裡本來也有東西。所以合併時要重查一次庫存、把不足的下修、還要回報「哪些因為缺貨被跳過」,不能默默吞掉——不然客人會發現「我明明加了,結帳怎麼少了」。

我做錯的那個決定:價格用動態,其實該用快照

這一段我想很誠實地講,因為它是我事後回頭看、覺得當初該反過來選的決定。

購物車裡顯示的價格,有兩種做法:

  • 動態價:每次讀取購物車,都即時去算現在的價格。
  • 快照價:加入購物車的當下,把價格記下來,之後就用那個記下來的價。

當初這套系統選了動態價——每次看車即時算。理由當時聽起來很合理:價格永遠是最新的,不會有「顯示舊價」的問題。

但實際跑久了,我覺得快照其實比較好。問題出在「隔很久回來看車」這個真實情境:客人某天把商品加進車、用了某個限時活動的價格,然後隔了兩週才回來結帳。這時候動態價會即時重算——但那個活動早就下架了。於是客人看到的金額,跟他當初加車時預期的完全不一樣,甚至可能突然變貴。金額對不起來,客訴就來了。

快照就沒這問題:加車當下是什麼價,之後就是什麼價(或者至少,你能清楚告訴客人「這是你加入時的價格,現在活動已結束」)。動態價看似「永遠最新」是優點,但在購物車這個「東西會放很久」的場景,「最新」反而變成「跟客人的預期對不上」

當然快照也有它的代價——價格漲了客人無感、活動細節要跟著快照存。這套系統靠 CheckoutSession(結帳時的快照)在最後一關補救。但如果重來,我會讓購物車本身就存快照,而不是靠後面補。

一個附帶的缺口:購物車沒有 TTL

順帶講一個這套系統的缺口:購物車沒有過期機制。 加了就一直在。這在動態價的設計下更麻煩——一個放了半年的車,裡面的商品可能早就下架、價格早就變了。加一個 TTL(例如 30 天沒動就清)能省掉很多「殭屍購物車」的問題。這是那種「不做也能動、但遲早會咬人」的設計,值得一開始就想到。

收:購物車的三個設計點

  1. 雙軌是必須:訪客 session + 會員帳號,合併時要重查庫存、回報被跳過的品項。
  2. 價格傾向快照:東西會放很久的場景,「動態最新」反而跟客人預期對不上——我選了動態,事後覺得該選快照。
  3. 記得加 TTL:殭屍購物車放久了,商品下架、價格變動,都是坑。

接下來往哪走

  • 庫存預佔 — 「加車不佔庫存、結帳才預佔」——購物車和庫存的界線在哪
  • 促銷疊加規則 — 購物車價格背後,促銷是怎麼疊上去的
  • 訂單狀態機 — 結帳把購物車變成訂單的那一刻