feat(order): complete 7.1 order full lifecycle state transitions, 15m/30m/48h auto-timeouts, expert reject/apply-complete, and partial refund arbitration

这个提交包含在:
xcbyy
2026-10-14 15:52:56 +08:00
父节点 a14f27d641
当前提交 3bf38db8b6
共有 10 个文件被更改,包括 551 次插入 和 34 次删除

查看文件

@@ -1265,17 +1265,38 @@ app.post('/api/orders/:id/cancel', async (req: Request, res: Response) => {
}
});
// 支付订单
// 支付订单 (支持流转为待接单或履约中)
app.post('/api/orders/:id/pay', async (req: Request, res: Response) => {
try {
const order = await repo.updateOrderStatus(String(req.params.id), '履约中');
res.json({ code: 20000, data: order, message: '支付成功,服务已进入履约中' });
const targetStatus = req.body?.targetStatus || '履约中';
const order = await repo.updateOrderStatus(String(req.params.id), targetStatus);
res.json({ code: 20000, data: order, message: `支付成功,订单已进入${targetStatus}` });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 确认完成验收
// 大神拒单自动全额退款 (待接单 -> 已退款)
app.post('/api/orders/:id/reject', async (req: Request, res: Response) => {
try {
const order = await repo.rejectOrder(String(req.params.id), req.body);
res.json({ code: 20000, data: order, message: '已拒单,订单款项已自动原路退还买家' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 大神点击申请完成服务 (服务中 -> 待确认,进入 48 小时买家确认窗口)
app.post('/api/orders/:id/apply-complete', async (req: Request, res: Response) => {
try {
const order = await repo.applyCompleteOrder(String(req.params.id));
res.json({ code: 20000, data: order, message: '已发起完成服务申请,等待买家确认验收(48小时自动确认)' });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });
}
});
// 买家确认完成验收 (待确认 -> 已完成,触发资金解冻结算)
app.post('/api/orders/:id/complete', async (req: Request, res: Response) => {
try {
const order = await repo.updateOrderStatus(String(req.params.id), '已完成');
@@ -1285,6 +1306,16 @@ app.post('/api/orders/:id/complete', async (req: Request, res: Response) => {
}
});
// 扫描处理超时订单 (15m未支付自动取消 / 30m未接单自动退款 / 48h待确认自动完成)
app.post('/api/orders/process-timeouts', async (_req: Request, res: Response) => {
try {
const result = await repo.processOrderTimeouts();
res.json({ code: 20000, data: result, message: '订单超时自动流转扫描完毕' });
} catch (err: any) {
res.status(500).json({ code: 50000, message: err.message });
}
});
// 申请退款/售后 (写入仲裁工单:退款理由 + 详细说明 + 凭证截图)
app.post('/api/orders/:id/refund', async (req: Request, res: Response) => {
try {
@@ -1374,13 +1405,13 @@ app.get('/api/arbitrations', async (_req: Request, res: Response) => {
app.post('/api/arbitrations/:id/handle', async (req: Request, res: Response) => {
try {
const arbId = String(req.params.id);
const { decision, operatorRole } = req.body; // 'refund_full' | 'reject' | 'deduct_credit'
const { decision, refundAmount, operatorRole } = req.body; // 'refund_full' | 'refund_partial' | 'reject' | 'deduct_credit'
const headerRole = req.headers['x-user-role'];
const callerRole = operatorRole || headerRole || 'admin';
if (callerRole === 'user' || callerRole === 'expert' || callerRole === 'PLAYER' || callerRole === 'EXPERT') {
return res.status(403).json({ code: 40300, message: '权限不足:买家与大神不可越权执行管理员仲裁' });
}
const result = await repo.handleArbitration(arbId, decision);
const result = await repo.handleArbitration(arbId, decision, refundAmount !== undefined ? Number(refundAmount) : undefined);
res.json({ code: 20000, data: result, message: `仲裁处理完毕: ${result.status}` });
} catch (err: any) {
res.status(400).json({ code: 40000, message: err.message });