PAYATHON 2026

如何在 PHP 项目中集成 Flagship Merchant Services

支付小周

明确结论

Flagship Merchant Services 通常负责商户账户、收单与结算。PHP 项目真正需要对接的,是它为商户开通的支付网关。仅凭“Flagship Merchant Services”无法确定 API 地址或 PHP SDK,也不能直接照搬网上某个网关的示例。

开始开发前,应先向 Flagship 客服或开户经理确认:

  • 当前账户绑定的支付网关名称
  • API 文档和测试环境地址
  • API Key、用户名、密码或签名密钥
  • 是否开通 Card Not Present(非面对面交易)
  • 是否支持 tokenization、Customer Vault 或 recurring billing
  • Webhook 配置和签名验证方式
  • 是否提供官方 PHP SDK

如果账户使用 Authorize.Net、NMI 等第三方网关,就应按照该网关的官方 API 进行集成。不同网关的接口、认证参数和周期扣款模型并不通用。

推荐的集成方式

支付流程可以这样设计:

  1. 浏览器通过网关提供的托管支付页或前端组件采集银行卡信息。
  2. 网关将银行卡转换为一次性 token 或长期 payment token。
  3. PHP 后端使用 token 创建付款或客户支付档案。
  4. 首次付款成功后,只保存网关返回的客户 ID、支付方式 ID 和交易 ID。
  5. 周期付款可以交给网关的订阅功能,也可以由项目的定时任务按周期发起 token 扣款。
  6. 通过 Webhook 接收付款成功、失败、退款和拒付等异步事件。
  7. 根据 Webhook 更新本地订单或订阅状态。

不要在项目数据库中保存完整卡号、CVV 或磁道数据。即使这些数据经过加密,只要系统直接接触银行卡信息,PCI DSS 合规范围仍会明显扩大。

PHP 调用网关 API 的基本结构

具体网关尚未确定时,可以先抽象支付接口,避免业务代码与某一家服务商绑定:

<?php

interface PaymentGateway
{
    public function charge(
        string $paymentToken,
        int $amountInCents,
        string $currency,
        string $orderId
    ): array;

    public function createCustomer(
        string $email,
        string $paymentToken
    ): array;

    public function createSubscription(
        string $customerId,
        string $paymentMethodId,
        int $amountInCents,
        string $currency,
        string $interval
    ): array;

    public function cancelSubscription(string $subscriptionId): void;
}

具体实现类中的认证方式、请求地址和参数,应根据 Flagship 实际分配的网关填写。

下面是一个通用的 JSON API 请求封装。示例中的 URL、请求字段和认证头只是占位结构,不能直接用于生产环境:

<?php

function gatewayRequest(
    string $url,
    string $apiKey,
    array $payload,
    string $idempotencyKey
): array {
    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . $apiKey,
            'Content-Type: application/json',
            'Accept: application/json',
            'Idempotency-Key: ' . $idempotencyKey,
        ],
        CURLOPT_POSTFIELDS => json_encode(
            $payload,
            JSON_THROW_ON_ERROR
        ),
    ]);

    $body = curl_exec($ch);

    if ($body === false) {
        $message = curl_error($ch);
        curl_close($ch);
        throw new RuntimeException('Gateway connection failed: ' . $message);
    }

    $statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    curl_close($ch);

    $response = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

    if ($statusCode < 200 || $statusCode >= 300) {
        $message = $response['message'] ?? 'Unknown gateway error';
        throw new RuntimeException(
            "Gateway returned HTTP {$statusCode}: {$message}"
        );
    }

    return $response;
}

实际请求必须遵循对应支付网关的官方文档。有些网关使用 Basic Authentication,有些需要多组 API 凭证或请求签名,不能假定它一定支持 Bearer token 或 Idempotency-Key。

Recurring Payment 的两种实现方案

使用网关托管订阅

如果网关提供 recurring billing 或 subscription API,可以让网关负责定期扣款:

创建客户
  → 保存 payment token
  → 创建订阅计划
  → 网关定期扣款
  → Webhook 通知扣款结果
  → 更新本地订阅状态

这种方式实现起来较简单,也能减少项目定时任务故障造成漏扣的风险。接入前需要确认网关是否支持:

  • 按天、周、月或年计费
  • 免费试用期
  • 首期金额与续费金额不同
  • 修改订阅金额
  • 暂停和恢复订阅
  • 失败自动重试
  • 更换银行卡
  • 按比例计费
  • 取消后的生效时间

项目自行调度扣款

如果业务需要按用量计费,或者账期和金额会动态变化,可以保存网关生成的 customer token,再由定时任务主动扣款:

<?php

$subscriptions = findSubscriptionsDueForBilling();

foreach ($subscriptions as $subscription) {
    $billingPeriod = $subscription['billing_period'];
    $idempotencyKey = hash(
        'sha256',
        $subscription['id'] . ':' . $billingPeriod
    );

    try {
        $result = $gateway->charge(
            $subscription['payment_token'],
            $subscription['amount_in_cents'],
            $subscription['currency'],
            $subscription['invoice_id']
        );

        markInvoicePaid(
            $subscription['invoice_id'],
            $result['transaction_id']
        );
    } catch (Throwable $e) {
        markInvoicePaymentFailed(
            $subscription['invoice_id'],
            $e->getMessage()
        );
    }
}

findSubscriptionsDueForBilling()、markInvoicePaid() 等函数需要由项目自行实现。生产环境还应增加数据库锁或唯一约束,防止多个任务实例对同一笔账单重复扣款。

Webhook 不能省略

不要只依赖用户付款后的浏览器跳转,因为用户可能在支付成功后直接关闭页面。订单的最终状态应以服务端查询结果或通过签名验证的 Webhook 为准。

通用处理逻辑如下:

<?php

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_GATEWAY_SIGNATURE'] ?? '';

if (!verifyGatewaySignature($rawBody, $signature)) {
    http_response_code(401);
    exit('Invalid signature');
}

$event = json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
$eventId = $event['id'] ?? null;

if ($eventId === null) {
    http_response_code(400);
    exit('Missing event ID');
}

if (webhookAlreadyProcessed($eventId)) {
    http_response_code(200);
    exit('Already processed');
}

switch ($event['type'] ?? '') {
    case 'payment.succeeded':
        handlePaymentSucceeded($event);
        break;

    case 'payment.failed':
        handlePaymentFailed($event);
        break;

    case 'subscription.cancelled':
        handleSubscriptionCancelled($event);
        break;
}

recordProcessedWebhook($eventId);

http_response_code(200);
echo 'OK';

这里的事件名称、签名请求头和验签算法都只是示意。实现时必须使用实际网关文档规定的原始请求体、密钥和签名算法,不能自行猜测。

上线前的检查清单

  • 测试批准、拒绝、超时和重复提交场景。
  • 所有金额都使用最小货币单位,例如美元使用 cents,避免浮点数误差。
  • 为支付请求设置唯一订单号和幂等键。
  • 不在日志中记录卡号、CVV、API Key 或完整 Webhook 内容。
  • 将 API 凭证存入环境变量或密钥管理系统。
  • 验证 Webhook 签名,并按事件 ID 做幂等处理。
  • 保存网关交易 ID,便于退款和对账。
  • 区分支付状态与订单状态,不能把网络超时直接视为支付失败。
  • 为续费失败设计重试、通知和订阅暂停策略。
  • 在正式环境启用 HTTPS,并限制后台接口的访问权限。
  • 上线前确认退款、撤销、拒付和结算对账流程。
  • 根据实际使用的集成方式完成相应的 PCI DSS 自评与合规工作。

首先要确认 Flagship 为该商户账户配置了哪个支付网关。拿到网关名称、开发者文档和测试凭证后,才能确定准确的 PHP SDK、API endpoint、认证参数以及 recurring payment 的具体实现。

备注:内容仅供参考。