PAYATHON 2026

PCI compliance:在 MySQL 中存储账单信息是否合规?

支付阿杰

明确结论

在 MySQL 中仅保存以下信息,通常不会因这些字段本身违反 PCI DSS:

  • 姓名
  • 账单地址
  • 邮政编码

前提是数据库确实没有保存完整银行卡号(PAN)、安全码(CVV/CVC/CID)、磁条数据、PIN/PIN Block 等支付账户数据。

但数据库没有保存信用卡数据,不代表整个购物车系统无需遵守 PCI DSS。只要系统接受银行卡付款,商户通常仍要根据实际支付流程、支付服务商的集成方式和收单机构的要求,完成相应的 PCI DSS 合规工作。具体适用范围应由收单机构、支付服务商或合格安全评估机构(QSA)判断。

为什么通常不属于 PCI 持卡人数据存储

PCI DSS 主要保护支付账户数据,尤其是完整的 PAN。姓名、账单地址和邮政编码如果脱离 PAN 单独保存,一般不属于需要按照 PCI DSS 持卡人数据要求保护的数据组合。

几种情况的区别如下:

  • Name + Billing Address + Zip Code:通常不属于 PCI DSS 所称的持卡人数据存储。
  • PAN + Name:属于持卡人数据。
  • PAN + Billing Address:其中包含 PAN,因此属于持卡人数据。
  • CVV/CVC/CID:属于敏感认证数据,完成授权后不得保存,即使经过加密也不应保存。
  • 支付服务商返回的不可逆令牌:通常不等同于 PAN,但是否影响合规范围,还要看商户系统能否将令牌还原,或通过令牌访问真实卡号。

服务器是否进入 PCI DSS 范围,也不能只看数据库字段。如果应用服务器能够接触、传输或影响支付数据,它仍可能属于持卡人数据环境的一部分。

推荐的支付架构

尽量使用支付服务商提供的托管支付页、iframe 或官方前端组件,让浏览器直接把银行卡信息发送给支付服务商:

Customer Browser
    ├── Name / Billing Address / Zip Code ──> Your Application
    └── Card Number / CVV ──────────────────> Payment Provider

Payment Provider ──> payment_method_token ──> Your Application

这样,你的服务器只接收支付令牌和必要的非卡片资料,不会直接接触完整卡号或 CVV。相比由自己的服务器接收卡号后再转发,这种方式通常能大幅缩小 PCI DSS 的评估范围,但不会自动免除所有合规责任。

数据库设计示例

账单地址可以作为用户保存的地址记录,并且不要创建 PAN、CVV 等字段:

CREATE TABLE billing_addresses (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    full_name VARCHAR(200) NOT NULL,
    address_line1 VARCHAR(255) NOT NULL,
    address_line2 VARCHAR(255) NULL,
    city VARCHAR(120) NOT NULL,
    region VARCHAR(120) NULL,
    postal_code VARCHAR(32) NOT NULL,
    country_code CHAR(2) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
        ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_billing_addresses_user_id (user_id)
);

支付记录只保存支付服务商生成的标识:

CREATE TABLE payments (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id BIGINT UNSIGNED NOT NULL,
    provider VARCHAR(50) NOT NULL,
    provider_payment_id VARCHAR(255) NOT NULL,
    amount_minor BIGINT UNSIGNED NOT NULL,
    currency CHAR(3) NOT NULL,
    status VARCHAR(32) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uq_provider_payment (
        provider,
        provider_payment_id
    )
);

provider_payment_id 应保存支付服务商生成的交易 ID 或令牌,不能保存完整银行卡号。也不要因为以后可能用到,就预留 card_number、cvv 或磁条数据字段。

如果需要向用户显示卡片提示,可以保存支付服务商明确允许返回的非敏感展示信息,例如卡品牌和后四位:

{
  "payment_method_token": "pm_provider_generated_token",
  "card_brand": "visa",
  "card_last4": "4242"
}

不要先接收完整 PAN,再自行截取出 card_last4。应直接使用支付服务商返回的展示字段。

实施步骤

1. 确认系统是否接触银行卡数据

检查完整卡号或 CVV 是否可能出现在以下位置:

  • HTML 表单提交内容
  • 应用服务器请求体
  • Web 服务器访问日志
  • 错误日志和异常追踪平台
  • 消息队列
  • 数据库及备份
  • 客服系统和分析平台
  • 前端埋点或会话回放工具

即使数据库没有保存卡号,只要卡号经过了你的服务器,PCI DSS 的适用范围仍可能扩大。

2. 使用支付服务商的托管组件

优先采用支付服务商提供的托管结账页、托管字段或令牌化组件。确认银行卡号和 CVV 会直接发送给支付服务商,不会先提交到自己的后端。

适用哪种 SAQ、评估范围能否缩小,取决于实际集成方式和收单机构的要求,不能仅凭数据库没有保存卡号来判断。

3. 分开保存地址数据和支付凭据

账单地址表只保存履约或改善客户体验所需的数据。支付表只保存服务商返回的令牌、交易 ID 和状态,不要混入完整 PAN 或敏感认证数据。

4. 为地址数据提供基本安全保护

账单地址单独存在时通常不属于 PCI 持卡人数据,但仍然是个人信息。至少应采取以下措施:

  • 全程使用 TLS。
  • 数据库账户遵循最小权限原则。
  • 对管理后台实施身份认证和权限控制。
  • 避免在日志中输出完整地址。
  • 对数据库备份实施访问控制并进行加密。
  • 设置合理的保存期限和删除机制。
  • 允许客户修改或删除已保存的地址。
  • 防止攻击者通过用户 ID 参数越权读取其他客户的地址。

例如,查询地址时必须同时限定当前登录用户:

SELECT
    id,
    full_name,
    address_line1,
    address_line2,
    city,
    region,
    postal_code,
    country_code
FROM billing_addresses
WHERE id = ?
  AND user_id = ?;

如果只按地址 ID 查询,可能产生 IDOR 越权漏洞。

5. 记录数据流并向相关机构确认

绘制实际的数据流图,列出所有可能接触支付信息的系统,然后向支付服务商或收单机构确认:

  • 适用的 PCI DSS 验证方式;
  • 需要填写哪一种 SAQ;
  • 在当前集成模式下,哪些服务器仍属于评估范围;
  • 支付令牌是否被视为不可还原;
  • 是否还有额外的合同要求或地区监管要求。

需要特别避免的做法

不要保存以下内容:

Full PAN
CVV / CVC / CID
Full magnetic-stripe data
PIN or PIN Block

尤其不要加密后保存 CVV。PCI DSS 严格限制在授权完成后保存敏感认证数据,加密不会让这类存储自动变得合规。

也不要把银行卡号写入通用文本字段、订单备注或日志。判断依据是字段中的实际内容,而不是字段名称。只要其中包含支付账户数据,就可能影响 PCI DSS 的适用范围。

其他合规注意事项

姓名和账单地址还可能受到隐私保护、数据泄露通知、消费者权益或行业合同等要求的约束。这些要求与 PCI DSS 属于不同的合规体系。

因此,单独保存姓名、账单地址和邮政编码,通常不会因这些字段本身违反 PCI DSS。但购物车的整体支付流程仍需进行 PCI DSS 范围判断,这些个人信息也应按照适用的隐私法规和安全规范妥善保护。

备注:内容仅供参考。