小游戏 iOS 端如何实现支付,支付宝支付能否绕过苹果?
明确结论
不能把接入支付宝当作”绕过苹果支付”的办法。
在 iOS 端销售游戏币、会员、道具、关卡等数字内容时,小游戏通常需要使用宿主平台提供的 iOS 支付能力。如果宿主平台没有开放相应能力,就不应自行拉起支付宝、展示付款二维码、跳转网页支付,也不能通过客服消息引导用户到站外购买。
实物商品、线下服务等非数字消费通常可以使用支付宝等第三方支付,但同时要遵守 App Store、小游戏宿主平台和支付机构的规则。
苹果的具体政策可能随应用类型、用户所在地区、店面以及开发者获得的 entitlement 而有所不同。上线前应查阅当前版本的 App Review Guidelines 和宿主平台最新文档。
为什么支付宝不能用于规避苹果支付
苹果判断支付方式是否合规,主要看用户购买的内容,而不是支付渠道叫什么:
- 游戏币、皮肤、道具、会员、去广告和关卡属于数字商品。
- 外卖、门票、酒店、实体礼品等属于在应用外消费的商品或服务。
- 募捐、打赏、点对点服务等场景各有适用条件,不能直接套用普通商品的支付流程。
如果用户在 iOS 小游戏中付款后立即获得数字权益,即使订单通过支付宝完成,也可能被认定为绕过应用内购买机制。以下做法风险较高:
- 直接集成支付宝 SDK 拉起支付。
- 在小游戏中展示支付宝二维码。
- 通过 H5、Safari、URL Scheme 或另一个 App 完成数字商品付款。
- 提示用户”前往官网充值更便宜”。
- 隐藏支付入口,等审核通过后再用远程配置开启。
- 把游戏币包装成”实体商品”,但实际只交付数字权益。
这些做法可能造成审核被拒,也可能导致应用下架、支付能力关闭或开发者账号受到处罚。
合规实现方案
方案一:使用宿主平台提供的 iOS 支付接口
小游戏一般运行在支付宝、微信或其他宿主 App 中,前端通常无法自由集成原生 StoreKit。可以按以下流程接入:
- 在宿主平台后台配置数字商品。
- 由小游戏服务端创建业务订单。
- 前端调用宿主平台正式开放的 iOS 支付 API。
- 支付完成后,由服务端查询或验证支付结果。
- 验证成功后发放游戏权益。
- 对支付通知进行验签、去重和补单处理。
各小游戏平台的 API 名称、商品配置方式和 iOS 开放范围并不相同。下面的代码只是抽象示例,不能据此猜测接口名称。实际接入时,应替换为目标平台文档中提供的真实 API:
async function purchaseDigitalProduct(productId) {
const order = await request("/api/orders", {
method: "POST",
body: JSON.stringify({
productId,
platform: "ios"
})
});
// 此处必须替换为宿主平台真实、正式开放的支付 API
const paymentResult = await hostPayment.purchase({
orderId: order.orderId,
paymentToken: order.paymentToken
});
// 客户端返回成功不能作为发货依据
const result = await request(
`/api/orders/${order.orderId}/confirm`,
{ method: "POST" }
);
if (result.status === "PAID") {
refreshUserEntitlements();
}
return paymentResult;
}
如果宿主平台明确说明 iOS 暂不支持小游戏数字商品支付,就只能暂时关闭购买入口,或者改用平台允许的其他产品设计,不能自行增加支付宝通道。
方案二:原生 App 使用 Apple In-App Purchase
如果所谓的小游戏其实是自有原生 App 中的游戏模块,而不是运行在第三方宿主平台中的小游戏,可以通过 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、originalID、productID 等信息发送给服务端验证
try await reportTransactionToServer(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
}
生产环境还要处理交易更新、恢复购买、退款、撤销和服务端通知,不能只依赖一次 purchase() 调用。
方案三:非数字商品使用支付宝
如果小游戏销售的是实物商品或需要线下履约的服务,可以在平台规则允许的情况下使用支付宝。订单应由服务端创建,再向客户端返回平台要求的支付参数:
app.post("/api/orders", async (req, res) => {
const product = await loadProduct(req.body.productId);
if (product.type !== "PHYSICAL_GOODS") {
return res.status(400).json({
error: "Third-party payment is unavailable for digital goods on iOS"
});
}
const order = await createOrder({
userId: req.user.id,
productId: product.id,
amount: product.price,
channel: "ALIPAY"
});
const paymentParams = await createAlipayPayment(order);
res.json({
orderId: order.id,
paymentParams
});
});
商品分类必须与实际履约内容一致。即使把订单写成实物商品,只要付款后发放的是游戏币,它仍然属于数字商品。
服务端验证与发货
无论使用哪种支付方式,都不能根据客户端返回的”支付成功”结果直接发货。订单可以采用以下状态流转:
CREATED -> PAYING -> PAID -> DELIVERED
\-> REFUNDED
服务端至少需要检查以下内容:
- 订单属于当前用户,商品和金额与支付信息一致。
- 支付凭证真实有效,并且与应用、商品和环境相匹配。
- 同一个交易号只能绑定一个业务订单。
- 发货操作具备幂等性,重复通知不会造成重复发放。
- 对未完成订单支持主动查询和补单。
- 发生退款或撤销后,能够回收权益,或按照业务规则冻结账户。
- 日志不能记录完整的支付凭证、密钥和用户敏感信息。
可以通过唯一交易号约束实现幂等发货:
CREATE UNIQUE INDEX uk_payment_transaction
ON payment_order(transaction_id);
发货接口还应在同一个数据库事务中更新订单状态和用户权益,避免其中一项成功而另一项失败。
上线前注意事项
- 先判断商品属于数字内容,还是实物商品或线下服务。
- 确认小游戏宿主平台是否向 iOS 开放数字商品支付,以及适用的主体、类目和地区。
- 不要在前端保存支付宝私钥、Apple 密钥或服务端签名密钥。
- 不能只靠客户端系统判断限制支付方式,服务端也要校验平台和商品类型。
- 为取消付款、支付处理中、重复付款、掉单、退款和未成年人限制设计完整的处理流程。
- 消耗型商品通常不能按”恢复购买”处理,永久权益和订阅则需要考虑跨设备恢复。
- 审核环境和正式环境应使用同一套支付逻辑,不能通过远程配置规避审核。
- 外部购买链接能否使用,取决于地区、应用类型和当前政策。没有明确适用资格时,不应默认允许使用。
因此,iOS 小游戏销售数字商品时,应使用宿主平台认可的苹果支付链路;只有销售非数字商品时,才适合考虑支付宝。如果平台没有提供 iOS 数字商品支付能力,就应调整功能或暂时关闭购买入口,不能通过支付宝绕过。
备注:内容仅供参考。
