如何在 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 进行集成。不同网关的接口、认证参数和周期扣款模型并不通用。
推荐的集成方式
支付流程可以这样设计:
- 浏览器通过网关提供的托管支付页或前端组件采集银行卡信息。
- 网关将银行卡转换为一次性 token 或长期 payment token。
- PHP 后端使用 token 创建付款或客户支付档案。
- 首次付款成功后,只保存网关返回的客户 ID、支付方式 ID 和交易 ID。
- 周期付款可以交给网关的订阅功能,也可以由项目的定时任务按周期发起 token 扣款。
- 通过 Webhook 接收付款成功、失败、退款和拒付等异步事件。
- 根据 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 的具体实现。
备注:内容仅供参考。
