行銷規則的爭議幾乎都是「用哪個小計當基準」,備好四個就夠了。
行銷單位提的需求,聽起來每次都很新。但你把它翻成程式碼,會發現爭議永遠只有同一個:
這一折,是折在哪個數字上?
這些問題沒有標準答案,答案取決於這次的客戶想怎麼賣。所以你要做的不是「決定一個正確答案」,而是準備好一組足以表達所有答案的小計。
我的購物車狀態上有四個:
itemsSubtotal() // 商品 + 加價購 的原價總和
linesSubtotal() // 只有商品,不含加價購
adjustmentsTotal() // 目前累積的所有調整(折扣為負)
runningTotal() // itemsSubtotal + adjustmentsTotal
就這四個。每一條促銷規則第一行要做的事,就是從裡面挑一個當基準。
「滿額門檻不含加價購」 → 用 linesSubtotal()
if ($state->linesSubtotal() < $promo->threshold) {
return $state;
}
「滿額門檻要含加價購」(客戶就是想衝客單價)→ 換成 itemsSubtotal()。改一個方法名,需求就翻面了。
「折扣碼要折完再折,變成連乘」 → 用 linesSubtotal() + adjustmentsTotal()
$base = max(0, $state->linesSubtotal() + $state->adjustmentsTotal());
滿額 9 折之後再折 9 折,結果是 81 折。
「兩個折扣都要用原價當基準,變成相加」 → 把 + adjustmentsTotal() 拿掉,改用 linesSubtotal()。9 折 + 9 折 = 8 折。
「點數最多折訂單金額的 20%」 → 用 runningTotal(),也就是所有促銷都套用完、還沒扣點數之前的應付金額。
$cap = (int) floor($state->runningTotal() * $maxRedemptionPercent / 100);
「免運門檻要用實付金額算」 → 用 payable(),連點數折抵都扣掉之後的數字。
因為它們是兩個維度交叉出來的:
不含調整 含調整
不含加價購 linesSubtotal (組合而成)
含加價購 itemsSubtotal runningTotal
加上一個單獨的 adjustmentsTotal(),你就可以自由組合出任何一格。四個是我覺得剛好的數量——再少就湊不出來,再多就開始有人記不住誰是誰。
而且它們全部都是唯讀的推導,不是儲存的欄位。CartState 裡真正存在的只有 lines、addOns、adjustments,這四個小計都是當場加總出來的。所以永遠不會有「小計跟明細對不起來」這種事。
這件事的價值,是它把行銷需求的討論變得可以進行。
以前跟行銷開會,對方說「滿額折扣要加強一點」,工程師說「好」,兩個禮拜後上線發現彼此想的不一樣。
現在我會直接問:「門檻用 linesSubtotal,也就是不含加價購,對嗎?折扣基準用折後金額,所以跟前面的活動是連乘,對嗎?」
對方不一定聽得懂變數名稱,但他一定聽得懂「含不含加價購」、「折完再折還是各折各的」。而這兩個問題,就是那四個小計的全部內容。
行銷玩法會一直變,但它們變的都是同一件事:基準怎麼挑、順序怎麼排。 這兩件事我都準備好了,所以新需求進來,我通常只要新增一條規則、選一個基準、在 pipeline 裡插一行。