TJC.Cyclops.Wechat 2026.3.12.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package TJC.Cyclops.Wechat --version 2026.3.12.2
                    
NuGet\Install-Package TJC.Cyclops.Wechat -Version 2026.3.12.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TJC.Cyclops.Wechat" Version="2026.3.12.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TJC.Cyclops.Wechat" Version="2026.3.12.2" />
                    
Directory.Packages.props
<PackageReference Include="TJC.Cyclops.Wechat" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TJC.Cyclops.Wechat --version 2026.3.12.2
                    
#r "nuget: TJC.Cyclops.Wechat, 2026.3.12.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TJC.Cyclops.Wechat@2026.3.12.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TJC.Cyclops.Wechat&version=2026.3.12.2
                    
Install as a Cake Addin
#tool nuget:?package=TJC.Cyclops.Wechat&version=2026.3.12.2
                    
Install as a Cake Tool

Cyclops.Wechat

项目概述

Cyclops.Wechat是Cyclops.Framework框架中的微信集成组件,提供与微信平台的全面对接能力,包括微信公众号、微信小程序、企业微信、微信支付和开放平台等服务的统一封装。该组件简化了微信API的调用过程,提供了完整的OAuth认证、消息处理、素材管理、用户管理、模板消息、微信支付等功能,帮助开发者快速构建微信生态应用。

核心功能模块

微信认证

  • OAuth2.0授权
  • 公众号网页授权
  • 小程序授权
  • 企业微信授权
  • 开放平台授权

消息处理

  • 消息接收与解析
  • 消息回复与格式化
  • 事件推送处理
  • 客服消息发送
  • 模板消息管理

素材管理

  • 临时素材上传下载
  • 永久素材管理
  • 图文消息编辑
  • 视频素材处理
  • 素材批量操作

用户管理

  • 用户信息获取
  • 用户标签管理
  • 黑名单管理
  • 用户分组
  • 用户统计分析

微信支付

  • 统一下单API
  • 支付回调处理
  • 订单查询
  • 退款申请
  • 企业付款
  • 账单下载

小程序

  • 小程序码生成
  • 订阅消息发送
  • 云开发集成
  • 数据预拉取
  • 内容安全检测

企业微信

  • 通讯录管理
  • 应用消息推送
  • 会话存档
  • 审批流程
  • 企业支付

技术栈

  • .NET 8.0
  • ASP.NET Core
  • Newtonsoft.Json / System.Text.Json
  • Cyclops.Common
  • Cyclops.Logging
  • Cyclops.Caching
  • Cyclops.HttpClient

环境依赖

  • .NET 8.0 SDK
  • 微信开发者账号(公众号/小程序/开放平台/企业微信)
  • 有效的AppID和AppSecret
  • 配置的服务器域名和回调地址

安装配置

NuGet安装

Install-Package Cyclops.Wechat

基本配置

在应用程序启动时进行配置:

// 在Program.cs或Startup.cs中
using Cyclops.Wechat;
using Cyclops.Wechat.Configurations;

var builder = WebApplication.CreateBuilder(args);

// 添加Cyclops.Wechat服务
builder.Services.AddCyclopsWechat(options => {
    // 公众号配置
    options.OffiAccount = new OffiAccountOptions {
        AppId = builder.Configuration["Wechat:OffiAccount:AppId"],
        AppSecret = builder.Configuration["Wechat:OffiAccount:AppSecret"],
        Token = builder.Configuration["Wechat:OffiAccount:Token"],
        EncodingAesKey = builder.Configuration["Wechat:OffiAccount:EncodingAesKey"],
        MessageHandlerPath = "/api/wechat/message",
        OAuthRedirectUri = builder.Configuration["Wechat:OffiAccount:OAuthRedirectUri"],
        UseIpProxy = false
    };
    
    // 小程序配置
    options.Miniprogram = new MiniprogramOptions {
        AppId = builder.Configuration["Wechat:Miniprogram:AppId"],
        AppSecret = builder.Configuration["Wechat:Miniprogram:AppSecret"],
        MessageHandlerPath = "/api/wechat/miniprogram/message",
        CloudEnvironmentId = builder.Configuration["Wechat:Miniprogram:CloudEnvironmentId"]
    };
    
    // 微信支付配置
    options.Payment = new PaymentOptions {
        AppId = builder.Configuration["Wechat:Payment:AppId"],
        MchId = builder.Configuration["Wechat:Payment:MchId"],
        Key = builder.Configuration["Wechat:Payment:Key"],
        CertificatePath = builder.Configuration["Wechat:Payment:CertificatePath"],
        CertificatePassword = builder.Configuration["Wechat:Payment:CertificatePassword"],
        NotifyUrl = builder.Configuration["Wechat:Payment:NotifyUrl"],
        ApiV3Key = builder.Configuration["Wechat:Payment:ApiV3Key"],
        EnableSandbox = false
    };
    
    // 企业微信配置
    options.Work = new WorkOptions {
        CorpId = builder.Configuration["Wechat:Work:CorpId"],
        CorpSecret = builder.Configuration["Wechat:Work:CorpSecret"],
        AgentId = int.Parse(builder.Configuration["Wechat:Work:AgentId"]),
        Token = builder.Configuration["Wechat:Work:Token"],
        EncodingAesKey = builder.Configuration["Wechat:Work:EncodingAesKey"]
    };
    
    // 缓存配置
    options.Cache = new CacheOptions {
        AccessTokenCacheKeyPrefix = "wechat:accesstoken:",
        JsApiTicketCacheKeyPrefix = "wechat:jsapi_ticket:",
        CacheExpiration = TimeSpan.FromMinutes(110),
        UseDistributedCache = true
    };
    
    // 日志配置
    options.Log = new LogOptions {
        EnableRequestLog = true,
        EnableResponseLog = false,
        LogLevel = LogLevel.Information
    };
});

var app = builder.Build();

// 配置路由
app.MapControllers();

// 配置微信消息处理中间件
app.UseWechatMessageHandler();

app.Run();

代码示例

微信公众号OAuth认证示例

using Cyclops.Wechat.Services;
using Cyclops.Wechat.Models.OAuth;
using Microsoft.AspNetCore.Mvc;

public class WechatOAuthController : Controller
{
    private readonly IWechatOffiAccountService _wechatService;
    private readonly ILogger<WechatOAuthController> _logger;
    
    public WechatOAuthController(IWechatOffiAccountService wechatService, ILogger<WechatOAuthController> logger)
    {
        _wechatService = wechatService;
        _logger = logger;
    }
    
    /// <summary>
    /// 生成OAuth授权URL
    /// </summary>
    [HttpGet("/oauth/wechat")]
    public IActionResult GetOAuthUrl(string redirectUri = null, string state = null, OAuthScope scope = OAuthScope.Base)
    {
        // 确保重定向URI是完整的
        if (string.IsNullOrEmpty(redirectUri))
        {
            redirectUri = $"{Request.Scheme}://{Request.Host}/oauth/wechat/callback";
        }
        
        // 生成授权URL
        var oauthUrl = _wechatService.GetOAuthUrl(redirectUri, state, scope);
        
        _logger.LogInformation("生成微信OAuth授权URL: {Scope}", scope);
        
        // 重定向到微信授权页面
        return Redirect(oauthUrl);
    }
    
    /// <summary>
    /// OAuth回调处理
    /// </summary>
    [HttpGet("/oauth/wechat/callback")]
    public async Task<IActionResult> OAuthCallback(string code, string state)
    {
        try
        {
            if (string.IsNullOrEmpty(code))
            {
                _logger.LogWarning("微信OAuth回调缺少code参数");
                return BadRequest("授权失败:缺少授权码");
            }
            
            // 获取访问令牌
            var accessTokenResult = await _wechatService.GetOAuthAccessTokenAsync(code);
            
            if (!accessTokenResult.IsSuccess)
            {
                _logger.LogError("获取OAuth访问令牌失败: {Error}", accessTokenResult.ErrorMessage);
                return BadRequest($"授权失败: {accessTokenResult.ErrorMessage}");
            }
            
            // 根据授权范围处理
            if (accessTokenResult.Scope == "snsapi_base")
            {
                // 静默授权,只获取OpenId
                var openId = accessTokenResult.OpenId;
                _logger.LogInformation("微信静默授权成功: {OpenId}", openId);
                
                // 这里可以根据OpenId获取或创建用户
                var userId = await GetOrCreateUserByOpenIdAsync(openId);
                
                // 存储用户会话
                HttpContext.Session.SetString("WechatOpenId", openId);
                HttpContext.Session.SetInt32("UserId", userId);
                
                // 重定向到原始页面或首页
                return Redirect(string.IsNullOrEmpty(state) ? "/" : state);
            }
            else if (accessTokenResult.Scope == "snsapi_userinfo")
            {
                // 获取用户详细信息
                var userInfoResult = await _wechatService.GetOAuthUserInfoAsync(
                    accessTokenResult.AccessToken, 
                    accessTokenResult.OpenId
                );
                
                if (!userInfoResult.IsSuccess)
                {
                    _logger.LogError("获取用户信息失败: {Error}", userInfoResult.ErrorMessage);
                    return BadRequest($"获取用户信息失败: {userInfoResult.ErrorMessage}");
                }
                
                _logger.LogInformation("获取微信用户信息成功: {Nickname}", userInfoResult.Nickname);
                
                // 保存用户信息到数据库
                var userId = await SaveWechatUserInfoAsync(userInfoResult);
                
                // 存储用户会话
                HttpContext.Session.SetString("WechatOpenId", userInfoResult.OpenId);
                HttpContext.Session.SetInt32("UserId", userId);
                
                // 返回用户信息或重定向
                return Redirect(string.IsNullOrEmpty(state) ? "/user/profile" : state);
            }
            
            return BadRequest("未知的授权范围");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "处理微信OAuth回调时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 根据OpenId获取或创建用户
    /// </summary>
    private async Task<int> GetOrCreateUserByOpenIdAsync(string openId)
    {
        // 这里应该实现具体的用户查找和创建逻辑
        // 示例中直接返回模拟用户ID
        _logger.LogInformation("根据OpenId获取用户: {OpenId}", openId);
        
        // 实际应用中,应该查询数据库,不存在则创建
        return 1001; // 模拟用户ID
    }
    
    /// <summary>
    /// 保存微信用户信息到数据库
    /// </summary>
    private async Task<int> SaveWechatUserInfoAsync(WechatUserInfo userInfo)
    {
        // 这里应该实现保存用户信息到数据库的逻辑
        _logger.LogInformation("保存微信用户信息: {OpenId}, {Nickname}", 
            userInfo.OpenId, userInfo.Nickname);
        
        // 模拟用户ID
        return 1001;
    }
}

// OAuth配置类扩展
public static class OAuthExtensions
{
    public static void ConfigureWechatOAuth(this IServiceCollection services)
    {
        services.AddAuthentication(options => {
            options.DefaultAuthenticateScheme = "WechatCookie";
            options.DefaultSignInScheme = "WechatCookie";
            options.DefaultChallengeScheme = "Wechat";
        })
        .AddCookie("WechatCookie")
        .AddOAuth("Wechat", options => {
            options.ClientId = Configuration["Wechat:OffiAccount:AppId"];
            options.ClientSecret = Configuration["Wechat:OffiAccount:AppSecret"];
            
            options.CallbackPath = new PathString("/signin-wechat");
            options.AuthorizationEndpoint = "https://open.weixin.qq.com/connect/oauth2/authorize";
            options.TokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/access_token";
            options.UserInformationEndpoint = "https://api.weixin.qq.com/sns/userinfo";
            
            // 自定义参数
            options.Events.OnRedirectToAuthorizationEndpoint = context => {
                var redirectUri = context.RedirectUri;
                // 微信需要特殊处理
                redirectUri = redirectUri.Replace("response_type=code", "response_type=code#wechat_redirect");
                context.Response.Redirect(redirectUri);
                return Task.CompletedTask;
            };
            
            options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "openid");
            options.ClaimActions.MapJsonKey(ClaimTypes.Name, "nickname");
            options.ClaimActions.MapJsonKey("headimgurl", "headimgurl");
            
            options.SaveTokens = true;
        });
    }
}

微信消息处理示例

using Cyclops.Wechat.Attributes;
using Cyclops.Wechat.Events;
using Cyclops.Wechat.Models.RequestMessages;
using Cyclops.Wechat.Models.ResponseMessages;
using Cyclops.Wechat.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[ApiController]
public class WechatMessageController : ControllerBase
{
    private readonly IWechatOffiAccountService _wechatService;
    private readonly ILogger<WechatMessageController> _logger;
    
    public WechatMessageController(IWechatOffiAccountService wechatService, ILogger<WechatMessageController> logger)
    {
        _wechatService = wechatService;
        _logger = logger;
    }
    
    /// <summary>
    /// 微信消息接收验证
    /// </summary>
    [HttpGet("receive")]
    public async Task<IActionResult> Validate([FromQuery] string signature, [FromQuery] string timestamp, [FromQuery] string nonce, [FromQuery] string echostr)
    {
        try
        {
            // 验证签名
            if (_wechatService.CheckSignature(signature, timestamp, nonce))
            {
                _logger.LogInformation("微信消息接收验证成功");
                return Content(echostr);
            }
            else
            {
                _logger.LogWarning("微信消息接收验证失败:签名无效");
                return BadRequest("签名无效");
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "微信消息接收验证过程中发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 接收并处理微信消息
    /// </summary>
    [HttpPost("receive")]
    [Consumes("text/xml")]
    public async Task<IActionResult> ReceiveMessage()
    {
        try
        {
            // 读取请求体
            using var reader = new StreamReader(Request.Body);
            var requestXml = await reader.ReadToEndAsync();
            
            _logger.LogInformation("收到微信消息: {Xml}", requestXml);
            
            // 解析消息
            var messageHandler = _wechatService.CreateMessageHandler(requestXml);
            
            // 根据消息类型进行处理
            var responseMessage = await messageHandler.HandleMessageAsync(new MessageHandlerOptions {
                TextMessageHandler = HandleTextMessage,
                ImageMessageHandler = HandleImageMessage,
                VoiceMessageHandler = HandleVoiceMessage,
                VideoMessageHandler = HandleVideoMessage,
                ShortVideoMessageHandler = HandleShortVideoMessage,
                LocationMessageHandler = HandleLocationMessage,
                LinkMessageHandler = HandleLinkMessage,
                EventMessageHandler = HandleEventMessage
            });
            
            // 返回响应消息
            if (responseMessage != null)
            {
                var responseXml = messageHandler.SerializeMessage(responseMessage);
                _logger.LogInformation("返回微信响应消息: {Xml}", responseXml);
                return Content(responseXml, "text/xml");
            }
            
            // 无响应
            return Ok();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "处理微信消息时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 处理文本消息
    /// </summary>
    private async Task<ResponseMessageBase> HandleTextMessage(TextRequestMessage message)
    {
        _logger.LogInformation("收到文本消息: {Content}", message.Content);
        
        // 根据文本内容返回不同的响应
        string responseContent = message.Content switch
        {
            "你好" => "您好!欢迎使用我们的服务。",
            "菜单" => "提供的服务:\n1. 天气查询\n2. 新闻资讯\n3. 客服帮助\n4. 关于我们",
            "天气" => await GetWeatherInfoAsync(),
            "新闻" => await GetNewsSummaryAsync(),
            "客服" => "正在转接人工客服,请稍候...",
            _ => $"收到您的消息:{message.Content}\n回复'菜单'了解更多服务。"
        };
        
        // 返回文本消息
        return new TextResponseMessage(message) {
            Content = responseContent
        };
    }
    
    /// <summary>
    /// 处理图片消息
    /// </summary>
    private async Task<ResponseMessageBase> HandleImageMessage(ImageRequestMessage message)
    {
        _logger.LogInformation("收到图片消息: {MediaId}", message.MediaId);
        
        // 保存图片到临时文件
        var imageUrl = await _wechatService.GetTemporaryMediaUrlAsync(message.MediaId);
        await SaveImageToStorageAsync(imageUrl, message.MediaId);
        
        // 返回图片消息
        return new ImageResponseMessage(message) {
            MediaId = message.MediaId // 可以返回相同或不同的图片
        };
    }
    
    /// <summary>
    /// 处理事件消息
    /// </summary>
    private async Task<ResponseMessageBase> HandleEventMessage(BaseEventMessage message)
    {
        switch (message.EventType)
        {
            case EventType.Subscribe:
                return HandleSubscribeEvent(message as SubscribeEventMessage);
            case EventType.Unsubscribe:
                return HandleUnsubscribeEvent(message as UnsubscribeEventMessage);
            case EventType.Scan:
                return HandleScanEvent(message as ScanEventMessage);
            case EventType.Location:
                return HandleLocationEvent(message as LocationEventMessage);
            case EventType.Click:
                return HandleClickEvent(message as ClickEventMessage);
            case EventType.View:
                return HandleViewEvent(message as ViewEventMessage);
            default:
                _logger.LogWarning("未知的事件类型: {EventType}", message.EventType);
                return null;
        }
    }
    
    /// <summary>
    /// 处理关注事件
    /// </summary>
    private ResponseMessageBase HandleSubscribeEvent(SubscribeEventMessage message)
    {
        _logger.LogInformation("用户关注: {FromUserName}", message.FromUserName);
        
        // 记录关注用户
        Task.Run(() => RecordUserSubscribeAsync(message.FromUserName, message.EventKey));
        
        // 欢迎消息
        string welcomeContent = "欢迎关注我们的公众号!\n\n回复'菜单'了解更多服务。";
        
        // 如果有场景值,个性化欢迎语
        if (!string.IsNullOrEmpty(message.EventKey) && message.EventKey.StartsWith("qrscene_"))
        {
            var sceneId = message.EventKey.Replace("qrscene_", "");
            welcomeContent += $"\n\n您通过场景 {sceneId} 关注我们。";
        }
        
        return new TextResponseMessage(message) {
            Content = welcomeContent
        };
    }
    
    // 其他消息处理方法...
    
    // 辅助方法
    private async Task<string> GetWeatherInfoAsync()
    {
        // 模拟获取天气信息
        await Task.Delay(100);
        return "当前天气:晴朗 25°C\n紫外线指数:中等\n适合户外活动";
    }
    
    private async Task<string> GetNewsSummaryAsync()
    {
        // 模拟获取新闻摘要
        await Task.Delay(100);
        return "今日热点:\n1. 最新科技突破\n2. 经济政策更新\n3. 健康生活指南\n回复'新闻1'查看详情";
    }
    
    private async Task SaveImageToStorageAsync(string imageUrl, string mediaId)
    {
        try
        {
            // 保存图片到存储服务
            _logger.LogInformation("保存图片: {MediaId}", mediaId);
            // 实际应用中实现存储逻辑
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "保存图片失败: {MediaId}", mediaId);
        }
    }
    
    private async Task RecordUserSubscribeAsync(string openId, string eventKey)
    {
        try
        {
            // 记录用户关注信息到数据库
            _logger.LogInformation("记录用户关注: {OpenId}, EventKey: {EventKey}", openId, eventKey);
            // 实际应用中实现数据库记录逻辑
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "记录用户关注失败: {OpenId}", openId);
        }
    }
}

// 自定义消息处理器
public class CustomMessageHandler : MessageHandlerBase
{
    private readonly IWechatService _wechatService;
    private readonly IUserService _userService;
    
    public CustomMessageHandler(IWechatService wechatService, IUserService userService)
    {
        _wechatService = wechatService;
        _userService = userService;
    }
    
    [MessageHandler(MessageType.Text)]
    public async Task<ResponseMessageBase> HandleTextMessage(TextRequestMessage message)
    {
        // 根据关键词进行处理
        switch (message.Content.Trim())
        {
            case "帮助":
                return CreateHelpMessage(message);
            case "账户":
                return await CreateAccountInfoMessage(message);
            default:
                return CreateDefaultResponse(message);
        }
    }
    
    [MessageHandler(EventType.Click)]
    public async Task<ResponseMessageBase> HandleMenuClick(ClickEventMessage message)
    {
        // 处理菜单点击事件
        switch (message.EventKey)
        {
            case "MENU_ABOUT":
                return CreateAboutMessage(message);
            case "MENU_SERVICE":
                return CreateServiceListMessage(message);
            case "MENU_CONTACT":
                return CreateContactMessage(message);
            default:
                return CreateDefaultResponse(message);
        }
    }
}

微信支付示例

using Cyclops.Wechat.Models.Payment;
using Cyclops.Wechat.Services;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;

[Route("api/[controller]")]
[ApiController]
public class WechatPayController : ControllerBase
{
    private readonly IWechatPaymentService _paymentService;
    private readonly IOrderService _orderService;
    private readonly ILogger<WechatPayController> _logger;
    
    public WechatPayController(
        IWechatPaymentService paymentService, 
        IOrderService orderService,
        ILogger<WechatPayController> logger)
    {
        _paymentService = paymentService;
        _orderService = orderService;
        _logger = logger;
    }
    
    /// <summary>
    /// 创建支付订单
    /// </summary>
    [HttpPost("create-order")]
    public async Task<IActionResult> CreateOrder([FromBody] CreatePayOrderRequest request)
    {
        try
        {
            if (!ModelState.IsValid)
            {
                _logger.LogWarning("创建支付订单参数无效");
                return BadRequest(ModelState);
            }
            
            // 验证订单信息
            var order = await _orderService.GetOrderByIdAsync(request.OrderId);
            
            if (order == null)
            {
                _logger.LogWarning("订单不存在: {OrderId}", request.OrderId);
                return NotFound("订单不存在");
            }
            
            if (order.Status != OrderStatus.Pending)
            {
                _logger.LogWarning("订单状态不允许支付: {OrderId}, Status: {Status}", 
                    request.OrderId, order.Status);
                return BadRequest("订单状态不允许支付");
            }
            
            // 生成商户订单号
            var outTradeNo = $"{DateTime.Now:yyyyMMddHHmmss}{request.OrderId}{Random.Shared.Next(100, 999)}";
            
            // 创建支付订单请求
            var payRequest = new UnifyOrderRequest {
                Body = order.Title, // 商品描述
                Detail = order.Description, // 商品详情
                OutTradeNo = outTradeNo, // 商户订单号
                TotalFee = (int)(order.Amount * 100), // 订单金额(分)
                SpbillCreateIp = GetClientIp(), // 客户端IP
                NotifyUrl = $"{Request.Scheme}://{Request.Host}/api/wechatpay/notify", // 支付结果通知URL
                TradeType = TradeType.JSAPI, // 交易类型
                OpenId = request.OpenId // 用户OpenId(JSAPI支付必需)
            };
            
            // 调用统一下单API
            var payResult = await _paymentService.UnifyOrderAsync(payRequest);
            
            if (!payResult.IsSuccess)
            {
                _logger.LogError("微信支付统一下单失败: {Error}", payResult.ErrorMessage);
                await _orderService.UpdateOrderStatusAsync(request.OrderId, OrderStatus.PaymentFailed);
                return BadRequest($"创建支付订单失败: {payResult.ErrorMessage}");
            }
            
            // 更新订单支付信息
            await _orderService.UpdateOrderPaymentInfoAsync(request.OrderId, 
                payResult.TransactionId, outTradeNo, PaymentMethod.WechatPay);
            
            // 生成JSAPI支付参数
            var jsApiParameters = _paymentService.GenerateJsApiParameters(payResult.PrepayId);
            
            _logger.LogInformation("微信支付订单创建成功: {OrderId}, {OutTradeNo}", 
                request.OrderId, outTradeNo);
            
            return Ok(new {
                orderId = request.OrderId,
                paymentParameters = jsApiParameters,
                timestamp = jsApiParameters.TimeStamp,
                nonceStr = jsApiParameters.NonceStr,
                package = jsApiParameters.Package,
                signType = jsApiParameters.SignType,
                paySign = jsApiParameters.PaySign
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "创建微信支付订单时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 支付结果通知
    /// </summary>
    [HttpPost("notify")]
    public async Task<IActionResult> PayNotify()
    {
        try
        {
            // 读取请求体
            using var reader = new StreamReader(Request.Body);
            var notifyXml = await reader.ReadToEndAsync();
            
            _logger.LogInformation("收到微信支付通知: {Xml}", notifyXml);
            
            // 解析通知数据
            var notifyResult = _paymentService.ParsePayNotifyXml(notifyXml);
            
            if (!notifyResult.IsSuccess)
            {
                _logger.LogError("解析支付通知失败: {Error}", notifyResult.ErrorMessage);
                return Content(_paymentService.GeneratePayNotifyResult(false));
            }
            
            // 验证签名
            if (!_paymentService.VerifyNotifySign(notifyResult.Parameters))
            {
                _logger.LogError("支付通知签名验证失败");
                return Content(_paymentService.GeneratePayNotifyResult(false));
            }
            
            // 处理支付结果
            var outTradeNo = notifyResult.Parameters["out_trade_no"];
            var transactionId = notifyResult.Parameters["transaction_id"];
            var totalFee = int.Parse(notifyResult.Parameters["total_fee"]);
            var resultCode = notifyResult.Parameters["result_code"];
            
            if (resultCode == "SUCCESS")
            {
                // 支付成功
                await ProcessSuccessfulPayment(outTradeNo, transactionId, totalFee / 100.0m);
                
                _logger.LogInformation("支付成功处理: {OutTradeNo}, {TransactionId}", 
                    outTradeNo, transactionId);
                
                // 返回成功响应给微信服务器
                return Content(_paymentService.GeneratePayNotifyResult(true));
            }
            else
            {
                // 支付失败
                var errCode = notifyResult.Parameters["err_code"];
                var errCodeDes = notifyResult.Parameters["err_code_des"];
                
                _logger.LogError("支付失败: {OutTradeNo}, {ErrCode}: {ErrDes}", 
                    outTradeNo, errCode, errCodeDes);
                
                // 返回成功响应(避免微信重复通知)
                return Content(_paymentService.GeneratePayNotifyResult(true));
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "处理微信支付通知时发生异常");
            return Content(_paymentService.GeneratePayNotifyResult(false));
        }
    }
    
    /// <summary>
    /// 查询订单
    /// </summary>
    [HttpGet("query-order/{orderId}")]
    public async Task<IActionResult> QueryOrder(int orderId)
    {
        try
        {
            // 获取订单信息
            var order = await _orderService.GetOrderByIdAsync(orderId);
            
            if (order == null)
            {
                return NotFound("订单不存在");
            }
            
            if (string.IsNullOrEmpty(order.TransactionId))
            {
                return BadRequest("订单未支付");
            }
            
            // 查询支付订单
            var queryResult = await _paymentService.QueryOrderAsync(order.TransactionId, order.PaymentNo);
            
            if (!queryResult.IsSuccess)
            {
                _logger.LogError("查询支付订单失败: {Error}", queryResult.ErrorMessage);
                return BadRequest($"查询支付订单失败: {queryResult.ErrorMessage}");
            }
            
            return Ok(new {
                orderId = order.Id,
                paymentNo = order.PaymentNo,
                transactionId = queryResult.TransactionId,
                tradeState = queryResult.TradeState,
                payTime = queryResult.TimeEnd,
                totalAmount = queryResult.TotalFee / 100.0m
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "查询微信支付订单时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 申请退款
    /// </summary>
    [HttpPost("refund/{orderId}")]
    public async Task<IActionResult> RefundOrder(int orderId, [FromBody] RefundRequest request)
    {
        try
        {
            // 验证订单信息
            var order = await _orderService.GetOrderByIdAsync(orderId);
            
            if (order == null)
            {
                return NotFound("订单不存在");
            }
            
            if (order.Status != OrderStatus.Paid)
            {
                return BadRequest("订单状态不允许退款");
            }
            
            if (string.IsNullOrEmpty(order.TransactionId))
            {
                return BadRequest("订单未支付,无法退款");
            }
            
            // 生成退款单号
            var outRefundNo = $"refund{DateTime.Now:yyyyMMddHHmmss}{orderId}{Random.Shared.Next(100, 999)}";
            
            // 创建退款请求
            var refundRequest = new RefundOrderRequest {
                TransactionId = order.TransactionId,
                OutTradeNo = order.PaymentNo,
                OutRefundNo = outRefundNo,
                TotalFee = (int)(order.Amount * 100),
                RefundFee = (int)(request.Amount * 100),
                RefundDesc = request.Reason,
                NotifyUrl = $"{Request.Scheme}://{Request.Host}/api/wechatpay/refund-notify" // 退款结果通知URL
            };
            
            // 调用退款API
            var refundResult = await _paymentService.RefundAsync(refundRequest);
            
            if (!refundResult.IsSuccess)
            {
                _logger.LogError("微信支付退款失败: {Error}", refundResult.ErrorMessage);
                return BadRequest($"申请退款失败: {refundResult.ErrorMessage}");
            }
            
            // 更新订单状态
            await _orderService.ApplyRefundAsync(orderId, request.Amount, request.Reason, outRefundNo);
            
            return Ok(new {
                orderId = orderId,
                refundNo = outRefundNo,
                refundId = refundResult.RefundId,
                amount = request.Amount,
                status = "REFUND_PROCESSING"
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "申请微信支付退款时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    // 辅助方法
    private string GetClientIp()
    {
        return Request.HttpContext.Connection.RemoteIpAddress.ToString();
    }
    
    private async Task ProcessSuccessfulPayment(string outTradeNo, string transactionId, decimal amount)
    {
        // 查找订单
        var order = await _orderService.GetOrderByPaymentNoAsync(outTradeNo);
        
        if (order != null && order.Status == OrderStatus.Pending)
        {
            // 更新订单状态为已支付
            await _orderService.UpdateOrderStatusAsync(order.Id, OrderStatus.Paid);
            
            // 触发订单支付成功事件
            await _orderService.TriggerOrderPaidEventAsync(order.Id);
            
            // 发送支付成功通知
            await SendPaymentSuccessNotificationAsync(order.Id, order.UserId);
        }
    }
    
    private async Task SendPaymentSuccessNotificationAsync(int orderId, int userId)
    {
        try
        {
            // 发送模板消息通知用户
            var templateData = new Dictionary<string, TemplateDataItem> {
                { "first", new TemplateDataItem { Value = "您的订单支付成功!" } },
                { "keyword1", new TemplateDataItem { Value = $"订单#{orderId}" } },
                { "keyword2", new TemplateDataItem { Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") } },
                { "remark", new TemplateDataItem { Value = "感谢您的购买,祝您使用愉快!" } }
            };
            
            // 获取用户OpenId
            var user = await _userService.GetUserByIdAsync(userId);
            if (!string.IsNullOrEmpty(user.WechatOpenId))
            {
                await _wechatService.SendTemplateMessageAsync(
                    user.WechatOpenId,
                    "templateId", // 替换为实际的模板ID
                    $"{Request.Scheme}://{Request.Host}/order/detail/{orderId}",
                    templateData
                );
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "发送支付成功通知失败: {OrderId}", orderId);
        }
    }
}

// 请求模型
public class CreatePayOrderRequest
{
    [Required]
    public int OrderId { get; set; }
    
    [Required]
    public string OpenId { get; set; }
}

public class RefundRequest
{
    [Required]
    [Range(0.01, double.MaxValue)]
    public decimal Amount { get; set; }
    
    [Required]
    [StringLength(200)]
    public string Reason { get; set; }
}

// 订单服务接口
public interface IOrderService
{
    Task<Order> GetOrderByIdAsync(int orderId);
    Task<Order> GetOrderByPaymentNoAsync(string paymentNo);
    Task<bool> UpdateOrderStatusAsync(int orderId, OrderStatus status);
    Task<bool> UpdateOrderPaymentInfoAsync(int orderId, string transactionId, string paymentNo, PaymentMethod method);
    Task<bool> ApplyRefundAsync(int orderId, decimal amount, string reason, string refundNo);
    Task TriggerOrderPaidEventAsync(int orderId);
}

public enum OrderStatus
{
    Pending,
    Paid,
    Shipped,
    Completed,
    Cancelled,
    PaymentFailed,
    Refunded
}

public enum PaymentMethod
{
    WechatPay,
    Alipay,
    BankTransfer
}

public class Order
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public decimal Amount { get; set; }
    public OrderStatus Status { get; set; }
    public string PaymentNo { get; set; }
    public string TransactionId { get; set; }
    public PaymentMethod? PaymentMethod { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
    public int UserId { get; set; }
}

微信小程序示例

using Cyclops.Wechat.Models.Miniprogram;
using Cyclops.Wechat.Services;
using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[ApiController]
public class MiniprogramController : ControllerBase
{
    private readonly IWechatMiniprogramService _miniprogramService;
    private readonly ILogger<MiniprogramController> _logger;
    
    public MiniprogramController(IWechatMiniprogramService miniprogramService, ILogger<MiniprogramController> logger)
    {
        _miniprogramService = miniprogramService;
        _logger = logger;
    }
    
    /// <summary>
    /// 生成小程序码
    /// </summary>
    [HttpGet("generate-qrcode")]
    public async Task<IActionResult> GenerateQrcode([FromQuery] string scene, [FromQuery] string page = "pages/index/index", [FromQuery] int width = 430)
    {
        try
        {
            if (string.IsNullOrEmpty(scene))
            {
                return BadRequest("场景值不能为空");
            }
            
            // 调用生成小程序码API
            var qrcodeResult = await _miniprogramService.CreateWxaQrcodeAsync(new WxaQrcodeRequest {
                Scene = scene,
                Page = page,
                Width = width,
                AutoColor = true,
                LineColor = new { r = 0, g = 0, b = 0 },
                IsHyaline = false
            });
            
            if (!qrcodeResult.IsSuccess)
            {
                _logger.LogError("生成小程序码失败: {Error}", qrcodeResult.ErrorMessage);
                return BadRequest($"生成小程序码失败: {qrcodeResult.ErrorMessage}");
            }
            
            // 返回图片流
            Response.ContentType = "image/png";
            return File(qrcodeResult.ImageData, "image/png");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "生成小程序码时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 发送订阅消息
    /// </summary>
    [HttpPost("send-subscribe-message")]
    public async Task<IActionResult> SendSubscribeMessage([FromBody] SubscribeMessageRequest request)
    {
        try
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
            
            // 构建订阅消息数据
            var data = new Dictionary<string, SubscribeMessageData> {
                { "thing1", new SubscribeMessageData { Value = request.OrderTitle } },
                { "time2", new SubscribeMessageData { Value = request.OrderTime } },
                { "amount3", new SubscribeMessageData { Value = request.Amount } },
                { "thing4", new SubscribeMessageData { Value = request.Message } },
                { "thing5", new SubscribeMessageData { Value = request.Remarks } }
            };
            
            // 发送订阅消息
            var sendResult = await _miniprogramService.SendSubscribeMessageAsync(new SendSubscribeMessageRequest {
                ToUser = request.OpenId,
                TemplateId = request.TemplateId,
                Page = request.Page,
                Data = data
            });
            
            if (!sendResult.IsSuccess)
            {
                _logger.LogError("发送订阅消息失败: {Error}", sendResult.ErrorMessage);
                return BadRequest($"发送消息失败: {sendResult.ErrorMessage}");
            }
            
            return Ok(new { message = "消息发送成功" });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "发送订阅消息时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 小程序登录验证
    /// </summary>
    [HttpPost("login")]
    public async Task<IActionResult> MiniprogramLogin([FromBody] MiniprogramLoginRequest request)
    {
        try
        {
            if (string.IsNullOrEmpty(request.Code))
            {
                return BadRequest("登录凭证不能为空");
            }
            
            // 调用登录凭证校验接口
            var loginResult = await _miniprogramService.LoginAsync(request.Code);
            
            if (!loginResult.IsSuccess)
            {
                _logger.LogError("小程序登录校验失败: {Error}", loginResult.ErrorMessage);
                return BadRequest($"登录失败: {loginResult.ErrorMessage}");
            }
            
            // 获取用户唯一标识
            var openId = loginResult.OpenId;
            var unionId = loginResult.UnionId;
            
            // 处理用户登录逻辑
            var userInfo = await ProcessMiniprogramLoginAsync(openId, unionId, request.UserInfo);
            
            // 生成JWT令牌
            var token = GenerateJwtToken(userInfo);
            
            return Ok(new {
                token = token,
                user = new {
                    id = userInfo.Id,
                    nickname = userInfo.Nickname,
                    avatar = userInfo.Avatar
                }
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "小程序登录时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    /// <summary>
    /// 内容安全检测
    /// </summary>
    [HttpPost("security-check")]
    public async Task<IActionResult> SecurityCheck([FromBody] SecurityCheckRequest request)
    {
        try
        {
            if (string.IsNullOrEmpty(request.Content))
            {
                return BadRequest("检测内容不能为空");
            }
            
            // 调用内容安全检测API
            var checkResult = await _miniprogramService.SecurityCheckAsync(request.Content);
            
            if (!checkResult.IsSuccess)
            {
                _logger.LogWarning("内容安全检测失败: {Content}", request.Content);
                return BadRequest(new {
                    pass = false,
                    reason = "内容包含敏感信息,不允许发布"
                });
            }
            
            return Ok(new {
                pass = true
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "内容安全检测时发生异常");
            return StatusCode(500, "服务器内部错误");
        }
    }
    
    // 辅助方法
    private async Task<UserInfo> ProcessMiniprogramLoginAsync(string openId, string unionId, UserInfoRequest userInfo)
    {
        // 查找或创建用户
        var existingUser = await GetUserByOpenIdAsync(openId);
        
        if (existingUser != null)
        {
            // 更新用户信息
            if (!string.IsNullOrEmpty(unionId) && existingUser.UnionId != unionId)
            {
                existingUser.UnionId = unionId;
            }
            
            if (userInfo != null)
            {
                existingUser.Nickname = userInfo.NickName;
                existingUser.Avatar = userInfo.AvatarUrl;
                existingUser.Gender = userInfo.Gender;
                existingUser.Country = userInfo.Country;
                existingUser.Province = userInfo.Province;
                existingUser.City = userInfo.City;
                existingUser.Language = userInfo.Language;
            }
            
            await UpdateUserAsync(existingUser);
            return existingUser;
        }
        else
        {
            // 创建新用户
            var newUser = new UserInfo {
                OpenId = openId,
                UnionId = unionId,
                Nickname = userInfo?.NickName,
                Avatar = userInfo?.AvatarUrl,
                Gender = userInfo?.Gender ?? 0,
                Country = userInfo?.Country,
                Province = userInfo?.Province,
                City = userInfo?.City,
                Language = userInfo?.Language,
                CreatedAt = DateTime.Now,
                LastLoginAt = DateTime.Now
            };
            
            await CreateUserAsync(newUser);
            return newUser;
        }
    }
    
    private string GenerateJwtToken(UserInfo userInfo)
    {
        // 生成JWT令牌的实现
        // 这里省略具体实现
        return "sample-jwt-token";
    }
    
    // 模拟数据库操作
    private Task<UserInfo> GetUserByOpenIdAsync(string openId) => Task.FromResult<UserInfo>(null);
    private Task UpdateUserAsync(UserInfo user) => Task.CompletedTask;
    private Task CreateUserAsync(UserInfo user) => Task.CompletedTask;
}

// 请求模型
public class SubscribeMessageRequest
{
    [Required]
    public string OpenId { get; set; }
    
    [Required]
    public string TemplateId { get; set; }
    
    public string Page { get; set; }
    
    [Required]
    public string OrderTitle { get; set; }
    
    [Required]
    public string OrderTime { get; set; }
    
    [Required]
    public string Amount { get; set; }
    
    [Required]
    public string Message { get; set; }
    
    public string Remarks { get; set; }
}

public class MiniprogramLoginRequest
{
    [Required]
    public string Code { get; set; }
    
    public UserInfoRequest UserInfo { get; set; }
}

public class UserInfoRequest
{
    public string NickName { get; set; }
    public string AvatarUrl { get; set; }
    public int Gender { get; set; }
    public string Country { get; set; }
    public string Province { get; set; }
    public string City { get; set; }
    public string Language { get; set; }
}

public class SecurityCheckRequest
{
    [Required]
    public string Content { get; set; }
}

public class UserInfo
{
    public int Id { get; set; }
    public string OpenId { get; set; }
    public string UnionId { get; set; }
    public string Nickname { get; set; }
    public string Avatar { get; set; }
    public int Gender { get; set; }
    public string Country { get; set; }
    public string Province { get; set; }
    public string City { get; set; }
    public string Language { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime LastLoginAt { get; set; }
}

版本信息

  • 当前版本 NuGet version (Cyclops.Framework)
  • 作者:yswenli
  • 描述:企服版框架中微信集成组件

贡献者

  • yswenli

许可证

保留所有权利

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2026.4.14.2 76 4/14/2026
2026.4.14.1 84 4/14/2026
2026.4.13.1 86 4/13/2026
2026.3.30.1 92 3/30/2026
2026.3.26.1 90 3/26/2026
2026.3.24.1 88 3/24/2026
2026.3.12.2 98 3/12/2026
2026.3.12.1 95 3/12/2026
2026.2.26.1 103 2/26/2026
2026.2.4.1 121 2/4/2026
2026.1.15.1 107 1/15/2026
2026.1.14.2 106 1/14/2026
2026.1.14.1 105 1/14/2026
2026.1.13.2 114 1/13/2026
2026.1.13.1 110 1/13/2026
2026.1.7.1 116 1/7/2026
2025.12.23.1 192 12/23/2025
2025.12.16.1 283 12/16/2025
2025.12.15.2 254 12/15/2025
2025.12.15.1 278 12/15/2025
Loading failed

企服版框架中微信对接相关业务核心项目