2024年Java+Jfinal+H5接入微信支付所遇到的问题

1 篇文章 0 订阅
1 篇文章 0 订阅

H5接入Native支付和Jsapi支付遇到的一些问题

接入微信支付之前的准备工作

提示:这是必须的准备 !这是必须的准备! 这是必须的准备!重要的事情说三遍
1.申请一个商户号
2.准备一个已备案的域名
问题1:简要梳理一下业务场景,然后决定是接入微信的Native支付还是Jsapi支付,我这里是原本是打算用jsapi支付的,但是Native支付比Jsapi支付要方便接入,所以后面改用Native支付,但是后面写完之后发现微信官方把手机端Native支付二维码的长按识别支付这个功能禁用了,至于原因是因为好像是银行风控风险,反正是用不了这个功能,说起来这里也是一个问题不能在长按识别二维码支付的话,对业务的影响还是比较大的,最后还是要jsapi支付的方式。所以还是要结合项目实际业务来决定接入那种支付方式,我是已经踩过了,在这里记录一下

手机端Native支付用不了

		<dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.12</version>
        </dependency>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-apache-httpclient</artifactId>
            <version>0.3.0</version>
        </dependency>

上面是我用的依赖,不得不说确实挺好用的,这波必须点个赞

概要:既然Native支付不太符合业务,那么我用jsapi支付咯。然后就开始我的踩坑之路,讲错了开发之路,不重要了。wechatpay-java封装验签等方法直接调就行了,下面说遇到的问题。用jsapi支付的流程大概是
1.获取用户code
2.根据code获取用户openId
3.根据openid等参数生成订单,得到微信支付的预支付ID等
4.返回预支付id、appid、时间戳等参数给前端,让前端调支付控件支付
5.用户支付完成之后,后台接收支付通知,然后处理你的业务,最后响应微信服务器的通知
6.前端变化一下或者提示一下用户支付完成了,然后就是没有然后了
问题2:因为我开发的时候的使用是固定的code获取,而且我的openid是固定的所以就没有出现死循环调用https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appId}&redirect_uri=${encodeURIComponent(local)}&response_type=code&scope=snsapi_base&state=123#wechat_redirect 然后我在vue的created()调用发现一直在请求调用微信的获取code接口,后面查询微信相关资料,发现微信获取code是重定向转发的加上vue的生命周期的执行顺序等,code值会一直重复获取,解决方案是:

			//从url中获取code的值
			getQueryVariable(paramName) {
				let url = window.location.href;
				let regex = new RegExp(`${paramName}=([^&]*)`);
				let match = regex.exec(url);
				if (match) {
					return match[1];
				} else {
					return null;
				}
			},
			//从url中祛除code,返回没有code的url
			ridUrlParam(url, params) {
				for (var index = 0; index < params.length; index++) {
					var item = params[index];
					var fromIndex = url.indexOf(item + "="); //必须加=号,避免参数值中包含item字符串
					if (fromIndex !== -1) {
						// 通过url特殊符号,计算出=号后面的的字符数,用于生成replace正则
						var startIndex = url.indexOf("=", fromIndex);
						var endIndex = url.indexOf("&", fromIndex);
						var hashIndex = url.indexOf("#", fromIndex);

						var reg = "";
						if (endIndex !== -1) {
							// 后面还有search参数的情况
							var num = endIndex - startIndex;
							reg = new RegExp(item + "=.{" + num + "}");
							url = url.replace(reg, "");
						} else if (hashIndex !== -1) {
							// 有hash参数的情况
							var num = hashIndex - startIndex - 1;
							reg = new RegExp("&?" + item + "=.{" + num + "}");
							url = url.replace(reg, "");
						} else {
							// search参数在最后或只有一个参数的情况
							reg = new RegExp("&?" + item + "=.+");
							url = url.replace(reg, "");
						}
					}
				}
				var noSearchParam = url.indexOf("=");
				if (noSearchParam === -1) {
					url = url.replace(/\?/, ""); // 如果已经没有参数,删除?号
				}
				return url;
			},

这是我调用的方法

getCodeApi() {
				let local = window.location.href
				let url =
					`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appId}&redirect_uri=${encodeURIComponent(local)}&response_type=code&scope=snsapi_base&state=123#wechat_redirect`
				// window.location.replace(url)
				// window.location.assign(url)
				if (window.location.href.indexOf('code=') != -1) { // 避免一直重复重定向无限获取code
					let code = this.getQueryVariable('code') // 从url中获取code的值(此方法文末会贴出)
					if (code == sessionStorage.getItem(
							'code')) { // 微信获取code会重定向,所以从别的页面返回本页后,返回的其实是重定向之后的url,此时url一定带有上次的code,code只能用一次,这时候要重新获取
						let urls = this.ridUrlParam(window.location.href, [
							'code'
						]) // 从url中祛除code,用没有code的url重新生成code (此方法文末会贴出)
						window.location.href = urls
					}
					sessionStorage.setItem('code', code)
				} else {
					window.location.href = url
				}

			},

在vue的created()的调用

created() {
			this.fetchPageData();//获取页面数据
			this.getScreen();//判断当前是否是pc端
			if(this.isPC==false){//手机端(我这里是做了兼容pc的native支付,所以要判断一下使用环境)
				this.codeUrl = this.getCodeApi()//获取用户的code
			}
		}

就这样这个问题2解决了

问题3:wechatpay-java的商户API私钥路径找不到这个路径,这也是一个小问题,不过问题不大,也记录一下,切换成线上的环境时要切换对应的路径,并且这个文件路径只能用一个,要么是本地要么是线上的,注意一下就行

    //Native支付回调地址 使用https://notify_url,商户配置的回调地址将会失效
    public static final String NotifyUrl = "https://notify_url";//
    //Jsapi支付回调地址
    public static final String payNotifyUrl = "https://******/parent/order/payNotify";
    //商户API私钥路径(线上Linux环境的路径)
    public static String privateKeyPath = "/usr/local/app/tomcat-tice_kc/webapps/tice/WEB-INF/classes/apiclient_key.pem";
    //商户API私钥路径(本地)
//    public static String privateKeyPath = "E://xuguanhui_tice_java//src//main//resources//apiclient_key.pem";
    //商户证书序列号
    public static String merchantSerialNumber = "4E86A06D6F4370***************";
    //商户APIV3密钥
    public static String apiV3Key = "k5jqhazzpzdlblw**********";
   

问题4:接着上一个问题说到商户路径配置好之后,然后就是调接口获取openid,然后微信那边返回请求的用户openid,这一步都没有遇到问题,然后就是封装参数返回给前端,这里有一个问题是关于timeStamp的,因为在Java后台生成时间戳是可以,但是在vue前端里面会被识别number类型,而jsapi支付是需要传入string类型的,所以会报参数缺少错误,解决方法是把timeStamp
tostring一下,如果还是不行,就在前端处理一下,把timeStamp转成string类型
在这里插入图片描述
参考代码如下:

  //生成订单并且获取jsapi支付的支付参数
    public void getOpenId() {//这里是jfinal框架封装的,根据你自己的框架的封装类调整返回值就行
        String code = this.getPara("code");
        Integer reportId = this.getParaToInt("reportId");
        Integer studentId = this.getParaToInt("studentId");
        String openIdStr = this.getPara("openId");
        log.info("jsapi:code===>" + code);
        log.info("jsapi:openIdStr===>" + openIdStr);
        log.info("jsapi:reportId===>" + reportId);
        log.info("jsapi:studentId===>" + studentId);

        if (reportId == null || reportId == 0) {
            this.error("未获取到报告信息");
            return;
        }
        if (studentId == null || studentId == 0) {
            this.error("未获取到学生信息");
            return;
        }
        ReportStudentMobile studentReport = stuReportService.getStudentReportById(reportId);
        if (studentReport == null) {
            this.error("未查询到该报告");
            return;
        }
        Student student = studentService.findById(studentId);
        if (student == null) {
            this.error("未查询到该学生信息");
            return;
        }
        Nursery nursery = nurseryService.findById(student.getNurseryId());
        if (nursery == null||nursery.getPrice()==0) {
            this.error("未查询到该订单收费金额!请联系客服");
            return;
        }
        //查询是否已经生成订单,存在,返回;不存在,生成
        StudentOrder studentOrderData = studentOrderService.getStudentOrderByStudentId(student.getNurseryId(), reportId, student.getParentPhone(), studentId);
        log.info("是否已经生成订单了=========>" + studentOrderData);
        try {
            if (studentOrderData != null && studentOrderData.getPrepayId() != null) {
            //封装支付参数
                WxPayRespVO wxPayRespVO = new WxPayRespVO();
                wxPayRespVO.setAppId(JsapiPayOrderConfig.AppID);
                wxPayRespVO.setPrepayId(studentOrderData.getPrepayId());

                Long timeStamp = Long.parseLong(studentOrderData.getTimeStamp());
                wxPayRespVO.setTimeStamp(timeStamp.toString());
                String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
                wxPayRespVO.setNonceStr(substring);
                String signatureStr = Stream.of(JsapiPayOrderConfig.AppID, String.valueOf(timeStamp), substring, "prepay_id=" + studentOrderData.getPrepayId())
                        .collect(Collectors.joining("\n", "", "\n"));
                String sign = OrderPayUtil.getSign(signatureStr, JsapiPayOrderConfig.privateKeyPath);
                wxPayRespVO.setPaySign(sign);
                wxPayRespVO.setPackageStr("prepay_id=" + studentOrderData.getPrepayId());
                log.info("订单已存在,获取订单信息成功======>" + wxPayRespVO.toString());

                this.success("订单已存在,请支付======>", wxPayRespVO);
                return;

            } else {
                JsapiPayOrderConfig jsapiPayOrderConfig = new JsapiPayOrderConfig();

                if (studentOrderData != null && studentOrderData.getOpenId() != null) {
                    openIdStr = studentOrderData.getOpenId();
                    log.info("获取订单中的用户OpenId=========>" + openIdStr);
                } else {
                    //  String openId = JsapiPayOrderConfig.testOpenId;//测试openid
                    openIdStr = studentOrderService.getStudentOrderOpenId(student.getParentPhone());
                    log.info("查找用户历史openid=====>" + openIdStr);
                    if (openIdStr == null) {
                        log.info("正在获取用户openid信息");
                        openIdStr = jsapiPayOrderConfig.getOpenId(code);
                        log.info("获取结果=======>" + openIdStr);
                    }
                }

                log.info("获取用户openId======>" + openIdStr);

                if (openIdStr == null) {
                    this.error("获取用户openId失败");
                    return;
                }
                //生成订单编号
                String orderNo = studentOrderService.createOrderNo();
                log.info("创建订单编号=====>" + orderNo);

                //生成订单信息
                StudentOrder studentOrder = new StudentOrder();
                studentOrder.setOrderNo(orderNo);
                studentOrder.setStatus(1);
                studentOrder.setIsOrg(nursery.getIsOrg());
                studentOrder.setMoney(nursery.getPrice());
                studentOrder.setNurseryId(student.getNurseryId());
                studentOrder.setReportId(studentReport.getId());
                studentOrder.setReportName(studentReport.getSchoolName() + studentReport.getClassName());
                studentOrder.setParentPhone(student.getParentPhone());
                studentOrder.setStudentId(student.getId());
                studentOrder.setStudentName(student.getName());
                studentOrder.setNurseryId(student.getNurseryId());
                studentOrder.setClassId(student.getClassId());
                studentOrder.setCreateTime(new Date());
                studentOrder.setOpenId(openIdStr);
                studentOrder.setOrderType("Jsapi支付");
                studentOrder.setCode(code);
                String jsapiPayId = jsapiPayOrderConfig.getJsapiPay(studentOrder);
                if (jsapiPayId.length() == 0) {
                    this.error("生成预支付订单id失败");
                    return;
                }
                studentOrder.setPrepayId(jsapiPayId);
                Long timeStamp = System.currentTimeMillis() / 1000;
                studentOrder.setTimeStamp(timeStamp.toString());
                studentOrder.save();
                WxPayRespVO wxPayRespVO = new WxPayRespVO();
                wxPayRespVO.setAppId(JsapiPayOrderConfig.AppID);
                wxPayRespVO.setPrepayId(jsapiPayId);


                wxPayRespVO.setTimeStamp(timeStamp.toString());
                String substring = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
                wxPayRespVO.setNonceStr(substring);
                String signatureStr = Stream.of(JsapiPayOrderConfig.AppID, String.valueOf(timeStamp), substring, "prepay_id=" + jsapiPayId)
                        .collect(Collectors.joining("\n", "", "\n"));
                String sign = OrderPayUtil.getSign(signatureStr, JsapiPayOrderConfig.privateKeyPath);
                wxPayRespVO.setPaySign(sign);
                wxPayRespVO.setPackageStr("prepay_id=" + jsapiPayId);
                log.info("创建订单成功======>" + wxPayRespVO.toString());

                this.success("创建订单成功======>", wxPayRespVO);

            }
        } catch (Exception e) {
            log.error("创建失败======>" + e.getMessage());
        }
    }

问题5:接问题4说,支付参数都准备好了,那这样就可以调支付控件支付,理论上这样没问题,这里我用的是WeixinJSBridge的方法,但是没有生效,原因是因为我没有按script标签的方式引入,按照script标签引入就行,还有一种可能就是你jsapi支付授权目录没有配置或者说配置不对,我这里是配置了支付授权目录的,所以没有这方面的原因

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <%= require('pages/common/header-link.html') %>
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <%= require('pages/common/flexible.html') %>
    <title>绑定信息</title>
</head>
<body>
<div id="app">
    <index></index>
</div>
<%= require('pages/common/footer.html') %>
<%= require('pages/common/footer-script.html') %>
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.4.4/build/qrcode.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.4.4"></script>
</body>
</html>

问题6:接着问题5说,支付参数正确,支付控件能调起正确,能支付正确,商户也能收到钱正确,业务没有正确的执行,有问题。
用户支付完成之后,微信服务会发送支付回调给服务端,你需要响应他的回调请求并且完成你的业务处理,代码如下

/**
     * jsapi支付回调
     *
     * @throws Exception
     */
    public void payNotify() throws Exception {
        log.info("===============接收微信支付通知========");
        JsapiPayOrderConfig jsapiPayOrderConfig = new JsapiPayOrderConfig();
        jsapiPayOrderConfig.payNotifyUrl(this.getRequest(), this.getResponse());
        this.success("接收成功");
    }

jsapiPayOrderConfig.payNotifyUrl封装方法里面的内容:

    public void payNotifyUrl(HttpServletRequest request, HttpServletResponse response) throws Exception {
        try {
            //读取请求体的信息
            ServletInputStream inputStream = request.getInputStream();
            StringBuffer stringBuffer = new StringBuffer();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            String s;
            //读取回调请求体
            while ((s = bufferedReader.readLine()) != null) {
                stringBuffer.append(s);
            }
            String body = stringBuffer.toString();
            String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
            String nonce = request.getHeader(WECHAT_PAY_NONCE);
            String signType = request.getHeader("Wechatpay-Signature-Type");
            String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
            // 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
            // 没有的话,则构造一个
//            log.error(com.alibaba.fastjson2.JSON.toJSONString(wxPayV3Bean));
            NotificationConfig config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(merchantId)
                    .privateKeyFromPath(privateKeyPath)
                    .merchantSerialNumber(merchantSerialNumber)
                    .apiV3Key(apiV3Key)
                    .build();
            // 初始化 NotificationParser
            NotificationParser parser = new NotificationParser(config);
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(serialNo)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
                    .signType(signType)
                    .body(body)
                    .build();
            Transaction transaction = parser.parse(requestParam, Transaction.class);
            Transaction.TradeStateEnum state = transaction.getTradeState();

            log.info("body" + body);

            log.info("parse = " + transaction);
            Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream()));
            if (!StringUtils.equals("SUCCESS", state.toString())) {
                log.error("微信回调失败,JsapiPayController.payNotify.transaction:{}");
                //通知微信回调失败
//                response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
                out.write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
            }
            //TODO------
            //根据自己的需求处理相应的业务逻辑,异步
            log.info("报文输出=======>" + requestParam.toString());

            StudentOrder orderData = studentOrderService.getStudentOrderByOrderNo(transaction.getOutTradeNo());
            if (orderData != null) {
                studentOrderService.updateStudentOrderByOrderNo(transaction.getOutTradeNo());
                reportStudentMobileService.updateReport(orderData.getReportId());
            }
            out.write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
            log.info("============支付成功==============");
            //通知微信回调成功
//            response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");

        } catch (Exception e) {
            log.error("支付通知失败");
        } finally {
            response.getOutputStream().flush();
        }

我这里用response.getWriter().write报输出异常,所以改用out.write的方式,finally 无论请求失败还是成功都要执行
问题7:接着问题6来说我这里创建支付订单到支付回调都可以跑通了,然后就是支付之后的操作了,在这里我的想法是用户支付之后直接跳转页面,不需要再点一下,但是从2024年起微信官方默认把支付之后跳转的这个功能关闭了,如果需要的话要去配置这个功能,这个功能的配置可以去看一下微信官方文档,我这里的做法是刷一下页面,然后让用户再点击一下跳转页面

结尾:jsapi支付就这样完成了,如果对你有所帮助记得点赞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值