摘要
使用wechatpay-apache-httpclient接入微信ApiV3支付后,需要处理微信反馈的支付结果,本文讲解了如何实现此部分逻辑
使用wechatpay-apache-httpclient接入微信ApiV3支付后,需要处理微信反馈的支付结果,本文讲解了如何实现此部分逻辑
关注微信公众号,查看最新原创技术文章
1、使用wechatpay-apache-httpclient接入微信ApiV3详细步骤
与其他仅仅实现支付结果回调的文章不同的是,本文在讲解如何实现微信ApiV3支付结果回调的同时,还会给大家一个如何高效的处理微信支付结果,并且达到微信多次请求的幂等性。
因此本文将会达到如下目的:
1、高效实现幂等性,增大系统处理微信支付结果的性能
2、实现微信支付结果回调的处理逻辑
微信支付结果反馈给服务器后,正常是需要很多的处理流程,诸如结果验签、订单金额校验、订单状态变更、通知用户支付结果、保留流水等等业务,如果把这些逻辑写在回调接口中,那么你就会发现这里会有很多的crud,业务逻辑一大推,这就会造成以下问题:
1、接口处理逻辑复杂,如果处理逻辑有bug,那么将会造成直接抛出异常给微信,导致微信重复发送支付结果,增大服务器的压力。
2、接口逻辑复杂,导致响应给微信时间过久,可能会触发微信多次重复调用接口,接口得考虑幂等性。
3、分步化处理,增加系统的稳健性
基于以上问题,本文将采用将上述业务分成两部分(异步化)
1、验签,支付状态、金额校验校验通过即数据落地,随机反馈给微信处理结果(减少接口响应时间)
2、订单状态变更、通知用户支付结果、保留流水等等业务交由Job或者队列来做
定义一张表来接收微信反馈的支付结果数据
create table wechat_pay_call_back_info ( id int auto_increment comment '主键' primary key, order_id bigint not null comment '订单ID', transaction_id varchar(32) not null comment '微信支付系统生成的订单号', trade_type varchar(16) not null comment '交易类型 JSAPI:公众号支付 NATIVE:扫码支付 APP:APP支付 MWEB:H5支付 ', trade_state varchar(32) not null comment '交易状态 SUCCESS:支付成功 REFUND:转入退款 NOTPAY:未支付 CLOSED:已关闭 REVOKED:已撤销 USERPAYING:用户支付中 PAYERROR:支付失败', request_body varchar(500) default '' not null comment '请求报文', total decimal(8, 2) not null comment '总金额,单位为元', payer_total decimal(8, 2) not null comment '用户实际支付金额,单位为元', del_status varchar(1) default '0' not null comment '删除状态 0:未删除;1:已删除', CREATE_DATETIME timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP, UPDATE_DATETIME timestamp null on update CURRENT_TIMESTAMP ) comment '微信支付结果响应表'; create index wechat_pay_call_back_info_order_id_index on wechat_pay_call_back_info (order_id);
引入wechatpay-apache-httpclient maven依赖
<!-- 微信支付API --> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.4.4</version> </dependency>
定义微信结果数据接收对象
@Data public class WeChatPayResultIn { private String id; private String create_time; private String resource_type; private String event_type; private String summary; private WechatPayResourceIn resource; } @Data public class WechatPayResourceIn { private String original_type; private String algorithm; private String ciphertext; private String associated_data; private String nonce; }
定义controller接口
/** * 参见文档:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter7_2_11.shtml * @param request * @param weChatPayResultIn * @return */ @PostMapping(value = "/wechatPayCallBack") @ApiOperation("微信支付结果通知") @ResponseBody public BaseResponse wechatPayCallBack(HttpServletRequest request, @RequestBody WeChatPayResultIn weChatPayResultIn) { payService.wechatPayCallBack(weChatPayResultIn); return ResponseData.out(ShunFengCheErrorEnums.COM_SUCCESS); }
service处理逻辑
@Transactional @Override public void wechatPayCallBack(WeChatPayResultIn weChatPayResultIn) { log.info("微信结果通知字符串:{}", JSON.toJSONString(weChatPayResultIn)); String nonce = weChatPayResultIn.getResource().getNonce(); String ciphertext = weChatPayResultIn.getResource().getCiphertext(); String associated_data = weChatPayResultIn.getResource().getAssociated_data(); AesUtil aesUtil = new AesUtil(weChatConfig.getAppV3Key().getBytes()); try { //验签 String s = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext); JSONObject jsonObject = JSONObject.parseObject(s); //订单号 String orderId = jsonObject.getString("out_trade_no"); //微信支付订单号 String transactionId = jsonObject.getString("transaction_id"); YhjOrderInfo yhjOrderInfo = orderInfoCusMapper.selectByPrePayIdAndPayType(Long.parseLong(orderId)); if (yhjOrderInfo == null || !StringUtils.equals(yhjOrderInfo.getPaymentType(), ShunFengCheConstant.ORDER_PAYMENT_TYPE_WEI_XIN)) { // TODO 告警 log.warn("订单{}不存在或者订单不是微信支付!{}", orderId, JSON.toJSONString(weChatPayResultIn)); throw new BusinessException(ShunFengCheErrorEnums.WECHAT_PAY_RESULT_CHECK_FAIL); } if (!StringUtils.equals(yhjOrderInfo.getPaymentStatus(), ShunFengCheConstant.PAYMENT_STATUS_WEI_ZHI_FU)) { log.warn("订单{}状态{}不是未支付,直接返回成功!", orderId, yhjOrderInfo.getPaymentStatus()); return; } String tradeState = jsonObject.getString("trade_state"); if (StringUtils.equals(tradeState, "USERPAYING")) { //还在支付中,不做处理 return; } //幂等性 YhjWechatPayCallBackInfo yhjWechatPayCallBackInfo = wechatPayCallBackInfoCusMapper.selectWechatCallBackInfoByOrderId(Long.parseLong(orderId)); if (yhjWechatPayCallBackInfo != null) { //已经反馈过了 return; } JSONObject amount = jsonObject.getJSONObject("amount"); //总金额 Long total = amount.getLong("total"); //用户实际支付金额 Long payerTotal = amount.getLong("payer_total"); //TODO 结果直接落地 } catch (GeneralSecurityException e) { e.printStackTrace(); } }
以上service处理逻辑需注意:
1、out_trade_no指的是你本地系统中的订单号,后续job会根据这个匹配微信支付结果
2、幂等性的实现是在上面代码中根据out_trade_no判断的时候做的
3、微信反馈的结果中状态为处理中的本代码不落地,等待对方支付成功后再落地(你需要根据实际业务处理)
4、校验通过,结果直接落地到上面的表中,请自己实现
5、微信反馈的结果中金额单位为分,需要注意转为你自己业务系统的单位
6、weChatConfig类的定义参见使用wechatpay-apache-httpclient接入微信ApiV3详细步骤
除了job,你也可以再上面代码中通过MQ消息来处理,原理都一样,此处不再赘述,只讲解job实现。
需要注意的是,这个job最好根据你自己的业务设计对应的触发周期,比如10秒跑一次(由于订单状态变更要求实时性比较强,这个也是一个缺陷,如果用MQ的话就不会存在这个问题,但是会增加系统的不稳定性)
@Override public void doWechatPayResultJob() { //TODO 查询待支付或者支付中的订单 //TODO 数据量大时需要分页 List<OrderInfo> orderInfoList = orderInfoMapper.selectAllNoPayOrderInfos(); if (CollectionUtils.isNotEmpty(orderInfoList)) { ExecutorService executorService = Executors.newFixedThreadPool(3); orderInfoList.forEach(e -> { executorService.submit(() -> { delWithEvOrder(e); }); }); executorService.shutdown(); } } private void delWithEvOrder(YhjOrderInfo orderInfo) { WechatPayCallBackInfo wechatPayCallBackInfo = wechatPayCallBackInfoMapper.selectWechatCallBackInfoByOrderId(orderInfo.getOrderId()); if (yhjWechatPayCallBackInfo == null) { return; } String tradeState = wechatPayCallBackInfo.getTradeState(); if (StringUtils.equals(tradeState, "SUCCESS")) { log.info("订单金额:{},反馈的总金额:{},实际支付金额:{}", orderInfo.getPaymentAmount(), yhjWechatPayCallBackInfo.getTotal(), yhjWechatPayCallBackInfo.getPayerTotal()); //TODO 支付成功,进行订单状态变更等业务逻辑 } else { log.info(String.format("其它未完成支付原因,当前的订单号:%s,通知的支付状态是:%s", orderInfo.getOrderId(), tradeState)); //TODO 支付失败,进行失败处理逻辑 } }
上述代码提升性能的地方体现在两处:
1、分页查询订单数据,防止一次性处理的订单过多
2、使用多线程处理订单处理,提升系统处理性能
通过上述步骤,就可以实现一个高性能的处理微信支付结果回调的逻辑,如果有问题请下面留言,或者加我微信gpf-182