做支付的前提条件:
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 = "" + "APPID " + "BODY" + "DEVICEINFO " + "MCHID " + "NONCESTR " + "NOTIFYURL " + "OPENID " + "OUTTRADENO " + "SIGN " + "TOTALFEE " + "TRADETYPE " + ""; 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 所在的接口,当成目录,填在商户平台上,最后终于调用成功。
备注:如果做支付的时候,微信返回不成功,又没有具体错误提示,这时候可以换手机试,手机分安卓和苹果,安卓手机上没有提示,不代表苹果上没有…我就是安卓手机上没有,后来在小伙伴的苹果手机上看到具体错误提示的