PAYATHON 2026

如何在 iPhone app 中集成和实现 payment gateways?

支付阿杰

结论

在 iPhone App 中接入第三方支付网关,不一定会导致 App Store 审核被拒。是否合规,主要看销售的是什么,而不是使用了哪家支付服务商。

  • 如果销售的是在 App 内解锁或消费的数字内容、会员权益、虚拟币或功能订阅,通常必须使用 Apple 的 In-App Purchase(StoreKit)。
  • 如果销售的是实体商品,或需要在现实世界中履约的服务,例如电商商品、外卖、打车、酒店、机票和线下课程,可以使用 Stripe、Adyen、PayPal 等第三方网关,也可以使用 Apple Pay。
  • 慈善捐赠、点对点转账、金融交易、阅读器类 App,以及特定地区适用的外部购买规则,都有额外条件,不能直接套用普通商品的支付方案。
  • 即使某类交易允许使用第三方网关,App 仍可能因为购买引导、隐私处理、账户机制或支付流程不完整而被拒。

Apple 会调整审核政策,部分规则也会因 App Store 地区、App 类型和 entitlement 资格而不同。提交审核前,应查阅当时有效的《App Review Guidelines》,尤其是 3.1.1、3.1.2、3.1.3,以及目标地区的补充条款。

先判断应该使用哪种支付方式

可以根据销售场景选择:

销售场景通常应采用的方式
App 内高级功能解锁In-App Purchase
视频、音乐、电子书等数字内容In-App Purchase,特殊类别和地区规则除外
自动续费数字会员StoreKit Auto-Renewable Subscription
游戏金币、道具、点数In-App Purchase
实体商品第三方支付网关或 Apple Pay
外卖、打车、酒店、机票第三方支付网关或 Apple Pay
线下咨询、维修、健身课程通常可以使用第三方支付
用户之间转账或金融产品交易需要结合金融监管和 App Store 专项规则判断

不能只因为某项服务按月收费,就认定它应该使用某一种订阅支付方式。要看用户购买的权益在哪里消费。线下健身房会员和 App 内数字课程会员都可能按月收费,但适用的规则并不相同。

推荐的系统架构

iPhone App 不应直接持有支付网关的私钥。常见架构如下:

iPhone App
    │
    │ 1. 创建订单
    ▼
业务服务器
    │
    │ 2. 使用支付网关私钥创建 Payment/Checkout Session
    ▼
支付网关
    │
    │ 3. 返回 client secret 或 checkout URL
    ▼
iPhone App
    │
    │ 4. 展示支付界面并完成用户授权
    ▼
支付网关
    │
    │ 5. Webhook 通知最终支付状态
    ▼
业务服务器
    │
    │ 6. 验签、更新订单并向 App 返回结果
    ▼
iPhone App

订单状态应以服务端记录为准。App 不能只根据客户端回调显示”支付成功”,因为客户端可能遭到篡改,也可能断网或在支付完成前退出。

第三方支付网关的接入步骤

1. 在支付平台创建商户账户

按照业务所在国家或地区的要求完成商户认证,并取得:

  • 可公开使用的客户端标识,例如 publishable key
  • 只能保存在服务端的 secret key
  • Webhook signing secret
  • 测试环境和生产环境配置

私钥不能写入 Swift 源代码、Info.plist、远程配置,也不能放在随安装包下发的资源文件中。

2. 由服务端创建订单

客户端只需提交商品标识、数量和必要的业务信息。价格、币种、折扣和最终金额必须由服务端重新计算,不能直接采用客户端传入的金额。

下面是一个不依赖具体支付服务商的接口示例:

POST /api/payments
Content-Type: application/json
Authorization: Bearer <user-token>

{
  "productId": "physical_product_001",
  "quantity": 1
}

服务端可以返回支付网关生成的客户端凭证:

{
  "orderId": "order_12345",
  "clientSecret": "gateway_generated_client_secret"
}

如果使用托管收银台,也可以返回一次性 HTTPS 地址:

{
  "orderId": "order_12345",
  "checkoutURL": "https://checkout.example-payment.com/session/..."
}

3. 在 App 中调用服务端

使用 URLSession 创建业务订单:

import Foundation

struct CreatePaymentRequest: Encodable {
    let productId: String
    let quantity: Int
}

struct CreatePaymentResponse: Decodable {
    let orderId: String
    let clientSecret: String
}

enum PaymentError: Error {
    case invalidResponse
    case requestFailed(Int)
}

func createPayment(
    productId: String,
    quantity: Int,
    accessToken: String
) async throws -> CreatePaymentResponse {
    let url = URL(string: "https://api.example.com/api/payments")!

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue(
        "Bearer \(accessToken)",
        forHTTPHeaderField: "Authorization"
    )
    request.httpBody = try JSONEncoder().encode(
        CreatePaymentRequest(
            productId: productId,
            quantity: quantity
        )
    )

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

    guard let httpResponse = response as? HTTPURLResponse else {
        throw PaymentError.invalidResponse
    }

    guard (200...299).contains(httpResponse.statusCode) else {
        throw PaymentError.requestFailed(httpResponse.statusCode)
    }

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

取得 clientSecret 后,将它交给支付网关提供的官方 iOS SDK。不同网关的 SDK 初始化方式、回调类型和 URL Scheme 配置各不相同,应按照所选服务商的官方文档实现。不要自行处理银行卡号或安全码。

4. 正确处理回跳和支付状态

有些付款方式需要跳转到银行 App、浏览器或身份验证页面。App 需要配置支付网关要求的回调 URL,并在 SwiftUI 中处理返回:

@main
struct ShopApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    PaymentRedirectHandler.shared.handle(url)
                }
        }
    }
}

这里的 PaymentRedirectHandler 只是抽象示例,不是系统 API。实际项目中应调用支付 SDK 提供的 URL 处理方法。

用户返回 App 后,不能根据 URL 参数直接发货。客户端应向业务服务器查询订单状态:

GET /api/orders/order_12345
Authorization: Bearer <user-token>

只有服务端确认了支付网关返回的最终状态,才能把订单标记为已支付。

5. 在服务端验证 Webhook

Webhook 处理至少需要完成以下工作:

  1. 读取原始请求体。
  2. 使用支付平台提供的签名算法验证请求来源。
  3. 检查事件是否已经处理,保证幂等性。
  4. 核对订单号、金额和币种。
  5. 更新订单状态。
  6. 事务完成后返回成功响应。

不能因为请求中出现 "status": "paid" 就认定支付成功。服务端必须验证签名,也不能把 Webhook 地址当成供客户端主动调用的”确认支付”接口。

如果销售的是数字内容

数字商品不应使用前面的第三方网关流程。此类商品应通过 StoreKit 获取并发起购买,例如:

import StoreKit

enum PurchaseError: Error {
    case failedVerification
}

func purchase(productId: String) async throws {
    let products = try await Product.products(for: [productId])

    guard let product = products.first else {
        return
    }

    let result = try await product.purchase()

    switch result {
    case .success(let verificationResult):
        switch verificationResult {
        case .verified(let transaction):
            await unlockEntitlement(for: transaction.productID)
            await transaction.finish()

        case .unverified:
            throw PurchaseError.failedVerification
        }

    case .pending:
        // 等待家长批准、付款授权或其他延迟处理。
        break

    case .userCancelled:
        break

    @unknown default:
        break
    }
}

生产环境还需要处理:

  • App 启动后尚未完成的交易
  • Transaction.updates
  • 恢复购买
  • 订阅续费、退款、撤销和宽限期
  • 服务端交易验证
  • 同一账户在不同设备间的权益同步

不要在客户端收到一次购买成功回调后,就永久写入一个本地布尔值。数字权益应根据经过验证的交易记录进行恢复和更新。

Apple Pay 与 In-App Purchase 不是一回事

Apple Pay 是银行卡等支付凭证的授权方式,适合实体商品和现实服务。In-App Purchase 则是 App Store 用于数字内容的交易体系。

Apple Pay 不能绕过数字商品必须使用 In-App Purchase 的要求。例如,用户在 App 内购买虚拟金币,即使付款按钮调用 Apple Pay,通常也不符合审核规则。

接入 Apple Pay 一般需要:

  • Apple Developer 中的 Merchant ID
  • App 对应的 Apple Pay Capability
  • 支付处理商支持的证书或令牌配置
  • PKPaymentRequest
  • 服务端向支付处理商提交支付令牌

可以先检查设备是否具备付款能力:

import PassKit

let canUseApplePay = PKPaymentAuthorizationController.canMakePayments()

具体支持哪些支付网络、商户能力和地区配置,应以实际商户账户为准,不要直接照搬不适用的枚举值。

容易导致审核被拒的情况

用第三方支付销售 App 内数字权益

这是常见的审核风险。例如,在 App 中放置信用卡付款页面,让用户购买会员后解锁数字功能。即使付款页面由正规网关托管,也可能违反审核规则。

在不符合资格时引导用户到站外购买

按钮、链接、文案或 WebView 能否引导用户前往外部购买,可能取决于 App 类型、店面地区和 entitlement。某些 App 可以显示外部购买链接,不代表所有 App 都能这样做。

如果没有明确适用的规则或授权,应避免使用”去官网购买”或”浏览器支付更便宜”等规避式引导。

在 WebView 中包装数字商品收银台

将第三方支付页面放进 WKWebView,不会改变交易的性质。审核关注用户买了什么,而不是付款页面使用原生 SDK、Safari 还是 WebView。

即使销售的是允许使用第三方支付的实体商品,通常也更适合使用支付平台的官方 SDK、ASWebAuthenticationSession 或安全的托管页面,不应自行在 WebView 中收集银行卡数据。

审核人员无法完成支付流程

应准备可用的审核账户,并在 App Store Connect 的 Review Notes 中写明:

  • 支付入口的位置
  • 测试账户和登录步骤
  • 测试环境是否会真实扣款
  • 商品属于实体商品、线下服务还是数字内容
  • 特殊权限或硬件要求
  • 审核人员如何取消或完成测试订单

不要要求审核人员先联系开发者,才能获得验证码或开通权限。

隐私披露不完整

如果支付 SDK 会收集设备标识、风控数据、联系方式或位置信息,需要核对:

  • App Privacy 中的数据收集声明
  • 隐私政策
  • 第三方 SDK 的隐私清单和 required reason API 要求
  • 用户追踪行为是否需要 App Tracking Transparency 授权

数据由支付服务商收集,并不代表开发者可以不申报。

安全与合规注意事项

  • 全程使用 HTTPS,并正确验证服务器证书。
  • 不要记录银行卡号、安全码、支付令牌或完整身份信息。
  • 不要把支付平台的 secret key 放进 App。
  • 金额和币种必须由服务端确认。
  • 为订单创建、Webhook 和发货流程设计幂等机制。
  • 妥善处理重复点击、超时、取消、退款和延迟付款等状态。
  • 使用支付服务商提供的 tokenization 或托管输入组件,减少系统直接接触银行卡数据的机会。
  • 是否需要符合 PCI DSS 以及适用哪个等级,应根据实际数据流和支付服务商方案确定。
  • 如果业务涉及金融服务、借贷、证券、加密资产或汇款,还需要确认当地的牌照和监管要求。通过 App Store 审核不等于已经取得经营许可。

提交审核前的检查清单

  • 确认商品属于数字权益,还是实体商品或线下服务。
  • 确认所有数字商品都通过 StoreKit 销售。
  • 检查第三方支付页面是否包含不允许的站外购买引导。
  • 检查购买失败、用户取消或网络中断后,界面能否恢复。
  • 确认 App 以服务端订单状态为准。
  • 确认 Webhook 已验签,并能防止重复处理。
  • 确认隐私政策和 App Privacy 已覆盖支付 SDK 收集的数据。
  • 准备完整的审核账户、操作路径和测试说明。
  • 检查恢复购买和订阅管理入口是否适用于相关数字商品。
  • 核对目标 App Store 地区最新的外部支付、链接和 entitlement 规则。

第三方支付网关可以接入 iPhone App,但只能用于 Apple 允许的交易场景。应先确认商品性质,再选择 StoreKit、Apple Pay 或第三方网关。这样比先接入 SDK,再等审核被拒后返工更稳妥。

备注:内容仅供参考。