微信的文档写的是真的烂,特别是刚写完支付宝和苹果支付,有了对比就显得微信的文档是真的垃圾,想体验的可以去看下:
微信官方文档
为了下次做微信支付的时候能舒服点,记录下
主要坑点
- 统一下单接口调用的时候记得将编码格式转为iso8859-1,否则中文会乱码,微信官方文档里面的demo没有转换也没有提到这点
- 统一下单后还需要对参数进行加密签名,v3理论上应该要有支持加密的api但是我在文档里面没有找到,自己网上找代码实现的
- v3版本的回调返回不是xml格式的了,而是终于改成了json格式的数据放回
- 回调的数据不是直接给你的,需要对数据进行解密
主要代码
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.2</version>
</dependency>
import org.apache.http.HttpRequest;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.logging.Logger;
@RestController
@RequestMapping("")
public class PayController {
@Resource
private WxpayUtil wxpayUtil;
@PostMapping("/wxpay/getOrder")
public DataResult wxpayGetOrder(Integer orderType,Integer orderId,Integer orderNum) throws Exception {
return wxpayUtil.getOrder(orderType,orderId,orderNum);
}
@PostMapping("/wxpay/callback")
public String wxPayCallback(HttpServletRequest request, HttpServletResponse response) throws Exception {
String resXml = "";
try {
InputStream inputStream = request.getInputStream();
//将InputStream转换成xmlString
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
resXml = sb.toString();
String result = wxpayUtil.callback(resXml,response);
return result;
} catch (Exception e) {
System.out.println("微信手机支付失败:" + e.getMessage());
e.printStackTrace();
String result = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
return result;
}
}
}
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class WxAPIV3AesUtil {
static final int KEY_LENGTH_BYTE = 32;
static final int TAG_LENGTH_BIT = 128;
private final byte[] aesKey;
public WxAPIV3AesUtil(byte[] key) {
if (key.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
}
this.aesKey = key;
}
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
}
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
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 com.youyi.heidong.util.ResultUtil;
import com.youyi.heidong.util.StringUtils;
import com.youyi.heidong.web.mapper.*;
import com.youyi.heidong.web.pojo.response.DataResult;
import com.youyi.heidong.web.service.MoneyDictionariesService;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.security.*;
import java.util.*;
@Service
public class WxpayUtil {
String privateKey = "微信私钥";
String mchId = "商户号";
//
String mchSerialNo = "商户证书序列号";
// V3密钥
String apiV3Key = "apiv3密钥";
String appid = "appid";
// 回调地址
String NOTIFY_URL = "回调地址";
String app_secret = "appSecret";
/**
* 获取订单号
*/
public DataResult getOrder(Integer orderType, Integer orderId, Integer orderNum) throws Exception {
// 加载商户私钥(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
HttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
// 请求body参数
String reqdata = "{"
+ "\"amount\": {"
+ "\"total\":1,"
+ "\"currency\":\"CNY\""
+ "},"
+ "\"mchid\":\""+mchId+"\","
+ "\"description\":\"7878787878会员充值\","
+ "\"notify_url\":\""+NOTIFY_URL+"\","
+ "\"out_trade_no\":\""+new Date().getTime()+"\","
+ "\"appid\":\""+appid+"\","
+ "\"attach\":\"自定义数据说明\""
+ "}";
StringEntity entity = new StringEntity(new String(reqdata.getBytes("utf-8"), "iso8859-1"));
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpPost);
try {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) { //处理成功
Map<String, String> param = new LinkedHashMap<>();
param.put("appid", appid);
param.put("partnerid",mchId);
String prepay_id = EntityUtils.toString(response.getEntity());
ObjectMapper objectMapper = new ObjectMapper();
param.put("prepayid",objectMapper.readValue(prepay_id,Map.class).get("prepay_id").toString());
param.put("package","Sign=WXPay");
param.put("noncestr", StringUtils.randomString(32));
param.put("timestamp",System.currentTimeMillis()/1000+"");
param.put("sign",createSign(param));
return ResultUtil.success(param);
//return ResultUtil.success(EntityUtils.toString(response.getEntity()));
} else if (statusCode == 204) { //处理成功,无返回Body
System.out.println("success");
return ResultUtil.success();
} else {
String flag = EntityUtils.toString(response.getEntity());
System.out.println("failed,resp code = " + statusCode+ ",return body = " + flag);
return ResultUtil.fail("failed,resp code = " + statusCode+ ",return body = " + flag);
//throw new IOException("request failed");
}
} finally {
response.close();
}
}
/**
* 微信支付签名算法sign
* @param parameters
* @return
*/
public String createSign(Map<String,String> parameters){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + apiV3Key);
System.out.println("签名字符串:"+sb.toString());
// System.out.println("签名MD5未变大写:" + MD5Util.MD5Encode(sb.toString(), characterEncoding));
String sign = md5Password(sb.toString()).toUpperCase();
return sign;
}
/**
* 生成32位md5码
*
* @param key
* @return
*/
public static String md5Password(String key) {
char hexDigits[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
try {
byte[] btInput = key.getBytes();
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(btInput);
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
return new String(str);
} catch (Exception e) {
return null;
}
}
public String callback(String param, HttpServletResponse response) throws IOException, GeneralSecurityException {
System.out.println(param);
// 新版本微信回调进行了修改使用json格式回调数据
Map<String, Object> paramMap = new HashMap<String, Object>();
ObjectMapper mapper = new ObjectMapper();
paramMap = mapper.readValue(param,Map.class);
// 判断是否为支付成功回调
if("TRANSACTION.SUCCESS".equals(paramMap.get("event_type"))){
Map<String,String> resourceMap = (Map)paramMap.get("resource");
// 使用商户私钥解密
byte[] key = apiV3Key.getBytes("UTF-8");
WxAPIV3AesUtil aesUtil = new WxAPIV3AesUtil(key);
String decryptToString = aesUtil.decryptToString(resourceMap.get("associated_data").getBytes("UTF-8"),
resourceMap.get("nonce").getBytes("UTF-8"),
resourceMap.get("ciphertext"));
System.out.println(decryptToString);
// 获取回调数据进行校验比对
Map map = mapper.readValue(decryptToString, Map.class);
if(mchId.equals(map.get("mchid"))&&appid.equals(map.get("appid"))&&"SUCCESS".equals(map.get("trade_state"))){
// todo 基础信息校验通过开始处理业务逻辑
String out_trade_no = map.get("out_trade_no").toString();
}
}
// 回应微信调用
return "{ \"code\": \"SUCCESS\", \"message\": \"成功\"\n }";
}
}