Checkout, Payments & Invoices | 更新时间: 2026-02-09
checkout/page.tsx (redirect),
checkout/[appointmentId]/page.tsx,
invoices/[id]/page.tsx
NewCheckoutFlow.tsx (主编排器),
OrderDetailsSection.tsx,
DiscountSection.tsx,
GiftCardInput.tsx,
TipSelector.tsx,
PaymentMethodSelector.tsx,
CashPaymentForm.tsx,
TerminalSelector.tsx,
PaymentInitiateModal.tsx,
PaymentProgressModal.tsx,
PaymentSuccessSummary.tsx,
PaymentStatusBar.tsx,
AmountDueDisplay.tsx,
FinalDueAmount.tsx,
PaidTransactionsList.tsx,
SplitPaymentSelector.tsx,
SplitPaymentModal.tsx,
ServiceSplitView.tsx,
PersonSplitView.tsx,
ManualSplitInput.tsx,
RefundModal.tsx,
AddTerminalModal.tsx,
CheckoutSkeleton.tsx,
CheckoutErrorBoundary.tsx,
PaymentCheckoutFlow.tsx (legacy)
view_payments + process_payments 权限 (ProtectedRoute)
| CUJ | 优先级 | 描述 | 触发点 | 业务价值 | E2E 状态 |
|---|---|---|---|---|---|
| F1 | P0 | 结账收款 — 现金 | 预约 pending_payment 后前台结账 | 核心营收路径 | 部分覆盖 |
| F2 | P0 | 结账收款 — 刷卡 | 客户选择信用卡/借记卡 | 核心营收路径 | 部分覆盖 |
| F3 | P1 | 折扣与礼品卡抵扣 | 结账时应用优惠 | 营销转化、客户留存 | 部分覆盖 |
| F4 | P1 | 拆分支付 | 客户要求多种支付方式 | 支付灵活性 | 部分覆盖 |
| F5 | P1 | 团体结账 | 多人预约结账 | 大额订单处理 | 缺失 |
| F6 | P1 | 退款 | 客户投诉/服务问题 | 客户满意度,纠纷处理 | 缺失 |
| F7 | P2 | 发票管理 | 查看/打印发票 | 财务记录 | 部分覆盖 |
| F8 | P2 | 终端管理 | 添加/管理 POS 终端 | 硬件设施管理 | 缺失 |
flowchart TD
PAGE["checkout/[appointmentId]/page.tsx"] --> NCF["NewCheckoutFlow.tsx - 主编排器"]
NCF --> ODS["OrderDetailsSection - 订单明细"]
NCF --> DS["DiscountSection - 折扣/优惠券/礼品卡"]
NCF --> TS["TipSelector - 小费"]
NCF --> PMS["PaymentMethodSelector - 支付方式"]
NCF --> SPS["SplitPaymentSelector - 拆分模式"]
PMS -->|"Cash"| CPF["CashPaymentForm - 现金支付"]
PMS -->|"Card"| TERM["TerminalSelector - 选择终端"]
TERM --> PIM["PaymentInitiateModal - 发起刷卡"]
PIM --> PPM["PaymentProgressModal - WebSocket 等待"]
CPF --> PSS["PaymentSuccessSummary - 成功"]
PPM --> PSS
SPS -->|"按服务"| SSV["ServiceSplitView"]
SPS -->|"按人"| PSV["PersonSplitView"]
SPS -->|"手动金额"| MSI["ManualSplitInput"]
style NCF fill:#2196F3,stroke:#1565C0,color:#fff
style PSS fill:#4CAF50,stroke:#2E7D32,color:#fff
stateDiagram-v2
[*] --> pending: 创建订单
pending --> processing: 发起支付
processing --> completed: 支付成功
processing --> failed: 支付失败
processing --> cancelled: 用户取消
failed --> processing: 重试
completed --> refunded: 全额退款
completed --> partial_refunded: 部分退款
state "Transaction Status" as ts {
pending: PENDING
processing: PROCESSING
completed: COMPLETED
failed: FAILED
cancelled: CANCELLED
refunded: REFUNDED
partial_refunded: PARTIAL_REFUNDED
}
| 概念 | 字段 | 可选值 | 说明 |
|---|---|---|---|
| 预约状态 | appointments.status |
completed / pending_payment / finished / closed |
服务完成后为 completed;部分付款为 pending_payment;全额付款为 finished |
| 预约支付状态 | appointments.payment_status |
pending / partial / paid / refunded |
追踪支付进度。partial = 部分付款(拆分中间态) |
| 交易状态 | transactions.payment_status |
pending / processing / completed / failed / cancelled / refunded / partial_refunded |
单笔支付交易的状态 |
| 发票状态 | invoices.status |
unpaid / partial / paid / void |
发票的收款状态 |
completed/pending_payment ← CUJ-B4 预约状态 |
支付完成后 closed ← CUJ-C 日结 |
退款 → 预约回退为 pending_payment/completed ← CUJ-B4
P0 核心营收路径 — 现金收款
flowchart TD
A["预约状态: pending_payment"] --> B["点击 Take Payment"]
B --> C["/checkout/[appointmentId]"]
C --> NCF["NewCheckoutFlow 加载订单"]
NCF --> D["OrderDetailsSection 显示服务明细"]
D --> E["TipSelector 选择小费"]
E --> F["PaymentMethodSelector 选择 Cash"]
F --> G["CashPaymentForm"]
G --> H["输入收到金额"]
H --> I{"金额 >= 应付?"}
I -->|"是"| J["显示找零金额"]
I -->|"否"| K["按钮禁用,提示不足"]
J --> L["点击确认"]
L --> M["POST /api/payment/cash"]
M --> N["PaymentSuccessSummary"]
N --> O["appointments.status = finished"]
style C fill:#2196F3,stroke:#1565C0,color:#fff
style N fill:#4CAF50,stroke:#2E7D32,color:#fff
pending_payment,服务总额 $65.00 + 税 $5.77 = $70.77/checkout/[appointmentId]POST /api/payment/cashpaid,status 变为 finishedpaid
测试: checkout-flow.spec.ts, full-flow/05-payment-pairwise.spec.ts (部分覆盖)
P0 核心营收路径 — POS 终端刷卡
flowchart TD
A["PaymentMethodSelector 选择 Card"] --> B["TerminalSelector 选择在线终端"]
B --> C["PaymentInitiateModal 确认金额"]
C --> D["POST /api/payment/initiate"]
D --> E["PaymentProgressModal"]
E --> F{"WebSocket 事件"}
F -->|"acknowledged"| G["终端已收到请求"]
F -->|"processing"| H["客户正在操作"]
F -->|"completed"| I["PaymentSuccessSummary"]
F -->|"failed"| J["显示失败原因,可重试"]
F -->|"timeout"| K["超时,提示检查终端"]
F -->|"cancelled"| L["用户取消"]
style E fill:#FF9800,stroke:#E65100,color:#fff
style I fill:#4CAF50,stroke:#2E7D32,color:#fff
style J fill:#f44336,stroke:#c62828,color:#fff
POST /api/payment/initiate 发送支付请求到 CodePaypayment:completed 事件paid,status = finished
测试: payment/processing.spec.ts (部分覆盖)
payment:failed(余额不足/卡被拒)POST /api/payment/initiate
payment:timeout 提示P1 营销转化 — 在结账时应用优惠
flowchart TD
A["OrderDetailsSection 显示原价"] --> B["DiscountSection"]
B --> C{"折扣类型"}
C -->|"自动折扣"| D["系统自动应用匹配的折扣"]
C -->|"优惠券"| E["输入优惠码"]
C -->|"礼品卡"| F["GiftCardInput 输入卡号"]
E --> G["POST validate coupon"]
G --> H{"验证结果"}
H -->|"有效"| I["应用折扣,更新金额"]
H -->|"无效/过期"| J["显示错误"]
F --> K["POST /api/payment/gift-card/validate"]
K --> L{"验证结果"}
L -->|"有效"| M["显示余额,应用抵扣"]
L -->|"无效"| N["显示错误"]
D --> O["AmountDueDisplay 更新应付金额"]
I --> O
M --> O
style B fill:#FF9800,stroke:#E65100,color:#fff
style O fill:#4CAF50,stroke:#2E7D32,color:#fff
测试: full-flow/02f-discount-crud.spec.ts (部分覆盖)
测试: full-flow/02g-giftcard-crud.spec.ts (部分覆盖)
P1 支付灵活性 — 多种支付方式或分次支付
| 模式 | 组件 | 说明 | 适用场景 |
|---|---|---|---|
| Full(默认) | — | 一次付清全部金额 | 普通结账 |
| By Service | ServiceSplitView.tsx |
选择本次要付哪些服务项 | 客户只想先付部分服务 |
| By Person | PersonSplitView.tsx |
按客人分别付款 | 团体预约各付各的 |
| Manual Amount | ManualSplitInput.tsx |
手动输入本次支付金额 | 不规则拆分 |
flowchart TD
A["SplitPaymentSelector 选择拆分模式"] --> B{"模式"}
B -->|"By Service"| C["ServiceSplitView 勾选服务"]
B -->|"By Person"| D["PersonSplitView 选择客人"]
B -->|"Manual"| E["ManualSplitInput 输入金额"]
C --> F["AmountDueDisplay 显示本次应付"]
D --> F
E --> F
F --> G["选择支付方式并完成"]
G --> H{"全部付清?"}
H -->|"是"| I["payment_status = paid"]
H -->|"否"| J["payment_status = partial"]
J --> K["PaidTransactionsList 显示已付记录"]
K --> L["继续下一笔支付"]
L --> A
style F fill:#FF9800,stroke:#E65100,color:#fff
style I fill:#4CAF50,stroke:#2E7D32,color:#fff
style J fill:#FFD54F,stroke:#F9A825,color:#333
paid
测试: full-flow/05-payment-pairwise.spec.ts (部分覆盖)
partialpartialP1 大额订单 — 多客人预约的结账
| 模式 | 说明 | 发票类型 |
|---|---|---|
| Total(总付) | 一人付全部客人的费用 | group invoice,payment_mode = total |
| Individual(分人付) | 每位客人分别付自己的费用 | group invoice,payment_mode = individual |
paidpaid
paidpartialpaidpaid
POST /api/invoice-items/:id/skip)skippedfinished)
测试: 缺失 — 需新增
P1 纠纷处理 — 全额或部分退款
| 退款类型 | 适用条件 | 到账时间 | 金额限制 | API |
|---|---|---|---|---|
| VOID(作废) | 当天的刷卡交易 | 即时 | 仅全额 | POST /api/payment/refunds/card |
| Card Refund | 非当天刷卡交易 | 3-5 个工作日 | 全额或部分 | POST /api/payment/refunds/card |
| Cash Refund | 原支付为现金 | 即时 | 全额或部分 | POST /api/payment/refunds/cash |
| Gift Card Refund | 原支付含礼品卡 | 即时(余额恢复) | 全额或部分 | POST /api/payment/refunds/gift-card |
flowchart TD
A["已完成支付的预约"] --> B["打开 RefundModal"]
B --> C["显示原交易信息"]
C --> D{"退款类型"}
D -->|"全额"| E["自动填入全部金额"]
D -->|"部分"| F["手动输入退款金额"]
E --> G["选择退款原因"]
F --> G
G --> H{"原支付方式"}
H -->|"Card 当天"| I["VOID 到同一终端"]
H -->|"Card 非当天"| J["Standard Refund"]
H -->|"Cash"| K["现金退款(关联钱箱)"]
H -->|"Gift Card"| L["余额恢复"]
I --> M{"处理结果"}
J --> M
K --> M
L --> M
M -->|"成功"| N["更新 transaction.refunded_amount"]
M -->|"失败"| O["显示错误,可重试"]
N --> P["更新 appointment.payment_status"]
style B fill:#f44336,stroke:#c62828,color:#fff
style N fill:#4CAF50,stroke:#2E7D32,color:#fff
completedrefundedrefunded,status 变为 completed
测试: 缺失 — 需新增
paid(部分退款不改变已付状态)
POST /api/payment/refunds/:refundId/retry)P2 财务记录 — 查看和打印发票
| 字段 | 说明 |
|---|---|
invoice_number | 人类可读的发票号 |
invoice_type | single(单人)/ group(团体) |
payment_mode | total(总付)/ individual(分人付) |
total_amount | 发票总额 |
paid_amount | 已付金额 |
status | unpaid / partial / paid / void |
/invoices/[id]测试: invoices/management.spec.ts, full-flow/09-invoice.spec.ts (部分覆盖)
groupDELETE /api/invoices/:id)voidP2 硬件设施 — 管理 CodePay POS 终端
测试: 缺失 — 需新增
GET /api/payment/terminals