CalculateShoppingCartCheckoutPayShippingProcessor
把可用金物流,整理成結帳選項
商品能搭配哪些付款與配送方式,前面的流程已經算好。這個 Processor 接著整理出購物車與結帳頁面可呈現的兩份選單。
承接什麼?前面算好的候選資料
- 可用金物流:
CheckoutType.PayTypeList與CheckoutType.DeliveryTypeList,包含候選付款方式、配送方式與已算好的運費。 - 可用分期:
context.MatchedInstallmentList,用來展開可選的分期期數。 - 本次購物車狀態:目前配送選擇、配送地區、金額與運費券等資訊,影響篩選與推薦結果。
執行時也會取得金物流配對設定,確認哪些付款方式能搭配哪些配送方式。
重點產出:寫回兩份顯示清單
結果寫入同一份購物車的 CheckoutType,供後續流程與頁面使用。
可以怎麼付款?
DisplayPayTypeList
整理付款分類、分期與顯示資訊,再依本次採用的物流篩選。
可以怎麼收貨?
DisplayShippingTypeList
整理配送方式、地區與運費明細,保留可用選項及推薦標記。
在流程中負責哪一段?
- 前面:準備計算資料算好商品金物流交集、運費與可用分期。
- 這裡:整理顯示選單組裝付款與配送清單,供頁面與後續流程使用。
- 後面:處理預選
PickupShoppingCartCheckoutTypeProcessor維護目前選中的付款與配送方式。
建立或重算購物車時,都可能經過
不同 API 會匯入共用的購物車計算流程。本 Processor 分別在促銷前、促銷後整理金物流選單。
哪些 API 會經過?
以下皆為 POST。通過前置檢查、沒有提前中斷時,才會繼續走到本 Processor。
| API | 進入情境 |
|---|---|
/api/carts/create | 建立購物車。 |
/api/carts/assist-create | 代客建立購物車。 |
/api/carts/calculate | 購物車頁面重算。 |
/api/checkouts/calculate | 結帳頁面重算。 |
/api/carts/sku-add | 代客下單分支,加入商品後重建購物車。 |
/api/checkouts/create | 有已選贈品、指定金物流贈品活動,或宅配重量計價時,才進入重算。 |
促銷前與促銷後,各整理一次
- API 匯入共用計算各自的外層流程進入
LogisticCalculate。 - 促銷前:建立初始選單準備交集、運費與分期後,由本 Processor 整理選單,再由後續流程處理預選與折抵。
- 計算優惠碼與促銷調整商品資格與促銷結果。
- 促銷後:更新選單重算金物流資料,本 Processor 再依最新結果整理選單,交給後續預選與折抵流程。
/api/carts/get 只讀取快取,不會因讀取購物車就進入這段計算。查看呼叫鏈路與條件
入口各自選擇外層 Pipeline,最後透過 ExecuteLogisticCalculateProcessor 進入 LogisticCalculate。
- 建立購物車:
CartCreateAsync→GetCart。 - 代客建立:
AssistCartCreateAsync→GetCart;代客加入商品則為AssistSkuAddAsync→GetCart。 - 購物車重算:
CalculateAsync→CalculateInnerAsync→CartCalculate。 - 結帳重算:
PayCalculateAsync→PayCalculate。 - 條件式結帳建立重算:
Create→CheckoutCreate→GetCartAndCalculateProcessor→BeforeCheckoutCreate。
促銷前由 ExecuteInitailPayShippingCalculateProcessor 啟動 InitailPayShippingCalculate;促銷後由 ExecutePayShippingCalculateProcessor 啟動 RePayShippingCalculate。兩段都註冊本 Processor,名稱保留原始拼字。
初始金物流可能因已設定物流且非重量計價、特定贈品券情境或未達門檻加價購而跳過;上游金物流無交集也可能中斷流程。因此上圖呈現完整走完兩段計算的主要順序,不代表每次請求固定執行兩次。
來源:CartsController、CheckoutController、CartService、CheckOutService、GetCartAndCalculateProcessor 與 ProcessorDefinitionCenter。
DisplayPayTypeList:從候選資料到畫面選項
前面先算出可用付款明細,這裡再組成顯示選項。清單建立後,還會經過物流篩選、分期門檻與折抵規則,最後保存並回傳。
從哪裡開始?
CheckoutType.DisplayPayTypeList 的型別是 List<CheckoutPayTypeGroupEntity>。它保存的是付款顯示群組,每個群組仍帶著底層付款明細。
| 進入情境 | 清單原本的狀態 |
|---|---|
| 新建購物車,首次進入 | null,不是空 List。 |
| 從 Redis 載入後重算 | 可能已有上次保存的內容。 |
| 同一輪進入促銷後重算 | 可能已有促銷前建立、篩選過的內容。 |
清單的一生
- 上游:準備付款原料商品付款資料 → 交集與相容性篩選 →
CheckoutType.PayTypeList。 - 第一次賦值:組裝顯示群組(
CalculateCheckoutPayTypeAsync)建立暫存payTypeGroupList,分組、排序、展開分期後,整份寫入。 - 第二次賦值:留下本次可用群組(
CalculateCheckoutShippingTypeAsync)依本次採用的物流與特殊訂單條件,篩選並重新賦值。 - 後續:移除不適用選項依序檢查分期門檻、購物金、禮物卡與 HamiPoint 的不相容付款規則。
- 目前是否為促銷前階段?是:回到第一次賦值 ↶
優惠碼、促銷與商品資格更新 → 重算PayTypeList→ 重新組裝,再套用篩選與後續規則。否:繼續往下 ↓
完成最後一輪後,進入分期金額更新與保存。 - 完成最後一輪:更新金額並保存更新分期每期金額 → 外層保存快取/回傳。下一次重算從快取還原,再重新準備來源資料。
上圖呈現完整走完促銷前、促銷後計算的主線;跳過、中斷或外層重算會改變實際路徑。配送明細為空且已選海外地區時,本 Processor 會提前返回,不重建本次清單。
來源詳情:初始化與上游七個準備步驟
初始化:首次建立與重算的差異
CartEntity 建構子會執行 CheckoutType = new CheckoutTypeEntity(),但 CheckoutTypeEntity.DisplayPayTypeList 只有屬性宣告,沒有指定初始值,也沒有建構子替它建立空清單。因此新建物件時,這個節點的值是 null,不是空 List。
第一次真正建立顯示清單的位置是本 Processor 的 CalculateCheckoutPayTypeAsync。在此之前,上游主要準備的是 CheckoutType.PayTypeList。
| 進入本 Processor 的情境 | 進入前的 DisplayPayTypeList |
|---|---|
| 新建購物車,第一次執行到本 Processor | 是 null,尚未產生顯示清單。 |
| 既有購物車從 Redis 載入後重算 | 可能已有上一次保存的清單。 |
| 同一輪計算進入促銷後金物流流程 | 可能已有促銷前產生、經後續規則篩選的清單。 |
所以「本 Processor 之前都沒有資料」只適用於新建購物車首次產生清單的情境,不能套用到所有重算情境。
上游來源:先形成 PayTypeList
兩份清單的層次不同:
| 欄位 | 角色 |
|---|---|
商品的 PayTypeList |
個別商品支援的付款明細。 |
CheckoutType.PayTypeList |
購物車經交集與相容性篩選後的付款明細,供後續計算使用。 |
CheckoutType.DisplayPayTypeList |
從上述明細組裝成的結帳付款群組,包含顯示資料與分期選項。 |
主要資料來源順序如下:
| 順序 | 處理位置 | 賦值或調整 |
|---|---|---|
| 1 | CartCreateProcessor |
透過 Repository 的 GetSalePagePayTypeAsync 取得商品付款資料,保存至 SalepageIdPayTypesDictionary,並配置到商品付款清單。 |
| 2 | GetPayTypeIsAvaliableProcessor |
準備購物車的 EnablePayProfileTypeDef,供後面排除不相容付款方式。 |
| 3 | CalculatePayDeliveryMappingProcessor → PayShippingMappingService.CalculatePayDeliveryMapping |
視 IsSkipSalepagePayDeliveryTypeInitial 旗標,從 Dictionary 還原商品金物流資料,再計算交集。 |
| 4 | PayShippingMappingService.CalculatePayType |
將一般商品與贈品券商品的付款方式依 PayProfileTypeDef 取交集並排序,再套用 EnablePayProfileTypeDef 與優惠碼指定金流限制。優惠碼有指定付款且存在交集時,才篩成指定付款清單。 |
| 5 | 同一方法 | 執行 CheckoutType.PayTypeList = excludeStaleAppPayTypeList。 |
| 6 | CalculatePayShippingMapping |
使用付款與配送 Mapping,從 PayTypeList 移除無法搭配目前配送方式的付款明細。 |
| 7 | GetShopShippingWeightFeeProfileProcessor |
進入跨境付款配送處理時,依海外支援清單再次篩選並覆寫 PayTypeList。 |
這些步驟準備的是 PayTypeList 原料,尚未首次產生 DisplayPayTypeList。若金物流無交集,上游可能設定 IsInterrupted 與 NeedReCalculate,本輪便不一定會繼續執行到顯示清單建立步驟。
兩次賦值,各自做什麼?
這裡的「兩次」指同一次正常執行中的兩個賦值點。促銷前、促銷後各執行一次 Processor,是另一個層次。
- 第一次:建立新的付款選單檢查「至少有一種物流能搭配」,再依付款分類組裝。新清單會取代舊清單。
- 第二次:依本次物流收斂檢查「能搭配本次採用的物流」,再套用特殊訂單規則。產生新的 List 容器,保留的群組物件沿用第一次的物件。
已有已選物流時,以該物流篩選;尚未選物流時,以最低費用候選篩選,候選選擇也考慮可否預選。走到這裡不代表消費者一定已經選好物流。
假設例子:付款清單為什麼會縮小?
假設來源有信用卡與 ATM,現有配送有宅配與超商取貨;其他門檻皆通過,配對設定如下:
| 付款分類 | 宅配 | 超商取貨 |
|---|---|---|
| 信用卡 | 可搭配 | 可搭配 |
| ATM | 可搭配 | 不可搭配 |
- 第一次賦值: 信用卡與 ATM 都有物流可搭配,因此兩者都加入。
- 第二次賦值: 若本次採用超商取貨,只保留信用卡;若採用宅配,兩者都保留。
為什麼用 statisticsTypeDefs?
顯示清單按 StatisticsTypeDef 組成付款分類,所以也用同一欄位比對本次物流允許的分類白名單。PayProfileTypeDef 則是分類底下的具體付款明細。這次篩選保留或移除整個群組,不會逐一重篩群組內的 PayTypeList。
第一次賦值詳情:呼叫順序、分組與各金流規則
第一次賦值:組裝付款顯示群組
本 Processor 的呼叫順序是:
ExecuteAsync → CalculateCheckoutShippingTypeAsync → CalculateCheckoutPayTypeAsync
CalculateCheckoutPayTypeAsync 先建立區域變數 payTypeGroupList = new List<CheckoutPayTypeGroupEntity>()。此時只是建立暫存清單,尚未改動 Entity 的 DisplayPayTypeList。
接著以 CheckoutType.PayTypeList 為來源,依 StatisticsTypeDef 分組,透過 StatisticsTypeDefComparer 排序,預設依市場順序,符合條件時由全域或商店的 EventSort 設定覆蓋。每一組先確認存在可搭配的配送方式,沒有相容配送的付款群組不加入。
| 付款類型 | 組裝與篩選規則 |
|---|---|
CreditCardInstallment、CreditCardInstallment_Razer |
從 MatchedInstallmentList 逐筆建立付款群組,依 HasInterest、InstallmentDef 排序;各選項直接引用對應的分期物件。 |
Wallet |
補上顯示名稱與圖示。 |
GiftCard |
補上顯示名稱。 |
Atome |
SubTotalPayment 須達設定的付款門檻。 |
RazerPay |
選取地區的國碼須為 60,且商店啟用 MYR 自訂匯率。 |
OPPayLater |
嘗試取得啟用分期的說明資訊;查詢失敗或超過等待時間,不因此移除該選項。 |
CreditCardOnce_KPay |
依商店設定補上支援卡別。 |
CreditCardOnce |
依信用卡設定補上支援卡別。 |
| 其他付款類型 | 建立一般付款群組,帶入該組的付款明細。 |
組裝完成後,執行第一次整份賦值:
shoppingCart.CheckoutType.DisplayPayTypeList = payTypeGroupList
這是以新清單取代舊清單,不是向舊清單追加選項。原始碼追蹤時的對應位置為 CalculateShoppingCartCheckoutPayShippingProcessor.cs 第 393 行;後續行號可能隨程式修改而變動,應以方法名稱與賦值敘述定位。
第二次賦值詳情:物流選擇、特殊訂單與分類白名單
第二次賦值:依物流與訂單情境篩選
回到 CalculateCheckoutShippingTypeAsync 後,依前面決定的物流類型建立付款白名單 statisticsTypeDefs。
| 購物車狀態 | 物流篩選基礎 |
|---|---|
已有 SelectedCheckoutShippingTypeGroup |
使用已選物流的 ShippingProfileTypeDef。 |
| 尚未選物流 | 使用最低費用物流候選;候選選擇也會考慮是否允許預選。 |
接著套用訂單情境:
| 情境 | 付款白名單 |
|---|---|
| 一般購物車 | 該物流 Mapping 對應的付款類型。 |
| 只有贈品券商品,且運費為 0 | 只留 FreeOfCharge。 |
| 只有贈品券商品,但運費大於 0 | 排除 FreeOfCharge。 |
| OSM 0 元代客單 | 只留 FreeOfCharge。 |
最後執行第二次整份賦值:
DisplayPayTypeList = DisplayPayTypeList.Where(x => statisticsTypeDefs.Contains(x.StatisticsTypeDef)).ToList()
這會建立新的 List 容器,保留的付款群組物件仍是第一次組裝時建立的物件。原始碼追蹤時的對應位置為同一檔案第 577 行。
因此 DisplayPayTypeList 不只是所有商品支援付款方式的聯集或交集;它還受到當下物流類型與特殊訂單條件限制。
為什麼用 statisticsTypeDefs 篩選?
因為 DisplayPayTypeList 已經是依 StatisticsTypeDef 分組的付款顯示群組,第二次篩選使用相同層級的欄位比對。
| 欄位 | 資料層級 | 使用位置 |
|---|---|---|
PayProfileTypeDef |
底層的具體付款方式。 | 商品付款交集、CheckoutType.PayTypeList 中的付款明細。 |
StatisticsTypeDef |
結帳畫面上的付款分類。 | DisplayPayTypeList 各付款群組的分類欄位;同分類仍可能因分期不同而展開成多個選項。 |
第一次組裝時,程式將 CheckoutType.PayTypeList 依 StatisticsTypeDef 分組,每個顯示群組再透過自己的 PayTypeList 保存底層付款明細。因此第二次篩選的順序是:
- 使用本次採用的
ShippingProfileTypeDef查詢金物流 Mapping。 - 取出該物流對應的
StatisticsTypeDef集合,存入statisticsTypeDefs,再依特殊訂單條件調整。 - 比對各顯示群組的
StatisticsTypeDef,只有在集合內的群組才保留。
statisticsTypeDefs 代表「本次物流與訂單情境允許顯示的付款分類白名單」。
兩次賦值的物流檢查也有不同範圍:
| 賦值階段 | 物流相容性檢查 |
|---|---|
| 第一次:組裝顯示群組 | 該付款分類是否至少有一種可搭配的配送方式;此外還會分組、排序、展開分期及套用付款個別規則。 |
| 第二次:篩選顯示群組 | 該付款分類是否能搭配本次採用的物流,並符合特殊訂單條件。 |
所以第一次不是單純拿到一份 PayTypeList 就直接指派,也不是直到第二次才考慮物流。差異在於第一次檢查「有沒有物流能搭配」,第二次檢查「能不能搭配本次採用的物流」。
此處的篩選粒度停在付款群組層級。保留一個群組時,這段 Where 不會再逐一篩選群組內部 PayTypeList 的 PayProfileTypeDef。本方法前段的原始碼 TODO 也指出,以 StatisticsTypeDef 過濾可用 Mapping,若同分類下某個 PayProfileTypeDef 不存在,可能造成取得最低運費的異常;這是方法前段 Mapping 候選計算的既有提醒,不能把兩個欄位視為完全等價。
離開這裡後,清單還會改變
| 後續處理 | 對清單的影響 |
|---|---|
| Pickup | 移除未達付款金額門檻的一般信用卡分期。 |
| 購物金 → 禮物卡 → HamiPoint | 各自符合檢查條件時,以 RemoveAll 移除不相容付款分類。 |
| 促銷後重算 | 從更新後的 PayTypeList 重建,前一輪被移除的選項會重新接受判斷。 |
| 每期金額計算 | 更新清單引用的分期物件,未必重新賦值整份 List。 |
| 快取保存與還原 | 保存本輪結果,下次載入時可能已有清單;單純 /api/carts/get 不重建。 |
禮物卡與 HamiPoint 通過可用性檢查後就會檢查不相容清單,不能把移除條件理解成「已勾選且折抵金額大於 0」。最終顯示結果取決於最後一輪計算。
後續詳情:四類移除條件、重算、每期金額與快取
離開本 Processor 後:四類移除規則
本 Processor 結束時的清單還不是最終結果。後續依金物流子流程順序,以 RemoveAll 修改現有 List:
| 順序 | 修改位置 | 條件與行為 |
|---|---|---|
| 1 | PickupShoppingCartCheckoutTypeProcessor.ExcludeInvalidPayType |
有選取物流時,以商品小計+所選運費-預估購物金-預估合作夥伴付款-預估禮物卡金額,移除未達 InstallmentAmountLimit 的 CreditCardInstallment 選項。這段判斷針對一般信用卡分期。 |
| 2 | CalculateCheckoutStoreCreditProcessor |
清單含購物金選項,且進入自動填入/已啟用折抵、餘額大於 0 的分支時,依購物金的 PayProfileTypeDefDisallowList 移除不相容付款類型。 |
| 3 | CalculateGiftCardSelectionProcessor → GiftCardSelectionHandler.CheckUnsupportedPayTypeList |
清單含 GiftCard 且有 DisplayGiftCard 時,依禮物卡的 PayProfileTypeDefDisallowList 移除不相容付款類型。 |
| 4 | CalculateCheckoutPartnerPayTypeProcessor → CheckoutHamiPointCalculator.CheckUnsupportedPayTypeList |
合作夥伴計算派送到 HamiPoint,且 HamiPoint 選項與顯示資料可用時,依其 PayProfileTypeDefDisallowList 移除不相容付款類型。 |
上述不相容清單的欄位雖名為 PayProfileTypeDefDisallowList,這幾段 RemoveAll 實作實際比對的是付款群組的 StatisticsTypeDef。
禮物卡與 HamiPoint 的移除條件,不等於「使用者已勾選且實際折抵金額大於 0」。通過可用性檢查後就會檢查不相容清單,不能額外推定一定要有正數折抵才移除。
建立結帳頁時,CreateCheckoutCalculateGiftCardSelectionProcessor 也會呼叫同一個 GiftCardSelectionHandler,因此即使該次建立結帳頁沒有進入完整金物流重算,後續禮物卡處理仍可能修改清單。
促銷後重建:前一輪移除不代表永久消失
初始金物流完成後,外層進行優惠碼、促銷與商品資格調整,再進入 RePayShippingCalculate。本 Processor 會從更新後的 PayTypeList 重新組裝 payTypeGroupList,重做兩次賦值及後續移除規則。
因此促銷前被移除的選項,若在促銷後仍存在於來源 PayTypeList,會重新接受組裝與篩選判斷;它不是永遠保留在某個已刪除狀態。最終是否顯示,取決於最後一輪的來源資料與規則。
清單內容更新:分期每期金額
外層 LogisticCalculate 後續的 CalculateShoppingCartEachInstallmentPriceProcessor 不重新賦值整個清單,而是更新清單引用的分期物件。
| 分支 | 更新內容 |
|---|---|
| 一般信用卡分期 | 從 DisplayPayTypeList 取出 CreditCardInstallment 的 InstallmentType,更新 EachInstallmentPrice;金額計算包含購物金、禮物卡與合作夥伴付款資料。 |
| 已選 Razer 信用卡分期 | 更新 MatchedInstallmentList 的 EachInstallmentPriceDecimal;付款群組建立時直接引用這些分期物件,因此結果也反映到顯示選項。此分支的計算使用總金額扣除購物金與禮物卡。 |
追蹤賦值時不能只搜尋 DisplayPayTypeList =;即使 List 沒有被替換,內部引用物件的欄位仍可能被後續更新。
快取保存、還原與其他同名欄位
| 情境 | 處理 |
|---|---|
| 購物車計算完成 | CacheCartDataProcessor 保存購物車資料,包含 CheckoutType 下的顯示清單。 |
| 結帳重算完成 | CachePayDataProcessor 保存計算結果。 |
| 下次購物車重算 | LoadCacheCartDataProcessor 從 Redis 還原 CartEntity,清單可能已有前次內容。 |
| 下次結帳重算 | LoadCachedCheckoutDataProcessor 從 Redis 還原 CheckoutEntity。 |
單純 /api/carts/get |
讀取已保存結果,不重新組裝付款清單。 |
CartEntityHashTransformer 與 CheckoutEntityHashTransformer 會還原整個 CheckoutType,所以雖然不是直接寫 DisplayPayTypeList = ...,仍是這個節點重新取得資料的入口。
OldCartMapperService 與資料驗證程式也有同名欄位的轉換賦值,但那是在建立另一份模型,不是重新決定新版購物車的付款選項。
就目前新版購物車主鏈路而言,核心修改可分成:本 Processor 內的兩個直接賦值點、後續四類移除規則、分期引用物件更新,以及快取還原。
原始碼對照:初始化、賦值、移除與還原位置
原始碼依據
| 查閱目的 | 檔案 |
|---|---|
| CartEntity 建構子 | src/BusinessLogic/Nine1.Cart.BL.BE/Processor/Cart/CartEntity.cs |
| 欄位宣告與預設值 | src/BusinessLogic/Nine1.Cart.BL.BE/CheckOut/CheckoutTypeEntity.cs |
| 商品付款資料來源 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/Create/CartCreateProcessor.cs |
| 可用付款類型 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/Create/GetPayTypeIsAvaliableProcessor.cs |
| 付款交集與 Mapping | src/BusinessLogic/Nine1.Cart.BL.Services/PayShippingMapping/PayShippingMappingService.cs |
| 跨境付款篩選 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/Create/GetShopShippingWeightFeeProfileProcessor.cs |
| 兩次整份賦值 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/Create/CalculateShoppingCartCheckoutPayShippingProcessor.cs |
| 分期門檻移除 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/Create/PickupShoppingCartCheckoutTypeProcessor.cs |
| 購物金不相容付款移除 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Calculate/CalculateCheckoutStoreCreditProcessor.cs |
| 禮物卡不相容付款移除 | src/BusinessLogic/Nine1.Cart.BL.Services/GiftCard/GiftCardSelectionHandler.cs |
| 建立結帳頁的禮物卡計算 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Checkout/Create/CreateCheckoutCalculateGiftCardSelectionProcessor.cs |
| HamiPoint 不相容付款移除 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Calculate/CheckoutHamiPointCalculator.cs |
| 分期金額更新 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/Create/CalculateShoppingCartEachInstallmentPriceProcessor.cs |
| 購物車快取還原 | src/BusinessLogic/Nine1.Cart.BL.Services/Cache/Hash/CartEntityHashTransformer.cs |
| 結帳快取還原 | src/BusinessLogic/Nine1.Cart.BL.Services/Cache/Hash/CheckoutEntityHashTransformer.cs |
CalculateCheckoutPayTypeAsync
把底層付款明細整理成畫面選項:同類付款先分組,分期展開成多筆,再補上名稱、說明與支援卡別。
核心工作:組裝新的付款選單
- 取得來源
讀取
CheckoutType.PayTypeList、上游可用分期與已取得的金物流 Mapping,建立暫存清單。 - 分組與排序
以
StatisticsTypeDef分組,再依付款分類的排序設定決定前後順序。 - 逐組檢查與組裝
至少有一種相容配送才處理;沒有就跳過該分類。有相容配送時,再進入下方各金流分支。
- 整份寫回
完成所有分類後,執行
DisplayPayTypeList = payTypeGroupList,取代舊清單。
這裡是第一次賦值。返回 CalculateCheckoutShippingTypeAsync 後,才會再依本次採用的物流與特殊訂單條件篩選。
輸入與分組詳情:呼叫路徑、輸出欄位與物流檢查
核心工作
本方法將 CheckoutType.PayTypeList 的底層付款明細,轉換成 DisplayPayTypeList 的結帳付款選項。它負責判斷付款選項是否出現、分組與排序、展開分期選項,以及補齊顯示與卡別資料。
呼叫路徑為 ExecuteAsync → CalculateCheckoutShippingTypeAsync → CalculateCheckoutPayTypeAsync。輸入包含購物車 shoppingCart 與上游準備好的可用分期 matchedInstallmentList,並使用本 Processor 已取得的 _payShippingMappingList。
方法先建立區域清單 payTypeGroupList,逐組處理後才執行 shoppingCart.CheckoutType.DisplayPayTypeList = payTypeGroupList,整份取代舊清單。這是第二節所述的第一次賦值;返回外層後,還會依本次採用的物流與特殊訂單條件進行第二次篩選賦值。
分組依據與共通物流檢查
來源為 shoppingCart.CheckoutType.PayTypeList,使用 GroupBy(i => i.StatisticsTypeDef) 分組。因此顯示選項的分類依據是 StatisticsTypeDef,底層的具體付款方式仍保存在群組內的 PayTypeList。
| 輸出群組欄位 | 意義 |
|---|---|
StatisticsTypeDef |
此選項的付款分類。 |
PayTypeList |
該分類包含的具體付款明細。 |
InstallmentType |
分期選項對應的分期資訊。 |
DisplayName、DisplayIconUrl、AdditionalInfo |
依付款類型需要補上的顯示資訊。 |
一般付款分類通常產生一個顯示選項;信用卡分期類型則會展開成多個選項,因此 StatisticsTypeDef 不是顯示清單中每一筆資料的唯一識別。
在進入各金流分支前,所有分類都先通過物流檢查:從 Mapping 取出該 StatisticsTypeDef 支援的 ShippingProfileTypeDef,確認目前 DeliveryTypeList 至少有一種相容配送。若完全沒有,直接 continue,不加入付款顯示清單。這裡檢查的是「是否存在可搭配物流」,外層才會進一步檢查「是否符合本次採用物流」。
分組與排序是兩件事
分組依據是付款分類 StatisticsTypeDef。 同分類的底層付款明細放在群組的 PayTypeList;分期則能在同一分類下產生多個選項,因此分類不是每筆選項的唯一識別。
排序由 StatisticsTypeDefComparer 決定。 先有市場預設順序,後面的設定符合條件才覆蓋:
- 市場預設
依
SystemDefault:Country使用 TW 或其他市場的內建順序。 - 全域活動
現在位於全域活動期間內,改用全域活動排序;否則沿用市場預設。
- 商店活動
商店在
CustomSortShopIds,有商店設定且活動期間有效時,改用商店排序;否則沿用前一步。
活動時間含開始、不含結束。列在最終排序清單的分類優先;兩者都沒列入時,以字串 CompareTo 比較。後續沒有有效已選付款時,Pickup 會取篩選後的第一筆,因此排序也可能影響預選。
排序設定詳情:設定 Key、覆蓋條件與比較方式
排序依據
方法取得全域與商店的 EventSort 設定,建立 StatisticsTypeDefComparer,再以 OrderBy(i => i.Key, statisticsTypeDefComparer) 排序付款群組。
Comparer 的排序來源依下列順序決定,後面符合條件的設定會覆蓋前面的排序:
| 順序 | 排序來源 | 套用條件 |
|---|---|---|
| 1 | 程式內建市場排序 | 依 SystemDefault:Country 選擇 TW 或其他市場的預設清單。 |
| 2 | 全域活動排序 | 現在時間在全域 StatisticsType.EventStartDateTime 與 StatisticsType.EventEndDateTime 之間時,改用全域 StatisticsType.EventSort。 |
| 3 | 商店活動排序 | 商店位於全域 CustomSortShopIds,且商店設定存在、目前位於商店活動時間內時,改用商店的 StatisticsType.EventSort。 |
活動時間判斷包含開始時間、不包含結束時間。付款分類依最終排序清單的位置比較;有列入清單的分類優先,若兩個分類都未列入,則使用字串 CompareTo 比較。
這個順序也會間接影響預選:後續 PickupShoppingCartCheckoutTypeProcessor 在沒有有效已選付款方式時,取 DisplayPayTypeList 第一筆。實際預選結果仍取決於中間有哪些選項被篩除。
不同金流,各自處理什麼?
所有分支都先通過「至少有一種相容配送」的檢查。以下以處理目的分組:
| 類型 | 本方法的處理 |
|---|---|
CreditCardInstallment、CreditCardInstallment_Razer |
每筆可用分期產生一個選項;分期清單為空就沒有選項。 |
Atome |
SubTotalPayment 達設定門檻才加入,等於門檻也保留。 |
RazerPay |
配送國碼為 60,且商店啟用 MYR 自訂匯率,才加入。 |
Wallet |
補顯示名稱與圖示。 |
GiftCard |
補商店顯示名稱。 |
OPPayLater |
嘗試取得啟用分期說明,寫入 AdditionalInfo;失敗或逾時仍保留選項。 |
CreditCardOnce、CreditCardOnce_KPay |
依各自設定補上支援卡別,設定缺少不因此移除選項。 |
| 其他付款類型 | 建立一般群組,帶入分類與付款明細。 |
OPPayLater 最多等待這次查詢約 1 秒,逾時只停止等待,沒有取消 API 請求。RazerPay 的條件只限這個類型,不能套用到所有名稱帶 _Razer 的付款方式。
各金流完整規則:門檻設定、查詢行為與顯示名稱
不同金流處理表格
下列分支都以前述共通物流檢查通過為前提。
| 付款類型 | 特別處理 | 是否在此分支決定不顯示 |
|---|---|---|
CreditCardInstallment |
從 matchedInstallmentList 逐筆展開分期選項。 |
是;清單為空就不產生分期選項。 |
CreditCardInstallment_Razer |
同樣從可用分期清單展開,並記錄分期資料數量。 | 是;清單為空就不產生分期選項。 |
Wallet |
從 Wallet Service 取得商店顯示名稱與圖示。 | 此分支主要補顯示資料。 |
Atome |
讀取 _N1CONFIG:PaymentLimitList:Atome,檢查 SubTotalPayment。 |
是;小於門檻時排除,等於門檻會保留。實際門檻取設定值,不應以註解中的 10 元視為固定規則。 |
RazerPay |
要求 SelectedShippingArea.CountryCode == "60",且商店啟用 MYR 自訂匯率。 |
是;兩條件必須同時成立。只針對此類型,不泛指所有 _Razer 金流。 |
GiftCard |
從 GiftCard Display Service 取得商店顯示名稱。 | 此分支主要補顯示資料。 |
OPPayLater |
查詢 Nine1Payments 的商店付款資訊,將啟用分期依期數排序後組成 AdditionalInfo。 |
查詢失敗、逾時或例外不因此移除付款選項。 |
CreditCardOnce_KPay |
讀取商店支援卡別設定,轉換後寫入付款明細的 SupportedCardBrandList。 |
此分支補卡別資料,沒有因設定缺少而排除選項。 |
CreditCardOnce |
呼叫 ApplyTwCreditCardSupportedBrandListAsync,依 AMEX 設定補支援卡別。 |
此分支補卡別資料,沒有因設定缺少而排除選項。 |
| 其他付款類型 | 建立一般付款群組,帶入 StatisticsTypeDef 與 PayTypeList。 |
通過共通物流檢查後加入。 |
OPPayLater 的 API 查詢以 Task.WhenAny 與 1 秒計時競速。API 在等待期限內成功時補充說明,否則仍加入原本的群組;此處僅停止等待,沒有取消原 API 請求。
一般分支沒有直接設定 DisplayName,不代表選項沒有名稱;CheckoutPayTypeGroupEntity.DisplayName 的 getter 會依付款分類提供對應名稱,分期名稱也會使用期數與是否有息資訊。
分期展開:一個分類,變成多個選項
兩種信用卡分期都直接使用 matchedInstallmentList,先按 HasInterest 排序,無息在前;同一利息類別內,再按 InstallmentDef 由小到大。
假設來源有「有息 12 期、無息 6 期、無息 3 期」,整理後如下:
| 顯示順序 | 付款分類 | 分期資訊 |
|---|---|---|
| 1 | CreditCardInstallment |
無息 3 期 |
| 2 | CreditCardInstallment |
無息 6 期 |
| 3 | CreditCardInstallment |
有息 12 期 |
每個選項是新群組,但 InstallmentType 直接引用原分期物件。這裡不重新計算分期資格,也不算每期金額;後續仍會檢查一般信用卡分期門檻與更新每期金額。
分期展開詳情:五個步驟、共用清單與物件引用
分期展開選項的邏輯
CreditCardInstallment 與 CreditCardInstallment_Razer 使用相同的展開方式:
- 直接使用上游傳入的
matchedInstallmentList。 - 先以
HasInterest排序,無息在前、有息在後。 - 同一利息類別內,再依
InstallmentDef由小到大排序。 - 每一筆分期建立一個新的
CheckoutPayTypeGroupEntity。 - 每個群組保留相同付款分類與該分類付款明細,但引用不同的
InstallmentType。
例如上游提供以下分期,會得到三個付款選項:
| 展開順序 | StatisticsTypeDef |
InstallmentType |
|---|---|---|
| 1 | CreditCardInstallment |
無息 3 期。 |
| 2 | CreditCardInstallment |
無息 6 期。 |
| 3 | CreditCardInstallment |
有息 12 期。 |
本方法負責展開既有結果,不重新計算分期資格,也不計算每期應付金額。後續 PickupShoppingCartCheckoutTypeProcessor 還會檢查一般信用卡分期門檻,CalculateShoppingCartEachInstallmentPriceProcessor 再更新每期金額。
目前兩種分期分支都直接遍歷傳入清單,本方法沒有再依付款供應商拆分該清單。InstallmentType = installment 也是直接引用原分期物件,不是深層複製;後續修改同一物件,會反映到顯示選項。
信用卡支援卡別,也會在這裡補上嗎?
會,明確補寫的是一次付清的兩個分支。 卡別寫在群組內付款明細的 SupportedCardBrandList,前端如何畫成圖示或文字則由前端決定。
| 分支 | 支援卡別處理 |
|---|---|
CreditCardOnce |
CanUseAECard 為 true 時寫入 Visa、MasterCard、JCB、AMEX;false 或沒有設定則保留原值。 |
CreditCardOnce_KPay |
將商店設定文字轉成卡別名稱清單;沒有設定則保留原值。 |
| 信用卡分期及其他信用卡類型 | 本方法沒有額外套用上述邏輯,以來源明細的資料為準。 |
卡別是直接修改來源付款物件;ToList() 只建立 List 容器,因此更新也會反映到 CheckoutType.PayTypeList。
卡別完整規則:設定來源、KPay 六種轉換與輔助方法限制
信用卡是否在這裡提供支援卡別?
會,但本方法明確補寫卡別的分支是 CreditCardOnce 與 CreditCardOnce_KPay,不是所有信用卡類型一律在此設定。這裡提供的是 Visa、MasterCard 等支援卡片品牌資料;前端如何呈現圖示或文字,不在此方法範圍內。
卡別寫入位置為群組底下付款明細的 SupportedCardBrandList,也就是 DisplayPayTypeList 各群組的 PayTypeList 項目。
| 類型 | 設定來源 | 寫入行為 |
|---|---|---|
CreditCardOnce |
ShopDefault:Group=CreditCard、Key=CreditCardSetting,解析 CanUseAECard。 |
為 true 時寫入 Visa、MasterCard、JCB、AMEX;設定不存在或為 false 時保留原值,不主動清空卡別。 |
CreditCardOnce_KPay |
ShopStaticSetting:Group=PayProfileSupportedCardBrand、Key=CreditCardOnce_KPay。 |
設定存在且非空白時,將逗號分隔內容轉換為 CreditCardBrandEnum 名稱清單,寫入每筆付款明細;沒有設定則保留原值。 |
CreditCardInstallment、CreditCardInstallment_Razer 與其他信用卡類型 |
本方法沒有額外呼叫上述卡別設定邏輯。 | 以來源付款明細已有的資料為準,不能據此方法推定一定有完整卡別清單。 |
KPay 的設定字串轉換如下:
| 設定文字 | 寫入的卡別名稱 |
|---|---|
Visa |
Visa |
Mastercard |
MasterCard |
American Express (AE) |
AMEX |
UnionPay |
UnionPay |
JCB |
JCB |
Diners Club |
DinersClub |
轉換前會去除每段文字前後空白,但 switch 比對使用上述文字;不匹配的值不加入結果。
ApplyTwCreditCardSupportedBrandListAsync 雖然名稱帶有 Tw,目前方法本體沒有額外檢查市場國碼,而是依商店信用卡設定決定是否寫入。其註解提到一次付清/分期共用,但目前 CalculateCheckoutPayTypeAsync 實際只在 CreditCardOnce 分支呼叫,分析應以呼叫位置為準。
卡別設定也會影響來源明細:程式直接修改 payTypeGroup 裡的物件,payTypeGroup.ToList() 只建立新 List 容器,不複製每筆付款物件。因此更新 SupportedCardBrandList 也會反映到來源 CheckoutType.PayTypeList。
原始碼對照:本方法、排序器與付款群組
原始碼依據
| 查閱目的 | 檔案 |
|---|---|
| 本方法與卡別輔助方法 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/Create/CalculateShoppingCartCheckoutPayShippingProcessor.cs |
| 排序來源與優先順序 | src/BusinessLogic/Nine1.Cart.BL.Services/Processor/Cart/StatisticsTypeDefComparer.cs |
| 付款群組與顯示名稱 | src/BusinessLogic/Nine1.Cart.BL.BE/CheckOut/CheckoutPayTypeGroupEntity.cs |
cheapestFeeShippingProfile
它是系統算出的最低費用物流候選。購物車尚未選好物流時,先用這個候選決定可以顯示哪些付款方式。
怎麼挑出來?
先標出各溫層最便宜的明細(
CalculateDeliveryTypeIsCheapestFee)同一配送方式、地區內,各溫層挑一筆最低費用明細,標記
IsCheapestFee。不符合配送地區的宅配重量計價明細不參與這次挑選。留下可搭配付款的物流(
CalculateCheckoutShippingTypeAsync)用目前
PayTypeList的付款分類查 Mapping,留下相容物流,再按配送方式、地區與Sort分組。加總後比較費用
每組加總已標記的各溫層費用,費用低的優先;同價時再看
Sort。例如常溫 60 元+冷藏 120 元,合計為 180 元。依可否預選調整候選
原本最便宜的若不可預選,但有其他可預選物流,就改用「可預選中最便宜的」。全部都不可預選時,保留原候選。
因此這個變數最後保存的候選,不一定是所有物流中費用最低的那一個;可否預選也會影響結果。不可預選不等於不能使用。
哪些因素會改變「最便宜」?
| 因子 | 對結果的影響 |
|---|---|
| 可用付款與物流 | 無法搭配目前付款方式的物流,不加入候選比較。 |
| 配送地區與商品溫層 | 不同地區可能有不同費用;多溫層要加總各溫層最低費用。 |
| 前面已算好的運費 | 商品、重量或促銷改變運費,都可能改變最低費用候選。 |
| 已選運費券 | 沒選券比較 TotalFee;選券後以群組的 EstimateTotalFee 比較。 |
預選資格與 Sort |
不可預選時可能換候選;費用相同時以 Sort 由小到大決定先後。 |
例子:運費券讓宅配變成最便宜
假設宅配與超商取貨都可用、都允許預選,且上游已算好運費券的預估結果:
| 物流 | 沒選運費券 | 選宅配運費券 |
|---|---|---|
| 宅配 | 100 元 | 0 元 |
| 超商取貨 | 60 元 | 60 元 |
- 沒選券: 超商取貨 60 元最低,候選是超商取貨。
- 選了宅配運費券: 宅配預估為 0 元,候選改成宅配。
這裡使用上游的運費結果比較,不在這個步驟重新計算運費券優惠。
怎麼影響付款方式?
先讀取 shoppingCart.SelectedCheckoutShippingTypeGroup。它是購物車目前的已選物流,可能來自消費者,也可能是前一輪系統預選。
| 目前狀態 | selectedShippingTypeDef 採用誰? |
|---|---|
| 已選宅配,即使超商更便宜 | 使用已選宅配的物流類型。 |
| 尚未選物流,超商候選最便宜 | 使用 cheapestFeeShippingProfile 的物流類型。 |
採用的物流類型 → 查金物流 Mapping → 取得 statisticsTypeDefs → 第二次篩選 DisplayPayTypeList。特殊訂單條件仍會進一步調整這份白名單。
這個步驟先決定付款清單以哪種物流為準。 它也參與推薦標記的判斷,後面的 PickupShoppingCartCheckoutTypeProcessor 再維護實際的已選物流群組。最低費用候選不等於消費者已選的結果,也不代表配送顯示清單只剩這一種物流。
費用比較細節:兩層排序與預估費用缺值
計算分成「挑各溫層明細」與「比較物流群組」兩層,使用的欄位與缺值處理不同:
| 層次 | 沒選運費券 | 已選運費券 |
|---|---|---|
| 各溫層最低費用明細 | 按 TotalFee 排序。 |
按 EstimateTotalFee ?? TotalFee 排序,再以 Fee 排序。 |
| 物流群組比較 | 加總 IsCheapestFee 明細的 TotalFee。 |
加總 IsCheapestFee 明細的 EstimateTotalFee ?? 0。 |
物流群組加總後再以 Sort 排序。預估費用缺值時,明細層退回實際費用,群組層卻以 0 加總;追查結果時,兩層不能當成相同規則。
明細先依 ShippingProfileTypeDef 與 ShippingAreaId 分組,再挑各 TemperatureTypeDef 的最低費用。群組加總時的 Key 則包含 ShippingProfileTypeDef、ShippingAreaId 與 Sort。
候選、推薦與提前返回的邊界
canDefaultSelectedCheapest記錄原本最便宜的物流是否可預選;候選換成其他可預選物流後,這個值不會重算。因此替代候選不會只因被換入就得到IsRecommend。IsRecommend還會比對候選的配送類型與地區,不是只比對費用。- 本方法使用付款分類
StatisticsTypeDef找 Mapping 候選。原始碼 TODO 提醒,同分類下若缺少某個底層PayProfileTypeDef,可能造成最低運費取得異常,不能把分類與具體付款明細當成等價。 - 配送明細為空且目前選取海外地區時,
CalculateCheckoutShippingTypeAsync提前返回,不走本次候選與顯示清單重建。
原始碼對照
來源為 CalculateShoppingCartCheckoutPayShippingProcessor.cs:
ExecuteAsync:先計算各溫層最低費用標記,再進入配送群組計算。CalculateDeliveryTypeIsCheapestFee:過濾地區不符的宅配重量計價明細、清除舊標記、選各溫層最低費用。CalculateCheckoutShippingTypeAsync:篩選候選、加總排序、判斷可否預選,並決定付款篩選用的物流類型。
預選與 canDefaultSelectedCheapest
預選就是消費者還沒自己選時,系統先選好一個選項。它會影響後續運費與可搭配的付款方式,不只是畫面停在哪一項。
三個概念,分開看
| 概念 | 意思 |
|---|---|
可否預選:CanDefaultSelected |
true 表示允許系統預先選它,但不保證選中;false 限制自動挑選策略,不等於物流不能使用。 |
推薦標記:IsRecommend |
標示本輪計算推薦的配送群組,不等於已經選中。 |
已選結果:SelectedCheckoutShippingTypeGroup |
購物車目前選中的物流,可能來自消費者,也可能來自前一輪系統預選。 |
預選結果在畫面上通常會呈現勾選狀態,消費者仍可更改。實際畫面呈現由前端負責。
例子:免費的門市自取,為什麼不直接選?
假設尚未選物流,以下三種都可用:
| 物流 | 運費 | 可否預選 |
|---|---|---|
| 門市自取 | 0 元 | 否 |
| 超商取貨 | 60 元 | 是 |
| 宅配 | 100 元 | 是 |
免費不代表消費者方便到門市。這個例子中,系統會把最低費用候選改成超商取貨,再用它篩選付款方式。
門市自取仍可保留在清單,供消費者自行選擇。 不可預選不是停用;能否完成結帳仍須符合其他條件。
這個布林值,記住的是「原本」的候選
在 CalculateCheckoutShippingTypeAsync 中,canDefaultSelectedCheapest 先記錄原本最低費用候選的 CanDefaultSelected。後來換候選時,這個值不會跟著重算。
原候選:門市自取 0 元
原候選不可預選,所以
canDefaultSelectedCheapest = false。換候選:超商取貨 60 元
改成可預選中最便宜的
cheapestFeeShippingProfile,但布林值仍為 false。推薦標記:不會自動變成 true
IsRecommend要求該布林值為 true,並符合候選的配送類型與地區。因此這次替代的超商取貨不會被標成推薦。
沒有推薦標記,不代表後續不能選中。 這個 Processor 先整理候選與推薦;後面的 PickupShoppingCartCheckoutTypeProcessor 再處理已選結果。
預選資格從哪裡來?
上游 CheckShoppingCartCheckoutTypeShippingDefaultSelectedProcessor.ExecuteAsync 先把配送明細的 CanDefaultSelected 設成 true,再依規則調整:
| 物流 | 調整規則 |
|---|---|
宅配 Home |
配送地區的 DefaultSelectedEnable 為 false 時,改為不可預選。 |
門市自取 LocationPickup |
預設改成不可預選;商店符合 LocationPickup:CanDefaultSelected.Enabled.ShopIds 開關時,再改為可預選。 |
商店開關支援全開、單店與商店編號範圍;沒有設定或總開關關閉時,不開啟門市自取預選。
為什麼不可預選不是整條流程的禁止令?
後續 PickupShoppingCartCheckoutTypeProcessor.PickupSelectedShippingType 有不同挑選分支:
- 已選物流仍在顯示清單中時,依配送類型與地區沿用對應群組。
- 需要重新挑選且只有一種配送選項時,直接檢查是否有未超重明細,這個分支沒有檢查
CanDefaultSelected。 - 多種選項時,先找帶
IsRecommend且有未超重明細的群組。 - 沒有推薦時,本地等分支才以
CanDefaultSelected與未超重條件挑選;海外分支則依已選配送地區的明細挑選。
因此可否預選是自動挑選策略的一部分,不能把 false 理解成禁止使用。也不能僅憑這個旗標,推定最後一定選中或一定不選中。
原始碼對照
CheckShoppingCartCheckoutTypeShippingDefaultSelectedProcessor.ExecuteAsync:設定各物流的預選資格。CalculateShoppingCartCheckoutPayShippingProcessor.CalculateCheckoutShippingTypeAsync:記錄原候選資格、必要時替換候選,再設定IsRecommend。PickupShoppingCartCheckoutTypeProcessor.PickupSelectedShippingType:維護SelectedCheckoutShippingTypeGroup。
DisplayShippingTypeList
這份清單回答「目前有哪些配送方式可選」,例如宅配、超商取貨、門市自取。每個選項包含配送明細、地區、名稱,以及可否預選與推薦標記。
從哪裡來?
新建購物車時,CheckoutType.DisplayShippingTypeList 預設為 null。上游先準備 CheckoutType.DeliveryTypeList,包含可用配送明細與已算好的運費,本 Processor 再首次組裝顯示清單。
快取載入或促銷後重算時,可能已有舊清單。本次會重新組裝並覆寫,不是每次都從 null 開始。
五個步驟,最後整份賦值一次
主要組裝位置是 CalculateCheckoutShippingTypeAsync:
挑可用物流
透過金物流 Mapping,留下能搭配目前付款方式的配送明細。
整理成配送群組
依配送類型、地區與
Sort分組,把各溫層明細放入群組的DeliveryTypeList。補上資格與推薦
帶入
CanDefaultSelected,依最低費用候選及原候選資格設定IsRecommend;特殊零元訂單情境另排除不適用的後付款配送。還原明細選取狀態
舊清單非空時,依明細 ID 還原
IsSelected;若某溫層完全漏選,補選該溫層明細。補名稱,寫回清單
AssignShippingTypeNameAsync補上名稱與地區名稱,再執行DisplayShippingTypeList = shippingTypeGroup。
正常完成方法時,配送顯示清單整份賦值一次。 這裡與 DisplayPayTypeList 的兩次賦值不同。提前返回時,不會走到本次清單寫回。
例子:宅配 100 元,超商取貨 60 元
假設只有常溫商品,兩種物流都能搭配目前付款、都允許預選,且沒有其他限制。上游提供宅配/本島/常溫 100 元,以及超商取貨/常溫 60 元,整理後得到:
| 顯示選項 | 包含的明細 | 推薦 |
|---|---|---|
| 宅配 | 本島常溫、100 元 | 否 |
| 超商取貨 | 常溫、60 元 | 是 |
兩個選項都保留。 最低費用主要影響推薦與尚未選物流時的付款篩選基礎,不會只留下超商取貨。
| 原本狀態 | 後續 Pickup 在此例的處理 |
|---|---|
| 還沒有選物流 | 預選有推薦標記的超商取貨。 |
| 原本已選宅配 | 確認宅配仍在清單中後沿用,不因超商更便宜就強制切換。 |
可選清單,與目前選中的是兩件事
| 欄位 | 回答的問題 |
|---|---|
DisplayShippingTypeList |
目前有哪些配送方式可選? |
SelectedCheckoutShippingTypeGroup |
這台購物車目前選哪一種? |
本 Processor 主要建立顯示清單,也整理物流明細的 IsSelected。後面的 PickupShoppingCartCheckoutTypeProcessor 再維護已選物流群組;後續調整明細選取與運費券結果,外層保存快取,下次重算時再載入更新。
保留選取狀態與特殊情境的細節
- 還原使用舊清單中
IsSelected明細的 ID。只對本次仍存在的明細還原;若某溫層完全沒選中,先取該溫層第一筆IsCheapestFee明細,沒有則取第一筆。這段補選在舊清單非空的分支內執行。 - 只有贈品券商品且運費為 0,或 OSM 0 元代客單等分支,會排除
CashOnDelivery、Family、SevenEleven、HiLife、OKmart這些後付款配送類型,不等於排除所有超商取貨。 - 特定建立/重算購物車且已有物流、含贈品券的情境,
CheckPickupShoppingCartCheckoutType若發現溫層選取不完整,會先清空舊顯示清單並重設明細選取。 - 配送明細為空且已選海外地區時,方法提前返回,不執行本次清單重建。
- 上游
CalculateShoppingCartShippingPriceLimitProcessor也有將此欄位設回null的分支,因此「首次組裝點」不代表其他流程永遠不會清除它。
原始碼對照
CheckoutTypeEntity.DisplayShippingTypeList與CartEntity建構子:欄位宣告與初始狀態。CalculateShoppingCartCheckoutPayShippingProcessor.CalculateCheckoutShippingTypeAsync:篩選、分組、選取還原與整份賦值。AssignShippingTypeNameAsync:寫入Name與RegionName。PickupShoppingCartCheckoutTypeProcessor.PickupSelectedShippingType:維護已選配送群組。





