微信支付文档很多坑,简单记录一下,防止再踩!
之前没排版,看起来很难受,现在舒服多了!
准备工作
商户证书
微信支付证书
通知地址notifyUrl(必须外网可访问!)
1、组装必要参数,调用下单接口
// 自动更新微信支付证书
private static AutoUpdateCertificatesVerifier verifier;
// 商户私钥(privateKey:一串特别长的base64串)
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 使用自动更新的签名验证器,不需要传入证书
verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchid, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes("utf-8"));
// 构建httpClient
httpClient = WechatPayHttpClientBuilder.create().withMerchant(mchid, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
// v3下单接口
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
// 设置订单失效时间4分钟(微信要求日期格式)
long expireTime = System.currentTimeMillis()+4*60*1000;
DateFormat sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Date date = new Date(expireTime);
String format = sf.format(date);
// 组装订单json串
String data = "{\r\n" +
" \"time_expire\": \""+ format +"\",\r\n" +
" \"amount\": {\r\n" +
" \"total\": "+ amount +",\r\n" +
" \"currency\": \"CNY\"\r\n" +
" },\r\n" +
" \"mchid\": \""+ mchid +"\",\r\n" +
" \"description\": \""+ orgiValue +",数量:" + numI + "客服微信:qzy345371236"+"\",\r\n" +
" \"notify_url\": \""+ WeChatPayConstants.notifyUrl +"\",\r\n" +
" \"payer\": {\r\n" +
" \"openid\": \""+ openid +"\"\r\n" +
" },\r\n" +
" \"out_trade_no\": \""+ outTradeNo +"\",\r\n" +
" \"appid\": \""+ appid +"\",\r\n" +
" \"attach\": \""+ orgi +"\",\r\n" +
" \"scene_info\": {\r\n" +
" \"payer_client_ip\": \""+ createIp +"\"\r\n" +
" }\r\n" +
"}";
// 订单json串
StringEntity reqEntity = new StringEntity(data, ContentType.create("application/json", "utf-8"));
httpPost.setEntity(reqEntity);
httpPost.addHeader("Accept", "application/json");
// 微信支付证书序列号
httpPost.addHeader("Wechatpay-Serial", wechatPaySerial);
// 执行下单
CloseableHttpResponse response1 = httpClient.execute(httpPost);
try {
HttpEntity entity = response1.getEntity();
String s = EntityUtils.toString(entity);
JSONObject parseObject = JSONObject.parseObject(s);
String prepayId = parseObject.getString("prepay_id");
EntityUtils.consume(entity);
long now = System.currentTimeMillis()/1000;
String nonceStr = SignUtil.generateNonceStr();
prepayId = "prepay_id="+prepayId;
String token = SignUtil.getToken(appid, prepayId, now, nonceStr);
// 组装唤起微信支付
mmap.put("appId", appid);
mmap.put("timeStamp", now);
mmap.put("nonceStr", nonceStr);
mmap.put("packageStr", prepayId);
mmap.put("paySign", token);
mmap.put("outTradeNo", outTradeNo);
if (!StringUtils.isBlank(returnUrl)) {
mmap.put("returnUrl", returnUrl);// 付款成功后返回的地址
}
}
finally {
response1.close();
}
return "pay";// 唤起微信支付的页面路径
2、唤起支付页面直接用微信给的demo,改改路径参数直接用就行!
3、支付回调
boolean successFlag = true;
HttpServletRequest request;
// 获取回调报文
request.setCharacterEncoding("UTF-8");
int size = request.getContentLength();
InputStream is = request.getInputStream();
byte[] reqBodyBytes = readBytes(is, size);
String resBody = new String(reqBodyBytes);
//回调响应头必要数据,没有这些参数说明回调不对
String headerTimestamp = request.getHeader("Wechatpay-Timestamp");
String headerNonce = request.getHeader("Wechatpay-Nonce");
String headerSerial = request.getHeader("Wechatpay-Serial");
String headerSignature = request.getHeader("Wechatpay-Signature");
if(headerTimestamp==null || headerNonce==null || headerSerial==null || headerSignature==null) {
logger.error("回调响应头参数有误!");
failReason = "回调响应头参数有误!";
successFlag = false;
}else {
// 组装验证回调微信支付签名必要参数
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchid, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),
apiV3Key.getBytes("utf-8"));
String message = headerTimestamp + "\n" + headerNonce + "\n" + resBody + "\n";
// 执行验证回调微信支付签名
boolean verify = verifier.verify(headerSerial, message.getBytes(StandardCharsets.UTF_8), headerSignature);
if(!verify) {
logger.error("回调验证签名失败!");
failReason = "回调验证签名失败!";
successFlag = false;
}
//验证头部携带的序列号是否和付款时候一致,不一致说明有可能被串改,不执行后续操作
if(!wechatPaySerial.equals(headerSerial)) {
logger.error("回调的微信支付证书序列号与请求的不一致!");
failReason = "回调的微信支付证书序列号与请求的不一致!";
successFlag = false;
}
}
if(successFlag) {
//解析回调主体
JSONObject po = JSON.parseObject(resBody);
JSONObject jo = po.getJSONObject("resource");
String ciphertext = jo.getString("ciphertext");
String nonce = jo.getString("nonce");
String associatedData = jo.getString("associated_data");
//解密密文
String jsonData = "";
AesUtil decryptor = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
if(associatedData != null) {
jsonData = decryptor.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
}else {
jsonData = decryptor.decryptToString(null, nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
}
//解密完之后的资源数据
HashMap<String, Object> map = JSON.parseObject(jsonData, HashMap.class);
//商户订单号
String outTradeNo = (String) map.get("out_trade_no");
String tradeState = (String) map.get("trade_state");
if ("SUCCESS".equals(tradeState)) {
logger.info("success,测试回调验签成功");
// 1.查询订单,修改订单状态
}
}
// 返回响应
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getOutputStream().write(响应code.getBytes());
response.flushBuffer();
//回调结束
// readBytes工具
public static final byte[] readBytes(InputStream is, int contentLen) {
if (contentLen > 0) {
int readLen = 0;
int readLengthThisTime = 0;
byte[] message = new byte[contentLen];
try {
while (readLen != contentLen) {
readLengthThisTime = is.read(message, readLen, contentLen - readLen);
if (readLengthThisTime == -1) {// Should not happen.
break;
}
readLen += readLengthThisTime;
}
return message;
} catch (IOException e) {
}
}
return new byte[] {};
}