微信jsapi支付结果回调错误Tag mismatch

1 篇文章 0 订阅


现象

使用Java解密时,抛出异常AEADBadTagException: Tag mismatch!

错误信息

Tag mismatch

微信官方说明

在这里插入图片描述

官网说明

解决过程

疯狂之路

  1. 微信社区,参考了多种方案 微信社区-问题反馈参考
  2. 度娘搜索,getBytes时传入编码“UTF-8”
  3. 检查 apiv3秘钥在平台设置的和程序中使用的是否一致
  4. 检查是否Base64解码,其实使用微信官方的程序即可,官方程序-java版-解密

走上正轨

  1. 微信客服沟通(重要解决手段,也是最终查出问题所在的关键)
  2. 客服说回调数据中,普通支付,associated_data参数一定会传过来,但是在官网对于v3支付结果回调说明中,提示associated_data不是必传的,而且我也没有找到相关说明在什么情况下传,什么情况不传,有找到的伙伴可以留言做个说明
  3. 第6条中的问题,让我相信微信发送和我接收的数据没有问题😢 ,其实,最主要的就是缺少了associated_data数据,造成解密时出的问题

发现问题

  1. 接口使用的@RequestBody接收微信发送过来的数据,但是,但是,但是,接收的数据丢失了一部分,😱😱😱,这个不清楚是springboot框架的问题,还是微信推送消息机制的问题,最终使用HttpServletRequest读取stream的方式解决

代码-测试正常解密

controller

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * @author panda
 * @date 2021/4/27 14:06
 * <p>
 * description 回调接口
 */
@RestController
@RequestMapping("/pay")
public class PortalWechatController {

    @Autowired
    private PortalWechatService portalWechatService;

    @PostMapping("/wx/callback")
    public String wxCallback(HttpServletRequest request){
        return portalWechatService.callBack(request);
    }
}

service

import javax.servlet.http.HttpServletRequest;

/**
 * @author panda
 * @date 2021/4/27 14:10
 * <p>
 * description service
 */
public interface PortalWechatService {

    /**
     * 微信支付回调
     * @param request 请求
     * @return 结果
     */
    String callBack(HttpServletRequest request);
}

serviceImpl

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

/**
 * @author panda
 * @date 2021/4/27 14:11
 * <p>
 * description serviceImpl
 */
@Slf4j
@Service
public class PortalWechatServiceimpl implements PortalWechatService {

    /**
     * 微信支付回调
     *
     * @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={}", streamReadString);
        try {
            WxPayCallbackResourceModel resource = model.getResource();
            String associatedData = resource.getAssociated_data();
            String nonce = resource.getNonce();
            String ciphertext = resource.getCiphertext();

            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);

            // 开始解密
            AesUtil aesUtil = new AesUtil(aesKey);
            String decryptedString = aesUtil.decryptToString(associatedDataBytes, nonceBytes, ciphertextBytes);
            log.info("微信支付回调 - 解密: {}", decryptedString);
            // 解密得到的json结果
            JSONObject decryptedJsonObj = JSONObject.parseObject(decryptedString);
            String code = decryptedJsonObj.getString("out_trade_no");

            // 1. 数据库通过订单号查询到的的订单
            // 2. 修改订单中支付状态,微信通知状态,支付相关其它信息
			
        } 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();
    }
    
    // *****************************************************业务支撑*****************************************************

    /**
     * 获取请求体
     * @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();
    }
}

相关工具和实体

import lombok.Data;

/**
 * @author panda
 * @date 2021/5/8 14:12
 * <p>
 * description 微信支付回调 model
 */
@Data
public class WxPayCallbackModel {

    /**
     * 通知的唯一ID
     * 示例值:EV-2018022511223320873
     */
    private String id = "";
    /**
     * 通知创建的时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示北京时间2015年05月20日13点29分35秒。
     * 示例值:2015-05-20T13:29:35+08:00
     */
    private String create_time = "";
    /**
     * 通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
     * 示例值:TRANSACTION.SUCCESS
     */
    private String event_type = "";
    /**
     * 通知的资源数据类型,支付成功通知为encrypt-resource
     * 示例值:encrypt-resource
     */
    private String resource_type = "";
    /**
     * 回调摘要
     * 示例值:支付成功
     */
    private String summary = "";

    /**
     * 通知资源数据
     * json格式,见示例
     */
    private WxPayCallbackResourceModel resource;

    @Override
    public String toString() {
        return "WxPayCallbackModel{" +
                "id='" + id + '\'' +
                ", create_time='" + create_time + '\'' +
                ", event_type='" + event_type + '\'' +
                ", resource_type='" + resource_type + '\'' +
                ", summary='" + summary + '\'' +
                ", resource=" + resource.toString() +
                '}';
    }
}



import lombok.Data;

/**
 * @author panda
 * @date 2021/5/8 14:12
 * <p>
 * description 微信支付回调通知数据 model
 */
@Data
public class WxPayCallbackResourceModel {

    /**
     * 加密算法类型
     * 对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM
     * 示例值:AEAD_AES_256_GCM
     */
    private String algorithm = "";
    /**
     * 数据密文
     * Base64编码后的开启/停用结果数据密文
     * 示例值:sadsadsadsad
     */
    private String ciphertext = "";
    /**
     * 附加数据
     * 附加数据
     * 示例值:fdasfwqewlkja484w
     */
    private String associated_data = "";
    /**
     * 原始类型
     * 原始回调类型,为transaction
     * 示例值:transaction
     */
    private String original_type = "";
    /**
     * 随机串
     * 加密使用的随机串
     * 示例值:fdasflkja484w
     */
    private String nonce = "";

    @Override
    public String toString() {
        return "WxPayCallbackResourceModel{" +
                "algorithm='" + algorithm + '\'' +
                ", ciphertext='" + ciphertext + '\'' +
                ", associated_data='" + associated_data + '\'' +
                ", original_type='" + original_type + '\'' +
                ", nonce='" + nonce + '\'' +
                '}';
    }
}




/**
 * @author panda
 * @date 2021/4/27 11:21
 * <p>
 * description 微信支付常量
 */
public interface WeChatPayCostant {

    /**
     * 应用id
     */
    String APP_ID = "";

    /**
     * 直连商户号
     */
    String MCH_ID = "";

    /**
     * 通知地址
     */
    String NOTIFY_URL = "https://a.b.c.com/xxx";

    /**
     * 货币类型
     */
    String CURRENCY = "CNY";

    /**
     * 微信支付url
     */
    String PAY_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";

    /**
     * APIv3密钥 32位(数字 + 字母_小写),可以直接使用Hutool的IdUtil.simpleUuid()生成
     */
    String API_V3_SECRET = "";

    /**
     * 商户API证书序列号
     */
    String SERIAL_NO = "";

}






import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class AesUtil {

    static final int KEY_LENGTH_BYTE = 32;
    static final int TAG_LENGTH_BIT = 128;
    private final byte[] aesKey;

    public AesUtil(byte[] key) {
        if (key.length != 32) {
            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
        } else {
            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(this.aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
            cipher.init(2, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "UTF-8");
        } catch (NoSuchPaddingException | NoSuchAlgorithmException var7) {
            throw new IllegalStateException(var7);
        } catch (InvalidAlgorithmParameterException | InvalidKeyException var8) {
            throw new IllegalArgumentException(var8);
        }
    }

    public String decryptToString(byte[] associatedData, byte[] nonce, byte[] ciphertext) throws GeneralSecurityException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(this.aesKey, "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
            cipher.init(2, key, spec);
            cipher.updateAAD(associatedData);
            return new String(cipher.doFinal(ciphertext), "UTF-8");
        } catch (NoSuchPaddingException | NoSuchAlgorithmException var7) {
            throw new IllegalStateException(var7);
        } catch (InvalidAlgorithmParameterException | InvalidKeyException var8) {
            throw new IllegalArgumentException(var8);
        }
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值