PHP Easy版支付验签报错:Call to a member function common() on null
结论
报错出现在调用 common() 之前。错误信息是:
Call to a member function common() on null
在下面的调用中,Factory::payment() 返回了 null:
Factory::payment()->common()->verifyNotify($params)
所以应先检查 SDK 初始化、Factory 类引用、依赖版本和封装代码,确认 Factory::payment() 为什么没有返回支付客户端。客户端创建正常后,再排查回调参数和支付宝配置。
先定位具体是哪一步为空
先拆开链式调用,分别检查每一步的返回值:
use Alipay\EasySDK\Kernel\Factory;
protected function verifyNotify($params)
{
try {
$payment = Factory::payment();
if ($payment === null) {
throw new \RuntimeException(
'Factory::payment() 返回 null,请检查 SDK 初始化、Factory 类和依赖版本'
);
}
$common = $payment->common();
if ($common === null) {
throw new \RuntimeException(
'payment()->common() 返回 null,请检查 SDK 版本及依赖完整性'
);
}
return $common->verifyNotify($params);
} catch (\Throwable $e) {
\think\facade\Log::error(
'支付宝验签失败:' . $e->getMessage()
);
return false;
}
}
这里应捕获 \Throwable,不要只捕获 \Exception。在 PHP 7 及以上版本中,对 null 调用成员方法产生的是 Error,不属于 Exception,因此原来的 catch (\Exception $e) 可能无法捕获这个错误。
检查 SDK 是否完成初始化
调用支付宝 Easy SDK 的支付功能前,通常需要执行:
Factory::setOptions($options);
例如:
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Config;
$options = new Config();
$options->protocol = 'https';
$options->gatewayHost = 'openapi.alipay.com';
$options->signType = 'RSA2';
$options->appId = '实际的支付宝应用 ID';
$options->merchantPrivateKey = '实际的应用私钥';
$options->alipayPublicKey = '实际的支付宝公钥';
Factory::setOptions($options);
具体字段要以项目当前安装的 SDK 版本为准。检查时注意以下几点:
Factory::setOptions()是否确实在验签前执行。- 支付宝异步通知请求是否也会加载初始化代码。
- 初始化过程是否被配置文件、环境变量或条件分支跳过。
appId、应用私钥和支付宝公钥是否读取成了空值。- 生产环境与沙箱环境的配置是否混用。
如果初始化只写在某个控制器的构造方法或普通支付流程中,而异步通知使用了另一个入口,就可能出现“下单正常,回调验签异常”。
检查 Factory 是否引用了正确的类
项目中可能有多个名为 Factory 的类。检查文件顶部的 use 声明,确认引用的是支付宝 Easy SDK 的 Factory:
use Alipay\EasySDK\Kernel\Factory;
也可以临时记录实际类名:
\think\facade\Log::info('Factory class: ' . Factory::class);
日志中的类名应该是:
Alipay\EasySDK\Kernel\Factory
如果不是,需要修正命名空间,或者直接使用完整类名:
$payment = \Alipay\EasySDK\Kernel\Factory::payment();
检查 Composer 依赖和 SDK 版本
示例代码可能来自其他版本,项目实际安装的 SDK 未必支持相同的调用链。可以在项目环境中运行:
composer show alipay/easysdk
同时检查:
- SDK 是否通过 Composer 完整安装。
vendor/autoload.php是否正常加载。composer.lock是否与服务器实际安装的依赖一致。- 部署时是否漏传了
vendor文件,或依赖安装是否失败。 - 是否同时安装了多个支付宝 SDK,并引用了其他包中的类。
- 当前版本是否支持
payment()->common()->verifyNotify()。
如果依赖经过升级、降级或重新安装,应以当前版本的源码和对应文档为准,不要直接套用其他版本的示例。
检查是否修改或封装过 Factory::payment()
正常情况下,支付工厂方法应该返回支付客户端对象。如果项目对 SDK 做过二次封装,或者定义了同名的自定义 Factory,需要直接查看 payment() 的实现。
例如,某些分支可能没有 return:
public static function payment()
{
// 某些条件下没有 return
}
也可能在缺少配置时主动返回 null:
public static function payment()
{
if (empty(self::$options)) {
return null;
}
return new Payment(...);
}
如果某个分支没有返回对象,PHP 会默认返回 null,接着调用 common() 就会触发当前错误。
验证回调参数的获取方式
修复 Factory::payment() 返回 null 的问题后,如果验签仍然失败,再检查 $params。
支付宝异步通知通常使用表单参数。ThinkPHP 项目应读取完整的原始通知参数,例如:
$params = request()->post();
$result = $this->verifyNotify($params);
if (!$result) {
return 'failure';
}
return 'success';
验签前不要随意修改参数,例如:
- 删除
sign或sign_type。 - 对参数额外执行 URL 编码或解码。
- 修改字段内容、字符编码或空格。
- 将数组转换成 JSON 后再传给验签方法。
- 只选择部分业务字段参与验签。
排查时可以记录字段名称,但回调数据可能包含订单号、账号标识等敏感信息,不宜长期把完整参数和签名写入日志:
\think\facade\Log::info(
'支付宝回调字段:' . implode(',', array_keys($params))
);
推荐的完整处理方式
use Alipay\EasySDK\Kernel\Factory;
protected function verifyNotify(array $params): bool
{
try {
$payment = Factory::payment();
if (!is_object($payment)) {
throw new \RuntimeException(
'支付宝 Easy SDK 支付客户端未创建,请检查 Factory 初始化和 SDK 版本'
);
}
$common = $payment->common();
if (!is_object($common)) {
throw new \RuntimeException(
'支付宝 Easy SDK common 客户端未创建'
);
}
return (bool) $common->verifyNotify($params);
} catch (\Throwable $e) {
\think\facade\Log::error(sprintf(
'支付宝回调验签异常:%s,文件:%s,行号:%d',
$e->getMessage(),
$e->getFile(),
$e->getLine()
));
return false;
}
}
建议按以下顺序排查:
- 确认实际引用的是
Alipay\EasySDK\Kernel\Factory。 - 确认回调入口执行过
Factory::setOptions()。 - 单独检查
Factory::payment()的返回值和实际类型。 - 核对 Composer 中安装的 SDK 名称和版本。
- 检查是否存在自定义封装、同名类或不完整部署。
- 支付客户端创建正常后,再核对支付宝公钥、应用私钥和回调参数。
不能通过跳过验签来绕过异常。异步通知必须先通过验签,并完成订单号、金额、商户身份和订单状态校验,之后才能进入业务处理流程。
备注:内容仅供参考。
