feat(order): complete 7.1 order full lifecycle state transitions, 15m/30m/48h auto-timeouts, expert reject/apply-complete, and partial refund arbitration
这个提交包含在:
@@ -248,4 +248,89 @@ describe('CompanionOrderService (TDD 测试套件: 线上游戏与同城陪玩·
|
||||
}).toThrow('无权核销该订单');
|
||||
});
|
||||
});
|
||||
|
||||
describe('3. 7.1 陪玩服务全生命周期超时自动流转与部分退款仲裁闭环', () => {
|
||||
it('待付款订单超过 15 分钟未支付自动流转为已取消 (CANCELLED)', () => {
|
||||
const order = orderService.createOrder({
|
||||
buyerId: 1001,
|
||||
expertId: 2001,
|
||||
track: CompanionTrack.ONLINE_GAME,
|
||||
serviceTitle: '王者双排',
|
||||
hours: 1,
|
||||
totalAmountCents: 3500,
|
||||
});
|
||||
|
||||
const future16Min = new Date(order.createdAt.getTime() + 16 * 60 * 1000);
|
||||
const res = orderService.processOrderTimeouts(future16Min);
|
||||
expect(res.cancelledIds).toContain(order.id);
|
||||
expect(order.status).toBe(OrderStatus.CANCELLED);
|
||||
});
|
||||
|
||||
it('待接单订单超过 30 分钟大神未接单自动流转为已退款 (REFUNDED)', () => {
|
||||
const order = orderService.createOrder({
|
||||
buyerId: 1001,
|
||||
expertId: 2001,
|
||||
track: CompanionTrack.ONLINE_GAME,
|
||||
serviceTitle: '王者双排',
|
||||
hours: 1,
|
||||
totalAmountCents: 3500,
|
||||
});
|
||||
orderService.payOrder(order.id, 1001);
|
||||
expect(order.status).toBe(OrderStatus.PENDING_ACCEPT);
|
||||
|
||||
const future31Min = new Date((order.paidAt || order.createdAt).getTime() + 31 * 60 * 1000);
|
||||
const res = orderService.processOrderTimeouts(future31Min);
|
||||
expect(res.refundedIds).toContain(order.id);
|
||||
expect(order.status).toBe(OrderStatus.REFUNDED);
|
||||
expect(order.refundAmountCents).toBe(3500);
|
||||
});
|
||||
|
||||
it('待确认订单超过 48 小时买家未确认自动流转为已完成 (COMPLETED)', () => {
|
||||
const order = orderService.createOrder({
|
||||
buyerId: 1001,
|
||||
expertId: 2001,
|
||||
track: CompanionTrack.ONLINE_GAME,
|
||||
serviceTitle: '王者双排',
|
||||
hours: 2,
|
||||
totalAmountCents: 7000,
|
||||
});
|
||||
orderService.payOrder(order.id, 1001);
|
||||
orderService.expertAccept(order.id, 2001);
|
||||
orderService.expertApplyFinish(order.id, 2001);
|
||||
expect(order.status).toBe(OrderStatus.PENDING_CONFIRM);
|
||||
|
||||
const future49Hours = new Date((order.finishAppliedAt || order.createdAt).getTime() + 49 * 60 * 60 * 1000);
|
||||
const res = orderService.processOrderTimeouts(future49Hours);
|
||||
expect(res.autoCompletedIds).toContain(order.id);
|
||||
expect(order.status).toBe(OrderStatus.COMPLETED);
|
||||
});
|
||||
|
||||
it('争议仲裁支持客服判定部分退款 (PARTIAL_REFUND) 并流转为已退款', () => {
|
||||
const order = orderService.createOrder({
|
||||
buyerId: 1001,
|
||||
expertId: 2001,
|
||||
track: CompanionTrack.ONLINE_GAME,
|
||||
serviceTitle: '王者三排',
|
||||
hours: 2,
|
||||
totalAmountCents: 10000,
|
||||
});
|
||||
orderService.payOrder(order.id, 1001);
|
||||
orderService.expertAccept(order.id, 2001);
|
||||
orderService.expertApplyFinish(order.id, 2001);
|
||||
orderService.buyerDispute(order.id, 1001, '只打了一半时长申请部分退款');
|
||||
|
||||
const arbOrder = orderService.arbitrate({
|
||||
orderId: order.id,
|
||||
operatorAdminId: 9001,
|
||||
decision: 'PARTIAL_REFUND',
|
||||
refundAmountCents: 5000,
|
||||
reason: '核实已完成1小时对局,判定退还买家一半费用',
|
||||
});
|
||||
|
||||
expect(arbOrder.status).toBe(OrderStatus.REFUNDED);
|
||||
expect(arbOrder.arbitrationDecision).toBe('PARTIAL_REFUND');
|
||||
expect(arbOrder.refundAmountCents).toBe(5000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户