实战微信支付 APIv3 接口
Posted sp42a
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战微信支付 APIv3 接口相关的知识,希望对你有一定的参考价值。
开场白直接引用官方文档的吧。
为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付 APIv3 接口。
为啥不用官方 SDK?
官方 SDK 不错,只是依赖 Apache-httpclient,可是我连 Apache-httpclient 都不想用啊,于是就自行接入。其实官方文档也很详尽,只是有点乱(否则就没有我写本文的需要啦)。官方文档如是说。
在规则说明中,你将了解到微信支付API v3的基础约定,如数据格式、参数兼容性、错误处理、UA说明等。我们还重点介绍了微信支付API v3新的认证机制(证书/密钥/签名)。你可以跟随着开发指南,使用命令行或者你熟悉的编程语言,一步一步实践签名生成、签名验证、证书和回调报文解密和敏感信息加解密。在最后的常见问题中,我们总结了商户接入过程遇到的各种问题。
准备条件
该申请的都申请,把所需的条件准备好。形成如下 Java POJO 要求的字段。
/**
* 微信支付 商户配置
*
* @author Frank Cheung<sp42@qq.com>
*
*/
public class MerchantConfig
/**
* 商户号
*/
private String mchId;
/**
* 商户证书序列号
*/
private String mchSerialNo;
/**
* V3 密钥
*/
private String apiV3Key;
/**
* 商户私钥
*/
private String privateKey;
public String getMchId()
return mchId;
public void setMchId(String mchId)
this.mchId = mchId;
public String getMchSerialNo()
return mchSerialNo;
public void setMchSerialNo(String mchSerialNo)
this.mchSerialNo = mchSerialNo;
public String getApiV3Key()
return apiV3Key;
public void setApiV3Key(String apiV3Key)
this.apiV3Key = apiV3Key;
public String getPrivateKey()
return privateKey;
public void setPrivateKey(String privateKey)
this.privateKey = privateKey;
签名
访问商户平台的支付接口都要在 HTTP Head 加上签名才能访问。下图以小程序的为例子。
如何生成签名?下面按照文档指引,以获取商户平台证书为例子,生成签名。
准备私钥
首先你要准备好商户 API 证书里面的私钥(Private Key),例如我当前读取磁盘的证书(当然这个到时要部署到服务器资源目录下)。
private String privateKey = FileHelper.openAsText("C:\\\\Users\\\\frank\\\\Downloads\\\\WXCertUtil\\\\cert\\\\1623777099_20220330_cert\\\\apiclient_key.pem");
@Autowired
private MerchantConfig cfg;
……
cfg.setPrivateKey(privateKey);// 保存到配置
转换为 Java 里面的 PrivateKey
对象,依靠下面的工具类 PemUtil
。
public class PemUtil
public static PrivateKey loadPrivateKey(String privateKey)
privateKey = privateKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "").replaceAll("\\\\s+", "");
try
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("无效的密钥格式");
……
签名生成器
签名过程参见文档,此处不再赘述。除了一般的时间戳、请求随机串(nonce_str
)等等之外,签名要求内容有请求接口的 HTTP 方法、URL 和 请求报文主体,为此我们准备一个简单的 Bean。
/**
* 请求接口的 HTTP 方法、URL 和 请求报文主体
*
* @author Frank Cheung<sp42@qq.com>
*
*/
public class HttpRequestWrapper
public String method;
public String url;
public String body;
我们看看调用例子。
HttpRequestWrapper r = new HttpRequestWrapper();
r.method = "GET";
r.url = "/v3/certificates";
r.body = "";
SignerMaker signer = new SignerMaker(cfg);
String token = signer.getToken(r);// 得到签名
签名生成器 SignerMaker
源码如下。
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import com.ajaxjs.util.StrUtil;
import com.ajaxjs.util.logger.LogHelper;
import com.ajaxjs.wechat.applet.util.PemUtil;
import com.ajaxjs.wechat.applet.util.RsaCryptoUtil;
/**
* 签名生成器
*
* @author Frank Cheung<sp42@qq.com>
*
*/
public class SignerMaker
private static final LogHelper LOGGER = LogHelper.getLog(SignerMaker.class);
private MerchantConfig cfg;
protected final PrivateKey privateKey;
/**
* 创建签名生成器
*
* @param cfg 商户平台的配置
*/
public SignerMaker(MerchantConfig cfg)
this.cfg = cfg;
this.privateKey = PemUtil.loadPrivateKey(cfg.getPrivateKey());
/**
* 生成签名
*
* @param request
* @return 签名 Token
*/
public String getToken(HttpRequestWrapper request)
String nonceStr = StrUtil.getRandomString(32);
long timestamp = System.currentTimeMillis() / 1000;
String message = buildMessage(request, nonceStr, timestamp);
LOGGER.debug("authorization message=[0]", message);
String signature = RsaCryptoUtil.sign(privateKey, message.getBytes(StandardCharsets.UTF_8));
// @formatter:off
String token = "mchid=\\"" + cfg.getMchId() + "\\","
+ "nonce_str=\\"" + nonceStr + "\\","
+ "timestamp=\\"" + timestamp + "\\","
+ "serial_no=\\"" + cfg.getMchSerialNo() + "\\","
+ "signature=\\"" + signature + "\\"";
// @formatter:on
LOGGER.debug("authorization token=[0]", token);
return token;
/**
*
* @param request
* @param nonceStr
* @param timestamp
* @return
*/
static String buildMessage(HttpRequestWrapper request, String nonceStr, long timestamp)
// @formatter:off
return request.method + "\\n"
+ request.url + "\\n"
+ timestamp + "\\n"
+ nonceStr + "\\n"
+ request.body + "\\n";
// @formatter:on
从代码量看确实比以前简单了。
对签名数据进行签名
上述 getToken()
里面会调用 RsaCryptoUtil.sign()
,其源码如下。
/**
* 对签名数据进行签名。
*
* 使用商户私钥对待签名串进行 SHA256 with RSA 签名,并对签名结果进行 Base64 编码得到签名值。
*
* @param message
* @return 签名结果
*/
public static String sign(PrivateKey privateKey, byte[] message)
try
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(message);
return StrUtil.base64Encode(sign.sign());
catch (NoSuchAlgorithmException e)
throw new RuntimeException("当前 Java 环境不支持 SHA256withRSA", e);
catch (SignatureException e)
throw new RuntimeException("签名计算失败", e);
catch (InvalidKeyException e)
throw new RuntimeException("无效的私钥", e);
测试
得到签名 Token 后就可以放在请求头里面测试了,如下获取证书,这是我自己封装的请求方法(Get.api()
)。
商户API证书 v.s 微信支付平台证书
事情复杂起来了,
获取平台证书
以上是关于实战微信支付 APIv3 接口的主要内容,如果未能解决你的问题,请参考以下文章