通过 Web Services 传输支付信息是否符合 Apple 规定?
明确结论
是否符合 Apple 规定,不取决于是否使用 Web Services,而取决于用户购买的服务类型:
- 用户购买应用内使用的数字内容、功能、订阅或虚拟服务时,通常必须使用 Apple 的 In-App Purchase(StoreKit)。通过 Web Services 将支付信息发送到第三方支付门户,一般不能替代应用内购买,还可能导致 App Store 审核被拒。
- 用户购买实体商品或在应用外实际履行的服务,例如外卖、交通、酒店或线下预约时,通常可以使用第三方支付。
- “符合 Apple 审核规则”与”法律上允许”是两回事。即使 Apple 允许使用第三方支付,开发者仍需遵守支付牌照、PCI DSS、隐私保护、消费者权益和当地数据保护法规。
部分地区或特定类型的应用可能适用外部购买链接、特殊 entitlement 或其他例外。不过,相关规则会随销售地区和提交时间变化,不能因此认定所有应用都可以绕过 In-App Purchase。
先判断服务属于哪一类
可以按以下标准判断:
| 购买内容 | 通常采用的支付方式 |
|---|---|
| 应用内会员、数字课程、虚拟道具、功能解锁、云端数字服务 | In-App Purchase |
| 实体商品、外卖、打车、酒店、线下活动或预约服务 | 第三方支付 |
| 用户之间实时提供的服务 | 需要结合具体业务模式判断 |
| 企业内部服务、阅读器类应用、特定地区的外部购买 | 可能存在例外,需核对适用条件 |
如果用户付款后获得的权益主要在该 App 内使用,通常应将其视为数字服务。把支付页面重定向到浏览器、WebView 或第三方门户,并不会改变交易的性质。
推荐实现方式
数字服务使用 StoreKit
使用 StoreKit 2 发起购买时,应由服务端校验交易并发放权益。客户端不能只根据本地回调直接开通服务。
import StoreKit
func purchase(_ product: Product) async throws {
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
// 将 transaction.id 或签名交易数据发送至服务端校验
await sendTransactionToServer(transaction)
await transaction.finish()
case .pending:
print("Purchase is pending")
case .userCancelled:
print("User cancelled the purchase")
@unknown default:
break
}
}
func checkVerified<T>(
_ result: VerificationResult<T>
) throws -> T {
switch result {
case .verified(let value):
return value
case .unverified:
throw PurchaseError.failedVerification
}
}
enum PurchaseError: Error {
case failedVerification
}
服务端应使用 Apple 提供的交易验证机制确认交易真实性,并处理退款、撤销、续订和订阅状态变化。
允许第三方支付时使用托管结账页
如果交易涉及实体商品或应用外服务,建议让合规支付服务商直接收集银行卡信息,应用服务器只接收支付令牌。
struct CheckoutRequest: Codable {
let orderId: String
let paymentToken: String
}
func submitPayment(orderId: String, paymentToken: String) async throws {
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(
CheckoutRequest(
orderId: orderId,
paymentToken: paymentToken
)
)
let (_, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw PaymentError.requestFailed
}
}
enum PaymentError: Error {
case requestFailed
}
这里的 paymentToken 应由支付服务商的 SDK 或托管页面生成,不能直接使用银行卡号、CVV 等原始支付数据。
安全与合规要求
不要通过普通 Web Services 自行传输或保存完整的银行卡资料,尤其不要:
- 在日志、崩溃报告或分析事件中记录银行卡号、CVV 或完整支付请求。
- 将支付信息写入
UserDefaults、本地数据库或未加密缓存。 - 自行保存 CVV。
- 仅根据客户端返回的”支付成功”状态发放权益。
- 在不符合 Apple 规则的场景中,通过 WebView 隐藏第三方数字支付入口。
网络通信至少应使用 HTTPS,并正确验证服务器证书。后台还应使用订单签名、幂等键、服务端支付结果校验和 webhook 验签,防止重复扣款和伪造付款结果。
只要系统接触、处理或存储银行卡数据,就可能进入 PCI DSS 的合规范围。使用支付服务商提供的托管页面、原生 SDK 和令牌化方案,通常可以明显缩小合规范围,但不会自动免除所有责任。
上架前需要确认的事项
提交审核前,应明确以下信息:
- 用户具体购买了什么。
- 权益是在 App 内使用,还是在线下或 App 外履行。
- 支付页面由谁提供,应用服务器是否会接触原始银行卡数据。
- 支付完成后如何验证交易、处理退款和恢复订单。
- 目标销售地区是否允许相应的外部购买方式。
- 是否需要 Apple 的特定 entitlement,以及应用是否符合申请条件。
如果”特定服务”实际属于应用内数字功能,建议直接使用 StoreKit。如果属于实体商品或线下服务,可以接入第三方支付,但应采用令牌化或托管结账方案,并完成 PCI DSS、隐私政策和当地法律方面的评估。最终审核结果仍取决于应用的具体业务模式、销售地区,以及提交时有效的 App Review Guidelines。
备注:内容仅供参考。
