我们看一下官方文档说明
首先先自查三种原因
通常有三种可能:
- 使用了错误的API v3密钥,如使用了其他商户号的密钥,或者使用了APIv2的APIKey。
- 密文不正确。请检查提交解密的密文和收到的密文。注意报文中的密文经过了Base64编码。
- 解密时接口遗漏传入附加数据(associated_data)
如果以上都没有问题的话那么进入下一步操作
由于微信调用回调地址的时候可能并不是一次传输数据,如果使用@requestBody 标签去接收参数的话可能会不全,所以我们要使用 HttpServletRequest 去接收参数流,话不多说上代码
Controller层
@Autowired
private WechatService wechatService;
@PostMapping("/wx/callback")
public String wxCallback(HttpServletRequest request){
return wechatService.callBack(request);
}
业务代码层
/**
* 微信支付回调
*
* @param request 请求
* @return 结果
*/
@Override
public String callBack(HttpServletRequest request) {
// TODO 必须使用request获取body(readLine读取),微信推送的消息使用@RequestBody可能一次性无法读完,造成解密失败
String resCode = "SUCCESS";
String resMessage = "成功";
String streamReadString = getRequestBody(request);
WxPayCallbackModel model = JSONObject.parseObject(streamReadString, WxPayCallbackModel.class);
log.info("pay callback: request read={}, request body read={}", streamReadString, model.toString());
try {
WxPayCallbackResourceModel resource = model.getResource();
String associatedData = resource.getAssociated_data();
String nonce = resource.getNonce();
String ciphertext = resource.getCiphertext();
// 你的Apiv3秘钥转换成Utf8的Byte
byte[] aesKey = WeChatPayCostant.API_V3_SECRET.trim().toLowerCase().getBytes("UTF-8");
// 以下微信传来的附加值(很重要)
byte[] associatedDataBytes = associatedData.getBytes("UTF-8");
byte[] nonceBytes = nonce.getBytes("UTF-8");
byte[] ciphertextBytes = Base64.decodeBase64(ciphertext);
// 开始解密 解密工具类请参照微信官网文档
// https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi
AesUtil aesUtil = new AesUtil(aesKey);
// 这里所需要的的工具类在文末
String decryptedString = WxUtil.decryptToString(associatedDataBytes, nonceBytes, ciphertextBytes);
log.info("微信支付回调 - 解密: {}", decryptedString);
// 解密得到的json结果
JSONObject decryptedJsonObj = JSONObject.parseObject(decryptedString);
} catch (Exception e) {
log.error("支付回调失败: {}", e.getMessage());
e.printStackTrace();
resCode = "FAIL";
resMessage = "支付失败";
}
JSONObject returnJson = new JSONObject();
returnJson.put("code", resCode);
returnJson.put("message", resMessage);
return returnJson.toJSONString();
}
getRequestBody()方法;
/**
* 获取请求体
* @param request 请求
*/
private String getRequestBody(HttpServletRequest request) {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuilder sb = new StringBuilder();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
assert stream != null;
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
assert reader != null;
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
/*
* 微信工具类
*/
public class WxUtil{
private static final int KEY_LENGTH_BYTE = 32;
private static final int TAG_LENGTH_BIT = 128;
/**
* 解密
* @param associatedData 附加数据
* @param nonce 随机串
* @param ciphertext 数据密文
* @param aesKey apiv3秘钥
* @return 结果
* @throws Exception 异常
*/
public static String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext,byte[] aesKey) throws Exception {
try {
if (aesKey.length != KEY_LENGTH_BYTE) {
throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
}
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);
if(associatedData != null && associatedData.length > 0) {
cipher.updateAAD(associatedData);
}else {
cipher.updateAAD("".getBytes("utf-8"));
}
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);
}
}
}
以上就是本文的全部内容,希望对大家的学习有所帮助