先收斂出一份「一張訂單所需的全部事實」,再落地成 Order。


結帳 controller 是電商裡最容易爛掉的地方。它要處理表單驗證、超商門市、發票欄位、運費、付款方式、訂單建立、寄信、導向金流⋯⋯一路寫下來,八百行起跳。

我讓它不爛的方法是:在寫進資料庫之前,先把所有東西收斂成一個物件。

CheckoutInput(顧客在結帳頁填的)
     +
CartState(跑完計價 pipeline 的購物車)
     +
ShippingQuote(依地區與配送方式算出的運費)
     ↓
CheckoutState
     ↓
Order(寫 DB)

CheckoutInput 只承載顧客意圖:聯絡資訊、收件地址、配送方式、付款方式、發票。裡面沒有任何一個金額欄位。 表單不該決定價錢。

CheckoutState 是三者合體,它負責最後兩個數字:

public function merchandiseTotal(): int   // 商品應付(促銷後、未含運費)
public function grandTotal(): int          // merchandiseTotal + shippingFee

到這一步,這個物件就包含了「一張訂單所需的全部事實」。接下來寫 DB 的那段程式碼,只做一件事:照抄。

'items_subtotal' => $cart->itemsSubtotal(),
'discount_total' => $discountTotal,
'shipping_fee'   => $checkoutState->shippingFee(),
'total'          => $checkoutState->grandTotal(),

沒有任何計算發生在寫 DB 的那一層。persistOrder() 裡面不該出現加減乘除。它只是把算好的事實搬進資料表。

這個切法帶來幾個實際的好處:

訂單是歷史快照。 落地時商品名稱、單價、運費、付款方式的顯示名稱全部存下來,之後商品改名改價,這張舊訂單看起來還是當時的樣子。不要在訂單頁 join 回去讀現在的商品資料。

不信任畫面。 結帳送出時,我不看畫面傳回來的金額,而是從 session 重建購物車、重新跑一次完整 pipeline。畫面上的數字只是顯示用的,最終金額一律以伺服器重算的為準——不然你就等著被人改 hidden input。

運費算在最後。 免運門檻要用「促銷後的商品應付金額」當基準,所以運費必須等 pipeline 跑完才能算。順序是:先算促銷、再算運費、最後加總。

結帳的複雜度本身沒辦法消失,但你可以把它擠到一個地方:在建立 CheckoutState 之前,什麼都可以髒;建立之後,只剩下照抄。


⭐️ Shopify 網站開發服務(給品牌)
https://job.turn.tw/shopify-services

⭐️ 川川電商事務所(全客製電商開發服務)
https://chuanchuan.tw

⭐️ 台灣 Shopify 商家交流 LINE 群(非官方)
https://line.me/ti/g2/PZ_1LILWVWWuzZQ50HNpYA-A3k6QXWF6znqoBQ

⭐️ 台灣 Shopify 開發者 LINE 群(非官方)
https://line.me/ti/g2/YUasX5K3CJ4QdIx76zppjHlh3-q8w-xkSyK1LA

共有 0 則留言


⭐️ Shopify 網站開發服務(給品牌)
https://job.turn.tw/shopify-services

⭐️ 川川電商事務所(全客製電商開發服務)
https://chuanchuan.tw

⭐️ 台灣 Shopify 商家交流 LINE 群(非官方)
https://line.me/ti/g2/PZ_1LILWVWWuzZQ50HNpYA-A3k6QXWF6znqoBQ

⭐️ 台灣 Shopify 開發者 LINE 群(非官方)
https://line.me/ti/g2/YUasX5K3CJ4QdIx76zppjHlh3-q8w-xkSyK1LA
🏆 本月排行榜
🥇
站長阿川
📝45   💬1  
643
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次