- 相关官方文档
下载批次核销明细https://pay.weixin.qq.com/docs/merchant/apis/cash-coupons/stock/use-flow.html如何生成请求签名
https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-generation.html如何使用V3验签工具校验签名
https://developers.weixin.qq.com/community/develop/article/doc/00084630cf40e82afc2c843eb5b413
签名相关问题https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-faqs.html
代码实现
验签
参数说明:
url:请求地址url
paymentData : 微信支付配置
主要参数: mch_id 微信支付商户号
sn 序列号
key_path : 证书路径 私钥
stockId : 批次号
public static String createAuth(String url,JSONObject paymentData,String stockId){
//组装Authorization 以及签名
String schema = "WECHATPAY2-SHA256-RSA2048";
//时间戳
long timestamp = System.currentTimeMillis() / 1000L;
//随机串
String s = OrderNoGeneratorUtil.generateOrderNumber("");
String method = "GET";
//批次号
String stock_id = stockId;
HttpUrl httpurl = HttpUrl.parse(url);
String body = "";
//商户号
String yourMerchantId = paymentData.getString("mch_id");
//证书序列号
String yourCertificateSerialNo = paymentData.getString("sn");
String signature = null;
String preSign = buildMessage(method, httpurl, timestamp, s, body);
try {
signature = sign(preSign.getBytes("utf-8"),paymentData.getString("key_path").substring(1));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("签名:" + signature);
return schema + " mchid=\"" + yourMerchantId + "\","
+ "nonce_str=\"" + s + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + yourCertificateSerialNo + "\","
+ "signature=\"" + signature + "\"";
}
private static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
System.out.println(method + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonceStr + "\n" + body);
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
/***
* 对签名串进行RSA2加签
*/
private static String sign(byte[] message,String keyPath) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
//微信支付私钥
String content = new String(Files.readAllBytes(Paths.get(keyPath)), "utf-8");
try {
String privateKeyStr = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKeyStr)));
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
请 求接口 下载批次核销明细
//设置HTTP Authorization头
String hostUrl = "https://api.mch.weixin.qq.com";
//stock_id 替换成已有的值
String method = "v3/marketing/favor/stocks/{stock_id}/use-flow";
//GET请求(官网)
HttpGet httpGet = new HttpGet(hostUrl + method);
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Authorization", auth);
response = httpClient.execute(httpGet);
//POST请求
HttpPost httpPost = new HttpPost(hostUrl + method);
StringEntity reqEntity = new StringEntity(
requestJson, ContentType.create("application/json", "utf-8"));
httpPost.setEntity(reqEntity);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Authorization", auth);
response = httpClient.execute(httpPost);
HttpEntity resEntity = response.getEntity();
String retMsg = EntityUtils.toString(resEntity, "UTF-8");// 微信返回的参数
- 返回参数示例:url是储存在微信服务器的文件地址
{
"url" : "https://api.mch.weixin.qq.com/v3/billdownload/file?token=ja7q-s1yy1ZbROASakz0Jx4BjW3qdnympjfcB4v4yLftXXXXXXXXXXXX",
"hash_value" : "8ae0eb442c408d2e90d669d6f4ad6b7e6e049d6f",
"hash_type" : "SHA1"
}
请求返回的url
url 无法直接请求,需要加上请求头
Authorization: 认证类型 签名信息
Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"
要保证:要保证与 下载批次核销明细的时间戳(timestamp) 随机数(s) 商户号(mch_id) 证书(私钥)(key_path) 序列号(sn) 批次号(stockId) 一致
//读取数据
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder()
.url(url) // url为下载核销明细api返回url
.header("Accept", "application/json")
.header("Authorization",auth)
.build();
try {
Response response = okHttpClient.newCall(request).execute();
if (response.isSuccessful()) {
response.body().string();
}
} catch (IOException e) {
e.printStackTrace();
}
错误
- 验签错误
没有保证两次生成验签的数据一致
- 请求头 的Authorization 是两部分组成
Authorization: 认证类型 签名信息