因为微信支付回调需要一个外网可访问的地址,我本地调试是采用的内网穿透的方式进行调试的。

Cpolar是一款内网穿透工具,它支持http/https/tcp协议,不限制流量,操作简单,无需公网IP,也无需路由器,可以轻松把服务暴露到公网访问。

1 下载安装cpolar内网穿透

访问cpolar官网,注册一个账号,并下载安装cpolar客户端。详细可以参考文档教程进行下载安装。

微信支付IDEA SPRING BOOT 支付回调 springboot集成微信支付_apache

2 创建隧道
cpolar安装成功后,我们在浏览器上访问本地9200端口,登录Cpolar的web ui界面:http://localhost:9200。

点击左侧仪表盘的隧道管理——创建隧道,由于tomcat中配置的是82端口,因此我们要来创建一条http隧道,指向82端口:

隧道名称:可自定义,注意不要与已有隧道名称重复
协议:http协议
本地地址:82
域名类型:免费选择随机域名
地区:选择China top

微信支付IDEA SPRING BOOT 支付回调 springboot集成微信支付_微信支付_02

 

微信支付IDEA SPRING BOOT 支付回调 springboot集成微信支付_apache_03

点击左侧仪表盘的状态——在线隧道列表,可以看到刚刚创建的隧道已经有生成了相应的公网地址,一个http协议,一个https协议(免去配置ssl证书的繁琐步骤),将其复制想下来 

微信支付IDEA SPRING BOOT 支付回调 springboot集成微信支付_微信支付_04

接下来我们就直接上代码了:

一、引入官方提供的pom依赖

<!--  微信支付 -->
<dependency>
  <groupId>com.github.wechatpay-apiv3</groupId>
  <artifactId>wechatpay-apache-httpclient</artifactId>
  <version>0.4.2</version>
</dependency>

二、配置微信支付必要的参数

weixin:
  appid:  # appid
  mch-serial-no:  # 证书序列号
  private-key-path: apiclient_key.pem # 证书路径  我这边保存在resource文件夹下读取文件时通过ClassPathResource去读取的
  mch-id: # 商户号
  key:  # api秘钥
  domain: https://api.mch.weixin.qq.com # 微信服务器地址
  notify-domain: http://303fe2d4.r5.cpolar.top/v3/wechat/callback # 回调,自己的回调地址需要外网可访问

三、编写微信支付配置类

import com.qz.common.exception.CommonException;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;

/**
 * @author lihao
 * @create 2023-03-22 17:24
 * @desc
 **/
@Configuration
@PropertySource("classpath:application.yml") //读取配置文件
@ConfigurationProperties(prefix = "weixin") //读取weixin节点
@Data //使用set方法将weixin节点中的值填充到当前类的属性中
public class WxPayConfig {

    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;

    /**
     * 获取商户的私钥文件
     *
     * @param filename 证书地址
     * @return 私钥文件
     */
    public PrivateKey getPrivateKey(String filename) {
        try {
            ClassPathResource classPathResource = new ClassPathResource(filename);
            return PemUtil.loadPrivateKey(classPathResource.getInputStream());
        } catch (IOException e) {
            throw new CommonException("私钥文件不存在");
        }
    }


    /**
     * 获取签名验证器
     */
    @Bean
    public Verifier getVerifier() {
        // 获取商户私钥
        final PrivateKey privateKey = getPrivateKey(privateKeyPath);
        // 私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
        // 身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        try {
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(mchId, wechatPay2Credentials, key.getBytes(StandardCharsets.UTF_8));
        } catch (IOException | GeneralSecurityException | HttpCodeException e) {
            e.printStackTrace();
        }
        try {
            return certificatesManager.getVerifier(mchId);
        } catch (NotFoundException e) {
            e.printStackTrace();
            throw new CommonException("获取签名验证器失败");
        }
    }


    /**
     * 获取微信支付的远程请求对象
     *
     * @return Http请求对象
     */
    @Bean
    public CloseableHttpClient getWxPayClient() {
        // 获取签名验证器
        Verifier verifier = getVerifier();
        // 获取商户私钥
        final PrivateKey privateKey = getPrivateKey(privateKeyPath);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        return builder.build();
    }
}

四、微信支付枚举类

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author lihao
 * @create 2023-03-22 17:21
 * @desc 微信支付枚举类
 **/
@AllArgsConstructor
@Getter
public enum WxApiType {

    /**
     * Native下单
     */
    NATIVE_PAY("/v3/pay/transactions/native"),


    /**
     * jsapi下单
     */
    JSAPI_PAY("/v3/pay/transactions/jsapi"),

    /**
     * jsapi下单
     */
    H5_PAY("/v3/pay/transactions/h5"),

    /**
     * APP下单
     */
    APP_PAY("/v3/pay/transactions/app"),

    /**
     * 查询订单
     */
    ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s/?mchid=%s"),

    /**
     * 查询订单
     */
    ORDER_QUERY_BY_ID("/v3/pay/transactions/id/%s?mchid=%s"),

    /**
     * 关闭订单
     */
    CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),

    /**
     * 申请退款
     */
    DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),

    /**
     * 查询单笔退款
     */
    DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),

    /**
     * 申请交易账单
     */
    TRADE_BILLS("/v3/bill/tradebill"),

    /**
     * 申请资金账单
     */
    FUND_FLOW_BILLS("/v3/bill/fundflowbill"),

    /**
     * 发起商家转账
     */
    TRANSFER_BATCHES("/v3/transfer/batches");


    /**
     * 类型
     */
    private final String type;
}

五、微信支付工具类(这边以native扫码支付为例)

(1)创建native扫码支付工具类  后面需要用到时传入参数直接调用即可

/**
 * @author lihao
 * @create 2023-03-23 9:58
 * @desc Native扫码支付工具类
 **/
@Slf4j
public class WxPayNativeUtils {


    public static Map<String, Object> nativePay(WxPayConfig wxPayConfig, WeChatPayNativeParam param, CloseableHttpClient wxPayClient) throws Exception {
        Gson gson = new Gson();
        // 创建POST请求对象,里面输入的是地址,也就是那个https://api.wxpayxxxxx 那个,只不过我们直接去配置文件里面读取然后拼接出来了,懒得每次都输入一遍
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.NATIVE_PAY.getType()));
        Map paramsMap =new HashMap();
        paramsMap.put("appid",wxPayConfig.getAppid()); // 我们的APPID
        paramsMap.put("mchid",wxPayConfig.getMchId()); // 我们的商户ID
        paramsMap.put("description",param.getDescription());  // 扫码之后显示的标题
        paramsMap.put("out_trade_no",param.getOutTradeNo()); // 商户订单号 我们自己生成的那个
        // 这里就是微信响应给我们系统的那个地址 必须是能外网访问的
        paramsMap.put("notify_url",wxPayConfig.getNotifyDomain());
        Map amountMap = new HashMap();
        amountMap.put("total",param.getTotal()); // 支付金额
        amountMap.put("currency","CNY"); // 交易货币的类型
        paramsMap.put("amount",amountMap);
        paramsMap.put("attach",param.getDeviceInfo());
        // 将这个json对象转换成字符串,用于后面进行网络传输
        String jsonParams = gson.toJson(paramsMap);
        log.info("请求微信支付V3 Native扫码支付接口参数 =========> " + jsonParams);
        // 设置请求体
        StringEntity entity = new StringEntity(jsonParams,"utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        HashMap<String, Object> returnMap = new HashMap<>();
        try {
            String body = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) { //处理成功 有返回就会进入这里
                log.info("请求微信支付V3 Native扫码支付接口成功,返回结果 =========> " + body);
            } else if (statusCode == 204) { //处理成功,无返回就会进入到这里
                System.out.println("success");
            } else { // 接口调用失败的话就会进入这里
                log.info("Native下单失败,响应码 =====> " + statusCode+ ",返回结果= " + body);
                HashMap<String,String> resultMap = gson.fromJson(body, HashMap.class);
                returnMap.put("err_code_des",resultMap.get("message"));
            }
            // 相微信那边返回的结果   我们将json返回转成一个Map对象
            HashMap<String,String> resultMap = gson.fromJson(body, HashMap.class);
            // 然后从这个Map里面拿到code_url,这个是微信定义的,我们拿这个结果生成二维码
            String code_url = resultMap.get("code_url");

            returnMap.put("code_url",code_url);
            return returnMap;
        } finally {
            response.close();
        }
    }

}

(2)native支付成功回调工具类

/**
 * @author lihao
 * @create 2023-03-23 11:40
 * @desc
 **/
@Slf4j
public class WxPayCallbackUtil {

    /**
     * 微信支付创建订单回调方法
     * @param verifier 证书
     * @param wxPayConfig 微信配置
     * @param businessCallback 回调方法,用于处理业务逻辑
     * @return json格式的string数据,直接返回给微信
     */
    public static String wxPaySuccessCallback(HttpServletRequest request, HttpServletResponse response, Verifier verifier, WxPayConfig wxPayConfig, Consumer<WxchatCallbackSuccessResult> businessCallback) {
        Gson gson = new Gson();

        // 1.处理通知参数
        final String body = HttpUtils.readData(request);
        HashMap<String, Object> bodyMap = gson.fromJson(body, HashMap.class);

        // 2.签名验证
        WechatPayValidatorForRequest wechatForRequest = new WechatPayValidatorForRequest(verifier, body, (String) bodyMap.get("id"));
        try {
            if (!wechatForRequest.validate(request)) {
                // 通知验签失败
                response.setStatus(500);
                final HashMap<String, Object> map = new HashMap<>();
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return gson.toJson(map);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


        // 3.获取明文数据
        String plainText = decryptFromResource(bodyMap,wxPayConfig);
        HashMap<String,Object> plainTextMap = gson.fromJson(plainText, HashMap.class);
        log.info("plainTextMap:{}",plainTextMap);
        // 4.封装微信返回的数据
        WxchatCallbackSuccessResult callbackData = new WxchatCallbackSuccessResult();
        callbackData.setSuccessTime(String.valueOf(plainTextMap.get("success_time")));
        callbackData.setOrderId(String.valueOf(plainTextMap.get("out_trade_no")));
        callbackData.setTransactionId(String.valueOf(plainTextMap.get("transaction_id")));
        callbackData.setTradestate(String.valueOf(plainTextMap.get("trade_state")));
        callbackData.setTradetype(String.valueOf(plainTextMap.get("trade_type")));
        callbackData.setAttach(String.valueOf(plainTextMap.get("attach")));
        String amount = String.valueOf(plainTextMap.get("amount"));
        HashMap<String,Object> amountMap = gson.fromJson(amount, HashMap.class);
        String total = String.valueOf(amountMap.get("total"));
        callbackData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
        log.info("callbackData:{}",callbackData);

        if ("SUCCESS".equals(callbackData.getTradestate())) {
            // 执行业务逻辑
            businessCallback.accept(callbackData);
        }

        // 5.成功应答
        response.setStatus(200);
        final HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("code", "SUCCESS");
        resultMap.put("message", "成功");
        return gson.toJson(resultMap);
    }

    /**
     * 对称解密
     */
    private static String decryptFromResource(HashMap<String, Object> bodyMap, WxPayConfig wxPayConfig) {
        // 通知数据
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        // 数据密文
        String ciphertext = resourceMap.get("ciphertext");
        // 随机串
        String nonce = resourceMap.get("nonce");
        // 附加数据
        String associateData = resourceMap.get("associated_data");
        AesUtil aesUtil = new AesUtil(wxPayConfig.getKey().getBytes(StandardCharsets.UTF_8));
        try {
            return aesUtil.decryptToString(associateData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            throw new CommonException("解密失败");
        }
    }
}

(3)请求参数

/**
 * @author lihao
 * @create 2023-03-23 10:02
 * @desc 微信Native支付参数
 **/
@Data
public class WeChatPayNativeParam {

    /**
     * 扫码之后显示的标题
     */
    private String description;

    /**
     * 商户订单号 我们自己生成的那个
     */
    private String outTradeNo;

    /**
     * 支付金额
     */
    private BigDecimal total;

    /**
     * 自定义参数  具体看业务要求  无要求可不填
     */
    private String deviceInfo;

}

(4)controller中调用native扫码支付工具类

@Autowired
private WxPayConfig wxPayConfig;

@Autowired
private CloseableHttpClient wxPayClient;

@ApiOperation("微信支付v3版返回链接")
@SaPlatCheckLogin
@PostMapping("/nativePay")
public CommonResult<WeChatCodeResult> nativePay(@RequestBody WeChatPayParam payParam) throws Exception {
        WeChatPayNativeParam param = new WeChatPayNativeParam();
        param.setDescription(payParam.getDescription());
        //这里注意要保持商户订单号唯一  
        param.setOutTradeNo(payParam.getOrderId()+ RandomUtil.randomNumbers(4));
        param.setDeviceInfo(payParam.getOrderId());
        BigDecimal money = new BigDecimal(0.01);
        param.setTotal((money.multiply(new BigDecimal(100))).setScale(0,BigDecimal.ROUND_HALF_DOWN));
        Map<String, Object> map = WxPayNativeUtils.nativePay(wxPayConfig,param,wxPayClient);
        if(ObjectUtil.isEmpty(map) || ObjectUtil.isNotEmpty(map.get("err_code_des"))){
            return CommonResult.data(new WeChatCodeResult(null,"FAIL",map.get("err_code_des").toString()));
        }
        if(ObjectUtil.isEmpty(map.get("code_url"))){
            throw new CommonException("生成支付二维码失败!");
        }
        return CommonResult.data(new WeChatCodeResult(map.get("code_url").toString(),"SUCCESS",null));
    }

@ApiOperation("微信支付回调接口")
@PostMapping("/callback")
public String courseNative(HttpServletRequest request, HttpServletResponse response) {
        return WxPayCallbackUtil.wxPaySuccessCallback(request, response, verifier, wxPayConfig, callbackData -> {
         log.info("微信支付返回的信息:{}", callbackData);
         //这里处理自己的业务
               weChatPayService.payOrder(callbackData.getAttach(),callbackData.getTotalMoney().toString(),        callbackData.getTransactionId());
        });
    }

六、附下其他几种支付方式

(1)微信退款工具类

/**
 * @author lihao
 * @create 2023-03-23 11:18
 * @desc 微信退款工具类
 **/
@Slf4j
public class WxPayRefundUtil {

    /**
     * 发起微信退款申请
     *
     * @param wxPayConfig 微信配置信息
     * @param param       微信支付申请退款请求参数
     * @param wxPayClient 微信请求客户端
     * @return
     */
    public static String refundPay(WxPayConfig wxPayConfig, WeChatRefundParam param, CloseableHttpClient wxPayClient) {
        // 1.获取请求参数的Map格式
        Map<String, Object> paramsMap = getRefundParams(param);
        // 2.获取请求对象
        HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.DOMESTIC_REFUNDS, paramsMap);
        // 3.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("微信支付请求失败");
        }
        // 4.解析response对象
        HashMap<String, String> resultMap = resolverResponse(response);
        log.info("============>发起微信退款参数:{}",resultMap);
        if (resultMap != null) {
            // 返回微信支付退款单号
            return resultMap.get("out_refund_no");
        }
        return null;
    }

    /**
     * 查询单笔退款
     * @param wxPayConfig 微信配置信息
     * @param outRefundNo 商户退款单号
     * @param wxPayClient 微信请求客户端
     * @return
     */
    public static Map<String,String> refundQuery(WxPayConfig wxPayConfig, String outRefundNo, CloseableHttpClient wxPayClient) {
        // 1.请求路径和对象
        String url = String.format(wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS_QUERY.getType()),outRefundNo);
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        // 2.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("微信支付请求失败");
        }
        // 3.解析返回的数据
        Map<String,String> map = resolverResponse(response);
        log.info("微信支付查询单笔退款信息========>{}",map);
        return map;

    }


    /**
     * 解析响应数据
     *
     * @param response 发送请求成功后,返回的数据
     * @return 微信返回的参数
     */
    private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
        try {
            // 1.获取请求码
            int statusCode = response.getStatusLine().getStatusCode();
            // 2.获取返回值 String 格式
            final String bodyAsString = EntityUtils.toString(response.getEntity());

            Gson gson = new Gson();
            if (statusCode == 200) {
                // 3.如果请求成功则解析成Map对象返回
                HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                return resultMap;
            } else {
                if (StringUtils.isNoneBlank(bodyAsString)) {
                    log.error("微信支付请求失败,提示信息:{}", bodyAsString);
                    // 4.请求码显示失败,则尝试获取提示信息
                    HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                    throw new CommonException(resultMap.get("message"));
                }
                log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
                // 其他异常,微信也没有返回数据,这就需要具体排查了
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new CommonException(e.getMessage());
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 获取请求对象(Post请求)
     *
     * @param wxPayConfig 微信配置类
     * @param apiType     接口请求地址
     * @param paramsMap   请求参数
     * @return Post请求对象
     */
    private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
        // 1.设置请求地址
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));
        // 2.设置请求数据
        Gson gson = new Gson();
        String jsonParams = gson.toJson(paramsMap);
        // 3.设置请求信息
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        return httpPost;
    }

    /**
     * 封装微信支付申请退款请求参数
     *
     * @param param 微信支付申请退款请求参数
     * @return 封装后的map微信支付申请退款请求参数对象
     */
    private static Map<String, Object> getRefundParams(WeChatRefundParam param) {
        Map<String, Object> paramsMap = new HashMap<>();
        if (StringUtils.isNoneBlank(param.getTransactionId())) {
            paramsMap.put("transaction_id", param.getTransactionId());
        } else if (StringUtils.isNoneBlank(param.getOrderId())) {
            paramsMap.put("out_trade_no", param.getOrderId());
        } else {
            throw new CommonException("微信支付订单号和商户订单号必须填写一个");
        }
        paramsMap.put("out_refund_no", param.getRefundOrderId());
        if (StringUtils.isNoneBlank(param.getReason())) {
            paramsMap.put("reason", param.getReason());
        }
        //paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(param.getNotify().getType()));
        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("refund", param.getRefundMoney().multiply(new BigDecimal(100)).setScale(0,BigDecimal.ROUND_HALF_DOWN));
        amountMap.put("total", param.getTotalMoney().multiply(new BigDecimal(100)).setScale(0,BigDecimal.ROUND_HALF_DOWN));
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);
        return paramsMap;
    }
}

(2)订单查询工具类

/**
 * @author lihao
 * @create 2023-03-23 15:49
 * @desc 微信支付查询订单信息工具类
 **/
@Slf4j
public class WxPaySearchOrderUtil {


    /**
     * 根据微信支付系统生成的订单号查询订单详情
     * @param wxPayConfig 微信支付配置信息
     * @param transactionId 微信支付系统生成的订单号
     * @param wxPayClient 微信支付客户端请求对象
     * @return 微信订单对象
     */
    public static WxchatCallbackSuccessResult searchByTransactionId(WxPayConfig wxPayConfig, String transactionId, CloseableHttpClient wxPayClient) {
        // 1.请求路径和对象
        String url = String.format(wxPayConfig.getDomain().concat(WxApiType.ORDER_QUERY_BY_ID.getType()),transactionId,wxPayConfig.getMchId());;
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        // 2.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("微信支付请求失败");
        }

        // 3.解析返回的数据
        WxchatCallbackSuccessResult callbackData = resolverResponse(response);
        log.info("微信支付根据订单号查询信息========>callbackData:{}",callbackData);
        return callbackData;
    }

    /**
     * 根据微信支付系统生成的订单号查询订单详情
     * @param wxPayConfig 微信支付配置信息
     * @param orderId 我们自己的订单id
     * @param wxPayClient 微信支付客户端请求对象
     * @return 微信订单对象
     */
    public static WxchatCallbackSuccessResult searchByOrderId(WxPayConfig wxPayConfig, String orderId, CloseableHttpClient wxPayClient) {
        // 1.请求路径和对象
        String url = String.format(wxPayConfig.getDomain().concat(WxApiType.ORDER_QUERY_BY_NO.getType()),orderId,wxPayConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        // 2.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpGet);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("微信支付请求失败");
        }
        // 3.解析返回的数据
        WxchatCallbackSuccessResult callbackData = resolverResponse(response);
        log.info("微信支付根据商户订单号查询信息========>callbackData:{}",callbackData);
        return callbackData;
    }


    /**
     * 解析响应数据
     * @param response 发送请求成功后,返回的数据
     * @return 微信返回的参数
     */
    private static WxchatCallbackSuccessResult resolverResponse(CloseableHttpResponse response) {
        try {
            // 1.获取请求码
            int statusCode = response.getStatusLine().getStatusCode();
            // 2.获取返回值 String 格式
            final String bodyAsString = EntityUtils.toString(response.getEntity());
            Gson gson = new Gson();
            if (statusCode == 200) {
                // 3.如果请求成功则解析成Map对象返回
                HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                // 4.封装成我们需要的数据
                WxchatCallbackSuccessResult callbackData = new WxchatCallbackSuccessResult();
                callbackData.setSuccessTime(String.valueOf(resultMap.get("success_time")));
                callbackData.setOrderId(String.valueOf(resultMap.get("out_trade_no")));
                callbackData.setTransactionId(String.valueOf(resultMap.get("transaction_id")));
                callbackData.setTradestate(String.valueOf(resultMap.get("trade_state")));
                callbackData.setTradetype(String.valueOf(resultMap.get("trade_type")));
                callbackData.setAttach(String.valueOf(resultMap.get("attach")));
                String amount = String.valueOf(resultMap.get("amount"));
                HashMap<String,Object> amountMap = gson.fromJson(amount, HashMap.class);
                String total = String.valueOf(amountMap.get("total"));
                callbackData.setTotalMoney(new BigDecimal(total).movePointLeft(2));
                return callbackData;
            } else {
                if (StringUtils.isNoneBlank(bodyAsString)) {
                    log.error("微信支付请求失败,提示信息:{}", bodyAsString);
                    // 4.请求码显示失败,则尝试获取提示信息
                    HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                    throw new CommonException(resultMap.get("message"));
                }
                log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
                // 其他异常,微信也没有返回数据,这就需要具体排查了
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new CommonException(e.getMessage());
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



}

(3)商家发起转账工具类

/**
 * @author lihao
 * @create 2023-03-22 17:21
 * @desc 发起商家转账工具类
 **/
@Slf4j
public class WxPayTransferBatchesUtils {

    /**
     * 发起商家转账,支持批量转账
     *
     * @param wxPayConfig 微信配置信息
     * @param param 转账请求参数
     * @param wxPayClient 微信请求客户端()
     * @return 微信支付二维码地址
     */
    public static String transferBatches(WxPayConfig wxPayConfig, WechatTransferBatchesParam param, CloseableHttpClient wxPayClient) {
        // 1.获取请求参数的Map格式
        Map<String, Object> paramsMap = getParams(wxPayConfig, param);
        // 2.获取请求对象,WxApiType.TRANSFER_BATCHES="/v3/transfer/batches"
        HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.TRANSFER_BATCHES , paramsMap);
        // 3.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("商家转账请求失败");
        }
        // 4.解析response对象
        HashMap<String, String> resultMap = resolverResponse(response);
        if (resultMap != null) {
            // batch_id微信批次单号,微信商家转账系统返回的唯一标识
            return resultMap.get("batch_id");
        }
        return null;
    }


    /**
     * 解析响应数据
     * @param response 发送请求成功后,返回的数据
     * @return 微信返回的参数
     */
    private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
        try {
            // 1.获取请求码
            int statusCode = response.getStatusLine().getStatusCode();
            // 2.获取返回值 String 格式
            final String bodyAsString = EntityUtils.toString(response.getEntity());

            Gson gson = new Gson();
            if (statusCode == 200) {
                // 3.如果请求成功则解析成Map对象返回
                HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                return resultMap;
            } else {
                if (StringUtils.isNoneBlank(bodyAsString)) {
                    log.error("商户转账请求失败,提示信息:{}", bodyAsString);
                    // 4.请求码显示失败,则尝试获取提示信息
                    HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                    throw new CommonException(resultMap.get("message"));
                }
                log.error("商户转账请求失败,未查询到原因,提示信息:{}", response);
                // 其他异常,微信也没有返回数据,这就需要具体排查了
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new CommonException(e.getMessage());
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 获取请求对象(Post请求)
     *
     * @param wxPayConfig 微信配置类
     * @param apiType     接口请求地址
     * @param paramsMap   请求参数
     * @return Post请求对象
     */
    private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
        // 1.设置请求地址
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));

        // 2.设置请求数据
        Gson gson = new Gson();
        String jsonParams = gson.toJson(paramsMap);

        // 3.设置请求信息
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        return httpPost;
    }

    /**
     * 封装转账请求参数
     *
     * @param wxPayConfig 微信的配置文件
     * @param param 批量转账请求数据
     * @return 封装后的map对象
     */
    private static Map<String, Object> getParams(WxPayConfig wxPayConfig, WechatTransferBatchesParam param) {
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("out_batch_no", param.getBatchId());
        paramsMap.put("batch_name", param.getTitle());
        paramsMap.put("batch_remark", param.getRemark());
        paramsMap.put("total_amount", param.getTotalMoney().multiply(new BigDecimal("100")).intValue());
        paramsMap.put("total_num", param.getTransferDetailList().size());

        // 存储转账明细,一次最多三千笔
        if (param.getTransferDetailList().size() > 3000) {
            throw new CommonException("发起批量转账一次最多三千笔");
        }
        List<Map<String, Object>> detailList = new ArrayList<>();
        for (WechatTransferBatchesParam.transferDetail detail : param.getTransferDetailList()) {
            Map<String, Object> detailMap = new HashMap<>();
            detailMap.put("out_detail_no",detail.getBatchId());
            detailMap.put("transfer_amount",detail.getTotalDetailMoney().multiply(new BigDecimal("100")).intValue());
            detailMap.put("transfer_remark",detail.getDetailRemark());
            detailMap.put("openid",detail.getOpenid());
            detailList.add(detailMap);
        }
        paramsMap.put("transfer_detail_list", detailList);
        return paramsMap;
    }
}

本人针对支付方式做了一个简单的通用类(包含native方式、jsapi方式、app方式),如果用不到可不采用

支付通用参数

/**
 * @author lihao
 * @create 2023-03-22 17:48
 * @desc 微信支付通用参数
 **/
@Data
public class WeChatBasePayData {

    /**
     * 商品描述
     */
    private String title;

    /**
     * 商家订单号,对应 out_trade_no
     */
    private String orderId;

    /**
     * 订单金额
     */
    private BigDecimal price;

    /**
     * 回调地址
     */
    private String notify;

}

微信支付通用配置

/**
 * @author lihao
 * @create 2023-03-22 17:45
 * @desc 微信通用配置
 **/
@Slf4j
public class WxPayCommon {

    /**
     * 创建微信支付订单-Native方式
     *
     * @param wxPayConfig 微信配置信息
     * @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
     * @param wxPayClient 微信请求客户端
     * @return 微信支付二维码地址
     */
    public static String wxNativePay(WxPayConfig wxPayConfig, WeChatBasePayData basePayData, CloseableHttpClient wxPayClient) {
        // 1.获取请求参数的Map格式
        Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, basePayData);

        // 2.获取请求对象
        HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.NATIVE_PAY, paramsMap);

        // 3.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("微信支付请求失败");
        }

        // 4.解析response对象
        HashMap<String, String> resultMap = resolverResponse(response);
        if (resultMap != null) {
            // native请求返回的是二维码链接,前端将链接转换成二维码即可
            return resultMap.get("code_url");
        }
        return null;
    }


    /**
     * 创建微信支付订单-jsapi方式
     *
     * @param wxPayConfig 微信配置信息
     * @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
     * @param openId 通过微信小程序或者公众号获取到用户的openId
     * @param wxPayClient 微信请求客户端
     * @return 微信支付二维码地址
     */
    public static String wxJsApiPay(WxPayConfig wxPayConfig, WeChatBasePayData basePayData, String openId,CloseableHttpClient wxPayClient) {
        // 1.获取请求参数的Map格式
        Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, basePayData);
        // 1.1 添加支付者信息
        Map<String,String> payerMap = new HashMap();
        payerMap.put("openid",openId);
        paramsMap.put("payer",payerMap);

        // 2.获取请求对象
        HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.JSAPI_PAY, paramsMap);

        // 3.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("微信支付请求失败");
        }

        // 4.解析response对象
        HashMap<String, String> resultMap = resolverResponse(response);
        if (resultMap != null) {
            // native请求返回的是二维码链接,前端将链接转换成二维码即可
            return resultMap.get("prepay_id");
        }
        return null;
    }


    /**
     * 创建微信支付订单-APP方式
     *
     * @param wxPayConfig 微信配置信息
     * @param basePayData 基础请求信息,商品标题、商家订单id、订单价格
     * @param wxPayClient 微信请求客户端
     * @return 微信支付二维码地址
     */
    public static String wxAppPay(WxPayConfig wxPayConfig, WeChatBasePayData basePayData, CloseableHttpClient wxPayClient) {
        // 1.获取请求参数的Map格式
        Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, basePayData);

        // 2.获取请求对象
        HttpPost httpPost = getHttpPost(wxPayConfig, WxApiType.APP_PAY, paramsMap);

        // 3.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("微信支付请求失败");
        }

        // 4.解析response对象
        HashMap<String, String> resultMap = resolverResponse(response);
        if (resultMap != null) {
            // native请求返回的是二维码链接,前端将链接转换成二维码即可
            return resultMap.get("prepay_id");
        }
        return null;
    }

    /**
     * 解析响应数据
     * @param response 发送请求成功后,返回的数据
     * @return 微信返回的参数
     */
    private static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
        try {
            // 1.获取请求码
            int statusCode = response.getStatusLine().getStatusCode();
            // 2.获取返回值 String 格式
            final String bodyAsString = EntityUtils.toString(response.getEntity());

            Gson gson = new Gson();
            if (statusCode == 200) {
                // 3.如果请求成功则解析成Map对象返回
                HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                return resultMap;
            } else {
                if (StringUtils.isNoneBlank(bodyAsString)) {
                    log.error("微信支付请求失败,提示信息:{}", bodyAsString);
                    // 4.请求码显示失败,则尝试获取提示信息
                    HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                    throw new CommonException(resultMap.get("message"));
                }
                log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
                // 其他异常,微信也没有返回数据,这就需要具体排查了
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new CommonException(e.getMessage());
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 获取请求对象(Post请求)
     *
     * @param wxPayConfig 微信配置类
     * @param apiType     接口请求地址
     * @param paramsMap   请求参数
     * @return Post请求对象
     */
    private static HttpPost getHttpPost(WxPayConfig wxPayConfig, WxApiType apiType, Map<String, Object> paramsMap) {
        // 1.设置请求地址
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(apiType.getType()));

        // 2.设置请求数据
        Gson gson = new Gson();
        String jsonParams = gson.toJson(paramsMap);

        // 3.设置请求信息
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        return httpPost;
    }

    /**
     * 封装基础通用请求参数
     *
     * @param wxPayConfig 微信的配置文件
     * @param basePayData 微信支付基础请求数据
     * @return 封装后的map对象
     */
    private static Map<String, Object> getBasePayParams(WxPayConfig wxPayConfig, WeChatBasePayData basePayData) {
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        // 如果商品名称过长则截取
        String title = basePayData.getTitle().length() > 62 ? basePayData.getTitle().substring(0, 62) : basePayData.getTitle();
        paramsMap.put("description", title);
        paramsMap.put("out_trade_no", basePayData.getOrderId());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain());
        Map<String, Integer> amountMap = new HashMap<>();
        amountMap.put("total", basePayData.getPrice().multiply(new BigDecimal("100")).intValue());
        paramsMap.put("amount", amountMap);
        return paramsMap;
    }


}