Stripe-Fee
Stripe 請款的時候,其實一次要跟商家收兩筆錢:一筆是平台的系統使用費,另一筆是信用卡本身的手續費。這兩筆錢的費率怎麼查、什麼時候確定、又是怎麼一起送給 Stripe 申報的,就是這篇要拆解的內容
兩種費用的差別
| 費用類型 | 說明 |
|---|---|
系統使用費SalesOrder | 平台向商家收取的服務費,費率與商品分類、供應商有關 |
金流手續費PayProfile | Stripe 信用卡交易手續費,費率與「發卡國家+卡片品牌」的組合有關 |
費用取得的時間點(在 Pipeline 中的位置)
這兩種費率不是一開始就知道,而是隨著下單 Pipeline 一步步往下跑,才逐漸被鎖定、查出來、最後算成金額:
ArrangePayTypeExpressInfoProcessor — 設定卡片品牌與發卡國代碼(這兩個值就是後面手續費計算的依據)GetOrderProcessingFeeProcessor — 分別載入「系統使用費」與「金流手續費」兩種費率ThirdPartyPayApiProcessor — 呼叫費用計算方法算出最終要向 Stripe 申報的手續費金額,隨請款請求一併送出Brand / 發卡國代碼的資料來源(依情境不同)
手續費要算得準,關鍵就在「卡片品牌」與「發卡國家」這兩個值有沒有抓對。金流手續費率正是依「發卡國家+卡片品牌」的組合去查,常見的卡片品牌如下:
但這兩個值在不同付款情境下,來源完全不一樣:
| 使用情境 | 資料來源 |
|---|---|
| 記住信用卡(舊卡復用) | 從 PayTypeExpress 資料表讀取後設定 |
| 定期購自動成單 | 從既有的定期購結帳資訊解析後設定 |
| Google Pay / Apple Pay | 即時向 Stripe API 查詢取得 |
| 新信用卡(首次付款) | 付款完成後才能取得,因此計算手續費時暫時視為未知 |
前面提到 Pipeline 第 19 道會設定卡片品牌與發卡國代碼,但這一步不是「每次結帳都會設定」,而是有明確的觸發條件。這個 Processor 真正的角色是:把「使用者選擇的已記住付款工具」還原成付款所需的機敏資料,只有在使用者明確選擇快速結帳/記住的卡片時才會動作。
identity → PayTypeExpress 記錄 → 機敏付款資訊的還原器,跟「這次是不是輸入全新卡號」完全無關;有沒有觸發,取決於前端這次結帳有沒有帶 identity 上來。
- 前提:
context.ThirdPartyPaymentInfo.ExtendInfo裡必須帶有identity(代表前端已指定「用第幾張記住的卡」) - 用
identity查出對應的PayTypeExpress記錄,解密還原出機敏付款資訊(如 Stripe 的customer_id/payment_method)寫回 context - 只有 Stripe 這個分支才會額外多做一件事:把
Brand/IssueCountryCode一併設進context.CreditCardInfo,專門為了讓後面第 26 道能查到正確的金流手續費率
- 邏輯類似,同樣需要
identity才會觸發 - 從
PayTypeExpress撈出CardToken/BankCode - 處理的是「發卡行資訊可能過期需要更新」的問題,跟手續費計算無關,服務對象也不是 Stripe
GetOrderProcessingFeeProcessor 就是前面提到的 Pipeline 第 26~27 道,它唯一的工作是:把「系統使用費」與「金流手續費」這兩種費率查出來,放進 context.ProcessingFeeInfo,讓後面第 30 道 ThirdPartyPayApiProcessor 能直接拿來算 application_fee_amount。它會不會執行、抓到的是不是正確的值,是這篇最值得深挖的地方。
PayProfileType 決定要不要查、以及查哪一種公式,完全不判斷這次結帳是不是快速結帳、記不記得卡、是不是全新卡。
第一道關卡:只有 HK 環境才會執行 HK Only
1 | // GetOrderProcessingFeeProcessor.Process() |
其他地區的商店,這個 Processor 一進來就直接跳過,context.ProcessingFeeInfo 全程維持空值。
兩條各自獨立的查詢邏輯
CreditCardOnce_Stripe/GooglePay/ApplePay才會查,其餘付款方式回傳null- 邏輯很單純:拿購物車第一個非贈品的商品分類
SourceCategoryId,查該分類 + 供應商對應的系統使用費率 - 這條路徑跟卡片品牌/發卡國完全無關,不會受全新卡或記住卡影響
CreditCardOnce_Stripe:直接拿當下的context.CreditCardInfo.Brand/IssueCountryCode去查——這兩個值是不是正確GooglePay/ApplePay:呼叫GetBrandAndCountry(),即時打 Stripe API(RetrievePaymentMethod)用payment_method查出真實卡片品牌與發卡國- 其餘付款方式回傳
null
三種 PayProfileType 分支對照
null,ProcessingFeeInfo 維持空值,後面也不會計入 application_fee_amount
前兩個 tab 一直圍繞著一個假設:全新卡(沒有 identity)在後端 Pipeline 裡「查不到」正確的卡片品牌/發卡國。實際追下去才發現,這個假設只對了一半——後端的確沒有主動去查,但前端在送出訂單之前,就已經先把真實資料準備好了。
送單前的隱藏一步:CardValidate
!PayProcessData.HasCreditCard(全新卡) 且 PayProfileType === CreditCardOnce_Stripe,才會進入下一步;記住的卡(HasCreditCard = true)不會走這條路CardOptions,POST 到後端 /CreditCard/Validatebrand、countryPayProcessData.CreditCardInfo.Brand / IssueCountryCode,這兩個值現在是真實卡片資訊,不再是預設值PayProcessData(也就是後端的 PayProcessContextEntity)送給 CompleteForNewCartV2,Pipeline 開始跑第 1 道時,context.CreditCardInfo 就已經帶著真實資料進來了拿到兩種費率之後,接下來就是把它們套進公式,算出一個要跟 Stripe 申報的 application_fee_amount。這筆金額由三個部分加總而成:
Custom 帳戶類型才會加上)
總支付金額 × 金流手續費率 + 金流固定手續費
application_fee_amount 欄位。Stripe 扣款成功後,這筆金額會直接從交易款項中撥給 91APP 的主帳號,作為平台向商家收取的服務費。
付款成功後,系統會把這次交易用到的費率資訊(付款流程類型、帳戶類型、卡片品牌、發卡國家、各項費率與固定費用)整批序列化,存進這筆訂單的第三方付款紀錄裡,供後續 ERP 轉單時讀取使用。實際負責這件事的是 StripePayChannelService.GetTradesOrderThirdPartyPaymentInfo(),它會把當次交易用到的費率組成一段 JSON,寫進 TradesOrderThirdPartyPayment.Info 欄位:
1 | { |
| 欄位 | 意義 |
|---|---|
PaymentFlow | 這筆交易走的是 DirectCharge 還是 DestinationCharge |
AccountType | 子帳戶類型:Standard / Custom / Express,決定金流手續費要不要平台自己算 |
SubAccount | 實際收款的 Stripe 子帳戶 ID |
CardBrand / CardIssueCountry | 這次刷卡用的卡片品牌與發卡國,也就是查金流手續費率的組合鍵 |
FeeRate / FixedFee | PayProfile 查出來的金流手續費率與固定費 |
SourceId / SourceType | 這個費率的來源,SourceId 對應 PayProfileProcessingFee 表的主鍵 |
SCMSalesFeeRate | SalesOrder 查出來的系統使用費率 |
PaymentServiceProvider | 固定為 PaymentMiddleware,標示這筆交易走哪一套金流串接 |
ERP 轉單時的欄位映射
ERP 轉單 Job 會讀出 TradesOrderThirdPartyPayment.Info 這段 JSON,把它還原成一般會計看得懂的訂單欄位:
| Info 欄位 | ERP 目標欄位 | 說明 |
|---|---|---|
FeeRate | SalesOrderSlave_SCMCreditCardFeeRate /SCMCreditCardFeeRate2 | 信用卡手續費率 |
SCMSalesFeeRate | SalesOrderSlave_SCMSalesFeeRate | 系統使用費率 |
FixedFee | SalesOrderGroup_PaymentFixedFee | 付款固定費用 |
CardBrand / CardIssueCountry | SalesOrder 相關欄位 | 卡別與國家資訊 |
把前面幾個 tab 拆解的內容串成一條完整的線,從 Pipeline 第 19 道設定卡片資訊開始,一路走到 ERP 生出費用單為止:
[Step #19] ArrangePayTypeExpressInfoProcessor 設定 context.CreditCardInfo.Brand / IssueCountryCode │ ▼ [Step #26] GetOrderProcessingFeeProcessor(HK Only) ┌──────────────────────────┐ ┌───────────────────────────────┐ │ ProcessingFeeService │ │ ProcessingFeeService │ │ .GetSalesFeeInfo() │ │ .GetCreditCardPayProfileFeeInfo│ │ │ │ │ │ DB: SupplierContract │ │ DB: csp_GetPayProfileProcessing│ │ SalesFeeRate 表 │ │ Fee SP(WebStoreDB) │ └──────────────────────────┘ └───────────────────────────────┘ │ │ ↓ context.ProcessingFeeInfo.SalesOrder ↓ context.ProcessingFeeInfo.PayProfile │ │ └─────────────────┬────────────────────┘ ▼ [Step #30] ThirdPartyPayApiProcessor → PaymentMiddlewareService.PaymentRequest() → StripePayChannelService.GetPayExtendInfo() → GetStripeApplicationFee() ① 商品費 = Σ(商品金額 - 購物金) × SalesOrder.Rate ② 運費費 = Σ(運費 - 購物金) × SalesOrder.Rate ③ Custom Only: + TotalPayment × PayProfile.Rate + PayProfile.FixedFee ───────────────────────────────────────────── → ExtendInfo["application_fee_amount"] = ①+②+③ → HTTP POST PMW /api/v1/Pay/CreditCardOnce_Stripe/{tgCode} (帶 application_fee_amount) │ ▼ Stripe API POST /v1/payment_intents (application_fee_amount * 100 → 整數,送給 Stripe) │ ▼ Stripe 扣款成功 │ ▼ [付款成功後] StripePayChannelService.GetTradesOrderThirdPartyPaymentInfo() → 序列化 FeeRate/SCMSalesFeeRate/FixedFee/CardBrand/CardIssueCountry → 存入 TradesOrderThirdPartyPayment.Info(JSON) │ ▼ [TransferOrderProcessor → AfterOrderProcessor → ERP Job] csp_TradesOrderTransToSalesOrderWithFlow_Mall → 讀取 ThirdPartyPayment_Info → 更新 SalesOrderSlave_SCMCreditCardFeeRate / SCMSalesFeeRate / PaymentFixedFee → 生成 ExpenseOrder(費用單)


