PAYATHON 2026

在 CodeIgniter Framework 中加载 External PHP SDK

支付老李

结论

不需要把 payu.php 改写成 CodeIgniter 代码。第三方 SDK 最好保持原样,通过 require_once、CodeIgniter 的 third_party 目录或 Composer 自动加载,再由 Controller、Service 或自定义 Library 调用。

对于这个 PayU Biz SDK,可以这样处理:

  1. 将原始 SDK 放到 application/third_party/payu/。
  2. 用自定义 Library 加载 SDK,并封装 pay_page()。
  3. 在 Controller 中处理支付发起、成功回调和失败回调。
  4. surl 和 furl 要填写 PayU 能够访问的完整 HTTP/HTTPS URL,不能填写普通 PHP 函数名。
  5. 商户密钥和 Salt 应存放在配置文件或环境变量中,不能写死在 Controller 里,也不能提交到版本库。

下面的示例基于 CodeIgniter 3。CodeIgniter 2 的目录结构与其基本相似。CodeIgniter 4 则更适合使用 Composer、命名空间和 Service。

推荐目录结构

application/
├── config/
│   └── payu.php
├── controllers/
│   └── Payment.php
├── libraries/
│   └── Payu_gateway.php
└── third_party/
    └── payu/
        ├── payu.php
        └── ...

尽量不要修改 SDK 内部文件。保留原始目录结构,后续升级 SDK 时会方便很多。

配置 PayU 参数

创建 application/config/payu.php:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

$config['payu_key']  = getenv('PAYU_KEY');
$config['payu_salt'] = getenv('PAYU_SALT');

如果当前部署环境不方便使用环境变量,也可以暂时将参数写入配置文件,但必须确保文件不会泄露:

$config['payu_key']  = 'your_merchant_key';
$config['payu_salt'] = 'your_merchant_salt';

问题中的 gtKFFx 和 eCwWELxi 看起来是示例或测试凭据,不能直接用于生产环境。实际参数应以 PayU 商户后台提供的值为准。

封装成 CodeIgniter Library

创建 application/libraries/Payu_gateway.php:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Payu_gateway
{
    protected $CI;
    protected $key;
    protected $salt;

    public function __construct()
    {
        $this->CI =& get_instance();

        $this->CI->config->load('payu', true);

        $this->key  = $this->CI->config->item('payu_key', 'payu');
        $this->salt = $this->CI->config->item('payu_salt', 'payu');

        require_once APPPATH . 'third_party/payu/payu.php';
    }

    public function createPayment(array $data)
    {
        $data['key'] = $this->key;

        return pay_page($data, $this->salt);
    }
}

这个 Library 是一层适配代码,负责加载 SDK、读取配置,并提供适合项目使用的方法。没有必要把 payu.php 中的每个函数都改写成 CodeIgniter 类方法。

也不建议全局 autoload 这个 Library。支付 SDK 一般只会在少数请求中使用,按需加载更合适,也能避免 SDK 在每次请求时执行初始化代码。

在 Controller 中发起支付

创建 application/controllers/Payment.php:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Payment extends CI_Controller
{
    public function checkout()
    {
        $this->load->library('payu_gateway');

        $transactionId = uniqid('order_', true);

        $paymentData = [
            'txnid'       => $transactionId,
            'amount'      => '100.00',
            'firstname'   => 'Test',
            'email'       => 'test@example.com',
            'phone'       => '1234567890',
            'productinfo' => 'Product Info',
            'surl'        => site_url('payment/success'),
            'furl'        => site_url('payment/failure'),
        ];

        /*
         * 应先将 transactionId、订单金额和订单状态保存到数据库,
         * 再跳转到支付页面。
         */
        $this->payu_gateway->createPayment($paymentData);
    }

    public function success()
    {
        $response = $this->input->post(NULL, true);

        /*
         * 这里不能因为进入 success URL 就直接把订单标记为已支付。
         * 必须验证 PayU 返回的数据、哈希和订单金额。
         */

        log_message(
            'info',
            'PayU success response: ' . json_encode($response)
        );

        echo 'Payment Success';
    }

    public function failure()
    {
        $response = $this->input->post(NULL, true);

        log_message(
            'info',
            'PayU failure response: ' . json_encode($response)
        );

        echo 'Payment Failure';
    }
}

需要注意的是:

'surl' => site_url('payment/success'),
'furl' => site_url('payment/failure'),

支付平台回调的是 Web URL,而不是当前 PHP 文件中的普通函数:

function payment_success()
{
}

在 CodeIgniter 中,成功和失败的处理逻辑应放在 Controller 方法里,再由路由分发请求。

如果项目关闭了 index.php,生成的地址可能是:

https://example.com/payment/success

如果没有配置 URL 重写,地址可能是:

https://example.com/index.php/payment/success

只要这个 URL 能从公网访问,并能正确接收 PayU 的请求即可。使用本机 localhost 测试时,PayU 通常无法访问回调地址,因此需要可从公网访问的测试环境或安全的隧道服务。

也可以直接使用 require_once

如果 SDK 只在一个 Controller 中使用一次,直接加载也没有问题:

public function checkout()
{
    require_once APPPATH . 'third_party/payu/payu.php';

    $data = [
        'key'         => $this->config->item('payu_key'),
        'txnid'       => uniqid('order_', true),
        'amount'      => '100.00',
        'firstname'   => 'Test',
        'email'       => 'test@example.com',
        'phone'       => '1234567890',
        'productinfo' => 'Product Info',
        'surl'        => site_url('payment/success'),
        'furl'        => site_url('payment/failure'),
    ];

    pay_page($data, $this->config->item('payu_salt'));
}

路径应使用 CodeIgniter 提供的常量:

APPPATH . 'third_party/payu/payu.php'

不要依赖:

dirname(__FILE__)

Controller、Library 和 SDK 所在的目录不同,使用相对路径很容易加载到错误的位置。

直接使用 require_once 时,Controller 会同时负责加载 SDK、读取配置和调用支付接口。项目一旦增加退款、订单查询、签名验证或多种支付方式,代码很快就会变得难以维护。因此,正式项目通常适合增加一层 Library 或 Service。

如果 SDK 支持 Composer

如果第三方 SDK 提供 Composer 包,通常应优先使用 Composer。它可以处理依赖关系、命名空间和自动加载,不必在每个文件中手动调用 require_once。

CodeIgniter 3 可以在 application/config/config.php 中启用 Composer:

$config['composer_autoload'] = FCPATH . 'vendor/autoload.php';

也可以填写:

$config['composer_autoload'] = true;

具体写法取决于 vendor 目录的实际位置。启用后,可以按照 SDK 文档直接实例化相关类:

$client = new Vendor\Package\Client();

不要同时通过 Composer 和手动方式加载同一套 SDK,否则可能导致类被重复声明,或者加载到不同版本。

External PHP files 的通用加载方式

外部 PHP 代码应根据自身形式选择加载方法。

Composer 包

优先使用 Composer 自动加载。这是现代 PHP SDK 常用的依赖管理方式。

带有类但不支持 Composer 的 SDK

将完整 SDK 放入:

application/third_party/vendor_name/

然后在自定义 Library 中加载入口文件:

require_once APPPATH . 'third_party/vendor_name/sdk.php';

Library 只需向项目提供实际会用到的方法,Controller 不必直接依赖 SDK 的全部接口。

仅提供全局函数的 PHP 文件

如果文件主要定义辅助函数,可以放入自定义 Helper,也可以由 Helper 加载:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

require_once APPPATH . 'third_party/vendor_name/functions.php';

使用时加载对应的 Helper:

$this->load->helper('vendor_name');

不过,第三方原始文件最好仍放在 third_party 中,不要直接改造成 Helper,否则升级时很难比较和替换。

项目自己的业务类

项目自己编写且需要访问 CodeIgniter 实例或配置的类,可以放入:

application/libraries/

第三方 SDK 本体和项目适配代码应分开存放:

third_party/    原始 SDK
libraries/      项目适配层
controllers/    HTTP 请求和响应

支付回调的安全注意事项

集成支付网关时,除了正确加载文件,还要妥善处理支付结果:

  • 不能仅凭用户访问了 success 地址就认定支付成功。
  • 按当前使用的 PayU SDK 或接口文档验证返回的哈希或签名。
  • 将返回的 txnid 与数据库中的订单号进行比对。
  • 校验支付金额、商户标识、交易状态和产品信息。
  • 对同一交易做幂等处理,防止重复回调造成重复发货。
  • 不要把完整的支付响应直接输出给最终用户。
  • 记录日志时,应隐藏 Salt、密钥、银行卡信息和其他敏感字段。
  • 成功页和失败页用于用户跳转。如果 PayU 还提供服务器端通知接口,应以经过验证的服务器通知作为最终依据。
  • 不同 PayU 接口版本的哈希字段顺序和验证算法可能不同,应以当前接入版本的官方文档或 SDK 实现为准,不能自行猜测。

因此,更合适的做法是保留 payu.php 的原始内容,将 SDK 放入 third_party,再通过轻量的 CodeIgniter Library 加载和封装。这样既符合 CodeIgniter 的项目结构,也方便以后升级 SDK、切换支付渠道和编写测试。

备注:内容仅供参考。