调取微信支付需要有商户号,商户密钥,以及证书
有以上三样之后,才可以进行支付调用

1.声明商户号,商户密钥,商户证书序列号

X509Certificate 证书(Certificate,也称public-keycertificate)
它是用某种签名算法对某些内容(比如公钥)进行数字签名后得到的、可以用来当成信任关系中介的数字凭证。证书发行机构通过发行证书告知证书使用者或实体其公钥(public-key)以及其它一些辅助信息。

微信支付证书放在resources下,通过微信提供的工具类PemUtil实现证书解析,解析证书后,可以获得商户证书序列号

private static final String apiV3Key ="密钥";//密钥
private static final String merchantId = "商户号";// 商户号
private static final String merchantSerialNumber = WxUtils.getMerchantSerialNumber(); // 商户证书序列号

//获取序列号方式
  public static String getMerchantSerialNumber(){
        String merchantSerialNumber = "";
        try{
            X509Certificate X509 = getX509();
            merchantSerialNumber = X509.getSerialNumber().toString(16).toUpperCase();
        }catch(Exception e){
            e.printStackTrace();
        }
        return merchantSerialNumber;
    }
    
//获取X509Certificate证书
 public static X509Certificate getX509() throws Exception{
        ClassPathResource resource = new ClassPathResource("**apiclient_cert.pem**");//微信支付证书
        InputStream certid = resource.getInputStream();
        X509Certificate X509 = PemUtil.loadCertificate(certid);
        return X509;
    }

2.微信提供API中PemUtil

微信提供的API PemUtil中,实现了解析密钥,解析证书的方法

public static PrivateKey loadPrivateKey(String privateKey) {
        privateKey = privateKey
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");

        try {
            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 static PrivateKey loadPrivateKey(InputStream inputStream) {
        ByteArrayOutputStream os = new ByteArrayOutputStream(2048);
        byte[] buffer = new byte[1024];
        String privateKey;
        try {
            for (int length; (length = inputStream.read(buffer)) != -1; ) {
                os.write(buffer, 0, length);
            }
            privateKey = os.toString("UTF-8");
        } catch (IOException e) {
            throw new IllegalArgumentException("无效的密钥", e);
        }
        return loadPrivateKey(privateKey);
    }

    public static X509Certificate loadCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }

3.调取接口

成功调取微信支付的第一步,生成prepay_id(预支付交易会话标识),有效期为两小时,是后续调取接口的凭证
该步需要进行一次验签,通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签

必备参数:

appid ==>由微信生成的应用ID,全局唯一。
mchid ==>商户号
description ==>商品描述(String类型,1-127字符以内)
out_trade_no ==>订单号,唯一标识,建议使用UUID
notify_url ==>通知地址,异步调取

返回值bodyAsString 需要转为JSON字符串后获取里面的prepay_id

/**
 * 调取微信支付接口生成预订单
 * @param url
 * @param objectMapper
 * @param rootNode
 * @return
 */
public static String getHttpClient(String url, ObjectMapper objectMapper,ObjectNode rootNode){
    String bodyAsString= "";
    try{
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("Accept", "application/json");
        httpPost.addHeader("Content-type","application/json; charset=utf-8");
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        objectMapper.writeValue(bos, rootNode);//传入参数
        PrivateKey merchantPrivateKey =getPrivateKey();
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        String merchantSerialNumber = getMerchantSerialNumber();
        Verifier verifier = getVerifier();
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
        CloseableHttpClient httpClient = builder.build();
        httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
        CloseableHttpResponse response = httpClient.execute(httpPost);
        String bodyAsString = EntityUtils.toString(response.getEntity());
        JSONObject sessionData = JSON.parseObject(bodyAsString);
        bodyAsString = sessionData.getString("prepay_id");
    }catch (Exception e) {
        e.printStackTrace();
    }
   return bodyAsString ;
}

4.获取私钥

与解析证书相同,微信提供的工具类PemUtil 可以解析私钥

public static PrivateKey getPrivateKey() throws Exception{
        ClassPathResource privteKeyFile = new ClassPathResource("cert/apiclient_key.pem");
        InputStream keyis = privteKeyFile.getInputStream();
        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(keyis);
        return merchantPrivateKey;
    }

5.Verifier

自动更新证书

public static Verifier getVerifier() throws Exception{
        PrivateKey merchantPrivateKey;
        merchantPrivateKey =getPrivateKey();
        //不需要传入微信支付证书,将会自动更新
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        // 向证书管理器增加需要自动更新平台证书的商户信息
        certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
                new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
        Verifier verifier = certificatesManager.getVerifier(merchantId);
        return verifier;
    }

6.小程序调取微信支付所需要的参数处理

微信支付开发文档:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_4.shtml

必备参数:

appId ==>商户申请的小程序对应的appid,由微信支付生成,可在小程序后台查看
timeStamp ==>时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)
nonceStr ==>随机字符串,不长于32位
package ==> 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***

signType==>签名类型,默认为RSA,仅支持RSA。
paySign ==>签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值

注:签名顺序不可以乱,这个appId是大写,获取prepay_id标识时传递的appid是小写的

构造签名串的顺序

签名串一共有四行,每一行为一个参数。行尾以\n(换行符,ASCII编码值为0x0A)结束,包括最后一行。
如果参数本身以\n结束,也需要附加一个\n

示例:
小程序appId
时间戳(timeStamp)
随机字符串 (nonceStr)
订单详情扩展字符串(package )

/**
     * 调取微信支付,生成签名
     * @param param
     * @return
     */
    public static R Pay(Map<String, String> param){
        R result = new R();
        String url = "微信支付接口,即 小程序调起支付API";
        String currency = "CNY";//缴费金额类型
        String appid ="APPID";//appid
        String regDiscount = param.get("regDiscount");//金额
        String openId = param.get("openId");//openID
        String tradeno = param.get("tradeno");//票据号
        String notify_url = "回调地址";//回调地址
        String description = param.get("description");//描述

        int total = new BigDecimal(regDiscount).multiply(new BigDecimal(100)).intValue();//缴费金额==>金额以分为单位
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        rootNode.put("mchid",merchantId)
                .put("appid", appid)
                .put("description", description)
                .put("notify_url", notify_url)
                .put("out_trade_no", tradeno);
        rootNode.putObject("amount")
                .put("total", total)
                .put("currency", currency);
        rootNode.putObject("payer")
                .put("openid", openId);
        String prepay_id = WxUtils.getHttpClient(url,objectMapper,rootNode);//预支付交易会话标识
        long timeStamp = System.currentTimeMillis() / 1000;//时间戳
        String nonceStr = UUID;//随机字符串
       
        String content = appid+ "\n"+timeStamp+"\n"+nonceStr+"\nprepay_id="+prepay_id+"\n";//拼好的内容
        String paySign = WxUtils.signBySHA256WithRSA(content);//生成签名
        result.put("timeStamp", timeStamp+"");
        result.put("nonceStr",nonceStr);
        result.put("package","prepay_id="+prepay_id);
        result.put("signType","RSA");
        result.put("paySign",paySign);
        return result;
    }

生成RSA签名
V3只支持RSA签名方式

/**
     * rsa2Sign:rsa2签名
     * @author zhangh
     * @param content
     * @return
     */
    public static String signBySHA256WithRSA(String content){
       String paySign = "";
        try{
            try {
                Signature sign = Signature.getInstance("SHA256withRSA");
                sign.initSign(getPrivateKey());
                sign.update(content.getBytes(StandardCharsets.UTF_8));
                paySign = Base64Utils.encodeToString(sign.sign());
            } catch (IllegalBlockSizeException e) {
                e.printStackTrace();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
        return paySign;
    }

7.微信回调接口—> notify_url 回调地址

首先在控制层处理请求头
其次回调内容需解析
最后通过notification.getEventType() == ‘TRANSACTION.SUCCESS’ 判断是否支付成功

@ResponseBody
    @RequestMapping(value = "getPaySign", method = RequestMethod.POST)
    public R getPaySign(HttpServletRequest request, HttpServletResponse response){
       //回调通知的验签与解密
        String wechatPaySerial = request.getHeader(WECHAT_PAY_SERIAL);
        String nonce = request.getHeader(WECHAT_PAY_NONCE); // 请求头Wechatpay-Nonce
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); // 请求头Wechatpay-Timestamp
        String signature = request.getHeader(WECHAT_PAY_SIGNATURE); // 请求头Wechatpay-Signature
        String body = HttpUtils.readData(request);
        Map<String, String> param = new HashMap<>();
        param.put("wechatPaySerial",wechatPaySerial);
        param.put("nonce",nonce);
        param.put("timestamp",timestamp);
        param.put("signature",signature);
        param.put("body",body);
        //处理订单
        R result = registAppletService.getPaySign(param);
        return result;
    }

/**
     * 支付解析回调内容
     * @param param
     * @return
     */
    public static Notification analysisNotification(Map<String, String> param){
       try{
           String wechatPaySerial = param.get("wechatPaySerial");
           String nonce = param.get("nonce"); // 请求头Wechatpay-Nonce
           String timestamp = param.get("timestamp"); // 请求头Wechatpay-Timestamp
           String signature =param.get("signature"); // 请求头Wechatpay-Signature
           String body = param.get("body");
           Verifier verifier = WxUtils.getVerifier();
           // 构建request,传入必要参数
           NotificationRequest request = new NotificationRequest.Builder().withSerialNumber(wechatPaySerial)
                   .withNonce(nonce)
                   .withTimestamp(timestamp)
                   .withSignature(signature)
                   .withBody(body)
                   .build();
           NotificationHandler handler = new NotificationHandler(verifier, apiV3Key.getBytes(StandardCharsets.UTF_8));
           // 验签和解析请求体
           Notification notification = handler.parse(request);
           String eventType = notification.getEventType();
           logger.info("notification报文:"+notification);
           return notification;
       }catch(Exception e){
           e.printStackTrace();
           return null;
       }
    }

8.查询订单详情

查询订单详情也需要有签名处理

/**
     * 通过商户号和票号查询微信支付单
     * @param out_trade_no
     * @return
     */
    public static String HttpClientSel(String out_trade_no){
        String bodyAsString = "";
        try{
            PrivateKey merchantPrivateKey =WxUtils.getPrivateKey();
            String merchantSerialNumber = WxUtils.getMerchantSerialNumber();
            URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/"+out_trade_no+"?mchid="+merchantId);
            HttpGet httpGet = new HttpGet(uriBuilder.build());
            httpGet.addHeader("Accept", "application/json");
            Verifier verifier =  WxUtils.getVerifier();
            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier));
            // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
            CloseableHttpClient httpClient = builder.build();
            CloseableHttpResponse response = httpClient.execute(httpGet);
            bodyAsString = EntityUtils.toString(response.getEntity());
        }catch(Exception e){
            e.printStackTrace();
        }
        return bodyAsString;
    }