基础配置
maven依赖
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.15</version>
        </dependency>
配置信息
wxpay:
  
  appId: 
  
  mchId:  
  
  key: 
  
  apiV3Key: 
  mchSerialNo: 
  
  keyPath: .../apiclient_cert.p12 
  privateKeyPath: .../apiclient_key.pem 
  privateCertPath: .../apiclient_cert.pem 
  privateKey: 
  publicKeyId: 
  publicKeyPath: .../pub_key.pem 
  
  
  notifyUrl: .../wechat/pay/notify/order 
  
  refundNotifyUrl: .../wechat/pay/wl/refund_notify 
@Data
@Configuration
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {
    private String appId;
    private String mchId;
    private String apiV3Key;
    private String mchSerialNo;
    private String keyPath;
    private String privateKeyPath;
    private String privateCertPath;
    private String notifyUrl;
    private String refundNotifyUrl;
    private String privateKey;
    private String publicKeyId;
    private String publicKeyPath;
}
支付工具类
public class WXPayUtility {
    private static final Gson gson = new GsonBuilder()
            .disableHtmlEscaping()
            .addSerializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                    return expose != null && !expose.serialize();
                }
                @Override
                public boolean shouldSkipClass(Class<?> aClass) {
                    return false;
                }
            })
            .addDeserializationExclusionStrategy(new ExclusionStrategy() {
                @Override
                public boolean shouldSkipField(FieldAttributes fieldAttributes) {
                    final Expose expose = fieldAttributes.getAnnotation(Expose.class);
                    return expose != null && !expose.deserialize();
                }
                @Override
                public boolean shouldSkipClass(Class<?> aClass) {
                    return false;
                }
            })
            .create();
    private static final char[] SYMBOLS =
            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
    private static final SecureRandom random = new SecureRandom();
    
    public static String toJson(Object object) {
        return gson.toJson(object);
    }
    
    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
        return gson.fromJson(json, classOfT);
    }
    
    private static String readKeyStringFromPath(String keyPath) {
        try {
            return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
    
    public static PrivateKey loadPrivateKeyFromString(String keyString) {
        try {
            keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            return KeyFactory.getInstance("RSA").generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException(e);
        }
    }
    
    public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
        return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
    }
    
    public static PublicKey loadPublicKeyFromString(String keyString) {
        try {
            keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
                    .replace("-----END PUBLIC KEY-----", "")
                    .replaceAll("\\s+", "");
            return KeyFactory.getInstance("RSA").generatePublic(
                    new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(e);
        } catch (InvalidKeySpecException e) {
            throw new IllegalArgumentException(e);
        }
    }
    
    public static PublicKey loadPublicKeyFromPath(String keyPath) {
        return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
    }
    
    public static String createNonce(int length) {
        char[] buf = new char[length];
        for (int i = 0; i < length; ++i) {
            buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
        }
        return new String(buf);
    }
    
    public static String encrypt(PublicKey publicKey, String plaintext) {
        final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
        try {
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new IllegalArgumentException("Plaintext is too long", e);
        }
    }
    public static String aesAeadDecrypt(byte[] key, byte[] associatedData, byte[] nonce,
                                        byte[] ciphertext) {
        final String transformation = "AES/GCM/NoPadding";
        final String algorithm = "AES";
        final int tagLengthBit = 128;
        try {
            Cipher cipher = Cipher.getInstance(transformation);
            cipher.init(
                    Cipher.DECRYPT_MODE,
                    new SecretKeySpec(key, algorithm),
                    new GCMParameterSpec(tagLengthBit, nonce));
            if (associatedData != null) {
                cipher.updateAAD(associatedData);
            }
            return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
        } catch (InvalidKeyException
                 | InvalidAlgorithmParameterException
                 | BadPaddingException
                 | IllegalBlockSizeException
                 | NoSuchAlgorithmException
                 | NoSuchPaddingException e) {
            throw new IllegalArgumentException(String.format("AesAeadDecrypt with %s Failed",
                    transformation), e);
        }
    }
    
    public static String sign(String message, String algorithm, PrivateKey privateKey) {
        byte[] sign;
        try {
            Signature signature = Signature.getInstance(algorithm);
            signature.initSign(privateKey);
            signature.update(message.getBytes(StandardCharsets.UTF_8));
            sign = signature.sign();
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
        } catch (SignatureException e) {
            throw new RuntimeException("An error occurred during the sign process.", e);
        }
        return Base64.getEncoder().encodeToString(sign);
    }
    
    public static boolean verify(String message, String signature, String algorithm,
                                 PublicKey publicKey) {
        try {
            Signature sign = Signature.getInstance(algorithm);
            sign.initVerify(publicKey);
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            return sign.verify(Base64.getDecoder().decode(signature));
        } catch (SignatureException e) {
            return false;
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("verify uses an illegal publickey.", e);
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
        }
    }
    
    public static String buildAuthorization(String mchid, String certificateSerialNo,
                                            PrivateKey privateKey,
                                            String method, String uri, String body) {
        String nonce = createNonce(32);
        long timestamp = Instant.now().getEpochSecond();
        String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
                body == null ? "" : body);
        String signature = sign(message, "SHA256withRSA", privateKey);
        return String.format(
                "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
                        "timestamp=\"%d\",serial_no=\"%s\"",
                mchid, nonce, signature, timestamp, certificateSerialNo);
    }
    
    public static String urlEncode(String content) {
        try {
            return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static String urlEncode(Map<String, Object> params) {
        if (params == null || params.isEmpty()) {
            return "";
        }
        int index = 0;
        StringBuilder result = new StringBuilder();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            result.append(entry.getKey())
                    .append("=")
                    .append(urlEncode(entry.getValue().toString()));
            index++;
            if (index < params.size()) {
                result.append("&");
            }
        }
        return result.toString();
    }
    
    public static String extractBody(Response response) {
        if (response.body() == null) {
            return "";
        }
        try {
            BufferedSource source = response.body().source();
            return source.readUtf8();
        } catch (IOException e) {
            throw new RuntimeException(String.format("An error occurred during reading response body. " +
                    "Status: %d", response.code()), e);
        }
    }
    
    public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
                                        Headers headers,
                                        String body) {
        String timestamp = headers.get("Wechatpay-Timestamp");
        String requestId = headers.get("Request-ID");
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
            
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
                throw new IllegalArgumentException(
                        String.format("Validate response failed, timestamp[%s] is expired, request-id[%s]",
                                timestamp, requestId));
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw new IllegalArgumentException(
                    String.format("Validate response failed, timestamp[%s] is invalid, request-id[%s]",
                            timestamp, requestId));
        }
        String serialNumber = headers.get("Wechatpay-Serial");
        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
            throw new IllegalArgumentException(
                    String.format("Validate response failed, Invalid Wechatpay-Serial, Local: %s, Remote: " +
                            "%s", wechatpayPublicKeyId, serialNumber));
        }
        String signature = headers.get("Wechatpay-Signature");
        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
                body == null ? "" : body);
        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
        if (!success) {
            throw new IllegalArgumentException(
                    String.format("Validate response failed,the WechatPay signature is incorrect.%n"
                                    + "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
                            headers.get("Request-ID"), headers, body));
        }
    }
    
    public static void validateNotification(String wechatpayPublicKeyId,
                                            PublicKey wechatpayPublicKey, Headers headers,
                                            String body) {
        String timestamp = headers.get("Wechatpay-Timestamp");
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
            
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
                throw new IllegalArgumentException(
                        String.format("Validate notification failed, timestamp[%s] is expired", timestamp));
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw new IllegalArgumentException(
                    String.format("Validate notification failed, timestamp[%s] is invalid", timestamp));
        }
        String serialNumber = headers.get("Wechatpay-Serial");
        if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
            throw new IllegalArgumentException(
                    String.format("Validate notification failed, Invalid Wechatpay-Serial, Local: %s, " +
                                    "Remote: %s",
                            wechatpayPublicKeyId,
                            serialNumber));
        }
        String signature = headers.get("Wechatpay-Signature");
        String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
                body == null ? "" : body);
        boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
        if (!success) {
            throw new IllegalArgumentException(
                    String.format("Validate notification failed, WechatPay signature is incorrect.\n"
                                    + "responseHeader[%s]\tresponseBody[%.1024s]",
                            headers, body));
        }
    }
    
    public static Notification parseNotification(String apiv3Key, String wechatpayPublicKeyId,
                                                 PublicKey wechatpayPublicKey, Headers headers,
                                                 String body) {
        validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body);
        Notification notification = gson.fromJson(body, Notification.class);
        notification.decrypt(apiv3Key);
        return notification;
    }
    
    public static class ApiException extends RuntimeException {
        private static final long serialVersionUID = 2261086748874802175L;
        private final int statusCode;
        private final String body;
        private final Headers headers;
        private final String errorCode;
        private final String errorMessage;
        public ApiException(int statusCode, String body, Headers headers) {
            super(String.format("微信支付API访问失败,StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode,
                    body, headers));
            this.statusCode = statusCode;
            this.body = body;
            this.headers = headers;
            if (body != null && !body.isEmpty()) {
                JsonElement code;
                JsonElement message;
                try {
                    JsonObject jsonObject = gson.fromJson(body, JsonObject.class);
                    code = jsonObject.get("code");
                    message = jsonObject.get("message");
                } catch (JsonSyntaxException ignored) {
                    code = null;
                    message = null;
                }
                this.errorCode = code == null ? null : code.getAsString();
                this.errorMessage = message == null ? null : message.getAsString();
            } else {
                this.errorCode = null;
                this.errorMessage = null;
            }
        }
        
        public int getStatusCode() {
            return statusCode;
        }
        
        public String getBody() {
            return body;
        }
        
        public Headers getHeaders() {
            return headers;
        }
        
        public String getErrorCode() {
            return errorCode;
        }
        
        public String getErrorMessage() {
            return errorMessage;
        }
    }
    public static class Notification {
        @SerializedName("id")
        private String id;
        @SerializedName("create_time")
        private String createTime;
        @SerializedName("event_type")
        private String eventType;
        @SerializedName("resource_type")
        private String resourceType;
        @SerializedName("summary")
        private String summary;
        @SerializedName("resource")
        private Resource resource;
        private String plaintext;
        public String getId() {
            return id;
        }
        public String getCreateTime() {
            return createTime;
        }
        public String getEventType() {
            return eventType;
        }
        public String getResourceType() {
            return resourceType;
        }
        public String getSummary() {
            return summary;
        }
        public Resource getResource() {
            return resource;
        }
        
        public String getPlaintext() {
            return plaintext;
        }
        private void validate() {
            if (resource == null) {
                throw new IllegalArgumentException("Missing required field `resource` in notification");
            }
            resource.validate();
        }
        
        private void decrypt(String apiv3Key) {
            validate();
            plaintext = aesAeadDecrypt(
                    apiv3Key.getBytes(StandardCharsets.UTF_8),
                    resource.associatedData.getBytes(StandardCharsets.UTF_8),
                    resource.nonce.getBytes(StandardCharsets.UTF_8),
                    Base64.getDecoder().decode(resource.ciphertext)
            );
        }
        public static class Resource {
            @SerializedName("algorithm")
            private String algorithm;
            @SerializedName("ciphertext")
            private String ciphertext;
            @SerializedName("associated_data")
            private String associatedData;
            @SerializedName("nonce")
            private String nonce;
            @SerializedName("original_type")
            private String originalType;
            public String getAlgorithm() {
                return algorithm;
            }
            public String getCiphertext() {
                return ciphertext;
            }
            public String getAssociatedData() {
                return associatedData;
            }
            public String getNonce() {
                return nonce;
            }
            public String getOriginalType() {
                return originalType;
            }
            private void validate() {
                if (algorithm == null || algorithm.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `algorithm` in Notification" +
                            ".Resource");
                }
                if (!Objects.equals(algorithm, "AEAD_AES_256_GCM")) {
                    throw new IllegalArgumentException(String.format("Unsupported `algorithm`[%s] in " +
                            "Notification.Resource", algorithm));
                }
                if (ciphertext == null || ciphertext.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `ciphertext` in Notification" +
                            ".Resource");
                }
                if (associatedData == null || associatedData.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `associatedData` in " +
                            "Notification.Resource");
                }
                if (nonce == null || nonce.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `nonce` in Notification" +
                            ".Resource");
                }
                if (originalType == null || originalType.isEmpty()) {
                    throw new IllegalArgumentException("Missing required field `originalType` in " +
                            "Notification.Resource");
                }
            }
        }
    }
}
支付实现
支付方法实现
@Slf4j
@Component
public class WxPayManage {
    @Autowired
    private WechatPayConfig wechatPayConfig;
    private static Config config;
    @Autowired
    private IEsOrderService orderService;
    
    @PostConstruct
    public void configInit() {
        try {
            config = new RSAPublicKeyConfig.Builder()
                    .merchantId(wechatPayConfig.getMchId())
                    .privateKeyFromPath(wechatPayConfig.getPrivateKeyPath())
                    .publicKeyFromPath(wechatPayConfig.getPublicKeyPath())
                    .publicKeyId(wechatPayConfig.getPublicKeyId())
                    .merchantSerialNumber(wechatPayConfig.getMchSerialNo())
                    .apiV3Key(wechatPayConfig.getApiV3Key())
                    .build();
            log.info("微信支付配置初始化完成");
        } catch (Exception e) {
            log.error("微信支付配置初始化失败",e);
        }
    }
    
    public void payNotifyV3(HttpServletRequest request, HttpServletResponse response) {
        log.info("收到app微信支付回调....");
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        String jsonResponse = "{\"code\": \"FAIL\", \"message\": \"失败\"}";
        try {
            
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String signature = request.getHeader("Wechatpay-Signature");
            String singType = request.getHeader("Wechatpay-Signature-Type");
            String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(wechatPayCertificateSerialNumber)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .signType(singType)
                    .body(getRequestBody(request))
                    .build();
            
            NotificationParser parser = new NotificationParser((NotificationConfig) config);
            Transaction decryptObject = parser.parse(requestParam, Transaction.class);
            if (decryptObject != null) {
                
                log.info("=============微信支付回调========trade_status:" + decryptObject.getTradeState());
                if (Transaction.TradeStateEnum.SUCCESS.equals(decryptObject.getTradeState())) {
                    
                    try {
                        
                        System.out.println("微信返回参数+++++++++++++++++++++++++++:" + decryptObject);
                        
                        EsOrder order = orderService.selectEsOrderByOrderNo(decryptObject.getOutTradeNo());
                        if(order != null){
                            order.setPaymentStatus(PaymentStatus.FULL_PAYMENT.getCode());
                            order.setPaidAmount(order.getOrderPrice());
                            if(order.getOrderStatus().equals(OrderStatus.WAIT_CONSULT.getCode())){
                                order.setOrderStatus(OrderStatus.IN_SERVICE.getCode());
                            }
                            order.setPaymentTime(new Date());
                            orderService.updateEsOrder(order);
                        }
                        
                        response.setStatus(200);
                        response.getWriter().flush();
                        response.getWriter().close();
                    } catch (Exception e) {
                        log.error("微信回调处理失败:" + e.getMessage());
                    }
                } else {
                    log.info("更新微信支付数据失败。");
                }
            }
            response.setStatus(500);
            response.getWriter().write(jsonResponse);
        } catch (Exception e) {
            log.error("微信支付回调处理异常: {}", e.getMessage());
            try {
                response.setStatus(500);
                response.getWriter().write(jsonResponse);
                response.getWriter().flush();
                response.getWriter().close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    
    private String getRequestBody(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("读取请求体时发生异常:" + e.getMessage());
        }
        return sb.toString();
    }
    
    public Map<String, String> prepay(String orderNumber, BigDecimal orderPrice, String goodsName, String miniOpenid) {
        PrepayRequest request1 = new PrepayRequest();
        Amount amount1 = new Amount();
        amount1.setTotal(orderPrice.multiply(BigDecimal.valueOf(100)).intValue());
        request1.setAmount(amount1);
        request1.setAppid(wechatPayConfig.getAppId());
        request1.setMchid(wechatPayConfig.getMchId());
        request1.setDescription(goodsName);
        request1.setNotifyUrl(wechatPayConfig.getNotifyUrl());
        request1.setOutTradeNo(orderNumber);
        Payer payer1 = new Payer();
        payer1.setOpenid(miniOpenid);
        request1.setPayer(payer1);
        JsapiService service  =  new JsapiService.Builder().config(config).build();
        try {
            System.out.println("请求参数-------"+JSONUtil.toJsonStr(request1));
            PrepayResponse response = service.prepay(request1);
            
            Map<String,String> signMap = new HashMap<>();
            signMap.put("appId",wechatPayConfig.getAppId());
            signMap.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
            signMap.put("nonceStr", WXPayUtility.createNonce(32));
            signMap.put("package","prepay_id=" + response.getPrepayId());
            signMap.put("signType","RSA");
            signMap.put("paySign",sign(signMap));
            return signMap;
        } catch (Exception e) {
            throw new ServiceException("支付错误" + e.getMessage());
        }
    }
    public String sign(Map<String,String> signMap){
        String signStr = signMap.get("appId") +"\n" + signMap.get("timeStamp")  +"\n" + signMap.get("nonceStr")  +"\n" + signMap.get("package")  +"\n";
        PrivateKey privateKey = PemUtil.loadPrivateKeyFromPath( wechatPayConfig.getPrivateKeyPath());
        return WXPayUtility.sign(signStr,"SHA256withRSA",privateKey);
    }
    
    public Refund refund(String orderNumber , BigDecimal orderPrice , BigDecimal returnPrice) {
        RefundService service = new RefundService.Builder().config(config).build();
        CreateRequest request = new CreateRequest();
        
        AmountReq amountReq = new AmountReq();
        
        returnPrice = orderPrice.min(returnPrice);
        long returnPriceFen = returnPrice
                .multiply(new BigDecimal("100"))
                .setScale(0, RoundingMode.DOWN)
                .longValueExact();
        amountReq.setRefund(returnPriceFen);
        
        long orderPriceFen = orderPrice
                .multiply(new BigDecimal("100"))
                .setScale(0, RoundingMode.DOWN)
                .longValueExact();
        amountReq.setTotal(orderPriceFen);
        
        amountReq.setCurrency("CNY");
        request.setAmount(amountReq);
        
        request.setOutRefundNo(orderNumber);
        
        request.setOutTradeNo(orderNumber);
        
        request.setNotifyUrl(wechatPayConfig.getRefundNotifyUrl());
        try {
            System.out.println("退款请求参数-------"+JSONUtil.toJsonStr(request));
            return service.create(request);
        } catch (ServiceException e) {
            throw new RuntimeException("退款申请失败", e);
        }
    }
    
    public void refundNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("收到微信支付退款回调....");
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        String jsonResponse = "{\"code\": \"FAIL\", \"message\": \"失败\"}";
        try {
            
            String timestamp = request.getHeader("Wechatpay-Timestamp");
            String nonce = request.getHeader("Wechatpay-Nonce");
            String signature = request.getHeader("Wechatpay-Signature");
            String singType = request.getHeader("Wechatpay-Signature-Type");
            String wechatPayCertificateSerialNumber = request.getHeader("Wechatpay-Serial");
            RequestParam requestParam = new RequestParam.Builder()
                    .serialNumber(wechatPayCertificateSerialNumber)
                    .nonce(nonce)
                    .signature(signature)
                    .timestamp(timestamp)
                    .signType(singType)
                    .body(getRequestBody(request))
                    .build();
            NotificationParser parser = new NotificationParser((NotificationConfig) config);
            RefundNotification refundNotification = parser.parse(requestParam, RefundNotification.class);
            String refundStatus = refundNotification.getRefundStatus().toString();
            String outTradeNo = refundNotification.getOutTradeNo();
            log.info("=============微信支付退款回调========trade_status:" + refundStatus);
            if ("SUCCESS".equals(refundStatus)){
                try {
                    System.out.println("微信退款返回参数+++++++++++++++++++++++++++:" + refundNotification);
                    
                    EsOrder order = orderService.selectEsOrderByOrderNo(outTradeNo);
                    if(order != null){
                        order.setAfterSaleStatus(AfterSaleStatus.REFUNDED.getCode());
                        orderService.updateEsOrder(order);
                    }
                    
                    response.setStatus(200);
                    response.getWriter().flush();
                    response.getWriter().close();
                } catch (Exception e) {
                    log.error("微信退款回调处理失败:" + e.getMessage());
                }
            }else{
                EsOrder order = orderService.selectEsOrderByOrderNo(outTradeNo);
                if(order != null){
                    order.setAfterSaleStatus(AfterSaleStatus.AUDIT_PASSED.getCode());
                    orderService.updateEsOrder(order);
                }
                log.info("更新微信支付退款数据失败。");
            }
            response.setStatus(500);
            response.getWriter().write(jsonResponse);
        } catch (Exception e) {
            log.error("微信支付退款回调处理异常: {}", e.getMessage());
            try {
                response.setStatus(500);
                response.getWriter().write(jsonResponse);
                response.getWriter().flush();
                response.getWriter().close();
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
}
回调地址
@Slf4j
@CrossOrigin
@RestController
@AllArgsConstructor
@RequestMapping("/wechat/pay")
public class PayNotifyController {
    WxPayManage wxPayManage;
    @ApiOperation("支付回调通知处理")
    @PostMapping("/notify/order")
    public void parseOrderNotifyResult(HttpServletRequest request, HttpServletResponse response) {
         wxPayManage.payNotifyV3(request,response);
    }
    @ApiOperation("支付退款回调通知处理")
    @PostMapping("/wl/refund_notify")
    public void parseRefundResult(HttpServletRequest request, HttpServletResponse response) {
        wxPayManage.refundNotify(request,response);
    }
}
业务调用
调用支付
    @ApiOperation("提交订单-支付")
    @PostMapping("/prepay/{id}")
    public AjaxResult prepay(@PathVariable("id") Long id) {
				.....
        try {
            ....
            
            Map<String, String> signMap = wxPayManage.prepay(...);
            ...
            return AjaxResult.success(signMap);
        } catch (Exception e) {
            throw new ServiceException("支付错误" + e.getMessage());
        }
    }
调用退款
    @Log(title = "售后订单退款")
    @PostMapping("/refund")
    public AjaxResult updateAfterSalesRefund(...) {
        ...
        
        Refund refund = adminWxPayManage.refund(...);
        return AjaxResult.success(refund);
    }