Cybersource-Pay
Cybersource 的 Pay 設計與多數金流根本不同。PMW 不呼叫 Cybersource 任何 API,他的做法是
- 組建一份已簽名的 HTML 表單(FormPost)
- 前台讓瀏覽器直接將這份表單 POST 到 Cybersource 托管頁
- 消費者在 Cybersource 頁面完成付款後,Cybersource 將結果 POST 回前台 callback URL

1 | mweb 前台 |
設計哲學:卡號等敏感資訊完全不經過商戶系統(mweb、PMW),由 Cybersource 的托管頁直接收取,大幅縮減 PCI DSS 合規範圍

Cybersource 有兩套平行的認證機制,用途不同
| 認證集 | 欄位 | 用途 |
|---|---|---|
| REST API 認證 | merchantId / merchantKeyId / secretKey |
呼叫 Cybersource REST API(Query、Refund 用) |
| Secure Acceptance Profile 認證 | profileId / profileAccessKey / profileSecret |
組建表單、HMAC-SHA256 簽名(Pay 用) |
Pay 流程只使用 Secure Acceptance Profile 認證,REST API 認證在 Pay 時僅傳入 Header 但未使用。

1 | mweb GetHeader() → 從 ShopSecret 讀取 6 個認證欄位 → 放入 HTTP Headers |
Phase 1:mweb 端準備(CybersourcePayChannelService)
1-1. 從 ShopSecret 讀取認證(GetHeader)
- Cybersource_ProfileId
- Cybersource_ProfileAccessKey
- Cybersource_ProfileSecret
- Cybersource_MerchantId
- Cybersource_SecretKey
- Cybersource_MerchantKeyId
1-2. 組建 Pay ExtendInfo(GetPayExtendInfo)
- uniqueKey
- lang
Phase 2:PMW 端處理(CybersourcePlugin.Pay)
| 環境 | PaymentUrl |
|---|---|
| Test | https://testsecureacceptance.cybersource.com/pay |
| Production | https://secureacceptance.cybersource.com/pay |
2-2. 組建表單(CybersourcePaymentFormEntity)
| 欄位(表單 key) | 值來源 | 說明 |
|---|---|---|
access_key |
config.ProfileAccessKey |
Secure Acceptance 識別用 |
profile_id |
config.ProfileId |
Profile 識別 |
transaction_uuid |
request.ExtendInfo.UniqueKey |
防重複提交唯一碼(≤ 50 字元) |
signed_field_names |
所有欄位 key 逗號串接(含自身) | 告知 Cybersource 哪些欄位被簽名 |
unsigned_field_names |
"" |
本實作無未簽名欄位 |
signed_date_time |
DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'") |
UTC 時間戳,Cybersource 驗簽用 |
locale |
request.ExtendInfo.Lang |
語系(如 zh-tw) |
transaction_type |
"sale" |
授權 + 請款一次完成(非兩階段) |
reference_number |
request.TradesOrderGroupCode |
TG Code,對應訂單號 |
amount |
request.Amount.ToString() |
付款金額 |
currency |
request.Currency |
幣別(如 HKD) |
signed_field_names的值是以上所有欄位 key 的逗號串接字串(包含signed_field_names本身),且在ToDictionary()中先把所有 key 寫入後才回填此欄位值。
2-3. HMAC-SHA256 簽名(SecureAcceptanceHelper)
1 | var formData = payment.ToDictionary(); |
2-4. 組建回傳(RequiredFormPostAction)
PMW 回傳 ReturnCode = 2003(WaitingToPay),這是 Pay 流程的正常結果,代表「請前台引導瀏覽器 POST 此表單」。
Phase 2 完整回傳結構
1 | { |

為什麼 Callback 需要兩跳?
Cybersource Profile 設定的 callback URL 是固定的,不含 TGCode:
1 | POST /PayChannel/ReturnPost/CreditCardOnce/Cybersource ← Cybersource 只知道這個 |
但後續真正做驗簽與付款確認的 PayChannelReturn route 需要 TGCode 在 URL 裡,且需要 login session:
1 | [] // ← 需要 browser session cookie |
所以 PayChannelReturnPost 扮演中繼轉接站的角色。

第一跳:Cybersource → PayChannelReturnPost
1 | // PayChannelController.PayChannelReturnPost() |
ProcessResponseFormAfterPayment() 做的事:
1 | // CybersourcePayChannelService.ProcessResponseFormAfterPayment() |
Cybersource 回傳欄位有
req_前綴(表示「request echo」)。TGCode 對應req_reference_number,UniqueKey 對應req_transaction_uuid。

為什麼用 return Content(HTML) 而不是直接 POST 到 PayChannelReturn?
| 方案 | 可行? | 問題 |
|---|---|---|
return Redirect(url) |
❌ | HTTP 302 redirect 永遠變成 GET,POST body 消失 |
HttpClient.SendAsync() 打 PayChannelReturn |
❌ | Server 發出的 request 不帶 browser session cookie,[RequireLoginGoLoginPage] 直接擋掉 |
return Content(HTML form + auto-submit)(現況) |
✅ | Browser 自己 POST,天然帶著 session cookie |
return Content() 只是回傳一段 HTML,browser 從來沒有離開,session cookie 全程保留。browser 執行 JavaScript 自動 submit form,這次 POST 等同使用者自己按按鈕,cookie 自然帶著。
第二跳:PayChannelReturnPost HTML → PayChannelReturn
browser 收到 HTML 後自動 POST:
1 | <form method="post" action="/V2/PayChannel/CreditCardOnce/Cybersource/TG260702M00043?shopId=2&k=K2026..."> |
ASP.NET MVC model binding 自動將 extendData.{key} 格式解析為 IDictionary<string, string> extendData 參數。
PayChannelReturn 收到後:
tgCode、k來自 URLextendData(含decision、signature等)來自 POST body- 呼叫
FinishPayment(extendData != null → Form-Data 路徑)→ 驗簽 → ReturnCode

Cybersource Pay 採用托管頁(Hosted Page)模式,PMW 的 Pay() 只組表單、不呼叫任何 API,回傳 ReturnCode = 2003(FormPostAction)後,付款結果完全由 Cybersource callback 決定
1 | PMW Pay() → ReturnCode = 2003(組完表單就結束) |
優點
| 優點 | 說明 |
|---|---|
| ✅ PCI DSS 合規範圍最小 | 卡號完全不經過 mweb / PMW,由 Cybersource 托管頁收取,大幅降低合規成本 |
| ✅ 無需自建 3DS 驗證流程 | Cybersource 托管頁內建 3D Secure,商戶完全不需處理 |
| ✅ Pay() 邏輯極簡 | 純 CPU 計算(簽名),無外部 API 呼叫,不會因金流商 API 超時而失敗 |
| ✅ 簽名防竄改 | HMAC-SHA256 雙向驗證,Pay 表單與 callback 結果都有防偽 |
缺點
| 缺點 | 說明 |
|---|---|
| ❌ Pay() 無法即時知道失敗 | 結果必須等 callback 打回來才知道,PMW 層完全非同步 |
| ❌ callback 可能不來 | 消費者關閉 Cybersource 頁面 → callback 不觸發 → 只能靠 ReCheck Job 輪詢補救 |
| ❌ Create Search API 有索引延遲 | ReCheck Job 路徑有 eventual consistency 問題,不能立刻查到結果 |
| ❌ 付款 UI 完全由 Cybersource 控制 | 商戶無法自訂卡號輸入頁的 UI,只能調整 Cybersource Profile 設定 |
sale = authorization + capture 一次完成,不走先授權後請款的兩階段流程。適合零售電商直接扣款,無需人工審核後再請款的場景
簽名雙向驗證
| 方向 | 誰簽 | 誰驗 |
|---|---|---|
| Pay 表單 → Cybersource | PMW(SecureAcceptanceHelper) |
Cybersource |
| Cybersource callback → mweb | Cybersource | PMW(SecureAcceptanceHelper) |
相同的 ProfileSecret 用於兩個方向,確保表單內容未被竄改。
Cybersource 的 QueryPayment 有兩條完全不同的路徑,由 extendData 是否存在來分支:
1 | // CybersourcePlugin.QueryPayment() |
| 路徑 | 觸發條件 | 說明 |
|---|---|---|
| Form-Data 路徑(路徑 B) | callback 時 extendData 有帶回(extendData != null) |
直接驗 Cybersource 回傳的 signature,解析 decision 欄位 |
| Create Search API 路徑(路徑 A) | ReCheck Job 輪詢,無 extendData,帶 TradesOrderGroupCode |
呼叫 Cybersource REST API 搜尋索引查詢 |

為什麼要優先走 Form-Data 路徑?
Cybersource 的 Create Search API 本質是搜尋索引查詢,而搜尋索引不是即時同步的(有 eventual consistency 問題):
1 | 消費者付款完成 |
若 callback 一到就立刻呼叫 Create Search API,有機率索引尚未同步 → 查無此交易 → 系統誤判付款失敗 → 消費者無法進付款完成頁
Form-Data 路徑完全不碰 API,直接用 Cybersource callback 夾帶的 form-data 本身驗簽,繞過索引延遲問題




