Third-party payment gateway 与 In-App Purchase 如何选择?
结论
如果这些 license 用来解锁应用内的数字功能,例如创建孩子或看护人账号,并允许他们登录和使用服务,那么在 iOS 应用内销售时,原则上需要使用 Apple 的 In-App Purchase(IAP)。
如果直接在 iOS App 内接入 Stripe、PayPal 等 third-party payment gateway,或通过 WebView 完成付款,App 审核被拒的风险较高。购买后的权益即使能在 iOS、Android 和 Web 之间通用,也不代表可以绕过 IAP。
比较稳妥的做法是:
- 在 iOS App 内购买:使用 Apple IAP。
- 在 Android App 内购买:遵守 Google Play 的计费政策。
- 在 Web portal 购买:继续使用 third-party payment gateway。
- 各渠道的购买结果:统一写入服务端的 license entitlement,由服务端管理跨平台权益。
跨平台同步和支付渠道并不矛盾。用户通过 Apple IAP 购买后,服务端仍可将 license 绑定到业务账号,让用户在 Android 和 Web 上使用。
为什么通常必须使用 IAP
判断依据不是商品是否叫 “license”,而是用户实际买到了什么。
这里的 license 可以让用户:
- 创建额外的辅助用户账号;
- 允许这些账号登录应用;
- 使用应用提供的数字服务。
这些权益通常属于应用内消费的数字内容或数字功能。只要购买入口位于 iOS App 内,Apple 一般就会要求使用 IAP 完成交易。
下面这些理由通常不能构成 IAP 豁免:
- 服务同时支持 iOS、Android 和 Web;
- 账号和数据可以在多个平台同步;
- 用户可能会在其他平台使用购买的 license;
- Web 端已经接入 Stripe 等支付服务;
- 商品名称是 license、seat、credit 或 membership。
审核人员关注的是权益的实际用途,而不是产品名称或背后的技术实现。
哪些实现容易被拒绝
如果没有适用的地区政策、特殊 entitlement 或明确例外,以下设计的审核风险较高:
- 在 iOS App 中嵌入 Stripe Checkout;
- 使用 WebView 打开 Web portal 的付款页面;
- 提供 “购买 license” 按钮并跳转到外部支付页面;
- 在 App 内引导用户前往网站付款;
- 通过二维码、客服提示或其他方式规避 IAP;
- 将购买按钮伪装成账号管理或升级入口。
外部链接、替代支付和相关提示是否允许,可能取决于 App Store storefront、应用类别以及当地监管要求。某个地区允许的实现不能直接用于全球版本。提交审核前,应根据目标发行地区核对届时有效的 App Store Review Guidelines、entitlement 要求和 StoreKit 文档。
推荐的业务架构
支付渠道和 license 权益应分开设计:
Apple IAP ───────┐
Google Play ─────┼──> Payment Verification ──> License Entitlement Service
Web Gateway ─────┘ │
├── iOS
├── Android
└── Web
服务端统一维护权益记录,例如:
{
"accountId": "parent_123",
"totalLicenses": 2,
"assignedLicenses": [
{
"licenseId": "lic_001",
"assignedTo": "kid_456",
"status": "active"
},
{
"licenseId": "lic_002",
"assignedTo": "caretaker_789",
"status": "active"
}
]
}
license 可以来自不同渠道,但权限判断应保持一致:
{
"entitlementId": "ent_10001",
"accountId": "parent_123",
"quantity": 2,
"source": "apple_iap",
"sourceTransactionId": "transaction_id",
"status": "active"
}
不要让客户端自行增加 license 数量。客户端只负责发起购买,服务端验证交易成功后再发放权益。
iOS 端的实现步骤
1. 在 App Store Connect 配置商品
商品类型要根据业务模式选择:
- 如果 license 永久有效,且每份只能购买一次,可以研究 non-consumable。但它不适合通过反复购买同一个商品来增加数量。
- 如果用户可以多次购买 license 包,通常需要按数量设计不同商品,也可以根据实际模型评估 consumable。
- 如果 license 按月或按年持续有效,通常更接近 auto-renewable subscription。
商品类型要与退款、恢复购买、重复购买和到期规则相匹配,不能只考虑哪种类型接入更方便。
例如,可以配置:
com.example.licenses.1
com.example.licenses.2
com.example.licenses.5
2. 使用 StoreKit 2 发起购买
import StoreKit
enum PurchaseError: Error {
case productNotFound
case verificationFailed
}
func purchaseLicensePack(productId: String) async throws {
let products = try await Product.products(for: [productId])
guard let product = products.first else {
throw PurchaseError.productNotFound
}
let result = try await product.purchase()
switch result {
case .success(let verification):
guard case .verified(let transaction) = verification else {
throw PurchaseError.verificationFailed
}
try await sendTransactionToServer(
transactionId: String(transaction.id),
productId: transaction.productID
)
await transaction.finish()
case .pending:
// 等待 Ask to Buy 或其他待处理付款流程
break
case .userCancelled:
break
@unknown default:
break
}
}
func sendTransactionToServer(
transactionId: String,
productId: String
) async throws {
// 通过 HTTPS 将交易信息发送到业务服务端。
// 服务端验证交易后再增加 license entitlement。
}
客户端显示购买成功,并不意味着服务端可以直接发放 license。服务端仍需验证交易,并保证处理过程具备幂等性。
3. 服务端验证并发放权益
服务端可以按以下流程处理:
接收交易信息
↓
验证 Apple 交易
↓
检查 transactionId 是否已经处理
↓
确定 productId 对应的 license 数量
↓
写入 entitlement
↓
返回最新 license 状态
伪代码示例:
async function grantAppleLicenses({ accountId, transaction }) {
const verifiedTransaction = await verifyAppleTransaction(transaction);
if (!verifiedTransaction.valid) {
throw new Error("Invalid Apple transaction");
}
const transactionId = verifiedTransaction.transactionId;
return database.transaction(async (db) => {
const existing = await db.purchases.findByTransactionId(transactionId);
if (existing) {
return db.entitlements.findByAccountId(accountId);
}
const quantity = getLicenseQuantity(
verifiedTransaction.productId
);
await db.purchases.insert({
accountId,
transactionId,
productId: verifiedTransaction.productId,
source: "apple_iap"
});
await db.entitlements.incrementLicenses(accountId, quantity);
return db.entitlements.findByAccountId(accountId);
});
}
生产环境还要处理退款、撤销、订阅续期、订阅到期和 App Store Server Notifications。具体字段及验证接口应以接入时的 Apple 官方文档为准。
已在 Web 购买的用户怎么办
可以让用户登录 iOS App,并使用此前在 Web 上合法取得的权益。通常没有必要让用户重复购买同一份 license。
需要区分以下两种行为:
- “登录并使用已有权益”:通常属于正常的跨平台服务场景。
- “在 iOS App 内引导用户去 Web 购买”:可能受到外部购买链接和支付引导规则的限制。
因此,iOS App 可以从服务端读取已有 license:
GET /v1/me/entitlements
Authorization: Bearer <access_token>
返回:
{
"licenses": {
"total": 2,
"assigned": 1,
"available": 1
}
}
App 内能否显示外部购买入口,则要结合应用类别、销售地区和当时适用的规则单独判断。
可能适用 third-party payment gateway 的情况
如果用户购买的是现实世界中的商品或服务,例如交通、送餐、线下护理或现场课程,通常不属于 IAP 的典型适用范围。
某些企业服务、特定类型的应用,以及部分地区的替代支付规则,也可能有例外。不过,当前描述中的 license 面向家长等普通终端用户,用于解锁应用内账号和数字服务,因此更接近消费者数字权益。不能仅凭 “多平台使用” 就将其认定为企业服务或线下服务。
如果这些账号实际上由学校、医院或企业统一采购,而 iOS App 只供签约机构的人员登录,结论可能不同。这种情况需要重新评估销售对象、合同关系、应用内是否提供购买入口,以及服务的实际交付方式。
审核时应准备的说明
在 App Review Notes 中应清楚说明:
- 主账号如何购买 license;
- 一个 license 能创建哪类辅助账号;
- 辅助账号如何登录;
- iOS、Android 和 Web 如何共享 entitlement;
- iOS 购买使用了哪些 IAP 商品;
- 测试账号和完整的测试路径;
- 已有 Web 用户如何访问既有权益;
- App 内是否存在外部付款入口。
不要只写 “license 可以跨平台使用,所以没有使用 IAP”。这种解释没有回答 Apple 对数字功能支付方式的审核问题。
注意事项
- 通过 IAP 购买的权益可以绑定业务账号并同步到其他平台,无需为了跨平台访问而改用 third-party payment gateway。
- license 的分配、回收和剩余数量必须由服务端管理。
- 交易验证必须具备幂等性,以免重复发放权益。
- 需要明确退款后如何处理已经创建的辅助账号,避免直接删除数据,造成无法恢复的结果。
- 对于订阅型 license,需要定义到期后的宽限期、降级方式和辅助账号状态。
- “恢复购买” 只能恢复可恢复的 Apple 商品,并不等于恢复全部 Web 或 Android 订单。跨渠道权益应通过登录业务账号恢复。
- Android 端也有自己的数字商品支付政策。iOS 使用 IAP,并不代表 Android 可以任意使用 third-party payment gateway。
- App Store 政策和地区规则可能调整。最终方案应根据计划上线时的发行地区和最新官方规则确认,必要时在正式开发前向 Apple 咨询具体业务模型。
备注:内容仅供参考。
