前面分享了一篇 JAVA后端调用微信支付“统一下单”接口实现微信二维码扫码支付 的博客,虽然二维码在生成的时候可以设置有效期,但是这依旧不能保证整个业务流程和数据的安全。因为通常网站提供给用户的支付方式不止一种,除了微信支付,可能还有支付宝和银联支付等。
我们不妨先来假设下面几个场景:
场景一:张三在你的网购平台下了一个订单,起初是选择微信二维码扫码支付的,但是打开微信时发信微信零钱余额不足,于是张三就把这个二维码截图发给自己的基友李四,等了五分钟,李四可能因为手头有事要忙迟迟没有支付,这时张三实在没耐心了,就想起自己的支付包刚好还有点钱,于是就用支付宝支付成功了。恰巧此时李四那边也忙完自己的事情了,拿着自己的微信也成功扫码支付了。这是就造成一个订单,被成功支付两次了。
场景二:张三在你的网购平台下了一个订单,选择微信二维码扫码支付,因自己余额不足,将生成的二维码发给李四,由李四代付,大约过了五分钟,李四那边有事忙着一直没有支付,此时张三突然不想买了,于是取消了订单,过了几分钟才想起来要通知李四取消订单,恰好此时李四那边已经成功扫码支付了。这就造成已取消订单被支付成功的情况。
针对这种一个订单被多次支付和已取消订单被支付的情况,平台需要提供退款的功能,并且添加流水的操作应该由定时任务来执行,具体的流程准备另写一篇博客说明(如果我还记着这回事的话)。但是这就足够了吗?所谓种什么因得什么果,为什么不从源头上降低上面这两种情况发生的概率呢!假设订单取消后的30秒内,生成的微信二维码依旧有效,如果这还勉强能够说得过去的话,那么用户订单取消5分钟后(甚至更长时间),这个二维码还能被正常使用,那这就说不太过去了吧。你可能会说,可以在生成二维码的时候设置二维码的有效时间是5分钟或者更短(这个可参考 微信支付接口调用之二维码失效时间的设置),但是你总会遇到一些需求,比如要求二维码的有效时间跟着平台订单的有效时间走的情况,这时二维码的有效时间就不只10分钟了吧。
针对这种情况,我的解决方案是:新建一张表 t_qr_code,用来存储生成的二维码图片路径,并且保存二维码的失效时间。表的结构如下:
CREATE TABLE `t_qr_code` (
`pk_id` varchar(50) NOT NULL COMMENT '主键',
`order_id` varchar(50) DEFAULT NULL COMMENT '商户订单号',
`code_url` varchar(255) DEFAULT NULL COMMENT '二维码保存的路径',
`status` tinyint(3) DEFAULT '1' COMMENT '二维码状态,1,正常;2,已调用微信api使之失效;3,需要调用微信api关闭订单;',
`expire` datetime DEFAULT NULL COMMENT '二维码失效日期',
`create_date` datetime DEFAULT NULL COMMENT '二维码生成时间',
`update_date` datetime DEFAULT NULL COMMENT '本记录更新时间',
PRIMARY KEY (`pk_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
该表有以下几个用处:
(1)当用户关闭二维码所在页面,下次再次对这个订单发起微信二维码扫码支付时,如果这时二维码有效期超过15秒,就依旧返回这个二维码图片和失效时间,而不会重新跟微信请求。当然这个也需要前端根据这个二维码的失效时间做一些特效处理以提高用户体验,比如二维码失效了,就弹出一个按钮让用户点击刷新。
(2)当用户取消订单的时候,查询 t_qr_code 表中是否有未是失效的二维码,如果有,修改status=3,由定时任务调用微信关闭订单接口的接口。
(3)如果用户一开始选择二维码扫码支付,成功生成了二维码,但是最终却是选择其他方式支付成功的,其它方式的支付接口中,需要执行下面这台sql语句:
update t_qr_code set status=3 where expire>now() and status=1 where order_id=#{orderId}
大致思路大概就如此,顺便提醒一下,你的调用微信“关闭订单”接口的定时任务的扫描间隔时间应该设置的尽量短一些,这个时间越短,造成失效订单被支付的概率就越低。在分享源码之前,请参考官方文档 ,值得注意的是,微信H5支付,微信JSAPI支付,微信Native支付(即二维码扫码支付)和微信APP支付 的关闭订单接口都是同一个。感兴趣的可以去 微信支付 官网逐一查看。此接口本人亲测有效,测试方法:当微信生成二维码的时候,设置的二维码有效期是30分钟,但是在生成二维码3-5分钟后,调用下面这个关闭订单的接口,等再次拿微信扫描二维码的时候,微信提示该订单已失效。官方文档提示要再二维码生成的5分钟后再调用关闭订单的接口,但是实际测试发现3分钟后也能成功取消。
下面分享具体的实现代码,我把他封装到了一个工具类中,返回的参数都在map中,如果想知道有哪些参数,可以在自己的控制台打印一下或者参考官方文档 ,下面分享代码:
package wxpay;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
public class WxApiUtils {
/**
* 关闭微信订单接口
* @param orderId你自己网站生成的订单号,注意要和你申请二维码时候用的订单号一致,
看过我前面那边申请二维码支付的朋友会发现,此时我的这个orderId应该是要带有
支付标识的,如果我网站的订单号是:123456 ,那么此时的这个参数
orderId=123456_NAVTIVE ,因为我申请二维码用的就是这个带标识的订单后
* @return
*/
public static Map<String, String> closeOrder(String orderId){
String appid =Constant.APP_ID;
String mch_id = Constant.MCH_ID;
String out_trade_no = orderId;
String nonce_str = WXUtil.createNoncestr().substring(1, 32);
SortedMap<String, String> packageParams = new TreeMap<String, String>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("nonce_str", nonce_str);
String sign = WXUtil.createSign(packageParams);
packageParams.put("sign", sign);
String xml =WXUtil.ArrayToXml(packageParams);
logger.info("map转换成xml的结果:"+xml);
String createOrderURL = "https://api.mch.weixin.qq.com/pay/closeorder";
return GetWxOrderno.getReturnUrl(createOrderURL, xml);
}
}
涉及到的所有工具类和实体类,以及一些依赖的jar包,在我的另一篇博客 JAVA后端调用微信支付“统一下单”接口实现微信二维码扫码支付 中都能找到,时间有限,此处不再上传。
至此,分享结束,希望对各位有帮助。