PAYATHON 2026

如何安全收集信用卡信息但不处理付款?

支付老李

明确结论

不要在自己的 PHP、MySQL、CSV 或 email 流程中收集、保存或传递完整的信用卡号、有效期和安全码。

即使公司不扣款,只是代酒店收集并转交信息,也不能免除安全责任。只要系统接触信用卡信息,就可能被纳入 PCI DSS 合规范围。发生泄露后,公司还可能承担通知、调查、赔偿、罚款和声誉损失。

更安全、可行的方案是:

  1. 由酒店直接向参会人员收集信用卡信息;
  2. 使用酒店认可且符合 PCI DSS 要求的服务,通过托管页面收集信息;
  3. 公司系统只保存报名资料和不具备支付能力的引用信息,如酒店确认号、第三方 token、处理状态和卡号后四位。

如果酒店无法提供安全的收集渠道,应由公司管理层、法务、安全负责人,以及收单机构或合格安全评估机构(QSA)共同确定处理方案。不要让普通报名系统临时承担银行卡信息保管功能。

为什么“只转发给酒店”仍然不安全

可以这样向非技术人员解释:

信用卡号就像酒店房间的万能钥匙。即使公司不准备使用这把钥匙,只要代为保管、复制或发送,就必须为钥匙丢失承担责任。把信用卡号存进数据库、CSV 或邮件,相当于复制了多把钥匙,并把它们留在服务器、员工电脑、邮箱和备份中。

一次报名可能留下许多副本:

  • 浏览器提交的内容;
  • Web server 或反向代理日志;
  • PHP 错误日志;
  • MySQL 数据库及其备份;
  • CSV 导出文件;
  • admin 的收件箱和邮件服务器;
  • 邮件转发、归档和垃圾邮件过滤系统;
  • 员工电脑的下载目录;
  • 调试工具、监控平台和工单截图。

即使数据库已经加密,应用程序仍需通过某种方式解密数据。如果攻击者同时获得应用权限或密钥,依然可以读取卡号。加密只能减少部分风险,不能自动让系统变得安全或符合 PCI DSS 要求。

CSV 和 email 为什么尤其危险

不要把完整卡号写入 CSV,也不要通过普通 email 发送完整卡号或安全码。

CSV 文件通常缺少以下保护:

  • 访问权限控制;
  • 操作审计;
  • 自动过期;
  • 防复制措施;
  • 集中删除能力;
  • 密钥管理。

email 的风险更大。邮件会经过多个系统,并可能长期保留在服务器、备份和用户终端上。传输过程使用 TLS,并不代表只有指定收件人能够读取,也无法防止转发、误发或邮箱被盗。

PCI DSS 并未简单地把 email 视为传输银行卡信息的安全渠道。通过消息技术传输未受保护的 PAN(Primary Account Number)时,需要采取相应的保护措施。安全码等敏感认证数据还受到更严格的限制。不要把“压缩包加密码”或“邮件启用 TLS”当作完整的解决方案。

推荐的业务流程

方案一:由酒店直接收集

应优先选择这种方案。报名系统完成普通资料登记后:

  1. 创建不含银行卡信息的报名记录;
  2. 将用户跳转到酒店提供的安全页面;
  3. 由酒店直接收集信用卡信息;
  4. 酒店返回预订确认号或处理状态;
  5. 系统保存确认号,不保存卡号。

如果酒店没有在线页面,可以要求酒店向参会人员发送安全的付款授权链接,或者提供受控的电话收集流程。

方案二:使用酒店认可的第三方托管页面

第三方页面应运行在公司服务器之外。银行卡信息必须由用户的浏览器直接提交给服务商,不能先经过公司的 PHP endpoint。

接入前需要确认:

  • 服务商是否持有适用于该服务范围的 PCI DSS 合规证明,如 AOC;
  • 服务是否明确支持“酒店预订担保”或延迟授权场景;
  • 生成的 token 能否供目标酒店使用;
  • 酒店通过什么方式获得数据;
  • 公司系统是否会接触完整 PAN;
  • 数据保存期限、删除方式和事件响应责任;
  • iframe、redirect、Hosted Fields 等集成方式会如何影响 PCI DSS 范围。

普通支付网关生成的 token 通常只能在该网关和指定商户账户中使用。不能想当然地认为酒店收到 token 后,就能通过自己的系统取得卡号或完成担保。

方案三:使用第三方安全保管库,并由酒店受控访问

只有在酒店确实无法直接收集信息时,才考虑使用专门的 card vault 或支付授权服务:

  1. 由第三方托管表单收集银行卡信息;
  2. 公司只接收不可逆的引用 ID;
  3. 酒店通过受控 portal 或正式 API 获取所需授权;
  4. 每次访问都需要身份验证,并保留审计记录;
  5. 信息按照约定期限自动删除;
  6. CSV 和 email 中仍不出现完整卡号。

这种方案必须得到酒店和第三方服务商的明确支持。不要自行设计“加密数据库 + 管理员下载解密”作为替代方案。

PHP 集成示例

以下内容只是架构示例,不对应任何特定服务商。实际使用的 endpoint、签名算法和字段必须以所选服务商的官方文档为准。

报名表只接收普通信息:

<form method="post" action="/register.php">
    <label>
        Name
        <input type="text" name="name" required>
    </label>

    <label>
        Email
        <input type="email" name="email" required>
    </label>

    <button type="submit">Continue to hotel guarantee</button>
</form>

PHP 创建报名记录后,将用户跳转到酒店或第三方托管页面:

<?php

declare(strict_types=1);

session_start();

$name = trim($_POST['name'] ?? '');
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);

if ($name === '' || $email === false || $email === null) {
    http_response_code(400);
    exit('Invalid registration data');
}

// 使用 prepared statement 保存普通报名资料。
// 数据表中不要设置 card_number、cvv、track_data 等字段。
$registrationId = createRegistration($name, $email);

$_SESSION['registration_id'] = $registrationId;

// 此 URL 应由酒店或合规服务商生成。
// production 环境不得使用自行拼接的敏感参数。
$hostedUrl = createHostedReservationUrl([
    'registration_reference' => (string) $registrationId,
    'return_url' => 'https://conference.example/booking-complete',
]);

header('Location: ' . $hostedUrl, true, 303);
exit;

系统接收第三方回调时,只保存状态、确认号、token 或 masked PAN:

<?php

declare(strict_types=1);

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

if (!verifyWebhookSignature($rawBody, $signature)) {
    http_response_code(401);
    exit;
}

$event = json_decode($rawBody, true, flags: JSON_THROW_ON_ERROR);

if (($event['type'] ?? '') === 'reservation.guaranteed') {
    updateRegistration([
        'registration_reference' => $event['registration_reference'],
        'reservation_status'     => 'guaranteed',
        'hotel_confirmation'     => $event['hotel_confirmation'],
        'payment_reference'      => $event['payment_reference'],
        'card_brand'             => $event['card_brand'] ?? null,
        'card_last4'             => $event['card_last4'] ?? null,
    ]);
}

http_response_code(204);

导出的 CSV 只能包含非敏感资料:

registration_id,name,email,reservation_status,hotel_confirmation,card_brand,card_last4
1042,Example User,user@example.com,guaranteed,HOTEL-88321,Visa,1234

通知邮件也只能发送摘要和后台链接:

Subject: New conference registration

Registration: 1042
Name: Example User
Reservation status: Guaranteed
Hotel confirmation: HOTEL-88321
Card: Visa ending in 1234

Review:
https://conference.example/admin/registrations/1042

访问后台链接时,应要求用户登录并通过多因素认证,同时实施最小权限控制和审计日志。不要在邮件中放入第三方的长期访问凭证,也不要发送能够兑换出完整卡号的 token。

必须避免的实现方式

以下做法都不能作为解决方案:

  • 将卡号明文写入 MySQL;
  • 将卡号“临时”写入数据库后再删除;
  • 把完整卡号放入 CSV;
  • 通过普通 email、聊天软件或工单传递卡号;
  • 收集 CVV 后长期保存;
  • 自行编写 AES 加密功能,并据此认为系统已经符合 PCI DSS 要求;
  • 把加密密钥与应用代码、配置文件或数据库放在同一个控制边界内;
  • 在 access log、exception log、analytics 或 error-reporting 服务中记录表单内容;
  • 使用前端 JavaScript 隐藏字段,却仍把卡号提交到自己的服务器;
  • 只依靠 HTTPS。HTTPS 只能保护传输链路,无法解决存储、邮件、导出、权限和终端泄露等问题。

合规和管理层需要确认的事项

PCI DSS 的具体适用范围取决于数据流、服务商的集成方式、酒店与公司的合同关系,以及谁会被认定为 merchant 或 service provider。应向收单机构、酒店的支付负责人或 QSA 获取针对该流程的正式判断。

至少需要书面确认:

  • 谁负责收集和保管银行卡信息;
  • 公司是否会接触完整 PAN;
  • 是否收集 CVV,以及何时删除;
  • 酒店是否接受第三方 token;
  • 哪一方负责身份验证、日志和访问审计;
  • 数据保留期限;
  • 发生泄露时由谁响应和通知;
  • 第三方的 PCI DSS 合规状态;
  • 是否还受到 GDPR、州隐私法或当地个人信息保护法规的约束。

还需要特别注意,CVV/CVC 属于敏感认证数据,授权完成后不能继续保存,即使经过加密也不行。具体业务能否在授权前短暂处理 CVV,以及酒店担保何时构成授权,应由支付服务商和 QSA 判断,不能由开发人员自行推定。

给管理层的简短说明

可以直接使用下面这段话:

公司是否实际扣款并不是关键。只要我们的网页、服务器、数据库、邮件或导出文件接触完整信用卡信息,公司就必须保护整个数据流,并可能承担 PCI DSS 合规和数据泄露责任。每导出一次 CSV 或发送一次邮件,都会增加一份难以控制和删除的信用卡副本。更安全的做法是让酒店或经过验证的 PCI DSS 合规服务商直接收集信息,我们只保存预订状态、确认号和卡号后四位。

权威资料

可将以下资料提供给管理层、法务和安全负责人:

最终设计应确保公司的报名系统始终不会收到完整的信用卡信息。做到这一点后,CSV、email 和普通报名管理功能仍可继续使用,但其中只能包含非敏感资料、处理状态和受限制的引用信息。

备注:内容仅供参考。