PAYATHON 2026

iPhone 支付应用是否需要符合 PCI 合规要求?

支付小周

结论

需要考虑 PCI DSS 合规要求。

如果 iPhone 应用直接接收、处理或传输信用卡主账号(PAN)、有效期、持卡人姓名或安全码(CVV/CVC),应用及相关后端、日志、监控和运维环境通常都会被纳入 PCI DSS 评估范围。HTTPS POST 只能保护数据传输,不能让整个支付流程自动满足 PCI DSS。

更稳妥的做法是避免让自己的应用和服务器接触原始卡号。可以使用支付网关提供的官方 iOS SDK、托管支付页面或 Apple Pay,由支付服务商采集卡片信息并返回一次性 token,自己的服务器只用这个 token 发起交易。

这种架构通常可以大幅缩小合规范围。不过,具体适用哪种 SAQ、是否需要额外评估,仍应由收单行、支付网关或合格安全评估机构确认。

为什么 HTTPS 还不够

HTTPS 解决的主要是客户端与服务器之间的数据加密。PCI DSS 检查的是整个支付链路,包括:

  • 卡号是否进入应用内存、崩溃报告或调试日志;
  • 卡号是否经过自己的服务器、代理、API 网关和消息队列;
  • 数据库、缓存和备份是否保存持卡人数据;
  • 哪些人可以访问生产环境和支付系统;
  • 密钥如何生成、保存、轮换和撤销;
  • 应用及服务器是否定期进行漏洞扫描、渗透测试和补丁更新;
  • 是否建立了访问审计、异常监控和事件响应流程。

即使请求使用 TLS,只要请求正文中包含原始卡号,由你控制的相关系统通常仍属于持卡人数据环境。

CVV/CVC 属于敏感认证数据,一般不得在授权完成后保存,即使经过加密也不应保留。

推荐的支付架构

优先采用以下流程:

iPhone App
    │
    │ 通过支付网关官方 SDK 或托管页面录入卡片
    ▼
Payment Gateway
    │
    │ 返回一次性 payment token / payment method ID
    ▼
iPhone App
    │
    │ 只把 token 和订单号发给自己的服务器
    ▼
Merchant Backend
    │
    │ 使用服务端凭据和 token 请求扣款
    ▼
Payment Gateway

这套架构需要满足几个条件:

  1. 卡号和 CVV 直接提交给支付网关。
  2. 自己的后端不接收原始卡片数据。
  3. iPhone 应用只取得无法直接还原卡号的一次性 token 或支付方式标识。
  4. 支付网关的私钥只保存在服务器端,不能写入 App。
  5. 最终支付金额由服务器根据订单重新计算,不能采用客户端提交的金额。

也可以优先考虑 Apple Pay。它能减少用户手工输入卡号的需要,但使用 Apple Pay 并不一定意味着商户完全退出 PCI DSS 范围,仍要看实际支付链路、服务商方案和收单行的要求。

Swift 实现思路

不同支付网关的 SDK 接口并不相同。下面只展示代码结构,不对应任何一家支付服务商的真实 API。实际项目应直接使用所选网关官方 SDK 提供的卡片输入组件和 tokenization 接口,不要自行用普通 UITextField 接收卡号。

struct PaymentTokenRequest: Encodable {
    let orderID: String
    let paymentToken: String
}

struct PaymentResult: Decodable {
    let status: String
    let transactionID: String?
}

func submitPayment(
    orderID: String,
    paymentToken: String
) async throws -> PaymentResult {
    let endpoint = URL(string: "https://api.example.com/payments")!

    var request = URLRequest(url: endpoint)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try JSONEncoder().encode(
        PaymentTokenRequest(
            orderID: orderID,
            paymentToken: paymentToken
        )
    )

    let (data, response) = try await URLSession.shared.data(for: request)

    guard let httpResponse = response as? HTTPURLResponse,
          (200..<300).contains(httpResponse.statusCode) else {
        throw PaymentError.requestFailed
    }

    return try JSONDecoder().decode(PaymentResult.self, from: data)
}

enum PaymentError: Error {
    case requestFailed
}

这段代码发送给商户服务器的是 paymentToken,而不是原始卡片信息:

{
  "cardNumber": "4111111111111111",
  "cvv": "123",
  "expiry": "12/30"
}

服务器再通过支付网关的服务端 SDK 或 API,结合订单和 token 发起支付:

POST /payments
{
  "orderID": "ORDER-2026-0001",
  "paymentToken": "gateway_generated_token"
}

收到请求后,服务器需要通过 orderID 查询真实订单,重新获取金额和币种。不能直接用客户端传来的最终金额扣款,否则攻击者可能篡改请求内容。

如果业务必须直接采集卡号

如果业务确实无法使用支付网关 SDK、托管页面或 tokenization 方案,就应把它当作完整的 PCI DSS 项目处理。仅给 App 增加几个安全设置并不能满足要求。

通常需要完成以下工作。

确定合规责任和评估范围

先与收单行和支付网关确认:

  • 商户在支付链路中的角色;
  • 哪些移动端、后端和基础设施属于评估范围;
  • 适用的 PCI DSS 版本及 SAQ 类型;
  • 是否需要由 QSA(Qualified Security Assessor)进行正式评估;
  • 是否需要外部漏洞扫描服务商执行扫描。

不能只因为使用了第三方网关就确定 SAQ 类型。如果卡号仍由自己的 App 采集,或者会经过自己的服务器,通常不能按照完全外包卡片采集的场景处理。

尽量减少数据范围

  • 不保存 CVV/CVC;
  • 没有明确业务需要时,不保存完整 PAN;
  • 页面和日志中只显示掩码卡号,例如 ************1234;
  • 清除请求日志、崩溃报告和分析工具中的敏感字段;
  • 检查反向代理、WAF、APM、客服系统和备份是否会复制请求正文;
  • 在网络和权限层面将支付环境与普通业务系统隔离。

保护必须保存的数据

如果确实需要保存 PAN,应使用符合要求的强加密,并建立完整的密钥管理机制。加密密钥不能与密文存放在同一位置,也不能把解密密钥硬编码到 App 中。

同时还要限制数据保留时间,建立自动删除机制,并记录相关数据的访问人员和访问时间。

建立安全开发流程

至少要覆盖:

  • 代码审查和依赖项检查;
  • 服务端输入验证与鉴权;
  • 防重放和幂等处理;
  • 漏洞管理及安全补丁;
  • 移动端与服务端渗透测试;
  • 生产访问最小权限和多因素认证;
  • 审计日志、告警和事件响应;
  • 定期确认第三方支付服务商的 PCI DSS 合规状态。

移动端不能视为完全可信的环境。越狱设备、动态注入、调试工具或恶意辅助功能都可能影响输入数据。仅依靠 App 自身的反调试、代码混淆或证书固定,无法满足合规要求。

容易忽略的问题

不要在以下位置输出卡号、CVV 或完整的支付请求:

print(cardNumber)
logger.debug("Payment request: \(requestBody)")

也不要把支付网关的服务端密钥放进 iOS 应用:

let secretKey = "sk_live_xxx" // 错误:App 中的密钥可以被提取

App 中只能保存支付网关明确允许公开使用的客户端标识或 publishable key。能够创建扣款、退款或访问交易数据的密钥必须由服务器保管。

支付结果也不能只依据 App 收到的响应。服务器应验证支付网关返回的结果,并对 webhook 执行签名校验、时间戳检查和幂等处理,以防止伪造通知或重复记账。

实际落地建议

可以按以下顺序实施:

  1. 选择已经通过相应 PCI DSS 验证的支付服务商。
  2. 使用其官方 iOS SDK、托管支付页面或 Apple Pay 集成。
  3. 确保原始卡号和 CVV 不经过自己的服务器。
  4. 让服务器只接收 token,并在服务端完成扣款。
  5. 清查日志、崩溃分析、监控和客服工具,确认其中没有持卡人数据。
  6. 根据真实的数据流图,与收单行或 QSA 确认适用的 SAQ 和验证材料。
  7. 按收单行要求完成年度验证、扫描或其他持续合规工作。

不要试图只把一个直接处理卡号的 App 做成所谓的 PCI 合规应用。更合适的办法是从架构上避免接触原始卡片数据,再对其余系统完成相应级别的 PCI DSS 验证。

备注:内容仅供参考。