AI Agent 处理微信支付投诉-项目实战-自动回复并发送消息到飞书
花了2天时间,做了一个AI智能体处理微信投诉的小项目!
核心功能:当用户首次投诉后,十分钟内由AI做出回复,同时把投诉单发送到飞书群,由飞书群里的运营/售后处理
核心流程
用户初次投诉(待处理)->AI自动回复(处理中)->用户再次留言->判断/接合AI判断用户留言是否满意->如果不满意则继续AI回复->如果满意,则关闭投诉单!
针对回复的投诉单->24小时内用户不再留言,则主动关闭投诉单!
截图:


核心代码:
智能体:
using AutoGen; using AutoGen.Core; using AutoGen.OpenAI; using AutoGen.OpenAI.Extension; using Google.Cloud.Iam.V1; using OpenAI; using PowerComplaint.Tools; using swapExternal.Complaints; using System; using System.ClientModel; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace PowerComplaint.Agents { /// <summary> /// 投诉处理 Agent 工厂(参考天才卧龙文章) /// </summary> public class ComplaintAgentFactory { private readonly OpenAIClient _client; private readonly string _modelId; private readonly string _mchid; private readonly IFeishuNotifyService _notifyService; public ComplaintAgentFactory( string apiKey, string endpoint, string modelId, string mchid, IFeishuNotifyService notifyService) { _client = new OpenAIClient( new ApiKeyCredential(apiKey), new OpenAIClientOptions { Endpoint = new Uri(endpoint) }); _modelId = modelId; _mchid = mchid; _notifyService = notifyService; Console.WriteLine("✅ ComplaintAgentFactory 初始化完成"); Console.WriteLine($"使用模型:{modelId}"); } /// <summary> /// 创建用户代理(发起指令) /// </summary> public IAgent CreateUserProxy() { Console.WriteLine("🔧 创建用户代理..."); return new UserProxyAgent( name: "User", humanInputMode: HumanInputMode.NEVER) // 自动模式 .RegisterPrintMessage(); } /// <summary> /// 创建投诉查询 Agent /// </summary> public IAgent CreateComplaintFetcher() { Console.WriteLine("🔧 创建投诉查询 Agent..."); var tools = new ComplaintTools(_mchid, _notifyService); // ① 创建 FunctionContract var functionContract = new FunctionContract { Name = "GetPendingComplaints", Description = "查询指定日期范围内的待处理投诉列表,只返回 PENDING 状态的投诉单", Parameters = new List<FunctionParameterContract> { new FunctionParameterContract { Name = "beginDate", Description = "开始日期(YYYY-MM-DD)", IsRequired = true, ParameterType = typeof(string) }, new FunctionParameterContract { Name = "endDate", Description = "结束日期(YYYY-MM-DD),与开始日期跨度不超过 30 天", IsRequired = true, ParameterType = typeof(string) }, new FunctionParameterContract { Name = "limit", Description = "分页大小(默认 50,最大 50)", IsRequired = false, ParameterType = typeof(int), DefaultValue = 50 } } }; // ② 创建 functionMap(函数执行逻辑) var functionMap = new Dictionary<string, Func<string, Task<string>>> { { functionContract.Name, async (args) => { var json = JsonDocument.Parse(args); var beginDate = json.RootElement.GetProperty("beginDate").GetString(); var endDate = json.RootElement.GetProperty("endDate").GetString(); var limit = json.RootElement.TryGetProperty("limit", out var limitElem) ? limitElem.GetInt32() : 50; return await tools.GetPendingComplaints(beginDate!, endDate!, limit); } } }; // ③ 创建 FunctionCallMiddleware var functionCallMiddleware = new FunctionCallMiddleware( functions: new[] { functionContract }, functionMap: functionMap); // ④ 创建 Agent 并注册中间件 var agent = new OpenAIChatAgent( name: "ComplaintFetcher", systemMessage: @"你是投诉查询助手,负责查询待处理的投诉列表。 职责: 1. 调用 GetPendingComplaints 查询指定日期范围内的待处理投诉 2. 只返回 PENDING 状态的投诉单 3. 日期格式:YYYY-MM-DD,查询范围最大 30 天", chatClient: _client.GetChatClient(_modelId)); return agent .RegisterMessageConnector() .RegisterMiddleware(functionCallMiddleware) .RegisterPrintMessage(); } /// <summary> /// 创建投诉分析 Agent /// </summary> public IAgent CreateComplaintAnalyzer() { Console.WriteLine("🔧 创建投诉分析 Agent..."); return new OpenAIChatAgent( name: "ComplaintAnalyzer", systemMessage: @"你是投诉分析专家,负责分析投诉内容并判断优先级。 投诉单状态流转: 1. PENDING(待处理)→ 用户投诉或继续投诉 2. PROCESSING(处理中)→ 商户已回复 3. PROCESSED(已处理完成)→ 处理完成 优先级判断规则: - need_immediate_service=true → 高优先级(需即时服务) - user_complaint_times>1 → 高优先级(多次投诉) - problem_type=REFUND → 高优先级(退款申请) - user_tag_list 包含 HIGH_RISK → 高优先级(高风险用户) - 其他 → 普通优先级 输出格式: - 投诉单号 - 优先级(高/中/普通) - 建议处理方案", chatClient: _client.GetChatClient(_modelId)) .RegisterMessageConnector() .RegisterPrintMessage(); } /// <summary> /// 创建回复生成 Agent /// </summary> public IAgent CreateResponseGenerator() { Console.WriteLine("🔧 创建回复生成 Agent..."); var tools = new ComplaintTools(_mchid, _notifyService); // ① 创建 FunctionContract var functionContract = new FunctionContract { Name = "ResponseComplaint", Description = "回复用户投诉,提交处理方案。只有 PENDING 或 PROCESSING 状态的投诉单才能回复", Parameters = new List<FunctionParameterContract> { new FunctionParameterContract { Name = "complaintId", Description = "投诉单号", IsRequired = true, ParameterType = typeof(string) }, new FunctionParameterContract { Name = "responseContent", Description = "回复内容(20-40 字,包含歉意、处理方案)", IsRequired = true, ParameterType = typeof(string) } } }; // ② 创建 functionMap var functionMap = new Dictionary<string, Func<string, Task<string>>> { { functionContract.Name, async (args) => { var json = JsonDocument.Parse(args); var complaintId = json.RootElement.GetProperty("complaintId").GetString(); var responseContent = json.RootElement.GetProperty("responseContent").GetString(); return await tools.ResponseComplaint(complaintId!, responseContent!); } } }; // ③ 创建 FunctionCallMiddleware var functionCallMiddleware = new FunctionCallMiddleware( functions: new[] { functionContract }, functionMap: functionMap); // ④ 创建 Agent 并注册中间件 var agent = new OpenAIChatAgent( name: "ResponseGenerator", systemMessage: @"你是客服回复专家,负责生成专业的投诉回复话术。 【回复原则】 1. 态度诚恳,表达歉意 2. 说明处理方案 3. 给出合理承诺(不要过于具体) 4. 引导用户关闭服务单 5. 字数控制在 20-40 字 【禁止事项】 ❌ 不要承诺具体时间(如""5 分钟内""、""2 小时内"") ❌ 不要承诺具体金额(如""赔偿 100 元"") ❌ 不要承诺具体人员(如""技术总监亲自处理"") 【推荐表达】 ✅ ""尽快"" 而不是 ""5 分钟内"" ✅ ""及时"" 而不是 ""2 小时内"" ✅ ""专人跟进"" 而不是 ""技术总监"" ✅ ""相应处理"" 而不是 ""全额赔偿"" 【示例】 尊敬的客户,您好!非常抱歉给您带来不便。我们已安排专人核实处理,将尽快与您联系确认详情。如确属服务问题,我们将优先处理并给您满意答复。感谢您的理解与支持!""", chatClient: _client.GetChatClient(_modelId)); return agent .RegisterMessageConnector() .RegisterMiddleware(functionCallMiddleware) .RegisterPrintMessage(); } /// <summary> /// 创建完成跟踪 Agent(带 24 小时等待期校验) /// </summary> public IAgent CreateCompletionTracker() { var tools = new ComplaintTools(_mchid, _notifyService); // ① 创建 FunctionContract var completeFunction = new FunctionContract { Name = "CompleteComplaint", Description = "反馈投诉处理完成。只有满足以下条件才能标记完成:1. 状态为 PROCESSING 2. 已回复用户 3. 回复后 24 小时内用户未继续投诉", Parameters = new List<FunctionParameterContract> { new FunctionParameterContract { Name = "complaintId", Description = "投诉单号", IsRequired = true, ParameterType = typeof(string) } } }; var detailFunction = new FunctionContract { Name = "GetComplaintDetail", Description = "查询投诉详情,包含状态、投诉内容、用户联系方式等", Parameters = new List<FunctionParameterContract> { new FunctionParameterContract { Name = "complaintId", Description = "投诉单号", IsRequired = true, ParameterType = typeof(string) } } }; // ✅ 新增:回复函数 var responseFunction = new FunctionContract { Name = "ResponseComplaint", Description = "再次回复用户投诉。当用户有新留言且表示不满时调用", Parameters = new List<FunctionParameterContract> { new FunctionParameterContract { Name = "complaintId", Description = "投诉单号", IsRequired = true, ParameterType = typeof(string) }, new FunctionParameterContract { Name = "responseContent", Description = "回复内容(针对用户留言正面回应,20-40 字)", IsRequired = true, ParameterType = typeof(string) } } }; // ② 创建 functionMap var functionMap = new Dictionary<string, Func<string, Task<string>>> { { completeFunction.Name, async (args) => { var json = JsonDocument.Parse(args); var complaintId = json.RootElement.GetProperty("complaintId").GetString(); try { // ① 查询投诉详情,检查状态 var detail = await complaintsHelper.GetComplaintDetailAsync(complaintId!); if (detail == null) { return $"❌ 查询投诉详情失败:{complaintId}"; } if (detail.ComplaintState != "PROCESSING") { return $"❌ 投诉单状态为 {detail.ComplaintState},只有 PROCESSING(处理中)状态才能标记完成"; } // ③ 查询协商历史 var history = await complaintsHelper.GetComplaintNegotiationHistoryAsync(complaintId, limit: 50, offset: 0); if (history == null || history.Data == null || history.Data.Count == 0) { return "❌ 协商历史为空,请先回复用户后再标记完成"; } // ④ 检查是否有商户回复记录 var merchantResponses = history.Data .Where(h => h.OperateType == "MERCHANT_RESPONSE") .ToList(); if (!merchantResponses.Any()) { return "❌ 未找到商户回复记录,请先回复用户后再标记完成"; } // ⑤ 获取最后一次商户回复时间 var lastMerchantResponse = merchantResponses .OrderByDescending(h => h.OperateTime) .First(); Console.WriteLine($"[完成校验] 最后商户回复时间:{lastMerchantResponse.OperateTime}"); // ⑥ 检查是否有用户继续投诉(在最后一次商户回复之后) var lastUserContinueComplaint = history.Data .LastOrDefault(h => h.OperateType == "USER_CONTINUE_COMPLAINT" && DateTimeOffset.TryParse(h.OperateTime, out var continueTime) && DateTimeOffset.TryParse(lastMerchantResponse.OperateTime, out var responseTime) && continueTime > responseTime); if (lastUserContinueComplaint != null) { Console.WriteLine($"[完成校验] 用户继续投诉时间:{lastUserContinueComplaint.OperateTime}"); return "⚠️ 用户已继续投诉,说明对上次回复不满意,建议再次回复后再标记完成"; } // ⑦ 检查是否已过 24 小时等待期 if (DateTimeOffset.TryParse(lastMerchantResponse.OperateTime, out var merchantResponseTime)) { var timeSinceResponse = DateTimeOffset.Now - merchantResponseTime; var hoursSinceResponse = timeSinceResponse.TotalHours; Console.WriteLine($"[完成校验] 回复后已过:{hoursSinceResponse:F2} 小时"); if (hoursSinceResponse < 24) { var remainingHours = 24 - hoursSinceResponse; return $"⏳ 回复后仅过去 {hoursSinceResponse:F1} 小时,需等待 24 小时后再标记完成(还需等待 {remainingHours:F1} 小时)"; } Console.WriteLine($"[完成校验] ✅ 已过 24 小时等待期"); } else { Console.WriteLine($"[完成校验] ⚠️ 无法解析回复时间,跳过 24 小时检查"); } Console.WriteLine("[完成校验] ✅ 校验通过,可以标记完成"); // ⑧ 调用完成接口 var success = await complaintsHelper.CompleteComplaintAsync(complaintId, _mchid); if (success) { Console.WriteLine("[完成校验] ✅ 处理完成,状态已从 PROCESSING 变为 PROCESSED"); return "✅ 处理完成,系统将向用户下发满意度评价"; } else { Console.WriteLine("[完成校验] ❌ 处理完成失败"); return "❌ 处理完成失败"; } } catch (Exception ex) { return $"❌ 异常:{ex.Message}"; } } }, { detailFunction.Name, async (args) => { var json = JsonDocument.Parse(args); var complaintId = json.RootElement.GetProperty("complaintId").GetString(); return await tools.GetComplaintDetail(complaintId!); } }, // ✅ 新增:回复函数执行逻辑 { responseFunction.Name, async (args) => { var json = JsonDocument.Parse(args); var complaintId = json.RootElement.GetProperty("complaintId").GetString(); var responseContent = json.RootElement.GetProperty("responseContent").GetString(); try { var success = await complaintsHelper.ResponseComplaintSimpleAsync(complaintId!, responseContent!, _mchid); if (success) { Console.WriteLine($"[回复校验] ✅ 已回复投诉 {complaintId}"); return $"✅ 已回复投诉 {complaintId},状态已变为 PROCESSING"; } else { Console.WriteLine($"[回复校验] ❌ 回复失败:{complaintId}"); return $"❌ 回复失败:{complaintId}"; } } catch (Exception ex) { return $"❌ 异常:{ex.Message}"; } } } }; // ③ 创建 FunctionCallMiddleware var functionCallMiddleware = new FunctionCallMiddleware( functions: new[] { completeFunction, detailFunction, responseFunction }, // ✅ 添加 responseFunction functionMap: functionMap); // ④ 创建 Agent 并注册中间件 var agent = new OpenAIChatAgent( name: "CompletionTracker", systemMessage: @"你是投诉完成跟踪助手,负责检查 PROCESSING 状态的投诉并执行操作。 【检查流程】 1. 调用 GetComplaintDetail 查询投诉详情和状态 2. 查询协商历史,检查用户最新留言(incoming_user_response) 3. 检查最后一次商户回复时间,计算是否已满 24 小时 【执行逻辑 - 必须调用函数】 - 如果用户有留言且不满(""还没好""、""不满意""、""投诉""、""曝光""、""抓紧退款"")→ 调用 ResponseComplaint 再次回复 - 如果用户有留言且满意(""好的""、""谢谢""、""解决了""、""好了""、""OK""、""可以"")→ 调用 CompleteComplaint 标记完成 - 如果用户无留言且已满 24 小时 → 调用 CompleteComplaint 标记完成 - 如果用户无留言但未满 24 小时 → 返回等待提示(不调用函数) 【重要】 - 不要只返回分析结果,必须调用相应函数执行操作 - 每次只处理一个投诉单,调用一个函数 现在请检查以下 PROCESSING 投诉:", chatClient: _client.GetChatClient(_modelId)); return agent .RegisterMessageConnector() .RegisterMiddleware(functionCallMiddleware) .RegisterPrintMessage(); } } }
工具、Function:
using AutoGen.Core; using swapExternal.Complaints; using swapExternal.WeScore; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using WuAnExternal; namespace PowerComplaint.Tools { /// <summary> /// 投诉处理工具类(供 Agent 调用) /// </summary> public class ComplaintTools { private readonly string _mchid; private readonly IFeishuNotifyService _notifyService; // ✅ 添加 public ComplaintTools(string mchid, IFeishuNotifyService notifyService) { _mchid = mchid; _notifyService = notifyService; } /// <summary> /// 查询待处理投诉列表-最大查询一月时间 /// </summary> [Function] public async Task<string> GetPendingComplaints( [Description("开始日期(YYYY-MM-DD)")] string beginDate, [Description("结束日期(YYYY-MM-DD)")] string endDate, [Description("分页大小(默认 50)")] int limit = 50) { var result = await complaintsHelper.GetComplaintListAsync( beginDate, endDate, limit, 0, _mchid); if (result == null) return "查询失败"; // 只返回待处理的投诉 var pending = result.Data .Select(c => new { c.ComplaintId, c.ComplaintTime, c.ComplaintDetail, c.ProblemType, c.NeedImmediateService, c.ComplaintState, // ✅ 添加 status 字段 }) .ToList(); return System.Text.Json.JsonSerializer.Serialize(pending); } /// <summary> /// 回复用户投诉(支持多次回复) /// </summary> [Function] public async Task<string> ResponseComplaint( [Description("投诉单号")] string complaintId, [Description("回复内容")] string responseContent) { try { Console.WriteLine($"[回复校验] 投诉单号:{complaintId}"); // ① 先查询投诉详情,检查状态 var detail = await complaintsHelper.GetComplaintDetailAsync(complaintId); if (detail == null) { return $"❌ 查询投诉详情失败:{complaintId}"; } Console.WriteLine($"[回复校验] 当前状态:{detail.ComplaintState}"); // ② 校验状态:PENDING 或 PROCESSING 都可以回复 if (detail.ComplaintState != "PENDING" && detail.ComplaintState != "PROCESSING") { return $"❌ 投诉单状态为 {detail.ComplaintState},只有 PENDING(待处理)或 PROCESSING(处理中)状态才能回复"; } // ③ 查询协商历史 var history = await complaintsHelper.GetComplaintNegotiationHistoryAsync(complaintId, limit: 50, offset: 0); if (history?.Data == null || history.Data.Count == 0) { return $"❌ 未找到协商历史记录"; } // ④ 检查是否有用户继续投诉 var lastUserContinueComplaint = history.Data .LastOrDefault(h => h.OperateType == "USER_CONTINUE_COMPLAINT"); var lastMerchantResponse = history.Data .LastOrDefault(h => h.OperateType == "MERCHANT_RESPONSE"); // ⑤ 时间比较(字符串转 DateTime) if (lastUserContinueComplaint != null && lastMerchantResponse != null && DateTimeOffset.TryParse(lastUserContinueComplaint.OperateTime, out var continueTime) && DateTimeOffset.TryParse(lastMerchantResponse.OperateTime, out var responseTime) && continueTime > responseTime) { Console.WriteLine("[回复校验] ⚠️ 用户继续投诉,需要再次回复"); Console.WriteLine($"[回复校验] 继续投诉时间:{continueTime:yyyy-MM-dd HH:mm:ss}"); Console.WriteLine($"[回复校验] 商户回复时间:{responseTime:yyyy-MM-dd HH:mm:ss}"); } // ⑥ 调用回复接口 var success = await complaintsHelper.ResponseComplaintSimpleAsync( complaintId, _mchid, responseContent); if (success) { var Phone = complaintsHelper.DecryptPayerPhone(detail.PayerPhone, WechatConstants.WxPayCertPath, WeScoreHelper.MCH_ID); // ③ 发送飞书通知 await _notifyService.SendComplaintReceivedAsync( complaintId: complaintId, detail: detail.ComplaintDetail, OrderNo: detail.ComplaintOrderInfo?.FirstOrDefault()?.OutTradeNo ?? "", Phone: Phone, Content: responseContent ); return "✅ 回复成功,投诉单状态已更新为处理中"; } else { return "❌ 回复失败"; } } catch (Exception ex) { return $"❌ 异常:{ex.Message}"; } } /// <summary> /// 反馈投诉处理完成(带状态校验) /// 只有状态为 PROCESSING(处理中)的投诉单才能标记完成 /// 标记完成后,状态将从 PROCESSING 变为 PROCESSED /// </summary> [Function] public async Task<string> CompleteComplaint( [Description("投诉单号")] string complaintId) { try { Console.WriteLine($"[完成校验] 投诉单号:{complaintId}"); // ① 先查询投诉详情,检查状态 var detail = await complaintsHelper.GetComplaintDetailAsync(complaintId); if (detail == null) { return $"❌ 查询投诉详情失败:{complaintId}"; } Console.WriteLine($"[完成校验] 当前状态:{detail.ComplaintState}"); // ② 校验状态:必须是 PROCESSING(处理中) if (detail.ComplaintState != "PROCESSING") { return $"❌ 投诉单状态为 {detail.ComplaintState},只有 PROCESSING(处理中)状态才能标记完成"; } // ③ 查询协商历史,检查是否有回复记录 var history = await complaintsHelper.GetComplaintNegotiationHistoryAsync(complaintId, limit: 50, offset: 0); if (history == null || history.Data == null || history.Data.Count == 0) { return "❌ 协商历史为空,请先回复用户后再标记完成"; } Console.WriteLine($"[完成校验] 协商历史条数:{history.Data.Count}"); // ④ 检查是否有商户回复记录 var hasMerchantResponse = history.Data.Any(h => h.OperateType == "MERCHANT_RESPONSE"); if (!hasMerchantResponse) { return "❌ 未找到商户回复记录,请先回复用户后再标记完成"; } // ⑤ 检查是否有用户继续投诉(说明用户不满意) var lastUserContinueComplaint = history.Data .LastOrDefault(h => h.OperateType == "USER_CONTINUE_COMPLAINT"); var lastMerchantResponse = history.Data .LastOrDefault(h => h.OperateType == "MERCHANT_RESPONSE"); if (lastUserContinueComplaint != null && lastMerchantResponse != null && DateTimeOffset.TryParse(lastUserContinueComplaint.OperateTime, out var continueTime) && DateTimeOffset.TryParse(lastMerchantResponse.OperateTime, out var responseTime) && continueTime > responseTime) { return "⚠️ 用户已继续投诉,说明对上次回复不满意,建议再次回复后再标记完成"; } Console.WriteLine("[完成校验] ✅ 校验通过,可以标记完成"); // ⑥ 调用完成接口 var success = await complaintsHelper.CompleteComplaintAsync(complaintId, _mchid); if (success) { Console.WriteLine("[完成校验] ✅ 处理完成,状态已从 PROCESSING 变为 PROCESSED"); return "✅ 处理完成,系统将向用户下发满意度评价"; } else { Console.WriteLine("[完成校验] ❌ 处理完成失败"); return "❌ 处理完成失败"; } } catch (Exception ex) { return $"❌ 异常:{ex.Message}"; } } /// <summary> /// 查询投诉详情 /// </summary> [Function] public async Task<string> GetComplaintDetail( [Description("投诉单号")] string complaintId) { var detail = await complaintsHelper.GetComplaintDetailAsync(complaintId); if (detail == null) return "查询失败"; return System.Text.Json.JsonSerializer.Serialize(new { detail.ComplaintId, detail.ComplaintState, detail.ComplaintDetail, detail.PayerPhone, detail.ProblemDescription, detail.NeedImmediateService }); } } }
飞书消息-发送
using Microsoft.Extensions.Logging; using swapCommon; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace PowerComplaint { /// <summary> /// 飞书通知服务实现 /// </summary> public class FeishuNotifyService : IFeishuNotifyService { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger<FeishuNotifyService> _logger; private string _tenantAccessToken; private DateTime _tokenExpireTime; public FeishuNotifyService(IHttpClientFactory httpClientFactory, ILogger<FeishuNotifyService> logger) { _httpClientFactory = httpClientFactory; _logger = logger; } /// <summary> /// 获取飞书 tenant_access_token /// </summary> private async Task<string> GetTenantAccessTokenAsync() { if (!string.IsNullOrEmpty(_tenantAccessToken) && DateTime.Now < _tokenExpireTime) { return _tenantAccessToken; } try { var client = _httpClientFactory.CreateClient(); var tokenUrl = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"; _logger.LogInformation("正在获取飞书 Token..."); // ⚠️ 注意:字段名是 app_id 不是 app_key var requestBody = new { app_id = ConstParm.FeShuAppID, app_secret = ConstParm.FeShuAppSecret }; var json = JsonSerializer.Serialize(requestBody); var httpContent = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync(tokenUrl, httpContent); var result = await response.Content.ReadAsStringAsync(); _logger.LogInformation("Token 响应:{Result}", result); var resultJson = JsonDocument.Parse(result); var code = resultJson.RootElement.GetProperty("code").GetInt32(); if (code == 0) { _tenantAccessToken = resultJson.RootElement.GetProperty("tenant_access_token").GetString(); _tokenExpireTime = DateTime.Now.AddHours(1); _logger.LogInformation("飞书 Token 获取成功,有效期 2 小时"); return _tenantAccessToken; } else { var msg = resultJson.RootElement.GetProperty("msg").GetString(); _logger.LogError("飞书 Token 获取失败:Code={Code}, Msg={Msg}", code, msg); return null; } } catch (Exception ex) { _logger.LogError(ex, "飞书 Token 获取异常"); return null; } } /// <summary> /// 发送飞书消息(文本格式) /// </summary> private async Task SendFeishuMessageAsync(string title, string content) { try { var token = await GetTenantAccessTokenAsync(); if (string.IsNullOrEmpty(token)) { _logger.LogWarning("飞书 Token 为空,跳过发送"); return; } var client = _httpClientFactory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); // 飞书消息 API URL var messageUrl = $"https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id"; // 消息体 var message = new { receive_id = ConstParm.FeShuChatId, msg_type = "text", content = JsonSerializer.Serialize(new { text = $"【{title}】\n\n{content}\n\n发送时间:{DateTime.Now:yyyy-MM-dd HH:mm:ss}" }) }; var json = JsonSerializer.Serialize(message); var httpContent = new StringContent(json, Encoding.UTF8, "application/json"); _logger.LogInformation("正在发送飞书通知:{Title}", title); var response = await client.PostAsync(messageUrl, httpContent); var result = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { var resultJson = JsonDocument.Parse(result); var code = resultJson.RootElement.GetProperty("code").GetInt32(); if (code == 0) _logger.LogInformation("飞书通知发送成功:{Title}", title); else _logger.LogWarning("飞书通知返回错误码:Code={Code}, Msg={Msg}", code, resultJson.RootElement.GetProperty("msg").GetString()); } else { _logger.LogWarning("飞书通知发送失败:StatusCode={StatusCode}, Content={Content}", response.StatusCode, result); } } catch (Exception ex) { _logger.LogError(ex, "飞书通知发送异常"); } } /// <summary> /// /// </summary> /// <param name="complaintId"></param> /// <param name="detail">投诉内容</param> /// <param name="OrderNo">充电宝订单号</param> /// <param name="Phone">投诉人联系方式</param> /// <param name="Content">AI响应内容</param> /// <returns></returns> public async Task SendComplaintReceivedAsync(string complaintId, string detail,string OrderNo,string Phone,string Content) { var content = $@"投诉单号:{complaintId} 订单编号:{OrderNo} 联系方式:{Phone} 投诉内容:{detail?.Substring(0, Math.Min(200, detail?.Length ?? 0))} 回复详情:{Content?.Substring(0, Math.Min(200, Content?.Length ?? 0))} 状态:待介入 请尽快处理!"; await SendFeishuMessageAsync("🔔 充电宝投诉", content); } public async Task SendComplaintRepliedAsync(string complaintId, string response) { var content = $@"投诉单号:{complaintId} 回复内容:{response?.Substring(0, Math.Min(200, response?.Length ?? 0))} 状态:已回复,等待 24 小时无继续投诉将自动完结 已通知运营人员关注。"; await SendFeishuMessageAsync("✅ 投诉已回复", content); } public async Task SendComplaintCompletedAsync(string complaintId) { var content = $@"投诉单号:{complaintId} 状态:已标记完成 结果:系统将向用户下发满意度评价 处理流程已结束。"; await SendFeishuMessageAsync("🎉 投诉已完成", content); } public async Task SendManualReviewNeededAsync(string complaintId, string reason) { var content = $@"投诉单号:{complaintId} 原因:{reason} 状态:需要人工介入 请立即登录系统处理!"; await SendFeishuMessageAsync("⚠️ 需要人工介入", content); } } /// <summary> /// 飞书通知服务接口 /// </summary> public interface IFeishuNotifyService { /// <summary> /// /// </summary> /// <param name="complaintId"></param> /// <param name="detail">投诉内容</param> /// <param name="OrderNo">充电宝订单号</param> /// <param name="Phone">投诉人联系方式</param> /// <param name="Content">AI响应内容</param> /// <returns></returns> Task SendComplaintReceivedAsync(string complaintId, string detail, string OrderNo, string Phone, string Content); Task SendComplaintRepliedAsync(string complaintId, string response); Task SendComplaintCompletedAsync(string complaintId); Task SendManualReviewNeededAsync(string complaintId, string reason); } }
定时执行任务类
using AutoGen; using AutoGen.Core; using Google.Cloud.AIPlatform.V1; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using PowerComplaint.Agents; using swapCommon; using swapExternal.Complaints; using swapExternal.WeScore; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using WuAnExternal; namespace PowerComplaint { public class WeScoreService : BackgroundService { private readonly ILogger<WeScoreService> _logger; private readonly IFeishuNotifyService notifyService; private readonly IHostApplicationLifetime _lifetime; private ComplaintAgentFactory _agentFactory; public WeScoreService( ILogger<WeScoreService> logger, IHostApplicationLifetime lifetime, IFeishuNotifyService notifyService) { _logger = logger; _lifetime = lifetime; this.notifyService = notifyService; } public override Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("🚀 WeScore 服务启动..."); if (!cancellationToken.IsCancellationRequested) { // 初始化 Agent 工厂 _agentFactory = new ComplaintAgentFactory( apiKey: ConstParm.apiKey, // 从配置读取 endpoint: ConstParm.endpoint, // 从配置读取 modelId: ConstParm.modelId, mchid: WeScoreHelper.MCH_ID, // 从配置读取 notifyService: notifyService ); } return base.StartAsync(cancellationToken); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("✅ WeScore 服务运行中,每 10 分钟检查一次投诉..."); while (!stoppingToken.IsCancellationRequested) { try { _logger.LogInformation($"⏰ [{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 开始检查投诉..."); // 执行智能体编排 await RunAgentOrchestrationAsync(stoppingToken); _logger.LogInformation($"✅ [{DateTime.Now:yyyy-MM-dd HH:mm:ss}] 本轮检查完成"); } catch (Exception ex) { _logger.LogError(ex, "❌ 投诉处理异常"); } // 等待 10 分钟 await Task.Delay(1000 * 60 * 10, stoppingToken); } } /// <summary> /// 智能体编排流程 /// </summary> private async Task RunAgentOrchestrationAsync(CancellationToken cancellationToken) { _logger.LogInformation("🤖 开始手动路由编排..."); // 创建各个 Agent var fetcher = _agentFactory.CreateComplaintFetcher(); var analyzer = _agentFactory.CreateComplaintAnalyzer(); var responder = _agentFactory.CreateResponseGenerator(); var tracker = _agentFactory.CreateCompletionTracker(); // 步骤 1:查询投诉列表 _logger.LogInformation("📋 步骤 1:查询投诉..."); var fetchMessage = new AutoGen.Core.TextMessage( role: Role.User, content: $"查询 {DateTime.Now.AddDays(-7):yyyy-MM-dd} 到 {DateTime.Now:yyyy-MM-dd} 的投诉列表", from: "User" ); var fetchResult = await fetcher.GenerateReplyAsync( messages: new List<IMessage>{ fetchMessage }, cancellationToken: cancellationToken); var complaintsJson = fetchResult.GetContent(); // 步骤 2:手动路由 var complaints = JsonSerializer.Deserialize<List<ComplaintInfo>>(complaintsJson); var pendingComplaints = complaints.Where(x => x.ComplaintState == "PENDING").ToList(); var processingComplaints = complaints.Where(x => x.ComplaintState == "PROCESSING").ToList(); _logger.LogInformation($"PENDING: {pendingComplaints.Count}条,PROCESSING: {processingComplaints.Count}条"); // 步骤 3:处理 PENDING 投诉 if (pendingComplaints.Any()) { _logger.LogInformation("✍️ 步骤 3:处理 PENDING 投诉..."); var pendingJson = JsonSerializer.Serialize(pendingComplaints); var analyzeMessage = new AutoGen.Core.TextMessage( role: Role.User, content: $"分析以下 PENDING 投诉的优先级:{pendingJson}", from: "User" ); var analyzeResult = await analyzer.GenerateReplyAsync( messages: new[] { analyzeMessage }, cancellationToken: cancellationToken); _logger.LogInformation($"分析结果:{analyzeResult.GetContent()}"); var responseMessage = new AutoGen.Core.TextMessage( role: Role.User, content: $"为高优先级投诉生成回复并调用接口:{analyzeResult.GetContent()}", from: "User" ); var responseResult = await responder.GenerateReplyAsync( messages: new[] { responseMessage }, cancellationToken: cancellationToken); _logger.LogInformation($"回复结果:{responseResult.GetContent()}"); } else { _logger.LogInformation("⏭️ 无 PENDING 投诉,跳过"); } // 步骤 4:处理 PROCESSING 投诉 // 步骤 4:处理 PROCESSING 投诉 if (processingComplaints.Any()) { _logger.LogInformation("✅ 步骤 4:处理 PROCESSING 投诉..."); foreach (var complaint in processingComplaints) { // ✅ 查询协商历史 var history = await complaintsHelper.GetComplaintNegotiationHistoryAsync(complaint.ComplaintId); if (history?.Data == null || history.Data.Count == 0) { continue; } // ✅ 获取最后一条记录 var lastRecord = history.Data.OrderByDescending(h => h.OperateTime).FirstOrDefault(); var isLastUserResponse = lastRecord?.OperateType == "USER_RESPONSE" || lastRecord?.OperateType == "USER_CONTINUE_COMPLAINT"; if (!isLastUserResponse) continue; var lastMerchantResponse = history.Data .Where(h => h.OperateType == "MERCHANT_RESPONSE") .OrderByDescending(h => h.OperateTime) .FirstOrDefault(); if (lastMerchantResponse == null) continue; var isUserResponseAfterMerchant = DateTimeOffset.TryParse(lastRecord.OperateTime, out var userTime) && DateTimeOffset.TryParse(lastMerchantResponse.OperateTime, out var merchantTime) && userTime > merchantTime; if (!isUserResponseAfterMerchant) continue; // ✅ 混合判断:先代码快速判断,不确定时让 AI 判断 var userMessage = lastRecord.OperateDetails ?? ""; // ① 明显不满关键词 var obviousDissatisfied = new[] { "投诉", "曝光", "12315", "平台介入", "垃圾", "骗", "坑", "假", "差", "烂", "无语", "服了" }; var isObviousDissatisfied = obviousDissatisfied.Any(k => userMessage.Contains(k)); // ② 明显满意关键词 var obviousSatisfied = new[] { "好的", "谢谢", "解决了", "可以", "OK", "满意", "行", "好了", "收到" }; var isObviousSatisfied = obviousSatisfied.Any(k => userMessage.Contains(k)); // ③ 金额抱怨正则 var moneyPattern = @"扣了\s*\d+\s*块 | 扣了\s*\d+\s*元 | 扣费\s*\d+|收费\s*\d+"; var isMoneyComplaint = System.Text.RegularExpressions.Regex.IsMatch(userMessage, moneyPattern); // ④ 疑问句正则 var questionPattern = @"怎么办 | 为什么 | 怎么还 | 多久 | 什么时候 | 能否 | 可以吗 | 咋办"; var isQuestion = System.Text.RegularExpressions.Regex.IsMatch(userMessage, questionPattern); _logger.LogInformation($"投诉单 {complaint.ComplaintId}: 用户留言={userMessage}"); _logger.LogInformation($"投诉单 {complaint.ComplaintId}: 明显不满={isObviousDissatisfied}, 明显满意={isObviousSatisfied}, 金额抱怨={isMoneyComplaint}, 疑问句={isQuestion}"); bool needResponse; // ⑤ 综合判断 if (isObviousDissatisfied || isMoneyComplaint || isQuestion) { _logger.LogInformation($"投诉单 {complaint.ComplaintId}: 代码判断=需要回复"); needResponse = true; } else if (isObviousSatisfied) { _logger.LogInformation($"投诉单 {complaint.ComplaintId}: 代码判断=用户满意,标记完成"); needResponse = false; } else { // ⑥ 不确定时让 AI 判断 _logger.LogInformation($"投诉单 {complaint.ComplaintId}: 代码无法判断,让 AI 判断..."); var analyzer_2 = _agentFactory.CreateComplaintAnalyzer(); var analyzeMessage = new AutoGen.Core.TextMessage( role: Role.User, content: $@"判断用户是否需要再次回复: 【用户留言】 {userMessage} 【投诉详情】 {complaint.ComplaintDetail} 用户是否表达了不满、疑问或抱怨?是否需要商户再次回复? 只输出:是/否", from: "User" ); var analyzeResult = await analyzer_2.GenerateReplyAsync( messages: new[] { analyzeMessage }, cancellationToken: cancellationToken); var analyzeContent = analyzeResult.GetContent() ?? ""; needResponse = analyzeContent.Contains("是"); _logger.LogInformation($"投诉单 {complaint.ComplaintId}: AI 判断结果={analyzeContent}, 需要回复={needResponse}"); } // ✅ 执行操作 if (!needResponse) { var success = await complaintsHelper.CompleteComplaintAsync(complaint.ComplaintId, WeScoreHelper.MCH_ID); _logger.LogInformation(success ? $"✅ 已标记完成:{complaint.ComplaintId}" : $"❌ 标记完成失败:{complaint.ComplaintId}"); } else { // ✅ 用户不满,让 AI 生成针对性回复 _logger.LogInformation($"投诉单 {complaint.ComplaintId}: 需要再次回复,让 AI 生成回复..."); var responder_2 = _agentFactory.CreateResponseGenerator(); // ✅ 提前查询好详情 var detail = await complaintsHelper.GetComplaintDetailAsync(complaint.ComplaintId); var orderNo = detail?.ComplaintOrderInfo?.FirstOrDefault()?.OutTradeNo ?? complaint.ComplaintOrderInfo?.FirstOrDefault()?.OutTradeNo ?? "未知"; var responseMessage = new AutoGen.Core.TextMessage( role: Role.User, content: $@"用户投诉充电宝问题,已回复一次但用户不满意。 【投诉详情】 {complaint.ComplaintDetail} 【订单号】 {orderNo} 【用户最新留言】 {userMessage} 【上次商户回复】 {lastMerchantResponse.OperateDetails} 【回复要求】 1. 正面安抚用户,回复人工 24 小时内会介入,请耐心等待! 2. 针对用户说的""{ userMessage }""给出友好安抚 3.不要说""如确属服务异常"" 4.人工联系后,给您满意方案 5.字数 20-40 字 请生成回复话术(只返回回复内容,不要其他内容):", from: "User" ); var responseResult = await responder_2.GenerateReplyAsync( messages: new[] { responseMessage }, cancellationToken: cancellationToken); var aiResponse = responseResult.GetContent(); // ✅ 清理 AI 回复 if (aiResponse.Contains("❌") || aiResponse.Contains("失败") || aiResponse.Contains("错误")) { _logger.LogWarning($"AI 回复包含错误信息,使用默认回复"); aiResponse = $@"尊敬的客户,您好!非常抱歉给您带来不便。您的订单已操作免单处理,不会扣款,请放心。我们已安排运维人员检查该设备({orderNo}),避免再次发生。本次订单免费,不会收取任何费用。感谢您的理解与支持!"; } _logger.LogInformation($"AI 生成的回复:{aiResponse}"); // ✅ 直接调用微信回复接口 var success = await complaintsHelper.ResponseComplaintSimpleAsync( complaint.ComplaintId, WeScoreHelper.MCH_ID, aiResponse); if (success) { var Phone = complaintsHelper.DecryptPayerPhone(detail.PayerPhone, WechatConstants.WxPayCertPath, WeScoreHelper.MCH_ID); await notifyService.SendComplaintReceivedAsync( complaintId: complaint.ComplaintId, detail: complaint.ComplaintDetail, OrderNo: orderNo, Phone: Phone, Content: aiResponse ); _logger.LogInformation($"✅ 已回复投诉 {complaint.ComplaintId}"); } else { _logger.LogError($"❌ 回复失败:{complaint.ComplaintId}"); } } } } else { _logger.LogInformation("⏭️ 无 PROCESSING 投诉,跳过"); } _logger.LogInformation("🎯 手动路由编排完成"); } public override Task StopAsync(CancellationToken cancellationToken) { _logger.LogWarning("⏹️ WeScore 服务停止..."); return base.StopAsync(cancellationToken); } } }
Main函数
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using OpenAI; using PowerComplaint.Agents; using Quartz; using Quartz.Impl; using swapCommon; using swapCommon.RedisHelpers; using swapExternal.WeScore; using System.ClientModel; namespace PowerComplaint { class Program { static async Task Main(string[] args) { CreateHostBuilder(args).Run(); } public static IHost CreateHostBuilder(string[] args) { var builder = Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { // 添加配置 var configBuilder = new ConfigurationBuilder(); configBuilder.AddJsonFile("appsettings.json", true, true); var config = configBuilder.Build(); services.AddSingleton<IConfiguration>(config); // 注册 Logging services.AddLogging(log => { log.AddConsole(); log.AddNLog(); log.SetMinimumLevel(LogLevel.Information); }); // 注册 HTTP Client services.AddHttpClient(); // 注册 Redis services.AddScoped<RedisDbHelper>(A => new RedisDbHelper(3)); // ✅ 注册飞书通知服务 services.AddSingleton<IFeishuNotifyService, FeishuNotifyService>(); // ✅ 注册 AgentFactory(直接传配置参数) services.AddSingleton(sp => { var notifyService = sp.GetRequiredService<IFeishuNotifyService>(); return new ComplaintAgentFactory( apiKey: ConstParm.apiKey, endpoint: ConstParm.endpoint, modelId: ConstParm.modelId, mchid: WeScoreHelper.MCH_ID, notifyService: notifyService ); }); // ✅ 注册 BackgroundService services.AddHostedService<WeScoreService>(); }) .UseWindowsService(); var host = builder.Build(); return host; } } }
微信投诉相关API
using Newtonsoft.Json; using swapExternal.WeScore; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace swapExternal.Complaints { public class complaintsHelper { // 微信支付凭据 (请替换为你的实际值) public const string MCH_ID = ""; // 商户号 public const string API_KEY = ""; // API 密钥 public const string APPID = ""; // 公众号/小程序 ID public const string SUB_MCH_ID = ""; // 子商户号 (可选) public const string SERVICE_ID = ""; // 服务ID /// <summary> /// 商户API证书序列号 /// </summary> public const string SerialNo = ""; /// <summary> /// 验签 RSA国际证书序列号 /// </summary> public const string RsaSerialNo = ""; /// <summary> /// 公钥 /// </summary> public const string publicKey = @""; /// <summary> /// 有效期 2030年 2- 18 日 参考: WeScoreHelper.GetCertificates() 方法 /// </summary> public const string RsaBookPublicKey = @""; /// <summary> /// 查询投诉列表 URL /// </summary> public const string ComplaintListUrl = "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2"; /// <summary> /// 查询投诉详情 URL /// </summary> public const string ComplaintDetailUrl = "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/{0}"; /// <summary> /// 查询投诉协商历史 URL /// </summary> public const string ComplaintNegotiationUrl = "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/{0}/negotiation-history"; /// <summary> /// 回复用户投诉 URL(普通版) /// 格式:https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/{complaint_id}/response /// </summary> public const string ComplaintResponseUrl = "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/{0}/response"; /// <summary> /// 反馈处理完成 URL /// 格式:https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/{complaint_id}/complete /// </summary> public const string ComplaintCompleteUrl = "https://api.mch.weixin.qq.com/v3/merchant-service/complaints-v2/{0}/complete"; /// <summary> /// 解密投诉人手机号(RSA-OAEP 解密) /// </summary> public static string DecryptPayerPhone(string encryptedPhone, string privateKeyPath, string privateKeyPassword) { try { // ① 加载商户证书(包含私钥) X509Certificate2 cert = new X509Certificate2(privateKeyPath, privateKeyPassword, X509KeyStorageFlags.Exportable); // ② 获取 RSA 私钥 RSA rsa = cert.GetRSAPrivateKey(); // ③ RSA-OAEP 解密 byte[] ciphertext = Convert.FromBase64String(encryptedPhone); byte[] plaintext = rsa.Decrypt(ciphertext, RSAEncryptionPadding.OaepSHA1); // ④ 返回明文 return Encoding.UTF8.GetString(plaintext); } catch (Exception ex) { Console.WriteLine($"[手机号解密] ❌ 异常:{ex.Message}"); return ""; } } /// <summary> /// 查询投诉单列表 /// </summary> public static async Task<ComplaintListResult> GetComplaintListAsync( string beginDate, string endDate, int limit = 20, int offset = 0, string complaintedMchid = "") { // ① 构建查询参数 var queryParams = new List<string> { $"limit={limit}", $"offset={offset}", $"begin_date={beginDate}", $"end_date={endDate}" }; if (!string.IsNullOrEmpty(complaintedMchid)) { queryParams.Add($"complainted_mchid={complaintedMchid}"); } var queryString = string.Join("&", queryParams); var ApiUrl = $"{ComplaintListUrl}?{queryString}"; // ② 构建签名 var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); // ✅ ③ 调用 BuildAuthAsync 构建签名(返回不带前缀的签名部分) var signature = await WeScoreHelper.BuildAuthAsync( new HttpRequestMessage() { Method = HttpMethod.Get, RequestUri = new Uri(ApiUrl) }, timestamp, nonce); // ✅ ④ 添加认证方案前缀(和 SearchTransOrderNoAsync 一样) string token = $"WECHATPAY2-SHA256-RSA2048 {signature}"; // ✅ ⑤ 调用 GetWithToken(公共方法,不修改) var result = await WeScoreHelper.GetWithToken(ApiUrl, token); if (!string.IsNullOrEmpty(result)) { return Newtonsoft.Json.JsonConvert.DeserializeObject<ComplaintListResult>(result!); } return new ComplaintListResult(); } /// <summary> /// 构建投诉 API 签名(GET 请求) /// </summary> public static async Task<string> BuildComplaintAuthAsync( HttpClient httpClient, string url, long timestamp, string nonce) { // GET 请求 body 为空字符串 string message = $"GET\n{new Uri(url).PathAndQuery}\n{timestamp}\n{nonce}\n\n"; string signature = WeScoreHelper.RequestSign(message); return $"mchid=\"{MCH_ID}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{SerialNo}\",signature=\"{signature}\""; } /// <summary> /// 获取投诉单详情 /// </summary> /// <param name="complaintId"></param> /// <returns></returns> public static async Task<ComplaintDetailResult> GetComplaintDetailAsync(string complaintId) { var detailUrl = string.Format(ComplaintDetailUrl, complaintId); var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); var signature = await WeScoreHelper.BuildAuthAsync( new HttpRequestMessage() { Method = HttpMethod.Get, RequestUri = new Uri(detailUrl) }, timestamp, nonce); string token = $"WECHATPAY2-SHA256-RSA2048 {signature}"; var detailJson = await WeScoreHelper.GetWithToken(detailUrl, token); if (string.IsNullOrEmpty(detailJson)) { Console.WriteLine($"[投诉详情] ❌ 查询失败:{complaintId}"); return null; } // 解析详情(payer_phone 就是明文) var detail = JsonConvert.DeserializeObject<ComplaintDetailResult>(detailJson); Console.WriteLine($"[投诉详情] ✅ 成功"); Console.WriteLine($" 投诉单号:{detail.ComplaintId}"); Console.WriteLine($" 投诉人手机号:{detail.PayerPhone}"); // ✅ 直接就是明文 return detail; } /// <summary> /// 查询投诉单协商历史 /// </summary> /// <param name="complaintId">投诉单号</param> /// <param name="limit">分页大小(1-300,默认 50)</param> /// <param name="offset">分页开始位置(默认 0)</param> /// <returns>协商历史列表</returns> public static async Task<ComplaintNegotiationHistoryResult> GetComplaintNegotiationHistoryAsync( string complaintId, int limit = 50, int offset = 0) { if (string.IsNullOrEmpty(complaintId)) { Console.WriteLine("[协商历史] ❌ 投诉单号为空"); return null; } try { // ① 构建 URL var historyUrl = $"{string.Format(ComplaintDetailUrl, complaintId)}/negotiation-historys?limit={limit}&offset={offset}"; Console.WriteLine($"[协商历史] 🔍 请求:{historyUrl}"); var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); var signature = await WeScoreHelper.BuildAuthAsync( new HttpRequestMessage() { Method = HttpMethod.Get, RequestUri = new Uri(historyUrl) }, timestamp, nonce); string token = $"WECHATPAY2-SHA256-RSA2048 {signature}"; var historyJson = await WeScoreHelper.GetWithToken(historyUrl, token); if (string.IsNullOrEmpty(historyJson)) { Console.WriteLine($"[协商历史] ❌ 查询失败:{complaintId}"); return null; } var history = JsonConvert.DeserializeObject<ComplaintNegotiationHistoryResult>(historyJson); Console.WriteLine($"[协商历史] ✅ 成功,共 {history.TotalCount} 条记录"); return history; } catch (Exception ex) { Console.WriteLine($"[协商历史] ❌ 异常:{ex.Message}"); return null; } } /// 回复用户投诉(最简版 - 只回复文本) /// </summary> /// <param name="complaintId">投诉单号</param> /// <param name="complaintedMchid">被诉商户号</param> /// <param name="responseContent">回复内容(500 字符以内)</param> /// <returns>是否成功</returns> public static async Task<bool> ResponseComplaintSimpleAsync( string complaintId, string complaintedMchid, string responseContent) { if (string.IsNullOrEmpty(complaintId) || string.IsNullOrEmpty(complaintedMchid) || string.IsNullOrEmpty(responseContent)) { Console.WriteLine("[回复投诉] ❌ 参数不能为空"); return false; } if (responseContent.Length > 500) { Console.WriteLine("[回复投诉] ❌ 回复内容超过 500 字符"); return false; } try { var url = string.Format(ComplaintResponseUrl, complaintId); // 构建请求体(只传必要字段) var body = JsonConvert.SerializeObject(new { complainted_mchid = complaintedMchid, response_content = responseContent }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); Console.WriteLine($"[回复投诉] URL: {url}"); Console.WriteLine($"[回复投诉] Body: {body}"); var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); var signature = await WeScoreHelper.BuildAuthAsync( new HttpRequestMessage() { Method = HttpMethod.Post, RequestUri = new Uri(url), Content = new StringContent(body, Encoding.UTF8, "application/json") }, timestamp, nonce); string token = $"WECHATPAY2-SHA256-RSA2048 {signature}"; var result = await WeScoreHelper.PostWithToken(url, body, token); // 204 No Content 表示成功 if (string.IsNullOrEmpty(result)) { Console.WriteLine("[回复投诉] ✅ 成功"); return true; } else { Console.WriteLine($"[回复投诉] ❌ 失败:{result}"); return false; } } catch (Exception ex) { Console.WriteLine($"[回复投诉] ❌ 异常:{ex.Message}"); return false; } } /// 反馈投诉单已处理完成 /// 投诉单处理完成后系统将对用户下发满意度评价 /// 参考文档:https://pay.weixin.qq.com/doc/v3/merchant/4012467255 /// </summary> /// <param name="complaintId">投诉单号</param> /// <param name="complaintedMchid">被诉商户号</param> /// <returns>是否成功</returns> public static async Task<bool> CompleteComplaintAsync( string complaintId, string complaintedMchid) { if (string.IsNullOrEmpty(complaintId)) { Console.WriteLine("[处理完成] ❌ 投诉单号为空"); return false; } if (string.IsNullOrEmpty(complaintedMchid)) { Console.WriteLine("[处理完成] ❌ 被诉商户号为空"); return false; } try { // ① 构建 URL var url = string.Format(ComplaintCompleteUrl, complaintId); Console.WriteLine($"[处理完成] 🔗 URL: {url}"); // ② 构建请求体(只传被诉商户号) var body = JsonConvert.SerializeObject(new { complainted_mchid = complaintedMchid }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); Console.WriteLine($"[处理完成] 📝 Body: {body}"); // ③ 构建签名 Token var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); string nonce = Path.GetRandomFileName(); var signature = await WeScoreHelper.BuildAuthAsync( new HttpRequestMessage() { Method = HttpMethod.Post, RequestUri = new Uri(url), Content = new StringContent(body, Encoding.UTF8, "application/json") }, timestamp, nonce); string token = $"WECHATPAY2-SHA256-RSA2048 {signature}"; Console.WriteLine($"[处理完成] 🔑 Token: {token.Substring(0, 50)}..."); // ④ 调用 PostWithToken 发送请求 var result = await WeScoreHelper.PostWithToken(url, body, token); // ⑤ 处理响应(204 No Content 表示成功) if (string.IsNullOrEmpty(result)) { Console.WriteLine($"[处理完成] ✅ 成功,系统将向用户下发满意度评价"); return true; } else { Console.WriteLine($"[处理完成] ❌ 失败:{result}"); return false; } } catch (Exception ex) { Console.WriteLine($"[处理完成] ❌ 异常:{ex.Message}"); return false; } } } }
微信投诉MODEL
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; namespace swapExternal.Complaints { #region 投诉单状态枚举 /// <summary> /// 投诉单状态 /// </summary> public enum ComplaintState { /// <summary> /// 未知状态 /// </summary> [Description("未知状态")] Unknown = 0, /// <summary> /// 待处理 /// </summary> [Description("待处理")] PENDING = 1, /// <summary> /// 处理中 /// </summary> [Description("处理中")] PROCESSING = 2, /// <summary> /// 已处理完成 /// </summary> [Description("已处理完成")] PROCESSED = 3 } #endregion #region 问题类型枚举 /// <summary> /// 问题类型(用户发起投诉前选择的 FAQ 标题) /// </summary> public enum ProblemType { /// <summary> /// 未知类型 /// </summary> [Description("未知类型")] Unknown = 0, /// <summary> /// 申请退款(需最高优先处理) /// </summary> [Description("申请退款")] REFUND = 1, /// <summary> /// 服务权益未生效 /// </summary> [Description("服务权益未生效")] SERVICE_NOT_WORK = 2, /// <summary> /// 其他类型 /// </summary> [Description("其他类型")] OTHERS = 3 } #endregion #region 媒体文件类型枚举 /// <summary> /// 媒体文件业务类型 /// </summary> public enum MediaType { /// <summary> /// 未知类型 /// </summary> [Description("未知类型")] Unknown = 0, /// <summary> /// 用户提交投诉时上传的图片凭证 /// </summary> [Description("用户投诉图片")] USER_COMPLAINT_IMAGE = 1, /// <summary> /// 用户、商户、微信支付客服在协商解决投诉时,上传的图片凭证 /// </summary> [Description("协商图片")] OPERATION_IMAGE = 2 } #endregion /// <summary> /// 投诉列表查询结果 /// </summary> #region 投诉列表查询结果 /// <summary> /// 投诉列表查询结果 /// </summary> public class ComplaintListResult { /// <summary> /// 【用户投诉信息详情】 /// </summary> [JsonProperty("data")] public List<ComplaintInfo> Data { get; set; } /// <summary> /// 【分页大小】设置该次请求返回的最大投诉条数,范围 [1,50] /// </summary> [JsonProperty("limit")] public int Limit { get; set; } /// <summary> /// 【分页开始位置】该次请求的分页开始位置,从 0 开始计数 /// </summary> [JsonProperty("offset")] public int Offset { get; set; } /// <summary> /// 【投诉总条数】投诉总条数,当 offset=0 时返回 /// </summary> [JsonProperty("total_count")] public int TotalCount { get; set; } } #endregion #region 投诉单信息 /// <summary> /// 用户投诉信息详情 /// </summary> public class ComplaintInfo { /// <summary> /// 【投诉单号】投诉单对应的投诉单号 /// </summary> [JsonProperty("complaint_id")] public string ComplaintId { get; set; } /// <summary> /// 【投诉时间】投诉时间 /// </summary> [JsonProperty("complaint_time")] public string ComplaintTime { get; set; } /// <summary> /// 【投诉详情】投诉的具体描述 /// </summary> [JsonProperty("complaint_detail")] public string ComplaintDetail { get; set; } /// <summary> /// 【投诉单状态】 /// PENDING-待处理 /// PROCESSING-处理中 /// PROCESSED-已处理完成 /// </summary> [JsonProperty("complaint_state")] public string ComplaintState { get; set; } /// <summary> /// 【投诉人联系方式】投诉人联系方式。该字段已做加密处理 /// </summary> [JsonProperty("payer_phone")] public string PayerPhone { get; set; } /// <summary> /// 【投诉单关联订单信息】 /// </summary> [JsonProperty("complaint_order_info")] public List<ComplaintOrderInfo> ComplaintOrderInfo { get; set; } /// <summary> /// 【投诉单是否已全额退款】投诉单下所有订单是否已全部全额退款 /// </summary> [JsonProperty("complaint_full_refunded")] public bool ComplaintFullRefunded { get; set; } /// <summary> /// 【是否有待回复的用户留言】投诉单是否有待回复的用户留言 /// </summary> [JsonProperty("incoming_user_response")] public bool IncomingUserResponse { get; set; } /// <summary> /// 【用户投诉次数】用户投诉次数。用户首次发起投诉记为 1 次,用户每有一次继续投诉就加 1 /// </summary> [JsonProperty("user_complaint_times")] public int UserComplaintTimes { get; set; } /// <summary> /// 【投诉资料列表】用户上传的投诉相关资料,包括图片凭证等 /// </summary> [JsonProperty("complaint_media_list")] public List<ComplaintMediaInfo> ComplaintMediaList { get; set; } /// <summary> /// 【问题描述】用户发起投诉前选择的 faq 标题 /// </summary> [JsonProperty("problem_description")] public string ProblemDescription { get; set; } /// <summary> /// 【问题类型】 /// REFUND: 申请退款 /// SERVICE_NOT_WORK: 服务权益未生效 /// OTHERS: 其他类型 /// </summary> [JsonProperty("problem_type")] public string ProblemType { get; set; } /// <summary> /// 【申请退款金额】仅当问题类型为申请退款时,有值 (单位:分) /// </summary> [JsonProperty("apply_refund_amount")] public int? ApplyRefundAmount { get; set; } /// <summary> /// 【用户标签列表】 /// TRUSTED: 此类用户满足极速退款条件 /// HIGH_RISK: 高风险投诉,请按照运营要求优先妥善处理 /// </summary> [JsonProperty("user_tag_list")] public List<string> UserTagList { get; set; } /// <summary> /// 【投诉单关联服务单信息】 /// </summary> [JsonProperty("service_order_info")] public List<ServiceOrderInfo> ServiceOrderInfo { get; set; } /// <summary> /// 【是否在平台协助中】标识当前投诉单是否正处在平台协助流程中 /// </summary> [JsonProperty("in_platform_service")] public bool InPlatformService { get; set; } /// <summary> /// 【是否需即时服务用户】因用户诉求紧急度、用户界面差异等因素,部分投诉单建议商户更即时地响应用户诉求 /// </summary> [JsonProperty("need_immediate_service")] public bool NeedImmediateService { get; set; } /// <summary> /// 【是否是智能体投诉】对接商家智能体的商户,用户针对某笔订单下的智能服务质量产生投诉 /// </summary> [JsonProperty("is_agent_mode")] public bool IsAgentMode { get; set; } /// <summary> /// 【补充信息】用在特定行业或场景下返回的补充信息 /// </summary> [JsonProperty("additional_info")] public AdditionalInfo AdditionalInfo { get; set; } } #endregion #region 投诉单关联订单信息 /// <summary> /// 投诉单关联订单信息 /// </summary> public class ComplaintOrderInfo { /// <summary> /// 【微信订单号】投诉单关联的微信订单号 /// </summary> [JsonProperty("transaction_id")] public string TransactionId { get; set; } /// <summary> /// 【商户订单号】投诉单关联的商户订单号 /// </summary> [JsonProperty("out_trade_no")] public string OutTradeNo { get; set; } /// <summary> /// 【订单金额】订单金额,单位(分) /// </summary> [JsonProperty("amount")] public int Amount { get; set; } } #endregion #region 投诉资料列表 /// <summary> /// 投诉资料信息 /// </summary> public class ComplaintMediaInfo { /// <summary> /// 【媒体文件业务类型】 /// USER_COMPLAINT_IMAGE: 用户提交投诉时上传的图片凭证 /// OPERATION_IMAGE: 用户、商户、微信支付客服在协商解决投诉时,上传的图片凭证 /// </summary> [JsonProperty("media_type")] public MediaType MediaType { get; set; } /// <summary> /// 【媒体文件请求 url】微信返回的媒体文件请求 url /// </summary> [JsonProperty("media_url")] public List<string> MediaUrl { get; set; } } #endregion #region 投诉单关联服务单信息 /// <summary> /// 投诉单关联服务单信息 /// </summary> public class ServiceOrderInfo { /// <summary> /// 【微信支付服务订单号】 /// </summary> [JsonProperty("order_id")] public string OrderId { get; set; } /// <summary> /// 【商户服务订单号】商户系统内部服务订单号(不是交易单号) /// </summary> [JsonProperty("out_order_no")] public string OutOrderNo { get; set; } /// <summary> /// 【支付分服务单状态】 /// DOING: 服务订单进行中 /// REVOKED: 服务订单已取消 /// WAITPAY: 服务订单待支付 /// DONE: 服务订单已完成 /// </summary> [JsonProperty("state")] public string State { get; set; } } #endregion #region 补充信息(充电宝投诉相关) /// <summary> /// 补充信息(特定行业或场景) /// </summary> public class AdditionalInfo { /// <summary> /// 【补充信息类型】 /// SHARE_POWER_TYPE: 充电宝投诉相关行业 /// </summary> [JsonProperty("type")] public string Type { get; set; } /// <summary> /// 【充电宝投诉相关信息】当 type 为 SHARE_POWER_TYPE 时有值 /// </summary> [JsonProperty("share_power_info")] public SharePowerInfo SharePowerInfo { get; set; } } /// <summary> /// 充电宝投诉相关信息 /// </summary> public class SharePowerInfo { /// <summary> /// 【归还时间】遵循 rfc3339 标准格式 /// </summary> [JsonProperty("return_time")] public string ReturnTime { get; set; } /// <summary> /// 【归还地点信息】 /// </summary> [JsonProperty("return_address_info")] public ReturnAddressInfo ReturnAddressInfo { get; set; } /// <summary> /// 【是否归还同一柜机】用户声明是否将充电宝归还至与借取时同一柜机 /// </summary> [JsonProperty("is_returned_to_same_machine")] public bool IsReturnedToSameMachine { get; set; } } /// <summary> /// 归还地点信息 /// </summary> public class ReturnAddressInfo { /// <summary> /// 【归还地点】 /// </summary> [JsonProperty("return_address")] public string ReturnAddress { get; set; } /// <summary> /// 【归还地点经度】使用 GCJ-02 坐标系 /// </summary> [JsonProperty("longitude")] public string Longitude { get; set; } /// <summary> /// 【归还地点纬度】使用 GCJ-02 坐标系 /// </summary> [JsonProperty("latitude")] public string Latitude { get; set; } } #endregion /// <summary> /// 投诉详情查询结果 /// </summary> /// <summary> /// 投诉详情查询结果 /// </summary> public class ComplaintDetailResult { /// <summary> /// 【投诉单号】 /// </summary> [JsonProperty("complaint_id")] public string ComplaintId { get; set; } /// <summary> /// 【投诉时间】 /// </summary> [JsonProperty("complaint_time")] public string ComplaintTime { get; set; } /// <summary> /// 【投诉详情】 /// </summary> [JsonProperty("complaint_detail")] public string ComplaintDetail { get; set; } /// <summary> /// 【投诉单状态】 /// PENDING-待处理 /// PROCESSING-处理中 /// PROCESSED-已处理完成 /// </summary> [JsonProperty("complaint_state")] public string ComplaintState { get; set; } /// <summary> /// 【投诉人联系方式】(明文手机号) /// </summary> [JsonProperty("payer_phone")] public string PayerPhone { get; set; } /// <summary> /// 【投诉人 OpenID】 /// </summary> [JsonProperty("payer_openid")] public string PayerOpenid { get; set; } /// <summary> /// 【投诉单关联订单信息】 /// </summary> [JsonProperty("complaint_order_info")] public List<ComplaintOrderInfo> ComplaintOrderInfo { get; set; } /// <summary> /// 【投诉单是否已全额退款】 /// </summary> [JsonProperty("complaint_full_refunded")] public bool ComplaintFullRefunded { get; set; } /// <summary> /// 【是否有待回复的用户留言】 /// </summary> [JsonProperty("incoming_user_response")] public bool IncomingUserResponse { get; set; } /// <summary> /// 【用户投诉次数】 /// </summary> [JsonProperty("user_complaint_times")] public int UserComplaintTimes { get; set; } /// <summary> /// 【投诉资料列表】 /// </summary> [JsonProperty("complaint_media_list")] public List<ComplaintMediaInfo> ComplaintMediaList { get; set; } /// <summary> /// 【问题描述】 /// </summary> [JsonProperty("problem_description")] public string ProblemDescription { get; set; } /// <summary> /// 【问题类型】 /// REFUND: 申请退款 /// SERVICE_NOT_WORK: 服务权益未生效 /// OTHERS: 其他类型 /// </summary> [JsonProperty("problem_type")] public string ProblemType { get; set; } /// <summary> /// 【申请退款金额】(单位:分) /// </summary> [JsonProperty("apply_refund_amount")] public int? ApplyRefundAmount { get; set; } /// <summary> /// 【用户标签列表】 /// </summary> [JsonProperty("user_tag_list")] public List<string> UserTagList { get; set; } /// <summary> /// 【投诉单关联服务单信息】 /// </summary> [JsonProperty("service_order_info")] public List<ServiceOrderInfo> ServiceOrderInfo { get; set; } /// <summary> /// 【补充信息】 /// </summary> [JsonProperty("additional_info")] public AdditionalInfo AdditionalInfo { get; set; } /// <summary> /// 【是否在平台协助中】 /// </summary> [JsonProperty("in_platform_service")] public bool InPlatformService { get; set; } /// <summary> /// 【是否需即时服务用户】 /// </summary> [JsonProperty("need_immediate_service")] public bool NeedImmediateService { get; set; } /// <summary> /// 【是否是智能体投诉】 /// </summary> [JsonProperty("is_agent_mode")] public bool IsAgentMode { get; set; } } #region 协商历史查询结果 /// <summary> /// 投诉协商历史查询结果 /// </summary> public class ComplaintNegotiationHistoryResult { /// <summary> /// 【投诉协商历史】 /// </summary> [JsonProperty("data")] public List<ComplaintNegotiationHistory> Data { get; set; } /// <summary> /// 【分页大小】 /// </summary> [JsonProperty("limit")] public int Limit { get; set; } /// <summary> /// 【分页开始位置】 /// </summary> [JsonProperty("offset")] public int Offset { get; set; } /// <summary> /// 【投诉协商历史总条数】 /// </summary> [JsonProperty("total_count")] public int TotalCount { get; set; } } #endregion #region 协商历史记录 /// <summary> /// 投诉协商历史记录 /// </summary> public class ComplaintNegotiationHistory { /// <summary> /// 【操作流水号】 /// </summary> [JsonProperty("log_id")] public string LogId { get; set; } /// <summary> /// 【操作人】当前投诉协商记录的操作人 /// </summary> [JsonProperty("operator")] public string Operator { get; set; } /// <summary> /// 【操作时间】 /// </summary> [JsonProperty("operate_time")] public string OperateTime { get; set; } /// <summary> /// 【操作类型】 /// USER_CREATE_COMPLAINT: 用户提交投诉 /// USER_CONTINUE_COMPLAINT: 用户继续投诉 /// USER_RESPONSE: 用户留言 /// PLATFORM_RESPONSE: 平台留言 /// MERCHANT_RESPONSE: 商户留言 /// MERCHANT_CONFIRM_COMPLETE: 商户申请结单 /// ... /// </summary> [JsonProperty("operate_type")] public string OperateType { get; set; } /// <summary> /// 【操作内容】当前投诉协商记录的具体内容 /// </summary> [JsonProperty("operate_details")] public string OperateDetails { get; set; } /// <summary> /// 【图片凭证】商户或微信支付客服上传的图片 URL 列表 /// </summary> [JsonProperty("image_list")] public List<string> ImageList { get; set; } /// <summary> /// 【操作资料列表】 /// </summary> [JsonProperty("complaint_media_list")] public ComplaintMediaInfo ComplaintMediaList { get; set; } /// <summary> /// 【用户申请平台协助原因】(已废弃) /// </summary> [JsonProperty("user_appy_platform_service_reason")] public string UserAppyPlatformServiceReason { get; set; } /// <summary> /// 【用户申请平台协助原因描述】(已废弃) /// </summary> [JsonProperty("user_appy_platform_service_reason_description")] public string UserAppyPlatformServiceReasonDescription { get; set; } /// <summary> /// 【普通消息内容】 /// </summary> [JsonProperty("normal_message")] public NormalMessage NormalMessage { get; set; } /// <summary> /// 【用户点击回复消息】 /// </summary> [JsonProperty("click_message")] public ClickMessage ClickMessage { get; set; } } #endregion #region 普通消息内容 /// <summary> /// 普通消息内容(商户即时服务回复) /// </summary> public class NormalMessage { /// <summary> /// 【消息内容块列表】 /// </summary> [JsonProperty("blocks")] public List<MessageBlock> Blocks { get; set; } /// <summary> /// 【发送者身份类别】UNKNOWN/MANUAL/MACHINE /// </summary> [JsonProperty("sender_identity")] public string SenderIdentity { get; set; } /// <summary> /// 【自定义透传信息】 /// </summary> [JsonProperty("custom_data")] public string CustomData { get; set; } } /// <summary> /// 消息内容块 /// </summary> public class MessageBlock { /// <summary> /// 【消息块类型】TEXT/IMAGE/LINK/FAQ_LIST/BUTTON/BUTTON_GROUP /// </summary> [JsonProperty("type")] public string Type { get; set; } /// <summary> /// 【文本消息块】 /// </summary> [JsonProperty("text")] public TextMessage Text { get; set; } /// <summary> /// 【图片消息块】 /// </summary> [JsonProperty("image")] public ImageMessage Image { get; set; } /// <summary> /// 【链接消息块】 /// </summary> [JsonProperty("link")] public LinkMessage Link { get; set; } /// <summary> /// 【按钮消息块】 /// </summary> [JsonProperty("button")] public ButtonMessage Button { get; set; } } /// <summary> /// 文本消息 /// </summary> public class TextMessage { /// <summary> /// 【文字内容】 /// </summary> [JsonProperty("text")] public string TextContent { get; set; } /// <summary> /// 【文本颜色】DEFAULT/SECONDARY /// </summary> [JsonProperty("color")] public string Color { get; set; } /// <summary> /// 【是否粗体】 /// </summary> [JsonProperty("is_bold")] public bool IsBold { get; set; } } /// <summary> /// 图片消息 /// </summary> public class ImageMessage { /// <summary> /// 【媒体 ID】 /// </summary> [JsonProperty("media_id")] public string MediaId { get; set; } /// <summary> /// 【图片样式类型】IMAGE_STYLE_TYPE_NARROW/IMAGE_STYLE_TYPE_WIDE /// </summary> [JsonProperty("image_style_type")] public string ImageStyleType { get; set; } } /// <summary> /// 链接消息 /// </summary> public class LinkMessage { /// <summary> /// 【链接文案】 /// </summary> [JsonProperty("text")] public string Text { get; set; } /// <summary> /// 【动作】 /// </summary> [JsonProperty("action")] public MessageAction Action { get; set; } } /// <summary> /// 按钮消息 /// </summary> public class ButtonMessage { /// <summary> /// 【按钮文本】 /// </summary> [JsonProperty("text")] public string Text { get; set; } /// <summary> /// 【动作】 /// </summary> [JsonProperty("action")] public MessageAction Action { get; set; } /// <summary> /// 【失效配置】 /// </summary> [JsonProperty("invalid_info")] public InvalidInfo InvalidInfo { get; set; } } /// <summary> /// 消息动作 /// </summary> public class MessageAction { /// <summary> /// 【动作类型】 /// </summary> [JsonProperty("action_type")] public string ActionType { get; set; } /// <summary> /// 【跳转链接】 /// </summary> [JsonProperty("jump_url")] public string JumpUrl { get; set; } /// <summary> /// 【回复消息内容】 /// </summary> [JsonProperty("message_info")] public MessageInfo MessageInfo { get; set; } } /// <summary> /// 回复消息内容 /// </summary> public class MessageInfo { /// <summary> /// 【回复的消息内容】 /// </summary> [JsonProperty("content")] public string Content { get; set; } /// <summary> /// 【自定义透传字段】 /// </summary> [JsonProperty("custom_data")] public string CustomData { get; set; } } /// <summary> /// 失效配置 /// </summary> public class InvalidInfo { /// <summary> /// 【过期时间】 /// </summary> [JsonProperty("expired_time")] public string ExpiredTime { get; set; } /// <summary> /// 【是否可以多次点击】 /// </summary> [JsonProperty("multi_clickable")] public bool MultiClickable { get; set; } } #endregion #region 用户点击回复消息 /// <summary> /// 用户点击回复消息 /// </summary> public class ClickMessage { /// <summary> /// 【回复的消息内容】 /// </summary> [JsonProperty("message_content")] public string MessageContent { get; set; } /// <summary> /// 【动作 id】 /// </summary> [JsonProperty("action_id")] public string ActionId { get; set; } /// <summary> /// 【被点击的操作流水号】 /// </summary> [JsonProperty("clicked_log_id")] public string ClickedLogId { get; set; } } #endregion /// <summary> /// 回复用户投诉请求参数 /// </summary> public class ResponseComplaintRequest { /// <summary> /// 【被诉商户号】 /// </summary> [JsonProperty("complainted_mchid")] public string ComplaintedMchid { get; set; } /// <summary> /// 【回复内容】具体的投诉处理方案,限制 500 个字符以内 /// </summary> [JsonProperty("response_content")] public string ResponseContent { get; set; } } }
其他参考:https://www.cnblogs.com/chenwolong/p/19005776

浙公网安备 33010602011771号