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值连起来的时候,记得有个空格。
有时候签名一直错误,那么建议去微信官方下载一个验签的工具,自己在上面验证一下,一般证书密码就是商户号。
接着说第二种方法。来源自微信的: 自动更新证书
首先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 替换。替换包下载路径 下载补丁包
大致就这样吧,希望能帮到你们。 有错误和改进的地方希望可以指出来。