如何在 Yii2 中集成 CitrusPay payment gateway?
结论
这两个报错都源于 PHP 的类名解析和自动加载,与 Yii2 的支付流程无关:
- 控制器没有导入
CitrusPay,PHP 因而把它当成当前命名空间中的seller\modules\citruspay\controllers\CitrusPay。 Zend_Config_Ini是 Zend Framework 1 的旧式全局类。CitrusPay SDK 被放入命名空间后,PHP 会尝试查找seller\citruspay\lib\Zend_Config_Ini。项目中还需要安装 SDK 要求的 Zend Framework 依赖。- CitrusPay 的旧版 PHP SDK 往往不完全符合 PSR-4 规范。仅把文件复制到项目中通常不够,还需要通过 Composer 的
classmap、files或 SDK 自带的初始化文件注册自动加载。
解决时需要安装 SDK 依赖,让 Composer 正确加载 SDK,并在控制器中明确导入相关类或使用完整类名。
为什么 CitrusPay 会被解析到控制器命名空间
假设控制器声明了以下命名空间:
namespace seller\modules\citruspay\controllers;
如果直接在文件中调用:
CitrusPay::setApiKey('apikey', 'sandbox');
而文件里没有相应的 use 声明,PHP 会把这个类名解释为:
seller\modules\citruspay\controllers\CitrusPay
这就是报错中出现的类名。Transaction 也有同样的问题,只是程序可能还没有执行到那一行,或者这个类碰巧已经加载。
应在控制器顶部导入 SDK 中的实际类:
namespace seller\modules\citruspay\controllers;
use Yii;
use yii\web\Controller;
use Vendor\CitrusPay\CitrusPay;
use Vendor\CitrusPay\Transaction;
这里的 Vendor\CitrusPay 仅用于示意,需要替换成当前 CitrusPay SDK 源码中声明的真实命名空间。打开对应类文件,找到类似下面的声明:
namespace SomeNamespace;
再按该声明导入:
use SomeNamespace\CitrusPay;
use SomeNamespace\Transaction;
如果 SDK 中的类没有 namespace 声明,说明它们位于全局命名空间,可以明确写成:
\CitrusPay::setApiKey('apikey', 'sandbox');
$response = \Transaction::create(
$transactionData,
\CitrusPay::getApiKey()
);
使用全局类名前,仍需确认包含这些类的文件已进入自动加载流程。
配置 CitrusPay SDK 的自动加载
优先使用 SDK 官方提供的 Composer 包,并遵循其版本约束。如果拿到的是一组旧 PHP 文件,可以在项目根目录的 composer.json 中配置 classmap:
{
"autoload": {
"psr-4": {
"seller\\": "seller/"
},
"classmap": [
"seller/citruspay/lib/"
]
}
}
其中的目录必须指向 SDK 类文件的真实位置。修改完成后执行:
composer dump-autoload
Yii2 的入口文件通常已经加载 Composer:
require __DIR__ . '/../vendor/autoload.php';
不需要在每个控制器中重复加载 vendor/autoload.php。
如果 CitrusPay SDK 有专门的启动文件,例如 autoload.php、bootstrap.php 或 CitrusPay.php,而且该文件除了声明类,还会注册自己的 autoloader,可以通过 Composer 的 files 加载:
{
"autoload": {
"files": [
"seller/citruspay/lib/autoload.php"
]
}
}
配置后再次运行:
composer dump-autoload
是否需要同时配置 classmap 和 files,要看 SDK 的实际目录结构。没有检查 SDK 文件前,不应假定它一定使用某个固定的启动文件名。
解决 Zend_Config_Ini 找不到的问题
Zend_Config_Ini 属于 Zend Framework 1,不是 Yii2 自带的组件。要正常使用它,需要同时满足以下条件:
- 项目确实安装了 CitrusPay SDK 所要求的 Zend Framework 版本。
- SDK 使用全局类名引用该类,或者已经正确导入它。
如果 CitrusPay SDK 的依赖说明明确要求 Zend Framework 1,可以通过 Composer 安装兼容版本。具体版本应以当前 SDK 的 composer.json 或说明文档为准,例如:
composer require zendframework/zendframework1
这只是旧版依赖的安装示例。如果当前 Composer 或 PHP 环境无法安装,不能随意改用 Zend Framework 2、Laminas 或其他包,因为这些包并不提供同名的 Zend_Config_Ini 类。
如果已经安装该类,但报错中仍然出现:
seller\citruspay\lib\Zend_Config_Ini
说明 SDK 文件本身处于命名空间中,却使用了未限定的类名:
$config = new Zend_Config_Ini($configFile);
可以改为全局类名:
$config = new \Zend_Config_Ini($configFile);
也可以在 SDK 文件的命名空间声明后导入:
namespace seller\citruspay\lib;
use Zend_Config_Ini;
$config = new Zend_Config_Ini($configFile);
不过,直接修改 vendor/ 中的文件有一个问题:重新安装依赖时,改动会被覆盖。处理顺序可以是:
- 使用与当前 PHP 环境兼容,并且已经修复命名空间问题的 SDK 版本。
- 通过 Composer patch 或项目内适配器维护修复。
- 确实无法替换旧 SDK 时,再直接修改第三方源码,并记录所做的改动。
控制器调用示例
假设 SDK 中的 CitrusPay 和 Transaction 都是全局类,而且 Composer 已经能自动加载它们,可以这样调用:
<?php
namespace seller\modules\citruspay\controllers;
use Yii;
use yii\web\Controller;
use yii\web\BadRequestHttpException;
class DefaultController extends Controller
{
public function actionPay()
{
$transactionData = [
'cardHolderName' => Yii::$app->request->post('cardHolderName'),
'cardNumber' => Yii::$app->request->post('cardNumber'),
'expiryMonth' => Yii::$app->request->post('expiryMonth'),
'cardType' => Yii::$app->request->post('cardType'),
'cvvNumber' => Yii::$app->request->post('cvvNumber'),
'expiryYear' => Yii::$app->request->post('expiryYear'),
'returnUrl' => Yii::$app->request->post('returnUrl'),
'amount' => Yii::$app->request->post('amount'),
];
\CitrusPay::setApiKey(
Yii::$app->params['citrusPayApiKey'],
'sandbox'
);
$response = \Transaction::create(
$transactionData,
\CitrusPay::getApiKey()
);
$redirectUrl = $response->get_redirect_url();
$responseCode = (int) $response->get_resp_code();
if ($redirectUrl !== '' && $responseCode === 200) {
return $this->redirect($redirectUrl);
}
throw new BadRequestHttpException(
'CitrusPay 请求失败:' . $response->get_resp_msg()
);
}
}
如果 SDK 类声明了命名空间,则应通过 use 导入:
use Actual\Sdk\Namespace\CitrusPay;
use Actual\Sdk\Namespace\Transaction;
然后调用:
CitrusPay::setApiKey($apiKey, 'sandbox');
$response = Transaction::create(
$transactionData,
CitrusPay::getApiKey()
);
不要一边按全局类调用,一边又假定它属于某个自定义命名空间。具体写法必须以 SDK 源文件中的声明为准。
快速确认自动加载是否正常
进入支付业务逻辑前,可以临时执行以下检查:
var_dump(class_exists(\Zend_Config_Ini::class));
var_dump(class_exists(\CitrusPay::class));
var_dump(class_exists(\Transaction::class));
如果 SDK 使用命名空间,则检查完整类名:
var_dump(class_exists(\Actual\Sdk\Namespace\CitrusPay::class));
如果返回 false,说明自动加载仍未配置成功。可以依次检查:
- 类文件所在路径是否已加入 Composer 的
classmap或psr-4。 - 命名空间是否与目录结构和大小写一致。
- 修改配置后是否执行了
composer dump-autoload。 - Yii2 入口文件是否加载了当前项目的
vendor/autoload.php。 - Zend Framework 1 依赖是否确实存在,而不是只给类名前加了反斜杠。
- Web 服务器使用的项目目录,是否与执行 Composer 命令时所在的目录一致。
配置与安全注意事项
不要把 API Key 硬编码在控制器中。可以将它放在本地配置或环境变量里:
// config/params-local.php
return [
'citrusPayApiKey' => getenv('CITRUSPAY_API_KEY'),
];
生产环境还需要注意以下事项:
- 不要记录银行卡号、CVV、API Key 或完整的支付请求。
- 金额、订单号和回调地址应由服务端生成并校验,不能完全信任 POST 参数。
- 回调成功后还要校验签名,并查询或核对支付状态,不能仅凭浏览器跳转判断付款成功。
sandbox和生产环境应分别配置密钥与端点。- 旧版 CitrusPay SDK 可能依赖已经停止维护的 Zend Framework 1,也可能不兼容较新的 PHP 版本。如果当前环境无法安装这些依赖,应改用支付服务商仍在维护的 SDK 或 API,而不是屏蔽报错后强行运行旧库。
备注:内容仅供参考。
