feat(payment): P2 — 订单管理增强,支持取消订单和订单详情查询
## 后端改动
### PaymentClientService 增强
- 新增 `getOrderDetail(orderId)` — 获取完整订单信息(含支付详情)
- 新增 `cancelOrder(orderId)` — 取消未支付订单(调用 POST /orders/:id/cancel)
### 新增 cancel_order 工具
- 工具定义: 接收 orderId,取消未支付订单
- 实现: 调用 PaymentClientService.cancelOrder()
- 成功返回 { success, orderId, status, message }
- 失败返回友好错误信息(如"只有未支付的订单才能取消")
- coordinator-tools.ts 注册,concurrency map 标记 false(写操作)
## 前端改动
### cancel_order 结果渲染
- 成功: 绿色卡片 + CheckCircle 图标 + 成功提示
- 失败: 红色卡片 + AlertCircle 图标 + 错误原因
- 显示订单号
## 注意事项
- payment-service 暂无退款 API,cancel_order 仅限未支付订单
- 退款功能待 payment-service 侧实现后再扩展
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
db7964a461
commit
a3f2be078b
|
|
@ -386,6 +386,19 @@ export const DIRECT_TOOLS: ToolDefinition[] = [
|
|||
},
|
||||
isConcurrencySafe: true, // 只读
|
||||
},
|
||||
{
|
||||
name: 'cancel_order',
|
||||
description:
|
||||
'取消未支付的订单。仅限未支付状态可取消。用户明确要求取消时使用。',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
orderId: { type: 'string', description: '要取消的订单 ID' },
|
||||
},
|
||||
required: ['orderId'],
|
||||
},
|
||||
isConcurrencySafe: false, // 写操作
|
||||
},
|
||||
];
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -276,4 +276,5 @@ export const TOOL_CONCURRENCY_MAP: Record<string, boolean> = {
|
|||
save_user_memory: false, // 写操作
|
||||
generate_payment: false, // 创建支付订单
|
||||
collect_assessment_info: false, // 写操作
|
||||
cancel_order: false, // 取消订单
|
||||
};
|
||||
|
|
|
|||
|
|
@ -154,6 +154,20 @@ export class ImmigrationToolsService {
|
|||
properties: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'cancel_order',
|
||||
description: '取消未支付的订单。仅限状态为待支付的订单可以取消。当用户明确表示要取消订单时使用。',
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
orderId: {
|
||||
type: 'string',
|
||||
description: '要取消的订单ID',
|
||||
},
|
||||
},
|
||||
required: ['orderId'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'save_user_memory',
|
||||
description: '保存用户的重要信息到长期记忆,以便后续对话中记住用户情况',
|
||||
|
|
@ -306,6 +320,9 @@ export class ImmigrationToolsService {
|
|||
case 'query_order_history':
|
||||
return this.queryOrderHistory(context);
|
||||
|
||||
case 'cancel_order':
|
||||
return this.cancelOrder(input);
|
||||
|
||||
case 'save_user_memory':
|
||||
return this.saveUserMemory(input, context);
|
||||
|
||||
|
|
@ -615,6 +632,36 @@ export class ImmigrationToolsService {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel order — 取消未支付的订单
|
||||
*/
|
||||
private async cancelOrder(
|
||||
input: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
const { orderId } = input as { orderId: string };
|
||||
|
||||
console.log(`[Tool:cancel_order] Order: ${orderId}`);
|
||||
|
||||
if (!this.paymentClient) {
|
||||
return { success: false, error: '支付服务暂不可用' };
|
||||
}
|
||||
|
||||
const result = await this.paymentClient.cancelOrder(orderId);
|
||||
if (!result) {
|
||||
return {
|
||||
success: false,
|
||||
error: '取消订单失败。只有未支付的订单才能取消,请确认订单状态。',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
orderId: result.orderId,
|
||||
status: result.status,
|
||||
message: '订单已成功取消',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user memory - 调用 knowledge-service Memory API
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -223,4 +223,57 @@ export class PaymentClientService implements OnModuleInit {
|
|||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个订单详情
|
||||
*/
|
||||
async getOrderDetail(orderId: string): Promise<OrderInfo | null> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/orders/${orderId}`);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
`[PaymentClient] getOrderDetail failed: ${response.status}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = (await response.json()) as ApiResponse<OrderInfo>;
|
||||
return data.success ? data.data : null;
|
||||
} catch (error) {
|
||||
console.error('[PaymentClient] getOrderDetail error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单(仅限未支付的订单)
|
||||
*/
|
||||
async cancelOrder(
|
||||
orderId: string,
|
||||
): Promise<{ orderId: string; status: string } | null> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}/orders/${orderId}/cancel`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errText = await response.text();
|
||||
console.error(
|
||||
`[PaymentClient] cancelOrder failed: ${response.status} ${errText}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = (await response.json()) as ApiResponse<{
|
||||
orderId: string;
|
||||
status: string;
|
||||
}>;
|
||||
return data.success ? data.data : null;
|
||||
} catch (error) {
|
||||
console.error('[PaymentClient] cancelOrder error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -397,5 +397,35 @@ function ToolCallResult({
|
|||
}
|
||||
}
|
||||
|
||||
if (toolCall.name === 'cancel_order') {
|
||||
const result = toolCall.result as {
|
||||
success?: boolean;
|
||||
orderId?: string;
|
||||
message?: string;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(
|
||||
'mt-3 p-3 rounded-lg border',
|
||||
result.success ? 'bg-green-50 border-green-200' : 'bg-red-50 border-red-200',
|
||||
)}>
|
||||
<div className="flex items-center gap-2">
|
||||
{result.success ? (
|
||||
<CheckCircle className="w-4 h-4 text-green-500" />
|
||||
) : (
|
||||
<AlertCircle className="w-4 h-4 text-red-500" />
|
||||
)}
|
||||
<span className="text-sm">
|
||||
{result.success ? result.message : result.error}
|
||||
</span>
|
||||
</div>
|
||||
{result.orderId && (
|
||||
<p className="mt-1 text-xs text-secondary-500">订单号: {result.orderId}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue