SpringBoot+微信支付-JSAPI{微信支付回调}

引入微信支付SDK

Maven: com.github.wechatpay-apiv3:wechatpay-java-core:0.2.12
Maven: com.github.wechatpay-apiv3:wechatpay-java:0.2.12

响应微信回调的封装

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class WxNotifyVo {
    private String code;
    private String message;
}

微信回调接口必须是:HTTPS的接口

@RestController
@RequestMapping("/wxpay/notify")
public class WxPayNotifyController {
    private static Logger logger = LoggerFactory.getLogger(WxPayNotifyController.class);
    @Autowired
    private WxPayCallBackNotifyService wxPayCallBackNotifyService;

    /**
     * 支付结果:回调接口通知
     *
     * @return
     */
    @RequestMapping("/pay/{merchantNo}")
    public WxNotifyVo pay(@PathVariable("merchantNo") String merchantNo, HttpServletRequest request) throws Exception {
        logger.info("微信支付回调通知------->");
        return wxPayCallBackNotifyService.dealPayResultV3(request,merchantNo);
    }

    /**
     * 退款结果通知
     *
     * @return
     */
    @RequestMapping("/refund/{merchantNo}")
    public WxNotifyVo refund(@PathVariable("merchantNo") String merchantNo,HttpServletRequest request) throws Exception {
        logger.info("微信退款回调通知------->");
        return wxPayCallBackNotifyService.dealRefundResultV3(request,merchantNo);
    }

}

微信回调实现

package xxxx.xxxx.cashier.payChannel.callback.handler;

import cn.hutool.core.date.DateUtil;
import xxxx.common.domain.model.exception.BusinessException;
import xxxx.xxxx.cashier.common.LocalTimeUtils;
import xxxx.xxxx.cashier.common.runable.NotifyThreadRunnable;
import xxxx.xxxx.cashier.domain.dto.ChannelRCRate;
import xxxx.xxxx.cashier.domain.model.CashierPayment;
import xxxx.xxxx.cashier.domain.model.PayTypeEnum;
import xxxx.xxxx.cashier.notifyRecord.application.NotifyRecordService;
import xxxx.xxxx.cashier.payChannel.callback.vo.WxNotifyVo;
import xxxx.xxxx.cashier.payChannel.handler.OperationWxHandler;
import xxxx.xxxx.cashier.payment.application.MerchantRefundHandlerService;
import xxxx.xxxx.cashier.payment.application.ModifyAccountService;
import xxxx.xxxx.cashier.payment.application.PaymentService;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

import static com.wechat.pay.java.core.http.Constant.*;

/**
 * packageName xxxx.xxxx.cashier.payChannel.callback.handler
 *
 * @author GuoTong
 * @version JDK 8
 * @className WxPayCallBackNotifyService
 * @date 2024/3/22
 * @description 微信支付通知回调业务处理
 */
@Component
@SuppressWarnings("all")
public class WxPayCallBackNotifyService {

    private static Logger log = LoggerFactory.getLogger(WxPayCallBackNotifyService.class);

    @Autowired
    private OperationWxHandler operationWxHandler;

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private NotifyRecordService notifyRecordService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private ModifyAccountService modifyAccountService;

    @Autowired
    private MerchantRefundHandlerService merchantRefundHandlerService;

    /**
     * 对账之后订单的费率计算结果----入账{调用方若批量请加事务处理}
     * 调用支付渠道结算流程
     *
     * @return
     */
    @Transactional
    public boolean orderRateSupplement(String paymentNo, ChannelRCRate channelRCRate, Date time) {
        log.info("入账-----orderRateSupplement-----paymentNo:{}", paymentNo);
        if (StringUtils.isBlank(paymentNo)) {
            log.error("paymentNo is null|订单号不能为空");
            return false;
        }
        CashierPayment cashierPayment = paymentService.queryByPamentNo(paymentNo);
        if (cashierPayment == null) {
            log.error("paymentNo not exist|订单不存在");
            return false;
        }
        // 设置渠道手续费
        if (channelRCRate.getChannelROrMBalance()) {
            // 渠道对账入账
            cashierPayment.setChannelServiceFee(channelRCRate.getChannelServiceFee());
            cashierPayment.setCheckStatus(channelRCRate.getCheckStatus());
            cashierPayment.setCheckTime(channelRCRate.getCheckTime());
            cashierPayment.setCheckAmount(channelRCRate.getCheckAmount());
            if (channelRCRate.getCheckStatus() == 1) {
                // 渠道结算
                cashierPayment.setSettleStatus(1);
                cashierPayment.setSettleDate(new Date());
                // 订单金额 - 渠道手续费 = 结算金额
                long channelSettleAmount = cashierPayment.getAmount() - channelRCRate.getChannelServiceFee().longValue();
                cashierPayment.setSettleAmount(channelSettleAmount);
            }
        } else {
            cashierPayment.setSettleAmount(channelRCRate.getSettleAmount());
            cashierPayment.setSettleDate(channelRCRate.getSettleDate());
            cashierPayment.setSettleStatus(channelRCRate.getSettleStatus());
        }
        // 获取交易的退款总金额(原始交易记录流水号)
        String channelRefundFlowNo = cashierPayment.getChannelFlowNo();

        Date beginOfDay = DateUtil.beginOfDay(time);
        String beginDay = DateUtil.format(beginOfDay, LocalTimeUtils.WX_SUCCESS_TIME_FORMATTER);
        Date endOfDay = DateUtil.endOfDay(time);
        String endDay = DateUtil.format(endOfDay, LocalTimeUtils.WX_SUCCESS_TIME_FORMATTER);
        List<CashierPayment> cashierPaymentRefunds = paymentService.queryForThatOrderRefundInfo(channelRefundFlowNo, beginDay, endDay);
        BigDecimal refundAmountTotalFee = BigDecimal.ZERO;
        cashierPayment.setRefundFeeAmountCount(channelRCRate.getChannelServiceFee() == null ? BigDecimal.ZERO : channelRCRate.getChannelServiceFee());
        if (CollectionUtils.isNotEmpty(cashierPaymentRefunds)) {
            Long refundAmountTotal = new Long(0);
            // 结算金额--原订单(不包含退款)
            BigDecimal checkAmount = channelRCRate.getCheckAmount();
            for (CashierPayment refund : cashierPaymentRefunds) {
                Long refundAmount = refund.getRefundAmount();
                BigDecimal channelServiceFee = refund.getChannelServiceFee();
                if (channelServiceFee == null) {
                    channelServiceFee = BigDecimal.ZERO;
                }
                if (refundAmount == null) {
                    refundAmount = new Long(0);
                }
                checkAmount = checkAmount.subtract(new BigDecimal(refundAmount));
                refundAmountTotalFee = refundAmountTotalFee.add(channelServiceFee);
                refundAmountTotal += refundAmount;
            }
            if (checkAmount.compareTo(BigDecimal.ZERO) <= 0) {
                cashierPayment.setCheckAmount(BigDecimal.ZERO);
            }
            cashierPayment.setRefundFeeAmountCount(cashierPayment.getRefundFeeAmountCount().multiply(refundAmountTotalFee));
            cashierPayment.setRefundAmount(refundAmountTotal);
        }
        // 设置商户未结算
        cashierPayment.setMerchantRefundStatus(0);
        // 订单流痕
        paymentService.update(cashierPayment);
        // 执行支付渠道结算流程---动账通知
        modifyAccountService.channelSettleHandler(cashierPayment);
        return true;
    }

    /**
     * 微信支付的回调通知
     * 首先,你需要在你的服务器上创建一个公开的 HTTP 端点,接受来自微信支付的回调通知。 当接收到回调通知,使用 notification 中的 NotificationParser 解析回调通知。
     * <p>
     * 具体步骤如下:
     * <p>
     * 使用回调通知请求的数据,构建 RequestParam。
     * HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
     * HTTP 头 Wechatpay-Signature。应答的微信支付签名。
     * HTTP 头 Wechatpay-Serial。微信支付平台证书的序列号,验签必须使用序列号对应的微信支付平台证书。
     * HTTP 头 Wechatpay-Nonce。签名中的随机数。
     * HTTP 头 Wechatpay-Timestamp。签名中的时间戳。
     * HTTP 头 Wechatpay-Signature-Type。签名类型。
     * 初始化 RSAAutoCertificateConfig。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。
     * 初始化 NotificationParser。
     * 调用 NotificationParser.parse() 验签、解密并将 JSON 转换成具体的通知回调对象。如果验签失败,SDK 会抛出 ValidationException。
     * 接下来可以执行你的业务逻辑了。如果执行成功,你应返回 200 OK 的状态码。如果执行失败,你应返回 4xx 或者 5xx的状态码,例如数据库操作失败建议返回 500 Internal Server Error。
     * | 多商户支持
     * @param request HttpServletRequest
     * @return WxNotifyVo
     */
    public WxNotifyVo dealPayResultV3(HttpServletRequest request,String merchantNo) {
        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
            // 读取请求体
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
            String notify = sb.toString();
            log.info("微信支付通知: {}", notify);
            RequestParam requestParam = buildByHeaderRequestParam(request, notify);
            // 初始化 RSAAutoCertificateConfig。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。
            NotificationConfig config = operationWxHandler.getCallBackConfig(merchantNo,PayTypeEnum.WECHAT);
            // 验证签名并解析请求体
            NotificationParser notificationParser = new NotificationParser(config);
            // 调用 NotificationParser.parse() 验签、解密并将 JSON 转换成具体的通知回调对象。如果验签失败,SDK 会抛出 ValidationException。
            log.info("支付通知回调:验签、解密并转换成 Transaction对象:-------");
            Transaction transaction = null;
            try {
                /**
                 * 常用的通知回调调对象类型有:
                 * 支付 Transaction
                 * 退款 RefundNotification
                 * 若 SDK 暂不支持的类型,请使用 Map.class,嵌套的 Json 对象将被转换成 LinkedTreeMap
                 */
                transaction = notificationParser.parse(requestParam, Transaction.class);
            } catch (Exception e) {
                throw new BusinessException("支付通知回调,验签、解密失败--->" + e.getMessage());
            }
            log.info("Transaction:===>>>>支付CallBack状态" + transaction.getTradeState());
            log.info("Transaction:===>>>>" + transaction);
            // 开启线程异步通知业务侧
            taskExecutor.execute(new NotifyThreadRunnable(transaction, paymentService, notifyRecordService));
            // 接收消息成功
            return new WxNotifyVo().setCode("SUCCESS");
        } catch (Exception e) {
            log.error("解析付款通知出错:{}", e.getMessage(), e);
            return new WxNotifyVo().setCode("FAIL").setMessage(e.getMessage());
        }
    }

    /**
     * 使用回调通知请求的数据,构建 RequestParam。
     *
     * @param request HttpServletRequest
     * @param notify  notify
     * @return notify
     */
    public RequestParam buildByHeaderRequestParam(HttpServletRequest request, String notify) {
        log.info("-------------------WxPay---------GetHeader--------------------BEGIN");
        // HTTP 头 Wechatpay-Timestamp。签名中的时间戳。
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        // HTTP 头 Wechatpay-Nonce。签名中的随机数。
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        // HTTP 头 Wechatpay-Signature-Type。签名类型。
        String signType = request.getHeader("Wechatpay-Signature-Type");
        //HTTP 头 Wechatpay-Serial。微信支付平台证书的序列号,验签必须使用序列号对应的微信支付平台证书。
        String serialNo = request.getHeader(WECHAT_PAY_SERIAL);
        //  HTTP 头 Wechatpay-Signature。应答的微信支付签名。
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE);
        log.info("-------------------WxPay---------GetHeader--------------------ENDING");
        // 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serialNo)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .signType(signType)
                .body(notify)
                .build();
        return requestParam;
    }


    /**
     * 处理退款结果| 多商户支持
     *
     * @param request HttpServletRequest
     * @return WxNotifyVo
     */
    public WxNotifyVo dealRefundResultV3(HttpServletRequest request,String merchantNo) {
        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
            // 读取请求体
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }
            String notify = sb.toString();
            log.info("微信退款通知: {}", notify);
            RequestParam requestParam = buildByHeaderRequestParam(request, notify);
            // 初始化 RSAAutoCertificateConfig。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。

            NotificationConfig config = operationWxHandler.getCallBackConfig(merchantNo,PayTypeEnum.WECHAT);
            // 验证签名并解析请求体
            NotificationParser notificationParser = new NotificationParser(config);
            // 调用 NotificationParser.parse() 验签、解密并将 JSON 转换成具体的通知回调对象。如果验签失败,SDK 会抛出 ValidationException。
            log.info("退款通知回调:验签、解密并转换成 RefundNotification对象:-------");
            RefundNotification refundNotification = null;
            try {
                /**
                 * 常用的通知回调调对象类型有:
                 * 支付 Transaction
                 * 退款 RefundNotification
                 * 若 SDK 暂不支持的类型,请使用 Map.class,嵌套的 Json 对象将被转换成 LinkedTreeMap
                 */
                refundNotification = notificationParser.parse(requestParam, RefundNotification.class);
            } catch (Exception e) {
                throw new BusinessException("退款通知回调,验签、解密失败(可能是微信官方的探测流量故意生成的错误签名)--->" + e.getMessage());
            }
            log.info("Transaction:===>>>>退款CallBack状态" + refundNotification.getRefundStatus());
            log.info("Transaction:===>>>>" + refundNotification);
            // 开启线程异步通知业务侧
            taskExecutor.execute(new NotifyThreadRunnable(refundNotification, paymentService, notifyRecordService));
            log.info("退款通知回调:验签、解密并转换成 RefundNotification对象:-------end");
            // 解析消息成功
            return new WxNotifyVo().setCode("SUCCESS");
        } catch (Exception e) {
            log.error("解析退款通知出错:{}", e.getMessage(), e);
            return new WxNotifyVo().setCode("FAIL").setMessage(e.getMessage());
        }
    }
}

posted on 2024-06-05 10:22  白嫖老郭  阅读(363)  评论(0编辑  收藏  举报

导航