企服版框架中微信对接相关业务核心项目
License
—
Deps
8
Install Size
—
Vulns
✓ 0
Published
Feb 26, 2026
$ dotnet add package TJC.Cyclops.WechatCyclops.Wechat是Cyclops.Framework框架中的微信集成组件,提供与微信平台的全面对接能力,包括微信公众号、微信小程序、企业微信、微信支付和开放平台等服务的统一封装。该组件简化了微信API的调用过程,提供了完整的OAuth认证、消息处理、素材管理、用户管理、模板消息、微信支付等功能,帮助开发者快速构建微信生态应用。
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();
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; }
}
保留所有权利