CodeIgniter 有哪些可用的支付 API/Library?
结论
不要在 CodeIgniter 页面中直接接收、保存或处理银行卡号、有效期、CVV 等敏感信息。更安全的做法是使用支付服务商提供的托管收银台或安全支付组件,让银行卡数据直接提交给支付服务商。
常见方案有:
- Stripe Checkout / Stripe Billing:适用于信用卡、借记卡和周期订阅。
- PayPal Subscriptions:适用于 PayPal 账户及其支持的银行卡付款。
- Adyen、Braintree 等支付平台:适用于对支付方式、覆盖地区或企业功能有更多要求的项目。
- Omnipay:PHP 的统一支付抽象层,可以对接多个网关,但订阅、Webhook 等高级功能仍要结合具体支付服务商实现。
CodeIgniter 本身不包含支付功能。支付 SDK 通常以普通 PHP Composer 包的形式提供,可以与 CodeIgniter 3 或 CodeIgniter 4 配合使用。
推荐的实现结构
以订阅功能为例,可以按以下流程实现:
- 用户登录后点击“订阅”。
- CodeIgniter 后端创建支付服务商的 Checkout Session 或订阅订单。
- 浏览器跳转到支付服务商托管的收银台。
- 用户在收银台填写银行卡信息并付款。
- 支付服务商通过 Webhook 通知网站付款结果。
- 网站验证 Webhook 签名,并根据事件更新用户的订阅状态。
- 用户访问附加功能时,服务端检查其订阅状态。
不要根据付款后的浏览器跳转结果直接开通权限。用户可以伪造请求,也可能在付款成功后直接关闭页面。更新订阅状态应以 Webhook 为主要依据。
使用 Stripe Checkout 的示例
以下示例使用 CodeIgniter 4 和 Stripe Checkout。接入前,需要先在 Stripe 后台创建产品和周期价格,并取得相应的 price ID。
安装官方 PHP SDK:
composer require stripe/stripe-php
将密钥保存在 .env 中,不要写入控制器,也不要提交到 Git:
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
STRIPE_SUBSCRIPTION_PRICE_ID=price_xxx
创建订阅结账会话
<?php
namespace App\Controllers;
use Stripe\StripeClient;
class Subscription extends BaseController
{
public function checkout()
{
$user = auth()->user();
if (!$user) {
return redirect()->to('/login');
}
$stripe = new StripeClient(getenv('STRIPE_SECRET_KEY'));
$session = $stripe->checkout->sessions->create([
'mode' => 'subscription',
'line_items' => [
[
'price' => getenv('STRIPE_SUBSCRIPTION_PRICE_ID'),
'quantity' => 1,
],
],
'client_reference_id' => (string) $user->id,
'success_url' => site_url(
'subscription/success?session_id={CHECKOUT_SESSION_ID}'
),
'cancel_url' => site_url('subscription/cancel'),
]);
return redirect()->to($session->url);
}
}
对应路由可以这样配置:
$routes->post('subscription/checkout', 'Subscription::checkout');
$routes->get('subscription/success', 'Subscription::success');
$routes->get('subscription/cancel', 'Subscription::cancel');
订阅按钮应发送 POST 请求,并启用 CodeIgniter 的 CSRF 防护:
<form method="post" action="/subscription/checkout">
<?= csrf_field() ?>
<button type="submit">订阅</button>
</form>
如果用户尚未登录,不要只依赖浏览器传来的 user_id。应从登录会话中识别用户,以免为错误的账户创建订阅。
通过 Webhook 更新订阅状态
Webhook 端点需要读取原始请求体,并使用支付服务商提供的签名密钥验证请求。
<?php
namespace App\Controllers;
use Stripe\Exception\SignatureVerificationException;
use Stripe\Webhook;
use UnexpectedValueException;
class StripeWebhook extends BaseController
{
public function handle()
{
$payload = $this->request->getBody();
$signature = $this->request->getHeaderLine('Stripe-Signature');
$secret = getenv('STRIPE_WEBHOOK_SECRET');
try {
$event = Webhook::constructEvent(
$payload,
$signature,
$secret
);
} catch (UnexpectedValueException | SignatureVerificationException $e) {
return $this->response
->setStatusCode(400)
->setBody('Invalid webhook');
}
switch ($event->type) {
case 'checkout.session.completed':
$session = $event->data->object;
// 使用 client_reference_id 找到本地用户。
// 保存 customer、subscription 等标识。
// 注意:应保证重复处理同一事件不会产生副作用。
break;
case 'invoice.paid':
$invoice = $event->data->object;
// 确认续费成功,并更新订阅有效状态。
break;
case 'invoice.payment_failed':
$invoice = $event->data->object;
// 记录付款失败,可限制相关功能或提示用户更新付款方式。
break;
case 'customer.subscription.updated':
case 'customer.subscription.deleted':
$subscription = $event->data->object;
// 同步取消、暂停、到期等状态。
break;
}
return $this->response
->setStatusCode(200)
->setBody('OK');
}
}
路由示例:
$routes->post('webhooks/stripe', 'StripeWebhook::handle');
Webhook 请求来自支付服务商,而不是网站表单,因此通常需要将其路径排除在 CSRF 校验之外。但即使排除了 CSRF 校验,也不能省略支付服务商的签名验证。
具体如何排除,取决于 CodeIgniter 的版本和过滤器配置,请参考对应版本的 CodeIgniter 文档。
数据库应该保存什么
数据库可以保存支付服务商返回的标识和业务状态,例如:
user_id
provider
provider_customer_id
provider_subscription_id
price_id
subscription_status
current_period_end
cancel_at_period_end
created_at
updated_at
不要保存以下内容:
完整银行卡号
CVV/CVC
银行卡密码
完整磁条数据
未经保护的支付密钥
即使需要向用户展示银行卡信息,通常也只保存支付服务商返回的非敏感摘要,例如卡品牌、尾号四位和到期月份。不要自行从用户提交的数据中提取并保存这些字段。
Stripe、PayPal 还是 Omnipay
Stripe
如果主要需求是银行卡周期订阅,Stripe Checkout 和 Stripe Billing 通常更容易接入。托管收银台、订阅续费、退款、Webhook 和付款方式管理都可以在同一平台完成。
接入前还要根据业务所在地和账户类型,确认 Stripe 是否支持目标国家、结算币种和当地支付方式。
PayPal
如果目标用户习惯使用 PayPal,可以选择 PayPal Subscriptions。订阅应由服务端创建,并通过 Webhook 同步激活、续费失败和取消等状态。
PayPal 的订单支付与周期订阅使用不同的接口。实现会员订阅时,应选择 Subscriptions 相关 API,不能只创建一次性订单。
Omnipay
Omnipay 适合需要统一多个支付网关调用方式的 PHP 项目:
composer require league/omnipay
安装 Omnipay 后,还要安装具体的网关适配器。它的统一接口主要覆盖常见支付操作,而不同服务商的订阅模型、客户门户、Webhook 和强客户认证流程并不完全相同。如果只接入一家支付平台,直接使用该平台的官方 SDK 通常更清楚。
安全与业务注意事项
- 全站必须使用 HTTPS。
- 密钥只能保存在服务器端环境变量或安全的密钥管理系统中。
- 使用可公开的
priceID,不要相信前端提交的金额。 - 服务端必须重新确认商品、币种、价格和登录用户。
- Webhook 必须验证签名,并记录已经处理过的事件 ID。
- Webhook 处理必须具备幂等性,因为支付平台可能重复发送同一事件。
- 不要将
success_url视为付款成功的证明。 - 需要提前确定订阅取消后是立即停用,还是等当前周期结束后再停用。
- 系统应处理续费失败、退款、争议、重复付款和订阅变更。
- 正式上线前,应使用支付平台的测试环境和测试卡覆盖完整流程。
- 银行卡支付涉及 PCI DSS 等合规要求。托管收银台通常可以大幅缩小系统直接接触敏感卡数据的范围,但不会自动免除所有安全和合规责任。
- 如果业务覆盖多个国家或地区,还要确认税务、发票、强客户认证和数据保护要求。
对于这个场景,省事且风险较低的方案是:由 CodeIgniter 后端创建 Stripe Checkout 订阅会话,银行卡信息在 Stripe 托管的页面中填写,再通过经过签名验证的 Webhook 开通和维护会员权限。
备注:内容仅供参考。
