微信公众号内发起微信JSAPI支付的实现(JAVA源码)

       前面写了一篇 JAVA后端调用微信支付“统一下单”接口实现微信二维码扫码支付 ,好事成双嘛,想起了自己还做过微信JSAPI支付,所以一并分享了。

       官方对微信JSAPI的应用场景的解释是:

微信公众号内发起微信JSAPI支付的实现(JAVA源码)_第1张图片

 

官方的使用场景介绍是:商户已有H5商城网站,用户通过消息或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买的流程。点击 查看完整的场景介绍 。

 

我是在自己的微信公众号里调用此接口的,实现的效果如下图(图片来源于 微信官方文档):

微信公众号内发起微信JSAPI支付的实现(JAVA源码)_第2张图片

 

这个功能主要涉及到了两个接口,
接口一:当用户点击图一中的“立即支付”的时候调用该接口,此时后台就会调用微信支付的“统一下单”接口,点击 此处 查看微信官方提供的文档。接口调用成功后,后端会将请求得到一些数据封装好并返回给前端。前端只要调用一段js代码,就能实现图二中的效果。这里比较难的一点,就是在发起统一下单接口的时候,需要获取到用户的openId,我的项目在用户登录公众号的时候,就已经将openId保存到session中,而用户在调用H5付款的接口是做了登录拦击的,所以只要用户成功调用了接口一,那说明他就能拿到openId。感兴趣的可以参考我的另一篇博客 微信用户与第三方网站用户的绑定策略(实现用户第一次登陆后永久免登陆) 。如果无法保证准确获取到openId,不妨参考微信的 H5支付 来实现,这种支付方式,用户openId不是必传参数。

接口二:微信支付成功后的回调接口,跟前面那篇 JAVA后端调用微信支付“统一下单”接口实现微信二维码扫码支付 中的是同一个接口,具体可看那篇博客的说明。

 

下面开始上代码,先上依赖的jar包:




    org.jdom
    jdom
    1.1




    xml-apis
    xml-apis
    1.0.b2

 

然后是接口一和接口二的代码:

package wxpay;

import java.io.*;
import java.util.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/pay")
public class PayController {
	
	private static final Logger logger = Logger.getLogger(PayController.class);

	/**
	 * 接口一:从微信公众号发起微信H5支付接口
	 */
	@RequestMapping(value = "/toH5Pay", method = RequestMethod.POST)
	@ResponseBody
	public Result toWxPay(String orderId,String userId,Double amount,
			HttpServletRequest request) {

		Result dg = new Result();
		try {

			//我在用户登录时就会获取用户的openId并保存到session中,而当前这个接口又做了登录拦截,所以只要请求进来了,
			//就一定能获取到openId,但是为了保险起见,还是需要坐下判断
			String openId = (String) request.getSession().getAttribute("openId");
			if (StringUtils.isEmpty(openId)) {
				dg.setSuccess(false);
				dg.setMsg("没有获取到openId");
				return dg;
			}

			if(StringUtils.isBlank("orderId")||StringUtils.isBlank("userId")||amount==null){
				dg.setSuccess(false);
				dg.setMsg("缺少必要参数");
				return dg;
			}

			//个人核心业务隐身符,此处需要拿着orderId和userId为查询条件去数据库查询这个订单是否存在,
			//判断是否已经支付过了,同时还要携带这个金额是否已数据库中记录的商品实际金额一致
			//如果检验不通过,就不执行后面的操作
			//········

			String body = "平台-运费";//商品简单描述,该字段请按照规范传递,具体请见参数规定
			String attach = orderId+","+userId+","+amount+",wxpay";//附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
			String totalFee = getMoney(amount+"");//订单总金额,单位为分,详见支付金额
			String device_info = "WEB";
			String spbill_create_ip = request.getRemoteAddr();
			String notify_url = "https://www.域名.cn/项目名/pay/wxRedirect";//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
			String trade_type = "JSAPI"; // 支付类型
			String nonce_str = UUID.randomUUID().toString().replace("-", "").substring(1, 32);

            //因为通常,一个网站接入微信支付,pc端接入的是微信Native支付,
            //微信公众号端接入的是JSAPI支付,app接入的是微信app支付,此外还有H5支付。
            //真实开发时遇到同一个订单号,如果开始使用二维码支付,获取二维码后没有扫码,
            //二维码依旧有效的时候,公众号那边发起JSAPI支付会失败。
            //不可能同一个订单使用不同的订单号,这个本身就很矛盾,
            //所以还是订单号后面追加标识用于区分,当然了支付回调接口在接收的时候
            //也要通过处理获取真实的订单号
			String out_trade_no = orderId+"_JSAPI";
			String mch_id = Constant.MCH_ID;
			String appId = Constant.APP_ID;

			SortedMap packageParams = new TreeMap();
			packageParams.put("appid", appId);
			packageParams.put("mch_id", mch_id);
			packageParams.put("nonce_str", nonce_str);
			packageParams.put("body", body);
			packageParams.put("attach", attach);
			packageParams.put("out_trade_no", out_trade_no);
			packageParams.put("total_fee", totalFee);
			packageParams.put("device_info", device_info);
			packageParams.put("notify_url", notify_url);
			packageParams.put("spbill_create_ip", spbill_create_ip);
			packageParams.put("trade_type", trade_type);
			packageParams.put("openid", openId);

			String sign = WXUtil.createSign(packageParams);
	        logger.info("生成的签名是:"+sign);
	        packageParams.put("sign", sign);

	         String xml =WXUtil.ArrayToXml(packageParams);
	         logger.info("map转换成xml的结果:"+xml);

			String createOrderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

			Map map = GetWxOrderno.getReturnUrl(createOrderURL, xml);
			logger.info("-----------------json-----------" + map);
			if (map != null) {

				map.put("timeStamp", Long.toString(System.currentTimeMillis() / 1000));
				map.put("signType", "MD5");

				SortedMap params = new TreeMap();
				params.put("appId", appId);
				params.put("timeStamp", map.get("timeStamp"));
				params.put("nonceStr", UUID.randomUUID().toString().replace("-", "").substring(1, 32).toUpperCase());
				params.put("package", "prepay_id=" + map.get("prepay_id"));
				params.put("signType", map.get("signType"));
				String paySign = WXUtil.createSign(params);

				map.put("paySign", paySign);
				map.put("nonce_str", params.get("nonceStr"));
				map.put("prepay_id", "prepay_id=" + map.get("prepay_id"));
				map.put("app_id", appId);
			}
			dg.setSuccess(true);
			logger.info("发起微信h5支付-----json-----------" + map);

			dg.setObj(map);

		} catch (Exception e) {
			dg.setSuccess(false);
			dg.setMsg("调用微信二维码失败:"+e.getMessage());
		}
		return dg;
	}

	String getMoney(String amount) {
		if (amount == null) {
			return "";
		}
		String currency = amount.replaceAll("\\$|\\¥|\\,", "");

		int index = currency.indexOf(".");
		int length = currency.length();
		Long amLong = 0l;
		if (index == -1) {
			amLong = Long.valueOf(currency + "00");
		} else if (length - index >= 3) {
			amLong = Long.valueOf((currency.substring(0, index + 3)).replace(
					".", ""));
		} else if (length - index == 2) {
			amLong = Long.valueOf((currency.substring(0, index + 2)).replace(
					".", "") + 0);
		} else {
			amLong = Long.valueOf((currency.substring(0, index + 1)).replace(
					".", "") + "00");
		}
		return amLong.toString();
	}


	/**
	 * 接口二:微信支付回调接口,不管是H5支付,还是微信二维码扫码支付,使用的是同一个回调接口
	 * 注意!注意!注意!这个接口不要做权限拦截操作,要直接放行
	 */
	@RequestMapping(value = "wxRedirect")
	@ResponseBody
	public void wxRedirect(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		logger.info("微信支付回调开始了");

		// 创建支付应答对象
		ResponseHandler resHandler = new ResponseHandler(req, resp);
		// 读取参数
		InputStream inputStream = req.getInputStream();
		StringBuffer sb = new StringBuffer();
		BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
		String s;
		while ((s = in.readLine()) != null) {
			sb.append(s);
		}
		in.close();
		inputStream.close();

		// 解析xml成map
		Map m =XMLUtil.doXMLParse(sb.toString());

		// 过滤空 设置 TreeMap
		SortedMap packageParams = new TreeMap();
		Iterator it = m.keySet().iterator();

		while (it.hasNext()) {
			String parameter = it.next();
			String parameterValue = m.get(parameter);

			String v = "";
			if (null != parameterValue) {
				v = parameterValue.trim();
			}
			packageParams.put(parameter, v);
		}

		// 判断签名是否正确
		if (!PayCommonUtil.isTenpaySign("UTF-8", packageParams, Constant.MCH_KEY)) {
			logger.info("回调后发现签名失败了...");
			return ;
		}

		if (!"SUCCESS".equals(packageParams.get("result_code"))) {
			// 错误时,返回结果未签名,记录retcode、retmsg看失败详情。
			logger.info("查询验证签名失败或业务错误");
			return;
		}

		// 到这里,说明是支付成功了
		// 执行自己的业务逻辑

		// 通知id
		String mch_id = (String) packageParams.get("mch_id");
		String openid = (String) packageParams.get("openid");//付款方的openId
		String is_subscribe = (String) packageParams.get("is_subscribe");//是否关注公众号
		String out_trade_no = (String) packageParams.get("out_trade_no"); // 自己网站定义的订单号
		String attach = (String) packageParams.get("attach");//读取附带的参数
		String total_fee = (String) packageParams.get("total_fee");//支付金额
		String transaction_id = (String) packageParams.get("transaction_id");//微信那边生成的流水号,注意需要保存

		logger.info("attach:" + attach + ",mch_id:" + mch_id + ",openid:" + openid + ",is_subscribe:" + is_subscribe
				+ ",out_trade_no:" + out_trade_no + ",total_fee:" + total_fee + ",transaction_id:"+ transaction_id);

		//因为前面已经有定义格式:网站的订单号_支付标识 ,所以此处需要获取真实的订单号
		out_trade_no=out_trade_no.split("_")[0];
		logger.info("真正的订单号是:"+out_trade_no);


		// ----------- 处理业务开始 --------------
		logger.info("处理业务逻辑开始。。。");

		// 处理数据库逻辑,注意交易单不要重复处理,注意判断返回金额

		try {

			//注意我们原来定义的参数形式是这样的:orderId+","+userId+","+amount+",wxpay";
			String[] str = attach.split(",");
			if (str == null || str.length != 4) {
				logger.info("微信回调的attach参数错误,可能是非法请求");
				return;
			}

			String orderId = str[0];//自己网站定义的订单号
			String userId = str[1];//这个订单号对应的用户userId
			double amount = Double.parseDouble(str[2]);//订单金额
			String type = str[3];//用于标识此次微信支付是支付那种类型的金额,由自己定义;

			//执行到这里,应该单独往表中添加一条记录,用于记录微信付款成功了,注意这里应该使用独立的事务,
			// 防止后面的业务失败时,导致整个事务回滚。
			// 主要是为了将来排查错误使用,比如同一个订单,但是前面2分钟用户已经使用支付宝支付了,现在用户又用
			//微信支付了一遍,此时回调操作怎么处理呢?所以类似的这个回调成功的记录都必须要有,后面退款的时候可能需要用


			if ("wxpay".equals(type)) {
				logger.info("进入微信支付后续处理逻辑");

				//执行你自己的业务逻辑,比如设置订单为已支付,添加资金流水等
				//······

				//设置支付成功标志,不管你用哪种支付方式,只要这个订单支付成功了,就要设置这个标志
				RedisUtils.set("PAY_"+orderId,"1",60*60*24*30);//设置有效期30天,具体时间看你自己的业务

			}

		} catch (Exception e) {

			logger.info("回调错误:"+ e.getMessage());
		}

		logger.info("商家流水号:" + out_trade_no);// 其他字段也可用类似方式获取
		logger.info("FrontRcvResponse前台接收报文返回结束");
		// ------------------------------
		// 处理业务完毕
		// ------------------------------
		// 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
		String resXml = "" + ""
				+ "" + " ";
		resHandler.sendToCFT(resXml);
	}

}

 

接着,在你的H5网页中执行JS调起支付,详情可参考官方文档:微信内H5调起支付

下面是具体的实现代码:

//点击“立即支付”,调用下面的这个函数
function pays() {
	$.ajax({
		type:"POST",
		url:"http://localhost:8080/项目名/pay/toH5Pay.do",
		data:{
			amount:"",
			userId:"",
			orderId:""
		},error:function(requst){
			pophint("系统错误,请稍后重试")
		},success:function(result){

			//请求成功了,开始解析数据
			eval("var r ="+result);
			
			if(r.success){
				var obj=r.obj;
				WeixinJSBridge.invoke('getBrandWCPayRequest',{  
	               "appId" : obj.app_id,
	             	"timeStamp":obj.timeStamp,  
	                "nonceStr" : obj.nonce_str,
	                "package" : obj.prepay_id,  
	                "signType" : obj.signType,
	                "paySign" : obj.paySign
	           },function(res){  
	                if(res.err_msg == "get_brand_wcpay_request:ok"){ 
	                	pophint("付款成功!",null,null,"javascript:window.history.back();return false;");	
	                }else{  
	                	pophint("付款失败");
	                	//此处,若用户取消付款(也就是退出公众号或者关闭那个输入密码的窗口),你可以执行一些自己的操作
	                }  
	       	 }); 
			}else{
				pophint(r.msg)
			}
		}
	});
}

 

剩下的一些java类(如下图所示),都可以在这篇博客 JAVA后端调用微信支付“统一下单”接口实现微信二维码扫码支付 找到,此处不再分享(补充:还有一个WXUtil.java文件也要引入,下图中漏掉了,但上面的这篇博客能找到)。

微信公众号内发起微信JSAPI支付的实现(JAVA源码)_第3张图片

 

至此,分享结束,希望对诸位有所帮助。

 

你可能感兴趣的:(Java后端,微信开发)