微信支付APlv3--java版

211 阅读11分钟

之前做了微信商城小程序,第二次接触微信支付,第一次是大学,这次使用新版lv3支付方式。刚开始只做了jsapi支付,后来pc端需要扫描支付,遇到坑了,接触native支付才发现共用点。 支付和退款是分开的两个对象,发现每种支付的查询接口和关闭订单接口是相同的。所以将查询和关闭又单独提了出来。 最终改成了以下的设计

image.png 1.支付基础父类

package com.ruoyi.web.pay.base;

import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.Constant;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * @author sk
 * @date 2024/10/28
 * @description 微信支付基础父类
 */
public abstract class BaseSerive {

    public static final Logger log = LoggerFactory.getLogger(BaseSerive.class);

    public WeChatProperties properties;
    public NotificationParser notificationParser;

    public BaseSerive(WeChatProperties properties, NotificationParser notificationParser) {
        this.properties = properties;
        this.notificationParser = notificationParser;
    }

    /**
     * 解析支付回调参数
     * @param request
     * @return
     */
    public <T> R<T> parse(HttpServletRequest request, Class<T> clazz) {
        String body = getBody(request);
        String timestamp = request.getHeader(Constant.WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(Constant.WECHAT_PAY_NONCE);
        String serialNo = request.getHeader(Constant.WECHAT_PAY_SERIAL);
        String signature = request.getHeader(Constant.WECHAT_PAY_SIGNATURE);
        RequestParam requestParam = new RequestParam.Builder()
                .serialNumber(serialNo)
                .nonce(nonce)
                .signature(signature)
                .timestamp(timestamp)
                .body(body)
                .build();
        try {
            // 以支付通知回调为例,验签、解密并转换成 Transaction
            T object = notificationParser.parse(requestParam, clazz);
            return R.ok(object);
        } catch (ValidationException e) {
            return R.fail(500, e.getMessage());
        }
    }

    public String getBody(HttpServletRequest request){
        try(ServletInputStream inputStream = request.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));){
            StringBuffer stringBuffer = new StringBuffer();
            String s;
            //读取回调请求体
            while ((s = bufferedReader.readLine()) != null) {
                stringBuffer.append(s);
            }
            return stringBuffer.toString();
        }catch (Exception e){
            log.error("解析请求失败:"+e.getMessage());
        }
        return null;
    }
}

2.配置类

package com.ruoyi.web.pay.configuration;

import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaySerive;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.ruoyi.web.pay.service.WeChatRefundService;
import com.ruoyi.web.pay.service.impl.*;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.refund.RefundService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.util.List;

/**
 * @author sk
 * @date 2024/10/28
 * @description 微信支付配置类
 */
@Configuration
@RequiredArgsConstructor
public class PayConfiguration {

    @Bean
    public JsapiServiceExtension jsapiServiceExtension(RSAAutoCertificateConfig config) {
        return new JsapiServiceExtension.Builder().config(config).build();
    }

    /**
     * jsapi支付
     * @param weChatProperties
     * @return
     * @throws IOException
     */
    @Bean
    public JsapiPaySerive jsapiPaySerive(WeChatProperties weChatProperties, JsapiServiceExtension jsapiServiceExtension) throws IOException {
        return new JsapiPaySerive(weChatProperties,jsapiServiceExtension);
    }

    @Bean
    public NativePayService nativePayService(RSAAutoCertificateConfig config) {
        return new NativePayService.Builder().config(config).build();
    }

    /**
     * native支付
     * @param weChatProperties
     * @return
     * @throws IOException
     */
    @Bean
    public NativePaySerive nativePaySerive(WeChatProperties weChatProperties,NativePayService nativePayService) throws IOException {
        return new NativePaySerive(weChatProperties,nativePayService);
    }

    /**
     * 微信订单查询对象
     * @param config
     * @return
     * @throws IOException
     */
    @Bean
    public QuerySerive querySerive(RSAAutoCertificateConfig config) throws IOException {
        return new QuerySerive(config);
    }

    /**
     * 支付对象
     * @param weChatProperties 参数对象
     * @param notificationParser 解密对象
     * @param querySerive   查询对象
     * @param seriveList   查询对象
     * @return
     * @throws IOException
     */
    @Bean
    public WeChatPaySerive weChatPaySerive(WeChatProperties weChatProperties,
                                           NotificationParser notificationParser,
                                           QuerySerive querySerive,
                                           List<WeChatPaymentSerive> seriveList) throws IOException {
        return new WeChatPaySeriveImpl(weChatProperties,notificationParser,querySerive,seriveList);
    }

    @Bean
    public RefundService refundService(RSAAutoCertificateConfig config) {
        return new com.wechat.pay.java.service.refund.RefundService.Builder().config(config).build();
    }

    /**
     * 微信退款业务对象
     * @param weChatProperties
     * @param refundService
     * @return
     */
    @Bean
    public WeChatRefundService weChatRefundServiceImpl(WeChatProperties weChatProperties,
                                                       RefundService refundService,
                                                       NotificationParser notificationParser) {
        return new WeChatRefundServiceImpl(weChatProperties,refundService,notificationParser);
    }
}
package com.ruoyi.web.pay.configuration;

import com.ruoyi.web.pay.properties.WeChatProperties;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.cert.*;

/**
 * @author sk
 * @date 2024/10/23
 * @description 微信支付基础配置类
 * 配置共用的证书解析对象
 */
@Configuration
@RequiredArgsConstructor
public class WeChatConfiguration {

    private final ResourceLoader resourceLoader;
    private static final String CLASS_PATH = "classpath:";

    @Bean
    public WeChatProperties weChatProperties(){
        return new WeChatProperties();
    }

    /**
     * 自动更新证书
     * @param weChatProperties
     * @return
     * @throws IOException
     */
    @Bean
    public RSAAutoCertificateConfig config(WeChatProperties weChatProperties) throws IOException {
        String merchantSerialNumber = getCertificateSerialNumber(weChatProperties.getCertPath());
        String privateKey = readResourceAsString(weChatProperties.getKeyPath());

        return new RSAAutoCertificateConfig.Builder()
                .merchantId(weChatProperties.getMchId())
                .privateKey(privateKey)
                .merchantSerialNumber(merchantSerialNumber)
                .apiV3Key(weChatProperties.getAppLV3Key())
                .build();
    }

    /**
     * 解析器
     * @param config
     * @return
     * @throws IOException
     */
    @Bean
    public NotificationParser notificationParser(RSAAutoCertificateConfig config) throws IOException {
        return new NotificationParser(config);
    }

    /**
     * 读取私钥文件,将文件流读取成 string
     *
     * @param path 文件路径
     * @return 文件内容
     * @throws IOException
     */
    private String readResourceAsString(String path) throws IOException {
        Resource resource = resourceLoader.getResource(CLASS_PATH + path);
        try (InputStream inputStream = resource.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
            return stringBuilder.toString();
        }
    }

    /**
     * 获取证书序列号
     *
     * @param certPath 证书路径
     * @return 证书序列号
     * @throws IOException
     */
    private String getCertificateSerialNumber(String certPath) throws IOException {
        Resource resource = resourceLoader.getResource(CLASS_PATH + certPath);
        try (InputStream inputStream = resource.getInputStream()) {
            X509Certificate certificate = getCertificate(inputStream);
            return certificate.getSerialNumber().toString(16).toUpperCase();
        }
    }

    /**
     * 获取证书,将文件流转成证书文件
     *
     * @param inputStream 证书文件流
     * @return {@link X509Certificate} 证书对象
     */
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }
}

3.枚举定义了支付类型

package com.ruoyi.web.pay.enums;

/**
 * @author sk
 * @date 2024/10/29
 * @description 支付方式枚举类
 */
public enum PayTypeEnum {
    JSAPI("JSAPI"),

    NATIVE("NATIVE"),

    APP("APP"),

    MICROPAY("MICROPAY"),

    MWEB("MWEB"),

    FACEPAY("FACEPAY");
    private final String name;

    PayTypeEnum(String name) {
        this.name = name;
    }


    public String getName() {
        return name;
    }

}

4.存放支付信息

package com.ruoyi.web.pay.properties;

import lombok.Data;

/**
 * @author sk
 * @date 2024/10/23
 * @description 微信配置参数对象
 */
@Data
public class WeChatProperties {
    private String appSecret = "xxx55416868xxxb96ebxxx";//app密钥
    private String mchId = "169xxxxx8" ; //商户id
    private String appId = "wx70xxxxxxxx1" ; //appId
    private String appLV3Key = "xxxPprmxxxxxxxxP7ZbX" ; //apiLv3密钥
    private String appLV2Key = "xxxxtXa3wsGWxxxxxxHTS" ; //apiLv2密钥(目前使用的是v3支付)
    private String keyPath = "/apiclient_key.pem" ; //密钥文件的路径
    private String certPath = "/apiclient_cert.pem" ; //商户证书的路径
    private String payNotifyUrl = "http://ip:port/wxPay/payNotify" ; //微信支付回调接口
    private String refundNotifyUrl = "http://ip:port/wxPay/refundNotify" ; //微信退款回调接口
}

我是直接将密钥文件和证书文件放在resource下的 5.1支付接口

package com.ruoyi.web.pay.service;

import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.model.WeChatPay;
import com.wechat.pay.java.service.payments.model.Transaction;

import javax.servlet.http.HttpServletRequest;

/**
 * @author sk
 * @date 2024/10/28
 * @description 微信支付服务接口
 */
public interface WeChatPaySerive {

    /**
     * 微信预支付
     * @param weChatPay
     * @return 结果
     */
    R<?> prepay(WeChatPay weChatPay);

    /**
     * 根据商户订单号查询订单
     * @param outTradeNo 商户订单号
     * @return 结果
     */
    R<Transaction> queryOrderByOutTradeNo(String outTradeNo);

    /**
     * 根据微信支付订单号查询订单
     * @param transactionId 微信支付订单号
     * @return 结果
     */
    R<Transaction> queryOrderById(String transactionId);

    /**
     * 关闭订单
     * @param outTradeNo 订单号
     */
    R<?> closeOrder(String outTradeNo);

    /**
     * 解析支付回调参数
     * @param request
     * @return
     */
    R<Transaction> parse(HttpServletRequest request);
}

5.2退款接口

package com.ruoyi.web.pay.service;

import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.model.WeChatRefund;
import com.wechat.pay.java.service.refund.model.Refund;
import com.wechat.pay.java.service.refund.model.RefundNotification;

import javax.servlet.http.HttpServletRequest;

/**
 * @author sk
 * @date 2024/10/23
 * @description 微信退款服务接口
 */
public interface WeChatRefundService {

    /**
     * 微信退款申请
     * @param weChatRefund
     * @return 结果
     */
    R<Refund> refundWithRequest(WeChatRefund weChatRefund);

    /**
     * 根据退款单号查询退款信息
     * @param outRefundNo
     * @return
     */
    R<Refund> queryByOutRefundNo(String outRefundNo);

    /**
     * 解析参数
     * @param request
     * @return
     */
    R<RefundNotification> parse(HttpServletRequest request);
}

5.3由于支付有多种方式,定义了一个抽象父类

package com.ruoyi.web.pay.service;

import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;

/**
 * @author sk
 * @date 2024/10/29
 * @description 微信支付抽象类
 * 每一种支付方式都对应一个实现类
 */
public abstract class WeChatPaymentSerive {

    public WeChatProperties properties;
    public WeChatPaymentSerive(WeChatProperties properties) {
        this.properties = properties;
    }

    /**
     * 微信预支付
     * @param weChatPay
     * @return 结果
     */
    public abstract R<?> prepay(WeChatPay weChatPay);

    /**
     * 支付类型
     * @return
     */
    public abstract String payTyp();
}

5.4支付实现类

package com.ruoyi.web.pay.service.impl;

import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.base.BaseSerive;
import com.ruoyi.web.pay.model.CloseOrder;
import com.ruoyi.web.pay.model.QueryOrderById;
import com.ruoyi.web.pay.model.QueryOrderByNo;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaySerive;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.model.Transaction;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author sk
 * @date 2024/10/28
 * @description 微信支付
 */
public class WeChatPaySeriveImpl extends BaseSerive implements WeChatPaySerive {
    private QuerySerive querySerive;

    private Map<String, WeChatPaymentSerive> payServiceMap = new HashMap<>();
    public WeChatPaySeriveImpl(WeChatProperties weChatProperties,
                               NotificationParser notificationParser,
                               QuerySerive querySerive,
                               List<WeChatPaymentSerive> seriveList) {
        super(weChatProperties, notificationParser);
        this.querySerive = querySerive;
        seriveList.forEach(service -> {
            String payType = service.payTyp();
            payServiceMap.put(payType, service);
        });
    }

    @Override
    public R<?> prepay(WeChatPay weChatPay) {
        if(weChatPay == null || weChatPay.getPayTypeEnum() == null){
            return R.fail(500,"请选择支付方式");
        }
        WeChatPaymentSerive weChatPaymentSerive = payServiceMap.get(weChatPay.getPayTypeEnum().getName());
        if(weChatPaymentSerive == null){
            return R.fail(500,"目前该支付方式不支持");
        }
        return weChatPaymentSerive.prepay(weChatPay);
    }

    @Override
    public R<Transaction> queryOrderByOutTradeNo(String outTradeNo) {
        QueryOrderByNo queryOrderByNo = new QueryOrderByNo();
        queryOrderByNo.setOutTradeNo(outTradeNo);
        queryOrderByNo.setMchid(properties.getMchId());
        try {
            // 请求微信服务器关闭订单
            Transaction transaction = querySerive.queryOrderByOutTradeNo(queryOrderByNo);
            return R.ok(transaction);
        } catch (ServiceException e) {
            return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
        }
    }

    @Override
    public R<Transaction> queryOrderById(String transactionId) {
        QueryOrderById queryOrderById = new QueryOrderById();
        queryOrderById.setTransactionId(transactionId);
        queryOrderById.setMchid(properties.getMchId());
        try {
            // 请求微信服务器关闭订单
            Transaction transaction = querySerive.queryOrderById(queryOrderById);
            return R.ok(transaction);
        } catch (ServiceException e) {
            return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
        }
    }

    @Override
    public R<?> closeOrder(String outTradeNo) {
        CloseOrder closeOrder = new CloseOrder();
        closeOrder.setOutTradeNo(outTradeNo);
        closeOrder.setMchid(properties.getMchId());
        try {
            // 请求微信服务器关闭订单
            querySerive.closeOrder(closeOrder);
            return R.ok();
        } catch (ServiceException e) {
            return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
        }
    }

    @Override
    public R<Transaction> parse(HttpServletRequest request) {
        R<Transaction> parse = super.parse(request,Transaction.class);
        return parse;
    }
}

5.5退款实现类

package com.ruoyi.web.pay.service.impl;

import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.base.BaseSerive;
import com.ruoyi.web.pay.model.WeChatRefund;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatRefundService;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.*;

import javax.servlet.http.HttpServletRequest;

/**
 * @author sk
 * @date 2024/10/23
 * @description 微信退款实现类
 */
public class WeChatRefundServiceImpl extends BaseSerive implements WeChatRefundService {
    private RefundService refundService;
    public WeChatRefundServiceImpl(WeChatProperties properties, RefundService refundService, NotificationParser notificationParser) {
        super(properties,notificationParser);
        this.refundService = refundService;
    }

    @Override
    public R<Refund> refundWithRequest(WeChatRefund weChatRefund) {
        // 判断参数是否为空
        if (weChatRefund == null || weChatRefund.getRefund() == null || weChatRefund.getTotal() == null || weChatRefund.getReason() == null) {
            return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
        }

        CreateRequest createRequest = new CreateRequest();

        createRequest.setOutTradeNo(weChatRefund.getOutTradeNo());
        createRequest.setOutRefundNo(weChatRefund.getOutRefundNo());
        createRequest.setReason(weChatRefund.getReason());
        createRequest.setNotifyUrl(properties.getRefundNotifyUrl());
        AmountReq amountReq = new AmountReq();
        //退款金额
        amountReq.setRefund(weChatRefund.getRefund());
        //原订单金额
        amountReq.setTotal(weChatRefund.getTotal());
        amountReq.setCurrency("CNY");
        createRequest.setAmount(amountReq);
        try {
            // 请求微信服务器申请退款
            Refund refund = refundService.create(createRequest);
            return R.ok(refund);
        } catch(ServiceException e) {
            return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
        }
    }

    @Override
    public R<Refund> queryByOutRefundNo(String outRefundNo) {
        // 判断参数是否为空
        if (outRefundNo == null || outRefundNo.isEmpty()) {
            return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
        }

        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
        request.setOutRefundNo(outRefundNo);
        request.setSubMchid(properties.getMchId());
        try {
            // 请求微信服务器获取信息
            Refund refund = refundService.queryByOutRefundNo(request);
            return R.ok(refund);
        } catch(ServiceException e) {
            return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
        }
    }

    @Override
    public R<RefundNotification> parse(HttpServletRequest request) {
        R<RefundNotification> parse = super.parse(request,RefundNotification.class);
        return parse;
    }
}

5.6jsapi支付

package com.ruoyi.web.pay.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.enums.PayTypeEnum;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;

import java.util.HashMap;

/**
 * @author sk
 * @date 2024/10/29
 * @description jsapi支付
 */
public class JsapiPaySerive extends WeChatPaymentSerive {

    private  JsapiServiceExtension jsapiService;

    public JsapiPaySerive(WeChatProperties properties, JsapiServiceExtension jsapiService) {
        super(properties);
        this.jsapiService = jsapiService;
    }

    @Override
    public R<?> prepay(WeChatPay weChatPay) {
        // 判断参数是否为空
        if (weChatPay == null || weChatPay.getAmount() == null || weChatPay.getDescription() == null || weChatPay.getCode() == null) {
            return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
        }
        //获取openId
        String openId = getOpenId(weChatPay.getCode());
        PrepayRequest request = new PrepayRequest();
        // 1. 设置AppId
        request.setAppid(properties.getAppId());
        // 2. 设置商户号
        request.setMchid(properties.getMchId());
        // 3. 设置商品描述
        request.setDescription(weChatPay.getDescription());
        // 4. 设置商户订单号
        request.setOutTradeNo(weChatPay.getOutTradeNo());
        // 5. 设置支付回调地址
        request.setNotifyUrl(properties.getPayNotifyUrl());
        // 6. 设置金额,CNY-人民币
        Amount amount = new Amount();
        amount.setTotal(weChatPay.getAmount());
        amount.setCurrency("CNY");
        request.setAmount(amount);
        // 7. 设置支付者信息
        Payer payer = new Payer();
        payer.setOpenid(openId);
        request.setPayer(payer);

        try {
            // 请求微信服务器JSAPI下单
            PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(request);
            return R.ok(response);
        } catch(ServiceException e) {
            return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
        }
    }

    @Override
    public String payTyp() {
        return PayTypeEnum.JSAPI.getName();
    }

    private String getOpenId(String code) {
        String apiUrl = StrUtil.format(
                "https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code",
                properties.getAppId(), properties.getAppSecret(), code
        );
        try {
            String body = HttpRequest.get(apiUrl).execute().body();
            HashMap<String,String> hashMap = JSONObject.parseObject(body, HashMap.class);
            if(hashMap.containsKey("errcode")){
                throw new com.ruoyi.common.exception.ServiceException("微信获取OpenId失败:"+body);
            }
            return hashMap.get("openid");
        }catch (Exception e){
            throw e;
        }
    }
}

5.7native支付

package com.ruoyi.web.pay.service.impl;

import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.pay.enums.PayTypeEnum;
import com.ruoyi.web.pay.model.WeChatPay;
import com.ruoyi.web.pay.properties.WeChatProperties;
import com.ruoyi.web.pay.service.WeChatPaymentSerive;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;

/**
 * @author sk
 * @date 2024/10/29
 * @description native支付
 */
public class NativePaySerive extends WeChatPaymentSerive {

    private NativePayService nativePayService;
    public NativePaySerive(WeChatProperties properties, NativePayService nativePayService) {
        super(properties);
        this.nativePayService = nativePayService;
    }

    @Override
    public R<?> prepay(WeChatPay weChatPay) {
        // 判断参数是否为空
        if (weChatPay == null || weChatPay.getAmount() == null || weChatPay.getDescription() == null) {
            return R.fail(HttpStatus.BAD_REQUEST, "参数错误");
        }

        PrepayRequest request = new PrepayRequest();
        // 1. 设置AppId
        request.setAppid(properties.getAppId());
        // 2. 设置商户号
        request.setMchid(properties.getMchId());
        // 3. 设置商品描述
        request.setDescription(weChatPay.getDescription());
        // 4. 设置商户订单号
        request.setOutTradeNo(weChatPay.getOutTradeNo());
        // 5. 设置支付回调地址
        request.setNotifyUrl(properties.getPayNotifyUrl());
        // 6. 设置金额,CNY-人民币
        Amount amount = new Amount();
        amount.setTotal(weChatPay.getAmount());
        amount.setCurrency("CNY");
        request.setAmount(amount);

        try {
            PrepayResponse response = nativePayService.prepay(request);
            return R.ok(response);
        } catch(ServiceException e) {
            return R.fail(e.getHttpStatusCode(), e.getErrorMessage());
        }
    }

    @Override
    public String payTyp() {
        return PayTypeEnum.NATIVE.getName();
    }
}

5.8查询对象

package com.ruoyi.web.pay.service.impl;

import com.ruoyi.web.pay.model.CloseOrder;
import com.ruoyi.web.pay.model.QueryOrderById;
import com.ruoyi.web.pay.model.QueryOrderByNo;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.exception.ValidationException;
import com.wechat.pay.java.core.http.*;
import com.wechat.pay.java.core.util.GsonUtil;
import com.wechat.pay.java.service.payments.model.Transaction;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;


/**
 * @author sk
 * @date 2024/10/28
 * @description 查询service
 */
public class QuerySerive {
    private HttpClient httpClient;

    public QuerySerive(RSAAutoCertificateConfig config) {
        this.httpClient = new DefaultHttpClientBuilder().config(config).build();
    }

    /**
     * 微信支付订单号查询订单
     *
     * @param request 请求参数
     * @return Transaction
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
     */
    public Transaction queryOrderById(QueryOrderById request) {
        String requestPath = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/{transaction_id}";

        // 添加 path param
        requestPath = requestPath.replace("{" + "transaction_id" + "}", urlEncode(request.getTransactionId()));

        // 添加 query param
        QueryParameter queryParameter = new QueryParameter();
        if (request.getMchid() != null) {
            queryParameter.add("mchid", urlEncode(request.getMchid()));
        }
        requestPath += queryParameter.getQueryStr();
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(HttpMethod.GET)
                        .url(requestPath)
                        .headers(headers)
                        .build();
        HttpResponse<Transaction> httpResponse = httpClient.execute(httpRequest, Transaction.class);
        return httpResponse.getServiceResponse();
    }

    /**
     * 商户订单号查询订单
     *
     * @param request 请求参数
     * @return Transaction
     * @throws HttpException 发送HTTP请求失败。例如构建请求参数失败、发送请求失败、I/O错误等。包含请求信息。
     * @throws ValidationException 发送HTTP请求成功,验证微信支付返回签名失败。
     * @throws ServiceException 发送HTTP请求成功,服务返回异常。例如返回状态码小于200或大于等于300。
     * @throws MalformedMessageException 服务返回成功,content-type不为application/json、解析返回体失败。
     */
    public Transaction queryOrderByOutTradeNo(QueryOrderByNo request) {

        String requestPath =
                "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}";

        // 添加 path param
        requestPath = requestPath.replace("{" + "out_trade_no" + "}", urlEncode(request.getOutTradeNo()));

        // 添加 query param
        QueryParameter queryParameter = new QueryParameter();
        if (request.getMchid() != null) {
            queryParameter.add("mchid", urlEncode(request.getMchid()));
        }
        requestPath += queryParameter.getQueryStr();
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(HttpMethod.GET)
                        .url(requestPath)
                        .headers(headers)
                        .build();
        HttpResponse<Transaction> httpResponse = httpClient.execute(httpRequest, Transaction.class);
        return httpResponse.getServiceResponse();
    }

    /**
     * 关闭订单
     * @param request
     */
    public void closeOrder(CloseOrder request) {
        String requestPath =
                "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close";

        // 添加 path param
        requestPath = requestPath.replace("{" + "out_trade_no" + "}", urlEncode(request.getOutTradeNo()));
        HttpHeaders headers = new HttpHeaders();
        headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
        headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
        HttpRequest httpRequest =
                new HttpRequest.Builder()
                        .httpMethod(HttpMethod.POST)
                        .url(requestPath)
                        .headers(headers)
                        .body(createRequestBody(request))
                        .build();
        httpClient.execute(httpRequest, null);
    }

    private RequestBody createRequestBody(Object request) {
        return new JsonRequestBody.Builder().body(GsonUtil.toJson(request)).build();
    }

    public static String urlEncode(String string) {
        try {
            return URLEncoder.encode(string, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
}

6.接下来就是各种实体类了

package com.ruoyi.web.pay.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

/**
 * @author sk
 * @date 2024/10/28
 * @description 关闭订单对象
 */
public class CloseOrder {

    /** 商户订单号 */
    @SerializedName("out_trade_no")
    @Expose(serialize = false)
    private String outTradeNo;

    /** mchid 说明:直连商户号 */
    @SerializedName("mchid")
    private String mchid;

    public String getOutTradeNo() {
        return outTradeNo;
    }

    public void setOutTradeNo(String outTradeNo) {
        this.outTradeNo = outTradeNo;
    }

    public String getMchid() {
        return mchid;
    }

    public void setMchid(String mchid) {
        this.mchid = mchid;
    }
}
package com.ruoyi.web.pay.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

/**
 * @author sk
 * @date 2024/10/29
 * @description 查询对象
 */
public class QueryOrderById {

    /**
     * 微信交易号
     */
    @SerializedName("transaction_id")
    @Expose(serialize = false)
    private String transactionId;

    /** mchid 说明:直连商户号 */
    @SerializedName("mchid")
    @Expose(serialize = false)
    private String mchid;

    public String getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(String transactionId) {
        this.transactionId = transactionId;
    }

    public String getMchid() {
        return mchid;
    }

    public void setMchid(String mchid) {
        this.mchid = mchid;
    }
}
package com.ruoyi.web.pay.model;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

/**
 * @author sk
 * @date 2024/10/29
 * @description 查询对象
 */
public class QueryOrderByNo {

    /** mchid 说明:商户订单号 */
    @SerializedName("out_trade_no")
    @Expose(serialize = false)
    private String outTradeNo;

    /** mchid 说明:直连商户号 */
    @SerializedName("mchid")
    @Expose(serialize = false)
    private String mchid;

    public String getOutTradeNo() {
        return outTradeNo;
    }

    public void setOutTradeNo(String outTradeNo) {
        this.outTradeNo = outTradeNo;
    }

    public String getMchid() {
        return mchid;
    }

    public void setMchid(String mchid) {
        this.mchid = mchid;
    }
}
package com.ruoyi.web.pay.model;

import com.ruoyi.web.pay.enums.PayTypeEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author sk
 * @date 2024/10/23
 * @description 微信支付对象
 */
@Data
public class WeChatPay {

    @ApiModelProperty("商品描述")
    private String description;

    /**
     * 单位分
     */
    @ApiModelProperty("订单金额")
    private Integer amount;

    @ApiModelProperty("用户码")
    private String code;

    @ApiModelProperty("支付订单号")
    private String outTradeNo;

    @ApiModelProperty("支付方式")
    private PayTypeEnum payTypeEnum;
}
package com.ruoyi.web.pay.model;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @author sk
 * @date 2024/10/23
 * @description 微信退款对象
 */
@Data
public class WeChatRefund {

    @ApiModelProperty("退款原因")
    private String reason;

    /**
     * 单位分
     */
    @ApiModelProperty("原订单金额")
    private Long total;

    /**
     * 单位分
     */
    @ApiModelProperty("退款金额")
    private Long refund;

    @ApiModelProperty("退款单号")
    private  String outRefundNo;

    @ApiModelProperty("支付订单号")
    private  String outTradeNo;
}

如果需要加入新的支付方式,只需要实现WeChatPaymentSerive抽象类, 支付jsapi

//生成支付订单号
String payNo = "ZF202410110303443";
//支付
WeChatPay weChatPay = new WeChatPay();
weChatPay.setDescription("预支付下单");
weChatPay.setCode(wechatPayDto.getCode());
weChatPay.setAmount(1);
weChatPay.setOutTradeNo(payNo);
weChatPay.setPayTypeEnum(PayTypeEnum.JSAPI);
return weChatPayService.prepay(weChatPay);

native支付同理,修改 weChatPay.setPayTypeEnum(PayTypeEnum.NATIVE); 7.支付回调

package com.ruoyi.web.controller.pay;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.pay.service.WeChatPaySerive;
import com.ruoyi.web.pay.service.WeChatRefundService;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.RefundNotification;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/wxPay")
public class WeChatController {

    @Autowired
    private WeChatPaySerive weChatPaySerive;


    @Autowired
    private WeChatRefundService weChatRefundService;

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

    @ApiModelProperty("微信支付回调")
    @PostMapping("/payNotify")
    public void payNotify(@RequestBody JSONObject jsonObject, HttpServletRequest request){
        log.info("支付回调数据:"+jsonObject);
        try {
            R<Transaction> parse = weChatPaySerive.parse(request);
            Transaction data = parse.getData();
        }catch (Exception e){
            throw new ServiceException("解析支付回调参数错误");
        }
    }

    @ApiModelProperty("微信退款回调")
    @PostMapping("/refundNotify")
    public void refundNotify(@RequestBody JSONObject jsonObject, HttpServletRequest request){
        log.info("退款回调数据:"+jsonObject);
        try {
            R<RefundNotification> parse = weChatRefundService.parse(request);
            RefundNotification data = parse.getData();
        }catch (Exception e){
            throw new ServiceException("解析退款回调参数错误");
        }
    }

}

这样就拿到了回调对象了,可以复制属性到自己定义的对象上,然后添加数据库支付日志表会退款表。