JSAPI支付V3版本的2种用法

JSAPI支付V3版本更V2版本不同的是,V3需要使用自身的私钥对API URL、消息体等关键数据的组合进行SHA-256 with RSA签名。请求的签名信息通过HTTP头Authorization 传递。 说实话,微信官方文档有时候真的觉得是个憨批,东一块西一块的,说话不说全。找起来真的是麻烦。不多说,先开始第一种用法。

            //先将参数放入Object
            ObjectMapper objectMapper = new ObjectMapper();
            ObjectNode jsonObject = objectMapper.createObjectNode();
            jsonObject.put("mchid","");
            jsonObject.put("appid","");
            jsonObject.put("description","Image形象店-深圳腾大-QQ公仔");
            jsonObject.put("notify_url","http://****/wechat/backll");
            jsonObject.put("out_trade_no", "DO202111231042500002");  //订单号
            jsonObject.putObject("amount")
                      .put("total", 1);                             //金额
            jsonObject.putObject("payer")
                      .put("openid","");                            //支付人信息
            System.out.println(jsonObject.toString());
            V3Parpy v3 = new V3Parpy();                            //这里是我自己定义的类
            KeyPair keyPair = v3.createPKCS12("apiclient_cert.p12","Tenpay Certificate","1483413552");   //获取私钥,这里我用的是apiclient_cert.p12证书,直接放在resources下
            String url ="https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi"; //接口
            String nonceStr =  CommonUtils.generateUUID();         //随机数;
            long timestamp = System.currentTimeMillis() / 1000;    //时间戳
        
            String url2 ="/v3/pay/transactions/jsapi";           //加密时用到的请求地址,注意,千万不要带域名部分,带了就会报错。
            String sign = v3.sign("POST",url2,timestamp,nonceStr,jsonObject.toString(),keyPair); //获取sign签名
            System.out.println("sign    :" +sign);
            String token = v3.token("商户号mchid",nonceStr,timestamp,"证书序列号",sign);
            System.out.println(token);
            String surrecs =  HttpUtils.doPost2(url,jsonObject.toString(),4000,token); //发起请求,将token带过去 微信要求请求头中必须要带Authorization 格式为 WECHATPAY2-SHA256-RSA2048 token 
            //请求完成后,我们会得到预支付交易会话标识 prepay_id。这个后面下单需要用到。
            //先将下单接口需要用到的参数放入集合,然后加密得到sign签名
            List<String> list = new ArrayList<>();
            list.add("wx0e3c90e9f84c1c05");
            list.add(String.valueOf(timestamp));
            list.add(nonceStr);
            list.add("prepay_id="+surrecs);
            String  paySign=  GetPaySign(list,keyPair);
            System.out.println(paySign);
            //得到签名后,将需要用到的接口返回给前端页面,这里前端页面和上篇v2文档里差不多,就不列出来了。唯一不同的就是需要改一下签名的方式 从MD5该为RSA
            model.addAttribute("appId","wx0e3c90e9f84c1c05");
            model.addAttribute("timeStamp",timestamp);
            model.addAttribute("nonceStr",nonceStr);
            model.addAttribute("prepay_id","prepay_id="+surrecs);
            model.addAttribute("paySign",paySign);
            return "pay/JSAPIPay";
 //使用私钥对数据进行SHA256withRSA加密,并且进行Base64编码。
      public static String GetPaySign(List<String> list, KeyPair keyPair){
           try {
             StringBuffer buffer = new StringBuffer();
             for (String str: list) {
               buffer.append(str).append("\n");
             }
             System.out.println(buffer.toString());
             Signature sign = Signature.getInstance("SHA256withRSA");
             sign.initSign(keyPair.getPrivate());
             sign.update(buffer.toString().getBytes(StandardCharsets.UTF_8));
             return Base64Utils.encodeToString(sign.sign());
           }catch (Exception e){

           }
           return "";
        }

        /**
         * 获取公私钥.
         * @param keyPath  the key path
         * @param keyAlias the key alias
         * @param keyPass  password
         * @return the key pair
         */
        public KeyPair createPKCS12(String keyPath, String keyAlias, String keyPass) {
            ClassPathResource resource = new ClassPathResource(keyPath);
            char[] pem = keyPass.toCharArray();
            try {
                synchronized (lock) {
                    if (store == null) {
                        synchronized (lock) {
                            store = KeyStore.getInstance("PKCS12");
                            store.load(resource.getInputStream(), pem);
                        }
                    }
                }
                X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
                certificate.checkValidity();
                // 证书的序列号 也有用
                String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
                System.out.println(serialNumber);
                // 证书的 公钥
                PublicKey publicKey = certificate.getPublicKey();
                // 证书的私钥
                PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);
                return new KeyPair(publicKey, storeKey);
            } catch (Exception e) {
                throw new IllegalStateException("Cannot load keys from store: " + resource, e);
            }
      }


    /**
     * V3  SHA256withRSA 签名.
     * @param method       请求方法  GET  POST PUT DELETE 等
     * @param canonicalUrl 例如  /v3/pay/transactions/app?version=1 
     * @param timestamp    当前时间戳   因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
     * @param nonceStr     随机字符串  要和TOKEN中的保持一致
     * @param body         请求体 GET 为 "" POST 为JSON
     * @param keyPair      商户API 证书解析的密钥对  实际使用的是其中的私钥
     * @return the string
     */
      public String sign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair)  {
          try {
              String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
                      .collect(Collectors.joining("\n", "", "\n"));
              Signature sign = Signature.getInstance("SHA256withRSA");
              sign.initSign(keyPair.getPrivate());
              sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
              return Base64Utils.encodeToString(sign.sign());
          }catch (Exception e){

          }
          return "";
      }


    /**
     * 生成Token.
     * @param mchId      商户号
     * @param nonceStr   随机字符串
     * @param timestamp  时间戳
     * @param serialNo   证书序列号
     * @param signature  签名
     * @return           the string
     */
     public String token(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {
          final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
          // 生成token
          return String.format(TOKEN_PATTERN,
                  mchId,
                  nonceStr, timestamp, serialNo, signature);
      }

我在其中碰到的坑就是这个方法中的一些参数要保持一致,比如:随机数、时间戳。
在sign签名加密时,用的url不要有域名。
WECHATPAY2-SHA256-RSA2048和 token值连起来的时候,记得有个空格。
有时候签名一直错误,那么建议去微信官方下载一个验签的工具,自己在上面验证一下,一般证书密码就是商户号。
JSAPI支付V3版本的2种用法
JSAPI支付V3版本的2种用法

接着说第二种方法。来源自微信的: 自动更新证书

首先maven先导入依赖

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.2.3</version>
</dependency>

实现方法如下:

            //根据证书地址获取证书私钥
            PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
                    new FileInputStream("E:\\WXCertUtil\\cert\\apiclient_key.pem"));
             //apiv3密钥 自己在商户平台上,上传证书的时候设置的 
            String apiv3 ="";
             //自动更新证书功能
            AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                    new WechatPay2Credentials("商户号mchid", new PrivateKeySigner("证书序列号", merchantPrivateKey)), apiv3.getBytes());
             //WechatPayHttpClientBuilder构造的HttpClient,后续的HttpClient都可以在这个上取,不必每次生成,这里我就都放一起了。自己根据自己业务逻辑可以另外放     
            WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                    .withMerchant("商户号mchid", "证书序列号", merchantPrivateKey)
                    .withValidator(new WechatPay2Validator(verifier));
            HttpClient httpClient = builder.build();
            //JSAPI下单
            ObjectMapper objectMapper = new ObjectMapper();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectNode jsonObject = objectMapper.createObjectNode();
            jsonObject.put("mchid","");
            jsonObject.put("appid","");
            jsonObject.put("description","Image形象店-深圳腾大-QQ公仔");
            jsonObject.put("notify_url","http://********/wechat/backll");
            jsonObject.put("out_trade_no", "DO202111231042500002");
            jsonObject.putObject("amount")
                      .put("total", 1);
            jsonObject.putObject("payer")
                      .put("openid","");
            System.out.println(jsonObject.toString());
           //请求地址
            HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
            //设置请求头
            httpPost.addHeader("Accept", "application/json");  
            httpPost.addHeader("Content-type","application/json; charset=utf-8");
            objectMapper.writeValue(bos, jsonObject);
            httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
            CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
            //获取预下单的单据id
            String bodyAsString = EntityUtils.toString(response.getEntity());
            System.out.println(bodyAsString);
            //后面的下单内容和加密大致跟前面一样了,这里我就不重复了

这个里面可能会碰到这个问题,就是apiv3密钥哪里长度的错误。
使用Java8 加载密钥时,抛出异常InvalidKeyException: Illegal key size
这个问题在jdk9及以上不会出现,问题在于API V3密钥问题
解决方法是在 Java\jdk1.8.0_45\jre\lib\security下,将local_policy.jar 和 US_export_policy.jar 替换。替换包下载路径 下载补丁包

大致就这样吧,希望能帮到你们。 有错误和改进的地方希望可以指出来。

上一篇:electron+react 进程间通信碰见的问题解决


下一篇:electron-log 修改日志保存路径