在 CodeIgniter Framework 中加载 External PHP SDK
结论
不需要把 payu.php 改写成 CodeIgniter 代码。第三方 SDK 最好保持原样,通过 require_once、CodeIgniter 的 third_party 目录或 Composer 自动加载,再由 Controller、Service 或自定义 Library 调用。
对于这个 PayU Biz SDK,可以这样处理:
- 将原始 SDK 放到
application/third_party/payu/。 - 用自定义 Library 加载 SDK,并封装
pay_page()。 - 在 Controller 中处理支付发起、成功回调和失败回调。
surl和furl要填写 PayU 能够访问的完整 HTTP/HTTPS URL,不能填写普通 PHP 函数名。- 商户密钥和 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、切换支付渠道和编写测试。
备注:内容仅供参考。