做支付的前提條件:
1,得到用戶的openid,沒獲取到的朋友可以看我的上一篇文章:https://blog.csdn.net/qq_24800377/article/details/53437040
2,有一個微信支付的商戶號,其中公衆號支付功能必須開通。
下面開始具體的支付開發步驟:
步驟:
第一步,訪問微信提供的一個網址,獲取 prepay_id ,生成統一訂單,它又叫預支付id。
第二步,在需要支付的頁面,調用微信的jssdk,也就是網頁中的javascript,微信有代碼,調起微信支付頁面。(注:prepay_id爲該步驟使用 )
第三步:在微信提供的javascript代碼中填寫支付成功頁面的url,搞定。
看起來就三步,十分簡單,但微信支付能這麼簡單嘛?當然不可能,我做的時候最麻煩的地方就是第一步和第二步中,微信需要的參數,各種亂七八糟的,還有簽名什麼的,還有微信後臺需要配置的一些信息…
第一步詳解:
邏輯:訪問一個網址,按微信要求填寫參數,訪問成功後,微信返回一段xml數據,其中就包括 prepay_id 數據,也是我下一步需要的。
網址: https://api.mch.weixin.qq.com/pay/unifiedorder
參數:一段xml數據
生成參數xml的代碼:
public static String createXml(WxPayModel wxPayModel) { String xml = "<xml>" + "<appid>APPID</appid>" + "<body>BODY</body>" + "<device_info>DEVICEINFO</device_info>" + "<mch_id>MCHID</mch_id>" + "<nonce_str>NONCESTR</nonce_str>" + "<notify_url>NOTIFYURL</notify_url>" + "<openid>OPENID</openid>" + "<out_trade_no>OUTTRADENO</out_trade_no>" + "<sign>SIGN</sign>" + "<total_fee>TOTALFEE</total_fee>" + "<trade_type>TRADETYPE</trade_type>" + "</xml>"; xml = xml.replace("APPID", WxPayConstant.appid); xml = xml.replace("BODY", wxPayModel.getBody()); xml = xml.replace("DEVICEINFO", WxPayConstant.device_info); xml = xml.replace("MCHID", WxPayConstant.mch_id); xml = xml.replace("NONCESTR", wxPayModel.getNonceStr()); xml = xml.replace("NOTIFYURL", WxPayConstant.notify_url); xml = xml.replace("OPENID", wxPayModel.getOpenid()); xml = xml.replace("OUTTRADENO", wxPayModel.getOutTradeNo()); xml = xml.replace("SIGN", wxPayModel.getSign()); xml = xml.replace("TOTALFEE", wxPayModel.getTotalFee()); xml = xml.replace("TRADETYPE", WxPayConstant.trade_type); return xml; }
生成xml所需參數與獲取方法:
appid ==應用ID==登陸微信公衆號後臺-開發-基本配置 查看
body==商品描述==商品或支付單簡要描述 隨便寫,測試時最好上英文,防止編碼錯誤
device_info==設備號==終端設備號(門店號或收銀設備ID),注意:PC網頁或公衆號內支付請傳"WEB"
mch_id == 微信支付商戶號==登陸微信支付後臺,即可看到
nonce_str==隨機字符串==隨機字符串 ,下面有工具方法
notify_url==通知地址==接收微信支付異步通知回調地址,先隨便起一個
openid==用戶標識,前提條件已經獲取到了
out_trade_no==商戶訂單號==商戶系統內部的訂單號,32個字符內、可包含字母,下方有工具方法
sign==簽名==官方給的簽名算法 ,下方有工具方法,注:生成簽名的參數要按照工具方法中的參數寫,它與生成的xml是配套的,如果不一致會一直報簽名失敗,內部的參數可以自定義,但需要與xml同步修改,不建議改。
trade_type==交易類型==JSAPI 寫死
total_fee==總金額==訂單總金額,單位爲分,測試給1即可,1分錢
注:生成簽名sign的時候,需要值key,它是微信商戶平臺(pay.weixin.qq.com)-->賬戶設置-->API安全-->密鑰設置,自定義生成,用MD5加密成32位的字符串
工具方法:
/** * 生成隨機數 * * @return */ public static String getNonceStr() { Random random = new Random(); return MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)), "UTF-8"); }
/** * 生成訂單id * * @return */ public static String generateUUID() { return IdGen.uuid().toString().replaceAll("-", "").substring(0, 32); }
/** * 生成簽名 * @param wxPayModel * @return */ public static String createSign(WxPayModel wxPayModel) { // 微信api提供的參數 String appid = WxPayConstant.appid; String mch_id = WxPayConstant.mch_id; String device_info = WxPayConstant.device_info; String nonce_str = wxPayModel.getNonceStr(); String notify_url = WxPayConstant.notify_url; // System.out.println("++++++++++++++++++++++++++++++++++++++++"); // // System.out.println("參與簽名appid:" + appid); // System.out.println("參與簽名mch_id:" + mch_id); // System.out.println("參與簽名nonce_str:" + nonce_str); // System.out.println("參與簽名body:" + wxPayModel.getBody()); // System.out.println("參與簽名out_trade_no:" + wxPayModel.getOutTradeNo()); // System.out.println("參與簽名total_fee:" + wxPayModel.getTotalFee()); // System.out.println("參與簽名spbill_create_ip:" + wxPayModel.getSpbillCreateIp()); // System.out.println("參與簽名notify_url:" + notify_url); // System.out.println("參與簽名device_info:" + device_info); // System.out.println("參與簽名trade_type:" + WxPayConstant.trade_type); // System.out.println("參與簽名openid:" + wxPayModel.getOpenid()); // // System.out.println("++++++++++++++++++++++++++++++++++++++++"); SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); parameters.put("appid", appid); parameters.put("mch_id", mch_id); parameters.put("nonce_str", nonce_str); parameters.put("body", wxPayModel.getBody()); parameters.put("out_trade_no", wxPayModel.getOutTradeNo()); parameters.put("total_fee", wxPayModel.getTotalFee()); parameters.put("notify_url", notify_url); parameters.put("device_info", device_info); parameters.put("trade_type", WxPayConstant.trade_type); parameters.put("openid", wxPayModel.getOpenid()); String characterEncoding = "UTF-8"; StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet();// 所有參與傳參的參數按照accsii排序(升序) Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + WxPayConstant.key); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; }
生成xml(reqBody)後,執行以下訪問微信網址的代碼:
URL httpUrl = new URL("https://api.mch.weixin.qq.com/pay/unifiedorder"); HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection(); httpURLConnection.setRequestProperty("Host", "api.mch.weixin.qq.com"); httpURLConnection.setDoOutput(true); httpURLConnection.setRequestMethod("POST"); httpURLConnection.setConnectTimeout(10*1000); httpURLConnection.setReadTimeout(10*1000); httpURLConnection.connect(); OutputStream outputStream = httpURLConnection.getOutputStream(); outputStream.write(reqBody.getBytes("UTF8"));//reqBody是上面的費大力生成的xml //獲取內容 InputStream inputStream = httpURLConnection.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF8")); final StringBuffer stringBuffer = new StringBuffer(); String line = null; while ((line = bufferedReader.readLine()) != null) { stringBuffer.append(line); } String resp = stringBuffer.toString(); if (stringBuffer!=null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream!=null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (outputStream!=null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("++++++++++++++++++++++"); System.out.println("獲取預支付id返回的結果爲:"+resp); System.out.println("++++++++++++++++++++++"); Map<String, String> hm; String prepay_id = ""; try { hm = WXPayUtil.xmlToMap(resp); System.out.println("++++++++++++++++++++++"); System.out.println("支付id:"+hm.get("prepay_id")); System.out.println("++++++++++++++++++++++"); prepay_id = hm.get("prepay_id"); } catch (Exception e) { e.printStackTrace(); }得到: prepay_id 後,第一步搞定。
第二步詳解:
邏輯:調用微信一段js代碼,按微信要求填寫參數,訪問成功後,微信會彈出輸入密碼付款的界面,付款後,微信將進入回調。
特殊:這一步有兩種方式,第一種方式按照微信文檔上的javascript代碼片段調用,將相應的參數替換後即可成功,即(如下):
備註:我是怎麼調用都沒有成功,一直提示WeixinJSBridge未定義,調了老半天后,因爲時間有限,不得不換了第二種方式。
function onBridgeReady() { WeixinJSBridge.invoke('getBrandWCPayRequest', { "appId" : "${appid}", //公衆號名稱,由商戶傳入 "timeStamp" : "${timeStamp}", //時間戳,自1970年以來的秒數 "nonceStr" : "${nonceStr}", //隨機串 "package" : "${packageValue}", "signType" : "MD5", //微信簽名方式: "paySign" : "${sign}" //微信簽名 }, function(res) { // 使用以下方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在用戶支付成功後返回 ok,但並不保證它絕對可靠。 if (res.err_msg == "get_brand_wcpay_request:ok") { alert("支付成功"); window.location.href = "${ctx}/parent/user/buyList"; } else if (res.err_msg == "get_brand_wcpay_request:fail") { alert('支付失敗'); } else if (res.err_msg == "get_brand_wcpay_request:cancel") { alert('支付取消'); } else { alert(res.err_msg); } }); } if (typeof ('WeixinJSBridge') == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } } else { onBridgeReady(); }第二種方式,是根據微信文檔附錄上的javascript代碼片段寫成,如下:
wx.config({ debug : true, // 開啓調試模式,調用的所有api的返回值會在客戶端alert出來,若要查看傳入的參數,可以在pc端打開,參數信息會通過log打出,僅在pc端時纔會打印。 appId : "${appid}", // 必填,公衆號的唯一標識 timestamp : "${timeStamp}", // 必填,生成簽名的時間戳 nonceStr : "${nonceStr}", // 必填,生成簽名的隨機串 signature : "${signature}",// 必填,簽名 jsApiList : ['chooseWXPay'] // 必填,需要使用的JS接口列表 }); // config信息驗證後會執行ready方法,所有接口調用都必須在config接口獲得結果之後,config是一個客戶端的異步操作,所以如果需要在頁面加載時就調用相關接口,則須把相關接口放在ready函數中調用來確保正確執行。對於用戶觸發時才調用的接口,則可以直接調用,不需要放在ready函數中。 wx.ready(function() { wx.chooseWXPay({//微信支付接口,可以先註釋掉,當確定wx.ready執行後再放開 appId: "${appid}", timestamp : "${timeStamp}", // 支付簽名時間戳,注意微信jssdk中的所有使用timestamp字段均爲小寫。但最新版的支付後臺生成簽名使用的timeStamp字段名需大寫其中的S字符 nonceStr : "${nonceStr}", // 支付簽名隨機串,不長於 32 位 package : "${packageValue}", // 統一支付接口返回的prepay_id參數值,提交格式如:prepay_id=\*\*\*) signType : "MD5", // 簽名方式,默認爲'SHA1',使用新版支付需傳入'MD5' paySign : "${paySin}", // 支付簽名 success : function(res) { /* alert("支付成功,來自網頁的消息:"+res.errMsg); */ if (res.errMsg == "chooseWXPay:ok") { /* alert("支付成功"); */ } else if (res.errMsg == "chooseWXPay:fail") { /* alert('支付失敗'); */ } else if (res.errMsg == "chooseWXPay:cancel") { /* alert('支付取消'); */ } else { alert(res.errMsg); } } }); }); wx.error(function(res) { // config信息驗證失敗會執行error函數,如簽名過期導致驗證失敗,具體錯誤信息可以打開config的debug模式查看,也可以在返回的res參數中查看,對於SPA可以在這裏更新簽名。 alert('配置信息驗證出錯:' + res); });
解釋,當調用以上js代碼後,會先走wx.config,當驗證成功後,會走wx.ready,否則會走wx.error。
wx.config中的參數,需要在控制檯中得到,然後傳到前臺頁面的js中使用(注:除了下面四個參數外,還有調用微信支付接口chooseWXPay的參數,最後要將這些參數一起傳給前臺js中,開發階段可以先只傳下面四個,調通後再傳支付接口需要的參數):
appId:寫死的,就是第一步中的appid,注意區分大小寫,這裏的參數爲:appId
timestamp:簽名時間戳,都是小寫!下方面有工具方法
nonceStr:隨即字符串,用第一步的隨即字符
signature:簽名,下面有工具方法
/** * 生成時間戳 * * @return */ public static String getTimeStamp() { return String.valueOf(System.currentTimeMillis() / 1000); }
簽名的獲取比較複雜:
1,先通過access_token獲取票據
2,通過票據獲取簽名
/** * 獲取accessToken */ public static String getAccessToken() throws Exception { String appid = WxPayConstant.appid; String secret = WxPayConstant.secret; String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret; System.out.println("+++++++++++++++++++++"); System.out.println("獲取accessTiken的url:"+url); System.out.println("+++++++++++++++++++++"); HttpGet get = HttpClientConnectionManager.getGetMethod(url); HttpResponse response = httpclient.execute(get); String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8"); JSONObject object = JSON.parseObject(jsonStr); System.out.println("+++++++++++++++++++++"); System.out.println("得到的access_token:"+object.toString()); System.out.println("+++++++++++++++++++++"); String accessToken = object.getString("access_token"); return accessToken; }
通過access_token獲取臨時票據
/** * 獲取臨時票據 * * @return */ public static String getJsapiTicket() throws Exception { String access_token = getAccessToken(); String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + access_token + "&type=jsapi"; HttpGet get = HttpClientConnectionManager.getGetMethod(url); HttpResponse response = httpclient.execute(get); String jsonStr = EntityUtils.toString(response.getEntity(), "utf-8"); JSONObject object = JSON.parseObject(jsonStr); System.out.println("++++++++++++++"); System.out.println("臨時票據json:" + object.toString()); System.out.println("++++++++++++++"); String ticket = object.getString("ticket"); String expires_in = object.getString("expires_in");// 有效時間 String nowTime = object.getString("expires_in");// 當前時間 return ticket; }
/** * 生成js-sdk簽名算法 * * @param jsapi_ticket 臨時票據 * @param url 是你前臺頁面的url,也就是寫微信js代碼的那個頁面的地址, * @param nonce_str 隨即字符串,用第一步生的的隨即字符 * @param timestamp 時間戳 * @return */ public static String sign(String jsapi_ticket, String url, String nonce_str, String timestamp) { Map<String, String> ret = new HashMap<String, String>(); String string1; String signature = ""; // 注意這裏參數名必須全部小寫,且必須有序 string1 = "jsapi_ticket=" + jsapi_ticket + "&noncestr=" + nonce_str + "timestamp=" + timestamp + "&url=" + url; System.out.println("+++++++++++++++++++++++++++++"); System.out.println("js-sdk簽名算法:" + string1); System.out.println("+++++++++++++++++++++++++++++"); try { MessageDigest crypt = MessageDigest.getInstance("SHA-1"); crypt.reset(); crypt.update(string1.getBytes("UTF-8")); signature = byteToHex(crypt.digest()); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return signature; }備註:關於隨機數和時間戳,我分別只生成了一次,然後所有用到的地方都用的最開始生成的那一個,至於不用統一的值好不好使,沒有測試過,勤勞的小夥伴們可以自己嘗試一下。
調通wx.config後進入wx.ready。
到了這一步就要開始調用微信的支付js,裏面有六個參數,其中四個參數用上面的值就可以,主要是package、paySign這兩個參數。
package:"prepay_id="+prepay_id。記住,package的參數值一定是:"prepay_id="+prepay_id,不要忘記"prepay_id="
paySign:又一個簽名,下面有簽名工具方法。
public static String createPaySin(String packages, String timeStamp, String nonceStr) { SortedMap<Object, Object> parameters = new TreeMap<Object, Object>(); parameters.put("appId", WxPayConstant.appid); parameters.put("timeStamp", timeStamp);// 時間戳==規則:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2。看完仍是一臉迷茫的,沒關係,我們有工具類。誰知道呢,直接調用就好了 parameters.put("nonceStr", nonceStr); parameters.put("package", packages); parameters.put("signType", "MD5"); System.out.println("++++++++++++++++"); System.out.println("第二個簽名appId:"+WxPayConstant.appid); System.out.println("第二個簽名timeStamp:"+timeStamp); System.out.println("第二個簽名nonceStr:"+nonceStr); System.out.println("第二個簽名package:"+packages); System.out.println("第二個簽名signType:MD5"); System.out.println("++++++++++++++++"); String characterEncoding = "UTF-8"; StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet();// 所有參與傳參的參數按照accsii排序(升序) Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + WxPayConstant.key); String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase(); System.out.println("++++++++++++++++"); System.out.println("第二個簽名:"+sign); System.out.println("++++++++++++++++"); return sign; }
參與簽名的五個參數,到了這步它們應該都有值了,所以直接放進方法內,生成簽名即可。
完成以上所有步驟,這時候可以運行調用嘗試了!!!
如果一次成功,恭喜!!!如果沒成功還得繼續解決…
我遇到的問題:
支付目錄未授權
這是在微信商戶平臺上填寫的目錄授權,實際上就是一個路徑,要具體到支付頁的上一級目錄,比如你的支付頁是a文件夾下的b.jsp,那麼目錄授權必須填到a。
我不是這個原因,這個我填的沒問題,後來發現,我需要將我支付的接口也就是生成prepay_id 所在的接口,當成目錄,填在商戶平臺上,最後終於調用成功。
備註:如果做支付的時候,微信返回不成功,又沒有具體錯誤提示,這時候可以換手機試,手機分安卓和蘋果,安卓手機上沒有提示,不代表蘋果上沒有…我就是安卓手機上沒有,後來在小夥伴的蘋果手機上看到具體錯誤提示的