AI Agent 处理微信支付投诉-项目实战-自动回复并发送消息到飞书

花了2天时间,做了一个AI智能体处理微信投诉的小项目!

核心功能:当用户首次投诉后,十分钟内由AI做出回复,同时把投诉单发送到飞书群,由飞书群里的运营/售后处理

核心流程

用户初次投诉(待处理)->AI自动回复(处理中)->用户再次留言->判断/接合AI判断用户留言是否满意->如果不满意则继续AI回复->如果满意,则关闭投诉单!

针对回复的投诉单->24小时内用户不再留言,则主动关闭投诉单!

截图:

1787218104680

1787218129839

核心代码:

智能体:

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();
}
    }
}
View Code

工具、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
            });
        }
    }
}
View Code

飞书消息-发送

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);
    }
     
}
View Code

定时执行任务类

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);
        }
    }
}
View Code

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;
        }
    }
}
View Code

微信投诉相关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;
            }
        }
    }
}
View Code

微信投诉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; }
         
    }
     
 
}
View Code

其他参考:https://www.cnblogs.com/chenwolong/p/19005776

 

posted @ 2026-08-20 17:36  天才卧龙  阅读(5)  评论(0)    收藏  举报