如何在 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 处理至少需要完成以下工作:
- 读取原始请求体。
- 使用支付平台提供的签名算法验证请求来源。
- 检查事件是否已经处理,保证幂等性。
- 核对订单号、金额和币种。
- 更新订单状态。
- 事务完成后返回成功响应。
不能因为请求中出现 "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,再等审核被拒后返工更稳妥。
备注:内容仅供参考。
