PAYATHON 2026

iOS app 如何销售服务 tokens 并避免 30% 抽成?

支付老李

结论

可以在 iOS App 内完成付款,通常不需要使用 Apple 的 In-App Purchase,也不会产生 Apple 针对数字内容收取的 30% 佣金。

前提是用户购买的内容必须在线下美容院实际消费,而不是 App 内的数字内容、虚拟功能或数字权益。美甲、按摩、理发,以及支付给美容师的小费,都属于线下服务,通常应通过 Apple Pay、银行卡或其他合规方式付款,无须使用 StoreKit。

付款产品不宜设计成可以自由储值、转让或兑换的通用 “tokens”。更稳妥的方案是让用户直接购买明确的美容服务,或者把余额限定为只能用于平台内线下美容服务的预付余额。App 最终能否通过审核,仍取决于实际功能、商品说明,以及审核人员对整个交易流程的判断。

为什么不需要使用 In-App Purchase

Apple 判断应当使用哪种支付方式时,关注的是用户最终买到了什么,而不是商品被称为 “token”、“credit” 还是”积分”。

如果购买内容在 App 内消费,例如:

  • 游戏金币、钻石或道具
  • 会员解锁功能
  • 视频、课程或电子书
  • AI 调用次数
  • App 内的增值功能

这类内容一般需要使用 In-App Purchase。

如果付款购买的是 App 外发生的实体商品或线下服务,例如:

  • 美甲
  • 按摩
  • 理发
  • 打车
  • 外卖
  • 酒店住宿
  • 线下服务人员的小费

这类交易通常应使用 Apple Pay 或银行卡,而不是 In-App Purchase。

因此,用户购买余额后只能到线下合作美容院消费,与购买游戏代币并不是同一类交易。但如果 tokens 还能购买 App 内的数字功能、兑换现金、转让给其他用户,或者在多个无关场景中流通,App 审核和金融合规风险都会明显增加。

推荐的产品设计

最稳妥的方案是取消抽象的 token 定价,直接使用法定货币展示和销售具体服务:

  • Salon A 美甲:¥200
  • Salon B 按摩:¥400
  • Salon C 理发:¥300
  • 小费:¥10、¥20 或用户自定义金额

用户选择美容院和服务后,直接在 App 内通过 Apple Pay 或银行卡付款。平台后端记录订单,服务完成后再与美容院结算。

这种结构更容易向审核人员解释清楚:App 是一个线下美容服务交易平台,用户付款不会解锁任何数字内容。

如果业务确实需要预付余额,应当限制其用途:

  • 余额只能支付平台合作美容院提供的线下服务
  • 不得用于购买 App 内的数字内容或功能
  • 用户之间不能转账
  • 不得宣传为投资品、虚拟货币或可交易资产
  • 不能兑换现金,退款应按照明确的规则处理
  • 余额、充值记录和实际服务订单应能一一对应
  • 商品页应明确说明余额的用途和限制

即使采用这些限制,预付余额仍可能受到当地储值卡、预付资金、退款、资金托管和反洗钱规定的约束。是否需要许可证,不能只根据 App Store 规则判断,应请熟悉经营地区法律的专业人士确认。

支付流程

建议使用以下交易流程:

  1. 用户在 App 内选择美容院和具体服务。
  2. App 从后端获取实时价格、币种和订单编号。
  3. App 展示服务内容、预约时间、取消政策和退款条件。
  4. 用户使用 Apple Pay 或银行卡付款。
  5. 支付服务商在后端确认付款结果。
  6. 后端将订单标记为已支付。
  7. 美容院完成服务后,平台再进行结算。
  8. 小费作为线下服务订单的一部分单独记录。

不要只根据客户端返回的”支付成功”结果发放余额或确认订单。后端应向支付服务商验证最终支付结果,最好同时处理带签名的 webhook 通知。

Apple Pay 示例

下面的代码只负责在 iOS 端发起 Apple Pay。支付凭据应发送到后端,再由后端交给支付服务商处理。

import PassKit

final class BeautyPaymentService: NSObject, PKPaymentAuthorizationControllerDelegate {
    private let merchantIdentifier = "merchant.com.example.beauty"

    func startPayment(
        salonName: String,
        serviceName: String,
        amount: Decimal,
        tip: Decimal
    ) {
        guard PKPaymentAuthorizationController.canMakePayments() else {
            return
        }

        let request = PKPaymentRequest()
        request.merchantIdentifier = merchantIdentifier
        request.countryCode = "CN"
        request.currencyCode = "CNY"
        request.merchantCapabilities = [.capability3DS]
        request.supportedNetworks = [.visa, .masterCard, .chinaUnionPay]

        let serviceItem = PKPaymentSummaryItem(
            label: "\(salonName) - \(serviceName)",
            amount: NSDecimalNumber(decimal: amount)
        )

        let tipItem = PKPaymentSummaryItem(
            label: "小费",
            amount: NSDecimalNumber(decimal: tip)
        )

        let total = amount + tip
        let totalItem = PKPaymentSummaryItem(
            label: "Beauty Platform",
            amount: NSDecimalNumber(decimal: total)
        )

        request.paymentSummaryItems = [
            serviceItem,
            tipItem,
            totalItem
        ]

        let controller = PKPaymentAuthorizationController(
            paymentRequest: request
        )
        controller.delegate = self
        controller.present()
    }

    func paymentAuthorizationController(
        _ controller: PKPaymentAuthorizationController,
        didAuthorizePayment payment: PKPayment
    ) async -> PKPaymentAuthorizationResult {
        do {
            try await sendPaymentToBackend(payment)
            return PKPaymentAuthorizationResult(status: .success, errors: nil)
        } catch {
            return PKPaymentAuthorizationResult(
                status: .failure,
                errors: [error]
            )
        }
    }

    func paymentAuthorizationControllerDidFinish(
        _ controller: PKPaymentAuthorizationController
    ) {
        controller.dismiss()
    }

    private func sendPaymentToBackend(_ payment: PKPayment) async throws {
        let paymentData = payment.token.paymentData

        var request = URLRequest(
            url: URL(string: "https://api.example.com/payments/apple-pay")!
        )
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")

        let body: [String: Any] = [
            "orderId": "server-generated-order-id",
            "paymentToken": paymentData.base64EncodedString()
        ]

        request.httpBody = try JSONSerialization.data(withJSONObject: body)

        let (_, response) = try await URLSession.shared.data(for: request)

        guard let httpResponse = response as? HTTPURLResponse,
              (200..<300).contains(httpResponse.statusCode) else {
            throw PaymentError.authorizationFailed
        }
    }
}

enum PaymentError: Error {
    case authorizationFailed
}

在生产环境中,orderId、金额、币种、服务内容和收款方信息都应由后端生成或验证。不能把客户端传来的金额作为最终扣款依据,否则金额可能被篡改。

Apple Pay 本身不是支付处理机构。还需要接入支持 Apple Pay 的支付服务商或收单机构,并完成 Merchant ID、证书、域名和支付处理配置。虽然这类交易不需要支付 Apple 的数字内容佣金,但仍会产生支付服务商手续费。

如果必须保留 tokens

可以在后端账户中记录 tokens 余额,但要明确限定交易用途。例如:

{
  "balanceId": "bal_123",
  "currency": "CNY",
  "availableAmount": 60000,
  "usageScope": "offline_beauty_services_only",
  "transferable": false,
  "cashRedeemable": false
}

金额应按最小货币单位保存,例如人民币分,避免使用浮点数:

struct ServiceOrder: Codable {
    let orderId: String
    let salonId: String
    let serviceId: String
    let amountInFen: Int
    let tipInFen: Int
}

如果界面仍然显示 “1 token”,必须明确说明它对应的金额、有效期、退款方式和使用范围。不要通过调整 token 与法定货币的兑换比例来掩盖实际价格,也不要把 token 描述成可能升值的资产。

一种折中方式是保留套餐概念,同时在结算页显示真实的货币金额。例如:“美甲套餐 2 次,¥398”。付款记录和发票仍以法定货币计价。

App Store 审核说明

提交审核时,可以在 App Review Notes 中直接说明业务模式,例如:

The app facilitates payments for beauty services performed in physical salons. Payments do not unlock digital content or in-app functionality. Purchased balance can only be redeemed for in-person services such as haircuts, manicures, and massages, and cannot be transferred or exchanged for cash.

还应提供:

  • 审核人员可使用的测试账号
  • 完整的预约和付款操作路径
  • 合作美容院及线下履约说明
  • 余额使用限制
  • 退款和取消政策
  • 对应的服务订单示例

不要把线下服务包装成数字商品来规避审核,也不要在同一个余额中混入 App 内会员、数字课程或其他数字权益。只要该余额能够购买数字内容,Apple 就可能要求相关部分使用 In-App Purchase,甚至要求重新设计整个支付流程。

需要特别注意的事项

使用 Apple Pay 或银行卡,只解决 App Store 支付规则方面的问题,并不代表业务自动满足金融、税务和消费者保护要求。预收用户资金可能涉及:

  • 储值卡或预付账户监管
  • 未消费余额的退款义务
  • 资金托管和商户结算
  • 发票及税务处理
  • 退款、拒付和欺诈风险
  • 小费的归属与税务记录
  • 美容院停业后的余额处理

Apple 的审核规则也可能调整。上线前应以提交审核时有效的最新版 App Review Guidelines 为准,重点核对线下商品和服务的支付条款。

优先采用”选择具体线下服务,再用 Apple Pay 或银行卡付款”的模式。只有当业务确实依赖预付余额时,才保留用途受限的 tokens,并提前准备审核说明,处理当地储值资金的合规问题。

备注:内容仅供参考。