一、背景介紹
作為一名Android開發,從最初的跌跌撞撞到現在小有所悟,這其中經歷過的辛酸苦辣也是一種痛并快樂著的過程。在這一個過程中,不斷的在工作中、在網絡上向各位前輩朋友學習,一次一次的充實了自己,學到了新東西,解決了新問題,內心甚是感激。同時在這一過程中,也慢慢積累了自己的一些經驗,趁著有了一點空余時間,拿出來分享一下,希望能幫助到有需要的朋友。因為本人技術能力有限,如文章有欠缺不妥之處,還望指正。
最近的開發工作又重新接入了一次微信支付,終于完成了從開放平臺的賬號設置到APP端、服務器端的代碼編寫都有參與的一個歷程,算是理清了微信支付的一個整體流程,并且對如何避免入坑以及錯誤掃雷有了比較大的認知。記得上一次的接入還是一年前,那時的主要角色只限于APP端的開發工作,雖說也是APP端的主導開發,但是并不能從全局從整體去參與,所以部分流程和概念還是一個似懂非懂的一個狀態(最終APP接入微信支付是成功的,但也是知其然不知所以然)。故而在此寫下一遍文章以記錄所知所感,期以做個人備忘及新人參考之用。
這篇文章的主要目的不是介紹如何接入微信支付快速完成代碼開發,我相信這個問題的答案在微信的官方開發文檔以及網絡上都已有了先例,而且也寫得很好。如果你是剛剛接觸微信支付接入并且沒有閱讀過官方開發文檔,那么你在看這篇文章時會有很多困惑;但是如果你已經在接入的過程當中,并且對官方開發文檔比較熟悉,那么本文中的某些部分能夠給你一種豁然開朗的感覺。正如標題所言,本文的目的在于說明微信支付的分工合作,重點了解從【微信支付開放平臺的APP信息設置】到【服務器后臺獲取預支付id信息】再到【APP端拿到服務器信息后發起支付請求】直到最后【服務器和客戶端得到支付成功通知】的這樣一個過程,在這一過程中,將會在適當的位置以Tips的形式插入需要注意的地方,期望能夠作為錯誤掃雷手冊參考。因為本文針對的是開發者,特別是Android開發者,可能賬號申請及APP設置環節、服務器后臺處理流程環節大部分人沒有去參與過,本著查漏補缺的原則,所以會對這兩個環節進行更為詳細的描述,而對APP接入代碼編寫環節主要做一個理論上的描述,不涉及具體的代碼實現,如有興趣可以留言進行交流。
二、分工合作流程
2.1 賬號申請和APP設置
2.1.1 ?賬號申請
打開微信支付開放平臺登錄地址,按照微信支付申請接入流程,選擇一個接入場景進行接入。例如APP支付的接入流程是:
1、注冊開放平臺賬號
2、認證開發者資質
3、創建APP并提交審核(Tips:設置好APP的keystore簽名)
4、提交資料申請微信支付
5、開戶成功,登錄商戶平臺進行驗證
6、在線簽署協議
7、啟動設計和開發
2.1.2 ?APP設置
微信支付申請審核通過后,商戶在申請資料填寫的郵箱中將收取到由微信支付小助手發送的郵件,此郵件包含了開發時需要使用的支付賬戶信息內容。
收到郵件后,需要登錄微信商戶平臺設置API支付密鑰。
Tips:可以通過微信公眾平臺接口調試工具對微信支付的賬號以及API密鑰進行有效性驗證。具體方法是:
1、打開微信公眾平臺接口調試工具頁面,接口選擇【自定義】選項
2、填入微信支付必填參數及其參數值(注意:大小寫敏感)以及商戶Key(即API密鑰)
3、點擊生成簽名,就可以得到統一下單接口參數數據
4、將統一下單接口參數數據填入一個POST請求工具(例如Chrome插件Postman),向統一下單接口地址提交一個POST請求,如果能夠正常得到預支付id,那么說明微信支付賬號和API密鑰有效(Tips:除了appid、mch_id、trade_type、商戶Key,其他參數值都可以隨便填,只要符合字段類型定義就行,不會影響到最終獲取預支付id結果。例如notify_url參數如果還沒有自己的通知回調頁面,可以填入http://www.baidu.com之類的都行)。否則請返回之前的步驟,檢查支付賬號是否開通APP支付功能以及是否設置了API密鑰。
至此,微信支付賬號的準備工作完成,可以開始進行下一步的代碼開發工作了。
2.2 代碼開發流程
準備好了微信支付賬號,并確認賬號的支付權限和API密鑰設置無誤后,現在可以開始進行代碼開發了。上圖是微信官方文檔中的微信支付業務流程圖,從流程圖中可以看到代碼開發分服務器端和APP端,服務器端負責支付前的訂單生成、數據獲取和支付后的訂單狀態更新,APP端負責發起支付請求,簡言之,服務器負責支付前期、后期,APP則負責中期。
2.2.1 ?服務器接口開發
服務器接口的前期工作是:
1、生成內部訂單,調用統一下單接口,獲取到預支付id信息
服務器根據APP發送過來的商品id和用戶id信息,在數據庫中生成一條內部訂單,然后利用訂單信息、微信支付賬號信息生成統一下單接口參數,調用統一下單接口獲取得到預支付id。必填的接口參數如圖。
固定部分為每次調用接口都固定不變的參數(不是值全部寫死,而是每個訂單每次調用都不變),trade_type的值APP表明接入的是APP支付(如果是JSAPI支付,則填入JSAPI),notify_url則是支付回調頁面,這個頁面屬于后期工作中的支付結果監聽部分內容(Tips:該回調頁面應該能夠外網訪問,否則微信無法調用進行支付通知,這個在真正進行支付調試的時候要特別注意),spbill_create_ip可以直接填入服務器的外網IP地址(Tips:它的值是服務器內網或者外網地址,又或者是其他任意有效的IP地址,都不會影響獲取預支付id的結果,但是建議還是填入服務器外網IP地址)。
賬號部分為所申請的微信支付賬號信息部分,這部分主要提供appid和mch_id(Tips:appid和mch_id是一一對應的關系,不可以錯開使用,一個appid必須對應他在賬號申請時簽署的商戶mch_id,否則無法成功獲取預支付id信息)。appid和mch_id可以在后臺配置表中進行配置,一般情況下同一個APP每次調用統一下單接口時不會改變這兩個值,但也不排除針對不同訂單,其收款對象不同的這樣一個需求,所以在此處可以根據業務需求來進行賬號信息配置。
商品部分內容為所要支付的商品信息,out_trade_no為服務器生成的內部訂單id(Tips:該id會在支付回調頁面中返回,用以更新該訂單狀態,所以需要保證該id在內部訂單系統中的唯一性)。body字段為商品描述部分,用于在微信支付確認中顯示(Tips:該字段的類型定義是String(128),所以字段長度不要超過128字符)。total_fee字段則是商品的價格,其單位為分,類型為int,例如 total_fee = 666,則默認表明¥6.66。
驗證部分內容提供了安全性保障。nonce_str字段為一個隨機字符串,目的就是為了增加隨機性,讓簽名后的sign字段無法預料,使得通過攔截進行破解變得不可能。(Tips:nonce_str可以是16位隨機字符串,也可以是32位隨機字符串,或者其他少于等于32位的隨機字符串都可以,nonce_str字段在每次進行簽名前都應該重新生成而不是沿用原來的,否則就失去了它的意義。它的字符長度、字符類型集合、是否簽名前重新生成還是沿用都不會影響到最后獲取預支付id的成功與否,但是必須保證加入簽名獲得sign字段時的nonce_str與傳給微信的nonce_str參數兩者的值是一樣的。在獲得預支付id結果后,加入簽名獲得sign字段時的nonce_str與返回給前端的結果參數nonce_str也要一致,否則會出現簽名錯誤問題)。sign字段是整個參數的一個關鍵,它保證了參數傳輸和接收的安全性,保證這些參數從發送方到接收方的傳輸過程中,沒有被修改過。它的生成規則在官方文檔里面的說明中已經約定,所有傳遞過去的參數,除了sign參數字段,其他參數字段都要按照參數名進行ASCII碼字典序從小到大排序拼接成一串,然后在字符串末尾拼接API密鑰,最終得到一串拼接了API密鑰的參數鏈字符串,對該參數鏈字符串進行MD5加密成32位大寫字母字符串,即得到了最終的sign字段值。試想一下,如果有個黑客想要通過攔截修改的方式,將原本價格為100元的商品修改為0.01元,即把 total_fee = 10000 改成 total_fee = 1 ,然后發送給了微信,那么微信在驗證過程把參數拼接成參數鏈進行簽名后得到的sign字段已經和參數中原有的sign字段不一樣了,就會提示簽名錯誤異常,不接受該請求。而黑客如果想在修改了價格后再重新偽造sign字段來通過微信驗證,那么他就必須知道API密鑰,但該密鑰只有服務器端和微信端才有,并不通過網絡進行傳播,所以黑客也就無計可施了(Tips:所以API密鑰一定要妥善保管在服務器端,不能通過網絡傳播給APP客戶端,更不能內置在APP客戶端)。
2、請求統一下單接口獲得數據并處理
服務器端生成了參數后,調用統一下單接口獲得了接口返回的包含預支付id的結果,這時需要做兩個操作:
(1)對接口返回結果進行簽名驗證
從前面的sign字段分析中已經提到了,該字段保證了數據傳輸接收的安全性,所以在拿到接口返回數據時,要對該字段進行驗證,將所有參數剔除sign字段后按順序拼接成帶API密鑰的參數鏈字符串,然后通過MD5方法進行簽名,得到了新的32位大寫MD5字符串的sign簽名,將微信返回的sign字段與重新簽名的sign進行對比,如果一致,則表明參數準確無誤沒有被篡改過,否則一定是在某一個環節發生了問題(Tips:要善于利用微信公眾平臺接口調試工具來進行問題排查,如果簽名方法和參數字段都沒問題,但是調試工具得到的sign值和原有參數中的sign值不一致,那么,哈哈哈,恭喜你,你被黑客盯上了,否則請檢查你的簽名方法和參數字段是否一一對應)。
(2)生成APP前端調用支付所需參數并簽名,返回給前端
簽名驗證無誤后,再通過統一下單接口返回的數據生成前端調用支付所需字段,同時對這些字段進行簽名。例如Android APP支付所需的參數如下:
填入appid、mch_id、prepayid、packageValue,重新生成noncestr和timestamp,然后將這些參數排好序同時加上API密鑰拼成參數鏈字符串,對該字符串進行MD5簽名得到sign字段,最后將這些字段全部返回給前端即可(在與微信進行交互時,其命名規則一定要按照微信官方文檔進行定義,而在跟APP進行交互時,其參數key命名是否帶有下劃線、是否符合駝峰命名并不影響,這里的命名約定主要是服務器和APP前端兩者的約定。在此處我只能按照之前服務端接口小哥的命名來定義,所以各位不必深究)。
至此,服務器接口的前期工作已經完成。下面看看服務器接口在支付后期的工作內容。
服務器接口的后期工作是:編寫支付回調頁面,接收支付成功回調并修改訂單狀態,給微信返回訂單處理完成信息
在調用統一下單接口時需要填入一個notify_url回調頁面,這個頁面就是監聽支付成功通知的處理頁面,其本質就是給微信服務器在支付成功后的一個調用接口。在該頁面中,主要做三件事:
(1) 驗證接口傳入參數的安全性
微信調用該頁面時,會傳入以下信息:
在回調頁面收到這些參數數據時,首先需要對result_code、return_code進行驗證,只有兩者都為SUCCESS時才表明支付順利完成,否則在支付的某個環節中出了問題,請對照微信支付錯誤碼進行錯誤排除。
驗證了支付順利完成后,需要對sign簽名字段進行安全性驗證,其驗證規則與前期工作中的驗證規則一致。如果驗證通過,那么可以根據out_trade_no字段修改訂單狀態為已支付了。否則給微信服務器返回驗證錯誤信息(跳轉到下面的第3步)。
(2) 修改訂單狀態
驗證通過后,那么根據out_trade_no字段將訂單狀態修改為已支付狀態,如果修改過程一切順利,則可以給微信服務器返回處理成功信息,否則如果訂單修改過程中出現了問題(例如訂單已經不存在了),則給微信服務器返回訂單處理失敗信息。
(3)給微信服務器回復處理完成信息
在參數驗證和訂單處理過程中,無論成功與否,都需要給微信服務器返回一個處理完成信息,其返回格式如圖:
如果回調頁面處理的過程中出現了錯誤,那么請針對錯誤進行問題排查。如果沒有錯誤,成功處理回調請求,則該訂單支付順利完成。(Tips:回調頁面必須是外網能訪問的才能被微信服務器調用到,否則在調試階段請模擬數據進行調用)
至此,服務器接口的后期工作完成。并且整個微信支付開發過程中,服務器的工作也完成了。
2.2.2 APP代碼開發
APP在整個微信支付中主要負責中期的工作,該工作內容包含兩個方面:
1、調用服務器接口獲得預支付id等信息
當用戶點擊付款按鈕時,APP調用服務器的接口,將用戶id和商品id作為參數傳遞給服務器,獲取到包含sign簽名字段在內的返回數據(即服務器前期工作中最后一步的返回數據),用以發起微信支付請求。
2、利用微信API調用微信客戶端進行微信支付
獲取到預支付id等信息后,就可以調用微信支付SDK中的API,發起支付請求,彈出微信支付確認頁面,要求用戶輸入密碼進行支付。
至此,APP端就完成了支付的中期工作。
其實,APP端也有一個支付的后期工作。因為在前面微信支付流程圖中也有提到了,微信支付的結果通知,除了給服務器端,也會給APP端,所以APP端的這個后期工作是監聽微信支付結果通知。APP端監聽支付結果的方式是在項目工程的包名下,新建一個包文件夾,其必須命名為“wxapi”,在該包名下新建一個Activity,其命名也規定為“WXPayEntryActivity”,該Activity要實現IWXAPIEventHandler接口,另外別忘了在清單文件中聲明這個Activity。微信會在支付結果通知中回調該Activity的onResp方法,告知支付結果,Activity則在該方法中分別對三種結果進行響應處理(三種結果是:“resp.errCode == 0” 表明支付成功 ,“resp.errCode == -1”表明支付發生錯誤,“resp.errCode == -2” 表明用戶取消了支付)。
Tips:如果支付結果發生了錯誤,resp.errCode == -1,那么可能的原因是:
1、keystore簽名錯誤
在微信支付開放平臺創建APP時配置的應用簽名信息不對,請用正式發版的簽名keystore修正應用簽名信息;或者沒有打包簽名apk進行測試,用正式發版的簽名keystore簽名apk后再測。
2、sign字段簽名錯誤
服務器的返回數據中,sign簽名過程發生了錯誤。sign簽名過程中,參與簽名的參數字段命名一定要和微信規定的命名一致,簽名方法要按照官方文檔里面的說明進行。
3、未注冊appid、項目設置appid不正確、注冊的appid與后臺設置的不匹配、該appid沒有申請移動支付功能
先檢查代碼,看看在發起支付請求前有沒有調用API將appid注冊到微信。如果代碼有注冊了appid,那么聯系服務器開發和賬號申請相關人員,對appid的值進行排查比對,對appid與商戶mch_id是否一一對應進行確認,對該應用是否申請微信支付功能進行確認。
4、用戶微信客戶端的登錄被擠下線
可能用戶當前的微信客戶端登錄狀態已經被其他設備擠下線了,但是用戶并沒有在該設備重新登錄過微信,這時需要提示用戶重新登錄微信解決(曾經開發過程中遇到的這個問題,當時用模擬器和手機同時進行測試,一開始百試百靈,每次都能成功調用支付界面,但是模擬器和手機交叉進行測試有時卻不行了,后來在問題重現的過程中發現了這個規律,原來是當前設備微信登錄狀態被擠下線導致的)。
5、沒有進行微信客戶端是否安裝以及版本是否支持微信支付檢測
在發起支付請求前,就應該調用微信支付API對是否安裝微信以及安裝的微信版本是否支持微信支付進行判斷,如果沒安裝則提示安裝,如果微信版本不支持支付則提示用戶升級微信(Tips:一些會玩的Android手機玩家,常常通過綠色守護等軟件將微信客戶端綠色化,導致微信實際是安裝在手機上的,但是無法通過微信API獲得其是否支持支付的結果,所以這里在提示上面可以修改為“請先啟動或者升級你的微信客戶端來完成微信支付”)。
6、其他異常等。
to be continue
至此,APP端的微信支付完成了所有的工作。
三、結束語
到這里,經過分工合作,你應該已經成功接入了微信支付。在文章中只涉及了微信支付的付款流程,關于其他訂單查詢、退款等流程可以參照微信支付開放文檔進行接入(也許你也發現了,在前面完全沒有提及AppSecret,這是因為在付款的過程中并不會用到這個東東)。
好了,這篇文章到此結束,如果有不當之處或遺漏部分,歡迎留言指正,謝謝。