我对接的就是服务商模式,如果是对接普通商户模式自己去掉子商户的参数就可以了
直接上干货
POM引用
<dependency>
			<groupId>com.github.wechatpay-apiv3</groupId>
			<artifactId>wechatpay-apache-httpclient</artifactId>
			<version>0.2.1</version>
		</dependency>
		<!-- OKHttp3依赖 -->
		<dependency>
			<groupId>com.squareup.okhttp3</groupId>
			<artifactId>okhttp</artifactId>
			<version>3.8.1</version>
		</dependency>Entity
            
            
              java
              
              
            
          
          import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//统一下单参数
@Setter
@Getter
@Component
public class WXPayConfigEntity  {
    public enum SignType {
        MD5, HMACSHA256
    }
    //应用ID
    @Value(value = "${wx.appid}")
    private  String apppid;
    //商户ID
    @Value(value = "${wx.mchid}")
    private  String mch_id;
    //二级应用ID
    private  String sub_apppid = "暂时没有";
    //二级商户ID
    @Value(value = "${wx.sub_mchid}")
    private  String sub_mchid;
    //签名
    private  String sign;
    //终端IP
    @Value(value = "${wx.payer_client_ip}")
    private  String payer_client_ip;
    //API密钥key
    @Value(value = "${wx.apikey}")
    private  String apiKey ;
    //APIV3密钥key
    @Value(value = "${wx.apiv3key}")
    private  String apiV3Key;
    //支付通知回调地址
    @Value(value = "${wx.notify.url}")
    private  String notify_url;
    //支付公钥pash
    @Value(value = "${v3.certPath}")
    private  String certPath;
    //支付支付私钥pash
    @Value(value = "${v3.keyPath}")
    private  String keyPath;
    //支付证书pash
    @Value(value = "${v3.p12}")
    private  String p12;
    //平台证书pash
    @Value(value = "${v3.platformcertificate}")
    private  String platformcertificate;
    //支付证书序列号
    @Value(value = "${v3.serialNo}")
    private  String serialNo;
    //平台证书获取接口路径
    @Value(value = "${v3.certificates}")
    private  String certificates;
    //终端IP
    @Value(value = "${wx.appserect}")
    private  String appserect;
    //退款通知回调地址
    @Value(value = "${wx.refundnotify.url}")
    private  String refundnotify_url;
    //交易类型
    private final String tradetype = "APP";
    String authType   ="WECHATPAY2-SHA256-RSA2048";
    //APP下单支付路径
    private final String payurl ="https://api.mch.weixin.qq.com/v3/pay/partner/transactions/app";
    //H5下单支付路径
    private final String payH5url ="https://api.mch.weixin.qq.com/v3/pay/partner/transactions/h5";
    //Native下单支付路径
    private final String payNativeurl ="https://api.mch.weixin.qq.com/v3/pay/partner/transactions/native";
    //jspapi下单支付路径
    private final String payJSAPIurl ="https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi";
    //查询订单
    private final String paySelectUrl= "https://api.mch.weixin.qq.com/v3/pay/partner/transactions/id/{transaction_id}";
    //申请退款
    private final String payRefundurl ="https://api.mch.weixin.qq.com/secapi/pay/refund";
   
     //查询退款
    private final String RefundSelecturl="https://api.mch.weixin.qq.com/pay/refundquery";
}Controller
微信支付接口,我是根据微信文档自己写的.
V3接口提供自动签名和验签自动获取最新平台证书的方法,算是目前最新的方法
            
            
              java
              
              
            
          
          import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.mc.common.constant.CommonConstant;
import com.mc.common.exception.BusinessException;
import com.mc.common.utils.ResultUtils;
import com.mc.common.vo.Result;
import com.mc.jewelrybespoke.app.service.AppSkuService;
import com.mc.jewelrybespoke.app.wxutil.*;
import com.mc.jewelrybespoke.app.po.WeiXinPayPo;
import com.mc.jewelrybespoke.app.utils.LocalDateTimeUtil;
import com.mc.jewelrybespoke.app.vo.AppSalesOrderHeaderVo;
import com.mc.jewelrybespoke.entity.SalesOrderDetail;
import com.mc.jewelrybespoke.entity.SalesOrderHeader;
import com.mc.jewelrybespoke.entity.Sku;
import com.mc.jewelrybespoke.query.SalesOrderDetailQuery;
import com.mc.jewelrybespoke.service.ISalesOrderDetailService;
import com.mc.jewelrybespoke.service.ISalesOrderHeaderService;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.*;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
@Api(tags = "5.0 微信支付对接")
@RestController
@RequestMapping("/weixin/pay")
public class WeiXinPayController {
    @Autowired
    private WXPayConfigEntity wxPayConfigEntity; //支付参数
    @Autowired
    WXPayUtil wxPayUtil;
    @Autowired
    private ISalesOrderHeaderService salesOrderHeaderService;
    @Autowired
    private ISalesOrderDetailService salesOrderDetailService;
    @Autowired
    private AppSkuService appSkuService;
    @Autowired
    private ClientCustomSSL clientCustomSSL;  //证书
    private long timestamp = LocalDateTimeUtil.getLocalDateTimeSec();
    private String nonceStr = RandomStringUtils.randomAlphanumeric(32);
    private static Lock lock = new ReentrantLock();
    public static final String POST = "POST";
    public static final String GET = "GET";
    @ApiOperation(value = "微信App下单,返回微信预付款订单号", notes = "传入付款金额,订单号 ,商品信息")
    @PostMapping("/create/wxorder")
    public Object createWXOrder(@RequestBody WeiXinPayPo weiXinPayPo) {
        String out_trade_no = weiXinPayPo.getOut_trade_no();//商品订单号
        SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(out_trade_no);
        if (salesOrderHeader == null || StringUtils.isEmpty(salesOrderHeader.getId())) {
            return new ResultUtils<AppSalesOrderHeaderVo>().setErrorMsg("订单不存在!");
        }
        if (!CommonConstant.STATUS_DRAFT.equals(salesOrderHeader.getStatus())) {
            return new ResultUtils<AppSalesOrderHeaderVo>().setErrorMsg("订单状态异常,请确认订单状态!");
        }
        SortedMap<String, Object> sortedMap = new TreeMap<String, Object>();
        sortedMap.put("sp_appid", wxPayConfigEntity.getApppid()); //应用ID
        sortedMap.put("sp_mchid", wxPayConfigEntity.getMch_id()); //商户号
        sortedMap.put("sub_mchid", wxPayConfigEntity.getSub_mchid());//子商户号
        sortedMap.put("description", "珠宝纯定制-" + weiXinPayPo.getBody()); //商品描述
        sortedMap.put("out_trade_no", out_trade_no); //商户订单号
        sortedMap.put("notify_url", wxPayConfigEntity.getNotify_url()); //通知回调地址
        //总金额
        int totalFee = weiXinPayPo.getTotal_fee().multiply(new BigDecimal(100)).intValue();//总金额  需要乘以100 ,微信支付默认单位分
        Map<String, Object> map1 = new HashMap<>();
        map1.put("total", totalFee);
        map1.put("currency", "CNY");
        //订单金额
        sortedMap.put("amount", map1);
        JSONObject json = new JSONObject(sortedMap);
        String reqdata = json.toJSONString();
        log.info(reqdata);
        //校验支付路径
        // HttpUrl httpurl = HttpUrl.parse(wxPayConfigEntity.getPayurl());
        //String Sign = null;
        //  Sign = wxPayUtil.getToken(nonceStr,timestamp,POST,httpurl,reqdata);
        //======设置请求头========
        //不需要传入微信支付证书,将会自动更新
        // 建议从Verifier中获得微信支付平台证书,或使用预先下载到本地的平台证书文件中
        // X509Certificate wechatpayCertificate = verifier.getValidCertificate();
        AutoUpdateCertificatesVerifier verifier = wxPayUtil.getAutoUpdateCertificatesVerifier();
        //通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
        CloseableHttpClient httpClient = wxPayUtil.getCloseableHttpClient();
        HttpPost httpPost = new HttpPost(wxPayConfigEntity.getPayurl());
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000).setConnectTimeout(30000).build();
        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-Type", "application/json");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
        String authType = wxPayConfigEntity.getAuthType();
        //String authorization = wxPayUtil.getAuthorization(Sign, authType);
        //httpPost.addHeader("Authorization",authorization);
        StringEntity entity = new StringEntity(reqdata, "UTF-8");
        httpPost.setEntity(entity);
        //======发起请求========
        CloseableHttpResponse response = null;
        String result = null;
        try {
            response = httpClient.execute(httpPost);
            result = EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
        Header firstHeader = response.getFirstHeader("Request-ID");
        String s = firstHeader.toString();
        log.info("Request-ID:" + s);
        int statusCode = response.getStatusLine().getStatusCode();
        Map<String, Object> map = new HashMap<>();
        if (statusCode == 200) {
            Map<String, Object> resultMap = JSONObject.parseObject(result, Map.class);
            String prepayId = (String) resultMap.get("prepay_id");//微信下单号
            salesOrderHeader.setPayAmount(weiXinPayPo.getTotal_fee());
            salesOrderHeader.setRefNo(prepayId);
            salesOrderHeader.setPaid(OrdersOperateEnum.SUBMIT.code);
            salesOrderHeader.setIsChannel(weiXinPayPo.getIsChannel());
            salesOrderHeader.setPayType(weiXinPayPo.getPayType());
            salesOrderHeaderService.update(salesOrderHeader); //根据付款单号修改订单表保存微信需要付款总金额,支付微信下单号
            String sign2 = null;
            try {
                sign2 = wxPayUtil.getToken2(nonceStr, timestamp, prepayId, wxPayConfigEntity.getSub_apppid());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (SignatureException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            }
            map.put("return_code", "操作成功");
            map.put("appid", wxPayConfigEntity.getSub_apppid());
            map.put("partnerid", wxPayConfigEntity.getSub_mchid());
            map.put("prepayid", prepayId);
            map.put("package", "Sign=WXPay");
            map.put("noncestr", nonceStr);
            map.put("timestamp", timestamp);
            map.put("paySign", sign2);
            return map;
        }
        map.put("return_code", result);
        return map;
    }
    /**
     * 微信支付回调地址
     */
    @ApiOperation(value = "微信支付回调地址")
    @PostMapping("/callback/wxorder")
    public Map<String, String> callBack(HttpServletRequest request) {
        log.info("这里是微信支付回调!");
        Map<String, String> map = new HashMap<>(2);
        try {
            PrivateKey privateKey = wxPayUtil.getPrivateKey(wxPayConfigEntity.getKeyPath());
            //微信返回的请求体
            String body = wxPayUtil.getRequestBody(request);
            //如果验证签名序列号通过
            if (wxPayUtil.verifiedSign(request, body, privateKey)) {
                //微信支付通知实体类
                WXPayNotifyVO wxPayNotifyVO = JSONObject.parseObject(body, WXPayNotifyVO.class);
                //如果支付成功
                if ("TRANSACTION.SUCCESS".equals(wxPayNotifyVO.getEvent_type())) {
                    //通知资源数据
                    WXPayNotifyVO.Resource resource = wxPayNotifyVO.getResource();
                    //解密后资源数据
                    String notifyResourceStr = PayResponseUtils.decryptResponseBody(wxPayConfigEntity.getApiV3Key(), resource.getAssociated_data(), resource.getNonce(), resource.getCiphertext());
                    //通知资源数据对象
                    NotifyResourceVO notifyResourceVO = JSONObject.parseObject(notifyResourceStr, NotifyResourceVO.class);
                    //查询订单 可以优化把订单放入redis
                    SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(notifyResourceVO.getOut_trade_no());//根据付款单号查询订单表保存微信需要付款总金额
                    //支付完成时间
                    String success_time = notifyResourceVO.getSuccess_time();
                    LocalDateTime localDateTime = LocalDateTimeUtil.string3LocalDateTime(success_time);
                    //微信支付订单号
                    String transactionId = notifyResourceVO.getTransaction_id();
                    if (salesOrderHeader != null) {
                        //如果订单状态是提交状态
                        if (OrdersOperateEnum.SUBMIT.code == salesOrderHeader.getPaid() || OrdersOperateEnum.USERPAYING.code == salesOrderHeader.getPaid()) {
                            //如果付款成功
                            if ("SUCCESS".equals(notifyResourceVO.getTrade_state())) {
                                Integer total = notifyResourceVO.getAmount().getTotal();
                                Integer payer_total = notifyResourceVO.getAmount().getPayer_total();
                                int totalAmount = salesOrderHeader.getTotalAmount().multiply(new BigDecimal(100)).intValue();
                                if (totalAmount == total && payer_total == total) {
                                    salesOrderHeader.setPayTime(localDateTime);
                                    salesOrderHeader.setRefNo(transactionId);
                                    salesOrderHeader.setPaid(OrdersOperateEnum.SUCCESS.code);
                                    salesOrderHeader.setStatus(CommonConstant.STATUS_PENDING);
                                    BigDecimal payAmount = BigDecimal.valueOf(payer_total).divide(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
                                    salesOrderHeader.setPayAmount(payAmount);
                                    salesOrderHeader.setModifiedDate(LocalDateTimeUtil.getLocalDateTime());
                                    salesOrderHeaderService.update(salesOrderHeader);
                                } else {
                                    map.put("code", "ERROR_AMOUNT");
                                    map.put("message", "支付金额与订单金额不匹配");
                                    return map;
                                }
                            } else {
                                salesOrderHeader.setPayTime(localDateTime);
                                salesOrderHeader.setRefNo(transactionId);
                                //支付状态code
                                Integer oneCode = OrdersOperateEnum.getOneCode(notifyResourceVO.getTrade_state());
                                salesOrderHeader.setPaid(oneCode);
                                salesOrderHeader.setModifiedDate(LocalDateTimeUtil.getLocalDateTime());
                                salesOrderHeaderService.update(salesOrderHeader);
                                map.put("code", "SUCCESS");
                                map.put("message", "");
                                return map;
                            }
                        }
                    } else {
                        log.info("微信返回支付错误摘要:" + wxPayNotifyVO.getSummary());
                    }
                    //通知微信正常接收到消息,否则微信会轮询该接口
                    map.put("code", "SUCCESS");
                    map.put("message", "");
                    return map;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        } catch (RequestWechatException e) {
            e.printStackTrace();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        map.put("code", "ERROR_SIGN");
        map.put("message", "验签不通过");
        return map;
    }
    /**
     * 微信支付获取用户open_id的回调
     */
    @ApiOperation(value = "微信支付获取用户open_id的回调")
    @PostMapping("/callback/wxauth")
    public String wxauth(@RequestParam("code") String code) {
        String geturl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + wxPayConfigEntity.getApppid() + "&secret=" + wxPayConfigEntity.getAppserect() + "&code=" + code + "&grant_type=authorization_code";
        RestTemplate restTemplate = new RestTemplate();
        String result = restTemplate.getForObject(geturl, String.class);
        log.info(result);
        return result;
    }
    @ApiOperation(value = "微信H5下单,返回微信H5_url", notes = "传入付款金额,订单号 ,商品信息")
    @PostMapping("/create/wxH5order")
    public Object createWXH5Order(@RequestBody WeiXinPayPo weiXinPayPo) {
        String out_trade_no = weiXinPayPo.getOut_trade_no();//商品订单号
        SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(out_trade_no);
        if (salesOrderHeader == null || StringUtils.isEmpty(salesOrderHeader.getId())) {
            return new ResultUtils<AppSalesOrderHeaderVo>().setErrorMsg("订单不存在!");
        }
        if (!CommonConstant.STATUS_DRAFT.equals(salesOrderHeader.getStatus())) {
            return new ResultUtils<AppSalesOrderHeaderVo>().setErrorMsg("订单状态异常,请确认订单状态!");
        }
        SortedMap<String, Object> sortedMap = new TreeMap<String, Object>();
        sortedMap.put("sp_appid", wxPayConfigEntity.getApppid()); //应用ID
        sortedMap.put("sp_mchid", wxPayConfigEntity.getMch_id()); //商户号
        sortedMap.put("sub_mchid", wxPayConfigEntity.getSub_mchid());//子商户号
        sortedMap.put("description", "珠宝纯定制-H5-" + weiXinPayPo.getBody()); //商品描述
        sortedMap.put("out_trade_no", out_trade_no); //商户订单号
        sortedMap.put("notify_url", wxPayConfigEntity.getNotify_url()); //通知回调地址
        //总金额
        int totalFee = weiXinPayPo.getTotal_fee().multiply(new BigDecimal(100)).intValue();//总金额  需要乘以100 ,微信支付默认单位分
        Map<String, Object> map1 = new HashMap<>();
        map1.put("total", totalFee);
        map1.put("currency", "CNY");
        //订单金额
        sortedMap.put("amount", map1);
        Map map2 = new HashMap();
        map2.put("payer_client_ip", wxPayConfigEntity.getPayer_client_ip());
        Map map3 = new HashMap();
        map3.put("type", "iOS, Android, Wap");
        map2.put("h5_info", map3);
        //场景信息
        sortedMap.put("scene_info", map2);
        JSONObject json = new JSONObject(sortedMap);
        String reqdata = json.toJSONString();
        log.info(reqdata);
        //校验支付路径
        // HttpUrl httpurl = HttpUrl.parse(wxPayConfigEntity.getPayurl());
        // 请求body参数
        //String Sign = null;
        //  Sign = wxPayUtil.getToken(nonceStr,timestamp,POST,httpurl,reqdata);
        //======设置请求头========
        AutoUpdateCertificatesVerifier verifier = wxPayUtil.getAutoUpdateCertificatesVerifier();
        //通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
        CloseableHttpClient httpClient = wxPayUtil.getCloseableHttpClient();
        HttpPost httpPost = new HttpPost(wxPayConfigEntity.getPayH5url());
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000).setConnectTimeout(30000).build();
        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-Type", "application/json");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
        String authType = wxPayConfigEntity.getAuthType();
        //String authorization = wxPayUtil.getAuthorization(Sign, authType);
        //httpPost.addHeader("Authorization",authorization);
        StringEntity entity = new StringEntity(reqdata, "UTF-8");
        httpPost.setEntity(entity);
        //======发起请求========
        CloseableHttpResponse response = null;
        String result = null;
        try {
            response = httpClient.execute(httpPost);
            result = EntityUtils.toString(response.getEntity());
        } catch (IOException e) {
            e.printStackTrace();
        }
        Header[] allHeaders = response.getAllHeaders();
        Header firstHeader = response.getFirstHeader("Request-ID");
        String s = firstHeader.toString();
        log.info("Request-ID:" + s);
        int statusCode = response.getStatusLine().getStatusCode();
        Map<String, Object> map = new HashMap<>();
        if (statusCode == 200) {
            Map<String, Object> resultMap = JSONObject.parseObject(result, Map.class);
            String h5_url = (String) resultMap.get("h5_url");//支付跳转链接
            log.info("h5_url:" + h5_url);
            salesOrderHeader.setPayAmount(weiXinPayPo.getTotal_fee());
            String prepay_id = PayResponseUtils.getParam(h5_url, "prepay_id");
            salesOrderHeader.setRefNo(prepay_id);
            salesOrderHeader.setPaid(OrdersOperateEnum.SUBMIT.code);
            salesOrderHeader.setIsChannel(weiXinPayPo.getIsChannel());
            salesOrderHeader.setPayType(weiXinPayPo.getPayType());
            salesOrderHeaderService.update(salesOrderHeader); //根据付款单号修改订单表保存微信需要付款总金额,支付微信下单号
            map.put("return_code", "操作成功");
            map.put("h5_url", h5_url);
            return map;
        }
        map.put("return_code", result);
        return map;
    }
    @ApiOperation(value = "微信JSAPI下单", notes = "传入付款金额,订单号 ,商品信息")
    @PostMapping("/create/wxJSAPIorder")
    public Object createWXJSAPIOrder(@RequestBody WeiXinPayPo weiXinPayPo) {
        String out_trade_no = weiXinPayPo.getOut_trade_no();//商品订单号
        SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(out_trade_no);
        if (salesOrderHeader == null || StringUtils.isEmpty(salesOrderHeader.getId())) {
            return new ResultUtils<AppSalesOrderHeaderVo>().setErrorMsg("订单不存在!");
        }
        if (!CommonConstant.STATUS_DRAFT.equals(salesOrderHeader.getStatus())) {
            return new ResultUtils<AppSalesOrderHeaderVo>().setErrorMsg("订单状态异常,请确认订单状态!");
        }
        SortedMap<String, Object> sortedMap = new TreeMap<String, Object>();
        sortedMap.put("sp_appid", wxPayConfigEntity.getApppid()); //应用ID
        sortedMap.put("sp_mchid", wxPayConfigEntity.getMch_id()); //商户号
        sortedMap.put("sub_mchid", wxPayConfigEntity.getSub_mchid());//子商户号
        sortedMap.put("description", "珠宝纯定制-JSAPI-" + weiXinPayPo.getBody()); //商品描述
        sortedMap.put("out_trade_no", out_trade_no); //商户订单号
        sortedMap.put("notify_url", wxPayConfigEntity.getNotify_url()); //通知回调地址
        //总金额
        int totalFee = weiXinPayPo.getTotal_fee().multiply(new BigDecimal(100)).intValue();//总金额  需要乘以100 ,微信支付默认单位分
        Map<String, Object> map1 = new HashMap<>();
        map1.put("total", totalFee);
        map1.put("currency", "CNY");
        //订单金额
        sortedMap.put("amount", map1);
        Map map2 = new HashMap();
        map2.put("sp_openid", weiXinPayPo.getSpOpenid());
        //支付者
        sortedMap.put("payer", map2);
        JSONObject json = new JSONObject(sortedMap);
        String reqdata = json.toJSONString();
        log.info(reqdata);
        //校验支付路径
        // HttpUrl httpurl = HttpUrl.parse(wxPayConfigEntity.getPayurl());
        // 请求body参数
        //String Sign = null;
        //  Sign = wxPayUtil.getToken(nonceStr,timestamp,POST,httpurl,reqdata);
        //获取最新的平台证书
        //AutoUpdateCertificatesVerifier verifier = wxPayUtil.getAutoUpdateCertificatesVerifier();
        //通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
        CloseableHttpClient httpClient = wxPayUtil.getCloseableHttpClient();
        HttpPost httpPost = new HttpPost(wxPayConfigEntity.getPayJSAPIurl());
        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000).setConnectTimeout(30000).build();
        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-Type", "application/json");
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
        //String authType = wxPayConfigEntity.getAuthType();
        //String authorization = wxPayUtil.getAuthorization(Sign, authType);
        //httpPost.addHeader("Authorization",authorization);
        StringEntity entity = new StringEntity(reqdata, "UTF-8");
        httpPost.setEntity(entity);
        //======发起请求========
        CloseableHttpResponse response = null;
        String result = null;
        try {
            response = httpClient.execute(httpPost);
            result = EntityUtils.toString(response.getEntity());
        } catch (IOException e) {
            e.printStackTrace();
        }
        Header firstHeader = response.getFirstHeader("Request-ID");
        String s = firstHeader.toString();
        log.info("Request-ID:" + s);
        int statusCode = response.getStatusLine().getStatusCode();
        Map<String, Object> map = new HashMap<>();
        if (statusCode == 200) {
            Map<String, Object> resultMap = JSONObject.parseObject(result, Map.class);
            String prepayId = (String) resultMap.get("prepay_id");//微信下单号
            salesOrderHeader.setPayAmount(weiXinPayPo.getTotal_fee());
            salesOrderHeader.setRefNo(prepayId);
            salesOrderHeader.setPaid(OrdersOperateEnum.SUBMIT.code);
            salesOrderHeader.setIsChannel(weiXinPayPo.getIsChannel());
            salesOrderHeader.setPayType(weiXinPayPo.getPayType());
            salesOrderHeaderService.update(salesOrderHeader); //根据付款单号修改订单表保存微信需要付款总金额,支付微信下单号
            String packages = "prepay_id=" + prepayId;
            String sign2 = null;
            try {
                sign2 = wxPayUtil.getToken3(packages, timestamp, nonceStr, wxPayConfigEntity.getApppid());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (SignatureException e) {
                e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (InvalidKeyException e) {
                e.printStackTrace();
            }
            map.put("return_code", "操作成功");
            map.put("appId", wxPayConfigEntity.getApppid());
            map.put("timeStamp", timestamp);
            map.put("nonceStr", nonceStr);
            map.put("package", packages);
            map.put("signType", "RSA");
            map.put("paySign", sign2);
            return map;
        }
        map.put("return_code", "操作失败");
        return map;
    }
    @ApiOperation(value = "关闭订单")
    @PostMapping("/create/closeOrder")
    public Result closeOrder(@RequestParam("orderId") String orderId) throws RequestWechatException {
        //查询订单 可以优化把订单放入redis
        SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(orderId);
        //如果是付款 返回成功
        if (salesOrderHeader == null || StringUtils.isEmpty(salesOrderHeader.getId()) ||
                salesOrderHeader.getStatus().equals(CommonConstant.STATUS_DELETE)) {
            return new ResultUtils<>().setErrorMsg("订单不存在");
        } else if (salesOrderHeader != null && OrdersOperateEnum.CLOSED.code == salesOrderHeader.getPaid()) {
            return new ResultUtils<>().setSuccessMsg("订单已关闭");
        } else {
            //未付款 请求微信 获取订单状态
            String url = String.format("https://api.mch.weixin.qq.com/v3/pay/partner/transactions/out-trade-no/", salesOrderHeader.getId(), "/close");
            AutoUpdateCertificatesVerifier verifier = wxPayUtil.getAutoUpdateCertificatesVerifier();
            //通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
            CloseableHttpClient httpClient = wxPayUtil.getCloseableHttpClient();
            HttpPost httpPost = new HttpPost(url);
            RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(30000).setConnectTimeout(30000).build();
            httpPost.setConfig(requestConfig);
            httpPost.addHeader("Content-Type", "application/json");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36");
            Map map = new HashMap();
            map.put("sp_mchid", wxPayConfigEntity.getMch_id());
            map.put("sub_mchid", wxPayConfigEntity.getSub_mchid());
            JSONObject json = new JSONObject(map);
            String reqdata = json.toJSONString();
            StringEntity entity = new StringEntity(reqdata, "UTF-8");
            httpPost.setEntity(entity);
            HttpResponse response = null;
            try {
                response = httpClient.execute(httpPost);
            } catch (IOException e) {
                throw new RequestWechatException();
            }
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 204) {
                salesOrderHeader.setPaid(OrdersOperateEnum.CLOSED.code);
                salesOrderHeaderService.update(salesOrderHeader);
                return new ResultUtils<>().setErrorMsg("订单关闭成功");
            }
            return new ResultUtils<>().setErrorMsg("订单关闭失败");
        }
    }
    @ApiOperation(value = "查验订单")
    @GetMapping("/create/selectOrder")
    @Transactional
    public Result selectOrder(@RequestParam("orderId") String orderId) throws RequestWechatException {
//        return CallbackResult.getSuccessResult("500");
        //查询订单 可以优化把订单放入redis
        SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(orderId);
        //如果是付款 返回成功
        if (salesOrderHeader == null || StringUtils.isEmpty(salesOrderHeader.getId()) ||
                salesOrderHeader.getStatus().equals(CommonConstant.STATUS_DELETE)) {
            return new ResultUtils<>().setErrorMsg("订单不存在");
        } else if (salesOrderHeader != null && OrdersOperateEnum.SUCCESS.code == salesOrderHeader.getPaid()) {
            return new ResultUtils<>().setSuccessMsg("付款成功");
        } else {
            //未付款 请求微信 获取订单状态
            String url = String.format(wxPayConfigEntity.getPaySelectUrl(), salesOrderHeader.getRefNo(),
                    "?", "sp_mchid=", wxPayConfigEntity.getMch_id(), "&", "sub_mchid=", wxPayConfigEntity.getSub_mchid());
            AutoUpdateCertificatesVerifier verifier = wxPayUtil.getAutoUpdateCertificatesVerifier();
            //通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
            CloseableHttpClient httpClient = wxPayUtil.getCloseableHttpClient();
            HttpGet httpGet = new HttpGet(url);
            httpGet.addHeader("Content-Type", "application/json;charset=UTF-8");
            httpGet.addHeader("Accept", "application/json");
            HttpResponse response = null;
            try {
                response = httpClient.execute(httpGet);
            } catch (IOException e) {
                throw new RequestWechatException();
            }
            //如果状态码为200,就是正常返回
            if (response.getStatusLine().getStatusCode() == 200) {
                String result = "";
                try {
                    result = EntityUtils.toString(response.getEntity(), "UTF-8");
                    JSONObject jsonObject = JSON.parseObject(result);
                    NotifyResourceVO notifyResourceVO = JSONObject.parseObject(jsonObject.toString(), NotifyResourceVO.class);
                    //支付完成时间
                    String success_time = notifyResourceVO.getSuccess_time();
                    LocalDateTime localDateTime = LocalDateTimeUtil.string3LocalDateTime(success_time);
                    //微信支付订单号
                    String transactionId = notifyResourceVO.getTransaction_id();
                    //如果付款成功
                    if ("SUCCESS".equals(notifyResourceVO.getTrade_state())) {
                        Integer total = notifyResourceVO.getAmount().getTotal();
                        Integer payer_total = notifyResourceVO.getAmount().getPayer_total();
                        int totalAmount = salesOrderHeader.getTotalAmount().multiply(new BigDecimal(100)).intValue();
                        if (totalAmount == total && payer_total == total) {
                            salesOrderHeader.setPayTime(localDateTime);
                            salesOrderHeader.setRefNo(transactionId);
                            salesOrderHeader.setPaid(OrdersOperateEnum.SUCCESS.code);
                            salesOrderHeader.setStatus(CommonConstant.STATUS_PENDING);
                            BigDecimal payAmount = BigDecimal.valueOf(payer_total).divide(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
                            salesOrderHeader.setPayAmount(payAmount);
                            salesOrderHeader.setModifiedDate(LocalDateTimeUtil.getLocalDateTime());
                            salesOrderHeaderService.update(salesOrderHeader);
                            return new ResultUtils<>().setSuccessMsg(notifyResourceVO.getTrade_state_desc());
                        } else {
                            return new ResultUtils<>().setSuccessMsg("支付金额与订单金额不匹配");
                        }
                    } else {
                        return new ResultUtils<>().setSuccessMsg(notifyResourceVO.getTrade_state_desc());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return new ResultUtils<>().setSuccessMsg("500");
    }
    @ApiOperation(value = "微信退款")
    @PostMapping("/create/refundOrder")
    public Object refundOrder(@RequestBody WeiXinPayPo weiXinPayPo) throws Exception {
        //查询订单 可以优化把订单放入redis
        SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(weiXinPayPo.getOut_trade_no());
        //如果是付款 返回成功
        if (salesOrderHeader == null || StringUtils.isEmpty(salesOrderHeader.getId()) ||
                salesOrderHeader.getStatus().equals(CommonConstant.STATUS_DELETE)) {
            return new ResultUtils<>().setErrorMsg("订单不存在");
        } else if (salesOrderHeader != null && OrdersOperateEnum.CLOSED.code == salesOrderHeader.getPaid()) {
            return new ResultUtils<>().setSuccessMsg("订单已关闭");
        } else {
            SortedMap<String, String> sortedMap = new TreeMap<String, String>();
            BigDecimal totalAmount = salesOrderHeader.getTotalAmount();
            //订单总金额 金额单位为分 *100
            String totalFee = totalAmount.multiply(new BigDecimal(100)).toString().replace(".00","");
            sortedMap.put("appid", wxPayConfigEntity.getApppid()); //应用ID
            sortedMap.put("mch_id", wxPayConfigEntity.getMch_id()); //商户号
            sortedMap.put("sub_mch_id", wxPayConfigEntity.getSub_mchid());//子商户号
            sortedMap.put("nonce_str", nonceStr);
            sortedMap.put("transaction_id", salesOrderHeader.getRefNo());
            //sortedMap.put("out_trade_no", salesOrderHeader.getId());
            sortedMap.put("out_refund_no", salesOrderHeader.getId());
            sortedMap.put("total_fee", totalFee);
            sortedMap.put("refund_fee", totalFee);
            sortedMap.put("notify_url",wxPayConfigEntity.getRefundnotify_url());
            //生成签名
            String sign = wxPayUtil.generateSignature(sortedMap, wxPayConfigEntity.getApiKey(),WXPayConfigEntity.SignType.MD5);
            sortedMap.put("sign", sign);
            log.info("发送给微信的参数:" + sortedMap);
            String xml = WXPayUtil.getRequestXml(sortedMap);
            log.info("===>>>发送给微信的xml:" + xml);
            String string = clientCustomSSL.doRefund(wxPayConfigEntity.getPayRefundurl(), xml);
            System.out.println("微信返回的xml===>>>" + string);
            //AutoUpdateCertificatesVerifier verifier = wxPayUtil.getAutoUpdateCertificatesVerifier();
            //因为退款是V2接口,所以先不用CloseableHttpClient自动签名处理发起请求,就用普通的HttpClient发起请求
            boolean bool = wxPayUtil.isSignatureValid(string, wxPayConfigEntity.getApiKey());//验证签名是否正确
            Map<String, String> map = new HashMap<String, String>();
            if (bool) {
                Map<String, String> resultMap = wxPayUtil.xmlToMap(string);
                if ("SUCCESS".equals(resultMap.get("return_code"))) { //返回成功 退款成功
                    map.put("refund_fee", resultMap.get("refund_fee"));//退款金额
                    map.put("out_refund_no", resultMap.get("out_refund_no")); //退款单号
                    map.put("message", "SUCCESS");//退款成功
                    return map;
                }
            }
            map.put("message", "faild"); //退款失败
            return map;
        }
    }
    @ApiOperation(value = "微信退款回调地址")
    @PostMapping("/callback/refundOrder")
    public Object refundCallBack(HttpServletRequest request) throws Exception {
        String resultxml = wxPayUtil.getRequestBody(request);
        Map<String,String> resultMap = wxPayUtil.xmlToMap(resultxml);
        SortedMap<String, String> map = new TreeMap<String, String>(); //返回给微信的map
        if ("SUCCESS".equals(resultMap.get("return_code"))) { //返回成功
            //获取加密信息
            String reqInfo = resultMap.get("req_info").toString();
            byte[] asBytes = Base64.getDecoder().decode(reqInfo);
            String md5Key = wxPayUtil.md5(wxPayConfigEntity.getApiKey()); //md5加密后的key
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            SecretKeySpec key = new SecretKeySpec(md5Key.toLowerCase().getBytes(), "AES");
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] doFinal = cipher.doFinal(asBytes);
            String result = new String(doFinal, "utf-8");
            if(org.apache.commons.lang3.StringUtils.isNotEmpty(result)){
                Map<String, String> res = wxPayUtil.xmlToMap(result);
                String refundStatus =  res.get("refund_status"); //退款状态
                log.info("退款状态:-->"+refundStatus);
                String outRefundNo =  res.get("out_refund_no");//退款单号
                log.info("退款单号:-->"+outRefundNo);
                String refund_id = res.get("refund_id"); //微信退款单号 可以保存到数据库
//                String settlementRefundFee = res.get("settlement_refund_fee");//退款金额
//                String refundFee = res.get("refund_fee");//申请退款金额
//                String successTime = res.get("success_time"); // 退款成功时间
                if("SUCCESS".equals(refundStatus)){  //退款成功
                    Long refund_fee = Long.parseLong(res.get("refund_fee"));
                    log.info("退款金额:(单位:分)-->"+outRefundNo);
                    String refund_success_time = res.get("success_time");
                    log.info("退款时间:-->"+refund_success_time);
                    LocalDateTime refundsuccesstime =LocalDateTimeUtil.string2LocalDateTime(refund_success_time);
                    log.info("退款时间:-->"+refundsuccesstime.toString());
                    BigDecimal refundfee = BigDecimal.valueOf(refund_fee).divide(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
                    SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(outRefundNo);
                    salesOrderHeader.setRefundPrice(refundfee);
                    salesOrderHeader.setRefundTime(refundsuccesstime);
                    salesOrderHeader.setPaid(OrdersOperateEnum.REFUNDSUCCEE.code);
                    salesOrderHeader.setStatus(CommonConstant.STATUS_RETURN);
                    salesOrderHeaderService.update(salesOrderHeader);
                    SalesOrderDetailQuery sdetailQuery = new SalesOrderDetailQuery();
                    sdetailQuery.setSalesOrderHeader(salesOrderHeader.getId());
                    sdetailQuery.setStatus(CommonConstant.FINISHED_GOODS);
                    List<SalesOrderDetail> orderDetails = salesOrderDetailService.findAll(sdetailQuery);
                    try {
                        lock.lock();
                        for (SalesOrderDetail order : orderDetails){
                            Sku sku = appSkuService.get(order.getSkuId());
                            if (sku != null && !StringUtils.isEmpty(sku.getId())){
                                sku.setStock(sku.getStock()+order.getQty());
                                sku.setSalesCount(sku.getSalesCount()-order.getQty());
                                appSkuService.update(sku);
                            }
                        }
                    }catch (Exception e){
                        throw new BusinessException("库存修改异常!");
                    }finally {
                        lock.unlock();
                    }
                    map.put("return_code", "SUCCESS");
                    map.put("return_msg", "OK");  //正确返回给微信
                    return wxPayUtil.getRequestXml(map);
                }else if("CHANGE".equals(refundStatus)){  //退款异常
                }else if ("REFUNDCLOSE".equals(refundStatus)){  //退款关闭
                }
            }
        }
        map.put("return_code", "FAIL");
        map.put("return_msg", "return_code不正确");
        return wxPayUtil.getRequestXml(map);
    }
    @ApiOperation(value = "查询退款")
    @PostMapping("/create/refundquery")
    @Transactional
    public Object refundquery(@RequestParam("orderId") String orderId) throws Exception {
        //查询订单 可以优化把订单放入redis
        SalesOrderHeader salesOrderHeader = salesOrderHeaderService.get(orderId);
        //如果是付款 返回成功
        if (salesOrderHeader == null || StringUtils.isEmpty(salesOrderHeader.getId()) ||
                salesOrderHeader.getStatus().equals(CommonConstant.STATUS_DELETE)) {
            return new ResultUtils<>().setErrorMsg("订单不存在");
        } else if (salesOrderHeader != null && OrdersOperateEnum.REFUNDSUCCEE.code == salesOrderHeader.getPaid()) {
            return new ResultUtils<>().setSuccessMsg("退款成功");
        } else {
            SortedMap<String, String> sortedMap = new TreeMap<String, String>();
            sortedMap.put("appid", wxPayConfigEntity.getApppid()); //应用ID
            sortedMap.put("mch_id", wxPayConfigEntity.getMch_id()); //商户号
            sortedMap.put("sub_mch_id", wxPayConfigEntity.getSub_mchid());//子商户号
            sortedMap.put("nonce_str", nonceStr);
            sortedMap.put("transaction_id", salesOrderHeader.getRefNo());
            //sortedMap.put("out_trade_no", salesOrderHeader.getId());
            sortedMap.put("out_refund_no", salesOrderHeader.getId());
            //生成签名
            String sign = wxPayUtil.generateSignature(sortedMap, wxPayConfigEntity.getApiKey(),WXPayConfigEntity.SignType.MD5);
            sortedMap.put("sign", sign);
            log.info("发送给微信的参数:" + sortedMap);
            String xml = WXPayUtil.getRequestXml(sortedMap);
            log.info("===>>>发送给微信的xml:" + xml);
            String string = clientCustomSSL.doRefund(wxPayConfigEntity.getRefundSelecturl(), xml);
            System.out.println("微信返回的xml===>>>" + string);
            //因为退款是V2接口,所以先不用CloseableHttpClient自动签名处理发起请求,就用普通的HttpClient发起请求
            boolean bool = wxPayUtil.isSignatureValid(string, wxPayConfigEntity.getApiKey());//验证签名是否正确
            Map<String, String> map = new HashMap<String, String>();
            if (bool) {
                Map<String, String> resultMap = wxPayUtil.xmlToMap(string);
                if ("SUCCESS".equals(resultMap.get("return_code"))) { //返回成功 退款成功
                    String result_code = resultMap.get("result_code");//业务结果
                    String refund_id = resultMap.get("refund_id_0");//微信退款单号
                    String refund_status_0 = resultMap.get("refund_status_0");//退款状态
                    Long refund_fee = Long.parseLong(resultMap.get("refund_fee_0"));//退款金额
                    BigDecimal refundfee = BigDecimal.valueOf(refund_fee).divide(BigDecimal.valueOf(100)).setScale(2, BigDecimal.ROUND_HALF_UP);
                    salesOrderHeader.setPaid(OrdersOperateEnum.REFUNDSUCCEE.code);
                    salesOrderHeader.setRefundId(refund_id);
                    salesOrderHeader.setRefundPrice(refundfee);
                    map.put("result_code",result_code);
                    map.put("refund_id",refund_id);
                    map.put("refund_fee", refundfee.toString());//退款金额
                    map.put("out_refund_no", resultMap.get("out_refund_no_0")); //退款单号
                    map.put("refund_status_0",refund_status_0);
                    map.put("message", "SUCCESS");//退款成功
                    return map;
                }
            }
            map.put("message", "faild"); //退款失败
            return map;
        }
    }
}WXPayUtil
特别需要注意的,我在这栽跟头栽了两天,如果你使用了下面的方法,就不需要自己再去生成签名了,一定要记住,不让会报401.
文档第一步就是生成签名,我就是跟着文档开发,后面又发现了下面的方法,也用上了,结果就是一直401,虽然我在用这个方法的时候就在想既然自动签名了那为什么我还自己生成签名呢的疑问下,但是一直都没舍得把自己生成签名的步骤删掉,直到我绝望了.
            
            
              java
              
              
            
          
          import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.DocumentBuilder;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.ParseException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
@Component
public class WXPayUtil {
    @Autowired
    private  WXPayConfigEntity wxPayConfigEntity; //支付参数
    private static final int byteLowHalfFlag = 0x0F;
    private static final int byteHalfShift = 4;
    private  final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
            'F' };
    /**
     * 获取证书。
     *
     * @param filename 证书文件路径  (required)
     * @return X509证书
     */
    public  X509Certificate getCertificate(String filename) throws IOException {
        InputStream fis = new FileInputStream(filename);
        BufferedInputStream bis = new BufferedInputStream(fis);
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(bis);
            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);
        } finally {
            bis.close();
        }
    }
    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public  PrivateKey getPrivateKey(String filename) throws IOException {
        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }
    public  String getToken(String nonceStr,long timestamp,String method, HttpUrl url, String body) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        String signature = sign(message.getBytes("utf-8"));
        return "mchid=\"" + wxPayConfigEntity.getMch_id() + "\","
                + "nonce_str=\"" + nonceStr + "\","
                + "timestamp=\"" + timestamp + "\","
                + "serial_no=\"" + wxPayConfigEntity.getSerialNo() + "\","
                + "signature=\"" + signature + "\"";
    }
    //生成签名
    public  String sign(byte[] message) throws NoSuchAlgorithmException, IOException, InvalidKeyException, SignatureException {
        Signature sign = Signature.getInstance("SHA256withRSA");
        PrivateKey privateKey1 = getPrivateKey(wxPayConfigEntity.getKeyPath());
        sign.initSign(privateKey1);
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
    }
    //待签名串
    //生成签名串
    public  String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {
            canonicalUrl += "?" + url.encodedQuery();
        }
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }
    //生成Authorization
    public  String getAuthorization(String Sign,String  authType) {
        return authType.concat(" ").concat(Sign);
    }
    //APP支付二次生成签名
    public  String getToken2(String nonceStr,long timestamp,String prepayid,String subAppid) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        String message2 = buildMessage2(prepayid, timestamp, nonceStr, subAppid);
        String signature2 = sign(message2.getBytes("utf-8"));
        return signature2;
    }
    //APP支付二次生成签名串
    public  String buildMessage2(String prepayid, long timestamp, String nonceStr, String subAppid) {
        return subAppid + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + prepayid + "\n";
    }
    //JSAPI支付二次生成签名
    public  String getToken3(String packages, long timestamp, String nonceStr, String appId) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        String message2 = buildMessage3(packages, timestamp, nonceStr, appId);
        String signature2 = sign(message2.getBytes("utf-8"));
        return signature2;
    }
    //JSAPI支付二次生成签名串
    public  String buildMessage3(String packages, long timestamp, String nonceStr, String appId) {
        return appId + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + packages + "\n";
    }
    /**
     * 获取请求文体
     * @param request
     * @return
     * @throws IOException
     */
    public  String getRequestBody(HttpServletRequest request) throws IOException {
        ServletInputStream stream = null;
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        try {
            stream = request.getInputStream();
            // 获取响应
            reader = new BufferedReader(new InputStreamReader(stream));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            throw new IOException("读取返回支付接口数据流出现异常!");
        } finally {
            reader.close();
        }
        return sb.toString();
    }
    /**
     * 验证微信签名
     * @param request
     * @param body
     * @return
     * @throws GeneralSecurityException
     * @throws IOException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ParseException
     */
    public boolean verifiedSign(HttpServletRequest request, String body,PrivateKey privateKey) throws GeneralSecurityException, ParseException, RequestWechatException {
        //微信返回的证书序列号
        String serialNo = request.getHeader("Wechatpay-Serial");
        log.info("Wechatpay-Serial:---"+serialNo);
        //微信返回的随机字符串
        String nonceStr = request.getHeader("Wechatpay-Nonce");
        log.info("Wechatpay-Nonce:---"+nonceStr);
        //微信返回的时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        log.info("Wechatpay-Timestamp:---"+timestamp);
        //微信返回的签名
        String wechatSign = request.getHeader("Wechatpay-Signature");
        log.info("Wechatpay-Signature:---"+wechatSign);
        //组装签名字符串
        String signStr = Stream.of(timestamp, nonceStr, body)
                .collect(Collectors.joining("\n", "", "\n"));
        //自动获取最新平台证书
        AutoUpdateCertificatesVerifier verifier = getAutoUpdateCertificatesVerifier();
        X509Certificate certificate = verifier.getValidCertificate();
        //SHA256withRSA签名
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(certificate);
        signature.update(signStr.getBytes());
        //返回验签结果
        return signature.verify(Base64Utils.decodeFromString(wechatSign));
    }
    //自动获取最新平台证书
    public AutoUpdateCertificatesVerifier getAutoUpdateCertificatesVerifier()  {
        PrivateKey privateKey1 = null;
        try {
            privateKey1 = getPrivateKey(wxPayConfigEntity.getKeyPath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        AutoUpdateCertificatesVerifier verifier = null;
        try {
            verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials(wxPayConfigEntity.getMch_id(), new PrivateKeySigner(wxPayConfigEntity.getSerialNo(), privateKey1)),
                    wxPayConfigEntity.getApiV3Key().getBytes("utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return verifier;
    }
      
    public CloseableHttpClient  getCloseableHttpClient()  {
        PrivateKey privateKey1 = null;
        AutoUpdateCertificatesVerifier verifier = null;
        try {
            privateKey1 = getPrivateKey(wxPayConfigEntity.getKeyPath());
            verifier = getAutoUpdateCertificatesVerifier();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
        CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
                .withMerchant(wxPayConfigEntity.getMch_id(), wxPayConfigEntity.getSerialNo(), privateKey1)
                .withValidator(new WechatPay2Validator(verifier)).build();
        return httpClient;
    }
//=======================微信支付V2工具类 因为V3接口暂时没有退款接口所以暂时还是需要用V2的退款接口=========================
    /**
     * 生成签名
     *
     * @param data 待签名数据
     * @param key  API密钥
     * @return 签名
     */
    public  String generateSignature(final Map<String, String> data, String key) throws Exception {
        return generateSignature(data, key, WXPayConfigEntity.SignType.MD5);
    }
    /**
     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
     *
     * @param data     待签名数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名
     */
    public static String generateSignature(final Map<String, String> data, String key, WXPayConfigEntity.SignType signType) throws Exception {
        Set<String> keySet = data.keySet();
        String[] keyArray = keySet.toArray(new String[keySet.size()]);
        Arrays.sort(keyArray);
        StringBuilder sb = new StringBuilder();
        for (String k : keyArray) {
            if (k.equals("sign")) {
                continue;
            }
            if (data.get(k).toString().trim().length() > 0){
                // 参数值为空,则不参与签名
                sb.append(k).append("=").append(data.get(k).toString().trim()).append("&");
            }
        }
        sb.append("key=").append(key);
        if (WXPayConfigEntity.SignType.MD5.equals(signType)) {
            return MD5(sb.toString()).toUpperCase();
        } else if (WXPayConfigEntity.SignType.HMACSHA256.equals(signType)) {
            return HMACSHA256(sb.toString(), key);
        } else {
            throw new Exception(String.format("Invalid sign_type: %s", signType));
        }
    }
        /**
         * 生成 MD5
         *
         * @param data 待处理数据
         * @return MD5结果
         */
        public static String MD5(String data) throws Exception {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] array = md.digest(data.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte item : array) {
                sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
            }
            return sb.toString().toUpperCase();
        }
    /**
     * 生成 HMACSHA256
     *
     * @param data 待处理数据
     * @param key  密钥
     * @return 加密结果
     * @throws Exception
     */
    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) {
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        }
        return sb.toString().toUpperCase();
    }
    
    
    
    /**
     * 转为xml格式
     * @param parameters
     * @return
     */
    public static String getRequestXml(SortedMap<String,String> parameters){
        StringBuffer sb = new StringBuffer();
        sb.append("<xml>");
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            String k = (String)entry.getKey();
            String v = (String)entry.getValue();
            if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
                sb.append("<"+k+">"+v+"</"+k+">");
            }else {
                sb.append("<"+k+">"+v+"</"+k+">");
            }
        }
        sb.append("</xml>");
        return sb.toString();
    }
    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            log.info("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }
    }
    public  String md5(String s) throws Exception {
        try {
            byte[] btInput = s.getBytes();
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[(byte0 >>> byteHalfShift) & byteLowHalfFlag];
                str[k++] = hexDigits[byte0 & byteLowHalfFlag];
            }
            return new String(str);
        } catch (Exception e) {
            throw e;
        }
    }
    public String md5(byte[] btInput) throws Exception {
        try {
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char[] str = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                str[k++] = hexDigits[byte0 >>> byteHalfShift & byteLowHalfFlag];
                str[k++] = hexDigits[byte0 & byteLowHalfFlag];
            }
            return new String(str);
        } catch (Exception e) {
            throw e;
        }
    }
    /**
     * 判断签名是否正确
     *
     * @param xmlStr XML格式数据
     * @param key    API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public boolean isSignatureValid(String xmlStr, String key) throws Exception {
        Map<String, String> data = xmlToMap(xmlStr);
        if (!data.containsKey("sign")) {
            return false;
        }
        String sign = (String) data.get("sign");
        return generateSignature(data, key).equals(sign);
    }
    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
     *
     * @param data Map类型数据
     * @param key  API密钥
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
        return isSignatureValid(data, key, WXPayConfigEntity.SignType.MD5);
    }
    /**
     * 判断签名是否正确,必须包含sign字段,否则返回false。
     *
     * @param data     Map类型数据
     * @param key      API密钥
     * @param signType 签名方式
     * @return 签名是否正确
     * @throws Exception
     */
    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConfigEntity.SignType signType) throws Exception {
        if (!data.containsKey("sign")) {
            return false;
        }
        String sign = (String) data.get("sign");
        return generateSignature(data, key, signType).equals(sign);
    }
}PayResponseUtils
解密回调响应加密数据的工具类
            
            
              java
              
              
            
          
          import com.google.common.base.Splitter;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Map;
/**
 * 微信付款返回数据工具类
 * @author Jacky
 * @date 2020/12/22 13:46
 */
public class PayResponseUtils {
    /**
     * 用微信V3密钥解密响应体.
     *
     * @param associatedData  response.body.data[i].encrypt_certificate.associated_data
     * @param nonce          response.body.data[i].encrypt_certificate.nonce
     * @param ciphertext     response.body.data[i].encrypt_certificate.ciphertext
     * @return the string
     * @throws GeneralSecurityException the general security exception
     */
    public static String decryptResponseBody(String v3key,String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(v3key.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }
    public static String getParam(String url, String name) {
        String params = url.substring(url.indexOf("?") + 1, url.length());
        Map<String, String> split = Splitter.on("&").withKeyValueSeparator("=").split(params);
        return split.get(name);
    }
}OrdersOperateEnum
            
            
              java
              
              
            
          
          /**
 * 订单操作枚举类
 * @author Jacky
 * @date 2020/12/15 9:27
 */
public enum OrdersOperateEnum {
    UNPAID(0,"待支付"),
    SUBMIT(1,"提交支付"),
    SUCCESS(2,"支付成功"),
    REFUND(3,"转入退款"),
    NOTPAY(4,"未支付"),
    CLOSED(5,"已关闭"),
    REVOKED(6,"已撤销(付款码支付)"),
    USERPAYING(7,"用户支付中(付款码支付)"),
    PAYERROR(8,"支付失败"),
    REFUNDSUCCEE(9,"退款成功");
    public int code;
    public String msg;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    OrdersOperateEnum(Integer code, String msg){
        this.code = code;
        this.msg = msg;
    }
    /**
     * 根据code获得msg
     * @param code
     * @return
     */
    @SuppressWarnings("unlikely-arg-type")
    public static String getValueByCode(Integer code){
        for(OrdersOperateEnum platformFree:OrdersOperateEnum.values()){
            if(code.equals(platformFree.getCode())){
                return platformFree.getMsg();
            }
        }
        return "未知";
    }
    public static OrdersOperateEnum getByCode(Integer code){
        for(OrdersOperateEnum transactType : values()){
            if (code.equals(transactType.getCode())) {
                //获取指定的枚举
                return transactType;
            }
        }
        return null;
    }
    public  static Integer getOneCode(String trade_state){
        switch (trade_state){
            case "SUBMIT" :
                return OrdersOperateEnum.SUBMIT.code;
            case "SUCCESS":
                return OrdersOperateEnum.SUCCESS.code;
            case "REFUND" :
                return OrdersOperateEnum.REFUND.code;
            case "NOTPAY" :
                return OrdersOperateEnum.NOTPAY.code;
            case "CLOSED" :
                return OrdersOperateEnum.CLOSED.code;
            case "REVOKED" :
                return OrdersOperateEnum.REVOKED.code;
            case "USERPAYING" :
                return OrdersOperateEnum.USERPAYING.code;
            default:
                return OrdersOperateEnum.PAYERROR.code;
        }
    }
}ClientCustomSSL
调用退款接口会用的生成请求的工具类
            
            
              java
              
              
            
          
          import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
/**
 * @ Author :Jacky
 * @ Date   :
 **/
@Component
public class ClientCustomSSL {
    @Autowired
    private WXPayConfigEntity wxPayConfigEntity;
    @SuppressWarnings("deprecation")
    public String doRefund(String url,String data) throws Exception {
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        /**
         *
         *wxconfig.SSLCERT_PATH : 指向你的证书的绝对路径,带着证书去访问
         */
        //你下载的证书,解压后的apiclient_cert.p12这个文件全路径加全名
        FileInputStream instream = new FileInputStream(new File(wxPayConfigEntity.getP12()));//P12文件目录
        try {
            /**
             *
             *
             * 下载证书时的密码、默认密码是你的MCHID mch_id
             * */
            keyStore.load(instream, wxPayConfigEntity.getMch_id().toCharArray());//这里写密码.默认商户id
        } finally {
            instream.close();
        }
        // Trust own CA and all self-signed certs
        /**
         *
         * 下载证书时的密码、默认密码是你的MCHID mch_id
         * */
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, wxPayConfigEntity.getMch_id().toCharArray())//这里也是写密码的,默认商户id
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[] { "TLSv1" },
                null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }
}NotifyResourceVO
这里是微信回调通知里面的加密数据解密之后用来封装解密数据的Vo
            
            
              java
              
              
            
          
          /**
 * 微信通知数据解密后对象
 * @author Jacky
 * @date 2020/12/18 11:17
 */
public class NotifyResourceVO {
    /**
     * 服务商公众号ID
     */
    private String sp_appid;
    /**
     * 服务商户号
     */
    private String sp_mchid;
    /**
     * 子商户号
     */
    private String sub_mchid;
    /**
     * 商户订单号
     */
    private String out_trade_no;
    /**
     * 微信支付订单号
     */
    private String transaction_id;
    /**
     * 交易类型
     */
    private String trade_type;
    /**
     * 交易状态
     */
    private String trade_state;
    /**
     * 交易状态描述
     */
    private String trade_state_desc;
    /**
     * 付款银行
     */
    private String bank_type;
    /**
     * 支付完成时间
     */
    private String success_time;
    /**
     * 支付者
     */
    private Payer payer;
    /**
     * 订单金额
     */
    private Amount amount;
    /**
     * 支付者
     */
    public class Payer{
        /**
         * 用户标识
         */
        private String openid;
        public String getOpenid() {
            return openid;
        }
        public void setOpenid(String openid) {
            this.openid = openid;
        }
    }
    /**
     * 订单金额
     */
    public class Amount{
        /**
         * 总金额
         */
        private Integer total;
        /**
         * 用户支付金额
         */
        private Integer payer_total;
        /**
         * 货币类型
         */
        private String currency;
        /**
         * 用户支付币种
         */
        private String payer_currency;
        public Integer getTotal() {
            return total;
        }
        public void setTotal(Integer total) {
            this.total = total;
        }
        public Integer getPayer_total() {
            return payer_total;
        }
        public void setPayer_total(Integer payer_total) {
            this.payer_total = payer_total;
        }
        public String getCurrency() {
            return currency;
        }
        public void setCurrency(String currency) {
            this.currency = currency;
        }
        public String getPayer_currency() {
            return payer_currency;
        }
        public void setPayer_currency(String payer_currency) {
            this.payer_currency = payer_currency;
        }
    }
    public String getSp_appid() {
        return sp_appid;
    }
    public void setSp_appid(String sp_appid) {
        this.sp_appid = sp_appid;
    }
    public String getSp_mchid() {
        return sp_mchid;
    }
    public void setSp_mchid(String sp_mchid) {
        this.sp_mchid = sp_mchid;
    }
    public String getSub_mchid() {
        return sub_mchid;
    }
    public void setSub_mchid(String sub_mchid) {
        this.sub_mchid = sub_mchid;
    }
    public String getOut_trade_no() {
        return out_trade_no;
    }
    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }
    public String getTransaction_id() {
        return transaction_id;
    }
    public void setTransaction_id(String transaction_id) {
        this.transaction_id = transaction_id;
    }
    public String getTrade_type() {
        return trade_type;
    }
    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }
    public String getTrade_state() {
        return trade_state;
    }
    public void setTrade_state(String trade_state) {
        this.trade_state = trade_state;
    }
    public String getTrade_state_desc() {
        return trade_state_desc;
    }
    public void setTrade_state_desc(String trade_state_desc) {
        this.trade_state_desc = trade_state_desc;
    }
    public String getBank_type() {
        return bank_type;
    }
    public void setBank_type(String bank_type) {
        this.bank_type = bank_type;
    }
    public String getSuccess_time() {
        return success_time;
    }
    public void setSuccess_time(String success_time) {
        this.success_time = success_time;
    }
    public Payer getPayer() {
        return payer;
    }
    public void setPayer(Payer payer) {
        this.payer = payer;
    }
    public Amount getAmount() {
        return amount;
    }
    public void setAmount(Amount amount) {
        this.amount = amount;
    }
}WXPayNotifyVO
这里是接收回调通知的Vo,
            
            
              java
              
              
            
          
          /**
 * 接收微信支付通知VO
 * @author Jacky
 * @date
 */
public class WXPayNotifyVO {
    /**
     * 通知的唯一ID
     */
    private String id;
    /**
     * 通知创建时间
     */
    private String create_time;
    /**
     * 通知类型 支付成功通知的类型为TRANSACTION.SUCCESS
     */
    private String event_type;
    /**
     * 通知数据类型 支付成功通知为encrypt-resource
     */
    private String resource_type;
    /**
     * 通知资源数据
     */
    private Resource resource;
    /**
     * 回调摘要
     */
    private String summary;
    /**
     * 通知资源数据
     */
    public class Resource{
        /**
         * 加密算法类型
         */
        private String algorithm;
        /**
         * 数据密文
         */
        private String ciphertext;
        /**
         * 附加数据
         */
        private String associated_data;
        /**
         * 随机串
         */
        private String nonce;
        public String getAlgorithm() {
            return algorithm;
        }
        public void setAlgorithm(String algorithm) {
            this.algorithm = algorithm;
        }
        public String getCiphertext() {
            return ciphertext;
        }
        public void setCiphertext(String ciphertext) {
            this.ciphertext = ciphertext;
        }
        public String getAssociated_data() {
            return associated_data;
        }
        public void setAssociated_data(String associated_data) {
            this.associated_data = associated_data;
        }
        public String getNonce() {
            return nonce;
        }
        public void setNonce(String nonce) {
            this.nonce = nonce;
        }
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getCreate_time() {
        return create_time;
    }
    public void setCreate_time(String create_time) {
        this.create_time = create_time;
    }
    public String getEvent_type() {
        return event_type;
    }
    public void setEvent_type(String event_type) {
        this.event_type = event_type;
    }
    public String getResource_type() {
        return resource_type;
    }
    public void setResource_type(String resource_type) {
        this.resource_type = resource_type;
    }
    public Resource getResource() {
        return resource;
    }
    public void setResource(Resource resource) {
        this.resource = resource;
    }
    public String getSummary() {
        return summary;
    }
    public void setSummary(String summary) {
        this.summary = summary;
    }
}
**```
RequestWechatException** 
异常类
```java
/**
 * 请求微信异常,只有在请求微信地址不通时才会抛出该异常
 * @author shenlu
 * @date 2020/12/29 9:37
 */
public class RequestWechatException extends Exception {
    private static final long serialVersionUID = -6477104653316285034L;
    public RequestWechatException() {
        super("请求微信异常");
    }
}WXPayXmlUtil
xml转换工具类
            
            
              java
              
              
            
          
          /**
 *
 */
public final class  WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http:///sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http:///sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);
        return documentBuilderFactory.newDocumentBuilder();
    }
    public static Document newDocument() throws ParserConfigurationException {
        return newDocumentBuilder().newDocument();
    }
}WXPayUtility工具类
            
            
              java
              
              
            
          
          import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.security.MessageDigest;
import java.io.InputStream;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
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();
    /**
     * 将 Object 转换为 JSON 字符串
     */
    public static String toJson(Object object) {
        return gson.toJson(object);
    }
    /**
     * 将 JSON 字符串解析为特定类型的实例
     */
    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
        return gson.fromJson(json, classOfT);
    }
    /**
     * 从公私钥文件路径中读取文件内容
     *
     * @param keyPath 文件路径
     * @return 文件内容
     */
    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);
        }
    }
    /**
     * 读取 PKCS#8 格式的私钥字符串并加载为私钥对象
     *
     * @param keyString 私钥文件内容,以 -----BEGIN PRIVATE KEY----- 开头
     * @return PrivateKey 对象
     */
    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);
        }
    }
    /**
     * 从 PKCS#8 格式的私钥文件中加载私钥
     *
     * @param keyPath 私钥文件路径
     * @return PrivateKey 对象
     */
    public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
        return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
    }
    /**
     * 读取 PKCS#8 格式的公钥字符串并加载为公钥对象
     *
     * @param keyString 公钥文件内容,以 -----BEGIN PUBLIC KEY----- 开头
     * @return PublicKey 对象
     */
    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);
        }
    }
    /**
     * 从 PKCS#8 格式的公钥文件中加载公钥
     *
     * @param keyPath 公钥文件路径
     * @return PublicKey 对象
     */
    public static PublicKey loadPublicKeyFromPath(String keyPath) {
        return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
    }
    /**
     * 创建指定长度的随机字符串,字符集为[0-9a-zA-Z],可用于安全相关用途
     */
    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);
    }
    /**
     * 使用公钥按照 RSA_PKCS1_OAEP_PADDING 算法进行加密
     *
     * @param publicKey 加密用公钥对象
     * @param plaintext 待加密明文
     * @return 加密后密文
     */
    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);
        }
    }
    /**
     * 使用私钥按照指定算法进行签名
     *
     * @param message    待签名串
     * @param algorithm  签名算法,如 SHA256withRSA
     * @param privateKey 签名用私钥对象
     * @return 签名结果
     */
    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);
    }
    /**
     * 使用公钥按照特定算法验证签名
     *
     * @param message   待签名串
     * @param signature 待验证的签名内容
     * @param algorithm 签名算法,如:SHA256withRSA
     * @param publicKey 验签用公钥对象
     * @return 签名验证是否通过
     */
    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);
        }
    }
    /**
     * 根据微信支付APIv3请求签名规则构造 Authorization 签名
     *
     * @param mchid               商户号
     * @param certificateSerialNo 商户API证书序列号
     * @param privateKey          商户API证书私钥
     * @param method              请求接口的HTTP方法,请使用全大写表述,如 GET、POST、PUT、DELETE
     * @param uri                 请求接口的URL
     * @param body                请求接口的Body
     * @return 构造好的微信支付APIv3 Authorization 头
     */
    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);
    }
    /**
     * 计算输入流的哈希值
     *
     * @param inputStream 输入流
     * @param algorithm   哈希算法名称,如 "SHA-256", "SHA-1"
     * @return 哈希值的十六进制字符串
     */
    private static String calculateHash(InputStream inputStream, String algorithm) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }
            byte[] hashBytes = digest.digest();
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new UnsupportedOperationException(algorithm + " algorithm not available", e);
        } catch (IOException e) {
            throw new RuntimeException("Error reading from input stream", e);
        }
    }
    /**
     * 计算输入流的 SHA256 哈希值
     *
     * @param inputStream 输入流
     * @return SHA256 哈希值的十六进制字符串
     */
    public static String sha256(InputStream inputStream) {
        return calculateHash(inputStream, "SHA-256");
    }
    /**
     * 计算输入流的 SHA1 哈希值
     *
     * @param inputStream 输入流
     * @return SHA1 哈希值的十六进制字符串
     */
    public static String sha1(InputStream inputStream) {
        return calculateHash(inputStream, "SHA-1");
    }
    /**
     * 计算输入流的 SM3 哈希值
     *
     * @param inputStream 输入流
     * @return SM3 哈希值的十六进制字符串
     */
    public static String sm3(InputStream inputStream) {
        // 确保Bouncy Castle Provider已注册
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
        try {
            SM3Digest digest = new SM3Digest();
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }
            byte[] hashBytes = new byte[digest.getDigestSize()];
            digest.doFinal(hashBytes, 0);
            StringBuilder hexString = new StringBuilder();
            for (byte b : hashBytes) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (IOException e) {
            throw new RuntimeException("Error reading from input stream", e);
        }
    }
    /**
     * 对参数进行 URL 编码
     *
     * @param content 参数内容
     * @return 编码后的内容
     */
    public static String urlEncode(String content) {
        try {
            return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 对参数Map进行 URL 编码,生成 QueryString
     *
     * @param params Query参数Map
     * @return QueryString
     */
    public static String urlEncode(Map<String, Object> params) {
        if (params == null || params.isEmpty()) {
            return "";
        }
        StringBuilder result = new StringBuilder();
        boolean isFirstParam = true;
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            if (!isFirstParam) {
                result.append("&");
            }
            String valueString;
            Object value = entry.getValue();
            // 如果是基本类型、字符串或枚举,直接转换;如果是对象,序列化为JSON
            if (value instanceof String || value instanceof Number || value instanceof Boolean || value instanceof Enum) {
                valueString = value.toString();
            } else {
                valueString = toJson(value);
            }
            result.append(entry.getKey())
                    .append("=")
                    .append(urlEncode(valueString));
            isFirstParam = false;
        }
        return result.toString();
    }
    /**
     * 从应答中提取 Body
     *
     * @param response HTTP 请求应答对象
     * @return 应答中的Body内容,Body为空时返回空字符串
     */
    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);
        }
    }
    /**
     * 根据微信支付APIv3应答验签规则对应答签名进行验证,验证不通过时抛出异常
     *
     * @param wechatpayPublicKeyId 微信支付公钥ID
     * @param wechatpayPublicKey   微信支付公钥对象
     * @param headers              微信支付应答 Header 列表
     * @param body                 微信支付应答 Body
     */
    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));
        }
    }
    /**
     * 根据微信支付APIv3通知验签规则对通知签名进行验证,验证不通过时抛出异常
     *
     * @param wechatpayPublicKeyId 微信支付公钥ID
     * @param wechatpayPublicKey   微信支付公钥对象
     * @param headers              微信支付通知 Header 列表
     * @param body                 微信支付通知 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));
        }
    }
    /**
     * 对微信支付通知进行签名验证、解析,同时将业务数据解密。验签名失败、解析失败、解密失败时抛出异常
     *
     * @param apiv3Key             商户的 APIv3 Key
     * @param wechatpayPublicKeyId 微信支付公钥ID
     * @param wechatpayPublicKey   微信支付公钥对象
     * @param headers              微信支付请求 Header 列表
     * @param body                 微信支付请求 Body
     * @return 解析后的通知内容,解密后的业务数据可以使用 Notification.getPlaintext() 访问
     */
    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;
    }
    /**
     * 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
     */
    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;
            }
        }
        /**
         * 获取 HTTP 应答状态码
         */
        public int getStatusCode() {
            return statusCode;
        }
        /**
         * 获取 HTTP 应答包体内容
         */
        public String getBody() {
            return body;
        }
        /**
         * 获取 HTTP 应答 Header
         */
        public Headers getHeaders() {
            return headers;
        }
        /**
         * 获取 错误码 (错误应答中的 code 字段)
         */
        public String getErrorCode() {
            return errorCode;
        }
        /**
         * 获取 错误消息 (错误应答中的 message 字段)
         */
        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;
        }
        /**
         * 获取解密后的业务数据(JSON字符串,需要自行解析)
         */
        public String getPlaintext() {
            return plaintext;
        }
        private void validate() {
            if (resource == null) {
                throw new IllegalArgumentException("Missing required field `resource` in notification");
            }
            resource.validate();
        }
        /**
         * 使用 APIv3Key 对通知中的业务数据解密,解密结果可以通过 getPlainText 访问。
         * 外部拿到的 Notification 一定是解密过的,因此本方法没有设置为 public
         *
         * @param apiv3Key 商户APIv3 Key
         */
        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");
                }
            }
        }
    }
    /**
     * 根据文件名获取对应的Content-Type
     *
     * @param fileName 文件名
     * @return Content-Type字符串
     */
    public static String getContentTypeByFileName(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return "application/octet-stream";
        }
        // 获取文件扩展名
        String extension = "";
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
            extension = fileName.substring(lastDotIndex + 1).toLowerCase();
        }
        // 常见文件类型映射
        Map<String, String> contentTypeMap = new HashMap<>();
        // 图片类型
        contentTypeMap.put("png", "image/png");
        contentTypeMap.put("jpg", "image/jpeg");
        contentTypeMap.put("jpeg", "image/jpeg");
        contentTypeMap.put("gif", "image/gif");
        contentTypeMap.put("bmp", "image/bmp");
        contentTypeMap.put("webp", "image/webp");
        contentTypeMap.put("svg", "image/svg+xml");
        contentTypeMap.put("ico", "image/x-icon");
        // 文档类型
        contentTypeMap.put("pdf", "application/pdf");
        contentTypeMap.put("doc", "application/msword");
        contentTypeMap.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        contentTypeMap.put("xls", "application/vnd.ms-excel");
        contentTypeMap.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        contentTypeMap.put("ppt", "application/vnd.ms-powerpoint");
        contentTypeMap.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
        // 文本类型
        contentTypeMap.put("txt", "text/plain");
        contentTypeMap.put("html", "text/html");
        contentTypeMap.put("css", "text/css");
        contentTypeMap.put("js", "application/javascript");
        contentTypeMap.put("json", "application/json");
        contentTypeMap.put("xml", "application/xml");
        // 音视频类型
        contentTypeMap.put("mp3", "audio/mpeg");
        contentTypeMap.put("wav", "audio/wav");
        contentTypeMap.put("mp4", "video/mp4");
        contentTypeMap.put("avi", "video/x-msvideo");
        contentTypeMap.put("mov", "video/quicktime");
        // 压缩文件类型
        contentTypeMap.put("zip", "application/zip");
        contentTypeMap.put("rar", "application/x-rar-compressed");
        contentTypeMap.put("7z", "application/x-7z-compressed");
        return contentTypeMap.getOrDefault(extension, "application/octet-stream");
    }
}