Skip to content

xy-peng/wechatpay-java

 
 

Repository files navigation

JavaDoc Maven Central Security Rating Maintainability Rating Coverage

微信支付 APIv3 Java SDK

微信支付 APIv3 官方 Java 语言客户端开发库。

开发库由 coreservice 组成:

  • 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.5'

Maven

加入以下依赖

<dependency>
  <groupId>com.github.wechatpay-apiv3</groupId>
  <artifactId>wechatpay-java</artifactId>
  <version>0.2.5</version>
</dependency>

调用业务请求接口

以 JSAPI 下单为例,先构建 configservice,再发送请求。详细代码可参考 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 支付推荐使用服务拓展类 JsapiServiceExtensionAppServiceExtension,两者包含了下单并返回调起支付参数方法。

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:主动重试。
    • 状态码为其他:获取错误中的 errorCodeerrorMessage,上报监控和日志打印。
  • 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 通过 RSAAutoCertificateProvider 自动下载微信支付平台证书。 同时,RSAAutoCertificateProvider 会启动一个后台线程,定时更新证书(目前设计为60分钟),以实现证书过期时的新老证书平滑切换。

Note

每个商户号只能创建一个 RSAAutoCertificateConfig。同一个商户号构造多个实例,会抛出 IllegalStateException 异常。

我们建议你将配置类作为全局变量。如果你的程序是多线程,建议使用多线程安全的单例模式。

使用本地的微信支付平台证书

如果你不想使用 SDK 提供的定时更新平台证书,你可以使用配置类 RSAConfig 加载本地证书。

Config config =
    new RSAConfig.Builder()
        .merchantId(merchantId)
        .privateKeyFromPath(privateKeyPath)
        .merchantSerialNumber(merchantSerialNumber)
        .wechatPayCertificatesFromPath(wechatPayCertificatePath)
        .build();

回调通知验签和解密

可以使用 notification 中的 NotificationParser 解析回调通知。具体步骤如下:

  1. 获取 HTTP 请求头中的以下值,构建 RequestParam
    • Wechatpay-Signature
    • Wechatpay-Nonce
    • Wechatpay-Timestamp
    • Wechatpay-Serial
    • Wechatpay-Signature-Type
  2. 获取 HTTP 请求体 body。切记不要用 JSON 对象序列化后的字符串,避免验签的 body 和原文不一致。
  3. 根据解密后的通知数据数据结构,构造解密对象类 DecryptObject 。支付结果通知解密对象类为 Transaction,退款结果通知解密对象类为 RefundNotification
  4. 初始化 RSAAutoCertificateConfig。微信支付平台证书由 SDK 的自动更新平台能力提供,也可以使用本地证书。
  5. 初始化 NotificationParser
  6. 使用请求参数 requestParamDecryptObject.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);

发送 HTTP 请求

如果 SDK 未支持你需要的接口,你可以使用 OkHttpClientAdapter 的实现类发送 HTTP 请求,它会自动生成签名和验证签名。

发送请求步骤如下:

  1. 初始化 OkHttpClientAdapter,建议使用 DefaultHttpClientBuilder 构建。
  2. 构建请求 HttpRequest
  3. 调用 httpClient.execute 或者 httpClient.get 等方法来发送 HTTP 请求。httpClient.execute 支持发送 GET、PUT、POST、PATCH、DELETE 请求,也可以调用指定的 HTTP 方法发送请求。

OkHttpClientAdapterTest 中演示了如何构造和发送 HTTP 请求。如果现有的 OkHttpClientAdapter 实现类不满足你的需求,可以继承 AbstractHttpClient 拓展实现。

下载账单

因为下载的账单文件可能会很大,为了平衡系统性能和签名验签的实现成本,账单下载API 被分成了两个步骤:

  1. /v3/bill/tradebill 申请账单下载链接,并获取账单摘要。
  2. /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 中的 RSAPrivacyEncryptorRSAPrivacyDecryptor ,手动对敏感信息加解密。

// 微信支付平台证书中的公钥
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);

RSAPrivacyEncryptorTestRSAPrivacyDecryptorTest 中演示了如何使用以上函数做敏感信息加解密。

日志

SDK 使用了 SLF4j 作为日志框架的接口。这样,你可以使用你熟悉的日志框架,例如 LogbackLog4j2 或者 SLF4j-simple。 SDK 的日志会跟你的日志记录在一起。

为了启用日志,你应在你的构建脚本中添加日志框架的依赖。如果不配置日志框架,默认是使用 SLF4j 提供的 空(NOP)日志实现,它不会记录任何日志。

使用国密

我们提供基于 腾讯 Kona 国密套件 的国密扩展。文档请参考 shangmi/README.md

常见问题

为什么收到应答中的证书序列号和发起请求的证书序列号不一致?

请求和应答使用 数字签名 ,保证数据传递的真实、完整和不可否认。为了验签方能识别数字签名使用的密钥(特别是密钥和证书更换期间),微信支付 APIv3 要求签名和相应的证书序列号一起传输。

  • 商户请求使用商户API私钥签名。商户应上送商户证书序列号。
  • 微信支付应答使用微信支付平台私钥签名。微信支付应答返回微信支付平台证书序列号。

综上所述,请求和应答的证书序列号是不一致的。

证书和回调解密需要的 AesGcm 解密在哪里?

请参考 AeadAesCipherAeadAesCipherTest

由于 SDK 已经提供了微信支付平台证书下载服务 CertificateService 以及回调通知解析器 NotificationParser ,这两者会完成所有的解析与解密工作。因此除非你想要自定义实现,否则你应该不需要用到 AeadXxxCipher 中提供的方法。

为什么我使用 NotificationHandler 验证回调通知失败,抛出 ValidationException

如果你使用的是 SDK 自动更新的微信支付平台证书,验证失败原因是:参与验证的参数不正确。从开发者反馈来看,大部分失败案例没有使用回调原始 body,而是用 body 反序列化得到的对象再做 JSON 序列化得到的 body。很遗憾,这样的 body 几乎一定跟原始报文不一致,所以签名验证不通过。具体案例可参考 #112

如果你使用的是本地的微信支付平台证书,请检查微信支付平台证书是否正确,不要把商户证书和微信支付平台证书搞混了。

如何计算前端签名?

有一部分 API 需要计算前端签名,例如调起支付、调起支付分小程序等。

  • 调起支付签名,SDK 提供了下单并生成调起支付参数的方法,请参考 示例

  • 其他场景计算签名,请参考 JsapiServiceExtension 使用 Signer 计算签名的例子。

如何参与开发

微信支付欢迎来自社区的开发者贡献你们的想法和代码。请你在提交 PR 之前,先提一个对应的 issue 说明以下内容:

  • 背景(如,遇到的问题)和目的。
  • 着重说明你的想法。
  • 通过代码或者其他方式,简要的说明是如何实现的,或者它会是如何使用。
  • 是否影响现有的接口。

联系微信支付

如果你发现了 BUG,或者需要的功能还未支持,或者有任何疑问、建议,欢迎通过 issue 反馈。

也欢迎访问微信支付的 开发者社区

About

微信支付 APIv3 的 Java Library

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%