一、支付架构总览
核心原则:自有支付底座(金币体系) + 多渠道接入。金币是内部唯一价值载体,外部支付通道只负责"钱→金币"的兑换。
1.1 支付架构图
[用户点击充值] → [Payment Service 选择通道] → [PayNow/Stripe/Apple/Google]
↓
[外部支付完成] → [Webhook/回调] → [Payment Service 验证] → [发放金币] → [用户余额更新]
↓
[对账系统 T+1] → [差异告警] → [人工处理]
1.2 通道对比
| 通道 | 费率 | 到账 | 适用场景 | Phase |
| PayNow | 0.5%/笔 | 实时 | 新加坡用户首选 | P1 |
| Stripe (卡) | 2.9%+$0.5 | T+2 | 国际卡/非新加坡 | P1 |
| Apple IAP | 30% 佣金 | T+30 | iOS 充值/订阅 | P1 |
| Google Play | 30% 佣金 | T+15 | Android 充值/订阅 | P1 |
| GrabPay | 1.8%/笔 | 实时 | 补充渠道 | P1.5 |
| Touch'n Go | 1.5%/笔 | 实时 | 马来西亚 | P2 |
| MoMo | 2.5%/笔 | 实时 | 越南 | P2 |
| DANA/OVO | 2%/笔 | 实时 | 印尼 | P2 |
| UPI | 1.2%/笔 | 实时 | 印度 | P3 |
| stc pay/Mada | 2%/笔 | 实时 | 沙特/阿联酋 | P3 |
1.3 金币汇率体系
| 货币 | 汇率 | 说明 |
| S$ (新加坡) | 1 S$ = 100 金币 | 基准货币 |
| US$ (美元) | 1 US$ = 135 金币 | Stripe 默认 |
| RM (马来西亚) | 1 RM = 33 金币 | P2 启用 |
| ₫ (越南盾) | 1000 ₫ = 5 金币 | P2 启用 |
| Rp (印尼盾) | 1000 Rp = 4 金币 | P2 启用 |
汇率每周同步一次(周一 00:00 SGT),由财务团队维护。用户充值时刻的汇率锁定,不随波动变化。
二、PayNow 接入(新加坡核心通道)
2.1 技术对接
| 项目 | 规格 |
| 对接方式 | DBS API(直连) + XanPool(聚合备用) |
| 协议 | REST API + JSON + SHA-256 签名 |
| 认证 | Client ID + Secret(每请求签名) |
| 支付流程 | 服务端创建订单 → 返回 QR → 用户扫码 → DBS 回调 |
| QR 标准 | EMV QR(PayNow 标准) |
| 限额 | 默认 S$200/笔 → 可申请提高到 S$1000 |
| 退款 | 不支持原路退 → 退到 Vocalink 金币余额 |
2.2 支付流程
// 1. 前端请求创建订单
POST /payment/charge
{ "package_id": "pkg_1200", "channel": "paynow" }
// 2. 后端 → DBS API:创建 PayNow 订单
POST https://api.dbs.com/sg/paynow/v1/orders
Headers: X-DBS-Client-Id, X-DBS-Signature
Body: {
"amount": 9.99,
"currency": "SGD",
"description": "Vocalink 1200 coins",
"callback_url": "https://api.vocalink.sg/v1/payment/webhook/dbs",
"reference_id": "ord_xxx"
}
// 3. DBS 返回 QR 数据
Response: {
"order_id": "dbs_ord_123",
"qr_data": "00020101021126490009SG.PAYNOW01...",
"qr_image_url": "https://...",
"expires_at": "2026-08-15T10:15:00Z"
}
// 4. 用户扫码支付 → DBS 回调
POST /payment/webhook/dbs
{ "order_id": "dbs_ord_123", "status": "completed", "amount": 9.99 }
// 5. 后端验证签名 → 发放金币 → WebSocket 通知客户端
2.3 错误处理
| 错误码 | 含义 | 处理 |
| 6001 | 余额不足 | 提示"银行余额不足,换卡或换通道" |
| 6002 | 订单超时 | QR 过期 → 引导重新发起 |
| 6003 | 签名验证失败 | 服务器错误 → 重试 3 次 |
| 6004 | DBS 服务不可用 | 降级到 Stripe |
| 6005 | 重复回调 | 幂等处理(order_id 唯一) |
三、Stripe 接入(国际卡)
3.1 技术对接
| 项目 | 规格 |
| SDK | Stripe SDK (iOS/Android/Web) + Stripe Java SDK (后端) |
| 模式 | PaymentIntents API(SCA 合规) |
| 3DS | 强制启用(新加坡 MAS 要求) |
| Webhook | payment_intent.succeeded / failed / canceled |
| 争议 | Stripe Radar(AI 风控)+ 人工审核 |
| 结算 | T+2 到银行账户(SGD 账户) |
3.2 支付流程
// 1. 后端创建 PaymentIntent
StripePaymentIntent intent = stripe.paymentIntents.create(
amount = 999, // 分
currency = "sgd",
metadata = mapOf(
"order_id" to "ord_xxx",
"user_id" to "usr_xxx",
"coins" to "1400"
),
paymentMethodTypes = listOf("card"),
description = "Vocalink 1200+200 bonus coins"
)
// 2. 返回 client_secret 给前端
Response: { "client_secret": "pi_xxx_secret_yyy", "order_id": "ord_xxx" }
// 3. 前端 Stripe SDK 确认支付
stripe.confirmCardPayment(clientSecret, { paymentMethod: cardElement })
// 4. Stripe Webhook → 后端
POST /payment/webhook/stripe
{ "type": "payment_intent.succeeded", "data": { "object": { "id": "pi_xxx", "metadata": {...} } } }
// 5. 验证 + 幂等 + 发放金币
3.3 退款处理
| 场景 | 方式 | 到账 |
| 用户申请退款 | 原路退(Stripe refund API) | 5-10 工作日 |
| 重复扣款 | 原路退 + 道歉补偿 | 即时 |
| 争议/Chargeback | 提交证据 → Stripe 裁决 | 60-75 天 |
| 无法原路退 | 退到 Vocalink 金币余额 | 即时 |
四、Apple In-App Purchase
4.1 产品配置
| Product ID | 类型 | 价格 | 对应内容 |
| sg.vocalink.coins.500 | Consumable | $0.99 | 500 金币 |
| sg.vocalink.coins.1200 | Consumable | $2.99 | 1200+200 金币 |
| sg.vocalink.coins.3000 | Consumable | $5.99 | 3000+600 金币 |
| sg.vocalink.coins.6000 | Consumable | $9.99 | 6000+1500 金币 |
| sg.vocalink.vip.month | Auto-Renewable | $9.99/月 | VIP 月订阅 |
| sg.vocalink.vip.year | Auto-Renewable | $79.99/年 | VIP 年订阅 |
4.2 验证流程
// 1. iOS 客户端完成购买 → 获取 receipt
let receiptData = try! Data(contentsOf: Bundle.main.appStoreReceiptURL!)
let receiptString = receiptData.base64EncodedString()
// 2. 客户端发送 receipt 到后端验证
POST /payment/subscription
{ "plan_id": "vip_month", "channel": "apple_iap", "receipt_data": receiptString }
// 3. 后端 → Apple VerifyReceipt API
POST https://buy.itunes.apple.com/verifyReceipt
{ "receipt-data": receiptString, "password": "shared_secret" }
// 4. Apple 返回验证结果
{ "status": 0, "receipt": {...}, "latest_receipt_info": [{ "product_id": "sg.vocalink.vip.month", "expires_date_ms": "1718000000000" }] }
// 5. 后端验证 + 发放 VIP + 记录原始交易 ID(用于续订)
4.3 订阅续订处理
| 事件 | Apple 通知 | 后端动作 |
| 首次购买 | DID_CHANGE_RENEWAL_STATUS | 发放 VIP + 记录 original_transaction_id |
| 续订成功 | DID_RENEW | 延长 VIP 有效期 |
| 续订失败 | FAILED_TO_RENEW | 标记 grace_period → 3 天宽限 |
| 用户取消 | CANCEL | 到期日停止 VIP |
| 退款 | REFUND | 撤回 VIP + 扣回金币 |
| 恢复购买 | RESTORE | 重新激活 VIP |
⚠️ Apple 30% 佣金影响:
Apple IAP 抽成 30%,意味着 S$9.99 套餐实际到手 S$6.99。因此:
- Web 端充值(Stripe/PayNow)利润率比 IAP 高 ~20%
- 引导用户"前往网页充值更划算"(但注意 Apple 审核规则 3.1.1)
- Phase 2 起开发 Web 充值引导(合规方式)
五、Google Play Billing
5.1 产品配置
| Product ID | 类型 | 价格 | 对应内容 |
| coins_500 | Consumable | S$0.99 | 500 金币 |
| coins_1200 | Consumable | S$2.99 | 1200+200 金币 |
| coins_3000 | Consumable | S$5.99 | 3000+600 金币 |
| coins_6000 | Consumable | S$9.99 | 6000+1500 金币 |
| vip_month | SUBSCRIPTION | S$9.99/月 | VIP 月订阅 |
| vip_year | SUBSCRIPTION | S$79.99/年 | VIP 年订阅 |
5.2 验证流程
// 1. Android 客户端完成购买 → 获取 purchaseToken
Purchase purchase = ...; // from BillingClient
String purchaseToken = purchase.getPurchaseToken();
// 2. 发送到后端验证
POST /payment/subscription
{ "plan_id": "vip_month", "channel": "google_play", "purchase_token": "..." }
// 3. 后端 → Google Play Developer API 验证
GET https://androidpublisher.googleapis.com/androidpublisher/v3/applications/sg.vocalink/purchases/subscriptions/vip_month/tokens/{token}
Headers: Authorization: Bearer {service_account_jwt}
// 4. Google 返回
{ "kind": "androidpublisher#subscriptionPurchase", "paymentState": 1, "expiryTimeMillis": "1718000000000", "autoRenewing": true }
// 5. 后端验证 + 发放 VIP
5.3 Google vs Apple 差异
| 维度 | Apple | Google |
| 佣金 | 30%(小开发者 15%) | 30%(前 100 万 15%) |
| 验证方式 | receipt + shared secret | service account JWT |
| 退款 | 用户申请 → Apple 裁决 | 用户申请 → Google 裁决 |
| 测试 | Sandbox Apple ID | License Testers |
| 宽限期 | 3 天 grace | 7 天 grace |
| 取消 | 设置 → 订阅 | Play Store → 订阅 |
六、对账系统
6.1 对账流程
[T+1 00:30] → [拉取各通道结算文件] → [DBS Statement / Stripe Payout / Apple Financial / Google](@)
↓
[与内部 payments 表比对] → [匹配:标记 reconciled] → [差异:生成告警]
↓
[差异分类] → [金额差异 → 财务调查] / [丢失交易 → 补发/追回] / [重复 → 退款]
6.2 对账规则
| 通道 | 对账文件 | 频率 | 容差 |
| PayNow (DBS) | DBS Merchant Statement (CSV) | 每日 | S$0.01 |
| Stripe | Stripe Payout Report (CSV/API) | 每笔 payout | $0.01 |
| Apple | Apple Financial Report (PDF→CSV) | 每月 | $0.10 |
| Google | Google Play Payout Report | 每月 | $0.10 |
6.3 差异处理
| 差异类型 | 原因 | 处理 |
| 金额少 1-2% | 通道费率四舍五入 | 自动调整(容差内) |
| 交易丢失 | Webhook 未送达 | 查询通道 API → 补发金币 |
| 重复发放 | Webhook 重复推送 | 幂等拦截(已处理) |
| 金额差异大 | 汇率波动/手续费变更 | 财务人工调查 |
| 退款未同步 | 通道退款延迟 | 标记 pending → 7 天复查 |
七、风控系统
7.1 风控规则
| 规则 | 触发条件 | 动作 |
| 高频充值 | 同一用户 1 小时内 > 5 笔 | 限流 + 人工审核 |
| 大额充值 | 单笔 > S$100 | 加强验证(3DS/短信) |
| 异地充值 | IP 国家 ≠ 注册国家 | 邮件确认 + 限流 |
| 新账号大额 | 注册 < 24h + 充值 > S$50 | 延迟到账(24h 观察期) |
| 退款率异常 | 用户退款率 > 30% | 标记高风险 + 限制 |
| Chargeback | Stripe 收到争议 | 冻结账号 + 收集证据 |
| 金币异常消耗 | 1 小时内消耗 > 5000 金币 | 临时冻结 + 验证 |
7.2 风控架构
# 风控引擎(Payment Service 内)
class RiskEngine:
def evaluate(self, payment_request):
risk_score = 0
user = get_user(payment_request.user_id)
# 规则 1:高频
if self._count_recent_payments(user.id, hours=1) > 5:
risk_score += 30
# 规则 2:大额
if payment_request.amount_sgd > 100:
risk_score += 20
# 规则 3:异地
if payment_request.ip_country != user.registered_country:
risk_score += 25
# 规则 4:新账号
if (now() - user.created_at) < 24h and payment_request.amount_sgd > 50:
risk_score += 35
# 决策
if risk_score >= 70:
return "block", "High risk score"
elif risk_score >= 40:
return "review", "Manual review needed"
else:
return "allow", "Low risk"
7.3 PCI-DSS 合规
| 要求 | 实现 |
| 不存储卡号 | Stripe 代持(不接触原始卡数据) |
| 加密传输 | TLS 1.2+ 全链路 |
| 访问日志 | 所有支付操作审计日志(不可篡改) |
| 定期扫描 | 季度漏洞扫描 + 年度渗透测试 |
| 员工培训 | 半年一次 PCI 意识培训 |
| 网络隔离 | 支付服务独立 VPC + 最小权限 |
八、退款与争议处理
8.1 退款政策
| 场景 | 退款方式 | 时限 | 审批 |
| 未消费金币 | 原路退(Stripe/PayNow) | 7 个工作日 | 自动(< S$50) |
| 已消费金币 | 退到 Vocalink 余额 | 即时 | 客服审批 |
| VIP 未使用 | 按比例退(原路) | 7 个工作日 | 自动 |
| VIP 已使用 | 按剩余天数退 | 7 个工作日 | 客服审批 |
| 技术问题导致 | 全额退 + 补偿金币 | 即时 | 自动(标记 bug) |
8.2 Chargeback 处理
| 阶段 | 时间 | 动作 |
| 收到通知 | Day 0 | 标记订单 + 冻结争议金额 |
| 收集证据 | Day 1-7 | 交易日志/IP/设备指纹/用户行为 |
| 提交辩护 | Day 7-14 | 通过 Stripe Dashboard 提交 |
| 等待裁决 | Day 14-75 | Stripe/银行/卡组织裁决 |
| 结果处理 | Day 75+ | 胜诉→解冻 / 败诉→扣款+标记用户 |
九、Phase 演进
| Phase | 支付通道 | 新增能力 | 预期月流水 |
| P1 | PayNow + Stripe + Apple + Google | 基础充值/订阅/退款 | ~S$8 万 |
| P1.5 | + GrabPay | Web 充值引导 + 优惠券系统 | ~S$12 万 |
| P2 | + Touch'n Go / MoMo / DANA | 多国本地支付 | ~S$30 万 |
| P3 | + UPI / stc pay / Mada | 印度+中东支付 | ~S$60 万 |
| P4 | 全通道 | 企业支付/发票/B2B | ~S$120 万 |
附录
附录 A:Webhook 端点清单
| URL | 来源 | 验证方式 |
| POST /v1/payment/webhook/dbs | DBS PayNow | SHA-256 HMAC |
| POST /v1/payment/webhook/stripe | Stripe | Stripe Signature (HMAC) |
| POST /v1/payment/webhook/apple | Apple | Apple Root CA |
| POST /v1/payment/webhook/google | Google Play | Google Pub/Sub JWT |
附录 B:相关文档
| 文档 | 关联内容 |
| 10 API 接口规范 | /payment/* 接口契约 |
| 11 数据库设计 | payments / subscriptions 表 |
| 12 第三方服务集成 | Stripe/PayNow 接入概述 |
| 04 PRD | 充值/VIP 功能需求 |
附录 C:修订记录
| 版本 | 日期 | 修改内容 | 作者 |
| V1.0 | 2026-08-15 | 初始版本 | 支付团队 |