PAYATHON 2026

Payment API 返回 AuthenticationFailed 401 错误

支付小周

结论

401 AuthenticationFailed 表示请求在身份验证阶段已被拒绝,通常与 eCheck 参数无关。常见原因包括:

  • OAuth 授权时未申请 Payments API 所需的 scope;
  • Access Token、应用凭据和请求环境不匹配;
  • 刷新后的 Access Token 已失效、保存有误,或属于另一家公司;
  • 获得授权的 QuickBooks 公司未开通或关联可用的 Payments 商户账户。

Access Token 字符串存在,不代表该 Token 有权调用 Payments API。

优先检查 OAuth scope

QuickBooks Online Accounting API 和 Payments API 所需的授权范围不同。生成 Payments 授权链接时,需要包含以下 scope:

com.intuit.quickbooks.payment

如果应用还要访问 Accounting API,可以同时申请:

com.intuit.quickbooks.accounting
com.intuit.quickbooks.payment

使用 Intuit OAuth 客户端生成授权地址时,scope 配置应包含 Payments 权限。具体写法取决于项目使用的 OAuth SDK 版本,基本逻辑如下:

$scopes = [
    'com.intuit.quickbooks.accounting',
    'com.intuit.quickbooks.payment',
];

如果首次授权时没有包含 com.intuit.quickbooks.payment,之后调用 Refresh Token 接口也不会自动获得这项权限。用户必须重新完成 OAuth 授权,应用则需要保存新返回的 Access Token 和 Refresh Token。

检查 sandbox 与 production 是否混用

以下三项必须属于同一环境:

  1. OAuth 应用的 Client ID 和 Client Secret;
  2. 获取 Access Token 时使用的授权环境;
  3. PaymentClient 的 environment。

Sandbox Token 应按以下方式使用:

$client = new PaymentClient([
    'access_token' => $record->accessToken,
    'environment' => 'sandbox',
]);

Production Token 应按以下方式使用:

$client = new PaymentClient([
    'access_token' => $record->accessToken,
    'environment' => 'production',
]);

不能用 sandbox 凭据获取 Token,再向 production 端点发送请求,反过来也不行。同时要确认数据库中的 Token 未被其他环境、测试公司或用户的授权结果覆盖。

确认刷新后的 Token 已保存并用于请求

代码先调用:

$this->refreshToken();

随后再次读取:

$record = QuickbookModel::first();

需要确认 refreshToken() 已完成这些操作:

  • 使用当前有效的 Refresh Token 换取新的 Access Token;
  • 将新的 Access Token 写入数据库;
  • 如果响应中包含新的 Refresh Token,也一并更新;
  • 提交数据库事务;
  • 确保 QuickbookModel::first() 读到的就是刚更新的授权记录。

更稳妥的方式是让刷新方法直接返回新 Token,避免再次读取时取到错误记录:

public function echeck(Request $request)
{
    $token = $this->refreshToken();

    if (empty($token->accessToken)) {
        return response()->json([
            'status' => 401,
            'message' => 'Unable to obtain a valid access token.',
        ], 401);
    }

    $client = new PaymentClient([
        'access_token' => $token->accessToken,
        'environment' => 'sandbox',
    ]);

    $bank = ECheckOperations::buildFrom([
        'bankAccount' => [
            'phone' => $request->phone,
            'routingNumber' => $request->routingNumber,
            'name' => $request->name,
            'accountType' => $request->accountType,
            'accountNumber' => $request->accountNumber,
        ],
        'description' => 'Easyfi Testing',
        'paymentMode' => 'WEB',
        'amount' => '5.55',
    ]);

    $response = $client->debit($bank);

    if ($response->failed()) {
        return response()->json([
            'status' => $response->getStatusCode(),
            'message' => $response->getBody(),
        ], $response->getStatusCode());
    }

    $charge = $response->getBody();

    return response()->json([
        'status' => $charge->status,
        'message' => $charge,
        'data' => $charge->id,
    ]);
}

refreshToken() 的返回类型和字段名,应以项目实际使用的 SDK 版本为准。

确认 Payments 商户资格

OAuth scope 正确后,获得授权的公司还必须具备可用的 QuickBooks Payments 能力。需要逐项确认:

  • 授权连接的是预期的 QuickBooks 公司;
  • 该公司已经开通或关联 Payments 商户账户;
  • 当前商户账户允许使用 eCheck/ACH debit;
  • sandbox 测试公司具有对应的 Payments 测试配置;
  • 应用已经配置并获准调用相应环境的 Payments API。

如果普通 Accounting API 可以正常访问,而 Payments API 始终返回 401,应优先检查 Payments scope 和商户账户关联,无需继续修改 bankAccount 字段。

建议的排查顺序

  1. 检查最初生成的 OAuth 授权链接是否包含 com.intuit.quickbooks.payment。
  2. 如果没有,删除旧授权记录并重新完成 OAuth 授权。
  3. 保存重新授权后返回的 Access Token 和 Refresh Token。
  4. 确认应用凭据、Token 和 environment 均属于 sandbox,或均属于 production。
  5. 确认刷新后使用的是新的 Access Token,而不是数据库中的旧记录。
  6. 确认获得授权的公司已关联可用的 QuickBooks Payments 商户账户。
  7. 记录实际请求端点、HTTP 状态码和响应体,但不要在日志中写入完整 Token、银行账号或路由号码。

身份验证通过后,再排查 routingNumber、accountType、paymentMode 等业务参数。参数格式错误通常会产生与参数校验有关的 4xx 响应,而不是当前的 AuthenticationFailed。

备注:内容仅供参考。