CreditCardGatewayType — 信用卡閘道路由機制
CreditCardGatewayType 是一張路由票,決定這筆信用卡訂單要讓哪個 Gateway 執行實際授權。
1 | public enum PayProcessCreditCardGatewayTypeEnum |
預設值:PayProcessContextEntity 初始化時預設 NCCC(context 第 57 行)

四個 Gateway 各代表什麼?
| 值 | 代表 Gateway | 授權方式 | 典型付款方式 |
|---|---|---|---|
NCCC |
聯卡中心 | 直連 NCCC API | 台灣信用卡一次付清、分期 |
TapPay |
TapPay SDK | 帶 Prime Token 呼叫 TapPay | TapPay 刷卡、Apple Pay (TW) |
Nine1Payment |
九一金流 | 帶 CardToken 呼叫 91Payments | 91Payments 信用卡 |
PaymentMiddleWare |
PMW 統一代理層 | 轉發到 PMW,由 PMW 選 Plugin | Stripe、CheckoutDotCom、KPay、Razer… |

和 FlowType 的關係
CreditCardGatewayType 是在 FlowType 框架內的第二層路由。
flowchart TD
A["PayProcessFlowType\n第一層路由"] -->|CreditCardProcess| B["CreditCardGatewayType\n第二層路由"]
A -->|ThirdPartyProcess| C["PaymentMiddleWare\n(CreditCardGatewayType 固定為 PMW)"]
B --> D["NCCC"]
B --> E["TapPay"]
B --> F["Nine1Payment"]
style A fill:#4A7CB5,color:#fff
style B fill:#7B5EA7,color:#fff
style C fill:#5BA85B,color:#fff
ThirdPartyProcess(PMW 金流)→ CreditCardGatewayType 幾乎固定為PaymentMiddleWareCreditCardProcess(台灣信用卡直連)→ 才需要進一步看是 NCCC / TapPay / Nine1Payment

CreditCardGatewayType 由 Cart 端的 GetPayDataProcessor 決定,透過 CompleteForNewCartV2 POST body 帶給 mweb。
flowchart LR
A["前端結帳\nCheckoutOrderCompleteRequest"] --> B["Cart\nGetPayDataProcessor"]
B --> C{"決定 CreditCardGatewayType"}
C --> D["OldPayProcessContext\n(context)"]
D -->|POST| E["mweb\nCompleteForNewCartV2"]
E --> F["Request.ToTypedObject\n直接還原整個 context"]
F --> G["_payProcessService\n.CreateTradesOrder(context)"]
style B fill:#4A7CB5,color:#fff
style E fill:#7B5EA7,color:#fff
Step 1:查 ShopStaticSetting → OriginalCreditCardGatewayType
1 | group: PaymentServiceProvider |
- 有值 → Parse 為 enum,設為
OriginalCreditCardGatewayType - 無值 → 預設
PaymentMiddleWare
Step 2:決定最終 CreditCardGatewayType
1 | if (checkoutContext.Request.CreditCardGatewayType.IsNullOrWhiteSpace()) |
特殊情境:Apple Pay 強制 Nine1Payment
1 | // Cart 端直接強制覆蓋 |
GetCreditCardGateway 優先序
1 | 1. ShopStaticSetting (shopId=0 全域) |
CreditCardGatewayType 在 mweb 的 Processor Pipeline 裡共有 7 個分流點,每個點的行為完全不同。

CheckCreditCardProcessor — 格式驗證
1 | // TapPay / Nine1Payment → 不做 NCCC 格式驗證,直接略過 |
ArrangeDataProcessor — 收單行設定
1 | // 僅 TapPay / Nine1Payment 才從卡片資訊取 AcquiringBankCode |
AuthProcessor — 實際發起授權(核心分流)
1 | // 設定 PaymentServiceProvider 欄位(下游路由依據) |
PMW 核心:走
default分支,paymentRequest.PaymentServiceProvider = "PaymentMiddleWare"告知下游路由到 PMW,而非 NCCC 直連。
MapToTradesOrderProcessor — 訂單 Secret 寫入
flowchart TD
A["CreditCardGatewayType"] -->|TapPay| B["TapPayCardInfo\n→ LastFour / IdentificationCode"]
A -->|Nine1Payment| C["PaymentInfo.CreditCardInfo\n→ FirstSix / LastFour"]
A -->|"NCCC / PaymentMiddleWare"| D["不特別寫入卡資料\n(PMW 授權後另外回填 CreditCardInfo)"]
A -->|"ApplePay + Nine1Payment 特例"| E["GetCreditCardTradesOrderSecret\n(PaymentInfo.CreditCardInfo)"]
style B fill:#4A7CB5,color:#fff
style C fill:#7B5EA7,color:#fff
style D fill:#888,color:#fff
style E fill:#D4813A,color:#fff
AfterOrderProcessor — 快速結帳卡資訊組裝
訂單成立後,組 PayTypeExpressCreditCardEntity 的資料來源因 Gateway 不同而完全不同:
1 | switch (CreditCardGatewayType) |
CreateRegularOrderProcessor — 定期購 Token 記錄
1 | switch (CreditCardGatewayType) |
分流影響總覽
| Processor | NCCC | TapPay | Nine1Payment | PaymentMiddleWare |
|---|---|---|---|---|
CheckCreditCardProcessor |
✅ 驗證格式 | ❌ 跳過 | ❌ 跳過 | ✅ 驗證格式 |
ArrangeDataProcessor(收單行) |
❌ 不設定 | ✅ 從 TapPayCardInfo 取 | ✅ 從 CreditCardInfo 取 | ❌ 不設定 |
AuthProcessor(查 ShopGateway) |
✅ 查 MerchantId | ✅ 查 MerchantId | ❌ 不查 | ❌ 不查 |
AuthProcessor(授權方法) |
CreateAuth (default) |
ByTapPay |
ByNine1Payment |
CreateAuth (default) |
MapToTradesOrderProcessor |
❌ 不寫卡資料 | ✅ TapPayCardInfo | ✅ CreditCardInfo | ❌ 不寫(PMW 另回填) |
AfterOrderProcessor(卡資訊來源) |
TapPayCardInfo | TapPayCardInfo | CreditCardInfo | context.CreditCardInfo |
CreateRegularOrderProcessor |
✅ TapPay Token | ✅ TapPay Token | ✅ Nine1Payment Token | ❌ 不支援 |

PaymentMiddleWare 是四個 Gateway 中設計最不一樣的一個
PMW 授權後的特殊回填:context.CreditCardInfo
NCCC / TapPay / Nine1Payment 在建單前就知道卡資訊(帶進 context),但 PMW 的卡資訊是授權後才由 PMW 回傳。
1 | NCCC / TapPay / Nine1Payment: |
因此 AfterOrderProcessor 對 PMW 要從 context.CreditCardInfo 取資料,而非 TapPayCardInfo 或 PaymentInfo.CreditCardInfo。

flowchart TD
A["用戶選擇付款方式\n(PayProfileType)"] --> B["Cart\nGetPayDataProcessor\n決定 CreditCardGatewayType"]
B -->|"CreditCardOnce\nCreditCardInstallment"| C["查 ShopStaticSetting\nCreditCard/GatewayType"]
C --> D{"有值?"}
D -->|是| E["Parse → NCCC / TapPay / Nine1Payment"]
D -->|否| F["預設 NCCC"]
B -->|"Stripe / CheckoutDotCom\nKPay / Razer ..."| G["查 ShopStaticSetting\nPaymentServiceProvider"]
G --> H{"有值?"}
H -->|是| I["Parse → 通常為 PaymentMiddleWare"]
H -->|否| J["預設 PaymentMiddleWare"]
B -->|"ApplePay(特殊)"| K["強制 Nine1Payment"]
E --> L["context 帶著值\nPOST → mweb"]
F --> L
I --> L
J --> L
K --> L
L --> M["mweb CompleteForNewCartV2\nGetPayProcessDataProcessor 重新計算"]
M --> N["Pipeline 分流\n(7 個分流點)"]
style B fill:#4A7CB5,color:#fff
style M fill:#7B5EA7,color:#fff
style N fill:#5BA85B,color:#fff🔴 純 Enum 無行為封裝 — 違反 OCP
PayProcessCreditCardGatewayTypeEnum 是純 int enum,所有分流邏輯散落在 7 個 Processor 的 if / switch 中。新增第 5 個 Gateway 時,每個 Processor 都要同時修改:
1 | CheckCreditCardProcessor.cs → if TapPay || Nine1Payment |

🔴 default 分支靜默吸收未知 GatewayType
AuthProcessor 的 default 同時涵蓋 NCCC 和 PaymentMiddleWare,未來新增 Gateway 若忘記加 case,**不會有編譯錯誤,會靜默走 CreateAuth**:
1 | switch (context.CreditCardGatewayType) |
🟠 Enum 預設值 0 = NCCC 的隱形陷阱
未初始化或 JSON 反序列化時欄位缺失,int 預設值 0 靜默對應 NCCC。若跨版本部署時 context cache 結構不一致,PMW 訂單可能靜默降級走 NCCC 授權路徑。

🟠 前端可竄改 GatewayType — 缺乏服務端驗證
Cart 端若前端帶入 CreditCardGatewayType,mweb 直接 Parse 使用,沒有驗證該值是否與商店的 PayProfileType 設定相符,存在惡意繞過授權路徑的風險。

改善方向:Gateway 策略介面
將每個 Gateway 的差異行為抽成實作同一介面的策略類別,Processor 只依賴介面,不再散落 switch。
介面設計
1 | public interface ICreditCardGatewayStrategy |
工廠:讓未知型別拋例外而非靜默
1 | public class CreditCardGatewayStrategyFactory |
Processor 改寫示意
1 | // CheckCreditCardProcessor — Before |
1 | // AuthProcessor — Before |

改善效果
| 面向 | 改前 | 改後 |
|---|---|---|
| 新增第 5 個 Gateway | 改 7 個 Processor | 新增 1 個策略類別 |
| PMW issuer 靜默回空 | bug 存在 | 介面強制實作,顯式處理 |
| 未知 GatewayType | default 靜默吸收 |
Factory 拋出 NotSupportedException |
| NCCC + TapPay 語意混用 | 共用 case 導致混淆 | 各自獨立實作 |
| PMW 定期購不支援 | 靠缺少 case 隱性排除 | return null 明確表達 |



