首页 > 其他分享 >SpringBoot+微信支付-JSAPI{微信支付回调}

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

时间:2024-06-05 10:34:51浏览次数:14  
标签:xxxx String 微信 JSAPI 支付 request import new

引入微信支付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());
        }
    }
}

标签:xxxx,String,微信,JSAPI,支付,request,import,new
From: https://www.cnblogs.com/gtnotgod/p/18232453

相关文章

  • 企业微信群发功能:提升沟通效率,助力企业营销
    随着信息技术的快速发展,企业微信作为企业内部沟通和企业对外营销的重要工具,其功能也在不断更新和完善。其中,企业微信的多次群发功能,不仅极大地提升了企业内部的沟通效率,更为企业的对外营销提供了强有力的支持。本文将从多个角度探讨企业微信群发功能的应用与价值。一、群发功......
  • 企业微信自动群发工具:高效沟通的得力助手
    随着企业数字化转型的加速,企业微信作为企业内部沟通的重要工具,其功能和效率成为了企业关注的焦点。在众多功能中,自动群发工具凭借其高效、便捷的特点,成为了企业沟通的新宠。本文将详细介绍企业微信自动群发工具的特点、应用场景及其在企业运营中的重要作用。一、自动群发工具......
  • 基于微信小程序的健身小助手打卡预约教学系统(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示项目运行截图技术框架后端采用SpringBoot框架前端框架Vue可行性分析系统测试系统测试的目的系统功能测试数据库表设计代码参考数据库脚本为什么选择我?获取源码前言......
  • SpringBoot+微信支付-JSAPI
    引入微信支付SDKMaven:com.github.wechatpay-apiv3:wechatpay-java-core:0.2.12Maven:com.github.wechatpay-apiv3:wechatpay-java:0.2.12代码示例packagexxxx.cashier.payChannel.handler;importxxxx.common.domain.model.exception.BusinessException;importxxxx.c......
  • 健身管理小程序|基于微信开发健身管理小程序的系统设计与实现(源码+数据库+文档)
    健身管理小程序目录基于微信开发健身管理小程序设计与实现一、前言二、系统设计三、系统功能设计 小程序端:后台四、数据库设计 五、核心代码 六、论文参考七、最新计算机毕设选题推荐八、源码获取:博主介绍:✌️大厂码农|毕设布道师,阿里云开发社区乘风者计划专......
  • Manjaro Linux下使用wine运行企业微信
    开始因为工作原因所以必须用到企业微信,但是企业微信是目前主流办公软件中可能唯一一个不支持Linux系统的软件了,于是只能使用wine来运行windows版本来使用。期间遇到了各种坑在此记录一下,以便于能帮到同样需要的同学,也希望腾讯能够尽快开发Linux原生的企业微信。安装首先我们需......
  • 易支付系统新版【2024.5.27】正版【部署简单直接】
    今天我将详细介绍如何对接拉卡拉聚合收银台支付,并指出其中应注意的点。我希望这篇文章能够帮助那些正在寻找如何实现这个功能的开发者。一、拉卡拉聚合收银台支付简介拉卡拉聚合收银台支付是一种整合了多种支付方式的支付服务,包括但不限于微信支付、支付宝支付、银联支付等。......
  • wechat 微信扫码登录
    一、申请应用注册登录创建网站应用填写应用信息[官网]1、注意点#1、应用官网地址(域名、域名/子目录,例如:www.baidu.com、www.baidu.com/fanyi)。#2、登记表需与网站应用填写一致(登记表有说明)。授权回调域注意(只需填域名,无需子目录)二、应用对接任选其中......
  • 基于微信小程序的门票预定系统
    随着网络的出现,网页逐渐融入人们的生活。它打破了地域限制,真正使信息得以共享,改变了人们的工作和生活方式。信息的来源是多元化的,而互联网作为信息传播的快速渠道,我们应当充分利用其优势进行科研信息的互动,以期促进科研工作的顺利开展,大力满足知识创新的需求。因此,建立高校科研......
  • uniapp的h5和微信小程序判断用户设备的位置服务是否开启
    //判断用户是否开启定位-暂时不用 checkOpenGPSServiceByAndroid(){ letthat=this //1、判断手机定位服务【GPS】是否授权 uni.getSystemInfo({ success(res){ //console.log(res) letlocationEnabled=res.locationEnabled;//判断手......