易支付接口對接指南:MD5 簽名、異步通知與訂單超時的三個坑
易支付(epay)協議的 submit.php、mapi.php 和 api.php 怎麼對接?本文講清簽名算法、為什麼只通知支付成功、訂單有效期該怎麼設,以及驗簽失敗和收不到通知的排查方法。
本文目錄
易支付(epay)是很多自建站系統內置的一套支付協議:V2Board、SSPanel、獨角數卡,以及 Xboard 自帶的「EPay」支付方式都支持它。對接方只需要填四樣東西——網關地址、商戶 ID(pid)、商戶密鑰(key)和支付通知地址(notify_url)——就能收款,不用寫代碼。
協議本身很簡單,真正容易出問題的是三個地方:簽名串的拼法、支付通知只代表成功,以及訂單有效期與對接系統的超時不一致。下文以 KukoPay 的 易支付兼容接口 為例逐一説明,規則對其他易支付服務商也基本通用。
易支付協議由哪幾個接口組成?
一套完整的易支付對接通常只用到三個地址,都拼在網關地址後面:
| 地址 | 方式 | 用途 |
|---|---|---|
/submit.php | GET 或表單 POST | 頁面跳轉支付:買家瀏覽器帶着簽名參數訪問,被重定向到收銀台 |
/mapi.php | POST | 服務端下單:返回收銀台地址 payurl,由你把買家帶過去 |
/api.php?act=order | POST(推薦)或 GET | 查詢單筆訂單狀態 |
在 KukoPay,網關地址是 https://api.kukopay.com/epay。對接系統會自己在後面拼 /submit.php 等路徑,所以設置裏只填到 /epay 為止。
下單參數裏最關鍵的是 out_trade_no(你的訂單號)、money(金額,最多兩位小數)、notify_url 和 sign。money 只有數字沒有幣種,所以收款幣種由商戶在控制台為這個 pid 選定。
易支付的簽名怎麼算?
簽名方法和彩虹易支付 SDK 一致,四步:
- 去掉
sign、sign_type和值為空的參數; - 按參數名的 ASCII 順序排序;
- 拼成
a=1&b=2的形式,值不做 URL 編碼; - 在末尾直接接上商戶密鑰,求 MD5,取 32 位小寫。
function epay_sign(array $params, string $key): string {
ksort($params);
$pairs = [];
foreach ($params as $k => $v) {
if ($k === 'sign' || $k === 'sign_type' || $v === '' || $v === null) continue;
$pairs[] = $k . '=' . $v;
}
return md5(implode('&', $pairs) . $key);
}簽名校驗失敗時,按出現頻率從高到低檢查:
- 密鑰或
pid填錯了環境。 沙箱和正式環境各有一套pid和密鑰,混用必然失敗。 - 值被 URL 編碼了。 比如
name=Pro%20plan和name=Pro plan算出來的簽名不同,協議要求用原值。 - 把
sign_type也拼進去了,或者忘了排序。 - 空值的處理。 有些系統用 PHP 的
http_build_query拼簽名串,會把空值也拼進去。KukoPay 對這兩種寫法都能驗簽通過,但換到別的服務商時值得留意。
為什麼易支付的支付通知只在成功時發送?
這是易支付對接裏最反直覺、也最重要的一點:KukoPay 只在訂單支付成功時發送通知,失敗和過期都不發。
原因在客戶端。很多易支付客戶端(包括 Xboard 自帶的 EPay)收到通知後只校驗簽名,不看 trade_status。如果平台把「支付失敗」也簽名發過去,這些系統會把它當成支付成功,直接給用戶開通服務。對於這類客戶端,「只通知成功」是唯一安全的做法。
所以:
- 收到通知 ⇒ 這筆訂單已經支付成功(前提是驗簽通過);
- 沒收到通知 ⇒ 不代表失敗,可能還在支付中、已過期,或者通知還在重試;
- 需要知道失敗或過期,請用查詢接口
api.php?act=order,status為1表示已支付,2表示已全額退款,0表示未支付、已失敗或已過期。
收到支付通知後應該怎麼處理?
通知是一個 GET 請求,參數在查詢字符串裏。處理順序:
- 先驗簽,算法同上。簽名使用的是發送時刻的商戶密鑰。
- 按
out_trade_no去重。 同一筆訂單的通知可能重複到達,已經處理過的直接返回success。 - 核對金額。 用通知裏的
money和你自己訂單的金額比對,不一致就不要發貨。 - 響應正文必須是
success這幾個字母,HTTP 狀態碼為 2xx,並且在 10 秒內返回。
返回別的內容、非 2xx 或超時,都算失敗。KukoPay 會依次間隔 30 秒、2 分鐘、10 分鐘、30 分鐘、2 小時重試,最多共發送 6 次。修好接收端之後,還可以在控制台「開發者 → Webhook」的投遞記錄裏手動重發,不需要買家重新付款。
不要在返回 success 之前做耗時的事情,比如發郵件、調用第三方接口。超過 10 秒會被判為失敗並重試,你的系統就會收到第二次通知——這正是需要按訂單號去重的原因。
訂單有效期為什麼要比對接系統的超時短?
易支付協議沒有表示「訂單何時失效」的參數,但對接系統自己通常會取消長時間未支付的訂單:Xboard 和 V2Board 是 2 小時,獨角數卡默認 5 分鐘。
問題出在兩邊的時鐘不一致時。假設收銀台開放 24 小時,而對接系統 2 小時後就取消了訂單:買家第 3 小時付款成功,平台照常發出通知,對接系統找不到這筆「已取消」的訂單,可能直接回復 success 卻不發貨——錢收了,貨沒給,只能人工退款。
所以 KukoPay 要求商戶在控制台為每個環境設置一個訂單有效期(5 分鐘到 24 小時,默認 30 分鐘),並建議設成不超過對接系統自己的超時時間:
- 設短了,最壞情況是買家需要重新下單;
- 設長了,才可能收到已取消訂單的錢;
- 拿不準時寧可設短。
這個問題不只存在於易支付。KukoPay 的 Xboard 插件走原生 API,會主動把收銀台的關閉時間設為 Xboard 訂單創建後 1 小時 50 分,比 Xboard 取消訂單早 10 分鐘,原理相同。
查詢訂單接口為什麼建議用 POST?
api.php?act=order 按協議直接用商戶密鑰 key 作為憑證。協議允許把參數放在 GET 查詢字符串裏,但 URL 會被反向代理、CDN 和瀏覽器歷史原樣記錄,密鑰就跟着留在了那些日誌裏。
curl -X POST https://api.kukopay.com/epay/api.php \
-d act=order -d pid=10001 -d key="$EPAY_KEY" -d out_trade_no=XB2026…只從服務端調用,並且把參數放在請求體裏。
付款成功了,對接系統卻沒收到通知?
到控制台「開發者 → Webhook」查看這筆訂單的投遞記錄:
- 對方返回了內容,但不是
success:接收端報錯或驗簽失敗,看返回內容裏的錯誤信息。 - 連接失敗或超時:檢查對接系統的防火牆、CDN 或 WAF 是否攔截了來自 KukoPay 的請求。
- 沒有投遞記錄:確認訂單確實是通過易支付下的,並且已經支付成功。
什麼時候不該用易支付?
易支付兼容接口是為已經支持這個協議的系統準備的。如果你在開發一個新系統,建議直接對接 API:
- 易支付使用 MD5 簽名,API 的 Webhook 使用 HMAC-SHA256,並支持密鑰輪換期間新舊密鑰同時簽名;
- 易支付只能表達「支付成功」這一個結果,API 有失敗、過期、退款、拒付等完整事件;
- 易支付協議本身沒有退款、對賬和拒付舉證,這些要到控制台操作,或者另外調用 API。
兩種方式創建的訂單完全相同,走同一個收銀台和同一套結算流程,所以先用易支付跑起來、以後再遷移到 API 也沒有問題。
想先試一試?在 沙箱控制台 開通易支付,用測試卡 4242 4242 4242 4242 下一單,確認對接系統收到通知、訂單變成已支付。沙箱不需要等待資質審核。
在沙箱裏把它跑通
註冊商戶後即可申請測試密鑰,不需要等待資質審核。