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 范围判断,这些个人信息也应按照适用的隐私法规和安全规范妥善保护。
备注:内容仅供参考。
