App 订阅必须使用 Apple IAP 还是可接第三方支付?
结论
如果 Premium 订阅提供的是金融账户服务,例如减免用户之间真实资金转账的手续费、提高转账额度,通常不属于 Apple IAP 覆盖的数字内容,可以使用银行卡、Stripe 等第三方支付渠道。用户之间的真实资金转账及相关手续费也不应通过 IAP 处理。
不确定的部分是“每月查询一次金融机构信息”。如果查询属于金融服务的一部分,通常可以包含在账户套餐中;如果 Apple 认为它是在 App 内购买和使用的独立数字功能,则可能要求该订阅使用 IAP。
Revolut 可以使用自己的支付体系,并不是因为它拥有普遍适用的“免抽成特权”,而是因为它销售和提供的是受监管的银行、支付及金融账户服务,不是普通 App 中的数字内容或虚拟权益。
审核结果仍会受到业务资质、订阅实际权益、购买入口所在地区及审核人员判断的影响。App Store 规则也可能调整,上线前需要重新核对最新版 App Review Guidelines,特别是第 3.1.1、3.1.3 条及金融服务相关条款。
如何判断是否必须使用 IAP
Apple 通常会根据用户实际购买的内容判断支付方式,而不是看产品是否被称为“订阅”。
一般需要使用 IAP 的内容包括:
- App 内解锁的高级功能;
- 数字内容、会员内容或云端功能;
- 可在 App 内消费的次数、积分或虚拟货币;
- 仅在 App 内产生价值的订阅权益。
通常不使用 IAP 的内容包括:
- 真实商品;
- 在线下或 App 外消费的服务;
- 银行账户、支付、汇款等真实金融服务;
- 真实资金的存入、转出及相关金融交易费用。
可以据此拆分当前业务:
| 收费项目 | 更可能采用的支付方式 | 原因 |
|---|---|---|
| 用户间真实资金转账 | 金融支付系统 | 属于真实资金流转,IAP 不适合处理 |
| 每笔转账的 $1 手续费 | 从账户余额扣除或第三方支付 | 属于金融交易费用 |
| Premium 减免转账手续费 | 第三方支付可能可行 | 权益直接关联真实金融服务 |
| 每月一次金融信息查询 | 需要结合查询内容判断 | 可能被认定为金融服务,也可能被视为数字功能 |
| 单独销售查询次数 | 更可能被要求使用 IAP | 很像在 App 内消费的数字次数 |
判断的重点是 Premium 的主要价值。如果套餐主要提供“免转账手续费的金融账户”,定期查询只是附属服务,那么使用第三方支付的理由较充分。如果用户购买 Premium 主要是为了在 App 内查看更多数据或增加查询次数,它就更接近数字订阅。
建议的处理步骤
1. 明确资金和服务性质
准备一份业务说明,至少写清楚以下内容:
- 转账的是现实中的法定货币,还是 App 内积分;
- 谁持有用户资金;
- 由哪家持牌机构执行转账;
- $1 是金融交易手续费,还是购买数字额度的费用;
- 查询的具体数据是什么;
- 查询结果用于账户管理、合规或信用服务,还是仅供用户阅读;
- Premium 的主要权益及定价依据。
如果所谓的“转账”实际上是购买 App 内积分,再将积分转给其他用户,Apple 很可能会把它视为数字商品体系,而非金融转账。
2. 不要把查询次数设计成虚拟商品
如果使用第三方支付,产品描述应尽量避免以下表述:
- “购买 12 次查询”;
- “充值查询点数”;
- “解锁 App 内查询功能”。
更合适的做法是将其定义为金融账户计划,例如:
- 免费计划:收取标准转账手续费,每半年生成一次账户报告;
- Premium 计划:免除指定转账手续费,每月更新一次账户报告。
这种设计不能保证通过审核,但能更准确地说明服务性质,降低它被理解成数字消耗品的可能性。
3. 在提交审核前主动说明
可以在 App Review Notes 中写明:
The Premium plan is a financial account service.
It covers fees associated with real-money transfers between users and
periodic information requests made to our financial institution partner.
It does not sell digital content, virtual currency, consumable credits,
or standalone in-app features.
Payments are processed by our payment provider as part of the regulated
financial service.
同时提供测试账号、测试流程,以及相关金融机构或支付机构的信息。审核人员未必能仅凭 App 界面理解完整的业务模式。
4. 准备可切换的订阅架构
由于审核结果可能不同,最好将“支付来源”和“权益状态”分开处理。无论用户通过第三方支付,还是以后改用 IAP,App 都只读取后端返回的权益状态。
例如:
{
"plan": "premium",
"status": "active",
"provider": "stripe",
"benefits": {
"transfer_fee_waived": true,
"financial_query_interval_days": 30
},
"current_period_end": "2026-09-30T23:59:59Z"
}
iOS 客户端不应根据本地付款结果永久开启权益,而应向服务器查询订阅状态:
struct SubscriptionStatus: Decodable {
let plan: String
let status: String
let provider: String
let benefits: Benefits
struct Benefits: Decodable {
let transferFeeWaived: Bool
let financialQueryIntervalDays: Int
enum CodingKeys: String, CodingKey {
case transferFeeWaived = "transfer_fee_waived"
case financialQueryIntervalDays = "financial_query_interval_days"
}
}
}
func canMakeFreeTransfer(_ subscription: SubscriptionStatus) -> Bool {
subscription.status == "active"
&& subscription.benefits.transferFeeWaived
}
如果以后被要求接入 IAP,只需增加 provider: "app_store" 的验证流程,不必重写业务层。
使用第三方支付时的实现方式
支付会话应由后端创建,不能信任客户端提交的价格或套餐名称。
POST /api/subscriptions/checkout
Authorization: Bearer <access-token>
Content-Type: application/json
{
"plan": "premium"
}
后端根据预先配置的产品生成支付会话:
app.post("/api/subscriptions/checkout", authenticate, async (req, res) => {
if (req.body.plan !== "premium") {
return res.status(400).json({ error: "invalid_plan" });
}
const session = await paymentProvider.createCheckoutSession({
customerId: req.user.paymentCustomerId,
priceId: process.env.PREMIUM_PRICE_ID,
metadata: {
userId: req.user.id
}
});
res.json({ checkoutUrl: session.url });
});
订阅状态应通过支付服务商的服务器回调更新:
app.post("/api/payment/webhook", verifyWebhook, async (req, res) => {
const event = req.paymentEvent;
if (event.type === "subscription.activated") {
await subscriptions.activate({
userId: event.metadata.userId,
provider: event.provider,
externalSubscriptionId: event.subscriptionId
});
}
if (
event.type === "subscription.canceled" ||
event.type === "subscription.payment_failed"
) {
await subscriptions.updateStatus(
event.subscriptionId,
event.type === "subscription.canceled" ? "canceled" : "past_due"
);
}
res.sendStatus(200);
});
实际使用的事件名称、签名校验方式和字段由支付服务商决定,不能直接照搬示例中的占位接口。
如果审核要求使用 IAP
如果 Apple 明确认定 Premium 销售的是 App 内数字功能,就需要使用 StoreKit 自动续期订阅,并由服务器验证交易。
import StoreKit
func purchasePremium(product: Product) async throws {
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
await sendTransactionToServer(transaction)
await transaction.finish()
case .pending:
break
case .userCancelled:
break
@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
}
服务器需要处理续订、退款、撤销和账单宽限期等状态,不能在首次购买成功后就将用户永久标记为 Premium。
Apple 的实际佣金也不一定固定为 30%。费率可能受开发者计划、订阅周期、地区及当时生效政策的影响,因此不能以“所有订阅一律收取 30%”为实现前提。
需要特别注意的事项
- 不要使用 IAP 处理真实资金转账、存款或提现。
- 不要将通过 IAP 购买的虚拟货币兑换成现金,或用于用户之间的真实资金结算。
- 使用第三方支付不代表可以在所有地区的 iOS App 中任意放置购买按钮或外部支付链接。购买入口、跳转方式和提示文案可能受到 storefront 及授权条件的限制。
- 即使用户只能在网站购买,App 内只能登录并使用已有账户,也需要核对当前地区关于外部购买提示和链接的规则。
- Premium 同时包含金融服务和数字功能时,Apple 可能将整个套餐视为混合订阅。最好不要把独立数字内容与金融账户权益捆绑在同一个第三方支付套餐中。
- 金融业务还涉及当地支付牌照、KYC、AML、资金托管、退款和消费者保护要求。通过 App Store 审核并不等于已经满足金融监管要求。
- Revolut 的业务模式、牌照和审核安排,不能作为其他 App 必然获批的依据。
针对所描述的方案,较稳妥的做法是让真实转账和手续费使用金融支付体系,将 Premium 明确定义为金融账户套餐,并把金融查询纳入账户服务。提交审核时,应主动说明业务模式和资金流,同时保留接入 StoreKit 的技术能力,以便在 Apple 将查询权益认定为 App 内数字服务时进行调整。
备注:内容仅供参考。
