微信支付 APIv3 官方 Java 语言客户端开发库。
开发库由 core
和 service
组成:
为了向广大开发者提供更好的使用体验,微信支付诚挚邀请您反馈使用微信支付 Java SDK 中的感受。您的反馈将对改进 SDK 大有帮助,点击参与问卷调查。
- Java 1.8+。
- 成为微信支付商户。
- 商户 API 证书:指由商户申请的,包含商户的商户号、公司名称、公钥信息的证书。
- 商户 API 私钥:商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。
- APIv3 密钥:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了 AES-256-GCM 加密。APIv3 密钥是加密时使用的对称密钥。
最新版本已经在 Maven Central 发布。
在你的 build.gradle 文件中加入如下的依赖
implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.5'
加入以下依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.5</version>
</dependency>
以 JSAPI 下单为例,先构建 config
和 service
,再发送请求。详细代码可参考 QuickStart。
package com.wechat.pay.java.service;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
/** JSAPI 下单为例 */
public class QuickStart {
/** 商户号 */
public static String merchantId = "";
/** 商户API私钥路径 */
public static String privateKeyPath = "";
/** 商户证书序列号 */
public static String merchantSerialNumber = "";
/** 商户APIV3密钥 */
public static String apiV3key = "";
public static void main(String[] args) {
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3key)
.build();
JsapiService service = new JsapiService.Builder().config(config).build();
// request.setXxx(val)设置所需参数,具体参数可见Request定义
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(100);
request.setAmount(amount);
request.setAppid("wxa9d9651ae******");
request.setMchid("190000****");
request.setDescription("测试商品标题");
request.setNotifyUrl("https://notify_url");
request.setOutTradeNo("out_trade_no_001");
Payer payer = new Payer();
payer.setOpenid("oLTPCuN5a-nBD4rAL_fa********");
request.setPayer(payer);
PrepayResponse response = service.prepay(request);
System.out.println(response.getPrepayId());
}
}
从示例可见,使用 SDK 并不需要计算请求签名和验证应答签名。
QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
queryRequest.setMchid("190000****");
queryRequest.setTransactionId("4200001569202208304701234567");
try {
Transaction result = service.queryOrderById(queryRequest);
System.out.println(result.getTradeState());
} catch (ServiceException e) {
// API返回失败, 例如ORDER_NOT_EXISTS
System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(), e.getErrorMessage());
System.out.printf("reponse body=[%s]\n", e.getResponseBody());
}
CloseOrderRequest closeRequest = new CloseOrderRequest();
closeRequest.setMchid("190000****");
closeRequest.setOutTradeNo("out_trade_no_001");
// 方法没有返回值,意味着成功时API返回204 No Content
service.closeOrder(closeRequest);
JSAPI 支付和 APP 支付推荐使用服务拓展类 JsapiServiceExtension 和 AppServiceExtension,两者包含了下单并返回调起支付参数方法。
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
// 跟之前下单示例一样,填充预下单参数
PrepayRequest request = new PrepayRequest();
// response包含了调起支付所需的所有参数,可直接用于前端调起支付
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
为了方便开发者快速上手,微信支付给每个服务生成了示例代码 XxxServiceExample.java
,可以在 example 中查看。
例如:
SDK 使用的是 unchecked exception,会抛出四种自定义异常。每种异常发生的场景及推荐的处理方式如下:
- HttpException:调用微信支付服务,当发生 HTTP 请求异常时抛出该异常。
- 构建请求参数失败、发送请求失败、I/O错误:推荐上报监控和打印日志,并获取异常中的 HTTP 请求信息以定位问题。
- ValidationException :当验证微信支付签名失败时抛出该异常。
- 验证微信支付返回签名失败:上报监控和日志打印。
- 验证微信支付回调通知签名失败:确认输入参数与 HTTP 请求信息是否一致,若一致,说明该回调通知参数被篡改导致验签失败。
- ServiceException:调用微信支付服务,发送 HTTP 请求成功,HTTP 状态码小于200或大于等于300。
- 状态码为5xx:主动重试。
- 状态码为其他:获取错误中的
errorCode
、errorMessage
,上报监控和日志打印。
- MalformedMessageException:服务返回成功,返回内容异常。
- HTTP 返回
Content-Type
不为application/json
:不支持其他类型的返回体,下载账单 应使用download()
方法。 - 解析 HTTP 返回体失败:上报监控和日志打印。
- 回调通知参数不正确:确认传入参数是否与 HTTP 请求信息一致,传入参数是否存在编码或者 HTML 转码问题。
- 解析回调请求体为 JSON 字符串失败:上报监控和日志打印。
- 解密回调通知内容失败:确认传入的 apiV3 密钥是否正确。
- HTTP 返回
在 API 请求过程中,客户端需使用微信支付平台证书,验证服务器应答的真实性和完整性。
在 v0.2.3 版本,我们加入了自动更新平台证书的配置类 RSAAutoCertificateConfig
。
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3key)
.build();
RSAAutoCertificateConfig
通过 RSAAutoCertificateProvider
自动下载微信支付平台证书。
同时,RSAAutoCertificateProvider
会启动一个后台线程,定时更新证书(目前设计为60分钟),以实现证书过期时的新老证书平滑切换。
Note
每个商户号只能创建一个
RSAAutoCertificateConfig
。同一个商户号构造多个实例,会抛出IllegalStateException
异常。我们建议你将配置类作为全局变量。如果你的程序是多线程,建议使用多线程安全的单例模式。
如果你不想使用 SDK 提供的定时更新平台证书,你可以使用配置类 RSAConfig
加载本地证书。
Config config =
new RSAConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.wechatPayCertificatesFromPath(wechatPayCertificatePath)
.build();
可以使用 notification 中的 NotificationParser
解析回调通知。具体步骤如下:
- 获取 HTTP 请求头中的以下值,构建
RequestParam
。Wechatpay-Signature
Wechatpay-Nonce
Wechatpay-Timestamp
Wechatpay-Serial
Wechatpay-Signature-Type
- 获取 HTTP 请求体 body。切记不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
- 根据解密后的通知数据数据结构,构造解密对象类
DecryptObject
。支付结果通知解密对象类为Transaction
,退款结果通知解密对象类为 RefundNotification。 - 初始化
RSAAutoCertificateConfig
。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。 - 初始化
NotificationParser
。 - 使用请求参数
requestParam
和DecryptObject.class
,调用parser.parse
验签并解密报文。
// 构造 RequestParam
RequestParam requestParam = new Builder()
.serialNumber(wechatPayCertificateSerialNumber)
.nonce(nonce)
.signature(signature)
.timestamp(timstamp)
// 若未设置signType,默认值为 WECHATPAY2-SHA256-RSA2048
.signType(signType)
.body(requestBody)
.build();
// 如果已经初始化了 RSAAutoCertificateConfig,可直接使用
// 没有的话,则构造一个
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3key)
.build();
// 初始化 NotificationParser
NotificationParser parser = new NotificationParser(config);
// 验签并解密报文
DecryptObject decryptObject = parser.parse(requestParam,DecryptObject.class);
如果 SDK 未支持你需要的接口,你可以使用 OkHttpClientAdapter 的实现类发送 HTTP 请求,它会自动生成签名和验证签名。
发送请求步骤如下:
- 初始化
OkHttpClientAdapter
,建议使用DefaultHttpClientBuilder
构建。 - 构建请求
HttpRequest
。 - 调用
httpClient.execute
或者httpClient.get
等方法来发送 HTTP 请求。httpClient.execute
支持发送 GET、PUT、POST、PATCH、DELETE 请求,也可以调用指定的 HTTP 方法发送请求。
OkHttpClientAdapterTest 中演示了如何构造和发送 HTTP 请求。如果现有的 OkHttpClientAdapter
实现类不满足你的需求,可以继承 AbstractHttpClient 拓展实现。
因为下载的账单文件可能会很大,为了平衡系统性能和签名验签的实现成本,账单下载API 被分成了两个步骤:
/v3/bill/tradebill
申请账单下载链接,并获取账单摘要。/v3/billdownload/file
账单文件下载,请求需签名但应答不签名。
SDK 提供了 HttpClient.download()
方法。它返回账单的输入流。开发者使用完输入流后,应自主关闭流。
InputStream inputStream = httpClient.download(downloadUrl);
// 非压缩的账单可使用 core.util.IOUtil 从流读入内存字符串,大账单请慎用
String respBody = IOUtil.toString(inputStream);
inputStream.close();
Warning
开发者在下载文件之后,应使用第一步获取的账单摘要校验文件的完整性。
为了保证通信过程中敏感信息字段(如用户的住址、银行卡号、手机号码等)的机密性,
- 微信支付要求加密上送的敏感信息
- 微信支付会加密下行的敏感信息
详见 接口规则 - 敏感信息加解密。
如果是 SDK 已支持的接口,例如商家转账,SDK 将根据契约自动对敏感信息做加解密:
- 发起请求时,开发者设置原文。SDK 自动加密敏感信息,并设置
Wechatpay-Serial
请求头 - 收到应答时,解密器自动解密敏感信息,开发者得到原文
如果是 SDK 尚未支持的接口,你可以使用 cipher 中的 RSAPrivacyEncryptor
和 RSAPrivacyDecryptor
,手动对敏感信息加解密。
// 微信支付平台证书中的公钥
PublicKey wechatPayPublicKey = null;
String plaintext = "";
PrivacyEncryptor encryptor = new RSAPrivacyEncryptor(wechatPayPublicKey);
String ciphertext = encryptor.encryptToString(plaintext);
// 商户私钥
PrivateKey merchantPrivateKey = null;
String ciphertext = "";
PrivacyDecryptor decryptor = new RSAPrivacyDecryptor(merchantPrivateKey);
String plaintext = decryptor.decryptToString(ciphertext);
RSAPrivacyEncryptorTest 和 RSAPrivacyDecryptorTest 中演示了如何使用以上函数做敏感信息加解密。
SDK 使用了 SLF4j 作为日志框架的接口。这样,你可以使用你熟悉的日志框架,例如 Logback、Log4j2 或者 SLF4j-simple。 SDK 的日志会跟你的日志记录在一起。
为了启用日志,你应在你的构建脚本中添加日志框架的依赖。如果不配置日志框架,默认是使用 SLF4j 提供的 空(NOP)日志实现,它不会记录任何日志。
我们提供基于 腾讯 Kona 国密套件 的国密扩展。文档请参考 shangmi/README.md。
请求和应答使用 数字签名 ,保证数据传递的真实、完整和不可否认。为了验签方能识别数字签名使用的密钥(特别是密钥和证书更换期间),微信支付 APIv3 要求签名和相应的证书序列号一起传输。
- 商户请求使用商户API私钥签名。商户应上送商户证书序列号。
- 微信支付应答使用微信支付平台私钥签名。微信支付应答返回微信支付平台证书序列号。
综上所述,请求和应答的证书序列号是不一致的。
请参考 AeadAesCipher 和 AeadAesCipherTest 。
由于 SDK 已经提供了微信支付平台证书下载服务 CertificateService
以及回调通知解析器 NotificationParser
,这两者会完成所有的解析与解密工作。因此除非你想要自定义实现,否则你应该不需要用到 AeadXxxCipher
中提供的方法。
如果你使用的是 SDK 自动更新的微信支付平台证书,验证失败原因是:参与验证的参数不正确。从开发者反馈来看,大部分失败案例没有使用回调原始 body,而是用 body 反序列化得到的对象再做 JSON 序列化得到的 body。很遗憾,这样的 body 几乎一定跟原始报文不一致,所以签名验证不通过。具体案例可参考 #112。
如果你使用的是本地的微信支付平台证书,请检查微信支付平台证书是否正确,不要把商户证书和微信支付平台证书搞混了。
有一部分 API 需要计算前端签名,例如调起支付、调起支付分小程序等。
-
调起支付签名,SDK 提供了下单并生成调起支付参数的方法,请参考 示例。
-
其他场景计算签名,请参考 JsapiServiceExtension 使用 Signer 计算签名的例子。
微信支付欢迎来自社区的开发者贡献你们的想法和代码。请你在提交 PR 之前,先提一个对应的 issue 说明以下内容:
- 背景(如,遇到的问题)和目的。
- 着重说明你的想法。
- 通过代码或者其他方式,简要的说明是如何实现的,或者它会是如何使用。
- 是否影响现有的接口。
如果你发现了 BUG,或者需要的功能还未支持,或者有任何疑问、建议,欢迎通过 issue 反馈。
也欢迎访问微信支付的 开发者社区。