因项目需要,有用到微信支付,这里对java微信支付的开发流程和注意事项到做一次记录,以遍后面有需要的时候翻阅,方便回顾。
快速开发一个功能,需要了解整个开发过程以及开发过程中需要注意的点,开发的功能才能足够安全高效
如下是微信支付官方给出的微信支付V3流程图:
微信支付V3开发流程(阅读下面代码时,需要对照当前流程):
1、首先是接入前准备
- 选择接入模式
- 参数申请
- 配置API key
- 下载并配置商户证书
- App支付页面规范
具体参考微信支付V3接入前准备:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_1.shtml
2、开发准备
搭建和配置开发环境,根据自身开发语言,选择对应的开发库构建项目,笔者这里使用的是Java,所以使用的是wechatpay-apache-httpclient,只要将对应的maven依赖引入项目即可
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.1</version>
</dependency>
,最新依赖版本可参考https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
3、API接口列表
需要注意的是接口的适用接入模式,请求参数,接口地址,返回参数一定要跟微信官方文档的一致。
4、接口规则
- 签名生成:微信支付Api v3 key要求商户对请求进行签名,微信支付会在收到请求后进行签名的验证。如果验证不通过,微信支付Api v3会拒绝处理请求。返回状态401
- 签名验证:微信支付会在回调的Http头部中包括回调报文的签名,以确保回调是由微信支付发送
- 证书和回调报文加密解密:为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密
- 平台证书更新:由于平台证书存在有效期,需要定时更换平台证书以确保交易安全
微信支付V3java 代码:
配置信息
wechat:
appId: ###
mchId: ###
aesKey: ###
unifiedOrder:
url: https://api.mch.weixin.qq.com/v3/pay/transactions/app
notify:
url: ###
queryOrder:
url: https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s
certificates:
url: https://api.mch.weixin.qq.com/v3/certificates
package com.hjy.ft.config;
import com.hjy.pay.WxpayV3Util;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.*;
import java.security.cert.X509Certificate;
/**
* @version 1.0
* @className WeChatStartUpRunner
* @description 程序启动后加载
* @since 2021/4/19 14:47
*/
@Component
@Order(value = 1)
public class WeChatStartUpRunner implements CommandLineRunner {
/**
* 微信商户号
*/
@Value("${wechat.mchId}")
private String wechatMchId;
/**
* 应用id
*/
@Value("${wechat.appId}")
private String wechatAppId;
/**
* API v3密钥
*/
@Value("${wechat.aesKey}")
private String wechatAesKey;
/**
* 统一下单地址
*/
@Value("${wechat.unifiedOrder.url}")
private String unifiedOrderUrl;
/**
* 异步接收微信支付 回调地址
*/
@Value("${wechat.notify.url}")
private String notifyUrl;
/**
* 微信订单查询地址
*/
@Value("${wechat.queryOrder.url}")
private String queryOrderUrl;
/**
* 获取平台证书列表
*/
@Value("${wechat.certificates.url}")
private String certificatesUrl;
@Override
public void run(String... args) throws Exception {
WxpayV3Util.wechatMchId = wechatMchId;
WxpayV3Util.wechatAppId = wechatAppId;
WxpayV3Util.wechatAesKey = wechatAesKey;
WxpayV3Util.unifiedOrderUrl = unifiedOrderUrl;
WxpayV3Util.notifyUrl = notifyUrl;
WxpayV3Util.queryOrderUrl = queryOrderUrl;
X509Certificate certificate = PemUtil.loadCertificate(new FileInputStream(readPayPath("apiclient_cert.pem")));
WxpayV3Util.wechatCertificatePath = certificate;
//获取证书序列号
WxpayV3Util.serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
WxpayV3Util.wechatKeyPath = PemUtil.loadPrivateKey(new FileInputStream(readPayPath("apiclient_key.pem")));
WxpayV3Util.certificatesUrl = certificatesUrl;
//获取平台证书(由于微信证书存在有限期限制,微信支付会不定期更换平台证书以确保交易安全)
WxpayV3Util.certificateMap = WxpayV3Util.refreshCertificate();
}
/**
* 读取文件地址,适用发布环境
* @param fileName (文件路径)
* @return 临时文件路径
*/
public String readPayPath(String fileName) {
String url = null;
//返回读取指定资源的输入流
InputStream is = this.getClass().getResourceAsStream("/wxpay/"+fileName);
//InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("/alipay/"+fileName);
String path = System.getProperty("user.dir");
//create folder
String dirPath = path + File.separator + "uploadWxPayFiles";
File dir = new File(dirPath);
dir.mkdirs();
//create file
String filePath = dirPath + File.separator + fileName;
File file = new File(filePath);
//判断文件是否存在
if (!file.exists()) {
try {
file.createNewFile();
//文件不存,创建流输入数据到新文件
inputStreamToFile(is, file);
} catch (IOException e) {
e.printStackTrace();
}
url = filePath;
}else{
url = filePath;
}
return url;
}
public void inputStreamToFile(InputStream ins, File file) {
OutputStream os = null;
try {
os = new FileOutputStream(file);
int bytesRead = 0;
byte[] buffer = new byte[1024];
while ((bytesRead = ins.read(buffer, 0, 1024)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.close();
ins.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}