從 DB 撈出來的促銷設定,先組成一份 Catalog,再丟進計價邏輯。


這是一個很小的設計,但它決定了你的促銷模組能不能長期維護。

促銷規則需要參數:門檻是多少、折幾折、送什麼贈品、加購價多少。這些參數住在資料庫,而且會被後台改來改去。

很多人的寫法是,在促銷規則裡面直接查 DB:

// 不要這樣
$tiers = ThresholdDiscount::orderBy('threshold')->get();

一旦這樣寫,計價邏輯就跟資料庫黏死了。測試要開 DB、要寫 seeder;想做一個「試算這筆訂單」的後台工具,你得先在 DB 裡塞假資料。

我的做法是中間放一份 PromotionCatalog——這次購物適用的所有促銷設定,一個唯讀的物件:

new PromotionCatalog(
    thresholdTiers: [...],   // 滿額折扣階梯
    thresholdGifts: [...],   // 多檔滿額禮
    coupon: ...,             // 一次購物一組
    addOnOffers: [...],      // 多檔加價購
    pointsProgram: ...,      // 一次購物一組
);

有一個細節我很在意:這裡的參數順序,刻意對齊 pipeline 的執行順序。滿額折扣 → 滿額禮 → 折扣碼 → 加購 → 點數。讀這個類別的人,順手就理解了計算順序。

然後另外做一個 PromotionCatalogBuilder,它唯一的工作是「DB model → Promotion DTO」的對應:

public static function build(?string $appliedCouponCode = null): PromotionCatalog

這個 builder 裡面沒有任何一行數學。沒有 if 金額 >、沒有 * 0.9、沒有 floor()。它只是撈資料、換個形狀。數學全部留在計價規則裡。

分開之後你會得到幾個很實際的東西:

  • 促銷規則的測試不需要資料庫,手動 new 一份 catalog 就能跑。
  • 後台想做「訂單試算」、「這張購物車套用哪些活動」的工具,直接組 catalog 丟進去。
  • 哪天促銷設定要改成從別的地方來(快取、設定檔、外部活動系統),只改 builder,計價邏輯一行都不用動。
  • 哪一檔活動「有沒有資格參加」的過濾(過期、停用、會員限定),發生在 builder,不會汙染規則。catalog 一旦組出來,裡面每一筆都是「這次確定適用」的。

一句話:builder 負責回答「這次有哪些活動」,rules 負責回答「這些活動怎麼算錢」。 兩件事分開,促銷模組就不會爛掉。


⭐️ 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
本數據每小時更新一次