微信授权以及微信支付所遇到的坑(完善)
一、最近又做了微信公众号支付,前一次做支付没有好好记录,这次又浪费了不少时间,故完整的记录下,下次就可以直接用了。
- 1、准备工作(微信公众号、微信商户号申请)
- 2、域名购买、域名备案(微信支付必须是备案的域名,测试环境支付测试不了)
- 测试环境能测试授权等功能,扫描关注可获得微信管方测试、app_id、app_secret 有了两个就可以了
二、准备工作续
第一步:开通微信公众号支付功能后,就可以获得app_id、app_secret,这个地方还需要设置一个ip白名单,代码所放置的服务器ip地址
第二步:设置回调域名,必须是备案的域名,域名去掉 http
第三步:设置调起微信支付h5页面的地址,在微信商户平台设置
以上工作准备好了,接下来开始上代码。
微信支付重点在签名的生成,这块错一点就很麻烦。
主代码:
package com.snp.app.controller; import com.alibaba.fastjson.JSONObject; import com.snp.app.domain.VO.PayVO; import com.snp.common.controller.BaseController; import com.snp.common.utils.ConfigUtil; import com.snp.common.utils.StringUtil; import com.snp.order.dao.FinancialClaimOrderDao; import com.snp.order.domain.FinancialClaimOrderDO; import com.snp.userManager.domain.UserWechatDO; import com.snp.userManager.service.UserWechatService; import com.snp.wechat.config.WechatConfig; import com.snp.wechat.model.bean.*; import com.snp.wechat.utils.AdvancedUtil; import com.snp.wechat.utils.PayUtils; import com.snp.wechat.utils.WeiXinOrderUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; /** * 支付控制器 */ @RestController @RequestMapping("/interface/pay") public class PayController extends BaseController{ private static Logger logger = LoggerFactory.getLogger(PayController.class); @Autowired private WeiXinOrderUtil weiXinOrderUtil; @Autowired private FinancialClaimOrderDao financialClaimOrderDao; @Autowired private WechatConfig wechatConfig; @Autowired private UserWechatService userWechatService; @ResponseBody @RequestMapping(value = "/createWechatOrder", method ={ RequestMethod.GET,RequestMethod.POST}) public JSONObject createWechatOrder(PayVO payVO, HttpServletRequest request, HttpServletResponse response) throws Exception{ payVO.setPayAmount("1");//1分钱 payVO.setRobbingOrderNo(PayUtils.getTransferNo16()); //ResultBody result = new ResultBody(); logger.info("saveFinancialClaimOrder{}支付金额:"+payVO.getPayAmount()); logger.info("saveFinancialClaimOrder{}订单号:"+payVO.getRobbingOrderNo()); UserWechatDO userWechatDO = userWechatService.getUserWechatByUserId(ConfigUtil.getUserId(request)); WechatOrder wechatOrder = new WechatOrder(); wechatOrder.setOpenid(userWechatDO.getWechatOpenid()); wechatOrder.setOrderName("押金"); //PayUtils.getTransferNo16() wechatOrder.setOut_trade_no(payVO.getRobbingOrderNo()); wechatOrder.setNonce_str(PayUtils.getNonceStr()); //wechatOrder.setTotal_fee(MoneyUtil.getOrderMoney(payVO.getPayAmount())); wechatOrder.setTotal_fee(Integer.parseInt(payVO.getPayAmount())); //请求微信下单 并返回参数 String resultOrderStr = weiXinOrderUtil.buildWechatOrderParam(wechatOrder,request); JSONObject returnJson=JSONObject.parseObject(resultOrderStr); //更新 openID prepay_id 到抢单表中 FinancialClaimOrderDO finaClaimDO = new FinancialClaimOrderDO(); finaClaimDO.setOpenId(userWechatDO.getWechatOpenid()); finaClaimDO.setPrepayId(returnJson.get("pg").toString()); //financialClaimOrderDao.update(finaClaimDO); logger.info("createWechatOrder{}支付结果:"+resultOrderStr); //result.setData(resultOrderStr); return returnJson; } //支付之前先去下单 获取用户openID @RequestMapping(value = "/getWechatOpenId", method = RequestMethod.GET) public void getWechatOpenId(PayVO payVO,HttpServletRequest request,HttpServletResponse response) throws IOException{ //静默授权 只能获得openid //获得微信公众号的唯一标识 String appID = wechatConfig.getWechatAppId(); String appSecret = wechatConfig.getWechatAppSecret(); // 用户同意授权后,能获取到code String code = request.getParameter("code"); WeixinOauth2Token weixinOauth2Token = AdvancedUtil.getOauth2AccessToken(appID, appSecret, code); String openId = null; String accessToken = null; if(null!=weixinOauth2Token){ openId = weixinOauth2Token.getOpenId(); accessToken = weixinOauth2Token.getAccessToken(); } UserWechatDO userWechatDO = new UserWechatDO(); userWechatDO.setUserId(ConfigUtil.getUserId(request)); userWechatDO.setWechatOpenid(openId); userWechatDO.setAccessToken(accessToken); userWechatService.save(userWechatDO); //获取回调域名 String url = wechatConfig.getRedirectUri(); url=url+"/index.html"; System.out.println(url); response.sendRedirect(url); } /** * 支付下单之前 先去判断是否需要静默授权获取openID * @param request * @param response * @throws IOException */ @RequestMapping(value = "/saveFinancialClaimOrder", method = {RequestMethod.GET,RequestMethod.POST}) public void saveFinancialClaimOrder(PayVO payVO,HttpServletRequest request,HttpServletResponse response) throws Exception{ //查询数据库 如果没有记录就是第一次登录,如果有数据判断token有没有失效 if(ConfigUtil.getUserId(request) == 0L){ throw new Exception("用户id为空"); } UserWechatDO userWechatDO = userWechatService.getUserWechatByUserId(ConfigUtil.getUserId(request)); int isLoginFirst = 0; if(StringUtil.isEmpty(userWechatDO)){//首次支付 静默登录 isLoginFirst = 1; }else{ //第二次登录 支付这里不用检查token失效 openid唯一的不会失效 isLoginFirst = 0; } //获取回调域名 String url = wechatConfig.getRedirectUri(); //获得微信公众号的唯一标识 String appID = wechatConfig.getWechatAppId(); //微信本地中转站 去获取token String reUrl = wechatConfig.getReUrl(); reUrl = reUrl+"?userId="+ConfigUtil.getUserId(request); //微信静默授权地址 String accreditUrlBase = wechatConfig.getAccreditUrlBase(); if(isLoginFirst == 1){ accreditUrlBase=accreditUrlBase.replace("Redirect_UI", reUrl).replace("appId", appID); response.sendRedirect(accreditUrlBase);//未授权的用户继续授权 } //token effective 支付页面 url=url+"/index.html"; System.out.println(url); response.sendRedirect(url); } /** * 提交支付后的微信异步返回接口 * @throws IOException */ @RequestMapping(value="/weixinNotify") public void weixinNotify(HttpServletRequest request, HttpServletResponse response) throws IOException{ String out_trade_no=null; String return_code =null; try { String resultNotify = weiXinOrderUtil.getOrderReturnStream(request); Map<String, String> resultMap = PayUtils.getH5PayMap(resultNotify,request); System.out.println("支付回调参数:"+resultMap); out_trade_no = resultMap.get("out_trade_no"); return_code = resultMap.get("return_code"); //通知微信.异步确认成功.必写.不然微信会一直通知后台.八次之后就认为交易失败了. response.getWriter().write(PayUtils.setXML("SUCCESS", "")); } catch (Exception e) { logger.error("微信回调接口出现错误:",e); try { response.getWriter().write(PayUtils.setXML("FAIL", "error")); } catch (IOException e1) { e1.printStackTrace(); } } FinancialClaimOrderDO fClaimDO = financialClaimOrderDao.getFinanClaimByOrderNo(out_trade_no); if(return_code.equals("SUCCESS")){ //支付成功的业务逻辑 fClaimDO.setStatus(1); }else{ //支付失败的业务逻辑 fClaimDO.setStatus(-1); } financialClaimOrderDao.update(fClaimDO); } }
页面:
<!DOCTYPE html> <html> <head> <script src="/js/jquery-1.4.4.min.js"></script> <script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <meta charset="UTF-8"> <title>微信安全支付</title> </head> <body> <script type="text/javascript"> window.onload=function() { var totalfees = window.location.search; totalfees=totalfees.substring(11,12); $.ajax({ type : "POST", url : '/interface/pay/createWechatOrder?userId=4', success : function(data) { var appId = data.appId; var timeStamp = data.timeStamp; var nonceStr = data.nonceStr; var packages = "prepay_id=" + data.pg; var signType = "MD5"; var paySign = data.paySign; if (data.result == "success") { pay(); function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId" : appId, //公众号名称,由商户传入 "timeStamp": timeStamp+"", //时间戳,自1970年以来的秒数 "nonceStr" : nonceStr, //随机串 "package" : packages, "signType" : signType, //微信签名方式: "paySign" : paySign //微信签名 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { alert("支付成功"); // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 //回到用户订单列表 // window.location.href="http://wx.ooklady.com/wechat/order/orderlist"; }else if (res.err_msg == "get_brand_wcpay_request:cancel") { alert("支付过程中用户取消"); }else{ //支付失败 alert(JSON.stringify(res)) } } ); } //唤起微信支付 function pay(){ 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(); } } } } }); } </script> </body> </html>
工具类:
package com.snp.wechat.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.snp.wechat.config.WechatConfig; import com.snp.wechat.model.bean.WechatOrder; import com.snp.wechat.model.bean.WeixinOauth2Token; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Date; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; /** * 微信公用工具类 */ @Component public class WeiXinOrderUtil { @Autowired private WechatConfig wechatConfig; /** * 组装统一下单参数 * @return */ public String buildWechatOrderParam(WechatOrder wechatOrder,HttpServletRequest request) throws Exception{ String payKey = wechatConfig.getPaykey(); String signType = wechatConfig.getSignType(); SortedMap<String, String> signMap = new TreeMap<String, String>(); long timeStamp = PayUtils.getUnixTime(new Date()); wechatOrder.setAppid(wechatConfig.getWechatAppId()); wechatOrder.setTrade_type(wechatConfig.getTradeType()); //构建参数返回前台 请求支付接口 String prepayId = createWechatOrder(wechatOrder,request); signMap.put("appId",wechatOrder.getAppid()); signMap.put("timeStamp", timeStamp+""); signMap.put("nonceStr", wechatOrder.getNonce_str()); signMap.put("package", "prepay_id="+prepayId); signMap.put("signType", signType); String paySign = PayUtils.getSign(signMap, payKey); signMap.put("pg", prepayId); signMap.put("paySign", paySign); signMap.put("result", "success"); String json = JSON.toJSONString(signMap); JSONObject returnJson=JSONObject.parseObject(json); return returnJson.toJSONString(); } /** * 创建下单签名 包含商品信息 * @return */ public String createWechatSign(WechatOrder wechatOrder){ String mch_id = wechatConfig.getMchId(); String notify_url = wechatConfig.getNotifyUrl(); String device_info = wechatConfig.getDeviceInfo(); String payKey = wechatConfig.getPaykey(); //将商品信息打包 SortedMap<String, String> parameters = new TreeMap<String, String>(); parameters.put("appid", wechatOrder.getAppid());//公众号id 这地方一定要小写并跟下面xml文件对应都是小写 parameters.put("mch_id",mch_id);//商户ID parameters.put("device_info", device_info); parameters.put("body", wechatOrder.getOrderName());//名称 parameters.put("trade_type", wechatOrder.getTrade_type()); parameters.put("nonce_str", wechatOrder.getNonce_str());//随机数 parameters.put("notify_url", notify_url); parameters.put("out_trade_no", wechatOrder.getOut_trade_no()); parameters.put("total_fee", wechatOrder.getTotal_fee()+""); // parameters.put("spbill_create_ip", spbill_create_ip ); parameters.put("openid", wechatOrder.getOpenid()); //根据上述的数据生成预支付订单号的前面sign return PayUtils.getSign(parameters, payKey); } /** * 创建微信订单 请求微信接口下单 * @return */ public String createWechatOrder(WechatOrder wechatOrder,HttpServletRequest request) throws Exception{ String mch_id = wechatConfig.getMchId(); String notify_url = wechatConfig.getNotifyUrl(); String device_info = wechatConfig.getDeviceInfo(); String createOrderURL = wechatConfig.getCreateOrderUrl(); //生成统一支付接口数据 String xml = "<xml>"+ "<appid>"+wechatOrder.getAppid()+"</appid>"+ "<body>"+wechatOrder.getOrderName()+"</body>"+ "<device_info>"+device_info+"</device_info>"+ "<mch_id>"+mch_id+"</mch_id>"+ "<nonce_str>"+wechatOrder.getNonce_str()+"</nonce_str>"+ "<notify_url>"+notify_url+"</notify_url>"+ "<openid>"+wechatOrder.getOpenid()+"</openid>"+ "<out_trade_no>"+wechatOrder.getOut_trade_no()+"</out_trade_no>"+ "<total_fee>"+wechatOrder.getTotal_fee()+"</total_fee>"+ "<trade_type>"+wechatOrder.getTrade_type()+"</trade_type>"+ "<sign>"+createWechatSign(wechatOrder)+"</sign>"+ "</xml>"; //调用统一支付接口 String result = PayUtils.httpsRequest(createOrderURL, "POST", xml); System.out.println("-----------------------------统一下单结果---------------------------"); System.out.println(result); Map<String, String> resultMap = null; resultMap=PayUtils.getH5PayMap(result,request); return resultMap.get("prepay_id"); //预支付ID,保存到数据库中 } /** * 解析微信支付回调的结果 * @return */ public static String getOrderReturnStream(HttpServletRequest request) throws IOException { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); return new String(outSteam.toByteArray(),"utf-8"); } /** * 获取 WeixinOauth2Token * @return */ public WeixinOauth2Token getWeixinOauth2Token(String code){ //获得微信公众号的唯一标识 String appId = wechatConfig.getWechatAppId(); String appSecret = wechatConfig.getWechatAppSecret(); return AdvancedUtil.getOauth2AccessToken(appId, appSecret, code); } }
package com.snp.wechat.utils; import com.snp.common.utils.StringUtil; import com.snp.userManager.domain.UserWechatDO; import com.snp.wechat.model.bean.WeixinOauth2Token; import org.apache.commons.lang3.StringUtils; import com.alibaba.fastjson.JSONObject; import java.util.Date; /** * @author yuwei * 工具类 * 2016年12月21日 下午1:58:38 */ public class AdvancedUtil { public AdvancedUtil() { super(); // TODO Auto-generated constructor stub } /** * 获取网页授权凭证 * * @param appId 公众账号的唯一标识 * @param appSecret 公众账号的密钥 * @param code * @return * @return WeixinAouth2Token */ public static WeixinOauth2Token getOauth2AccessToken(String appId, String appSecret, String code) { WeixinOauth2Token wat = null; // 拼接请求地址 String requestUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; requestUrl = requestUrl.replace("APPID", appId); requestUrl = requestUrl.replace("SECRET", appSecret); requestUrl = requestUrl.replace("CODE", code); // 获取网页授权凭证 JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null); if (null != jsonObject) { try { wat = new WeixinOauth2Token(); wat.setAccessToken(jsonObject.getString("access_token")); wat.setExpiresIn(jsonObject.getInteger("expires_in")); wat.setRefreshToken(jsonObject.getString("refresh_token")); wat.setOpenId(jsonObject.getString("openid")); wat.setScope(jsonObject.getString("scope")); } catch (Exception e) { wat = null; int errorCode = jsonObject.getInteger("errcode"); String errorMsg = jsonObject.getString("errmsg"); System.out.println(errorCode); System.out.println(errorMsg); // log.error("获取网页授权凭证失败 errcode:{} errmsg:{}", errorCode, errorMsg); } } return wat; } /** * 通过网页授权获取用户信息 * * @param accessToken 网页授权接口调用凭证 * @param openId 用户标识 * @return SNSUserInfo */ public static UserWechatDO getWechatInfo(String accessToken, String openId,String refreshToken) { UserWechatDO userWechatDO = null; // 拼接请求地址 String requestUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID"; requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId); // 通过网页授权获取用户信息 JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null); if (!StringUtil.isEmpty(jsonObject)) { try { userWechatDO = new UserWechatDO(); // 用户的标识 userWechatDO.setWechatOpenid(jsonObject.getString("openid")); // 昵称 String nickname = jsonObject.getString("nickname"); nickname = filterEmoji(nickname); userWechatDO.setNickName(nickname); // 性别(1是男性,2是女性,0是未知) userWechatDO.setSex(jsonObject.getInteger("sex")); // 用户所在国家 userWechatDO.setCountry(jsonObject.getString("country")); // 用户所在省份 userWechatDO.setProvince(jsonObject.getString("province")); // 用户所在城市 userWechatDO.setCity(jsonObject.getString("city")); // 用户头像 userWechatDO.setHeadImgUrl(jsonObject.getString("headimgurl")); //UnicodeID userWechatDO.setWechatUnionid(jsonObject.getString("unionid")); //userWechatDO.setWin(0); //userWechatDO.setLose(0); //查询时间 // Date date = new Date(); // player.setJoinTime(date); //首次授权时间 userWechatDO.setCreateTime(new Date()); //更新时间 userWechatDO.setUpdateTime(new Date()); userWechatDO.setLasttIme(new Date()); //凭证保存 userWechatDO.setAccessToken(accessToken); //刷新凭证 userWechatDO.setRefreshToken(refreshToken); // 用户特权信息 // snsUserInfo.setPrivilegeList(JSONArray.parseObject(jsonObject.getJSONArray("privilege"), List.class)); } catch (Exception e) { userWechatDO = null; int errorCode = jsonObject.getInteger("errcode"); String errorMsg = jsonObject.getString("errmsg"); System.out.println(errorCode); System.out.println(errorMsg); // log.error("获取用户信息失败 errcode:{} errmsg:{}", errorCode, errorMsg); } } return userWechatDO; } //检验凭证是否失效 @SuppressWarnings("unused") public static boolean judgeToken(String accessToken, String openId){ // 拼接请求地址 String requestUrl = "https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID"; requestUrl = requestUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId); // 通过网页授权获取用户信息 JSONObject jsonObject = CommonUtil.httpsRequest(requestUrl, "GET", null); int errorCode = jsonObject.getInteger("errcode"); String errorMsg = jsonObject.getString("errmsg");//正确返回OK errorMsg = errorMsg.toUpperCase(); if(errorMsg.equals("OK")){ return true; } return false; } //去掉ios特殊字符 public static String filterEmoji(String source) { if (StringUtils.isBlank(source)) { return source; } StringBuilder buf = null; int len = source.length(); for (int i = 0; i < len; i++) { char codePoint = source.charAt(i); if (isNotEmojiCharacter(codePoint)) { if (buf == null) { buf = new StringBuilder(source.length()); } buf.append(codePoint); } } if (buf == null) { return source; } else { if (buf.length() == len) { buf = null; return source; } else { return buf.toString(); } } } //判断特殊字符串 private static boolean isNotEmojiCharacter(char codePoint) { return (codePoint == 0x0) || (codePoint == 0x9) || (codePoint == 0xA) || (codePoint == 0xD) || ((codePoint >= 0x20) && (codePoint <= 0xD7FF)) || ((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) || ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF)); } }
以上就是主要代码。代码写的还不够好,不喜勿喷