奥地利 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 资金流程如下:
- 卖家注册平台账户。
- 支付服务商对卖家执行 KYC/KYB、银行账户验证和制裁名单检查。
- 平台服务端创建金额为 €10 的支付订单。
- 支付服务商向买家收取 €10。
- 支付成功后,支付服务商在内部账本中分配款项:
- 卖家应收:€8
- 平台服务费:€2
- 支付服务商按照结算计划,将卖家的款项转入其银行账户。
- 平台通过 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:
- 平台先完成买家付款。
- 服务端等待发货、履约或争议期条件满足。
- 条件满足后创建 transfer,将卖家应收金额转入其 Connected Account。
- 平台保留约定的佣金。
这种方式可以更灵活地控制分账时间,但不能自行称为“托管”或“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 更合适。正式上线前,还需要由支付服务商和熟悉奥地利、欧盟支付法规的专业顾问确认资金流及合同结构。
备注:内容仅供参考。
