超賣會炸、金流會叫,折扣算錯不會有任何聲音——客人多付了 13 元不會知道,少收了 13 元你也不會知道,直到對帳那天一筆一筆挖。這章講優惠:規則怎麼收納、聰明演算法怎麼輸給主播的一句話、以及讓分攤永遠加總相等的笨算術。
當年的優惠分三個棲息地:

多件優惠可以疊,一個商品掛多種——那客人的購物車該套哪個組合?當年的第一版答案很工程師:寫一個最優惠組合演算法,幫客人算出全場最省的套法。這是組合優化,朝著 NP-hard 的方向長,但商品數不大,算是算得動。
然後主播說:不要。按照順序,每次採最大扣除就好。
我很久之後才真正理解這個要求有多對。最優解的問題不在算力,在它的答案沒辦法對人解釋:
主播要的規則精確地說是:優惠有固定順序,依序一個套到滿、再換下一個——先把買 5 送 3 套到不能再套,才輪到買 3 送 2。答案穩定、單調、可預期——它不是全域最省,但每一步都看得懂。這跟狀態機被主播拔掉是同一族的故事:工程師寫了聰明的,現場要求換笨的,而現場是對的。收一句:演算法的正確性標準由使用情境定義——直播的「正確」,是客人聽得懂、數字不跳。
優惠算完的結果,直接在 orders payment、order、order item 上開欄位——哪層合適放哪層。這不是隨性,是三層結構的正確用法:錢的欄位跟著它的語意住——商品自己的多件優惠寫在 item、檔期限定券寫在 order(它的範疇就是檔期)、全檔期合算的寫在 payment。金額一經結帳就定格(承諾點原則),發票、退款、對帳全部站在定格值上。
金額全部用整數算——這是錢的程式碼的第一戒律。真正的考驗在分攤:一張檔期券折了 100 元,攤在兩件商品上;客人退其中一件,該退多少?

規則只有兩條:按比例算出的優惠後價格,無條件捨去小數(零頭讓給客人,公司吸收——爭議永遠往對客人有利的方向倒);最後一份不算比例,用減法補到總額(總和恆等是用結構保證的,不是用測試保證的)。這是 largest-remainder 分攤法的務實簡化,而且當年立了個好規矩:類似的情境一律照此處理——分攤算法全系統只有一種,對帳的人只需要理解一次。
這章的當年設計大半保留:三軸參數表、admin 實驗層、固定順序套滿、floor-and-subtract 全是對的。重來的清單不長:
最優組合演算法是我們寫過技術含量最高的程式碼之一,也是被砍得最快的之一。它輸給 greedy 的原因,值得每個工程師背下來:演算法的總成本=計算成本+解釋成本,而面向消費者的系統裡,解釋成本幾乎總是大頭。客人問「為什麼是這個折扣」時,客服要能答、主播要能講、工程師要能查——最優解在這三關全滅。這是主播第三次教我們設計(狀態機、重喊、greedy),而三次的教訓是同一條:現場智慧的核心是可解釋性,系統要嘛遷就它,要嘛被繞過。
優惠系統最怕長成 if 海——每檔活動加一段特判,兩年後沒人敢動。當年的結構避開了它:會重複的模式(滿額×範疇×效果)收斂成參數表,新券=填表;不確定的點子(花式買 A 送 B)隔離進 admin 試驗場,爛了就丟、站穩了才升格。**規則引擎的真義不是「能表達一切」——是把重複的變便宜、把實驗的變安全。**兩層各司其職,行銷的創意速度和系統的可維護性才能同時活著。
floor-and-subtract 在數學家眼裡很醜:分攤不按精確比例、最後一份用減法硬補。但錢的程式碼要回答的從來不是「分得準不準」,是**「加總相不相等、零頭歸不歸屬、事後查不查得清」**——這是會計的標準,不是數學的標準。0.1 元分給誰根本不重要,重要的是每一分錢都有主、退款永遠退得出一個明確的數、對帳永遠對得平。錢的程式碼,優雅讓位給對帳——這句話值得貼在每個電商工程師的螢幕上。
那個被砍掉的最優解演算法,到底是不是 NP-hard?明天用一整篇做誠實的複雜度鑑定。
本文改寫自我的部落格系列《Re:從零開始做直播代購電商平台》,本篇完整版:https://blog.aidan.tw/blog/rezero-promotion/