PAYATHON 2026

如何在 PHP 中实现信用卡 payment gateway?

支付老李

明确结论

不能只用 PHP 独立完成信用卡扣款和资金入账。PHP 可以处理订单、发起支付请求和接收回调,但真正的扣款仍需通过收单银行、支付处理商或持牌支付服务商接入 Visa、Mastercard 等银行卡网络。

实际项目更适合接入成熟支付服务商的 API,并采用托管支付页、Hosted Fields 或前端令牌化组件。不要让信用卡号、有效期和 CVV 经过自己的 PHP 服务器,否则会扩大 PCI DSS 合规范围,也会增加数据泄露风险。

为什么无法绕过中间机构

一笔信用卡支付通常要经过以下环节:

  1. 用户输入信用卡信息。
  2. 支付服务商对卡片信息进行令牌化。
  3. 收单机构通过银行卡网络发送授权请求。
  4. 发卡银行批准或拒绝交易。
  5. 支付成功后进入清算和结算流程。
  6. 服务商按照结算周期将款项转入商户银行账户。

即使选择直接与银行合作,也需要签订收单协议、申请商户号、建立风控体系、满足 PCI DSS 要求,并接入银行提供的网关接口。这类方案通常适合交易规模较大,而且拥有专门支付和安全团队的企业。

推荐的实现方式

比较稳妥的架构如下:

浏览器
  ├─ 将卡片信息直接提交给支付服务商
  └─ 获得 token 或确认 Payment Intent
           ↓
PHP 后端
  ├─ 创建支付请求
  ├─ 绑定内部订单号
  ├─ 验证金额和币种
  └─ 接收并验证 webhook
           ↓
数据库
  └─ 根据 webhook 更新订单状态

后端不能因为浏览器返回了”支付成功”就直接发货。订单的最终支付状态应以通过签名验证的服务端 webhook 为准。

PHP 示例

下面使用 Stripe 的 Payment Intent 模型说明基本流程。其他支付服务商通常也有类似的创建支付、前端确认和异步通知机制。具体字段要以实际使用的服务商及 SDK 版本文档为准。

先安装官方 PHP SDK:

composer require stripe/stripe-php

创建支付请求:

<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use Stripe\StripeClient;

$stripe = new StripeClient($_ENV['STRIPE_SECRET_KEY']);

$orderId = 'ORDER-20260901-001';
$amount = 19900; // 最小货币单位,例如 USD 199.00 = 19900 cents

try {
    $paymentIntent = $stripe->paymentIntents->create(
        [
            'amount' => $amount,
            'currency' => 'usd',
            'automatic_payment_methods' => [
                'enabled' => true,
            ],
            'metadata' => [
                'order_id' => $orderId,
            ],
        ],
        [
            'idempotency_key' => 'payment:' . $orderId,
        ]
    );

    header('Content-Type: application/json; charset=utf-8');

    echo json_encode(
        [
            'clientSecret' => $paymentIntent->client_secret,
        ],
        JSON_THROW_ON_ERROR
    );
} catch (Throwable $e) {
    http_response_code(500);

    echo json_encode(
        ['error' => 'Unable to create payment'],
        JSON_THROW_ON_ERROR
    );
}

金额必须由后端根据商品、税费和优惠重新计算,不能直接采用前端提交的最终金额。

浏览器拿到 clientSecret 后,应通过服务商提供的前端组件采集卡片信息并确认支付。卡号和 CVV 不应发送到上面的 PHP 接口。

处理支付回调

受 3-D Secure、银行风控或网络延迟影响,支付可能暂时处于异步状态。因此,订单状态需要通过 webhook 更新,不能只依赖用户付款后跳转到的页面。

<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use Stripe\Exception\SignatureVerificationException;
use Stripe\Webhook;

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_STRIPE_SIGNATURE'] ?? '';
$webhookSecret = $_ENV['STRIPE_WEBHOOK_SECRET'];

try {
    $event = Webhook::constructEvent(
        $payload,
        $signature,
        $webhookSecret
    );
} catch (UnexpectedValueException | SignatureVerificationException $e) {
    http_response_code(400);
    exit;
}

switch ($event->type) {
    case 'payment_intent.succeeded':
        $paymentIntent = $event->data->object;
        $orderId = $paymentIntent->metadata->order_id ?? null;

        if ($orderId !== null) {
            /*
             * 在事务中执行:
             * 1. 查询本地订单;
             * 2. 核对金额、币种和支付对象;
             * 3. 如果尚未处理,则标记为已支付;
             * 4. 保存 event ID,防止重复处理。
             */
        }
        break;

    case 'payment_intent.payment_failed':
        $paymentIntent = $event->data->object;

        // 记录失败状态,但不要保存完整支付敏感信息。
        break;
}

http_response_code(200);

生产环境还要保存 webhook 的事件 ID,并为该字段建立唯一索引。支付服务商可能重复发送同一个事件,因此处理逻辑必须具备幂等性。

数据库应保存什么

通常只需保存以下信息:

  • 内部订单号
  • 支付服务商的交易或 Payment Intent ID
  • 金额及币种
  • 支付状态
  • webhook 事件 ID
  • 创建和更新时间
  • 必要的失败原因代码

以下信息不应保存:

  • 完整信用卡号
  • CVV/CVC
  • 磁道数据
  • PIN
  • 未加密的支付密钥

即使服务商返回了卡片摘要,通常也只应保存品牌、后四位和有效期等用于展示的非敏感信息。

必须注意的事项

PCI DSS

只要系统接触、传输或存储银行卡数据,就可能需要满足更严格的 PCI DSS 要求。使用服务商的托管页面或令牌化组件可以缩小合规范围,但不代表商户自动免除所有合规责任。

3-D Secure 和强客户认证

部分国家或地区要求 Strong Customer Authentication。支付流程需要支持跳转或额外的用户验证,不能假设一次同步 API 请求必然立即成功。

幂等性

创建支付和处理 webhook 都要支持幂等。网络超时后重试请求时,不能对同一订单重复扣款。

金额校验

金额应以整数形式的最小货币单位表示,并由后端计算。不同货币的小数位规则并不完全相同,不能默认所有货币都有两位小数。

密钥管理

API Secret 和 webhook secret 应存放在环境变量或专用密钥管理系统中,不能写入源码、提交到 Git,也不能发送给浏览器。

HTTPS 和日志脱敏

所有支付接口都必须使用 HTTPS。应用日志不能记录卡号、CVV、完整请求体、密钥或其他支付敏感信息。

如果确实要直接连接收单银行

首先需要向银行申请互联网收单商户资质,并获取银行提供的 API、证书、商户号和测试环境。PHP 端通常需要负责:

  • 使用客户端证书建立 TLS 连接
  • 按照银行协议生成签名
  • 提交授权、撤销、退款和查询请求
  • 验证响应签名
  • 处理对账文件
  • 管理重复请求和异常状态
  • 满足 PCI DSS、审计及风控要求

这些工作不是编写一个 PHP 表单就能完成的。除非已经具备收单协议和完整的安全能力,否则接入成熟支付服务商通常成本更低,也更容易正确处理退款、拒付、3-D Secure、风控和资金结算。

备注:内容仅供参考。