文章目录
微信支付
一、微信支付
首先说明
SDK的位置:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay6_0.shtml
其实现在版本有两个
版本1:wechatpay-java(推荐):https://github.com/wechatpay-apiv3/wechatpay-java
版本:2:wechatpay-apache-httpclient:https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
但是这篇文章用的是不推荐的那个
1.1 流程介绍
-
引入支付参数
支付参数包含商户号、AppId、Api秘钥、数字证书等
使用代码将支付参数加载到程序之中
-
加载商户私钥
在非对称加密中需要用到私钥和公钥。
我们的平台向微信发送请求
当我们向微信平台发送请求的时候,商户需要用私钥进行签名,微信平台接收到商户的请求之后,需要使用商户的公钥进行验签
-
获取平台证书和验签器
微信向我们的支付平台发送请求
当微信向支付平台发送请求的时候,微信支付平台会用他的私钥进行签名,那我们的商户会使用微信支付平台的公钥进行验签
而平台的公钥是从平台的数字证书当中获取的。所以这一步我们要获取平台的数字证书,冰倩创建我们的签名验证器
-
获取HttpClient对象
我们远程请求的发送时建立在http连接的基础上,所以我们需要使用httpClient工具建立远程连接
-
API字段和接口规则
-
内网穿透
微信向我们的开发服务器发送请求的时候,我们的开发服务器必须有一个微信可以访问的外网地址,但我们的开发机一般都是局域网环境的,没有独立IP,因此需要进行内网穿透,通过这种方式帮助我们将我们的开发机器映射到外网
-
API v3
1.2 配置参数
# 微信支付相关参数
# 商户号
wxpay.mch-id=1558950191
# 商户API证书序列号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F
# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
wxpay.appid=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 注意:每次重新启动ngrok,都需要根据实际情况修改这个配置
wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io
# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
- 商户号
商户号,在“微信平台”中“账户中心”的“个人信息”模块,复制“登录账号”即可
- 商户API证书序列号
商户API证书序列号,需要在商户平台申请一个证书
申请的时候其实是申请的私钥与证书,但是证书里面封装了公钥
申请了证书之后,每一个证书都会有一个商户证书API序列号
证书序列号一定要和证书对应
-
商户私钥文件
商户私钥文件加载到程序中的目的就是为了做签名
用私钥将我们的请求进行签名,然后把我们的请求信息发送给微信的服务器端
微信的服务端根据我们商户API证书序列号找到对应的证书,然后从证书中解析出公钥,然后用公钥对我们的请求进行验签
-
API v3密钥
在下面这个地方设置,此密钥是对称加密的密钥
-
APPID
APPID是我们在申请商户号的时候也申请了一个微信的公众号,APPID就是微信公众号中帮我们分配的
-
微信服务器地址
我们调用微信API v3接口时,都是往微信服务器发送请求
-
接收结果通知地址
微信也要向我们的商户端发送请求,我们这里用到了内网穿透
1.3 读取配置参数
@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
public class WxPayConfig {
// 商户号
private String mchId;
// 商户API证书序列号
private String mchSerialNo;
// 商户私钥文件
private String privateKeyPath;
// APIv3密钥
private String apiV3Key;
// APPID
private String appid;
// 微信服务器地址
private String domain;
// 接收结果通知地址
private String notifyDomain;
}
1.4 读取私钥
首先我们需要找到这个私钥文件
把这个证书复制到项目的根目录下
怎么读取私钥文件?
下面这个图截错了
应该是进:https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient这个地址
点击“wechatpay”便进入GitHub了,但是进GitHub的速度很慢,需要等待
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
引进微信的SDK,那怎么使用呢?
继续查看GitHub
我们这个项目中,私钥是存储在文件中,所以选择第一种示例
将下面这段代码写在@Configuration修饰的配置类中
设置为私有方法是为了保证安全
/**
* 获取商户的私钥文件
* @param filePath 私钥文件路径
* @return
*/
public PrivateKey getPrivateKey(String filePath){
try {
return PemUtil.loadPrivateKey(new FileInputStream(filePath));
} catch (FileNotFoundException e) {
throw new RuntimeException("私钥文件不存在", e);
}
}
下面进行测试
@Autowired
private WxPayConfig config;
@Test
void contextLoads() {
String privateKeyPath = config.getPrivateKeyPath();
PrivateKey privateKey = config.getPrivateKey(privateKeyPath);
System.out.println(privateKeyPath);
System.out.println(privateKey);
}
@Autowired
private WxPayConfig wxPayConfig;
/**
* 获取商户的私钥
*/
@Test
void testGetPrivateKey() {
//获取私钥路径
String privateKeyPath = wxPayConfig.getPrivateKeyPath();
//获取私钥
PrivateKey privateKey = wxPayConfig.getPrivateKey(privateKeyPath);
System.out.println(privateKeyPath);
System.out.println(privateKey);
}
读取到的内容
RSA是私钥的非对称加密算法,用的是2048 bits的密钥
二、 获取验签器和HttpClient
2.1 证书密钥使用说明
网站:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_0.shtml
下图就是这个网站中的内容
下图中的内容完整的描述了商户和微信支付平台的支付和响应流程
M代表商户,W代表微信支付平台
当我们的商户向微信支付平台发送请求之前,在我们商户这一侧需要用我们的私钥对请求进行签名,将请求发送之后,微信这一端用商户的公钥进行签名的验证,这个地方是一个很典型的非对称加密过程
微信在处理商户的请求之后,在向商户返回响应之前,微信平台使用自己的私钥对将要响应的数据进行签名,签名之后响应给商户平台,商户平台再用微信平台的公钥对微信平台响应的内容进行验签,当商户平台验证成功后才会继续做下一步的处理
这就是一个完整的同步请求和响应的流程
对编程的过程来说,我们要实现计算签名和验证签名的过程
我们也不用很带劲计算签名和验证签名,因为之前我们引入过微信平台的SDK,我们只需要为SDK提供一些必要的参数即可
2.2 HttpClient
1.5.2与1.5.3能够完成定时更新微信平台证书功能
我们需要创建一个HttpClient对象,这个对象会执行一个execute方法,执行成功后我们就会得到响应
下面标红的地方的httpClient.execute就是请求的发送和响应的过程
上面的这份文档在这里
下面我们就创建一个HttpClient
/**
* 获取http请求对象
* @param verifier 签名验证器
* @return
*/
@Bean(name = "wxPayClient")
public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier){
log.info("获取httpClient");
//获取商户私钥
PrivateKey privateKey = getPrivateKey(privateKeyPath);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
对于ScheduledUpdateCertificatesVerifier,查看1.5.3即可
2.3 获取签名验证器 verifier
我们如果要对微信平台端返回的响应结果进行验签,我们需要拿到微信平台的公钥。为了防止公钥被冒用,是不能进行简单的分发的,我们要以证书的形式分发。所以拿到微信平台的公钥就意味着要拿到微信平台的证书。
但是证书是有有效期的,为了保证微信平台的证书永远不过期,所以在SDK中提供了一个“定时更新平台证书功能”
/**
* 获取签名验证器
* @return
*/
@Bean
public ScheduledUpdateCertificatesVerifier getVerifier() {
log.info("获取签名验证器");
//TODO 获取商户私钥
//参数1:商户私钥文件在哪里
PrivateKey privateKey = getPrivateKey(privateKeyPath);
//TODO 私钥签名对象
//参数1:商户API证书序列号, 参数2:商户私钥文件
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//TODO 身份认证对象
//参数1:商户号,参数2:私钥签名对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
//TODO 使用定时更新的签名验证器,不需要传入证书
//参数1:身份认证对象,参数2:对称加密的密钥(数据传输的过程中也有对称加密)
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
wechatPay2Credentials,
apiV3Key.getBytes(StandardCharsets.UTF_8));
return verifier;
}
三、 API 字典
我们可以在网站中看到Native支付的一些内容
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml
3.1 封装API路径
@AllArgsConstructor
@Getter
public enum WxApiType {
/**
* Native下单
*/
NATIVE_PAY("/v3/pay/transactions/native"),
/**
* Native下单V2
*/
NATIVE_PAY_V2("/pay/unifiedorder"),
/**
* 查询订单
*/
ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),
/**
* 关闭订单
*/
CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),
/**
* 申请退款
*/
DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),
/**
* 查询单笔退款
*/
DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),
/**
* 申请交易账单
*/
TRADE_BILLS("/v3/bill/tradebill"),
/**
* 申请资金账单
*/
FUND_FLOW_BILLS("/v3/bill/fundflowbill");
/**
* 类型
*/
private final String type;
}
比如Native下单的路径是https://api.mch.weixin.qq.com/v3/pay/transactions/native,我们将其封装成一个枚举
3.2 封装通知
比如说下面的支付通知和退款结果通知,我们需要给微信平台提供一些路径,他们才能给我们通知,我们把我们开发的接口的路径放到枚举里面
下面的路径都是我们开发的,不是微信平台的路径。我们要将接口的路径告诉微信平台,他们才能给我们发送通知
@AllArgsConstructor
@Getter
public enum WxNotifyType {
/**
* 支付通知
*/
NATIVE_NOTIFY("/api/wx-pay/native/notify"),
/**
* 支付通知V2
*/
NATIVE_NOTIFY_V2("/api/wx-pay-v2/native/notify"),
/**
* 退款结果通知
*/
REFUND_NOTIFY("/api/wx-pay/refunds/notify");
/**
* 类型
*/
private final String type;
}
3.3 微信退款状态
@AllArgsConstructor
@Getter
public enum WxRefundStatus {
/**
* 退款成功
*/
SUCCESS("SUCCESS"),
/**
* 退款关闭
*/
CLOSED("CLOSED"),
/**
* 退款处理中
*/
PROCESSING("PROCESSING"),
/**
* 退款异常
*/
ABNORMAL("ABNORMAL");
/**
* 类型
*/
private final String type;
}
3.4 微信交易状态
是我们的商户平台和微信平台直接的交易状态,是微信定义的
@AllArgsConstructor
@Getter
public enum WxTradeState {
/**
* 支付成功
*/
SUCCESS("SUCCESS"),
/**
* 未支付
*/
NOTPAY("NOTPAY"),
/**
* 已关闭
*/
CLOSED("CLOSED"),
/**
* 转入退款
*/
REFUND("REFUND");
/**
* 类型
*/
private final String type;
}
3.5 业务订单状态
下面的枚举和微信平台没有关系,这只是我们系统中定义的一些业务订单状态
也就是用户和我们的商户平台之前的一些状态
@AllArgsConstructor
@Getter
public enum OrderStatus {
/**
* 未支付
*/
NOTPAY("未支付"),
/**
* 支付成功
*/
SUCCESS("支付成功"),
/**
* 已关闭
*/
CLOSED("超时已关闭"),
/**
* 已取消
*/
CANCEL("用户已取消"),
/**
* 退款中
*/
REFUND_PROCESSING("退款中"),
/**
* 已退款
*/
REFUND_SUCCESS("已退款"),
/**
* 退款异常
*/
REFUND_ABNORMAL("退款异常");
/**
* 类型
*/
private final String type;
}