feat(order): complete 7.1 order full lifecycle state transitions, 15m/30m/48h auto-timeouts, expert reject/apply-complete, and partial refund arbitration
这个提交包含在:
@@ -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 });
|
||||
|
||||
在新工单中引用
屏蔽一个用户