评论

JAVAv3接口统一下单代码分享

jsapiV3代码分享

商户号:商户平台查看

appid:商户平台查看(需要有绑定关系对应的支付属性需要一一对应,比如app支付只能使用移动应用的appid)

apiv3密钥https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html(一定要设置的!!!!)

证书序列号:商户平台查看(登陆商户平台【API安全】->【API证书】->【查看证书】,可查看商户API证书序列号。商户API证书和微信支付平台证书均可以使用第三方的证书解析工具,查看证书内容。或者使用openssl命令行工具查看证书序列号。 注意区分:这个是商户api证书的证书序列号

商户证书私钥:就是商户api证书的私钥,链接:https://kf.qq.com/faq/161222NneAJf161222U7fARv.html(apiclient_key.pem这个文件,文本方式打开或者用方法获取)

代码环境:jdk15(jdk1.8即可)

如果是按照下面的方法统一下单还报签名错误,一定是你的参数有问题!!!!你的参数有问题!!!!

基础支付的统一下单基本是一样的!

参考官方文档链接:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_3.shtml

详细代码:


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 com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import okhttp3.HttpUrl;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
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.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.PrivateKey;


public class WxPayJsApi {

    //新建一个可以关闭的请求
    CloseableHttpClient httpClient;

    /**
     * 测试jsapi支付的统一下单
     */

    //1.先加载两个证书
    @Before
    public void setup() throws IOException {
         //获取商户api证书私钥的两种方法
        //方法1直接把商户证书的里面的私钥以常量的形式带着代码中(不安全)
        String privateKey1 = "你商户api证书的私钥";
        //方法2利用获取私钥的类加载私钥(此类是官方写好的方法直接照搬过来即可)
        PrivateKey privateKey = MyPrivateKey.getPrivateKey("商api证书路径");
        //通过方法把私钥转换为字符串格式,这个方法是我自己找的,官方并未给示例,应该是方法不唯一和获取方式不唯一
        byte[] encoded = privateKey.getEncoded();
        System.out.println("encoded:"+encoded);
        String prikeyStr = Base64.encodeBase64String(privateKey.getEncoded());//Base64:package org.apache.commons.codec.binary


        // 加载商户私钥(privateKey:私钥字符串)
        PrivateKey merchantPrivateKey = PemUtil
                  //依旧是商户api证书的私钥
                .loadPrivateKey(new ByteArrayInputStream(privateKey1.getBytes("utf-8")));

        // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
        //商户号在商户平台能看到,序列号一样也是,这个apiv3秘钥跟商户证书api私钥一定要注意区分
        //设置  apiv3秘钥参看文档:https://kf.qq.com/faq/180830E36vyQ180830AZFZvu.html
               AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
                new WechatPay2Credentials("你的商户号", new PrivateKeySigner("你商户api证书的序列号", merchantPrivateKey)),"apiv3秘钥".getBytes("utf-8"));
             
       //获取微信支付平台证书证书序列号
         BigInteger serialNumber = verifier.getValidCertificate().getSerialNumber();
        //转成16进制
          String serialnumber =  serialNumber.toString(16);
        //获取微信支付平台证书
        X509Certificate validCertificate = verifier.getValidCertificate();
        //获取微信支付平台证书公钥
         PublicKey publicKey1 = validCertificate.getPublicKey();
       //转成字符串
        String prikeyStr1 = Base64.encodeBase64String(publicKey.getEncoded());//Base64:package
        System.out.println("prikeyStr:"+prikeyStr);
  
                  // 初始化httpClient
                   httpClient = WechatPayHttpClientBuilder.create()
                              //merchantPrivateKey:平台证书就是上面的方法
                              .withMerchant("你的商号", "你商户api证书的序列号", merchantPrivateKey)
                              .withValidator(new WechatPay2Validator(verifier)).build();
              }
          
          
              //2.获取api证书私钥,详情见MyPrivateKey
               
          
              //3.构造签名,sdk自动构建签名了,无须商户自己构建签名
           
         
    
        
        // 请求body参数
       //这是照搬官方的参数,把商户号和appid修改为你自己使用的即可,需要和上面初始化httpClient的步骤中的参数是一一对应的
       //注意:官方文档显示必填参数就是必须填写的,金额一定不能是小数,参数不能携带空格
      //(到后期出现问题唤起支付的时候后期出现问题你就知道头疼)
      //请求URL
          HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
          httpPost.addHeader("Accept", "application/json");
          httpPost.addHeader("Content-type","application/json; charset=utf-8");
          ByteArrayOutputStream bos = new ByteArrayOutputStream();
          ObjectMapper objectMapper = new ObjectMapper();
            
            ObjectNode rootNode = objectMapper.createObjectNode();
            rootNode.put("mchid","你的商户号")
                    .put("appid", "你的appid")
                    .put("description", "Image形象店-深圳腾大-QQ公仔")
                    .put("notify_url", "https://www.weixin.qq.com/wxpay/pay.php")
                    .put("out_trade_no", "12177525012014070332333680183");
        rootNode.putObject("amount")
                .put("total", 1);
      
      objectMapper.writeValue(bos, rootNode);
  
    httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
    CloseableHttpResponse response = httpClient.execute(httpPost);
    
    String bodyAsString = EntityUtils.toString(response.getEntity());
    System.out.println(bodyAsString);
    
        }
    
    
    
        //最后会自动执行,释放资源
        @After
        public void after() throws IOException {
            httpClient.close();
        }
    
    }


生成签名的官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml

我的代码:CreateSign

import okhttp3.HttpUrl;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;

public class CreateSign {
    String getToken(String method, HttpUrl url, String body) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        //随机字符串
        String nonceStr = "asdasdffafafgddfvc";//真!随机字符串
        //时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        //从下往上依次生成
        String message = buildMessage(method, url, timestamp, nonceStr, body);
        System.out.println("message:"+message);
        //签名
        String signature = sign(message.getBytes("utf-8"));
        System.out.println("signature:"+signature);
        System.out.println("nonceStr:"+nonceStr);
        return "mchid=\"" + "你的商户号" + "\","
                + "nonce_str=\"" + nonceStr + "\","//真!随机自传承
                + "timestamp=\"" + timestamp + "\","//时间戳
                + "serial_no=\"" + "商户证书的证书序列号" + "\","
                + "signature=\"" + signature + "\"";
    }

    String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
        //加密方式
        Signature sign = Signature.getInstance("SHA256withRSA");
        //私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名
        sign.initSign(MyPrivateKey.getPrivateKey("商户证书的路径"));
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }

    String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
        String canonicalUrl = url.encodedPath();
        if (url.encodedQuery() != null) {    //get请求自动做了校验,会把空字符串进行识别,注意是空字符串!!!!!
            canonicalUrl += "?" + url.encodedQuery();
        }
        //官方的方法自动做了换行的所有动作,注意唤起支付的参数不一样需要更换(这里是统一下单所以直接照搬即可)
        return method + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + body + "\n";
    }

}



加载商户证书私钥的方法官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay7_1.shtml

我的代码:MyPrivatekey

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class MyPrivateKey {
    /**
     * 获取私钥。
     *  这是个静态方法,可以直接用类名调用
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     *
     * 完全不需要修改,注意此方法也是去掉了头部和尾部,注意文件路径名
     */
    public static 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("无效的密钥格式");
        }
    }
}
   



最后一次编辑于  2022-11-14  
点赞 5
收藏
评论

5 个评论

  • J
    J
    2022-06-06

    这个gettoken的方法在哪里用呢

    2022-06-06
    赞同
    回复 1
    • HOPE
      HOPE
      2022-06-22
      使用sdk就不需要使用了,sdk自动生成签名,如果你不使用官方的sdk,就可以使用这个示例代码是生成签名
      2022-06-22
      回复
  • 蓝贝贝
    蓝贝贝
    发表于移动端
    2022-04-21
    这是什么意思呀
    2022-04-21
    赞同
    回复
  • 一水
    一水
    2021-09-28

    楼主, okhttp3.HttpUrl; 这个类里面是啥代码呢?

    2021-09-28
    赞同
    回复 1
    • HOPE
      HOPE
      2021-10-04
      这个类的作用可以参考这篇文章:https://www.cnblogs.com/liyutian/p/9473747.html   ,写的还是比较详细的,你注意一下生成签名的方法是需要一个HttpUrl类,这是我自己使用的,当时官方sdk是不需要自己生成签名的,我就把下单里面的生成签名的方法删除了,这个类没有删除
      2021-10-04
      回复
  • 乔
    2021-07-10

    感谢,也就是说,如果在公众号里打开的jsp应用,需要调用支付,统一下单时,使用jsapiv3。

    就必须要v3密钥和证书序列号对吗?

    官方文档里写的太模糊了,以为直接Post:https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi这里就能得到prepay_id。


    2021-07-10
    赞同
    回复 2
  • 默默积极(王洋)
    默默积极(王洋)
    2021-05-20

    前端发起jspi支付怎么弄,前端要时间戳,你这签名使用的时间戳如何返回给前端使用?

    2021-05-20
    赞同
    回复 1
    • HOPE
      HOPE
      2021-10-04
      生成签名的时间戳可以自己查询相关的方法,只要生成的时候保存一下,比如定义一个变量来保存生成签名的时间戳,跟签名值一起返回给前端就可以
      2021-10-04
      1
      回复
登录 后发表内容