调用 Payment API 时出现 BadMethodCallException
结论
$this->checkDsa(...) 调用的是当前控制器 DsaAPIController 的实例方法。控制器没有定义 checkDsa(),也没有通过 Trait 引入这个方法,因此 Laravel 抛出以下异常:
BadMethodCallException: Method App\Http\Controllers\API\DsaAPIController::checkDsa does not exist.
同名的 Artisan Command 和 middleware 都是独立类,不会自动变成控制器的方法。该怎么处理,要看这段逻辑的用途:
- 如果只需判断用户是否已有有效 DSA,可以直接查询模型,也可以把逻辑放进 Service 或模型方法。
- 如果要在进入接口前统一检查,应注册并挂载 middleware。
- 如果确实需要执行 Artisan Command,可以使用
Artisan::call(),但 Command 不适合用作控制器里的布尔判断方法。
推荐做法:封装查询逻辑
如果 paymentStatus() 需要检查当前用户是否已有有效 DSA,可以先在控制器中定义一个方法:
use App\Models\Dsa;
use Illuminate\Support\Facades\Auth;
private function checkDsa(int $userId): bool
{
return Dsa::where('user_id', $userId)
->where('status', config('constants.subscriptions.status.1'))
->exists();
}
然后在 paymentStatus() 中调用:
public function paymentStatus(
PaymentStatusRequest $request,
Dsa $dsa_payment
) {
$userId = Auth::guard('api')->id();
if ($this->checkDsa($userId)) {
return $this->getMessage(
[],
config('constants.messages.errors.already_dsa'),
config('constants.validation_codes.unprocessable_entity'),
false
);
}
if (
$dsa_payment->status == config('constants.dsa_payments.payment_status.1')
|| $dsa_payment->user_id != $userId
) {
return $this->getMessage(
[],
config('constants.messages.errors.processing_old_request'),
config('constants.validation_codes.unprocessable_entity'),
false
);
}
// 后续支付处理逻辑……
}
原来的调用方式:
$this->checkDsa(DSA::class, 'user_id', Auth::guard('api')->id())
只有在控制器或其父类中已经定义了一个接收这三个参数的 checkDsa() 方法时才有效。Laravel 不会根据类名、字段名和字段值自动创建这个方法。
如果多个控制器都需要这段逻辑,放到 Service 中更合适:
namespace App\Services;
use App\Models\Dsa;
class DsaService
{
public function isActiveForUser(int $userId): bool
{
return Dsa::where('user_id', $userId)
->where('status', config('constants.subscriptions.status.1'))
->exists();
}
}
控制器可以通过依赖注入使用该 Service:
use App\Services\DsaService;
public function paymentStatus(
PaymentStatusRequest $request,
Dsa $dsa_payment,
DsaService $dsaService
) {
$userId = Auth::guard('api')->id();
if ($dsaService->isActiveForUser($userId)) {
return $this->getMessage(
[],
config('constants.messages.errors.already_dsa'),
config('constants.validation_codes.unprocessable_entity'),
false
);
}
// 后续逻辑……
}
如果要使用 middleware
Middleware 的 handle() 方法由 Laravel 的 HTTP 管道调用,不能通过 $this->checkDsa() 来执行。
先修正 middleware 中变量名不一致的问题。当前代码定义了 $is_subscribe,返回时却使用未定义的 $is_dsa。可以统一改成 $isDsa:
class CheckDsa
{
use MessagesTrait;
public function handle(Request $request, Closure $next)
{
$user = Auth::guard('api')->user();
$isDsa = $user
&& $user->dsa_payment()
->where(
'status',
config('constants.dsa_payment.status.1')
)
->exists();
return $isDsa
? $next($request)
: $this->getMessage(
[],
config('constants.messages.errors.not_dsa'),
config('constants.validation_codes.forbidden'),
false
);
}
}
接下来为 middleware 配置别名。Laravel 版本不同,注册位置也可能不同。
较早的版本通常在 app/Http/Kernel.php 中注册:
protected $routeMiddleware = [
'check.dsa' => \App\Http\Middleware\CheckDsa::class,
];
部分后续版本使用 $middlewareAliases:
protected $middlewareAliases = [
'check.dsa' => \App\Http\Middleware\CheckDsa::class,
];
注册后将其挂载到路由:
Route::post('/payment/status', [DsaAPIController::class, 'paymentStatus'])
->middleware(['auth:api', 'check.dsa']);
也可以直接传入 middleware 类名:
Route::post('/payment/status', [DsaAPIController::class, 'paymentStatus'])
->middleware([
'auth:api',
\App\Http\Middleware\CheckDsa::class,
]);
这里还要注意业务条件的方向。你给出的 middleware 只允许已经具备有效 DSA 的用户继续访问,而控制器的逻辑是在用户已有 DSA 时返回 already_dsa。两者的条件正好相反,不能同时照搬。需要先确认这个支付接口究竟允许未订阅用户访问,还是允许已订阅用户访问。
Artisan Command 不能当成控制器方法
在 Console\Kernel 中注册:
protected $commands = [
Commands\CheckDsa::class,
];
这只表示该命令可以从命令行运行:
php artisan check:is_dsa
它不会向 DsaAPIController 添加 checkDsa() 方法。
应用代码在技术上可以调用这个命令:
use Illuminate\Support\Facades\Artisan;
$exitCode = Artisan::call('check:is_dsa');
不过,Artisan::call() 返回的是命令退出码,不是表示“用户是否已有 DSA”的布尔值。Command 更适合定时扫描、批量处理或后台维护任务,不适合承担 HTTP 请求中的业务判断。
如果 Command 和控制器需要使用相同的检查逻辑,应让它们共同调用 DsaService,而不是由控制器调用 Command:
public function handle(DsaService $dsaService): int
{
// 调用 service 执行批量检查或处理
return self::SUCCESS;
}
代码中还需要检查的问题
原代码还有几个与 checkDsa 异常无关,但可能引发其他错误的问题:
-
当
checkDsa()返回false时,dd('poojan')会立即终止请求。调试完成后应将其删除。 -
middleware 中的
$is_subscribe与$is_dsa名称不一致,会导致未定义变量错误。 -
代码中出现了三个不同的配置键:
constants.dsa_payment.status.1
constants.dsa_payments.payment_status.1
constants.subscriptions.status.1
需要确认这些键都已定义,并且各自对应正确的状态含义。
- 代码使用了
$dsa_plan:
$plan = Plan::find($dsa_plan->plan_id);
但给出的 paymentStatus() 片段并未定义 $dsa_plan,执行到这里时可能出现未定义变量错误。
- PHP 类名通常不区分大小写,但最好始终使用模型实际声明的名称:
Dsa::class
不要混用 DSA::class 和 Dsa::class。
较合适的代码结构是:Service 保存可复用的 DSA 判断逻辑,middleware 负责请求访问控制,Command 处理命令行或计划任务,控制器负责组织支付接口的处理流程。
备注:内容仅供参考。
