Pu Zhibing
2025-04-01 8444084e6aa11efa23287e7f82474ac22378a5c4
ruoyi-modules/ruoyi-order/src/main/java/com/ruoyi/order/controller/miniapp/NotifyController.java
@@ -1,38 +1,52 @@
package com.ruoyi.order.controller.miniapp;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.ecommerce.*;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
import com.github.binarywang.wxpay.bean.profitsharingV3.ProfitSharingNotifyData;
import com.github.binarywang.wxpay.bean.profitsharingV3.ProfitSharingNotifyResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.ProfitSharingV3Service;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.auth.Verifier;
import com.github.binarywang.wxpay.v3.util.AesUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.order.domain.dto.AppSureOrderDto;
import com.ruoyi.order.domain.dto.WXPayNotifyDto;
import com.ruoyi.order.domain.vo.AppSureOrderVo;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.order.domain.dto.WechatPaymentRefundDto;
import com.ruoyi.order.domain.dto.WeixinPaymentNotifyDto;
import com.ruoyi.order.domain.pojo.account.OrderRefund;
import com.ruoyi.order.domain.vo.ProfitSharingNotifyNewResult;
import com.ruoyi.order.domain.vo.WeixinPaymentNotifyVo;
import com.ruoyi.order.domain.vo.WeixinPaymentRefundVo;
import com.ruoyi.order.enums.RefundStatusEnum;
import com.ruoyi.order.enums.WxPayNotifyEventTypeEnum;
import com.ruoyi.order.enums.dict.IDict;
import com.ruoyi.order.service.account.BackMessageService;
import com.ruoyi.order.service.account.OrderRefundService;
import com.ruoyi.order.service.order.OrderService;
import com.wechat.pay.contrib.apache.httpclient.notification.Notification;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler;
import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.RSAConfig;
import com.wechat.pay.java.core.cipher.PrivacyEncryptor;
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.partnerpayments.jsapi.model.Transaction;
import com.ruoyi.order.util.HuiFuTianXiaUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import net.bytebuddy.asm.Advice;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
 * @program: ruoyi
@@ -43,90 +57,254 @@
@Api(value = "微信通知控制", tags = "微信通知控制", description = "微信通知控制")
@RestController
@RequestMapping("/app/notify")
@Log4j2
public class NotifyController extends BaseController {
    /** 商户号 */
    public static String merchantId = "";
    /** 商户API私钥路径 */
    public static String privateKeyPath = "";
    /** 商户证书序列号 */
    public static String merchantSerialNumber = "";
    /** 微信支付平台证书路径 */
    public static String wechatPayCertificatePath = "";
    /** 商户APIV3密钥 */
    public static String apiV3key = "...";
    public static final String WECHAT_PAY_SERIAL = "Wechatpay-Serial";
    public static final String WECHAT_PAY_SIGNATURE = "Wechatpay-Signature";
    public static final String WECHAT_PAY_TIMESTAMP = "Wechatpay-Timestamp";
    public static final String WECHAT_PAY_NONCE = "Wechatpay-Nonce";
    private static final Gson GSON = new GsonBuilder().create();
    @Resource
    private OrderService orderService;
    @RequestMapping(value = "/payNotify", method = RequestMethod.POST)
    @ApiOperation(value = "微信支付通知")
    public Map payNotify(HttpServletRequest request, HttpServletResponse response) {
        Config config =
                new RSAConfig.Builder()
                        .merchantId(merchantId)
                        .privateKeyFromPath(privateKeyPath)
                        .merchantSerialNumber(merchantSerialNumber)
                        .wechatPayCertificatesFromPath(wechatPayCertificatePath)
                        .build();
    @Resource
    private WxPayService wxService;
    @Resource
    private BackMessageService backMessageService;
        PrivacyEncryptor encryptor = config.createEncryptor();
        String wechatPayCertificateSerialNumber = encryptor.getWechatpaySerial();
    @Resource
    private OrderRefundService orderRefundService;
    @Resource
    private RedisService redisService;
        String nonoc=request.getHeader("Wechatpay-Nonce");
        String signature=request.getHeader("Wechatpay-Signature");
        String timestamp=request.getHeader("Wechatpay-Timestamp");
        String serial=request.getHeader("Wechatpay-Serial");
        String signatureType=request.getHeader("Wechatpay-Signature-Type");
    @PostMapping(value = "/payNotify")
    @ApiOperation(value = "微信支付/退款通知")
    public String payNotify(@RequestBody String notifyData, HttpServletRequest request, HttpServletResponse response) throws WxPayException {
        String requestBody="";
        // 获取请求头
        SignatureHeader signatureHeader = getSignatureHeader(request);
        log.info("微信支付/退款通知:  {}", notifyData);
        NotifyResponse responseData = GSON.fromJson(notifyData, NotifyResponse.class);
        String eventType = responseData.getEventType();
        int resultType = 1;
        String resultMessage = notifyData;
        try {
            BufferedReader reader = request.getReader();
            String line = "";
            StringBuffer inputString = new StringBuffer();
            while ( (line = reader.readLine()) != null ) {
                inputString.append(line);
            if (WxPayNotifyEventTypeEnum.TRANSACTION_SUCCESS.getCode().equals(eventType)) {
                PartnerTransactionsNotifyResult notifyResult = wxService.getEcommerceService().parsePartnerNotifyResult(notifyData, signatureHeader);
                PartnerTransactionsResult result = notifyResult.getResult();
                // 支付通知回调
                if ("SUCCESS".equals(result.getTradeState())) {
                    orderService.payBack(result, BigDecimal.ONE);
                }
                resultMessage = GSON.toJson(notifyResult);
            } else if (WxPayNotifyEventTypeEnum.REFUND_SUCCESS.getCode().equals(eventType)
                    || WxPayNotifyEventTypeEnum.REFUND_ABNORMAL.getCode().equals(eventType)
                    || WxPayNotifyEventTypeEnum.REFUND_CLOSED.getCode().equals(eventType)) {
                RefundNotifyResult result = this.parseRefundNotifyResult(notifyData, signatureHeader);
                orderService.orderRefundBack(result);
                resultType = 2;
                resultMessage = GSON.toJson(result);
            }
            if(inputString!=null && !"".equals(inputString)) {
                requestBody = inputString.toString();
            }
            reader.close();
        } catch (IOException e) {
        }catch (Exception e){
            e.printStackTrace();
            log.info("微信支付/退款通知异常:  {}", e.getMessage());
        }
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(wechatPayCertificateSerialNumber)
                .nonce(nonoc)
                .signature(signature)
                .timestamp(timestamp)
                .body(requestBody)
                .build();
        NotificationConfig notifiConfig = new RSAAutoCertificateConfig.Builder()
                .merchantId(merchantId)
                .privateKeyFromPath(privateKeyPath)
                .merchantSerialNumber(merchantSerialNumber)
                .apiV3Key(apiV3key)
                .build();
        NotificationParser parser = new NotificationParser(notifiConfig);
        Transaction transaction = parser.parse(requestParam, Transaction.class);
        String tradeStateEnum=transaction.getTradeState().toString();
        if(tradeStateEnum.equals("success")){
            String outTradeNo=transaction.getOutTradeNo();
            orderService.payBack(outTradeNo);
        }
        Map map=new HashMap();
        map.put("code","SUCCESS");
        map.put("message","成功");
        return map;
        // 保存支付/退款回调信息
        backMessageService.saveBackMessage(resultType, resultMessage);
        return WxPayNotifyV3Response.success("成功");
    }
    @PostMapping(value = "/profitSharingNotify")
    @ApiOperation(value = "微信分账通知")
    public String profitSharingNotify(@RequestBody String notifyData, HttpServletRequest request, HttpServletResponse response) throws WxPayException {
        ProfitSharingV3Service sharingV3Service = wxService.getProfitSharingV3Service();
        // ProfitSharingNotifyResult
        log.info("微信分账通知:  {}", notifyData);
        // 获取请求头
        SignatureHeader signatureHeader = getSignatureHeader(request);
        ProfitSharingNotifyNewResult notifyResult = getProfitSharingNotifyData(notifyData, signatureHeader);
        ProfitSharingNotifyResult result = notifyResult.getResult();
        String eventType = "-1";
        String resultMessage = notifyData;
        try {
            eventType = notifyResult.getRawData().getEventType();
            if (WxPayNotifyEventTypeEnum.PROFIT_SHARING_SUCCESS.getCode().equals(eventType)) {
                // 分账通知回调
                orderService.profitSharingBack(result);
            }
            resultMessage = GSON.toJson(notifyResult);
        }catch (Exception e){
            log.info("微信分账通知通知异常:  {}", e.getMessage());
        }
        // 保存记录分账回调信息
        backMessageService.saveBackMessage(3, resultMessage);
        return WxPayNotifyV3Response.success("成功");
    }
    @RequestMapping(value = "/shareNotify", method = RequestMethod.POST)
    @ApiOperation(value = "微信支付通知")
    public Map shareNotify(HttpServletRequest request, HttpServletResponse response) {
        return null;
    }
    public ProfitSharingNotifyNewResult getProfitSharingNotifyData(String notifyData, SignatureHeader header) throws WxPayException {
        if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) {
            throw new WxPayException("非法请求,头部信息验证失败");
        }
        ProfitSharingNotifyData response = GSON.fromJson(notifyData, ProfitSharingNotifyData.class);
        ProfitSharingNotifyData.Resource resource = response.getResource();
        String cipherText = resource.getCipherText();
        String associatedData = resource.getAssociatedData();
        String nonce = resource.getNonce();
        String apiV3Key = this.wxService.getConfig().getApiV3Key();
        try {
            String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
            ProfitSharingNotifyResult profitSharingResult = GSON.fromJson(result, ProfitSharingNotifyResult.class);
            ProfitSharingNotifyNewResult notifyResult = new ProfitSharingNotifyNewResult();
            notifyResult.setRawData(response);
            notifyResult.setResult(profitSharingResult);
            return notifyResult;
        } catch (GeneralSecurityException | IOException e) {
            throw new WxPayException("解析报文异常!", e);
        }
    }
    private RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
        NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class);
        NotifyResponse.Resource resource = response.getResource();
        String cipherText = resource.getCiphertext();
        String associatedData = resource.getAssociatedData();
        String nonce = resource.getNonce();
        String apiV3Key = this.wxService.getConfig().getApiV3Key();
        try {
            String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
            RefundNotifyResult notifyResult = GSON.fromJson(result, RefundNotifyResult.class);
            notifyResult.setRawData(response);
            return notifyResult;
        } catch (GeneralSecurityException | IOException e) {
            throw new WxPayException("解析报文异常!", e);
        }
    }
    private boolean verifyNotifySign(SignatureHeader header, String data) throws WxPayException {
        String beforeSign = String.format("%s\n%s\n%s\n",
                header.getTimeStamp(),
                header.getNonce(),
                data);
        Verifier verifier = this.wxService.getConfig().getVerifier();
        if (verifier == null) {
            throw new WxPayException("证书检验对象为空");
        }
        return verifier.verify(header.getSerialNo(),
                beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
    }
    private SignatureHeader getSignatureHeader(HttpServletRequest request){
        SignatureHeader signatureHeader = new SignatureHeader();
        signatureHeader.setSerialNo(request.getHeader(WECHAT_PAY_SERIAL));
        signatureHeader.setSigned(request.getHeader(WECHAT_PAY_SIGNATURE));
        signatureHeader.setNonce(request.getHeader(WECHAT_PAY_NONCE));
        signatureHeader.setTimeStamp(request.getHeader(WECHAT_PAY_TIMESTAMP));
        log.info("timeStamp:{}   nonce : {}   signed:{}  serialNo:{}",
                signatureHeader.getTimeStamp(),
                signatureHeader.getNonce(),
                signatureHeader.getSigned(),
                signatureHeader.getSerialNo());
        return signatureHeader;
    }
    @RequestMapping(value = "/wechatPaymentCallback", method = RequestMethod.POST)
    @ApiOperation(value = "微信支付通知(汇付天下)")
    public void wechatPaymentCallback(WeixinPaymentNotifyDto dto, HttpServletResponse response){
        R<WeixinPaymentNotifyVo> r = HuiFuTianXiaUtil.weixinPaymentNotify(dto);
        if(r.getCode() == 200){
            WeixinPaymentNotifyVo data = r.getData();
            String transStat = data.getTransStat();
            if("S".equals(transStat)){
                PartnerTransactionsResult transaction = new PartnerTransactionsResult();
                transaction.setOutTradeNo(data.getReqSeqId());
                //防止回调频繁导致时间处理异常
                Object cacheObject = redisService.getCacheObject(data.getReqSeqId());
                if(null == cacheObject){
                    redisService.setCacheObject(data.getReqSeqId(), "", 10L, TimeUnit.SECONDS);
                }else{
                    return;
                }
                //开始处理业务数据
                orderService.payBack(transaction, data.getFeeFlag() == 1 ? BigDecimal.ZERO : new BigDecimal(data.getFeeAmount()));
                response.setStatus(200);
                PrintWriter out = null;
                try {
                    out = response.getWriter();
                    out.print("RECV_ORD_ID_" + data.getReqSeqId());
                    out.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    out.close();
                }
            }
        }
    }
    @RequestMapping(value = "/wechatPaymentRefundCallback", method = RequestMethod.POST)
    @ApiOperation(value = "微信支付退款通知(汇付天下)")
    public void wechatPaymentRefundCallback(WechatPaymentRefundDto dto, HttpServletResponse response){
        R<WeixinPaymentRefundVo> r = HuiFuTianXiaUtil.weixinPaymentRefundNotify(dto);
        if(r.getCode() == 200){
            WeixinPaymentRefundVo data = r.getData();
            String transStat = data.getTransStat();
            if("S".equals(transStat)){
                //开始处理业务数据
                String reqSeqId = data.getReqSeqId();
                OrderRefund orderRefund = orderRefundService.getById(reqSeqId);
                if(null != orderRefund && orderRefund.getRefundStatus() != 2){
                    orderRefund.setWxRefundId(data.getPartyOrderId());
                    orderRefund.setBackTime(data.getTransDate() + data.getTransTime());
                    orderRefund.setRefundStatus(2);
                    orderRefundService.saveOrUpdate(orderRefund);
                }
                response.setStatus(200);
                PrintWriter out = null;
                try {
                    out = response.getWriter();
                    out.print("RECV_ORD_ID_" + data.getReqSeqId());
                    out.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    out.close();
                }
            }
        }
    }
}