PAYATHON 2026

支付宝SDK接口报错40004及升级后JSON异常如何解决?

支付小周

结论

40004 是支付宝接口返回的通用业务失败码。仅凭这个错误码,无法判断是否因为 SDK 版本过低,还需要查看响应中的 sub_code、sub_msg 和 body。

如果升级到 4.35.37.ALL 后支付恢复正常,但系统同时出现 JSON 异常、验证码登录失效,通常是升级改变了项目的传递依赖,造成 Jackson、Fastjson、Fastjson2 或 Spring MVC 的 JSON 转换组件版本冲突。

建议保留可以正常支付的新 SDK,通过依赖树找出冲突,再统一 JSON 组件版本,或定向排除已经确认存在冲突的传递依赖。不要用全局放宽反序列化规则、删除所有 JSON 依赖等方式临时绕过问题,这样可能引入安全风险,也可能影响登录接口。

先确认 40004 的具体原因

支付宝响应一般包含以下信息:

  • code:网关级或业务级结果码。
  • msg:结果描述。
  • sub_code:更具体的业务错误码。
  • sub_msg:具体错误原因。
  • body:支付宝返回的原始响应内容。

调用完成后,应完整记录不涉及敏感信息的响应字段:

AlipayTradePagePayResponse response =
        alipayClient.pageExecute(request);

if (!response.isSuccess()) {
    log.error(
        "Alipay request failed, code={}, msg={}, subCode={}, subMsg={}, body={}",
        response.getCode(),
        response.getMsg(),
        response.getSubCode(),
        response.getSubMsg(),
        response.getBody()
    );
}

日志中不要输出应用私钥、用户密码、验证码、访问令牌或完整身份证号。

如果 sub_msg 明确要求升级 SDK,可以升级处理。如果提示的是签名、参数、权限或订单状态异常,就应按照具体的 sub_code 排查,升级 SDK 未必能解决。

对比升级前后的依赖树

分别在 4.22.32.ALL 和 4.35.37.ALL 下执行:

mvn dependency:tree -Dverbose > dependency-tree.txt

重点检查 JSON 相关依赖:

mvn dependency:tree -Dverbose \
  -Dincludes=com.fasterxml.jackson.core,com.fasterxml.jackson.datatype,com.alibaba.fastjson,com.alibaba.fastjson2,org.json

Gradle 项目可以使用:

./gradlew dependencies --configuration runtimeClasspath

需要检查以下情况:

  • 同一个 JSON 库是否同时出现多个版本。
  • jackson-core、jackson-databind、jackson-annotations 的版本是否一致。
  • 旧版 fastjson 与 fastjson2 是否被当作同一套 API 使用。
  • 某个依赖经过 Maven 冲突仲裁后,运行时版本是否与编译期不同。
  • 项目是否手动复制过 JSON JAR,导致 WEB-INF/lib、应用服务器共享目录或容器镜像中存在重复版本。
  • SDK 升级是否同时改变了 Spring Boot 管理的依赖版本。

常见异常及对应的排查方向如下:

异常表现优先检查
NoSuchMethodError编译期和运行期 JSON 库版本不一致
ClassNotFoundException必需依赖被错误排除或未打入制品
NoClassDefFoundError类初始化失败或运行时缺少依赖
ClassCastException同名类型、不同类加载器或错误的 JSON 实现
HttpMessageNotReadableException请求体格式、字段类型或反序列化配置
InvalidDefinitionExceptionJackson 模块或 Java 时间类型配置
验证码接口返回 400前端提交格式与后端 Content-Type、参数绑定方式不一致

统一 JSON 依赖版本

如果项目使用 Spring Boot,应优先采用 Spring Boot 自带的依赖管理,不要分别为 jackson-core、jackson-databind 和 jackson-annotations 指定互不匹配的版本。

非 Spring Boot 项目可以通过 Jackson BOM 统一版本:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.fasterxml.jackson</groupId>
            <artifactId>jackson-bom</artifactId>
            <version>${jackson.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

${jackson.version} 需要结合现有框架版本、安全要求和实际兼容性测试确定,不能为了消除 Maven 警告而随意选择。

如果依赖树已经证实支付宝 SDK 引入的某个传递依赖覆盖了应用需要的版本,可以定向排除该依赖:

<dependency>
    <groupId>com.alipay.sdk</groupId>
    <artifactId>alipay-sdk-java</artifactId>
    <version>4.35.37.ALL</version>

    <exclusions>
        <exclusion>
            <groupId>实际冲突依赖的 groupId</groupId>
            <artifactId>实际冲突依赖的 artifactId</artifactId>
        </exclusion>
    </exclusions>
</dependency>

排除项不能直接照搬。必须根据 dependency:tree、异常堆栈和 SDK 的 POM,确认支付宝在运行时是否还需要该依赖。错误排除可能导致支付接口在运行时出现 ClassNotFoundException。

检查验证码登录的 JSON 参数绑定

验证码登录失效,问题不一定出在验证码本身。如果升级影响了 HTTP 消息转换器或 JSON 配置,需要确认前后端约定是否仍然一致。

后端接收 JSON 时,可以明确声明请求体:

public class SmsLoginRequest {

    private String mobile;
    private String code;

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}
@PostMapping(
    value = "/login/sms",
    consumes = MediaType.APPLICATION_JSON_VALUE,
    produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<?> login(@RequestBody SmsLoginRequest request) {
    return ResponseEntity.ok(loginService.login(
        request.getMobile(),
        request.getCode()
    ));
}

对应请求应为:

POST /login/sms
Content-Type: application/json
{
  "mobile": "13800000000",
  "code": "123456"
}

如果前端发送的是表单:

Content-Type: application/x-www-form-urlencoded

后端就不应使用 @RequestBody 接收 JSON,而应改用表单参数绑定:

@PostMapping(
    value = "/login/sms",
    consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE
)
public ResponseEntity<?> login(
        @RequestParam String mobile,
        @RequestParam String code) {
    return ResponseEntity.ok(loginService.login(mobile, code));
}

还要检查以下全局配置是否发生了变化:

  • 自定义 ObjectMapper 是否覆盖了 Spring Boot 的默认配置。
  • 是否调用了 WebMvcConfigurer#configureMessageConverters,并清空了默认转换器。
  • 字段命名策略是否从驼峰改成了下划线。
  • 是否开启了严格的未知字段校验。
  • 验证码原本按字符串处理,升级后是否被转换成数字,造成前导零丢失。
  • 日期、枚举、布尔值或泛型返回值的序列化规则是否发生变化。

验证码必须以 String 类型保存和传输,不能使用 Integer:

private String code;

避免覆盖 Spring 默认消息转换器

如果项目需要增加转换器,通常应使用 extendMessageConverters,避免替换所有默认转换器:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(
            List<HttpMessageConverter<?>> converters) {
        // 按需增加或调整,不清空 Spring 已注册的默认转换器
    }
}

下面这种写法需要格外谨慎:

@Override
public void configureMessageConverters(
        List<HttpMessageConverter<?>> converters) {
    converters.clear();
}

清空默认转换器后,登录接口、统一响应对象、字符串响应和文件下载都可能受到影响。

隔离支付宝对象与业务 JSON 转换

支付宝 SDK 的请求和响应应尽量使用 SDK 提供的类型处理。不要先转成通用 Map,再通过项目的全局 JSON 工具反复序列化。

AlipayTradePagePayRequest request =
        new AlipayTradePagePayRequest();

request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
request.setBizContent(bizContent);

AlipayTradePagePayResponse response =
        alipayClient.pageExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        "Alipay failed: code=" + response.getCode()
            + ", subCode=" + response.getSubCode()
            + ", subMsg=" + response.getSubMsg()
    );
}

不要为了适配支付宝返回值而替换整个应用的全局 ObjectMapper。如果确实需要特殊的序列化配置,可以为支付宝相关逻辑创建独立实例:

ObjectMapper alipayObjectMapper = new ObjectMapper();
alipayObjectMapper.findAndRegisterModules();

签名验签仍应使用支付宝 SDK 提供的方法,不能将通知参数重新序列化后再验签。JSON 字段顺序、空值处理或字符转义一旦发生变化,都可能造成验签失败。

验证运行时实际加载的 JAR

依赖树只能反映构建阶段的选择结果。如果本地运行正常,但测试或生产环境出现异常,还需要检查最终制品:

jar tf target/application.jar | grep -E 'jackson|fastjson|alipay'

对于 Spring Boot 可执行 JAR,还可以检查:

jar tf target/application.jar | grep 'BOOT-INF/lib'

必要时,可以临时输出关键类的实际加载位置:

log.info(
    "ObjectMapper loaded from: {}",
    ObjectMapper.class
        .getProtectionDomain()
        .getCodeSource()
        .getLocation()
);

log.info(
    "AlipayClient loaded from: {}",
    AlipayClient.class
        .getProtectionDomain()
        .getCodeSource()
        .getLocation()
);

这样可以发现容器、应用服务器或旧部署目录中残留的 JAR。

推荐的处理顺序

  1. 保存 40004 的完整响应,确认实际的 sub_code 和 sub_msg。
  2. 保留旧版本作为可复现基线,再切换到 4.35.37.ALL。
  3. 对比两个版本的 Maven 或 Gradle 依赖树。
  4. 根据第一条 JSON 异常的完整堆栈定位冲突类,不要先处理后续的连锁报错。
  5. 统一 Jackson 各组件的版本,并区分 Fastjson 与 Fastjson2。
  6. 只排除已经确认存在冲突的传递依赖。
  7. 检查验证码接口的 Content-Type、@RequestBody、字段类型和消息转换器。
  8. 清理后重新构建和部署,避免残留旧 JAR。
  9. 分别回归支付下单、同步返回、异步通知、验签、验证码发送和验证码登录。

验收时,应确认新版支付宝 SDK 可以正常支付、应用中的 JSON 组件版本唯一且兼容,并且验证码接口已单独通过回归测试。支付页面能够打开,只能说明流程走到了这一步,不能证明整个升级已经完成。

备注:内容仅供参考。