JSAPI支付V3版本的2种用法

JSAPI支付V3版本的2种用法,第1张

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 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 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 "";
        }

        
        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);
            }
      }


    
      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 "";
      }


    
     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值连起来的时候,记得有个空格。
有时候签名一直错误,那么建议去微信官方下载一个验签的工具,自己在上面验证一下,一般证书密码就是商户号。

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

首先maven先导入依赖


    com.github.wechatpay-apiv3
    wechatpay-apache-httpclient
    0.2.3

实现方法如下:

            //根据证书地址获取证书私钥
            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密钥问题
解决方法是在 Javajdk1.8.0_45jrelibsecurity下,将local_policy.jar 和 US_export_policy.jar 替换。替换包下载路径 下载补丁包

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5596747.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存