Mobile app 使用 payment gateway 时谁承担交易手续费?
明确结论
在常见的 payment gateway 模式下,交易手续费通常由 merchant(商户)向支付服务商支付,不是由 consumer(消费者)直接支付。支付服务商一般会先从订单款项中扣除手续费,再将剩余金额结算给商户。
例如,用户支付 ¥100,费率为 2.9% + ¥0.30:
交易手续费 = 100 × 2.9% + 0.30 = ¥3.20
商户实收 = 100 - 3.20 = ¥96.80
商户可以提高商品价格、收取服务费,或者在合规的前提下收取银行卡附加费,从而将成本间接或直接转给消费者。能否这样做,要看所在地法律、银行卡组织规则、支付服务商条款和应用商店政策。
没有一种通用于所有订单的“完全免费”支付方案。即使支付网关本身不收费,银行转账、退款、拒付、汇率转换、税务处理和系统运维仍可能产生费用。
一般支付流程
Mobile app 中常见的订单流程是:
- App 将商品和数量提交给 merchant server。
- Server 根据数据库中的商品价格重新计算订单金额。
- Server 创建订单,并调用 payment gateway 创建支付请求。
- App 通过支付网关提供的 SDK 或托管支付页面完成付款。
- Payment gateway 向用户返回支付结果,同时通过 webhook 通知 server。
- Server 验证 webhook 签名并检查订单状态。
- 确认付款成功后,server 才将订单标记为已支付并安排发货。
- Payment gateway 扣除相关费用,再把剩余款项结算给商户。
不要看到 App 显示“支付成功”就直接更新订单。客户端返回结果可能被伪造,也可能因断网而丢失。订单的最终状态应以服务端查询结果或通过签名验证的 webhook 为准。
手续费实际上由谁承担
从合同和结算关系看,手续费通常由 merchant 承担。merchant 与 payment gateway 签订服务协议,支付网关根据交易金额、支付方式和商户类别收费。
但在实际经营中,这笔成本不一定全部由商户消化。商户可以:
- 将支付成本计入商品售价。
- 对所有订单统一收取服务费。
- 只对特定支付方式收取附加费。
- 为成本较低的付款方式提供折扣。
- 设置最低订单金额,减少固定手续费对小额订单的影响。
一笔交易的支付成本可以写成:
fee = amount × percentage_rate + fixed_fee
如果商户希望扣除手续费后仍收到目标净额 net_amount,理论售价可以这样计算:
gross_amount = (net_amount + fixed_fee) / (1 - percentage_rate)
假设目标净额为 ¥100,费率为 2.9% + ¥0.30:
gross_amount = (100 + 0.30) / (1 - 0.029)
≈ ¥103.30
实际计算时,应使用货币最小单位取整,并遵循支付渠道的计费规则。有些网关还会对税费、跨境交易、货币转换、退款或拒付另行收费。
能否把手续费直接转给消费者
从技术上说,可以在订单中增加一项费用,但法律和合同未必允许。
实施前至少要确认以下事项:
- 当地法律是否允许针对某种支付方式收取附加费。
- 银行卡组织是否限制附加费比例、适用卡种或展示方式。
- Payment gateway 的服务条款是否允许收取这类费用。
- 是否必须在结账前明确展示费用。
- 附加费是否需要计税。
- 退款时是否必须同时退还附加费。
- Apple App Store 或 Google Play 是否允许当前商品类型使用这种支付方式。
信用卡、借记卡和银行转账可能适用不同规则。部分地区禁止或严格限制银行卡附加费,有些规则还要求附加费不得超过实际处理成本。因此,不能简单地对所有支付方式统一加收“网关手续费”。
较稳妥的方式是将正常支付成本计入商品价格,或者为银行转账等成本较低的支付方式提供折扣。最终方案仍要结合业务所在地和支付服务商协议确定。
推荐的订单金额设计
所有金额都应由 server 计算。App 只能提交商品标识、数量和用户选择,不能直接决定最终付款金额。
下面是一个简化的 Node.js 示例:
function calculateOrder({ items, paymentMethod }) {
const subtotal = items.reduce((sum, item) => {
return sum + item.unitPriceMinor * item.quantity;
}, 0);
const shipping = calculateShipping(items);
const tax = calculateTax({ subtotal, shipping });
const surcharge = paymentMethod === "credit_card"
? calculatePermittedSurcharge(subtotal)
: 0;
return {
subtotal,
shipping,
tax,
surcharge,
total: subtotal + shipping + tax + surcharge,
currency: "CNY"
};
}
unitPriceMinor 使用货币最小单位。例如,人民币 ¥19.90 应保存为 1990 分,以避免浮点数误差。
服务端创建支付请求时,应使用已保存的订单金额:
app.post("/api/orders/:orderId/payments", async (req, res) => {
const order = await orderRepository.findById(req.params.orderId);
if (!order || order.status !== "pending") {
return res.status(409).json({ error: "Order cannot be paid" });
}
const payment = await paymentGateway.createPayment({
amount: order.totalMinor,
currency: order.currency,
reference: order.id,
idempotencyKey: `payment:${order.id}`
});
await paymentRepository.save({
orderId: order.id,
gatewayPaymentId: payment.id,
status: "pending"
});
res.json({
paymentId: payment.id,
clientToken: payment.clientToken
});
});
示例中的 paymentGateway.createPayment、clientToken 等都是抽象名称,实际开发时要替换为所选支付服务商提供的接口。
处理 webhook 时同样要验证签名,并确保重复通知不会引起重复发货:
app.post("/webhooks/payment", rawBodyMiddleware, async (req, res) => {
let event;
try {
event = paymentGateway.verifyWebhook({
body: req.body,
signature: req.headers["payment-signature"]
});
} catch {
return res.sendStatus(400);
}
if (event.type === "payment.succeeded") {
await orderRepository.markPaidOnce({
orderId: event.reference,
gatewayPaymentId: event.paymentId,
paidAmount: event.amount,
currency: event.currency
});
}
res.sendStatus(200);
});
markPaidOnce 需要做幂等处理,并核对订单编号、币种和金额,不能只检查事件类型。
Mobile app 的特殊限制
如果 App 销售需要在现实世界交付的商品或服务,例如服装、餐饮配送、酒店或交通服务,通常可以接入第三方 payment gateway。
如果销售的是在 App 内消费的数字内容、订阅或虚拟功能,可能必须使用应用商店规定的应用内购买系统。适用范围会因平台、地区、商品类型和政策变化而不同。发布前应检查 Apple、Google 和目标市场的最新规则。
应用商店抽成与 payment gateway 手续费不是一回事:
- 应用商店抽成通常与数字商品分发有关。
- Payment gateway 手续费通常与资金处理有关。
- Marketplace 还可能产生平台佣金、分账费和商户身份验证成本。
降低支付成本的可行办法
支付成本无法完全消除,但可以通过以下方式控制:
- 比较不同网关的百分比费率、固定费用、退款费和拒付费。
- 为小额订单设置最低消费金额,或者合并付款。
- 提供银行转账、即时支付或本地低成本支付方式。
- 交易量达到一定规模后,与支付服务商协商费率。
- 减少退款和拒付,完善账单描述与客服流程。
- 避免不必要的跨境收款和货币转换。
- 将预计产生的支付成本计入定价和毛利计算。
- 在合规的情况下,向消费者明确展示附加费,或为低成本支付方式提供折扣。
自行存储银行卡信息通常不能降低成本。这样做无法绕过收单机构和银行卡网络费用,反而会大幅增加 PCI DSS、安全和合规责任。应优先使用 payment gateway 提供的 tokenization、托管支付页面或官方 SDK。
实施时还要注意
选择方案前,应向支付服务商确认完整费率,不能只看首页展示的基础交易费。需要重点了解:
- 成功交易费用;
- 固定手续费;
- 跨境和货币转换费用;
- 退款后原手续费是否退还;
- 拒付及争议处理费用;
- 结算周期和提现费用;
- 是否支持附加费;
- 是否支持 marketplace 分账;
- 是否收取最低月费或账户维护费。
与其寻找“完全不用支付手续费”的支付网关,不如先确定商品类型、目标地区和支付方式,再将全部支付成本计入定价。如果决定向消费者单独收费,应在开发前完成法律、支付协议和应用商店政策检查,并在用户确认付款前清楚展示最终金额。
备注:内容仅供参考。
