一、首先分享 微信统一下单接口: javascript
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 php
微信接口 签名 对比网址:html
https://pay.weixin.qq.com/wiki/tools/signverify/前端
微信小程序 微信支付 网址:java
https://mp.weixin.qq.com/debug/wxadoc/dev/api/api-pay.html#wxrequestpaymentobject算法
二、微信小程序端 代码示例:sql
payment:function(event){ var that = this; console.log('去支付按钮点击事件') wx.request({ url: 'https://192.168.0.666:8080/matouwang/wechat/wechatAppletGolf/createUnifiedOrder', method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT // 当method 为POST 时 设置如下 的header header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { amount: teamMoney, openid: openId }, success: function (res) { if (res.data.prepayId != ''){ console.log('微信统一下单接口调用成功 数据包:' + res.data.prepayId); console.log('微信统一下单接口调用成功 订单号:' + res.data.outTradeNo); console.log('调用微信支付接口以前先生成签名') //保存订单号信息 var outTradeNo = res.data.outTradeNo; wx.request({ url: 'http://192.168.8.50:8080/matouwang/wechat/wechatAppletGolf/generateSignature', method: 'POST', // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT // 当method 为POST 时 设置如下 的header header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { prepayId: res.data.prepayId }, success: function (paryResult) { if (paryResult.data.sign != '') { console.log('微信支付接口以前先生成签名成功') console.log('签名:' + paryResult.data.sign) console.log('随机串:' + paryResult.data.nonceStr) console.log('时间戳:' + paryResult.data.timeStamp) //这个applyId必定要大写 并且签名的参数和调用方法的参数值必定要统一 wx.requestPayment({ 'appId': '', 'timeStamp': paryResult.data.timeStamp, 'nonceStr': paryResult.data.nonceStr, 'package': paryResult.data.package, 'signType': 'MD5', 'paySign': paryResult.data.sign, 'success': function (paymentRes) { console.log(paymentRes) that.setData({ notPay: true, paySuccess: false, teamNotPay: true, button:true, outTradeNo: outTradeNo, payDate:new Date() }) }, 'fail': function (error) { console.log(error) } }) } else { console.log('微信支付接口以前先生成签名失败') } } }) } } }); },
三、分享微信 统一下单 后台代码示例:小程序
@Override public JSONObject createUnifiedOrder(HttpServletRequest request,HttpServletResponse response) { logger.info("微信 统一下单 接口调用"); //设置最终返回对象 JSONObject resultJson = new JSONObject(); //建立条件 Criteria criteria = new Criteria(); //接受参数(金额) String amount = request.getParameter("amount"); //接受参数(openid) String openid = request.getParameter("openid"); //接口调用总金额单位为分换算一下(测试金额改为1,单位为分则是0.01,根据本身业务场景判断是转换成float类型仍是int类型) //String amountFen = Integer.valueOf((Integer.parseInt(amount)*100)).toString(); //String amountFen = Float.valueOf((Float.parseFloat(amount)*100)).toString(); String amountFen = "1"; //建立hashmap(用户得到签名) SortedMap<String, String> paraMap = new TreeMap<String, String>(); //设置body变量 (支付成功显示在微信支付 商品详情中) String body = "啦啦啦测试"; //设置随机字符串 String nonceStr = Utils.getUUIDString().replaceAll("-", ""); //设置商户订单号 String outTradeNo = Utils.getUUIDString().replaceAll("-", ""); //设置请求参数(小程序ID) paraMap.put("appid", APPLYID); //设置请求参数(商户号) paraMap.put("mch_id", MCHID); //设置请求参数(随机字符串) paraMap.put("nonce_str", nonceStr); //设置请求参数(商品描述) paraMap.put("body", body); //设置请求参数(商户订单号) paraMap.put("out_trade_no", outTradeNo); //设置请求参数(总金额) paraMap.put("total_fee", amountFen); //设置请求参数(终端IP) paraMap.put("spbill_create_ip", WebUtils.getIpAddress(request, response)); //设置请求参数(通知地址) paraMap.put("notify_url", WebUtils.getBasePath()+"wechat/wechatAppletGolf/payCallback"); //设置请求参数(交易类型) paraMap.put("trade_type", "JSAPI"); //设置请求参数(openid)(在接口文档中 该参数 是否必填项 可是必定要注意 若是交易类型设置成'JSAPI'则必须传入openid) paraMap.put("openid", openid); //调用逻辑传入参数按照字段名的 ASCII 码从小到大排序(字典序) String stringA = formatUrlMap(paraMap, false, false); //第二步,在stringA最后拼接上key获得stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将获得的字符串全部字符转换为大写,获得sign值signValue。(签名) String sign = MD5Util.MD5(stringA+"&key="+KEY).toUpperCase(); //将参数 编写XML格式 StringBuffer paramBuffer = new StringBuffer(); paramBuffer.append("<xml>"); paramBuffer.append("<appid>"+APPLYID+"</appid>"); paramBuffer.append("<mch_id>"+MCHID+"</mch_id>"); paramBuffer.append("<nonce_str>"+paraMap.get("nonce_str")+"</nonce_str>"); paramBuffer.append("<sign>"+sign+"</sign>"); paramBuffer.append("<body>"+body+"</body>"); paramBuffer.append("<out_trade_no>"+paraMap.get("out_trade_no")+"</out_trade_no>"); paramBuffer.append("<total_fee>"+paraMap.get("total_fee")+"</total_fee>"); paramBuffer.append("<spbill_create_ip>"+paraMap.get("spbill_create_ip")+"</spbill_create_ip>"); paramBuffer.append("<notify_url>"+paraMap.get("notify_url")+"</notify_url>"); paramBuffer.append("<trade_type>"+paraMap.get("trade_type")+"</trade_type>"); paramBuffer.append("<openid>"+paraMap.get("openid")+"</openid>"); paramBuffer.append("</xml>"); try { //发送请求(POST)(得到数据包ID)(这有个注意的地方 若是不转码成ISO8859-1则会告诉你body不是UTF8编码 就算你改为UTF8编码也同样很差使 因此修改为ISO8859-1) Map<String,String> map = doXMLParse(getRemotePortData(URL, new String(paramBuffer.toString().getBytes(), "ISO8859-1"))); //应该建立 支付表数据 if(map!=null){ //清空 criteria.clear(); //设置openId条件 criteria.put("openId", openid); //获取数据 List<WechatAppletGolfPayInfo> payInfoList = appletGolfPayInfoMapper.selectByExample(criteria); //若是等于空 则证实是第一次支付 if(CollectionUtils.isEmpty(payInfoList)){ //建立支付信息对象 WechatAppletGolfPayInfo appletGolfPayInfo = new WechatAppletGolfPayInfo(); //设置主键 appletGolfPayInfo.setPayId(outTradeNo); //设置openid appletGolfPayInfo.setOpenId(openid); //设置金额 appletGolfPayInfo.setAmount(Long.valueOf(amount)); //设置支付状态 appletGolfPayInfo.setPayStatus("0"); //插入Dao int sqlRow = appletGolfPayInfoMapper.insert(appletGolfPayInfo); //判断 if(sqlRow == 1){ logger.info("微信 统一下单 接口调用成功 而且新增支付信息成功"); resultJson.put("prepayId", map.get("prepay_id")); resultJson.put("outTradeNo", paraMap.get("out_trade_no")); return resultJson; } }else{ //判断 是否等于一条 if(payInfoList.size() == 1){ //获取 须要更新数据 WechatAppletGolfPayInfo wechatAppletGolfPayInfo = payInfoList.get(0); //更新 该条的 金额 wechatAppletGolfPayInfo.setAmount(Long.valueOf(amount)); //更新Dao int sqlRow = appletGolfPayInfoMapper.updateByPrimaryKey(wechatAppletGolfPayInfo); //判断 if(sqlRow == 1){ logger.info("微信 统一下单 接口调用成功 修改支付信息成功"); resultJson.put("prepayId", map.get("prepay_id")); resultJson.put("outTradeNo", paraMap.get("out_trade_no")); return resultJson; } } } } //将 数据包ID 返回 System.out.println(map); } catch (UnsupportedEncodingException e) { logger.info("微信 统一下单 异常:"+e.getMessage()); e.printStackTrace(); } catch (Exception e) { logger.info("微信 统一下单 异常:"+e.getMessage()); e.printStackTrace(); } logger.info("微信 统一下单 失败"); return resultJson; }
四、生成 微信支付 签名后台 代码示例:微信小程序
@Override public JSONObject generateSignature(HttpServletRequest request, HttpServletResponse response) { logger.info("微信 支付接口生成签名 方法开始"); //实例化返回对象 JSONObject resultJson = new JSONObject(); //得到参数(微信统一下单接口生成的prepay_id ) String prepayId = request.getParameter("prepayId"); //建立 时间戳 String timeStamp = Long.valueOf(System.currentTimeMillis()).toString(); //建立 随机串 String nonceStr = Utils.getUUIDString().replaceAll("-", ""); //建立 MD5 String signType = "MD5"; //建立hashmap(用户得到签名) SortedMap<String, String> paraMap = new TreeMap<String, String>(); //设置(小程序ID)(这块必定要是大写) paraMap.put("appId", APPLYID); //设置(时间戳) paraMap.put("timeStamp", timeStamp); //设置(随机串) paraMap.put("nonceStr", nonceStr); //设置(数据包) paraMap.put("package", "prepay_id="+prepayId); //设置(签名方式) paraMap.put("signType", signType); //调用逻辑传入参数按照字段名的 ASCII 码从小到大排序(字典序) String stringA = formatUrlMap(paraMap, false, false); //第二步,在stringA最后拼接上key获得stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将获得的字符串全部字符转换为大写,获得sign值signValue。(签名) String sign = MD5Util.MD5(stringA+"&key="+KEY).toUpperCase(); if(StringUtils.isNotBlank(sign)){ //返回签名信息 resultJson.put("sign", sign); //返回随机串(这个随机串是新建立的) resultJson.put("nonceStr", nonceStr); //返回时间戳 resultJson.put("timeStamp", timeStamp); //返回数据包 resultJson.put("package", "prepay_id="+prepayId); logger.info("微信 支付接口生成签名 设置返回值"); } logger.info("微信 支付接口生成签名 方法结束"); return resultJson; }
五、签名算法 将key Value 字典排序 代码示例:api
/** * * 方法用途: 对全部传入参数按照字段名的 ASCII 码从小到大排序(字典序),而且生成url参数串<br> * 实现步骤: <br> * * @param paraMap 要排序的Map对象 * @param urlEncode 是否须要URLENCODE * @param keyToLower 是否须要将Key转换为全小写 * true:key转化成小写,false:不转化 * @return */ private static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower){ String buff = ""; Map<String, String> tmpMap = paraMap; try { List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet()); // 对全部传入参数按照字段名的 ASCII 码从小到大排序(字典序) Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() { @Override public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { return (o1.getKey()).toString().compareTo(o2.getKey()); } }); // 构造URL 键值对的格式 StringBuilder buf = new StringBuilder(); for (Map.Entry<String, String> item : infoIds) { if (StringUtils.isNotBlank(item.getKey())) { String key = item.getKey(); String val = item.getValue(); if (urlEncode) { val = URLEncoder.encode(val, "utf-8"); } if (keyToLower) { buf.append(key.toLowerCase() + "=" + val); } else { buf.append(key + "=" + val); } buf.append("&"); } } buff = buf.toString(); if (buff.isEmpty() == false) { buff = buff.substring(0, buff.length() - 1); } } catch (Exception e) { return null; } return buff; }
六、发送远程请求 得到数据 代码示例:
/** * 方法名: getRemotePortData * 描述: 发送远程请求 得到代码示例 * 参数: @param urls 访问路径 * 参数: @param param 访问参数-字符串拼接格式, 例:port_d=10002&port_g=10007&country_a= * 版本号: v1.0 * 返回类型: String */ private String getRemotePortData(String urls, String param){ logger.info("港距查询抓取数据----开始抓取外网港距数据"); try { URL url = new URL(urls); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); // 设置链接超时时间 conn.setConnectTimeout(30000); // 设置读取超时时间 conn.setReadTimeout(30000); conn.setRequestMethod("POST"); if(StringUtil.isNotBlank(param)) { conn.setRequestProperty("Origin", "https://sirius.searates.com");// 主要参数 conn.setRequestProperty("Referer", "https://sirius.searates.com/cn/port?A=ChIJP1j2OhRahjURNsllbOuKc3Y&D=567&G=16959&shipment=1&container=20st&weight=1&product=0&request=&weightcargo=1&"); conn.setRequestProperty("X-Requested-With", "XMLHttpRequest");// 主要参数 } // 须要输出 conn.setDoInput(true); // 须要输入 conn.setDoOutput(true); // 设置是否使用缓存 conn.setUseCaches(false); // 设置请求属性 conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Connection", "Keep-Alive");// 维持长链接 conn.setRequestProperty("Charset", "UTF-8"); if(StringUtil.isNotBlank(param)) { // 创建输入流,向指向的URL传入参数 DataOutputStream dos=new DataOutputStream(conn.getOutputStream()); dos.writeBytes(param); dos.flush(); dos.close(); } // 输出返回结果 InputStream input = conn.getInputStream(); int resLen =0; byte[] res = new byte[1024]; StringBuilder sb=new StringBuilder(); while((resLen=input.read(res))!=-1){ sb.append(new String(res, 0, resLen)); } return sb.toString(); } catch (MalformedURLException e) { e.printStackTrace(); logger.info("港距查询抓取数据----抓取外网港距数据发生异常:" + e.getMessage()); } catch (IOException e) { e.printStackTrace(); logger.info("港距查询抓取数据----抓取外网港距数据发生异常:" + e.getMessage()); } logger.info("港距查询抓取数据----抓取外网港距数据失败, 返回空字符串"); return ""; }
七、XML 转换成 map 对象 代码示例:
/** * 解析xml,返回第一级元素键值对。若是第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws JDOMException * @throws IOException */ @SuppressWarnings("rawtypes") private Map<String,String> doXMLParse(String strxml) throws Exception { if(null == strxml || "".equals(strxml)) { return null; } Map<String,String> m = new HashMap<String,String>(); InputStream in = String2Inputstream(strxml); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if(children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } private InputStream String2Inputstream(String str) { return new ByteArrayInputStream(str.getBytes()); } /** * 获取子结点的xml * @param children * @return String */ @SuppressWarnings("rawtypes") private static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if(!children.isEmpty()) { Iterator it = children.iterator(); while(it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if(!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); }
八、获取本机IP
/** * @Title: getIpAddress * @Description: 获取客户端真实IP地址 * @author yihj * @param @param request * @param @param response * @param @return 参数 * @return String 返回类型 * @throws */ public static String getIpAddress(HttpServletRequest request) { // 避免反向代理不能获取真实地址, 取X-Forwarded-For中第一个非unknown的有效IP字符串 String ip = request.getHeader("x-forwarded-for"); if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("WL-Proxy-Client-IP"); } if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getRemoteAddr(); } return ip; }
九、通知地址 回调服务器 支付结果(这个回调 若是不返回给微信服务器 是否成功回调标示 则会一直回调8次 一直到返回成功标示位置) 代码示例:
@Override public void payCallback(HttpServletRequest request,HttpServletResponse response) { logger.info("微信回调接口方法 start"); logger.info("微信回调接口 操做逻辑 start"); String inputLine = ""; String notityXml = ""; try { while((inputLine = request.getReader().readLine()) != null){ notityXml += inputLine; } //关闭流 request.getReader().close(); logger.info("微信回调内容信息:"+notityXml); //解析成Map Map<String,String> map = doXMLParse(notityXml); //判断 支付是否成功 if("SUCCESS".equals(map.get("result_code"))){ logger.info("微信回调返回是否支付成功:是"); //得到 返回的商户订单号 String outTradeNo = map.get("out_trade_no"); logger.info("微信回调返回商户订单号:"+outTradeNo); //访问DB WechatAppletGolfPayInfo payInfo = appletGolfPayInfoMapper.selectByPrimaryKey(outTradeNo); logger.info("微信回调 根据订单号查询订单状态:"+payInfo.getPayStatus()); if("0".equals(payInfo.getPayStatus())){ //修改支付状态 payInfo.setPayStatus("1"); //更新Bean int sqlRow = appletGolfPayInfoMapper.updateByPrimaryKey(payInfo); //判断 是否更新成功 if(sqlRow == 1){ logger.info("微信回调 订单号:"+outTradeNo +",修改状态成功"); //封装 返回值 StringBuffer buffer = new StringBuffer(); buffer.append("<xml>"); buffer.append("<return_code>SUCCESS</return_code>"); buffer.append("<return_msg>OK</return_msg>"); buffer.append("</xml>"); //给微信服务器返回 成功标示 不然会一直询问 我们服务器 是否回调成功 PrintWriter writer = response.getWriter(); //返回 writer.print(buffer.toString()); } } } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } }
十、注意事项:
1)、全部的签名和发送微信服务器的数据必须一致 包括Key的大小写 不然签名失败
2)、微信小程序 前端调用 接口的时候 文档上并无写appId参数 该参数必定要穿 而且是大写
3)、交易类型 为 JSAPI 的时候 则必须传入openid
4)、body格式问题 写的是UTF-8 实际要的格式则是ISO8859-1 并且单独对body进行设置好像很差使 因此必须所有都改为该格式
5)、生成签名 最后加上key的那块 加的格式是 &key = KEY 这种 并且不是直接 + key 这个地方须要注意一下 我碰了个坑 文档没看仔细
6)、数据包ID 格式 不是 value直接设置成 数据包ID就能够 前面须要加 "prepay_id="
7)、最后一点强调 生成签名的数据和发送服务器的数据 必须保持一致