微信支付V3对接版
接入前准备工作
微信商户号、微信商户证书序列号、微信商户API秘钥、 微信商户证书私钥、 AppID
准备好以上需要用到的参数开始微信支付对接
接下来需要 导入微信支付的SDK
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.3.0</version> </dependency>
由于 Java 运行时环境的限制。当密钥大于128位的时候会抛出 java.security.InvalidKeyException: Illegal key size 异常
因此需要下载 jce补丁包 我用的是jdk8版本的 其他版本需要在网上自行下载
百度网盘链接:https://pan.baidu.com/s/1ilJu-X131sDO2n3JZdF_vw?pwd=6666 也可以自己去Oracle官网去下载 需要注册Oracle账号
将压缩包解压后会获得 local_policy.jar 和 US_export_policy.jar文件
打开电脑的环境变量查看 ==%JAVA_HOME%==的路径 打开该路径进入 /jre/lib/security 将压缩包解压的文 件进行替换即可
微信支付对接部分代码
//需要导入的依赖
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import
com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVe
rifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.impl.client.CloseableHttpClient;
//加载商家私钥 转换成私钥对象 在微信SDK中 PemUtil.loadPrivateKey(file); 方法有重载 参数可以直接传私钥字符串,也可以传由私钥文件的路径创建的输入流对象 这里采用字符串方式
/**
* 根据商户私钥获取 微信支付的私钥对象
*/
private static PrivateKey getPrivateKey(String fileContext){
if (fileContext==null) {
throw new RuntimeException("请配置商户私钥");
}
//PemUtil.loadPrivateKey(newFileInputStream(工具类名称.class.getResource("/").getPath()+"/apiclient_key.pem"));
return PemUtil.loadPrivateKey(fileContext);
}
//拿到私钥后获取微信签名验证器
/**
* 获取签名验证器
* 我的做法是把配置信息发在数据库,通过查询获取微信商家的配置信息,所以以参数的方式传递
* 也可以通过配置的方式读取 将该方法加入到spring容器中获取 减少该对象的生命周期产生的内存开销
* @param mchId 商户号
* @param apiV3Key V3的API私钥
* @param privateKeyString 商户证书私钥
* @param 商户证书序列号
* @return
*/
public static ScheduledUpdateCertificatesVerifier getVerifier(String mchId,String apiV3Key, String privateKeyString,String mchSerialNo){
//获取商户私钥对象
PrivateKey privateKey = getPrivateKey(privateKeyString);
//私钥签名对象
PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);
//身份认证对象
WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
return new ScheduledUpdateCertificatesVerifier(wechatPay2Credentials,apiV3Key.getBytes(StandardCharsets.UTF_8));
}
//获取了微信签名后需要获得微信支付远程请求对象
/**
* 获取微信支付的远程请求对象
* @param verifier 微信签名验证器
* @param mchId 商户号
* @param privateKeyString 商户证书私钥
* @param mchSerialNo商户序列号
*/
public static CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier,String mchId,String privateKeyString,String mchSerialNo){
//获取商户私钥对象
PrivateKey privateKey = getPrivateKey(privateKeyString);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, privateKey)
.withValidator(new WechatPay2Validator(verifier));
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
CloseableHttpClient httpClient = builder.build();
return httpClient;
}
/**
* 生成签名
*
* @param message 请求体
* @param fileContext 商家证书私钥
* @return 生成base64位签名信息
* @throws Exception
*/
public static String sign(String message, String fileContext) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey(fileContext));
sign.update(message.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 对称解密
* @param bodyMap 微信请求返回的参数转集合
* @param privateKeyString V3私钥 32位
* @return
*/
private String decryptFromResource(Map<String, Object> bodyMap,String privateKeyString) throws GeneralSecurityException {
//通知数据
Map<String, String> resourceMap = (Map) bodyMap.get("resource");
//数据密文
String ciphertext = resourceMap.get("ciphertext");
//随机串
String nonce = resourceMap.get("nonce");
//附加数据
String associatedData = resourceMap.get("associated_data");
AesUtil aesUtil = new AesUtil(privateKeyString.getBytes(StandardCharsets.UTF_8));
String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8),
ciphertext);
return plainText;
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
/**
* 获取随机的订单号 也可使用雪花算法生成随机数
*/
public static String number(int length) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < length; i++) {
if (i == 0)
str.append(getRandom(49, 57));
else
str.append(getRandom(48, 57));
}
return str.toString();
}
/**
* 获取精确到秒的时间戳
* @return
*/
public static String getSecondTimestamp(Date date){
if (null == date) {
return "0";
}
String timestamp = String.valueOf(date.getTime());
int length = timestamp.length();
if (length > 3) {
return timestamp.substring(0,length-3);
} else {
return "0";
}
}
//有了以上的远程请求对象就可以进行微信支付接口的对接了
//下面以V3 App下单 为例进行微信支付对接
// 这里作为测试返回string,并且直接填充参数 实际开发按照需修改即可
@SneakyThrows
public static String order(){
//创建 ObjectMapper 对象
ObjectMapper objectMapper = new ObjectMapper();
//创建 ObjectNode 对象 使用节点的方式添加数据为 集合对象 时更简单
ObjectNode param = objectMapper.createObjectNode();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
param.put("appid", "APPID");
param.put("mchid", "商户号");
param.put("description", "商品说明");
param.put("out_trade_no", number(15));
param.put("notify_url", "微信回调地址");
param.putObject("amount")
.put("total", 1) //商品金额 单位为分
.put("currency", "CNY");//支付货币类型
objectMapper.writeValue(bos, param);
// 创建 POST请求对象 传入微信下单的路径 GET请求创建HttpGet
HttpPost httpPost = new HttpPost(WeChatPayConstants.V3_ORDERQUERY_URL);
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
//创建 请求体 该请求头主要用于传递body的参数 并定义字符编码
StringEntity entity = new StringEntity(bos.toString("UTF-8"),"utf-8");
//设置请求报文格式
entity.setContentType("application/json");
//将请求报文放入请求对象
httpPost.setEntity(entity);
//设置响应报文格式
httpPost.setHeader("Accept", "application/json");
//获取微信的签名对象
final ScheduledUpdateCertificatesVerifier verifier = WeChatPayUtil.getVerifier(MERCHANT_ID,MERCHANT_API_KEY,MERCHANT_PRIVATE_KEY,CERT_CONTENT,MERCHANT_NO);
//获取微信的远程请求对象
final CloseableHttpClient wxPayClient =
WeChatPayUtil.getWxPayClient(verifier,MERCHANT_ID,MERCHANT_PRIVATE_KEY,CERT_CONTENT,MERCHANT_NO);
//将POST请求对象传入 完成签名并执行请求,并完成验签
CloseableHttpResponse response = wxPayClient.execute(httpPost);
try {
//获取请求中微信返回的响应状态码
int statusCode = response.getStatusLine().getStatusCode();
//获取请求中微信返回的响应体
String bodyAsString = EntityUtils.toString(response.getEntity());
String result = null;
if (statusCode == 200) { //处理成功
//参数对照微信支付文档的调起支付接口
final String timestamp = DateFormatUtil.getSecondTimestamp(new Date());
//使用 ObjectMapper对象将请求转换为map
Map<String, String> resultMap = objectMapper.readValue(bodyAsString, Map.class);
//再加上返回给前端的参数
resultMap.put("appid", APPID);
resultMap.put("package", "Sign=WXPay");
//随机字符串
resultMap.put("noncestr",generateNonceStr());
//获取时间串
resultMap.put("timestamp",timestamp);
//将调起支付需要的参数转成JSON对象 和 商家证书私钥 传入进行 V3的验签 V3改为对称加密跟V2的非对称加密方式有差别
String message =
APPID+ "\n" + timestamp + "\n" + resultMap.get("noncestr") + "\n" + resultMap.get("prepay_id") + "\n";
final String sign = WeChatPayUtil.sign(message,vehcPayConfig.getMchPrivateKey());
resultMap.put("sign",sign);
result = resultMap.toString();
} else {
result = "请求失败,响应码:" + statusCode + ",响应体:" + bodyAsString;
}
//返回给前端调起支付
return result;
}catch (Exception e){
e.printStackTrace();
return "请求失败";
}finally{
response.close();
}
}