排查全攻略)
簡介面向ECShop開源商城系統(tǒng)的碼支付插件旨在省去支付寶、微信、財付通逐一簽約的繁瑣流程讓商家通過二維碼收款快速上線三種主流支付方式特別適合使用ECShop搭建B2C商城、希望降低支付接入成本與運(yùn)營門檻的站長或二次開發(fā)者。壓縮包共21個文件其中14個php文件承載支付接口、回調(diào)處理與核心業(yè)務(wù)邏輯5個xml文件用于模塊配置與語言包1個txt為使用說明整體僅33KB輕量易部署。已有648人學(xué)習(xí)下載。資源內(nèi)含核心PHP源碼、XML配置、TXT使用說明及IDE工程信息并同時提供gb2312與utf-8語言包/回調(diào)文件可幫助用戶在ECShop后臺直接啟用免簽約支付由于碼支付省去了逐家簽約環(huán)節(jié)商家可借助二維碼完成支付寶、微信、財付通交易并在安裝后重點(diǎn)關(guān)注安全更新、支付測試與用戶體驗優(yōu)化從而降低運(yùn)營門檻、提升支付轉(zhuǎn)化率。這是一份適合ECShop商城快速接入多渠道支付的實用工具。 手里還有一套老 ECShop 商城的人估計都經(jīng)歷過這種糾結(jié)網(wǎng)站掛著商品上架了訂單也進(jìn)來了結(jié)果卡在收款這一步。想接支付寶、微信的官方支付接口申請頁面翻到底營業(yè)執(zhí)照、企業(yè)支付寶、對公賬戶挨個要個人站長只能看著訂單干瞪眼。我就是在那個節(jié)骨眼上接觸的碼支付——不需要企業(yè)資質(zhì)個人收款碼就能用網(wǎng)站、公眾號、App 里的支付場景都能掛。這篇文章把我折騰這套 ECShop 碼支付插件也就是支付寶微信財付通三合一的那類 zip 包的完整過程寫下來包括安裝步驟、回調(diào)原理、踩坑記錄和上線前的安全項給同樣跑個人站的朋友做個參考。1. 為什么跑ECShop的個人站長最后都繞不開碼支付1.1 個人站長的支付困境比你想象的更現(xiàn)實很多人覺得接支付是件小事只有真正以個人身份去申請一次才知道有多麻煩。官方支付接口本質(zhì)上是給企業(yè)、個體工商戶準(zhǔn)備的哪怕是最低門檻的版本也要有營業(yè)執(zhí)照、對公賬戶還要走簽約審核流程。對獨(dú)立博主、小工具站、個人開發(fā)者來說這些東西可能在很長一段時間里都湊不齊。于是問題就變成了網(wǎng)站功能都做完了唯獨(dú)錢收不進(jìn)來。ECShop 這個系統(tǒng)又比較特殊它火的時候是 2010 年前后那時候大量站長都是用個人虛擬主機(jī)建站本身就沒有工商主體概念。這套老框架能活到現(xiàn)在很大程度靠的是插件生態(tài)而支付恰恰是最剛需的插件類型。官方接口進(jìn)不來碼支付這類個人聚合支付接口就成了最順手的替代方案。1.2 碼支付到底是怎么運(yùn)作的碼支付的核心模式可以簡單理解成平臺幫你盯著個人收款賬戶。用戶掃碼付款后錢先進(jìn)你的個人收款賬戶平臺通過監(jiān)聽收款通知或模擬客戶端的方式感知到這筆到賬然后向你的網(wǎng)站服務(wù)器發(fā)起回調(diào)告訴 ECShop這筆訂單已經(jīng)付錢了。這套機(jī)制最大的優(yōu)勢就是個人身份可用不需要企業(yè)資質(zhì)資金也直接進(jìn)你個人賬戶沒有中間結(jié)算周期。代價是它不屬于官方直連通道本質(zhì)上是利用了個人收款碼的支付能力所以穩(wěn)定性、風(fēng)控規(guī)則都不在你自己手里。這也是后面我為什么反復(fù)強(qiáng)調(diào)測試和學(xué)習(xí)可以用正經(jīng)商用要慎重的原因。1.3 這個 zip 插件包解決了什么下載下來解壓后你會發(fā)現(xiàn)它不是一個獨(dú)立程序而是一個符合 ECShop 支付模塊規(guī)范的擴(kuò)展包。ECShop 的支付模塊目錄在includes/modules/payment/每種支付方式對應(yīng)一個 PHP 文件。這個插件包要做的事就是在這個目錄里新增一個碼支付模塊讓后臺支付方式列表里多出碼支付這一項再把碼支付平臺的各種支付類型支付寶、微信、財付通統(tǒng)一暴露給前臺用戶選擇。一句話總結(jié)它把個人收款碼和ECShop 訂單系統(tǒng)之間的信息鏈路打通了。沒有這個插件你只能收款后手動去后臺改訂單狀態(tài)有了它用戶支付完成訂單自動變成已付款整個流程就閉合了。2. 裝之前先摸清三件事別上來就傳文件2.1 PHP版本、ECShop版本、編碼格式一個都不能湊合這是我踩過的第一個坑。ECShop 2.7.3 年代的插件很多默認(rèn)是基于 PHP 5.x 寫的函數(shù)用mysql_connect編碼用 GBK。而現(xiàn)在市面上的虛擬主機(jī)普遍是 PHP 7.2 甚至 8.0老代碼直接甩上去大概率頁面白屏或者支付模塊無法安裝。所以裝插件前第一件事是去 ECShop 后臺或服務(wù)器上開一個phpinfo()頁面確認(rèn)三件事PHP 版本是多少、有沒有開啟curl擴(kuò)展、有沒有openssl擴(kuò)展。如果 PHP 版本高于 7.0拿到插件包后先打開里面的 PHP 文件掃一眼看到mysql_開頭的函數(shù)就要警惕這些函數(shù)在 PHP 7 里已經(jīng)被移除了需要讓作者改寫成mysqli或 PDO 版本。編碼這塊更要命。ECShop 分 GBK 版和 UTF-8 版插件也必須對應(yīng)。如果你用 UTF-8 版商城裝了一個 GBK 編碼的支付插件支付成功后回調(diào)里的中文參數(shù)會亂碼簽名驗簽大概率直接失敗。判斷方法很簡單用編輯器打開插件 PHP 文件看文件頭有沒有header(Content-Type: text/html; charsetutf-8)或者帶 BOM 的 UTF-8 標(biāo)記再和 ECShop 后臺的編碼設(shè)置對照一下。2.2 碼支付平臺的賬號三件套pid、key、收款碼安裝插件前你要先去碼支付平臺注冊一個商戶賬號。注冊門檻很低基本就是手機(jī)號加郵箱但有三樣?xùn)|西后面配置時會用到提前準(zhǔn)備好能省不少事商戶ID也就是平臺分配給你的唯一編號形如pid1000這種后面生成簽名和支付鏈接時都會帶上。商戶密鑰 key這是簽名用的私密字符串配置在插件后臺千萬不能泄露。收款賬戶綁定你需要把自己常用的支付寶或微信收款碼在平臺上完成綁定平臺監(jiān)聽到賬就是靠這個。如果你拿到的是已經(jīng)配置好的完整 zip 包里面可能還附帶一個codepay.php配置文件或說明文檔里面有平臺 API 地址、回調(diào)地址示例。這些信息千萬別扔后面排查簽名問題時全靠它。2.3 先搞懂插件包的文件結(jié)構(gòu)再動手解壓 zip 包后不要急著全部上傳。先看一眼目錄結(jié)構(gòu)正常的 ECShop 支付插件一般就兩三個文件includes/modules/payment/codepay.php支付模塊主文件負(fù)責(zé)支付請求生成和回調(diào)處理。notify.php或respond.php接收碼支付平臺通知的入口文件部分插件會把它放在站點(diǎn)根目錄。README.txt或配置說明.txt安裝說明和參數(shù)說明。弄清楚哪個文件負(fù)責(zé)什么再上傳能避免很多低級問題。比如有的插件把回調(diào)地址寫死成http://你的域名/notify.php但文件實際在子目錄里結(jié)果平臺怎么通知都找不到入口。3. 安裝與配置上傳文件、后臺啟用、填好參數(shù)再測試3.1 上傳文件的正確姿勢先把整個安裝包解壓到本地然后用 FTP 工具或?qū)毸姘宓奈募芾砥靼裪ncludes目錄整個覆蓋上傳到 ECShop 根目錄。上傳前給原來的includes/modules/payment/文件做個備份——這個目錄是你的支付模塊全家桶萬一覆蓋錯了其他支付方式全廢。上傳完成后給includes/modules/payment/codepay.php和根目錄下的回調(diào)文件設(shè)置 644 權(quán)限目錄設(shè)置 755 權(quán)限。這一步容易被忽略早期很多虛擬主機(jī)默認(rèn)目錄權(quán)限是 666PHP 文件能被網(wǎng)頁端讀取但不是問題但部分安全組件會攔截低權(quán)限目錄下的腳本執(zhí)行導(dǎo)致支付模塊后臺看不到。說白了權(quán)限保證文件可讀、可執(zhí)行但不可被網(wǎng)頁直接修改就夠了。3.2 后臺啟用支付方式并填寫參數(shù)登錄 ECShop 后臺進(jìn)入支付方式管理頁。正常情況下列表里會多出一項碼支付或codepay點(diǎn)擊安裝按鈕進(jìn)入?yún)?shù)配置頁。需要填的字段大致如下商戶ID填碼支付平臺分配的 pid。商戶密鑰填平臺給你的 key。支付類型選擇啟用哪幾種一般可選支付寶、微信、財付通/QQ錢包。財付通現(xiàn)在已經(jīng)很少單獨(dú)使用了大多數(shù)場景下選支付寶和微信就夠。回調(diào)地址有的插件會自動生成有的需要手工填。注意這個地址必須是可以從公網(wǎng)訪問的完整 URL不能寫127.0.0.1或內(nèi)網(wǎng) IP。保存后回到支付方式列表能看到碼支付處于啟用狀態(tài)說明模塊安裝成功。此時最好先去前臺商城走一遍下單—提交訂單—選擇支付方式的流程確認(rèn)頁面能正常跳轉(zhuǎn)到碼支付的收銀臺。3.3 插件內(nèi)部的代碼邏輯長什么樣為了后面排查問題有必要知道這個插件文件內(nèi)部大致做了什么。ECShop 支付模塊的本質(zhì)是一個 PHP 類類里實現(xiàn)幾個固定方法get_code($order, $payment)生成跳轉(zhuǎn)碼支付的表單或 URL。respond()接收碼支付平臺回調(diào)驗簽調(diào)用 ECShop 的order_paid()方法把訂單標(biāo)記為已付款。支付請求生成時核心是拼接參數(shù)和簽名。常見的碼支付簽名邏輯是把業(yè)務(wù)參數(shù)按固定順序拼接字符串末尾加上密鑰然后做一次 MD5。例如$signStr money . $money . name . $name . out_trade_no . $order_sn . pid . $pid . type . $type . notify_url . $notify_url; $sign md5($signStr . $key);這里的out_trade_no就是 ECShop 的訂單號type是支付方式支付寶、微信等notify_url是回調(diào)地址。每個平臺的參數(shù)名可能略有差異但思路完全一樣。回調(diào)處理時插件會收到碼支付平臺 POST 過來的通知數(shù)據(jù)先本地算一遍簽名和平臺傳來的簽名比對一致才繼續(xù)處理訂單這就是驗簽。3.4 測試支付的正確順序插件裝好后我的建議是先在碼支付平臺后臺發(fā)起一筆 0.1 元或 1 元的測試支付不要拿大額真實訂單試。測試時重點(diǎn)看三個東西支付頁面能不能正常打開、支付完成后頁面跳轉(zhuǎn)是否正常、ECShop 后臺訂單狀態(tài)是否從待付款變成已付款。如果某個環(huán)節(jié)斷了不要急著重裝插件按第 4 部分的排查思路走一遍大概率能定位到問題。4. 回調(diào)鏈路拆解支付成功但訂單狀態(tài)不變的排查思路4.1 一條支付成功通知的完整流轉(zhuǎn)路徑很多朋友第一次裝支付插件遇到錢扣了訂單沒變化就慌。要解決這個問題先得理解一條通知是怎么從碼支付平臺走到 ECShop 的完整鏈路大概是這樣的用戶在前臺提交訂單點(diǎn)擊碼支付→ ECShop 生成支付請求跳轉(zhuǎn)到碼支付收銀臺 → 用戶掃碼付款 → 碼支付平臺通過監(jiān)聽收款通知確認(rèn)到賬 → 平臺向你的網(wǎng)站發(fā)起 HTTP 回調(diào)請求POST 到notify_url→ ECShop 的respond()方法收到通知驗證簽名 → 校驗金額、訂單號 → 調(diào)用訂單更新邏輯 → 返回一個固定字符串比如success給平臺 → 平臺收到成功響應(yīng)后停止通知。任何一個環(huán)節(jié)斷了訂單狀態(tài)都會停在原地。要命的是很多插件在通知階段不寫日志出了問題你根本不知道平臺到底有沒有請求過你的服務(wù)器。4.2 排查第一步讓平臺的通知開口說話我的做法是先在插件回調(diào)入口的最前面加幾行日志代碼把收到的原始 POST 數(shù)據(jù)原樣記錄下來file_put_contents(__DIR__ . /codepay_notify.log, date(Y-m-d H:i:s) . . json_encode($_POST) . PHP_EOL, FILE_APPEND);然后重新發(fā)起一筆測試支付。支付完成后打開這個日志文件看里面有沒有平臺發(fā)來的通知記錄。這一步能直接確認(rèn)兩件事你的回調(diào)地址在碼支付平臺那邊是否配置正確以及你的服務(wù)器能否收到來自平臺的請求。如果日志文件是空的問題基本出在回調(diào)地址無法訪問或平臺還沒配置好回調(diào)地址。常見原因有三個回調(diào)地址寫成了http://localhost、服務(wù)器防火墻屏蔽了碼支付平臺的 IP、或者站點(diǎn)啟用了 CDN 但沒放行回調(diào)路徑。此時用瀏覽器直接訪問一次回調(diào)地址確認(rèn)它不是返回 404 或 500 就成功了一半。4.3 排查第二步驗簽失敗是最隱蔽的坑如果日志里有 POST 數(shù)據(jù)但 ECShop 后臺的訂單狀態(tài)還是沒變問題基本就出在驗簽環(huán)節(jié)。把日志里記錄的sign參數(shù)和你本地重新計算出來的簽名打印出來對比不一樣就說明拼接順序或編碼有問題。我碰到過一種經(jīng)典情況碼支付平臺返回的是 GBK 編碼的中文商品名而 ECShop 側(cè)是 UTF-8 編碼兩邊拼出來簽名字符串里的中文不一樣MD5 結(jié)果自然對不上。解決辦法是在驗簽前用mb_convert_encoding()把所有接收到的參數(shù)統(tǒng)一轉(zhuǎn)成 UTF-8 再拼接簽名。插件包里如果沒做這一步你要自己補(bǔ)上。4.4 排查第三步訂單號與金額的校驗不能放過驗簽通過只是第一步接下來插件還會校驗out_trade_no訂單號和money金額是否與數(shù)據(jù)庫里的訂單一致。有些插件包的訂單號處理有問題ECShop 的訂單號可能帶著前綴或后綴而碼支付平臺回傳的是原始訂單號。比如 ECShop 里存的訂單號是20250101093012345但平臺回傳的是20250101093012345中間多了一個空格對比就失敗。這不是小事。金額比較也建議用浮點(diǎn)數(shù)的差值絕對值判斷而不是直接因為 0.1 和 0.10000000000001 在浮點(diǎn)數(shù)比較時屬于不相等。實際處理時先round((float)$money, 2)再比較可以避免很多莫名其妙的問題。4.5 別忘了向平臺反饋處理結(jié)果回調(diào)處理完訂單后插件必須向碼支付平臺返回一個明確的成功標(biāo)識通常是輸出success字符串。如果不返回平臺會認(rèn)為通知沒送達(dá)然后按策略自動重試多次——這在某一筆訂單上會表現(xiàn)為用戶只付了一次錢但你的回調(diào)代碼被觸發(fā)了好幾遍。如果回調(diào)代碼沒有做冪等判斷每次觸發(fā)都會嘗試更新訂單狀態(tài)輕則重復(fù)寫日志重則在統(tǒng)計邏輯里產(chǎn)生臟數(shù)據(jù)。所以強(qiáng)烈建議在處理訂單開頭加一層檢查if ($order[pay_status] PS_PAYED) { echo success; exit; }已經(jīng)支付過的訂單直接返回成功避免重復(fù)處理。5. 文檔外那些坑PHP7兼容、編碼混亂與ECShop訂單狀態(tài)機(jī)5.1 PHP7環(huán)境下老插件的隱性崩潰很多下載下來的碼支付插件代碼風(fēng)格還停留在 PHP 5 時代。除了前面提到的mysql_*函數(shù)問題還有幾個隱蔽的地方容易出問題mcrypt_encrypt相關(guān)函數(shù)在 PHP 7.2 起被移除如果插件用它做加解密直接致命錯誤。each()函數(shù)在 PHP 8 里被移除老代碼里用while (list($k, $v) each($arr))的地方全部要改成foreach。json_encode在 PHP 5.4 之前不會處理中文轉(zhuǎn)義問題但 PHP 5.4 之后默認(rèn)轉(zhuǎn)義中文如果簽名串里拼接了 JSON 內(nèi)容前后端對不上就會導(dǎo)致簽名錯誤。拿到插件后先在本地 PHP 環(huán)境跑一遍語法檢查php -l codepay.php如果有語法錯誤再檢查是不是each、mysql_這類被廢棄的語法。很多時候你以為的插件不能用其實是運(yùn)行環(huán)境和代碼時代不匹配。5.2 編碼混亂的連鎖反應(yīng)編碼問題在 ECShop 上比想象中更普遍。ECShop 的老版本默認(rèn)是 GBK后來才推出 UTF-8 版本。如果你從網(wǎng)上隨便下的插件包是另一個編碼裝好后不僅僅回調(diào)驗簽有問題甚至前臺顯示就可能亂碼。這里有一個簡單實用的檢測方法用 Notepad 或 VS Code 打開插件里的 PHP 文件看狀態(tài)欄或右下角顯示的編碼格式。如果顯示的是 GB2312 或 GBK而你的 ECShop 是 UTF-8那就用編輯器做一個編碼轉(zhuǎn)換另存為 UTF-8 無 BOM 格式再上傳。注意轉(zhuǎn)換后要重新檢查簽名邏輯因為中文參數(shù)在拼接時已經(jīng)變成 UTF-8 字符串只要你把接收的參數(shù)也統(tǒng)一轉(zhuǎn)成 UTF-8兩邊還是能對齊。5.3 理解 ECShop 的訂單狀態(tài)更新邏輯ECShop 的訂單狀態(tài)不是一個字段而是由訂單狀態(tài) 支付狀態(tài) 發(fā)貨狀態(tài)三個維度組合控制的?;卣{(diào)里把訂單標(biāo)記為已支付本質(zhì)上是設(shè)置兩個值支付狀態(tài)變?yōu)镻S_PAYED訂單狀態(tài)變?yōu)橐汛_認(rèn)或進(jìn)行中狀態(tài)。插件調(diào)用的是order_paid($order_sn, $payment_id)方法這個方法內(nèi)部會做一系列操作更新支付記錄、寫訂單日志、給管理員發(fā)送新訂單通知還有可能觸發(fā)郵件短信。如果你的回調(diào)里沒有調(diào)用這個方法而只是自己UPDATE了一下訂單表的支付狀態(tài)字段那訂單列表里看到的狀態(tài)可能是半吊子既不是待付款也不是已付款后臺列表里顯示得模棱兩可。所以收到插件后第一時間搜一下響應(yīng)回調(diào)的方法看它是不是真的調(diào)用了order_paid或等價邏輯。很多支付成功但訂單狀態(tài)不動的問題根源就在這里。5.4 一次真實排查記錄簽名差了一個參數(shù)這里記錄一個我實際排查過的案例。當(dāng)時用插件接碼支付測試支付寶下單10 分鐘后訂單還是待付款。查日志發(fā)現(xiàn)平臺通知確實收到了respond()方法也確實執(zhí)行了但在驗簽?zāi)且徊椒祷亓耸?。我把日志里平臺傳來的參數(shù)打出來再去碼支付平臺后臺看通知詳情把兩個sign放在一起對比發(fā)現(xiàn)平臺計算簽名時用了 6 個參數(shù)而我的插件代碼拼接只用了 5 個漏掉了return_url。加上之后簽名立刻匹配通過訂單狀態(tài)瞬間更新。這個故事說明遇到問題先懷疑參數(shù)拼接和簽名而不是先去改數(shù)據(jù)庫。把日志做扎實問題通常會自己暴露出來。6. 上線前把安全項過一遍再考慮真實收款6.1 簽名校驗只是第一道門金額校驗才是關(guān)鍵很多碼支付插件確實做了簽名校驗但僅此而已。如果回調(diào)代碼拿到的money參數(shù)沒有被認(rèn)真和訂單金額對比攻擊者是可以構(gòu)造一條合法簽名但金額為 0.01 元的通知來刷訂單的。所以上線前必須確認(rèn)回調(diào)處理里有這行邏輯if (abs(floatval($notify[money]) - floatval($order[order_amount])) 0.01) { // 金額不符拒絕處理 }這一步不是可選項是必須項。沒有金額校驗相當(dāng)于把收款邏輯的門鎖只裝了一半。6.2 冪等處理防止重復(fù)通知導(dǎo)致的數(shù)據(jù)臟前面已經(jīng)提過重復(fù)通知的問題這里再強(qiáng)調(diào)一下碼支付平臺的通知機(jī)制在你返回成功之前會持續(xù)重試頻率可能從幾秒到幾分鐘不等。如果回調(diào)代碼不做冪等判斷每重試一次就會執(zhí)行一次更新邏輯寫一次日志這會讓訂單數(shù)據(jù)變得很不可靠。訂單處理開頭先查一次支付狀態(tài)已支付直接返回成功這是一個成本極低但收益極高的防御邏輯任何支付插件都建議保留。6.3 商戶密鑰的存放與使用習(xí)慣商戶密鑰 key 是簽名的核心相當(dāng)于你在這個支付體系里的銀行卡密碼。有些插件為了方便把密鑰直接明文寫在配置文件里而配置文件又放在網(wǎng)站根目錄下的data或includes里一旦網(wǎng)站目錄被掃描或源碼泄露密鑰就跟著丟了。務(wù)必要保證密鑰文件不會被瀏覽器直接訪問到Apache/Nginx 配置里把*.txt、*.log、*.sql這類文件全部禁止外部訪問。如果你對服務(wù)器不熟悉最簡單的辦法是給這些文件起一個難以猜到的名字盡量不要叫config.php、key.txt這種一眼能看穿的名稱。6.4 備用通道不能省個人接口不要用在真實交易上說到最后還是要潑一盆冷水。碼支付這類個人聚合接口本質(zhì)上是個人的收款碼在承接商業(yè)交易它天然有兩個問題一是收款有明顯的單日限額超過一定金額的交易會被風(fēng)控中斷二是通道穩(wěn)定性不掌握在你手里平臺方運(yùn)營狀況、收款賬戶被限制等風(fēng)險都會直接傳導(dǎo)到你的商城業(yè)務(wù)上。所以我的建議是個人學(xué)習(xí)、測試、內(nèi)測階段用碼支付可以把整套流程跑通但如果你真的在運(yùn)營一個面向陌生客戶的商城還是應(yīng)該老老實實申請官方商戶接口哪怕是個人小商戶的版本穩(wěn)定性也不是一回事。碼支付插件可以作為臨時的過渡方案或者在官方接口沒下來之前先頂一陣子但不要把它當(dāng)成長期生產(chǎn)方案。我的體會是這套插件真正值錢的地方不是支付寶微信財付通這三個名字而是它把個人收款和 ECShop 訂單系統(tǒng)之間那條縫給補(bǔ)上了。裝好它哪怕只是測試你也能把整套支付閉環(huán)從頭到尾跑一遍這對理解支付系統(tǒng)的工作方式幫助很大。最后再分享一個復(fù)盤技巧遇到任何支付問題先別急著改代碼把碼支付后臺的通知記錄和網(wǎng)站日志兩邊一對十有八九就能定位。這套排查思路比插件本身活得久。本文還有配套的精品資源點(diǎn)擊獲取