本文档描述订单与支付核心链路,涵盖订单生命周期、支付体系、金额字段定义与退款逻辑。 帮助开发人员快速理解"为什么这样设计"以及各环节的特殊处理方式。
haha-entity/.../Order.java)| 字段 | 类型 | 说明 |
|---|---|---|
| id | Long | 雪花算法主键 |
| orderNo | String | 设备端订单号(设备回调时写入) |
| outTradeNo | String | 外部交易号 |
| activityId | String | 哈哈平台活动ID(用于幂等防重复创建) |
| userId | Long | 用户ID |
| deviceId | String | 设备SN号 |
| totalAmount | BigDecimal | 订单总金额(原价),设备回调时写入 |
| discountAmount | BigDecimal | 订单优惠金额(营销活动 + 优惠券),默认0 |
| paidAmount | BigDecimal | 实付总金额 = totalAmount - discountAmount |
| payStatus | String | 支付状态:UNPAID / PAID / REFUND |
| payType | String | 支付方式 |
| payChannel | String | 支付渠道:wechat / alipay / alipay_credit / balance / wechat_payscore |
| transactionId | String | 第三方交易号 |
| refundStatus | String | 退款状态 |
| refundAmount | BigDecimal | 退款金额 |
| refundTime | LocalDateTime | 退款时间 |
| refundReason | String | 退款原因 |
| payScoreOrderId | String | 支付分服务订单号(微信侧) |
| payScoreState | String | 支付分状态:CREATED / DOING / USER_PAYING / DONE |
| status | Integer | 订单状态:0-已取消 1-待支付 2-已完成 3-已关闭 |
| orderType | String | 订单类型:OUT-正常消费,IN-异物放入 |
| items | String | 商品识别信息JSON |
| videoUrl | String | 识别视频URL |
| confidence | BigDecimal | AI识别置信度 |
OrderStatus)| code | 枚举值 | 说明 |
|---|---|---|
| 0 | CANCELLED | 已取消 |
| 1 | PENDING_PAYMENT | 待支付 |
| 2 | COMPLETED | 已完成 |
| 3 | CLOSED | 已关闭 |
PayStatus)| code | 枚举值 | 说明 |
|---|---|---|
| UNPAID | 未支付 | 订单已创建,等待支付 |
| PAID | 已支付 | 扣费成功 |
| REFUND | 已退款 | 已完成退款 |
用户扫码开门
↓
DoorRecord 记录开门(openType=OUT, doorStatus=OPENED)
↓
用户购物,关门
↓
设备AI识别回调 handleOrcResult()
↓
创建订单 createOrderFromRecognition()
- totalAmount = 0, discountAmount = 0, paidAmount = 0
- status = PENDING_PAYMENT, payStatus = UNPAID
↓
设备端计算金额(商品原价 - 营销活动优惠)
↓
订单回调 handleOrderCallback()
- 写入 totalAmount(设备传来的已含营销活动优惠的金额)
- 初始化 discountAmount = 0, paidAmount = totalAmount
↓
优惠券自动扣减 processCouponDiscount()
- 如有可用优惠券:计算优惠 → 更新 discountAmount / paidAmount
↓
支付分扣费 / 其他支付渠道
- 使用 paidAmount 进行扣费
↓
支付回调 → 更新 payStatus = PAID, status = COMPLETED
↓
订单完成 → 触发邀请奖励激活
sku_list 只包含 {code, quantity},商品价格在设备端查询HahaCallbackServiceImpl.processCouponDiscount() 自动匹配哈哈平台(设备端SDK)的AI视觉识别只负责识别"哪个商品被拿走了",返回商品编码(code)和数量(quantity),不涉及定价逻辑。商品价格由哈哈平台根据设备绑定的门店信息独立维护,服务端无法在AI识别回调时获取价格。这是设备平台架构决定的——识别与定价解耦。
设备端在用户关门后需要立即展示应付金额,如果等服务端计算再返回,用户等待时间过长。此外,哈哈平台设备端本身具备查询营销活动和计算优惠的能力。因此采用设备端计算、服务端信任的策略:
MarketingRuleService 作为备用校验能力,但当前不覆盖主流程智能售卖机的购物场景是用户关门后自动结算,没有购物车确认环节,用户无法在结算时手动选择优惠券。因此系统在订单回调时自动为用户匹配最优优惠券(优先使用最先到期的符合条件的券),这是业务场景决定的特殊处理。
AI识别回调(handleOrcResult)只告知"哪些商品被拿走了",不含价格信息。价格和最终金额要等设备端完成计算后通过订单回调(handleOrderCallback)传回。两步回调之间有时间差,期间订单金额为0是正常状态,不是异常。
通过 activityId(哈哈平台活动ID)保证订单不重复创建。AI识别回调可能因网络重试多次到达,createOrderFromRecognition() 先查询是否已有该 activityId 的订单,有则直接返回,避免重复创建。
| 字段 | 含义 | 计算方式 | 赋值时机 |
|---|---|---|---|
| totalAmount | 订单总金额(原价) | 设备端计算 | 订单回调时由设备传入 |
| discountAmount | 订单优惠金额 | 营销活动优惠 + 优惠券优惠 | 优惠券扣减时写入 |
| paidAmount | 实付总金额 | totalAmount - discountAmount | 优惠券扣减后计算 |
业务背景:早期订单表只有 totalAmount,运营平台和小程序展示“实付金额”时直接取 totalAmount。但引入优惠券后,totalAmount 存的是设备端传来的金额(已含营销活动优惠但不含优惠券优惠),导致前端显示的“实付”不正确。
解决方案:增加 discountAmount(记录优惠总额)和 paidAmount(记录实付总额),使三级金额语义清晰,前端取值简单可靠。
特殊处理:
totalAmount 已包含营销活动优惠(如首单立减5元后传95元),但营销活动优惠金额不单独持久化到订单表discountAmount 目前仅记录服务端计算的优惠券优惠部分UPDATE t_order SET discount_amount = 0, paid_amount = total_amount WHERE ...| 阶段 | totalAmount | discountAmount | paidAmount |
|---|---|---|---|
| 创建订单 | 0 | 0 | 0 |
| 订单回调 | 设备传来 | 0 | = totalAmount |
| 优惠券扣减 | 不变 | 优惠券金额 | = total - discount |
| 支付扣费 | 不变 | 不变 | 使用此金额 |
所有前端展示优先使用 paidAmount,兼容历史数据:
// 小程序
const displayAmount = order.paidAmount || order.totalAmount || 0;
// 运营平台
const amount = row.paidAmount || (row.totalAmount || 0) - (row.discountAmount || 0);
退款页面使用 totalAmount(基于订单原价计算退款),不使用 paidAmount。
退款金额应基于用户实际支付的金额来计算。但当前场景下,paidAmount 是服务端计算的优惠券扣减后金额,而设备端可能还有营销活动优惠未体现在 discountAmount 中。为避免退款金额计算混乱,退款暂时基于 totalAmount(设备端传来的已含营销优惠的金额),后续如需精确退款需结合 paidAmount 使用。
PaymentChannel)| code | 枚举值 | 说明 |
|---|---|---|
| 微信支付(JSAPI) | ||
| alipay | ALIPAY | 支付宝支付 |
| alipay_credit | ALIPAY_CREDIT | 支付宝信用支付(芝麻信用) |
| balance | BALANCE | 余额支付 |
| wechat_payscore | WECHAT_PAYSCORE | 微信支付分(先享后付) |
PaymentService (门面)
├── PaymentChannelFactory (渠道工厂)
├── WxPayStrategy (微信支付策略)
├── AliPayStrategy (支付宝策略)
├── AliCreditPayStrategy (信用支付策略)
└── PayScoreService (支付分服务)
PaymentStrategy 接口,每种渠道一个实现PaymentServiceImpl 统一调度,上层无需感知渠道差异智能售卖机场景下,用户先开门购物,后结算付款,属于典型的“先享后付”场景。微信支付分天然支持这种模式:
相比传统支付(用户需主动扫码付款),支付分消除了“忘记付款”的风险,也提升了转化率。
为保证扣费安全性,扫码开门流程中会强制检查用户是否已开通微信支付分:
这保证了每个开门用户都具备扣费能力,避免“开门购物但无法扣款”的风险
用户扫码 → 检查支付分开通状态
↓
未开通 → enablePayscore() → 引导用户授权
↓
已开通 → createUserPayScoreOrder() → 创建支付分服务订单
↓
用户开门购物 → 关门 → 订单回调
↓
completeUserPayScoreOrder() → 完结支付分(自动扣款)
↓
支付分回调 → 更新订单 payStatus = PAID
支付分服务订单状态:
运营平台发起退款
↓
OrderServiceImpl.refund()
- 验证订单状态和支付状态
- 调用 PaymentService.refund()
- 更新 refundStatus / refundAmount / refundTime
| 文件 | 职责 |
|---|---|
haha-entity/.../Order.java |
订单实体 |
haha-entity/.../OrderGoods.java |
订单商品明细 |
haha-service/.../OrderServiceImpl.java |
订单服务(创建、查询、退款) |
haha-service/.../HahaCallbackServiceImpl.java |
设备回调处理(订单回调、优惠券扣减) |
haha-service/.../payment/PaymentService.java |
支付门面接口 |
haha-service/.../payment/impl/PaymentServiceImpl.java |
支付门面实现 |
haha-service/.../payment/payscore/PayScoreService.java |
支付分服务接口 |
haha-admin/.../OrderController.java |
运营平台订单API |
haha-miniapp/.../OrderController.java |
小程序订单API |
haha-miniapp/.../PaymentController.java |
小程序支付API |
haha-miniapp/.../PayScoreController.java |
小程序支付分API |
haha-common/.../constant/OrderConstants.java |
订单常量定义 |
haha-common/.../enums/OrderStatus.java |
订单状态枚举 |
haha-common/.../enums/PayStatus.java |
支付状态枚举 |
haha-common/.../enums/PaymentChannel.java |
支付渠道枚举 |