PAYATHON 2026

奥地利 iOS app 如何实现 virtual wallet 实时分账

支付小周

明确结论

Apple In‑App Purchase 不适合处理“买家支付 €10,平台立即获得 €2,卖家获得 €8”这类 marketplace 分账。

更合适的做法是接入支持平台型支付的持牌支付服务商,例如:

  • Stripe Connect
  • Adyen for Platforms
  • Mangopay

支付服务商负责收款、卖家身份验证、资金分配和后续结算。一笔 €10 的付款可以在支付平台内部记为:平台服务费 €2,卖家应收 €8。

这里需要区分两种“实时”:

  • 实时分账:支付成功后,平台和卖家的余额立即分别记账。多数 marketplace 支付服务都能实现。
  • 实时到账银行账户:资金立即进入双方的银行账户。这通常无法实现,因为到账时间仍受卡组织清算、支付服务商可用余额和银行转账速度影响。

如果业务允许使用真实货币支付,建议直接以欧元交易,不要自行搭建可充值、可转让或可提现的 virtual credits。

Apple In‑App Purchase 是否适用

是否需要使用 In‑App Purchase,主要取决于用户购买的内容,而不是应用是否运行在 iOS 上。

数字内容或应用内数字服务

如果 credits 用来购买在 App 内消费的数字内容、功能、订阅或虚拟物品,通常需要使用 Apple 的 In‑App Purchase。

不过,In‑App Purchase 的款项由 Apple 收取,再按其结算周期支付给开发者。它不是可以自由配置的 marketplace 支付通道,无法在每笔交易中直接将 €2 支付给平台,再将 €8 支付到某个卖家的账户。

因此,即使使用 IAP,平台通常仍要建立自己的卖家结算系统,根据内部订单记录,之后再向卖家付款。这与实时资金分账并不是一回事。

实体商品或现实世界服务

如果用户购买的是实体商品、线下服务、交通或住宿等现实世界的商品和服务,通常应使用银行卡、Apple Pay 或其他外部支付方式,而不是 In‑App Purchase。

iOS 客户端可以展示支付界面,但订单创建、金额计算和分账都必须在服务端完成。

具体分类可能受商品类型、业务模式和 Apple 最新审核规则影响。上线前应结合实际商品核对最新版《App Review Guidelines》,不能仅靠使用“credits”来规避支付规则。

推荐架构

典型的 marketplace 资金流程如下:

  1. 卖家注册平台账户。
  2. 支付服务商对卖家执行 KYC/KYB、银行账户验证和制裁名单检查。
  3. 平台服务端创建金额为 €10 的支付订单。
  4. 支付服务商向买家收取 €10。
  5. 支付成功后,支付服务商在内部账本中分配款项:
    • 卖家应收:€8
    • 平台服务费:€2
  6. 支付服务商按照结算计划,将卖家的款项转入其银行账户。
  7. 平台通过 webhook 确认最终支付状态,并处理退款、拒付和分账冲正。

所有金额都必须由服务端计算。iOS 客户端不能决定平台抽成、卖家账户或最终支付金额,否则这些参数可能被篡改。

使用 Stripe Connect 的实现示例

以下示例使用 Stripe Connect 的 destination charge。假设:

  • 总金额:€10
  • 平台服务费:€2
  • 卖家 Connected Account:acct_xxx
  • 金额采用最小货币单位,因此 €10 写作 1000

服务端创建 PaymentIntent:

import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

const paymentIntent = await stripe.paymentIntents.create({
  amount: 1000,
  currency: "eur",
  automatic_payment_methods: {
    enabled: true
  },
  application_fee_amount: 200,
  transfer_data: {
    destination: "acct_xxx"
  },
  metadata: {
    order_id: "order_123",
    seller_id: "seller_456"
  }
});

return {
  clientSecret: paymentIntent.client_secret
};

在这个模型中,€8 分配到卖家的 Connected Account,€2 作为平台的 application fee。银行卡处理费、退款费用和拒付责任由谁承担,则取决于 Connect charge 类型、账户类型和服务商合同。因此,平台的实际净收入不一定正好是 €2。

iOS 客户端可以使用 Stripe PaymentSheet 完成付款:

import StripePaymentSheet
import UIKit

final class CheckoutViewController: UIViewController {
    private var paymentSheet: PaymentSheet?

    func preparePayment(clientSecret: String) {
        var configuration = PaymentSheet.Configuration()
        configuration.merchantDisplayName = "Example Marketplace"

        paymentSheet = PaymentSheet(
            paymentIntentClientSecret: clientSecret,
            configuration: configuration
        )
    }

    func pay() {
        guard let paymentSheet else {
            return
        }

        paymentSheet.present(from: self) { result in
            switch result {
            case .completed:
                print("Payment submitted")
            case .canceled:
                print("Payment canceled")
            case .failed(let error):
                print("Payment failed: \(error)")
            }
        }
    }
}

即使客户端显示 .completed,也不能立即将订单永久标记为已付款。服务端应以经过签名验证的 webhook 为准:

app.post(
  "/stripe/webhook",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const signature = req.headers["stripe-signature"];

    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        signature,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch {
      return res.status(400).send("Invalid webhook signature");
    }

    switch (event.type) {
      case "payment_intent.succeeded": {
        const paymentIntent = event.data.object;

        await markOrderAsPaid(
          paymentIntent.metadata.order_id,
          paymentIntent.id
        );
        break;
      }

      case "payment_intent.payment_failed": {
        const paymentIntent = event.data.object;

        await markOrderAsFailed(
          paymentIntent.metadata.order_id
        );
        break;
      }
    }

    res.sendStatus(200);
  }
);

订单更新逻辑必须支持幂等处理,因为 webhook 可能重复发送,也可能乱序到达。

如果需要先控制卖家收款时间

如果平台需要先确认交易已经完成,再把钱分配给卖家,可以考虑 separate charges and transfers:

  1. 平台先完成买家付款。
  2. 服务端等待发货、履约或争议期条件满足。
  3. 条件满足后创建 transfer,将卖家应收金额转入其 Connected Account。
  4. 平台保留约定的佣金。

这种方式可以更灵活地控制分账时间,但不能自行称为“托管”或“escrow”,除非支付服务商及相关合同明确支持这种法律结构。支付服务商通常也会限制平台持有资金的期限。

如果只是不希望卖家立即提现,也可以调整卖家账户的 payout schedule。不过,“余额已分配”和“银行已到账”仍然是两个不同的状态。

是否应该使用 virtual credits

如果 credits 是封闭、不可提现且不可转让的内部计量单位,自建积分账本会相对简单。例如,平台赠送的促销积分只能抵扣平台自身提供的服务。

出现以下情况时,合规风险会明显增加:

  • 用户可以用真实货币充值 credits;
  • credits 可以支付给多个独立卖家;
  • 卖家可以将 credits 兑换为欧元;
  • credits 可以在用户之间转让;
  • 平台长期保存用户余额;
  • 用户可以要求提现或退款。

这种模式可能涉及电子货币、支付服务、客户资金保护、反洗钱,以及奥地利和欧盟 PSD2 的相关要求。将余额命名为“credits”并不能绕过监管。

更稳妥的方式是由持牌支付服务商持有和分配真实资金。应用数据库只记录订单、平台费用和支付服务商交易 ID,不自行保存可以兑付的货币余额。

奥地利业务还需要处理什么

面向奥地利用户和卖家时,至少需要考虑:

  • 使用 EUR 作为交易和展示货币;
  • PSD2 要求的 Strong Customer Authentication,3‑D Secure 流程通常由 PaymentSheet 或支付服务商完成;
  • 卖家的 KYC/KYB 和受益所有人信息;
  • 平台服务费的 VAT 和发票处理;
  • 卖家收入、平台佣金和退款的会计记录;
  • GDPR 对支付数据、身份资料和订单数据处理的要求;
  • 退款时是否需要同步撤销卖家转账;
  • 拒付、负余额和欺诈损失由哪一方承担;
  • 支付服务商是否支持奥地利及目标卖家所在国家。

不要在自己的服务器上保存完整银行卡号或 CVC。应使用支付服务商提供的 SDK、tokenization 和托管支付组件,缩小 PCI DSS 的适用范围。

最终建议

如果交易对象是实体商品或现实世界服务,可以采用:

iOS PaymentSheet/Apple Pay + 服务端 + Stripe Connect、Adyen for Platforms 或 Mangopay。

如果交易对象是 App 内数字内容,应先确认 Apple 对该业务类别的支付要求。即使必须使用 In‑App Purchase,也不能将它当作卖家的实时分账服务,卖家结算仍需单独设计。

综合实施和合规成本,直接使用欧元,并由 marketplace 支付服务商完成分账,通常比自行发行 virtual credits 更合适。正式上线前,还需要由支付服务商和熟悉奥地利、欧盟支付法规的专业顾问确认资金流及合同结构。

备注:内容仅供参考。