技术交流QQ群
729987144
个人博客 http://www.coderyj.com
最近在对接微信支付,遇到的坑和大家分享
微信支付官方github网址 https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
0.私钥以及证书放在项目资源文件夹下
1.首先注册微信商户, 并且开通 JSAPI一定要确认
2.申请证书—> 之后得到文件, 这里会用到 aplicient_cert.pem
3.maven 引入 sdk
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.4.8</version>
</dependency>
4.初始化
// 加载私钥
private PrivateKey getPrivateKey() {
try {
InputStream inputStream = new ClassPathResource(privateKeyPath).getInputStream();
File file = new File("./abc");
FileUtils.copyInputStreamToFile(inputStream, file);
String privateKey = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
return merchantPrivateKey;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private CloseableHttpClient wechatInit(String mchId, String mchSerialNo, String apiV3Key) {
try {
PrivateKey merchantPrivateKey = getPrivateKey();
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(mchId);
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
CloseableHttpClient httpClient = builder.build();
return httpClient;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
5.下单接口
// 下单接口
private Object wxPayOrder(String orderNo, int amount, String desc, String openid, String notify_url, String orderId) {
try {
// 初始化商户
CloseableHttpClient httpClient = wechatInit(mchId, mchSerialNo, apiv3key);
if (httpClient == null) {
return null;
}
// 下单
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type", "application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("mchid", mchId)
.put("appid", appid)
.put("description", desc)
// 回调url
.put("notify_url", notify_url)
.put("out_trade_no", orderNo)
.put("attach",orderId);
rootNode.putObject("amount")
// 金额类型单位分
.put("total", amount)
// 支付金额类型
.put("currency", "CNY");
rootNode.putObject("payer")
.put("openid", openid);
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
String bodyAsString = EntityUtils.toString(response.getEntity());
log.info("支付数据:" + bodyAsString);
System.out.println(bodyAsString);
// 拼装信息 返回给小程序或者native app
JSONObject bodyAsJSON = JSONObject.parseObject(bodyAsString);
//判读下微信返回值是否正确
if (bodyAsJSON.containsKey("code")) {
return AjaxResult.error("错误", bodyAsJSON);
}
final String prepay_id = bodyAsJSON.getString("prepay_id");
final String timeStamp = String.valueOf(System.currentTimeMillis());
final String nonceStr = RandomStringGenerator.getRandomStringByLength(32);
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(appid + "\n");
stringBuffer.append(timeStamp + "\n");
stringBuffer.append(nonceStr + "\n");
stringBuffer.append("prepay_id=" + prepay_id + "\n");
Signature signature = Signature.getInstance("SHA256withRSA");
PrivateKey merchantPrivateKey = getPrivateKey();
signature.initSign(merchantPrivateKey);
signature.update(stringBuffer.toString().getBytes("UTF-8"));
byte[] signBytes = signature.sign();
Base64.Encoder encoder = Base64.getEncoder();
String paySign = encoder.encodeToString(signBytes);
JSONObject params = new JSONObject();
params.put("appId", appid);
params.put("timeStamp", timeStamp);
params.put("nonceStr", nonceStr);
params.put("prepay_id", "prepay_id=" + prepay_id);
params.put("signType", "RSA");
params.put("paySign", paySign);
log.info("拼装后的信息:" + params);
return params ;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
6.回调url,因为现在微信回调数据是加密的所以需要解密
@RequestMapping("callBack")
public Object callBack(HttpServletRequest request) throws Exception {
log.info("接口已被调用");
ServletInputStream inputStream = request.getInputStream();
String notifyJson = StreamUtils.inputStream2String(inputStream, "utf-8");
JSONObject object = (JSONObject) JSONObject.parse(notifyJson);
log.info("支付回调数据notify:" + object);
//获取报文
String body = getRequestBody(request);
//随机串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信传递过来的签名
String signature = request.getHeader("Wechatpay-Signature");
//证书序列号(微信平台)
String serialNo = request.getHeader("Wechatpay-Serial");
//时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//构造签名串
//应答时间戳\n
//应答随机串\n
//应答报文主体\n
String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));
try {
//验证签名是否通过
boolean result = verifiedSign(serialNo, signStr, signature);
if(result){
//解密数据
String plainBody = decryptBody(body);
log.info("解密后的明文:{}",plainBody);
JSONObject parse = (JSONObject) JSONObject.parse(plainBody);
log.info("parse:"+ parse);
String trade_state = parse.get("trade_state").toString();
if (trade_state.equals("SUCCESS")){
// 商户订单号
String out_trade_no = parse.get("out_trade_no").toString();
// 事务id
String transaction_id = parse.get("transaction_id").toString();
// 支付成功时间
String success_time = parse.get("success_time").toString();
// 传入的id
String orderId = parse.get("attach").toString();
log.info("支付成功....");
}
// 响应微信
parse.put("code", "SUCCESS");
parse.put("message", "成功");
return parse;
}
} catch (Exception e) {
log.error("微信支付回调异常:{}", e.getMessage());
e.printStackTrace();
}
return null;
}
- 解密工具类
/**
* 解密body的密文
*
* "resource": {
* "original_type": "transaction",
* "algorithm": "AEAD_AES_256_GCM",
* "ciphertext": "",
* "associated_data": "",
* "nonce": ""
* }
*
* @param body
* @return
*/
private String decryptBody(String body) throws UnsupportedEncodingException, GeneralSecurityException {
AesUtil aesUtil = new AesUtil(apiv3key.getBytes("utf-8"));
JSONObject object = JSONObject.parseObject(body);
JSONObject resource = object.getJSONObject("resource");
String ciphertext = resource.getString("ciphertext");
String associatedData = resource.getString("associated_data");
String nonce = resource.getString("nonce");
return aesUtil.decryptToString(associatedData.getBytes("utf-8"), nonce.getBytes("utf-8"), ciphertext);
}
/**
* 验证签名
*
* @param serialNo 微信平台-证书序列号
* @param signStr 自己组装的签名串
* @param signature 微信返回的签名
* @return
* @throws UnsupportedEncodingException
*/
private boolean verifiedSign(String serialNo, String signStr, String signature){
try {
PrivateKey merchantPrivateKey = getPrivateKey();
// 获取证书管理器实例
CertificatesManager certificatesManager = CertificatesManager.getInstance();
// 向证书管理器增加需要自动更新平台证书的商户信息
certificatesManager.putMerchant(mchId, new WechatPay2Credentials(mchId,
new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiv3key.getBytes(StandardCharsets.UTF_8));
// 从证书管理器中获取verifier
Verifier verifier = certificatesManager.getVerifier(mchId);
return verifier.verify(serialNo, signStr.getBytes("utf-8"), signature);
}catch (Exception e){
e.printStackTrace();
}
return false;
}
/**
* 读取请求数据流
*
* @param request
* @return
*/
private String getRequestBody(HttpServletRequest request) {
StringBuffer sb = new StringBuffer();
try (ServletInputStream inputStream = request.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("读取数据流异常:{}", e);
}
return sb.toString();
}