PAYATHON 2026

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 配合使用。

推荐的实现结构

以订阅功能为例,可以按以下流程实现:

  1. 用户登录后点击“订阅”。
  2. CodeIgniter 后端创建支付服务商的 Checkout Session 或订阅订单。
  3. 浏览器跳转到支付服务商托管的收银台。
  4. 用户在收银台填写银行卡信息并付款。
  5. 支付服务商通过 Webhook 通知网站付款结果。
  6. 网站验证 Webhook 签名,并根据事件更新用户的订阅状态。
  7. 用户访问附加功能时,服务端检查其订阅状态。

不要根据付款后的浏览器跳转结果直接开通权限。用户可以伪造请求,也可能在付款成功后直接关闭页面。更新订阅状态应以 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。
  • 密钥只能保存在服务器端环境变量或安全的密钥管理系统中。
  • 使用可公开的 price ID,不要相信前端提交的金额。
  • 服务端必须重新确认商品、币种、价格和登录用户。
  • Webhook 必须验证签名,并记录已经处理过的事件 ID。
  • Webhook 处理必须具备幂等性,因为支付平台可能重复发送同一事件。
  • 不要将 success_url 视为付款成功的证明。
  • 需要提前确定订阅取消后是立即停用,还是等当前周期结束后再停用。
  • 系统应处理续费失败、退款、争议、重复付款和订阅变更。
  • 正式上线前,应使用支付平台的测试环境和测试卡覆盖完整流程。
  • 银行卡支付涉及 PCI DSS 等合规要求。托管收银台通常可以大幅缩小系统直接接触敏感卡数据的范围,但不会自动免除所有安全和合规责任。
  • 如果业务覆盖多个国家或地区,还要确认税务、发票、强客户认证和数据保护要求。

对于这个场景,省事且风险较低的方案是:由 CodeIgniter 后端创建 Stripe Checkout 订阅会话,银行卡信息在 Stripe 托管的页面中填写,再通过经过签名验证的 Webhook 开通和维护会员权限。

备注:内容仅供参考。