微信支付 APIv3 Java SDK
微信支付 APIv3 官方 Java 语言客户端开发库。
开发库由 core
和 service
组成:
- core 为基础库,包含自动签名和验签的 HTTP 客户端、回调处理、加解密库。
- service 为业务服务,包含业务接口和使用示例。
帮助微信支付改进
为了向广大开发者提供更好的使用体验,微信支付诚挚邀请您反馈使用微信支付 Java SDK 中的感受。您的反馈将对改进 SDK 大有帮助,点击参与问卷调查。
前置条件
- Java 1.8+。
- 成为微信支付商户。
- 商户 API 证书:指由商户申请的,包含证书序列号、商户的商户号、公司名称、公钥信息的证书。
- 商户 API 私钥:商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。
- APIv3 密钥:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了 AES-256-GCM 加密。APIv3 密钥是加密时使用的对称密钥。
快速开始
安装
最新版本已经在 Maven Central 发布。
Gradle
在你的 build.gradle 文件中加入如下的依赖
implementation 'com.github.wechatpay-apiv3:wechatpay-java:0.2.14'
Maven
加入以下依赖
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.14</version>
</dependency>
调用业务请求接口
以 Native 支付下单为例,先补充商户号等必要参数以构建 config
,再构建 service
即可调用 prepay()
发送请求。
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.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
public class QuickStart {
public static String merchantId = "190000****";
public static String privateKeyPath = "/Users/yourname/your/path/apiclient_key.pem";
public static String merchantSerialNumber = "5157F09EFDC096DE15EBE81A47057A72********";
public static String apiV3Key = "...";
public static void main(String[] args) {
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
NativePayService service = new NativePayService.Builder().config(config).build();
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");
PrepayResponse response = service.prepay(request);
System.out.println(response.getCodeUrl());
}
}
从示例可见,使用 SDK 不需要计算请求签名和验证应答签名。详细代码可从 QuickStart 获得。
示例
查询支付订单
QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
queryRequest.setMchid("190000****");
queryRequest.setTransactionId("4200001569202208304701234567");
try {
Transaction result = service.queryOrderById(queryRequest);
System.out.println(result.getTradeState());
} catch (ServiceException e) {
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");
service.closeOrder(closeRequest);
下单并生成调起支付的参数
JSAPI 支付和 APP 支付推荐使用服务拓展类 JsapiServiceExtension 和 AppServiceExtension,两者包含了下单并返回调起支付参数方法。
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
PrepayRequest request = new PrepayRequest();
PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request);
上传图片
import com.wechat.pay.java.service.file.FileUploadService;
import com.wechat.pay.java.service.file.model.FileUploadResponse;
FileUploadService fileService = new FileUploadService.Builder().config(config).build();
FileUploadResponse fileUploadResponse = fileUploadService.uploadImage(uploadUrl, meta, imagePath);
更多示例
为了方便开发者快速上手,微信支付给每个服务生成了示例代码 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 密钥是否正确。
自动更新微信支付平台证书
为确保 API 请求过程中的安全性,客户端需要使用微信支付平台证书来验证服务器响应的真实性和完整性。
从 v0.2.3 版本开始,我们引入了一个名为 RSAAutoCertificateConfig
的配置类,用于自动更新平台证书。
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
RSAAutoCertificateConfig
会利用 AutoCertificateService
自动下载微信支付平台证书。
AutoCertificateService
将启动一个后台线程,定期(目前为每60分钟)更新证书,以实现证书过期时的平滑切换。
在每次构建 RSAAutoCertificateConfig
时,SDK 首先会使用传入的商户参数下载一次微信支付平台证书。
如果下载成功,SDK 会将商户参数注册或更新至 AutoCertificateService
。若下载失败,将会抛出异常。
为了提高性能,建议将配置类作为全局变量。
复用 RSAAutoCertificateConfig
可以减少不必要的证书下载,避免资源浪费。
只有在配置发生变更时,才需要重新构造 RSAAutoCertificateConfig
。
如果您有多个商户号,可以为每个商户构建相应的 RSAAutoCertificateConfig
。
Note
从 v0.2.10 开始,我们不再限制每个商户号只能创建一个 RSAAutoCertificateConfig
。
使用本地的微信支付平台证书
如果你不想使用 SDK 提供的定时更新平台证书,你可以使用配置类 RSAConfig
加载本地证书。
Config config =
new RSAConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.wechatPayCertificatesFromPath(wechatPayCertificatePath)
.build();
使用本地平台公钥
如果你的商户可使用微信支付的公钥验证应答和回调的签名,可使用微信支付公钥和公钥ID初始化。
Config config =
new RSAPublicKeyConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.publicKeyFromPath(publicKeyPath)
.publicKeyId(publicKeyId)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
回调通知
首先,你需要在你的服务器上创建一个公开的 HTTP 端点,接受来自微信支付的回调通知。
当接收到回调通知,使用 notification 中的 NotificationParser
解析回调通知。
具体步骤如下:
- 使用回调通知请求的数据,构建
RequestParam
。
- HTTP 请求体 body。切记使用原始报文,不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
- HTTP 头
Wechatpay-Signature
。应答的微信支付签名。 - HTTP 头
Wechatpay-Serial
。微信支付平台证书的序列号,验签必须使用序列号对应的微信支付平台证书。 - HTTP 头
Wechatpay-Nonce
。签名中的随机数。 - HTTP 头
Wechatpay-Timestamp
。签名中的时间戳。 - HTTP 头
Wechatpay-Signature-Type
。签名类型。
- 初始化
RSAAutoCertificateConfig
。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。 - 初始化
NotificationParser
。 - 调用
NotificationParser.parse()
验签、解密并将 JSON 转换成具体的通知回调对象。如果验签失败,SDK 会抛出 ValidationException
。 - 接下来可以执行你的业务逻辑了。如果执行成功,你应返回
200 OK
的状态码。如果执行失败,你应返回 4xx
或者 5xx
的状态码,例如数据库操作失败建议返回 500 Internal Server Error
。
RequestParam requestParam = new RequestParam.Builder()
.serialNumber(wechatPaySerial)
.nonce(wechatpayNonce)
.signature(wechatSignature)
.timestamp(wechatTimestamp)
.body(requestBody)
.build();
NotificationConfig config = new RSAAutoCertificateConfig.Builder()
.merchantId(merchantId)
.privateKeyFromPath(privateKeyPath)
.merchantSerialNumber(merchantSerialNumber)
.apiV3Key(apiV3Key)
.build();
NotificationParser parser = new NotificationParser(config);
try {
Transaction transaction = parser.parse(requestParam, Transaction.class);
} catch (ValidationException e) {
logger.error("sign verification failed", e);
return ResponseEntity.status(HttpStatus.UNAUTHORIZED);
}
if () {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
}
return ResponseEntity.status(HttpStatus.OK);
常用的通知回调调对象类型有:
- 支付
Transaction
- 退款
RefundNotification
- 若 SDK 暂不支持的类型,请使用
Map.class
,嵌套的 Json 对象将被转换成 LinkedTreeMap
你既可以为每个通知回调使用不同的 HTTP 端点,也可以使用一个端点根据 event_type
处理不同的通知回调。
我们建议,不同的通知回调使用不同的端点,直接调用 SDK 处理通知回调,避免商户自己解析报文。因为 SDK 会先验证通知回调的有效性,可有效防止"坏人"的报文攻击。
发送 HTTP 请求
如果 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);
String respBody = IOUtil.toString(inputStream);
inputStream.close();
Warning
开发者在下载文件之后,应使用第一步获取的账单摘要校验文件的完整性。
敏感信息加解密
为了保证通信过程中敏感信息字段(如用户的住址、银行卡号、手机号码等)的机密性,
- 微信支付要求加密上送的敏感信息
- 微信支付会加密下行的敏感信息
详见 接口规则 - 敏感信息加解密。
自动加解密
如果 SDK 已支持的接口,例如商家转账,SDK 将根据契约自动对敏感信息做加解密:
- 发起请求时,开发者设置原文。SDK 自动加密敏感信息,并设置
Wechatpay-Serial
请求头 - 收到应答时,解密器自动解密敏感信息,开发者得到原文
手动加解密
如果 SDK 尚未支持某个接口,你可以使用 cipher 中的 RSAPrivacyEncryptor
和 RSAPrivacyDecryptor
,手动对敏感信息加解密。
当你使用自动获取的微信支付平台证书时,可以通过以下方法获取加密器 PrivacyEncryptor
,以及对应的证书序列号。
PrivacyEncryptor encryptor = config.createEncryptor();
String wechatPayCertificateSerialNumber = encryptor.getWechatpaySerial();
String ciphertext = encryptor.encryptToString(plaintext);
当你使用本地的公钥或私钥,可以通过以下方法直接构建加密器 PrivacyEncryptor
和解密器 PrivacyDecryptor
。
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)日志实现,它不会记录任何日志。
网络配置
SDK 使用 OkHttp 作为默认的 HTTP 客户端。
如果开发者不熟悉 OkHttp,推荐使用 SDK 封装的 DefaultHttpClientBuilder 来构造 HTTP 客户端。
目前支持的网络配置方法见下表。
下面的示例演示了如何使用 DefaultHttpClientBuilder 初始化某个具体的业务 Service。
HttpClient httpClient =
new DefaultHttpClientBuilder()
.config(config)
.connectTimeoutMs(500)
.build();
JsapiService service = new JsapiService.Builder().httpclient(httpClient).build();
更多网络配置的说明,请看 wiki - 网络配置。
双域名容灾
为提升商户系统访问 API 的稳定性,SDK 实现了双域名容灾。如果主要域名 api.mch.weixin.qq.com
因网络问题无法访问,我们的 SDK 可自动切换到备用域名 api2.wechatpay.cn
重试当前请求。
这个机制可以最大限度减少因微信支付 API 接入点故障或主域名问题(如 DNS 劫持)对商户系统的影响。
默认情况下,双域名容灾机制处于关闭状态,以避免重试降低商户系统的吞吐量。因为 OkHttp 默认会尝试主域名的多个IP地址(目前为2个),增加备用域名重试很可能会提高异常情况下的处理时间。
我们推荐开发者使用 disableRetryOnConnectionFailure
和 enableRetryMultiDomain
的组合,启用双域名容灾并关闭 OkHttp 默认重试,这样不会增加重试次数。
假设 api.mch.weixin.qq.com
解析得到 [ip1a, ip1b],api2.wechatpay.cn
解析得到 [ip2a, ip2b],不同的重试策略组合对应的尝试顺序为:
- 默认:[ip1a, ip1b]
- disableRetryOnConnectionFailure:[ip1a]
- enableRetryMultiDomain:[ipa1, ip1b, ip2a, ip2b]
- (推荐)disableRetryOnConnectionFailure + enableRetryMultiDomain: [ip1a, ip2a]
以下是采用推荐重试策略的示例代码:
HttpClient httpClient =
new DefaultHttpClientBuilder()
.config(config)
.disableRetryOnConnectionFailure()
.enableRetryMultiDomain()
.build();
JsapiService service = new JsapiService.Builder().httpclient(httpClient).build();
开发者应该仔细评估自己的商户系统容量,根据自身情况选择合适的超时时间和重试策略,并做好监控和告警。
使用国密
我们提供基于 腾讯 Kona 国密套件 的国密扩展。文档请参考 shangmi/README.md。
常见问题
为什么收到应答中的证书序列号和发起请求的证书序列号不一致?
请求和应答使用 数字签名 ,保证数据传递的真实、完整和不可否认。为了验签方能识别数字签名使用的密钥(特别是密钥和证书更换期间),微信支付 APIv3 要求签名和相应的证书序列号一起传输。
- 商户请求使用商户API私钥签名。商户应上送商户证书序列号。
- 微信支付应答使用微信支付平台私钥签名。微信支付应答返回微信支付平台证书序列号。
综上所述,请求和应答的证书序列号是不一致的。
证书和回调解密需要的 AesGcm 解密在哪里?
请参考 AeadAesCipher 和 AeadAesCipherTest 。
由于 SDK 已经提供了微信支付平台证书下载服务 CertificateService
以及回调通知解析器 NotificationParser
,这两者会完成所有的解析与解密工作。因此除非你想要自定义实现,否则你应该不需要用到 AeadXxxCipher
中提供的方法。
为什么我使用 NotificationHandler
验证回调通知失败,抛出 ValidationException
?
如果你使用的是 SDK 自动更新的微信支付平台证书,验证失败原因是:参与验证的参数不正确。从开发者反馈来看,大部分失败案例没有使用回调原始 body,而是用 body 反序列化得到的对象再做 JSON 序列化得到的 body。很遗憾,这样的 body 几乎一定跟原始报文不一致,所以签名验证不通过。具体案例可参考 #112。
如果你使用的是本地的微信支付平台证书,请检查微信支付平台证书是否正确,不要把商户证书和微信支付平台证书搞混了。
如何计算前端签名?
有一部分 API 需要计算前端签名,例如调起支付、调起支付分小程序等。
为什么快速开始的示例程序执行后,程序不会退出?
在 v0.2.10 中,我们将定时更新证书的线程设置为后台线程,程序可以正常退出了。
如何参与开发
微信支付欢迎来自社区的开发者贡献你们的想法和代码。请你在提交 PR 之前,先提一个对应的 issue 说明以下内容:
- 背景(如,遇到的问题)和目的。
- 着重说明你的想法。
- 通过代码或者其他方式,简要的说明是如何实现的,或者它会是如何使用。
- 是否影响现有的接口。
联系微信支付
如果你发现了 BUG,或者需要的功能还未支持,或者有任何疑问、建议,欢迎通过 issue 反馈。
也欢迎访问微信支付的 开发者社区。