支付:餐饮SaaS最难的模块
如果有人问我餐饮SaaS出海最难做的模块是什么,我不会说是点餐界面,也不会说是厨房显示系统——我会说是支付。
原因是:每个市场的支付生态完全不同,而且支付直接关系到钱,容错率为零。
中国香港支付生态 vs 海外支付生态
| 特性 | 中国香港 | 东南亚 | 中东 | 欧美 |
|---|---|---|---|---|
| 主导方式 | 八达通、AlipayHK | GrabPay、GoPay、PromptPay | Mada、Apple Pay | Visa/Mastercard |
| 二维码支付 | 普及 | 非常普及 | 增长中 | 少用 |
| 现金比例 | <20% | 30-60% | 20-40% | <10% |
| 离线支付 | 八达通支持 | 部分支持 | 少见 | 极少 |
支付整合的三层架构
我们设计了一套分层的支付架构,来应对不同市场的差异:
┌────────────────────────────────────┐
│ 应用层 (POS UI) │
│ 点餐 → 结帐 → 选择支付方式 │
└────────────────────────────────────┘
│
┌────────────────────────────────────┐
│ 抽象层 (Payment Adapter) │
│ normalize() → processPayment() │
│ → handleRefund() → reconcile() │
└────────────────────────────────────┘
│
┌────────────────────────────────────┐
│ 网关层 (Gateways) │
│ Stripe │ Adyen │ 2C2P │ Omise │
│ 八达通 │ Alipay │ GrabPay │ ... │
└────────────────────────────────────┘
抽象层的设计
关键是定义一个统一的 PaymentAdapter 接口:
interface PaymentAdapter {
// 标准化支付请求
processPayment(order: Order, method: PaymentMethod): Promise<PaymentResult>;
// 退款
refund(transactionId: string, amount: number): Promise<RefundResult>;
// 查询状态
queryStatus(transactionId: string): Promise<PaymentStatus>;
// 对账
reconcile(startDate: Date, endDate: Date): Promise<ReconciliationReport>;
}
// 每个市场实现自己的Adapter
class StripeAdapter implements PaymentAdapter { /* ... */ }
class OctopusAdapter implements PaymentAdapter { /* ... */ }
class GrabPayAdapter implements PaymentAdapter { /* ... */ }
这样做的好处:
- 上层代码不知道也不关心底层用的是哪个网关
- 新增一个市场只需实现一个新的Adapter
- 每个Adapter可以独立测试
各市场支付策略
中国香港:必须支持的支付方式
| 支付方式 | 优先级 | 理由 |
|---|---|---|
| 八达通 | P0 | 中国香港渗透率98%,餐厅必须支持 |
| AlipayHK | P0 | 内地游客+本地用户 |
| WeChat Pay HK | P1 | 内地游客主要支付方式 |
| Visa/Mastercard | P1 | 国际客户和高单价消费 |
| 转数快 (FPS) | P2 | 政府推动,但餐厅使用率仍低 |
东南亚:碎片化的支付万花筒
| 国家 | 必接支付 | 备注 |
|---|---|---|
| 泰国 | PromptPay、TrueMoney | QR支付占主导 |
| 印尼 | GoPay、OVO、DANA | 三大钱包覆盖90%用户 |
| 新加坡 | PayNow、GrabPay | 与中国香港生态相似 |
| 马来西亚 | Touch’n Go、Boost | 钱包大战仍在进行 |
| 越南 | Momo、ZaloPay | 现金仍占40% |
实战建议: 在东南亚,不要尝试自己对接每个钱包。使用聚合支付平台(如2C2P、Omise、Stripe SEA),它们已经预先整合了大部分本地支付方式。
中东:高ARPU但支付基建特殊
- Mada(沙特):类似八达通的国家级支付网络
- Apple Pay / Samsung Pay:渗透率高于QR支付
- 现金交易比例仍较高,需要保留现金结帐功能
离线支付:餐厅的必备功能
餐厅和电商最大的不同:餐厅必须支持离线支付。当网络不稳定时,POS不能停止工作。
我们的实现方案:
- 离线时,终端本地缓存支付记录
- 网络恢复后自动同步到云端
- 使用乐观锁(Optimistic Locking)处理冲突
- 八达通等离线支付方式本身就支持离线交易
// 离线支付队列
class OfflinePaymentQueue {
private queue: OfflineTransaction[] = [];
async process(payment: PaymentRequest): Promise<PaymentResult> {
if (await isOnline()) {
return this.gateway.process(payment);
}
// 离线模式:缓存并返回临时确认
const offlineTx = await this.cache.store(payment);
this.queue.push(offlineTx);
return { status: 'pending_sync', offlineId: offlineTx.id };
}
async syncWhenOnline(): Promise<void> {
for (const tx of this.queue) {
await this.gateway.process(tx);
}
this.queue = [];
}
}
退款与对账
退款流程
餐厅退款比电商复杂得多——可能是整单退、部分退、甚至是换菜不退款。我们的设计:
- 退款必须经管理员授权(防止员工滥用)
- 部分退款要明细到菜品级别
- 退款原因分类(客户投诉/出错菜/等待太久/其他)
对账(Reconciliation)
每日对账是财务的刚需。POS需要自动匹配:
- 订单金额 vs 实际收款
- 手续费差异(不同支付方式手续费不同)
- 退款记录 vs 银行流水
经验: 对账功能不要等到财务投诉才做。我们在中国香港经历了三个月的每日手动对账后,才下定决心做了自动化对账系统。三个月的手工对账,财务同事几乎崩溃。
安全合规
- PCI DSS:如果用Stripe/Adyen等平台,合规负担大大降低
- PSD2 / SCA(欧洲):需要支持3D Secure等强客户认证
- 数据本地化:部分国家要求支付数据存储在本地服务器
结论
支付是餐饮SaaS出海最大的技术障碍,也是最大的护城河。一旦你的系统支持了10个市场的支付方式,后来者要追上需要同样的投入。我们的策略是:用2C2P做东南亚、Stripe做欧美、自研Adapter做中国香港和特殊市场。
相关文章
- 中国香港餐厅POS系统选购指南:月费 vs 按单收费 — 不同收费模式对支付系统架构的影响
- 餐饮SaaS出海合规挑战与解决方案 — 支付行业合规(PCI DSS / PSD2 / SCA)深度解析
- 出海第一站:为什么选择东南亚 — 东南亚各国支付生态差异与整合策略
阿公寄语:钱银嘅嘢,唔可以出错。支付模块嘅第一条规矩系:永远唔好自己实现加密算法。用成熟平台,专注做好适配层。

