• 微信商品二维码支付流程


    总的流程

    支付流程选型

    微信支持哪几种支付方式

    配置ip白名单

    登录微信公众平台–>开发>基本配置>ip白名单

    配置支付目录

    支付授权目录说明:
    1、商户最后请求拉起微信支付收银台的页面地址我们称之为“支付目录”,例如:https://www.weixin.com/pay.php。
    2、商户实际的支付目录必须和在微信支付商户平台设置的一致,否则会报错“当前页面的URL未注册:”

    支付授权目录设置说明:
    登录微信支付商户平台(pay.weixin.qq.com)–>产品中心–>开发配置,设置后一般5分钟内生效。

    支付授权目录校验规则说明:
    1、如果支付授权目录设置为顶级域名(例如:https://www.weixin.com/ ),那么只校验顶级域名,不校验后缀;
    2、如果支付授权目录设置为多级目录,就会进行全匹配,例如设置支付授权目录为https://www.weixin.com/abc/123/,则实际请求页面目录不能为https://www.weixin.com/abc/,也不能为https://www.weixin.com/abc/123/pay/,必须为https://www.weixin.com/abc/123/

    access_token

    微信开发,都得先了解微信的access_token获取及过期机制,获取access_token之后存到本地数据库,然后不要频繁去调用微信接口获取access_token,每天调用获取access_token接口的次数微信有限制,只需要判断过期时再去获取access_token。

    获取access_token接口地址:

    https://api.weixin.qq.com/cgi-bin/token?&grant_type=client_credential&appid=" + appId + "&secret="+ secret
    
    • 1

    更新数据库access_token和过期时间:

    wc.setAccessToken(json.getString("access_token"));
    Date expiresTime = new Date(new Date().getTime() + json.getIntValue("expires_in")*1000);
    wc.setExpiresTime(expiresTime);
    wxAccessConfMapper.updateWxAccessConf(wc);
    
    • 1
    • 2
    • 3
    • 4

    判断过期时调用获取access_token接口:

    if(wc.getExpiresTime() == null || StringUtils.isBlank(wc.getAccessToken()) || new Date().getTime() > wc.getExpiresTime().getTime()){
    	//调用获取access_token接口,更新数据库access_token和过期时间
    }
    
    • 1
    • 2
    • 3

    ticket

    微信开发,还得了解微信的jsapi的ticket获取及过期时间,和access_token类似但作用于微信前端页面的jsapi调用,access_token是作用于后端接口。

    获取ticket接口地址:

    https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken+ "&type=jsapi
    
    • 1

    更新数据库ticket和过期时间:

    wc.setJsapiTicket(json.getString("ticket"));
    Date expiresTime = new Date(new Date().getTime() + json.getIntValue("expires_in")*1000);
    wc.setJsapExpiresTime(expiresTime);
    wxAccessConfMapper.updateWxAccessConf(wc);
    
    • 1
    • 2
    • 3
    • 4

    判断过期时调用获取ticket接口:

    if (wc.getJsapExpiresTime() == null || StringUtils.isBlank(wc.getJsapiTicket()) || new Date().getTime() > wc.getJsapExpiresTime().getTime()) {
    	//调用获取ticket接口,更新数据库ticket和过期时间
    }
    
    • 1
    • 2
    • 3

    生成我们自己网站、自己商品的链接二维码(与微信无关)

    这个链接建议是一个接口地址,不能直接是html这种静态页面,因为我们需要在这个接口里获取微信openid等微信用户基本数据。
    示例二维码链接:

    http://www.xuexibisai.com/weixin/enter.do?productId=ss1000223000
    
    • 1

    二维码链接对应的接口

    	// 用户通过微信内置浏览器访问此接口
    	// 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息(目前我们只需要openid),进而实现业务逻辑。
    	@RequestMapping(value = "/enter.do", method = RequestMethod.GET)
    	public void enter(HttpServletRequest request, HttpServletResponse response, String productId) {
    		try {
    			WxAccessConf wc= weixinService.getSelfWxAccessConfAndUp();//获取本地数据库的微信基本数据,并判断access_token和ticket是否过期,如果过期就更新access_token和ticket
    			String url = "http://www.xuexibisai.com" + request.getContextPath() + "/weixin/pay.view?productId=" + productId;
    			// scope参数只有两种应用授权作用域,snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注的情况下,只要用户授权,也能获取其信息 )
    			// state参数是开发者自定义的参数,重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节
    			response.sendRedirect("https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + wc.getAppId() + "&redirect_uri=" + URLEncoder.encode(url, "utf-8")
    					+ "&response_type=code&scope=snsapi_base&state=1#wechat_redirect");
    			// 如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    根据code获取openid对应的接口(即上面接口跳转的最终链接对应的接口):

    // 经过微信重定向后,会返回code,然后根据code获取openid,并将openid返回给页面
    	@RequestMapping(value = "/pay.view", method = RequestMethod.GET)
    	public ModelAndView payView(HttpServletRequest request, HttpServletResponse response, String code, String productId) throws IOException {
    		WxAccessConf wxAccessConf = weixinService.getSelfWxAccessConf();
    		// 通过code拿openid
    		String openId = weiXinAuthManager.getOpenId(wxAccessConf, code);
    		// 通过openid查用户信息
    		JSONObject jsonObject = weiXinAuthManager.getUserInfo(wxAccessConf, openId);
    		LOG.debug(logPrefix + "最新的微信用户信息:" + jsonObject.toJSONString());
    		// 查数据库用户信息
    		WxUserInfo wxUserInfo = wxUserInfoMapper.selectByOpenId(openId);
    		if (!Utils.isEmpty(jsonObject, "openid")) {
    			if (wxUserInfo == null) {// 如果数据库没有就插入用户信息
    				wxUserInfo = new WxUserInfo();
    				wxUserInfo.setAppId(wxAccessConf.getAppId());
    				wxUserInfo.setOpenid(openId);
    				wxUserInfo.setCreateTime(new Date());
    			}
    			wxUserInfo.setNickname(jsonObject.getString("nickname"));
    			wxUserInfo.setHeadimgurl(jsonObject.getString("headimgurl"));
    			wxUserInfo.setSex(jsonObject.getInteger("sex"));
    			wxUserInfo.setCity(jsonObject.getString("city"));
    			wxUserInfo.setProvince(jsonObject.getString("province"));
    			wxUserInfo.setCountry(jsonObject.getString("country"));
    			wxUserInfo.setSubscribe(jsonObject.getInteger("subscribe"));
    			wxUserInfo.setSubscribeTime(jsonObject.getLong("subscribe_time"));
    			wxUserInfo.setGroupid(jsonObject.getInteger("groupid"));
    			wxUserInfo.setRemark(jsonObject.getString("remark"));
    			if (!Utils.isEmpty(jsonObject.getString("unionid"))) {
    				wxUserInfo.setUnionid(jsonObject.getString("unionid"));
    			}
    			wxUserInfo.setUpdateTime(new Date());
    			if (Utils.isEmpty(wxUserInfo.getUserId())) {
    				wxUserInfoMapper.insertSelective(wxUserInfo);
    			} else {
    				wxUserInfoMapper.updateByPrimaryKeySelective(wxUserInfo);
    			}
    		}
    		Product product = productMapper.selectByPrimaryKey(Long.parseLong(productId));
    		if (info == null) {
    			return WebUtil.getErrorPage("查询支付信息失败", LOG, logPrefix);
    		}
    		Map<String, Object> model = new HashMap<>(2);
    		model.put("openId", openId);
    		model.put("product", product);
    		return new ModelAndView("pay", model);// 跳到支付页面
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    支付页面

    <html>
    展示商品信息
    <input id="productId" name="productId" value="${(product.productId)!''}" type="hidden"/>
    <input id="openId" name="openId" value="${openId!''}" type="hidden"/>
    <input type="button" id="payBtn" value="支付" onclick="pay(this)"/>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    支付页面的初始化wx.config

    微信要求调用微信jsapi前需调用wx.config,声明你要调用哪些js接口。

    <html>
    <script type="text/javascript">
    $.ajax({
    	url : '${ctx}/weixin/getJsConfig.ajax',
    	async : false,
    	data : {
    		'url' : window.location.href
    	},
    	dataType : 'json',
    	type : 'post',
    	success : function(result) {
    		var data = result.data;
    		if (data.appid == null || data.appid == "") {
    			return;
    		}
    		wx.config({
    			debug : false,
    			appId : data.appid,
    			timestamp : data.timestamp,
    			nonceStr : data.nonceStr,
    			signature : data.signature,
    			jsApiList : [ "chooseWXPay" ]
    		});
    	}
    });
    wx.error(function(res) {
    	alert("页面鉴权失败:"+res);
    });
    wx.ready(function() {
    });
    script>
    展示商品信息
    <input id="productId" name="productId" value="${(product.productId)!''}" type="hidden"/>
    <input id="openId" name="openId" value="${openId!''}" type="hidden"/>
    <input type="button" id="payBtn" value="支付" onclick="pay(this)"/>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    支付页面的初始化wx.config之前你得在后端提供接口,给wx.config准备参数

    	private static String byteToHex(final byte[] hash) {
    		Formatter formatter = new Formatter();
    		for (byte b : hash) {
    			formatter.format("%02x", b);
    		}
    		String result = formatter.toString();
    		formatter.close();
    		return result;
    	}
    	@RequestMapping(value = "/getJsConfig.ajax")
    	@ResponseBody
    	public Map<String, Object> getJsConfig(HttpServletRequest request, HttpServletResponse response, String url) {
    		WxAccessConf wxAccessConf = weixinService.getSelfWxAccessConf();
    		if (StringUtils.isBlank(url)) {
    			LOG.info("url传递失败");
    			return fail("传递失败");
    		}
    		Map<String, String> ret = new HashMap<String, String>();
    		String nonceStr =UUID.randomUUID().toString();
    		String timestamp = Long.toString(System.currentTimeMillis() / 1000);
    		String string1;
    		String signature = "";
    		// 注意这里参数名必须全部小写,且必须有序
    		string1 = "jsapi_ticket=" + wxAccessConf.getJsapiTicket() + "&noncestr=" + nonceStr + "×tamp=" + timestamp + "&url=" + url;
    		try {
    			MessageDigest crypt = MessageDigest.getInstance("SHA-1");
    			crypt.reset();
    			crypt.update(string1.getBytes("UTF-8"));
    			signature = byteToHex(crypt.digest());
    		} catch (NoSuchAlgorithmException e) {
    			e.printStackTrace();
    		} catch (UnsupportedEncodingException e) {
    			e.printStackTrace();
    		}
    
    		ret.put("url", url);
    		ret.put("nonceStr", nonceStr);
    		ret.put("timestamp", timestamp);
    		ret.put("signature", signature);
    		LOG.debug("ret:=" + ret);
    		ret.put("appid", wxAccessConf.getAppId());
    		return success(ret);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    页面支付按钮对应的方法,微信jsapi的wx.chooseWXPay

    	var flag = false;
    	function pay(btn) {
    		if (flag == true) {
    			alert("您已经支付过了,请不要重复支付哦!");
    			return;
    		}
    		$.ajax({
    			url : '${ctx}/weixin/pay.ajax',//后端提供wx.chooseWXPay的参数
    			type : 'post',
    			'data' : {
    				"productId" : $("#productId").val(),
    				"openId" : $("#openId").val()
    			},
    			dataType : 'json',
    			aysnc : false,
    			success : function(res) {
    				if (res && res.code) {
    					if(res.code == "200"){
    						var data = res.data;
    						wx.chooseWXPay({
    						    'debug' : true,
    	                        'timestamp': data.timeStamp,
    	                        'nonceStr': data.nonceStr,
    	                        'package': 'prepay_id=' + data.prepayId, 
    	                        'signType': 'MD5',
    	                        'paySign': data.sign,
    							success : function(res) {
    	                            flag = true;
    								alert("支付成功");
    								$(btn).prop("disabled", "disabled");
    							},
    							fail:function(err) {
    								alert("系统错误:"+err);
    							}
    						});
    					}else{
    						alert("系统错误:"+res.msg);
    					}
    				} else {
    					alert("系统错误:"+res);
    				}
    			}
    		});
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    wx.chooseWXPay详解:

    wx.chooseWXPay({
        timeStamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
        nonceStr: '', // 支付签名随机串,不长于 32 位
        package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
        signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
        paySign: '', // 支付签名
        success: function (res) {
            // 支付成功后的回调函数
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    wx.chooseWXPay有一个替代品:

    WeixinJSBridge.invoke(
           'getBrandWCPayRequest', {
               "appId" : "wx2421b1c4370ec43b",     //公众号名称,由商户传入     
               "timeStamp":" 1395712654",         //时间戳,自1970年以来的秒数     
               "nonceStr" : "e61463f8efa94090b1f366cccfbbb444", //随机串     
               "package" : "prepay_id=u802345jgfjsdfgsdg888",     
               "signType" : "MD5",         //微信签名方式:     
               "paySign" : "70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名 
           },
           function(res){     
               if(res.err_msg == "get_brand_wcpay_request:ok" ) {}     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。 
           }
       ); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    调用wx.chooseWXPay之前,你得调用统一下单接口,并给wx.chooseWXPay准备参数

    
    public  String getSign(Map<String, String> map, String apiKey) {
    		StringBuffer sb = new StringBuffer();
    		Set es = map.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=" + apiKey);
    		String sign = MD5Util.MD5(sb.toString(),"UTF-8").toUpperCase();
    		return sign;
    	}
    
    /**
    	 * 返回微信支付前端参数对象
    	 */
    	public WxResp getPayConfig(WeixinOrderDTO weixinOrder, WxAccessConf accessConf, WeixinResponseDTO weixinResponse) {
    		// 返回对象值
    		LOG.debug("进入返回界面的wxRsp");
    		WxResp wxResp = new WxResp();
    		wxResp.setAppId(weixinResponse.getAppid());
    		wxResp.setPartnerId(weixinResponse.getMch_id());
    		wxResp.setNonceStr(weixinOrder.getNonceStr());
    		wxResp.setPrepayId(weixinResponse.getPrepay_id());
    		wxResp.setPackageValue("Sign=WXPay");
    		String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
    		wxResp.setTimeStamp(timeStamp);
    		Map<String, String> map = new TreeMap<String, String>();
    		map.put("appId", weixinResponse.getAppid());
    		map.put("timeStamp", timeStamp);
    		map.put("nonceStr", weixinOrder.getNonceStr());
    		map.put("package", "prepay_id=" + weixinResponse.getPrepay_id());
    		map.put("signType", "MD5");
    		StringBuffer sb = new StringBuffer();
    	
    		String sign = getSign(map,accessConf.getApiKey());
    		wxResp.setSign(sign);
    		LOG.debug("进入返回界面的wxRsp下面" + wxResp.toString());
    		return wxResp;
    	}
    
    	// 下单,生成prepay_id给前端H5,然后通过jsapi调用wx.chooseWXPay
    	@RequestMapping(value = "/wxPayMonthBalancePay.ajax", method = RequestMethod.POST)
    	@ResponseBody
    	public Object wxPayMonthBalancePay(HttpServletRequest request, HttpServletResponse response, String openId, Long infoId) throws IOException {
    		String logPrefix = "月账单结算支付>>>";
    		LOG.debug(logPrefix + "参数,openId=" + openId + ",infoId=" + infoId);
    		if (Utils.isEmpty(openId)) {
    			return JsonResult.getFailResultAndLog("openId为空", LOG, logPrefix);
    		}
    		WxUserInfo wxUser = wxUserInfoMapper.selectByOpenId(openId);
    		if (wxUser == null) {
    			return JsonResult.getFailResultAndLog("微信用户未录入", LOG, logPrefix);
    		}
    
    		if (Utils.isEmpty(infoId)) {
    			return JsonResult.getFailResultAndLog("infoId为空", LOG, logPrefix);
    		}
    		WxPayMonthBalanceInfo info = wxPayMonthBalanceInfoMapper.selectByPrimaryKey(infoId);
    		if (info == null) {
    			return JsonResult.getFailResultAndLog("未找到支付信息", LOG, logPrefix);
    		}
    		WxAccessConf wxAccessConf = weixinService.getSelfWxAccessConf();
    
    		TempBean tempBean = weixinService.wxPayMonthBalancePay(request, info, openId);
    		if (tempBean.weixinResponse == null) {
    			return JsonResult.getFailResultAndLog("发起支付失败", LOG, logPrefix);
    		}
    //		Map config = weixinService.getPayConfig(wxAccessConf, tempBean.weixinResponse.getPrepay_id());
    //		LOG.debug(logPrefix + "前端需要的支付参数config=" + config);
    		WxResp wxResp = getPayConfig(tempBean.weixinOrder, wxAccessConf, tempBean.weixinResponse);
    		return JsonResult.getSuccessResultByData(wxResp);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    统一下单

    
    public static class TempBean {
    		public WeixinOrderDTO weixinOrder;
    		public WeixinResponseDTO weixinResponse;
    	}
    
    /**
    	 * 微信支付下单
    	 */
    	public String sendOrder(WeixinOrderDTO weixinOrder, String key) {
    		String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    		Map<String, String> map = new TreeMap<String, String>();
    		StringBuffer sb = new StringBuffer();
    		sb.append("");
    		sb.append(" + weixinOrder.getAppId() + "]]>");
    		if (StringUtils.isNotBlank(weixinOrder.getBody())) {
    			sb.append(" + weixinOrder.getBody() + "]]>");
    			map.put("body", weixinOrder.getBody());
    		}
    		sb.append(" + weixinOrder.getMachId() + "]]>");
    		sb.append(" + weixinOrder.getNonceStr() + "]]>");
    		sb.append(" + weixinOrder.getNotifyUrl() + "]]>");
    		if(!StringUtils.isEmpty(weixinOrder.getOpenId())){
    			sb.append(" + weixinOrder.getOpenId() + "]]>");	
    		}
    		sb.append(" + weixinOrder.getOutTradeNo() + "]]>");
    		sb.append(" + weixinOrder.getSpbillCreateIp() + "]]>");
    		sb.append(" + weixinOrder.getTotalFee() + "]]>");
    		sb.append(" + weixinOrder.getTradeType() + "]]>");
    
    		map.put("appid", weixinOrder.getAppId());
    		map.put("mch_id", weixinOrder.getMachId());
    		map.put("nonce_str", weixinOrder.getNonceStr());
    		map.put("notify_url", weixinOrder.getNotifyUrl());
    		if(!StringUtils.isEmpty(weixinOrder.getOpenId())){
    			map.put("openid", weixinOrder.getOpenId());
    		}
    		map.put("out_trade_no", weixinOrder.getOutTradeNo());
    		map.put("spbill_create_ip", weixinOrder.getSpbillCreateIp() + "");
    		map.put("total_fee", weixinOrder.getTotalFee() + "");
    		map.put("trade_type", weixinOrder.getTradeType());
    		String sign = getSign(map, key);
    		sb.append(" + sign + "]]>");
    		sb.append("");
    		String str = null;
    		log.debug("url:" + url + "xml:" + sb.toString());
    		try {
    			str = HttpClientUtil.postByBody(url, sb.toString(), "text/xml;charset=utf-8");
    		} catch (HttpException e) {
    			log.error(e.getMessage(), e);
    			e.printStackTrace();
    		} catch (IOException e) {
    			log.error(e.getMessage(), e);
    			e.printStackTrace();
    		}
    		return str;
    	}
    
    /**
    	 * 下单返回结果是否合法
    	 * 
    	 * @param weixinResponse
    	 * @param apiKey
    	 * @return
    	 */
    	public boolean validateResponseSign(WeixinResponseDTO weixinResponse, String apiKey) {
    		Map<String, String> map = new TreeMap<String, String>();
    		if (StringUtils.isNotBlank(weixinResponse.getAppid())) {
    			map.put("appid", weixinResponse.getAppid());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getMch_id())) {
    			map.put("mch_id", weixinResponse.getMch_id());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getNonce_str())) {
    			map.put("nonce_str", weixinResponse.getNonce_str());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getPrepay_id())) {
    			map.put("prepay_id", weixinResponse.getPrepay_id());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getResult_code())) {
    			map.put("result_code", weixinResponse.getResult_code());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getReturn_code())) {
    			map.put("return_code", weixinResponse.getReturn_code());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getTrade_type())) {
    			map.put("trade_type", weixinResponse.getTrade_type());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getReturn_msg())) {
    			map.put("return_msg", weixinResponse.getReturn_msg());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getCode_url())) {
    			map.put("code_url", weixinResponse.getCode_url());
    		}
    		if (StringUtils.isNotBlank(weixinResponse.getMweb_url())) {
    			map.put("mweb_url", weixinResponse.getMweb_url());
    		}
    		if (StringUtils.isBlank(weixinResponse.getSign())) {
    			return false;
    		}
    		String sign = getSign(map, apiKey);
    		log.debug("-----------对比后的sign1111" + sign);
    		return sign.equals(weixinResponse.getSign());
    	}
    
    /**
    	 * 账单结算微信二维码支付-微信统一下单
    	 * @param partnerId,合作伙伴id,整数,必填
    	 * @param month 结算月份,yyyy-mm,必填
    	 * @param payMoney 交易金额,单位元,必填
    	 * @param remark 备注,非必填
    	 * @return 二维码图片base64数据
    	 * @author tangzhichao 20200922 新增
    	 */
    	public TempBean pay(HttpServletRequest request, WxPayMonthBalanceInfo info, String openId) {
    		TempBean tempBean = new TempBean();
    		WxAccessConf wxConfInfo = getSelfWxAccessConf();
    		String outTradeNo = WxPayMonthBalanceOrder.genOutTradeNo(info.getType());
    		LOG.debug("微信统一下单>>微信支付流水单号:" + outTradeNo + ",InfoId:" + info.getInfoId());
    		WxPayMonthBalanceOrder o = insertWxPayMonthBalanceOrder(request, wxConfInfo, info, openId, outTradeNo);
    
    		WeixinOrderDTO order = new WeixinOrderDTO();
    		order.setAppId(o.getAppId());
    		order.setBody(getOrderName(info));
    		order.setOutTradeNo(o.getOutTradeNo());
    		// 商户号 必填
    		order.setMachId(wxConfInfo.getWxMerchantNo());
    		order.setNonceStr(UUID.randomUUID().toString().replaceAll("-", ""));
    		String notifyUrl = wxConfInfo.getAuthDomain() + "/fmp-app/weixin/balancePayNotify.ws";
    		order.setNotifyUrl(notifyUrl);
    		order.setOpenId(o.getOpenId());
    		order.setTotalFee(new BigDecimal(o.getTotalFee()).intValue());
    		order.setTradeType(o.getTradeType());
    		order.setSpbillCreateIp(o.getSpbillCreateIp());
    		tempBean.weixinOrder = order;
    		String str = sendOrder(order, wxConfInfo.getApiKey());
    		LOG.debug("微信统一下单>>微信返回数据:" + str);
    		if (StringUtils.isBlank(str)) {
    			LOG.error("微信统一下单>>微信统一下单失败,微信未返回数据");
    			return tempBean;
    		}
    		XStream xs = new XStream(new DomDriver());
    		xs.alias("xml", WeixinResponseDTO.class);
    		WeixinResponseDTO weixinResponse = (WeixinResponseDTO) xs.fromXML(str);
    		if (weixinResponse == null) {
    			LOG.error("微信统一下单>>解析微信返回数据失败");
    			return tempBean;
    		}
    		if (StringUtils.isBlank(weixinResponse.getResult_code()) || StringUtils.isBlank(weixinResponse.getReturn_code()) || !weixinResponse.getResult_code().equals("SUCCESS")
    				|| !weixinResponse.getReturn_code().equals("SUCCESS")) {
    			LOG.error("微信统一下单>>微信返回数据return_code为失败");
    			return tempBean;
    		}
    		boolean flag = validateResponseSign(weixinResponse, wxConfInfo.getApiKey());
    		if (!flag) {
    			LOG.error("微信统一下单>>微信返回数据apikey校验失败");
    			return tempBean;
    		}
    		String prepay_id = weixinResponse.getPrepay_id();
    		LOG.debug("微信统一下单>>prepay_id:" + prepay_id);
    		if (!Utils.isEmptyTrim(prepay_id)) {
    			tempBean.weixinResponse = weixinResponse;
    			updateWxPayOrderStatus(o.getOrderId(), WxPayMonthBalanceOrder.status_2);
    		}
    		return tempBean;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166

    回调接口

    /**
    	 * 账单结算微信支付-支付回调
    	 * @param body 微信传递的消息体
    	 * @author tangzhichao 20200922 新增
    	 */
    	@RequestMapping(value = "/balancePayNotify.ws")
    	public void balancePayNotify(HttpServletRequest request, HttpServletResponse resp) throws IOException {
    		LOG.debug("账单结算微信二维码支付---支付回调...");
    		try {
    			String param = WebUtil.getParams(request);
    			if (StringUtils.isBlank(param)) {
    				LOG.error("支付回调>>>回调信息为空");
    				failByCallback(resp);
    				return;
    			}
    			XStream xs = new XStream(new DomDriver());
    			xs.alias("xml", WeChatBuyPostDTO.class);
    			WeChatBuyPostDTO weChatBuyPost = (WeChatBuyPostDTO) xs.fromXML(param);
    			if (weChatBuyPost == null) {
    				LOG.error("支付回调>>>回调信息解析失败");
    				failByCallback(resp);
    				return;
    			}
    			String outTradeNo = weChatBuyPost.getOut_trade_no();
    			if (Utils.isEmptyTrim(outTradeNo)) {
    				LOG.error("支付回调>>>回调信息out_trade_no为空");
    				failByCallback(resp);
    				return;
    			}
    			LOG.debug("支付回调>>>订单Id:{}", outTradeNo);
    			WxPayMonthBalanceOrder wxPayMonthBalanceOrder = wxPayMonthBalanceOrderMapper.selectByOutTradeNo(outTradeNo);
    			if (wxPayMonthBalanceOrder == null) {
    				LOG.error("支付回调>>>未找到订单号" + outTradeNo);
    				failByCallback(resp);
    				return;
    			}
    			if (StringUtils.isBlank(weChatBuyPost.getResult_code()) || StringUtils.isBlank(weChatBuyPost.getReturn_code()) || !weChatBuyPost.getResult_code().equals("SUCCESS")) {
    				LOG.error("支付回调>>>回调信息result_code返回失败,具体信息:{}", JSONObject.toJSONString(weChatBuyPost));
    				failAndUp(resp,wxPayMonthBalanceOrder, weChatBuyPost);
    				return;
    			}
    			WxAccessConf wxConfInfo = weixinService.getSelfWxAccessConf();
    			if (!weiXinAuthManager.verifySign(param, wxConfInfo.getApiKey())) {
    				LOG.error("支付回调>>>回调信息apikey校验失败");
    				failAndUp(resp,wxPayMonthBalanceOrder, weChatBuyPost);
    				return;
    			}
    			if (wxPayMonthBalanceOrder.getInfoId() == null) {
    				LOG.error("支付回调>>>未找到订单所属信息" + outTradeNo);
    				failAndUp(resp,wxPayMonthBalanceOrder, weChatBuyPost);
    				return;
    			}
    			LOG.debug("支付回调>>>debug-准备查询info>>>"+wxPayMonthBalanceOrder.getInfoId());
    			WxPayMonthBalanceInfo wxPayMonthBalanceInfo = wxPayMonthBalanceInfoMapper.selectByPrimaryKey(wxPayMonthBalanceOrder.getInfoId());
    			if (wxPayMonthBalanceInfo == null) {
    				LOG.error("支付回调>>>未找到所属信息" + wxPayMonthBalanceOrder.getInfoId());
    				failAndUp(resp,wxPayMonthBalanceOrder, weChatBuyPost);
    				return;
    			}
    			LOG.debug("支付回调>>>debug-判断status>>>"+wxPayMonthBalanceOrder.getStatus());
    			if (WxPayMonthBalanceOrder.status_2.equals(wxPayMonthBalanceOrder.getStatus())) {
    				LOG.debug("支付回调>>>debug-判断type>>>"+wxPayMonthBalanceInfo.getType());
    				if (WxPayMonthBalanceInfo.type_10.equals(wxPayMonthBalanceInfo.getType())||WxPayMonthBalanceInfo.type_11.equals(wxPayMonthBalanceInfo.getType())) {
    					LOG.debug("支付回调>>>debug-准备更新合作伙伴账户余额>>>"+weChatBuyPost.getTransaction_id());
    					weixinService.updatePartnerInfo(wxPayMonthBalanceOrder, wxPayMonthBalanceInfo, weChatBuyPost.getTransaction_id());
    				} else if (WxPayMonthBalanceInfo.type_20.equals(wxPayMonthBalanceInfo.getType())||WxPayMonthBalanceInfo.type_21.equals(wxPayMonthBalanceInfo.getType())) {
    					LOG.debug("支付回调>>>debug-准备更新客户账户余额>>>"+weChatBuyPost.getTransaction_id());
    					weixinService.updateCustomerInfo(wxPayMonthBalanceOrder, wxPayMonthBalanceInfo, weChatBuyPost.getTransaction_id());
    				} else {
    					LOG.error("支付回调>>>微信支付流水记录类型不匹配");
    					failAndUp(resp,wxPayMonthBalanceOrder, weChatBuyPost);
    					return;
    				}
    			} else {
    				LOG.debug("支付回调>>>重复回调...");
    			}
    			LOG.debug("支付回调>>>微信回调成功");
    		} catch (Exception e) {
    			LOG.error("支付回调>>>异常", e);
    			failByCallback(resp);
    			return;
    		}
    		LOG.debug("支付回调>>>debug-响应微信文件流>>>");
    		super.output(resp, "text/xml", "utf-8", "");
    	}
    	
    	private void failByCallback(HttpServletResponse resp) throws IOException {
    		super.output(resp, "text/xml", "utf-8", "");
    	}
    	private void failAndUp(HttpServletResponse resp,WxPayMonthBalanceOrder wxPayMonthBalanceOrder,WeChatBuyPostDTO weChatBuyPost) throws IOException {
    		weixinService.updateWxPayOrderStatusByCallback(wxPayMonthBalanceOrder.getOrderId(), WxPayMonthBalanceOrder.status_4, weChatBuyPost.getTransaction_id());
    		failByCallback(resp);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
  • 相关阅读:
    Lnmp架构之mysql数据库实战2
    java计算机毕业设计web二手交易平台源码+mysql数据库+系统+lw文档+部署
    java spring cloud 企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展
    基于中文金融知识的 LLaMA 系微调模型的智能问答系统:LLaMA大模型训练微调推理等详细教学
    基于Php美妆商城
    Less基础速学 —— 混入、运算、继承
    Mybatis+Servlet+Mysql 整合的一个小项目:对初学者非常友好,有助于初学者很快的上手Java Web
    漫谈:C语言 C++ static究竟是什么
    小解C语言文件编译过程【linux】
    ubuntu server 22.04安装 minikube
  • 原文地址:https://blog.csdn.net/u012643122/article/details/108872697