From a3f2be078b0325ee0ec9ea1b7f5178b2c5fbefcc Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 7 Feb 2026 01:23:28 -0800 Subject: [PATCH] =?UTF-8?q?feat(payment):=20P2=20=E2=80=94=20=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E7=AE=A1=E7=90=86=E5=A2=9E=E5=BC=BA=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=8F=96=E6=B6=88=E8=AE=A2=E5=8D=95=E5=92=8C=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E8=AF=A6=E6=83=85=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 后端改动 ### 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 --- .../agents/tools/coordinator-tools.ts | 13 +++++ .../agents/tools/tool-execution-queue.ts | 1 + .../claude/tools/immigration-tools.service.ts | 47 ++++++++++++++++ .../payment/payment-client.service.ts | 53 +++++++++++++++++++ .../presentation/components/MessageBubble.tsx | 30 +++++++++++ 5 files changed, 144 insertions(+) diff --git a/packages/services/conversation-service/src/infrastructure/agents/tools/coordinator-tools.ts b/packages/services/conversation-service/src/infrastructure/agents/tools/coordinator-tools.ts index d32569f..1d7f491 100644 --- a/packages/services/conversation-service/src/infrastructure/agents/tools/coordinator-tools.ts +++ b/packages/services/conversation-service/src/infrastructure/agents/tools/coordinator-tools.ts @@ -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, // 写操作 + }, ]; // ============================================================ diff --git a/packages/services/conversation-service/src/infrastructure/agents/tools/tool-execution-queue.ts b/packages/services/conversation-service/src/infrastructure/agents/tools/tool-execution-queue.ts index b64ef33..47afee6 100644 --- a/packages/services/conversation-service/src/infrastructure/agents/tools/tool-execution-queue.ts +++ b/packages/services/conversation-service/src/infrastructure/agents/tools/tool-execution-queue.ts @@ -276,4 +276,5 @@ export const TOOL_CONCURRENCY_MAP: Record = { save_user_memory: false, // 写操作 generate_payment: false, // 创建支付订单 collect_assessment_info: false, // 写操作 + cancel_order: false, // 取消订单 }; diff --git a/packages/services/conversation-service/src/infrastructure/claude/tools/immigration-tools.service.ts b/packages/services/conversation-service/src/infrastructure/claude/tools/immigration-tools.service.ts index e175674..d489fd9 100644 --- a/packages/services/conversation-service/src/infrastructure/claude/tools/immigration-tools.service.ts +++ b/packages/services/conversation-service/src/infrastructure/claude/tools/immigration-tools.service.ts @@ -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, + ): Promise { + 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 */ diff --git a/packages/services/conversation-service/src/infrastructure/payment/payment-client.service.ts b/packages/services/conversation-service/src/infrastructure/payment/payment-client.service.ts index 2ae934a..2747a57 100644 --- a/packages/services/conversation-service/src/infrastructure/payment/payment-client.service.ts +++ b/packages/services/conversation-service/src/infrastructure/payment/payment-client.service.ts @@ -223,4 +223,57 @@ export class PaymentClientService implements OnModuleInit { return []; } } + + /** + * 获取单个订单详情 + */ + async getOrderDetail(orderId: string): Promise { + 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; + 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; + } + } } diff --git a/packages/web-client/src/features/chat/presentation/components/MessageBubble.tsx b/packages/web-client/src/features/chat/presentation/components/MessageBubble.tsx index 458685e..1887859 100644 --- a/packages/web-client/src/features/chat/presentation/components/MessageBubble.tsx +++ b/packages/web-client/src/features/chat/presentation/components/MessageBubble.tsx @@ -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 ( +
+
+ {result.success ? ( + + ) : ( + + )} + + {result.success ? result.message : result.error} + +
+ {result.orderId && ( +

订单号: {result.orderId}

+ )} +
+ ); + } + return null; }