都說微信支付有些坑,都抱怨微信支付的文檔太爛,一會APPId,一會商戶id,還有appsecret,支付API秘鑰讓你傻傻分不清楚,還有這里大寫那里小寫,幾種標准,讓你眼花繚亂。沒錯,這就是很多技術團隊都存在的問題,沒有統一的標准!且團隊越大越嚴重,即使是在微信這樣的頂尖團隊。然而這些在一番痛苦折騰之后,最后都會不值一提。這里只詳細講JSAPI方式的公眾號支付
簡要思路圖
配置支付授權目錄
有點類似授權回調安全域名的韻味,需要將支付的頁面路徑添加到授權目錄里面,否則再頁面調起支付時會報沒有添加支付目錄的錯誤
配置地址: 微信公眾平台-微信支付-開發配置
獲取openid
統一下單
通常情況下是自身系統生成訂單后進入支付頁面,用戶點擊支付觸發一個請求,將訂單id、openid傳給后台微信統一下單接口,后台根據訂單id在自身系統查詢一遍,獲得價格、描述詳情等信息
一次簽名,發送xml報文給微信服務器
String nonceStr = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS";//暫時不變
// 加密,這里只列舉必填字段
Map<String, String> map = new HashMap<String, String>();
map.put("body", body);//商品描述
map.put("mch_id", MCHID);//商戶平台id
map.put("appid", WX_APPID);//公眾號id
map.put("nonce_str", nonceStr);//隨機字符串
map.put("notify_url", WX_PAY_CALLBACK);//異步回調api
map.put("spbill_create_ip", ip);//支付ip
map.put("out_trade_no", orderSn);//商品訂單號
map.put("total_fee", (int) relAmount + "");//真實金額
map.put("trade_type", "JSAPI");//JSAPI、h5調用
map.put("openid", openid);//支付用戶openid
String sign = WxPaySignatureUtils.signature(map, WX_PAY_KEY);
String xml = "<xml>" +
"<appid>"+ WX_APPID +"</appid>"+
"<body>"+ body +"</body>"+
"<mch_id>"+ MCHID +"</mch_id>"+
"<nonce_str>"+ nonceStr +"</nonce_str>"+
"<notify_url>"+ WX_PAY_CALLBACK +"</notify_url>"+
"<openid>"+ openid +"</openid>"+
"<out_trade_no>"+ orderSn +"</out_trade_no>"+
"<spbill_create_ip>"+ ip +"</spbill_create_ip>"+
"<total_fee>"+ (int) relAmount + "" +"</total_fee>"+
"<trade_type>JSAPI</trade_type>"+
"<sign>"+ sign +"</sign>"+
"</xml>";
LOGGER.info("發送給微信的報文:" + xml);
LOGGER.info("加密后的的簽名字符串:" + sign);
// 請求
String response = "";
try {
response = apiService.doPostString(WX_UNIFIEDORDER, xml);
} catch (Exception e) {
//TODO
return null;
}
LOGGER.info("請求/pay/unifiedorder下單接口后返回數據:" + response);
//處理請求結果
XStream s = new XStream(new DomDriver());
s.alias("xml", WechatOrder.class);
WechatOrder order = (WechatOrder) s.fromXML(response);
if ("SUCCESS".equals(order.getReturn_code()) && "SUCCESS".equals(order.getResult_code())) {
LOGGER.info("微信支付統一下單請求成功,獲得的Prepay_id是:" + order.getPrepay_id());
} else {
LOGGER.error("微信支付統一下單請求錯誤:" + order.getReturn_msg() + order.getErr_code());
//TODO
return null;
}
注:
- WX_UNIFIEDORDER變量的地址是: https://api.mch.weixin.qq.com/pay/unifiedorder
- relAmount 是實際支付的金額,實際項目中應該是根據訂單號在自身系統查詢后獲得
- body 是最終顯示在支付憑證里面的【商品詳情】
- orderSn 是最終顯示在支付憑證-交易記錄 里面的【商戶單號】,也就是自身系統的訂單號
這個時候如果你看到下面的日志,恭喜你偉大的第一步已經完成了,可以小憩喝杯咖啡再來哈哈
11:46:25.170 INFO com.cramix.portal.service.WechatService 451 createOrder - 請求/pay/unifiedorder下單接口后返回數據:<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><appid><![CDATA[wx8daca08d2f87216f]]></appid><mch_id><![CDATA[1482800922]]></mch_id><nonce_str><![CDATA[mjIbwmjLNFcD5tAF]]></nonce_str><sign><![CDATA[65EFB7B0BACBA01163765EB28B4E3F31]]></sign><result_code><![CDATA[SUCCESS]]></result_code><prepay_id><![CDATA[wx201706291146256b14b6eb620242392023]]></prepay_id><trade_type><![CDATA[JSAPI]]></trade_type></xml>
11:46:25.179 INFO com.cramix.portal.service.WechatService 459 createOrder - 微信支付統一下單請求成功,獲得的Prepay_id是:wx201706291146256b14b6eb620242392023
當然,更多的時候不會有這么順利,大多數人都會遇到簽名錯誤的狀態返回,微信確實是很喜歡用簽名,簽名錯誤這個四個字估計把所有的微信開發者都折磨了一遍,沒有經歷過簽名錯誤的程序員不是真正的微信開發者。
然而,簽名錯誤該怎么解決呢?通常情況下注意有三:
- 發送給微信的xml報文格式、字段有誤。body字段如有中文一定要在請求里面加上這句:
HttpPost post = new HttpPost(uri);
post.setEntity(new StringEntity(str,"utf-8"));
- 加密算法是否有誤,參考WxPaySignatureUtils
- 確認商戶平台API秘鑰(仔細閱讀微信支付申請成功后發過來的郵件,點擊【下載API證書、設置API密鑰】)
二次簽名
當你喝完那杯“甜”的咖啡之后,就要擼接下來的代碼了。將前端需要用的字段進行加密校驗,並返回
核心代碼如下:
HashMap<String, String> back = new HashMap<String, String>();
String time = Long.toString(System.currentTimeMillis());
back.put("appId", WX_APPID);
back.put("timeStamp", time);
back.put("nonceStr", nonceStr);
back.put("package", "prepay_id=" + order.getPrepay_id());
back.put("signType", "MD5");
String sign2 = WxPaySignatureUtils.signature(back, WX_PAY_KEY);
LOGGER.info("二次簽名后返回給前端的簽名證書字符串是:" + sign2);
JSONObject jsonObject = new JSONObject();
jsonObject.put("appId", WX_APPID);
jsonObject.put("timeStamp", time);
jsonObject.put("nonceStr", nonceStr);
jsonObject.put("package", "prepay_id=" + order.getPrepay_id());
jsonObject.put("signType", "MD5");
jsonObject.put("paySign", sign2);
LOGGER.info("二次簽名后返回給前端的數據是:" + jsonObject.toJSONString());
到此為止,微信支付統一下單環節簡單的后台代碼已經碼好了,只欠前端喚起支付了!
前端調起支付
這里有個巨坑,千萬不要踩,微信公眾平台技術文檔-微信JSSDK里面的微信支付跟這里沒有任何關系!你只要看商戶平台的文檔!,也就是通過WeixinJSBridge對象去調用,當然只在微信app里有用!
//統一下單
unifiedorder: function(){
var self = this;
var userInfo = localStorage.getItem('ssqf_h5_wxUserInfo');
CRAMIX.POST({
baseUrl: URL.BASE + '/api/wechat/pay/h5',
data: {
'openid': JSON.parse(userInfo).openid,
'body':'書身祈福支付',
'orderSn': $('#orderSn').val() || 20 ,
'amount': $('#money').val() || '0.01'
},
success: function(data){
self.options.unifiedorderData = data;
}
})
},
//支付
pay: function(){
var self = this;
var appId = self.options.unifiedorderData.appId;
var timeStamp = self.options.unifiedorderData.timeStamp;
var nonceStr = self.options.unifiedorderData.nonceStr;
var package1 = self.options.unifiedorderData.package;
var paySign = self.options.unifiedorderData.paySign;
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公眾號名稱,由商戶傳入
"timeStamp":timeStamp, //時間戳,自1970年以來的秒數
"nonceStr":nonceStr, //隨機串
"package":package1,
"signType":"MD5", //微信簽名方式:
"paySign":paySign //微信簽名
},
function(res){
WeixinJSBridge.log(res.err_msg);
if(res.err_msg == "get_brand_wcpay_request:ok"){
window.location.href = '/weixin/pay/pay-success.html';//去支付成功頁面
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
$.toast("用戶取消", "text");
}else{
$.toast("支付失敗", "forbidden", function() {
window.location.reload();//刷新頁面
});
}
}
);
},
授權獲取openid這里就不多說了,amount為支付金額,我這里是為了方便測試改為了金額前端傳輸,實際肯定不是哈!當然金額有變之后需要重新走一遍統一下單的流程。
感悟
其實我們認為的所有坑大部分原因都是源自不細心,盡管文檔等各種會讓我們多走一些彎路,但是不能帶着情緒去開發,遇到問題首先想自身不足,靜下心來,寫程序本身就需要有足夠的耐心和十分的細心。去年做微信分享,也是有個簽名,足足讓我困了三天才搞定,有時候真的都懷疑人生了,但是最后都解決了,解決的那一瞬間,真的能感動自己!這回做微信支付,同樣還是簽名問題,也前前后后搞了差不多一天,當然這其中有很大部分原因是吸取了去年的經驗,也就是上面所說。