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 是否混用
以下三项必须属于同一环境:
- OAuth 应用的 Client ID 和 Client Secret;
- 获取 Access Token 时使用的授权环境;
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 字段。
建议的排查顺序
- 检查最初生成的 OAuth 授权链接是否包含
com.intuit.quickbooks.payment。 - 如果没有,删除旧授权记录并重新完成 OAuth 授权。
- 保存重新授权后返回的 Access Token 和 Refresh Token。
- 确认应用凭据、Token 和
environment均属于 sandbox,或均属于 production。 - 确认刷新后使用的是新的 Access Token,而不是数据库中的旧记录。
- 确认获得授权的公司已关联可用的 QuickBooks Payments 商户账户。
- 记录实际请求端点、HTTP 状态码和响应体,但不要在日志中写入完整 Token、银行账号或路由号码。
身份验证通过后,再排查 routingNumber、accountType、paymentMode 等业务参数。参数格式错误通常会产生与参数校验有关的 4xx 响应,而不是当前的 AuthenticationFailed。
备注:内容仅供参考。
