購物車的付款與配送選單

把可用金物流,整理成結帳選項

商品能搭配哪些付款與配送方式,前面的流程已經算好。這個 Processor 接著整理出購物車與結帳頁面可呈現的兩份選單。

承接什麼?前面算好的候選資料

  • 可用金物流:CheckoutType.PayTypeList 與 CheckoutType.DeliveryTypeList,包含候選付款方式、配送方式與已算好的運費。
  • 可用分期:context.MatchedInstallmentList,用來展開可選的分期期數。
  • 本次購物車狀態:目前配送選擇、配送地區、金額與運費券等資訊,影響篩選與推薦結果。

執行時也會取得金物流配對設定,確認哪些付款方式能搭配哪些配送方式。

重點產出:寫回兩份顯示清單

結果寫入同一份購物車的 CheckoutType,供後續流程與頁面使用。

可以怎麼付款?

DisplayPayTypeList

整理付款分類、分期與顯示資訊,再依本次採用的物流篩選。

可以怎麼收貨?

DisplayShippingTypeList

整理配送方式、地區與運費明細,保留可用選項及推薦標記。

在流程中負責哪一段?

  1. 前面:準備計算資料算好商品金物流交集、運費與可用分期。
  2. 這裡:整理顯示選單組裝付款與配送清單,供頁面與後續流程使用。
  3. 後面:處理預選PickupShoppingCartCheckoutTypeProcessor 維護目前選中的付款與配送方式。
這裡產生的選單還會經過後續門檻與折抵規則調整,才成為最終回傳結果。圖中呈現主要職責順序,省略中間的重量資訊整理。
從 API 入口,走到金物流選單

建立或重算購物車時,都可能經過

不同 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有已選贈品、指定金物流贈品活動,或宅配重量計價時,才進入重算。

促銷前與促銷後,各整理一次

  1. API 匯入共用計算各自的外層流程進入 LogisticCalculate。
  2. 促銷前:建立初始選單準備交集、運費與分期後,由本 Processor 整理選單,再由後續流程處理預選與折抵。
  3. 計算優惠碼與促銷調整商品資格與促銷結果。
  4. 促銷後:更新選單重算金物流資料,本 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 載入後重算 可能已有上次保存的內容。
同一輪進入促銷後重算 可能已有促銷前建立、篩選過的內容。

清單的一生

  1. 上游:準備付款原料商品付款資料 → 交集與相容性篩選 → CheckoutType.PayTypeList。
  2. 第一次賦值:組裝顯示群組(CalculateCheckoutPayTypeAsync)建立暫存 payTypeGroupList,分組、排序、展開分期後,整份寫入。
  3. 第二次賦值:留下本次可用群組(CalculateCheckoutShippingTypeAsync)依本次採用的物流與特殊訂單條件,篩選並重新賦值。
  4. 後續:移除不適用選項依序檢查分期門檻、購物金、禮物卡與 HamiPoint 的不相容付款規則。
  5. 目前是否為促銷前階段?
    是:回到第一次賦值 ↶
    優惠碼、促銷與商品資格更新 → 重算 PayTypeList → 重新組裝,再套用篩選與後續規則。
    否:繼續往下 ↓
    完成最後一輪後,進入分期金額更新與保存。
  6. 完成最後一輪:更新金額並保存更新分期每期金額 → 外層保存快取/回傳。下一次重算從快取還原,再重新準備來源資料。

上圖呈現完整走完促銷前、促銷後計算的主線;跳過、中斷或外層重算會改變實際路徑。配送明細為空且已選海外地區時,本 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,是另一個層次。

  1. 第一次:建立新的付款選單檢查「至少有一種物流能搭配」,再依付款分類組裝。新清單會取代舊清單。
  2. 第二次:依本次物流收斂檢查「能搭配本次採用的物流」,再套用特殊訂單規則。產生新的 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 保存底層付款明細。因此第二次篩選的順序是:

  1. 使用本次採用的 ShippingProfileTypeDef 查詢金物流 Mapping。
  2. 取出該物流對應的 StatisticsTypeDef 集合,存入 statisticsTypeDefs,再依特殊訂單條件調整。
  3. 比對各顯示群組的 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

把底層付款明細整理成畫面選項:同類付款先分組,分期展開成多筆,再補上名稱、說明與支援卡別。

核心工作:組裝新的付款選單

  1. 取得來源

    讀取 CheckoutType.PayTypeList、上游可用分期與已取得的金物流 Mapping,建立暫存清單。

  2. 分組與排序

    以 StatisticsTypeDef 分組,再依付款分類的排序設定決定前後順序。

  3. 逐組檢查與組裝

    至少有一種相容配送才處理;沒有就跳過該分類。有相容配送時,再進入下方各金流分支。

  4. 整份寫回

    完成所有分類後,執行 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 決定。 先有市場預設順序,後面的設定符合條件才覆蓋:

  1. 市場預設

    依 SystemDefault:Country 使用 TW 或其他市場的內建順序。

  2. 全域活動

    現在位於全域活動期間內,改用全域活動排序;否則沿用市場預設。

  3. 商店活動

    商店在 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 使用相同的展開方式:

  1. 直接使用上游傳入的 matchedInstallmentList。
  2. 先以 HasInterest 排序,無息在前、有息在後。
  3. 同一利息類別內,再依 InstallmentDef 由小到大排序。
  4. 每一筆分期建立一個新的 CheckoutPayTypeGroupEntity。
  5. 每個群組保留相同付款分類與該分類付款明細,但引用不同的 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

它是系統算出的最低費用物流候選。購物車尚未選好物流時,先用這個候選決定可以顯示哪些付款方式。

怎麼挑出來?

  1. 先標出各溫層最便宜的明細(CalculateDeliveryTypeIsCheapestFee)

    同一配送方式、地區內,各溫層挑一筆最低費用明細,標記 IsCheapestFee。不符合配送地區的宅配重量計價明細不參與這次挑選。

  2. 留下可搭配付款的物流(CalculateCheckoutShippingTypeAsync)

    用目前 PayTypeList 的付款分類查 Mapping,留下相容物流,再按配送方式、地區與 Sort 分組。

  3. 加總後比較費用

    每組加總已標記的各溫層費用,費用低的優先;同價時再看 Sort。例如常溫 60 元+冷藏 120 元,合計為 180 元。

  4. 依可否預選調整候選

    原本最便宜的若不可預選,但有其他可預選物流,就改用「可預選中最便宜的」。全部都不可預選時,保留原候選。

因此這個變數最後保存的候選,不一定是所有物流中費用最低的那一個;可否預選也會影響結果。不可預選不等於不能使用。

哪些因素會改變「最便宜」?

因子 對結果的影響
可用付款與物流 無法搭配目前付款方式的物流,不加入候選比較。
配送地區與商品溫層 不同地區可能有不同費用;多溫層要加總各溫層最低費用。
前面已算好的運費 商品、重量或促銷改變運費,都可能改變最低費用候選。
已選運費券 沒選券比較 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。後來換候選時,這個值不會跟著重算。

  1. 原候選:門市自取 0 元

    原候選不可預選,所以 canDefaultSelectedCheapest = false。

  2. 換候選:超商取貨 60 元

    改成可預選中最便宜的 cheapestFeeShippingProfile,但布林值仍為 false。

  3. 推薦標記:不會自動變成 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:

  1. 挑可用物流

    透過金物流 Mapping,留下能搭配目前付款方式的配送明細。

  2. 整理成配送群組

    依配送類型、地區與 Sort 分組,把各溫層明細放入群組的 DeliveryTypeList。

  3. 補上資格與推薦

    帶入 CanDefaultSelected,依最低費用候選及原候選資格設定 IsRecommend;特殊零元訂單情境另排除不適用的後付款配送。

  4. 還原明細選取狀態

    舊清單非空時,依明細 ID 還原 IsSelected;若某溫層完全漏選,補選該溫層明細。

  5. 補名稱,寫回清單

    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:維護已選配送群組。