前言
前段时间弄了开发支付宝当面付的文章,有需要开发支付宝当面付的童鞋可以看一下我开发支付宝的文章,现在打算把开发微信的Native支付也写一下,做个参考。
准备
微信支付的开发一般很麻烦,需要的东西特别多,现在就来说一下开发微信的Native支付都需要些什么。这里要说下,微信支付是分为v3和v2两个版本的,这里选用的v3版本,但是网上关于v3版本的demo很少,v3和v2版本的差别很大,所以路途很艰难,作者是深有体会啊。
- AppID:应用ID
- mchid:商户号
- AppSecret:和AppID绑定的Secret参数(设置时需保存,设置后找不到)
- PrivateKey:私钥(这个私钥是证书的私钥,私钥下载下来后在
apiclient_key.pem
文件中) - mchSerialNo:证书序列号(绑定证书后可以查看证书,证书序列号就在上面)
- APIv3Key:APIv3密钥(自己设置)
以上的东西只需在开发时需要用到的,至于在哪里获取就不说了,获取的地方都不一样。
pom文件
<!-- 导入微信支付 -->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
这里要说明一下,开发微信APIv3版本时,微信会提供一个SDK,需要把这个SDK下载下来,放到项目中去
开发微信Native支付
发送请求
发送请求时需要注意几点
1、微信的金额单位是按分算的,0.01的商品要转换为100发送给微信,做微信的时候要做好金额的转换准备(比支付宝坑的地方这算一个)。
2、要注意传给微信对象的类型。例如:总金额(total)是int类型,作者就是传了一个String类型,这样也可以拿到支付的二维码,但是扫描二维码支付的时候出现了扫码不停地等待最后提示系统繁忙的信息。作者当初就在这里卡了好几天,最后才在网上找了很久资料才找到问题所在,是类型没有传对的原因。
3、微信开发Native官方文档中SDK的代码一定要放到项目中去,因为微信官方提供SDK已经帮我们封装了请求微信时签名的创建和回调时签名验证认证等。
/**微信支付的AppId*/
private static final String AppId = "你的AppID";
/**微信支付的商户号(mchid)*/
private static final String MchId = "你的商户号";
/**微信支付的secret参数*/
private static final String Secret = "你的AppIdSecret";
/**私钥字符串*/
private static final String privateKey = "你的私钥(商户(证书)的私钥)"
/**证书序列号*/
private static final String mchSerialNo = "你的证书序列号";
/**APIv3密钥*/
private static final String apiV3Key = "你的APIv3密钥";
/**
* 微信Native支付
* @param paymentMethod:支付方式(0:购物卡支付 1:支付宝支付 2:微信支付 3:购物卡和支付宝 4:购物卡和微信)
* @param userId:用户id
* @param price:总价
* @param orderNos:订单编号
* @return
*/
@Override
public R wxNativePay(Integer paymentMethod, Integer userId, Integer price, String[] orderNos) throws Exception {
//new一个空串 用来存储订单编号(订单编号可能会有多个,买多个不同商家的商品)
String newOrderNo = createOrderNo().toString();
//将传来的订单编号去重
Set orderSets = new HashSet();
//遍历订单编号
for (String orderNo : orderNos) {
orderSets.add(orderNo);
}
if (orderSets.size() < 1){
return R.error(-1,"没有订单编号");
}
//遍历去重后的订单编号
for (Object orderNo : orderSets) {
//根据订单编号,用户id查询订单
List<TOrder> orderList = orderDao.findByOrderNoAndUserId(orderNo.toString(), userId);
if (orderList.size() > 0){
//循环查出来的订单,将支付总订单编号设置到订单数据中
for (TOrder order : orderList) {
//给该订单设置支付总订单编号
order.setPayTotalOrderNo(newOrderNo);
Integer updateStatus = orderDao.updatePayTotalOrderNo(order);
}
}
}
/**********************上面是业务逻辑,按自己的写即可**********************/
/***************微信开发Native文档中有这些代码,配置用自己的即可 start*************/
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3秘钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(MchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
// 初始化httpClient(获得Http客户端)
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(MchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
/***************微信开发Native文档中有这些代码,配置用自己的即可 end*************/
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
String reqdata = "{"
+ "\"amount\": {"
+ "\"total\":" + "100" + ","
+ "\"currency\":\"CNY\""
+ "},"
+ "\"mchid\":\"" + "你的商户号" + "\","
+ "\"description\":\"商品描述\","
+ "\"notify_url\":\"你的回调地址" + "\","
+ "\"out_trade_no\":\""+ "你的订单编号" + "\","
+ "\"appid\":\""+ "你的AppID" + "\""
+ "}";
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);
//获取微信支付返回的支付状态码
Integer statusCode = response.getStatusLine().getStatusCode();
//获取微信返回回来的codeUrl链接
String codeUrl = EntityUtils.toString(response.getEntity());
//将微信返回的二维码链接进行截取,只获取链接本身,其它字符串不获取
String qrCode = codeUrl.substring(codeUrl.indexOf("w"),codeUrl.indexOf("}")-1);
try {
//处理成功
if (statusCode == 200) {
log.info("statusCode = " + statusCode + ",success,return body = " + codeUrl);
//处理成功,无返回Body
} else if (statusCode == 204) {
log.info("statusCode = " + statusCode + ",请求发送成功,但无返回body");
} else {
log.info("failed,resp code = " + statusCode + ",return body = " + codeUrl);
}
} finally {
//关闭资源
response.close();
httpClient.close();
}
//返回
if (statusCode.equals(200)){
return R.ok(1,"微信Native支付请求成功",qrCode);
}else if (statusCode.equals(204)){
return R.error(-1,"微信Native支付请求成功,但是没有二维码链接",codeUrl);
}else {
return R.error(-1,"微信Native支付请求失败",codeUrl);
}
}
开发微信回调
接收到微信回调后,不管支付是否成功,都要给微信返回信息,因为微信会通过一些策略来定时给用户发送回调(微信回调策略)
/**
* 微信回调
* @param request
* @param response
* @return
* @throws Exception
*/
@PostMapping("/wxCallBack")
public String wxCallBack(HttpServletRequest request, HttpServletResponse response) throws Exception{
//获取微信回调回来的数据流
ServletInputStream inputStream = request.getInputStream();
String notifyXmlInfo = StreamUtils.inputStream2String(inputStream, "utf-8");
//将数据转为map
Map<String, Map<String,String>> strToMap = JSONUtil.jsonStrToMap(notifyXmlInfo);
//获取微信返回的resource信息
String associatedData = strToMap.get("resource").get("associated_data");
String nonce = strToMap.get("resource").get("nonce");
String ciphertext = strToMap.get("resource").get("ciphertext");
//使用微信SDK提供的AesUtil工具类和APIv3密钥进行签名验证
AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
//如果APIv3密钥和微信返回的resource中的信息不一致就会导致签名失败,用户是拿不到微信返回的支付信息的
String decryptToString = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
//验证成功后将获取的支付信息转为map
Map<String, Object> map = JSONUtil.strToMap(decryptToString);
//判断是否支付成功
if ("SUCCESS".equals(map.get("trade_state"))){
//支付成功后进入业务处理逻辑中,这个代码就不贴出来了,每个公司的业务需求都不相同
wxPayService.wxCallBack(map);
//支付成功 给微信发送我已接收通知的响应
//创建给微信响应的对象
Map<String,String> returnMap = new HashMap<>();
returnMap.put("code","SUCCESS");
returnMap.put("message","成功");
//将返回给微信的响应对象转为xml
String returnXml = WXPayUtil.mapToXml(returnMap);
log.info("微信支付成功,并且给微信返回响应数据");
return returnXml;
}
//支付失败 给微信发送我已接收通知的响应
//创建给微信响应的对象
Map<String,String> returnMap = new HashMap<>();
returnMap.put("code","FAIL");
returnMap.put("message","");
//将返回给微信的响应对象转为xml
String returnXml = WXPayUtil.mapToXml(returnMap);
log.info("微信支付失败,给微信返回失败的响应数据");
return returnXml;
}
JSONUtil工具类
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
/**
* @author lpx
* @date 2021/4/15
*/
@Slf4j
public class JSONUtil {
/**
* Json字符串转Map
* @param wxStr:微信返回的数据
* @return
*/
public static Map<String, Map<String,String>> jsonStrToMap(String wxStr) {
Map<String,Map<String,String>> map = (Map) JSON.parse(wxStr);
log.info("Json字符串转Map:" + map);
return map;
}
/**
* str转map
* @param str:字符串
* @return
*/
public static Map<String,Object> strToMap(String str){
Map<String,Object> map = JSON.parseObject(str, HashMap.class);
log.info("微信回调验证签名后支付信息:" + map);
return map;
}
}
总结
作者在开发微信支付时遇到的坎坷颇多,因为网上关于微信支付开发的文章很少,而且作者是使用微信的v3版本开发的,这个就更少了,这些是作者一点一点在bug中探索出来的,如果各位童鞋有更好的方法可以私信下作者,让我们共同学习,共同成长,希望作者分享的微信开发的经验可以帮到各位还在路上的童鞋一些帮助。