1、介绍
代码地址: https://github.com/xm646236438/wechat_pay_score/tree/master
使用SpringBoot做的
第一眼看到文档, 确实有些懵逼
加上提供的文档, 可能是我懒, 真的不想看(不过说实话, 现在回头看看提供的文档, 还是很OK的)
废话不多说, 官方文档走起
官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter2_1.shtml
下一篇博客: 微信支付分(二)–查询支付分订单
2、参数介绍以及对应的位置
appid : 公众账号ID, 小程序, 公众号的都可以
service_id : 在申请后就会给到你, 如果没有就找申请的人要
serial_no: 证书序列号
商户号:
秘钥 证书,
重点, 如果秘钥已经设置了, 就问别人要, 如果有线上的在使用, 你懂的

3、贴代码, 具体代码github
创建支付分的签名:
package com.tomorrow.wechat_pay_score.util.wechart;
import com.tomorrow.wechat_pay_score.util.Utils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Scanner;
/**
* @author Tomorrow
* @date 2020/5/23 1:00
*/
@Slf4j
public class PayScore {
/**
* 微信支付API v3 签名
*
* @param method 请求类型GET、POST
* @param url 请求地址
* @param body 请求数据 GET: 传"" POST: json串
* @param merchantId 商户号
* @param certSerialNo 证书序列号
* @param filename API证书相对路径
* @return
* @throws Exception
*/
public static String getToken(String method, String url, String body, String merchantId, String certSerialNo, String filename) throws Exception {
String signStr = "";
HttpUrl httpurl = HttpUrl.parse(url);
// 随机字符串
String nonceStr = Utils.getRandomString(32);
// 时间戳
long timestamp = System.currentTimeMillis() / 1000;
if (StringUtils.isEmpty(body)) {
body = "";
}
String message = buildMessage(method, httpurl, timestamp, nonceStr, body);
String signature = sign(message.getBytes("utf-8"), filename);
signStr = "mchid=\"" + merchantId
+ "\",nonce_str=\"" + nonceStr
+ "\",timestamp=\"" + timestamp
+ "\",serial_no=\"" + certSerialNo
+ "\",signature=\"" + signature + "\"";
log.info("Authorization Token:" + signStr);
return signStr;
}
public static String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.encodedPath();
if (url.encodedQuery() != null) {
canonicalUrl += "?" + url.encodedQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
public static String sign(byte[] message, String filename) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey(filename));
sign.update(message);
return Base64.encodeBase64String(sign.sign());
}
/**
* 获取私钥。
*
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
// 编译后的相对路径
ClassPathResource classPathResource = new ClassPathResource(filename);
InputStream inputStream = classPathResource.getInputStream();
Scanner scanner = new Scanner(inputStream, "UTF-8");
String content = scanner.useDelimiter("\\A").next();
// 绝对路径
// String content = new String(Files.readAllBytes(Paths.get("F:\\key\\publicKey.pem")), "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.decodeBase64(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
log.info("异常:" + e);
throw new RuntimeException("无效的密钥格式");
}
}
}
业务逻辑:
@Override
public CommonResult wakeUpPaymentPoints(String orderNo, int depositAmount) {
// 创建支付分订单 请求参数
JSONObject parameters = new JSONObject();
parameters.put("out_order_no", orderNo);
parameters.put("appid", appId);
parameters.put("service_id", serviceId);
parameters.put("service_introduction", "阿啵呲嘚");
JSONObject timeRange = new JSONObject();
timeRange.put("start_time", "OnAccept");
parameters.put("time_range", timeRange);
JSONObject riskFund = new JSONObject();
riskFund.put("name", "DEPOSIT");
riskFund.put("amount", depositAmount);
riskFund.put("description", "阿啵呲嘚");
parameters.put("risk_fund", riskFund);
parameters.put("notify_url", notifyURL);
parameters.put("need_user_confirm", true);
JSONObject jsonObject;
try {
log.info("请求参数", JSONObject.toJSONString(parameters));
String data = HttpRequest.post(createOrderUrl)
.header(Header.CONTENT_TYPE, "application/json")
.header(Header.ACCEPT, "application/json")
// 签名
.header("Authorization", "WECHATPAY2-SHA256-RSA2048" + " "
+ PayScore.getToken("POST", createOrderUrl, JSONObject.toJSONString(parameters), mchId, serialNo, "pem/apiclient_key.pem"))
.body(JSONObject.toJSONString(parameters))
.execute().body();
jsonObject = JSONObject.parseObject(data);
System.out.println("返回参数" + jsonObject);
}catch (Exception e) {
throw new SpringExceptionResolver("500", "网络超时!");
}
if (!"CREATED".equals(jsonObject.getString("state"))) {
throw new SpringExceptionResolver("500", jsonObject.getString("message"));
}
// 处理返回数据, 前端拿到后可以进行直接使用
SortedMap<Object, Object> result = new TreeMap<Object, Object>();
result.put("mch_id", mchId);
result.put("package", jsonObject.getString("package"));
result.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
result.put("nonce_str", Utils.getRandomString(32));
result.put("sign_type", "HMAC-SHA256");
// 签名
result.put("sign", HMACSHA256.sha256_HMAC(result, mchKey));
return CommonResult.success("SUCCESS", result);
}
HMAC-SHA256签名: 校验地址
package com.tomorrow.wechat_pay_score.util.wechart;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Map;
import java.util.SortedMap;
/**
* @author Tomorrow
* @date 2020/5/23 1:00
*/
public class HMACSHA256 {
/**
* 将加密后的字节数组转换成字符串
*
* @param b 字节数组
* @return 字符串
*/
public static String byteArrayToHexString(byte[] b) {
StringBuilder hs = new StringBuilder();
String stmp;
for (int n = 0; b != null && n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0XFF);
if (stmp.length() == 1)
hs.append('0');
hs.append(stmp);
}
return hs.toString().toLowerCase();
}
/**
* sha256_HMAC加密
*
* @param parameters 参数
* @param key 秘钥
* @return 加密后字符串
*/
public static String sha256_HMAC(SortedMap<Object, Object> parameters, String key) {
// 对数据进行排序
StringBuilder sb = new StringBuilder();
for (Map.Entry entry : parameters.entrySet()) {
// 去除掉空参数以及sign
if (entry.getValue() != null && entry.getKey() != "sign") {
sb = sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
;
}
// 拼接API密钥
sb.append("key=" + key);
// 待签名字符串
String message = sb.toString();
String hash = "";
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
hash = byteArrayToHexString(bytes).toUpperCase();
} catch (Exception e) {
System.out.println("Error HmacSHA256 ===========" + e.getMessage());
}
return hash;
}
}
4、测试结果
{
"code": 200,
"message": "SUCCESS",
"data": {
"mch_id": "小嘛小儿郎背着那书包上学堂",
"nonce_str": "4i8tNute22VdlN5UXpu8a1UwT5h11813",
"package": "小嘛小儿郎背着那书包上学堂",
"sign": "868D29B5867A51F6B53C9180B54B9BD527638EDF0F30A2A3D5EDEA95B2971B02",
"sign_type": "HMAC-SHA256",
"timestamp": "1591426290"
}
}
比如小程序就可以直接用啦:
https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter8_2.shtml
5、杂谈
1、这个做完, 就相当于已经完成三分之一了
2、还有一个很重要的事情,感谢 蛋挞小子:https://www.cnblogs.com/nginxTest/p/12697136.html 签名借鉴了一波, 哈哈哈, 感谢感谢
3、GitHub上面的提交的代码, 只是一个列子, 要视自己的具体业务逻辑进行修改一下就行
4、其实很简单, 很简单, 很简单
5、时间太晚了, 写的就粗糙了一些, 包含包含
6、代码地址: https://github.com/xm646236438/wechat_pay_score/tree/master
7、关于证书,因为证书会更新以及过期, 这个地方最好是动态获取, 而不是从商户号拿,这个地方后期补充文章
8、碰到的问题可以在此处搜索:https://developers.weixin.qq.com/community/pay/doc/0004060fa7c65855d698166145b808?blockType=8
9、系统繁忙. 和微信技术沟通过, 说是系统有时会这样, 属于正常的.会有技术对接群, 可以询问
提供一个群:807770565,欢迎各位进来尬聊