预约定金在线收款流程(Universal Processing)

QuickClick / Collect Checkout 集成版|用于 Celoria 预约完成后收取定金

1. 目标与边界

目标:用户线上预约完成后,必须完成定金支付,预约才进入已确认状态。

已验证通过(2026-03-02):QuickClick 客户端支付链路可跑通,测试交易 ID 11772491727,金额 1.23 USD,状态 Approved

1.5 当前已接入(MVP)

已在 Web 预约成功页接入“支付定金”入口,当前代码流程如下:
  1. 用户完成预约后进入 /booking/{store}/success?appointment_id=...
  2. 点击“支付定金”按钮,前端调用 POST /api/public/booking/deposit-payment-link
  3. 后端校验预约归属(当前 guest)、计算金额(默认全额,若启用 business.require_deposit 且百分比有效则按比例)。
  4. 后端按环境变量 UP_PAYMENT_URL_TEMPLATE 生成 UP Hosted/QuickClick 支付链接并返回。
  5. 前端收到链接后直接跳转到网关支付页面。

当前版本重点是“预约后可在线收款跳转”。支付结果回写(Webhook/验单自动更新预约状态)可在下一阶段补齐。

2. 端到端业务流程图

flowchart TD
    A["用户提交预约"] --> B["Backend 创建 appointment
status=awaiting_deposit"] B --> C["Backend 创建 deposit_payment 记录
status=pending"] C --> D["Backend 生成托管支付链接
(QuickClick/Collect Checkout)"] D --> E["前端展示并跳转支付页"] E --> F{"用户操作"} F -- "支付成功" --> G["网关返回 successUrl + transaction_id"] F -- "取消/关闭页面" --> H["回到 cancelUrl 或无回跳"] G --> I["Backend Query API 二次验单
状态/金额/订单号一致性校验"] I --> J{"验单通过?"} J -- "是" --> K["deposit_payment=paid
appointment=confirmed"] J -- "否" --> L["deposit_payment=verification_failed"] H --> M["保持 awaiting_deposit,等待重试/超时取消"] N["Webhook: transaction.sale.success"] --> I M --> O["超时任务扫描"] O --> P["appointment=deposit_expired 或 canceled"]
关键原则:前端回跳不作为最终支付依据。最终状态必须由后端验单(Query API + Webhook)决定。

3. 预约状态机

stateDiagram-v2
    [*] --> draft
    draft --> awaiting_deposit: 用户提交预约
    awaiting_deposit --> deposit_paid: 支付成功且验单通过
    awaiting_deposit --> deposit_failed: 支付失败/验单失败
    awaiting_deposit --> deposit_expired: 支付超时
    deposit_failed --> awaiting_deposit: 用户重试支付
    deposit_paid --> confirmed: 系统确认预约
    deposit_expired --> canceled
    confirmed --> [*]
    canceled --> [*]
    

4. 支付单状态机(deposit_payment)

stateDiagram-v2
    [*] --> pending
    pending --> paid: Query API verify success
    pending --> failed: 网关明确失败
    pending --> canceled: 用户取消
    pending --> expired: 超时
    pending --> verification_failed: 回跳成功但验单不通过
    failed --> pending: 发起重试(新支付链接)
    canceled --> pending: 发起重试(新支付链接)
    verification_failed --> pending: 发起重试(新支付链接)
    paid --> [*]
    expired --> [*]
    

5. 技术时序图(推荐实现)

sequenceDiagram
    participant U as 用户
    participant FE as Web App
    participant BE as Celoria Backend
    participant GW as UP Gateway

    U->>FE: 提交预约
    FE->>BE: POST /api/appointments (含定金要求)
    BE->>BE: 创建 appointment + deposit_payment(pending)
    BE-->>FE: payment_url + payment_ref
    FE->>GW: 跳转 payment_url
    U->>GW: 输入卡信息并支付
    GW-->>FE: successUrl?transaction_id=xxx
    FE->>BE: POST /api/payments/deposit/confirm (transaction_id, payment_ref)
    BE->>GW: POST /api/query.php (security_key + transaction_id)
    GW-->>BE: transaction status/amount/order info
    BE->>BE: 验证通过后更新状态
    BE-->>FE: confirmed
    GW-->>BE: webhook(transaction.sale.success) 作为兜底
    

6. 数据模型建议

6.1 appointment(关键字段)

字段 说明
id 预约主键
status awaiting_deposit / confirmed / deposit_expired / canceled
deposit_amount 应付定金金额

6.2 deposit_payment(建议新增)

字段 说明
id 支付单主键
appointment_id 关联预约
tenant_id 租户隔离键
amount 定金金额
status pending/paid/failed/canceled/expired/verification_failed
gateway_provider universal_processing
gateway_txn_id 网关交易号(如 11772491727
payment_url 发给用户的托管支付链接
expires_at 支付超时时间

7. API 清单(Celoria 侧)

接口 职责
POST /api/appointments 创建预约并生成定金支付单 + 支付链接
POST /api/payments/deposit/confirm 处理 successUrl 回跳,执行 Query API 二次验单
POST /api/payments/up/webhook 接收网关 webhook,做异步补偿与最终一致性
POST /api/payments/deposit/retry 支付失败或取消后重新生成新链接

8. 幂等与风控要点

9. Batch Close 结算须知

UP 的 Batch Close 一旦执行,已结算的交易金额无法撤销(void/cancel)。 Batch Close 会将当天所有已授权(Approved)的交易提交给银行进行资金清算,一旦结算完成,这些交易就不能再 void,只能走退款(refund)流程。
运营建议:将 Batch Close 安排在每天营业结束时(如晚上 10 点)统一执行一次。这样在营业时间内取消的预约都可以走 Void 路径,避免不必要的退款手续费。

10. Test Mode 验收清单

上线生产前必须确认:Test Mode = Disabled、配置 Private API Key、配置 Webhook Endpoint、校验 URL Allowlist