Cybersource-Query
Cybersource QueryPayment 有兩條完全不同的執行路徑,由 request.ExtendInfo 是否包含 TradesOrderGroupCode 決定。
1 | QueryPayment 入口 |
背景設計決策(程式碼原始 comment):
1 | //// 由於 Cybersource 的 Create Search API 會有同步資料的延遲 |


mweb 觸發邏輯(GetQueryPaymentExtendInfo)
mweb 的 GetQueryPaymentExtendInfo() 根據 extendData 是否存在決定走哪條路徑:
1 | // CybersourcePayChannelService.GetQueryPaymentExtendInfo() |
| 呼叫情境 | extendData |
走哪條路徑 |
|---|---|---|
| PayChannelReturn(Cybersource callback 帶回 form-data) | 有值 | 路徑 B |
| ReCheck Job / 後台補查 | null |
路徑 A |

路徑 B:Form-Data 快速路徑(Callback 情境)
觸發條件
request.ExtendInfo 不包含 TradesOrderGroupCode,直接帶 Cybersource callback 的所有 form-data 欄位。
Step 1:HMAC-SHA256 驗簽
1 | var config = new CybersourceConfiguration(this._configuration, headers); |
- 使用與 Pay 表單相同的
ProfileSecret重新計算簽名 - 計算結果與 Cybersource 回傳的
signature欄位比對 - 不符 → 立即拋
SecurityException,防止偽造 callback
Step 2:解析 decision 欄位
1 | var decision = request.ExtendInfo.Get("decision"); |
decision |
ReturnCode | 說明 |
|---|---|---|
ACCEPT |
1000 Success |
付款成功 |
DECLINE |
3000 Failed |
發卡行拒絕 |
ERROR |
3000 Failed |
系統錯誤 |
CANCEL |
3000 Failed |
消費者取消 |
| 其他 | 2003 WaitingToPay |
未知狀態 |
Step 3:組建回應
| 欄位 | 來源 |
|---|---|
TransactionId |
request.ExtendInfo["transaction_id"] |
ReturnMessage |
request.ExtendInfo["message"] |
ExtendInfo.Source |
"FormData" |
ExtendInfo.card_type_name |
request.ExtendInfo["card_type_name"] |
ExtendInfo.req_payment_method |
request.ExtendInfo["req_payment_method"] |

觸發條件
request.ExtendInfo 包含 TradesOrderGroupCode(= TGCode),由 ReCheck Job 觸發。
API 呼叫
1 | POST /tss/v2/searches |
認證使用 HTTP Signature(HttpSignatureHelper.GenerateHttpSignatureHeaders()),帶 x-merchant-id、x-merchant-key-id、x-secret-key 三個 headers。
注意:路徑 A 使用 REST API 認證(merchantId / merchantKeyId / secretKey),與路徑 B 的 Secure Acceptance 認證(ProfileSecret)是完全不同的認證體系。
回傳結果判斷邏輯(4 步驟,順序不可調換)
1 | // Step 1:有無任何交易紀錄? |

為什麼判斷順序是「先找成功、再找失敗」?
Cybersource Secure Acceptance 付款失敗時不一定將消費者導回商戶頁,可在 Cybersource 頁面重試。因此一筆 TGCode 的搜尋結果中,可能同時存在多筆 transaction:
1 | TG123456 的查詢結果: |
如果先找失敗再找成功 → 誤判失敗。因此必須先找成功。

失敗判定只認 ESYSTEM 的設計意圖
| rFlag | 含義 | 判斷結果 |
|---|---|---|
ESYSTEM |
系統錯誤,無法重試 | 3000 Failed |
| 其他失敗 rFlag | 可能還在重試中(如 3D 驗證失敗) | 2003 WaitingToPay |
設計理念:只有確定無法重試的系統錯誤才視為失敗,保留消費者在 Cybersource 頁面重試的可能性。

ReturnCode 對照表
| ReturnCode | 說明 | 路徑 A 觸發情境 | 路徑 B 觸發情境 |
|---|---|---|---|
1000 |
Success | ics_bill application + reasonCode == "100" |
decision == "ACCEPT" |
2003 |
WaitingToPay | 無資料(索引延遲)/ 非 ESYSTEM 失敗 / Step 4 fallback | decision 非已知值 |
3000 |
Failed | rFlag == "ESYSTEM" |
decision 為 DECLINE / ERROR / CANCEL |
| 欄位 | 路徑 A(Query) | 路徑 B(FormData) |
|---|---|---|
Source |
"Query" |
"FormData" |
card_type_name |
"" |
Cybersource form-data 的 card_type_name |
req_payment_method |
"" |
Cybersource form-data 的 req_payment_method |
paymentInformation_paymentType_type |
transaction.PaymentInformation.PaymentType.Type |
"" |
paymentInformation_paymentType_method |
transaction.PaymentInformation.PaymentType.Method |
"" |
1 | 【路徑 B — Callback 快速路徑】 |

Q1:付款剛完成,前台立刻查詢(路徑 B)
1 | Cybersource callback → form-data(decision=ACCEPT, signature=xxx) |
Q2:消費者關掉 Cybersource 頁面(callback 未觸發)
1 | → 無 callback,ExtendData 為 null |
真實 Log(已驗證)— 索引尚未同步情境:
1 | // Request:extend_info 有 TradesOrderGroupCode → 路徑 A |
1 | → hasEmbedded = false(count = 0,無 _embedded) |
transaction_id為null(Pay 時 Cybersource 不回 TransactionId),count: 0代表 Cybersource 搜尋索引尚未同步,是索引延遲的直接佐證。
Q3:3D 驗證失敗,消費者在 Cybersource 頁面重試
1 | 路徑 A 查詢結果: |
Q4:同一訂單有多筆 transaction(重試付款場景)
1 | transaction #1:3D 失敗 |
Q5:簽名驗證失敗(路徑 B)
1 | form-data 被竄改或 ProfileSecret 設定錯誤 |
路徑 B(Form-Data)的存在,本質上是承認了路徑 A(Create Search API)在付款完成後立刻查詢的場景下不可信賴。這是一個「因為 API 不夠即時,所以繞過 API」的設計決策。
這種雙軌制帶來的代價:同一個 QueryPayment 方法,在兩種情境下行為完全不同,呼叫方必須清楚知道自己在哪條路徑上,增加了心智負擔。
路徑 B 成功時,TransactionId = request.ExtendInfo["transaction_id"],這是 Cybersource 在 callback form-data 裡回傳的欄位。
路徑 A 成功時,TransactionId = transaction.Id(Create Search 回傳的 transactionSummaries[].id)。
兩條路徑的 TransactionId 應指同一筆交易,但來源不同,若 Cybersource 在兩個 API 回傳的 id 格式不一致,可能造成後續退款查找問題。


ReturnCode = 2003 在路徑 A 中代表三種完全不同的現實:
| 情境 | 現實意義 |
|---|---|
_embedded 為空 |
資料還沒同步,稍後可能成功 |
| 有失敗 transaction 但非 ESYSTEM | 消費者可能在重試 |
| Step 4 fallback | 不明原因,系統不確定狀態 |
這三種情境對後端系統的處理策略應該不同(等待時間、是否告警等),但都用同一個 ReturnCode 表達,ReCheck Job 無法區分。

failFlags = new[] { "ESYSTEM" } 只認一種確定失敗,其餘一律 WaitingToPay。
實際上 Cybersource 有多種確定性失敗的 rFlag(參考 Cybersource 文件 000001630),例如卡片餘額不足、卡號無效等情境,也可能有不可重試的狀態。只認 ESYSTEM 可能導致某些確定失敗的訂單長期卡在 WaitingToPay,佔用 ReCheck 資源。
1 | _ => ReturnCodes.WaitingToPay // switch 的 default case |
當 Cybersource 回傳未知的 decision 值時,靜默返回 WaitingToPay,沒有任何日誌或告警。若 Cybersource 新增了 decision 值,系統會悄悄誤判,難以排查。



