PAYATHON 2026

如何在 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 自带的组件。要正常使用它,需要同时满足以下条件:

  1. 项目确实安装了 CitrusPay SDK 所要求的 Zend Framework 版本。
  2. 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/ 中的文件有一个问题:重新安装依赖时,改动会被覆盖。处理顺序可以是:

  1. 使用与当前 PHP 环境兼容,并且已经修复命名空间问题的 SDK 版本。
  2. 通过 Composer patch 或项目内适配器维护修复。
  3. 确实无法替换旧 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,而不是屏蔽报错后强行运行旧库。

备注:内容仅供参考。