支付单、业务单与退款单三表对齐实战指南

📅 2026/7/23 20:37:08 👁️ 阅读次数 📝 编程学习
支付单、业务单与退款单三表对齐实战指南

支付单、业务单、退款单:一次订场退款里的三表对齐

背景

「快约球」订场链路里,用户支付成功后会同时存在:

概念表 / 字段职责
业务订场单venue_bookingspay_status/booking_status场次是否确认、是否取消
支付单payment_ordersstatus与微信商户单号、金额、用户绑定
退款流水payment_refundsout_refund_no、金额、微信退款号、来源

早期取消订场只改业务表,并返回mock_refunded/wechat_pending占位——资金侧没有真实退款,后台也没有按会员聚合的订单视图。这在演示可以,在对账不行。

目标

  1. 用户取消与后台退款走同一条PaymentService.refundPaidOrder
  2. 已支付订场退款后:支付单refunded,业务单取消并释放slot_offer
  3. Admin 能按userId列出该会员全部支付单,并对paid发起退款。
  4. 微信未就绪且开启 mock 时,明确记mock,不假装已打到微信。

状态机(支付单)

pending → paid → refunding → refunded ↘ closed / failed(下单失败或关单)

说明:

  • refunding:已向微信发起或本地进入退款流程。
  • 微信返回SUCCESS:落payment_refunds.status=success,订单refunded,再执行业务冲正。
  • 微信返回PROCESSING:退款流水记processing业务上仍可先释放场地(避免档期继续被占),资金到账靠微信侧异步完成;产品文案不要写「秒到账」。

关键实现要点

1. 退款入口统一

Admin: POST /api/v1/admin/orders/{id}/refund User cancel (已支付订场): refundVenueBookingIfPaid(bookingId) ↓ PaymentService.refundPaidOrder(outTradeNo, reason, source) ↓ WeChatPayV3Client.createRefund(...) 或 mock ↓ applyBizAfterRefund → venueDirectBookingService.applyRefundCancel(bookingId)

source区分admin/user_cancel,方便以后审计。

2. 三表写入顺序

建议顺序:

  1. 插入payment_refunds(pending)
  2. 支付单paid → refunding
  3. 调微信(或 mock)
  4. 更新退款流水 + 支付单refunded
  5. 业务冲正(订场取消 + 释放时段)

微信调用失败时:退款流水failed,支付单回退到paid,避免卡在refunding无法重试。

3. 按会员查单

Admin 列表增加userId条件,并对users.username / nickname做关键词检索。
业务类型包含:player_membershipteam_vipvenue_booking

这比单独再做一张「会员订单宽表」更便宜:支付域本来就有user_id

4. 会员 / VIP 退款的产品选择

订场退款会冲正业务;会员与 VIP只退支付单,不自动撤销权益
原因:权益回收涉及到期日、叠加开通、人工赠送,自动扣回容易误伤。钱和权分离,交给运营。

接口与表(便于对照)

  • Flyway:V60__payment_refunds.sql(另有启动兜底PaymentRefundsTableMigration
  • 微信:POST /v3/refund/domestic/refunds
  • 支付单扩展状态:refunding/refunded

小结

建议
只改业务表、资金占位用户取消与后台退款必须共用履约
退款失败卡在 refunding失败回退paid,允许重试
PROCESSING 当失败可先释放库存,文案写清「处理中」
会员退款自动降级先退款,权益回收单独策略

远程库 + 微信退款的系统里,状态对齐比多做一个按钮重要。先让三张表能互相解释,再谈分账与对账报表。