微信ApiV3支付结果回调实现

作者:超级无敌大飞   阅读 (2497)  |  收藏 (0)  |  点赞 (0)

摘要

使用wechatpay-apache-httpclient接入微信ApiV3支付后,需要处理微信反馈的支付结果,本文讲解了如何实现此部分逻辑


原文链接:微信ApiV3支付结果回调实现

关注微信公众号,查看最新原创技术文章

image.png

系列文章

1、使用wechatpay-apache-httpclient接入微信ApiV3详细步骤

2、微信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实现过程

除了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


分类   Spring 管理
字数   6984

博客标签    APIV3支付结果回调  

评论