using Microsoft.Extensions.Logging; using SKIT.FlurlHttpClient.Wechat.TenpayV3; using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models; using YD_Event.Core.Entities; using YD_Event.Core.Infrastructure.WeChat.Dto; using Furion.DependencyInjection; using Microsoft.AspNetCore.Hosting; using System.Net.Http; using Microsoft.Extensions.Hosting; namespace YD_Event.Core.Infrastructure.WeChat; /// /// 微信支付服务 /// /// /// 提供微信支付相关的核心功能,包括: /// 1. JSAPI支付下单 /// 2. Native支付下单 /// 3. 订单状态查询 /// 4. 退款申请 /// 5. 退款查询 /// 6. 支付通知验证和解密 /// 7. 退款通知验证和解密 /// public class WeChatPayService : ITransient { private readonly ILogger _logger; private readonly IWebHostEnvironment _webHostEnvironment; private readonly IHttpClientFactory _httpClientFactory; private const int DefaultNetworkTimeoutSeconds = 30; public WeChatPayService(ILogger logger, IWebHostEnvironment webHostEnvironment, IHttpClientFactory httpClientFactory) { _logger = logger; _webHostEnvironment = webHostEnvironment; _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory)); } /// /// 创建微信支付客户端 /// /// 微信支付配置信息 /// 微信支付客户端实例 private async Task CreateClientAsync(WeChatPayConfig config) { var privateKey = await GetPrivateKeyAsync(config.MerchantCertificatePrivateKey); if (string.IsNullOrWhiteSpace(privateKey)) { throw new InvalidOperationException("无法获取微信支付商户私钥,请检查配置。"); } var options = new WechatTenpayClientOptions() { MerchantId = config.MerchantIdValue, MerchantV3Secret = config.MerchantV3Secret, MerchantCertificateSerialNumber = config.MerchantCertificateSerialNumber, MerchantCertificatePrivateKey = privateKey }; return new WechatTenpayClient(options); } /// /// 获取微信支付商户私钥 /// /// 私钥来源,可以是:直接的密钥内容、本地文件路径、网络URL /// 私钥内容字符串 private async Task GetPrivateKeyAsync(string source) { if (string.IsNullOrWhiteSpace(source)) { _logger.LogError("私钥来源为空"); throw new ArgumentException("私钥来源不能为空", nameof(source)); } try { var trimmedSource = source.Trim(); if (IsPrivateKeyContent(trimmedSource)) { _logger.LogDebug("使用直接提供的私钥内容"); return trimmedSource; } if (Uri.TryCreate(trimmedSource, UriKind.Absolute, out var uri)) { if (uri.IsFile) { _logger.LogDebug("从本地文件路径加载私钥: {Path}", uri.LocalPath); return await ReadPrivateKeyFromFileAsync(uri.LocalPath); } else if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) { _logger.LogDebug("从网络URL加载私钥: {Url}", trimmedSource); return await ReadPrivateKeyFromNetworkAsync(trimmedSource); } } var localPath = Path.Combine(_webHostEnvironment.WebRootPath, trimmedSource.TrimStart('/', '\\')); if (File.Exists(localPath)) { _logger.LogDebug("从 wwwroot 相对路径加载私钥: {Path}", localPath); return await ReadPrivateKeyFromFileAsync(localPath); } if (File.Exists(trimmedSource)) { _logger.LogDebug("从绝对路径加载私钥: {Path}", trimmedSource); return await ReadPrivateKeyFromFileAsync(trimmedSource); } _logger.LogError("无法从任何来源加载私钥: {Source}", source); throw new FileNotFoundException($"无法找到私钥文件或解析私钥内容: {source}"); } catch (Exception ex) when (ex is not ArgumentException && ex is not FileNotFoundException) { _logger.LogError(ex, "获取私钥过程中发生异常: {Source}", source); throw; } } /// /// 检查输入是否为有效的私钥内容 /// private bool IsPrivateKeyContent(string content) { return content.StartsWith("-----BEGIN", StringComparison.OrdinalIgnoreCase) || content.StartsWith("MII", StringComparison.OrdinalIgnoreCase); } /// /// 从本地文件读取私钥 /// private async Task ReadPrivateKeyFromFileAsync(string filePath) { if (!File.Exists(filePath)) { throw new FileNotFoundException($"私钥文件不存在: {filePath}"); } var content = await File.ReadAllTextAsync(filePath); if (string.IsNullOrWhiteSpace(content)) { throw new InvalidOperationException($"私钥文件内容为空: {filePath}"); } _logger.LogInformation("成功从文件加载私钥: {Path}", filePath); return content; } /// /// 从网络URL读取私钥 /// private async Task ReadPrivateKeyFromNetworkAsync(string url) { var client = _httpClientFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(DefaultNetworkTimeoutSeconds); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(DefaultNetworkTimeoutSeconds + 5)); try { var response = await client.GetAsync(url, cts.Token); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(cts.Token); if (string.IsNullOrWhiteSpace(content)) { throw new InvalidOperationException($"从网络获取的私钥内容为空: {url}"); } _logger.LogInformation("成功从网络加载私钥: {Url}", url); return content; } catch (HttpRequestException ex) { _logger.LogError(ex, "网络请求失败,无法获取私钥: {Url}", url); throw new InvalidOperationException($"网络请求失败,无法获取私钥: {url}", ex); } catch (OperationCanceledException ex) { _logger.LogError(ex, "获取私钥请求超时: {Url}", url); throw new TimeoutException($"获取私钥请求超时: {url}", ex); } } /// /// 创建 JSAPI 支付 /// /// 微信支付配置信息 /// JSAPI 支付请求参数 /// 支付结果,包含 prepay_id 和支付参数 /// /// JSAPI 支付适用于小程序、公众号等场景,需要用户的 OpenId /// public async Task CreateJsApiPayAsync(WeChatPayConfig config, CreateJsApiPayRequest request) { try { var client = await CreateClientAsync(config); var tenpayRequest = new CreatePayTransactionJsapiRequest() { OutTradeNumber = request.OutTradeNo, AppId = request.AppId, Description = request.Description, NotifyUrl = request.NotifyUrl, Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = request.TotalAmount, Currency = request.Currency }, Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = request.OpenId } }; if (request.ExpireTime.HasValue) { tenpayRequest.ExpireTime = request.ExpireTime.Value; } var response = await client.ExecuteCreatePayTransactionJsapiAsync(tenpayRequest); if (response.IsSuccessful()) { var result = new WeChatPayResult { Success = true, TradeType = "JSAPI", PrepayId = response.PrepayId }; var parameters = client.GenerateParametersForJsapiPayRequest(request.AppId, response.PrepayId); result.JsApiParameters = new JsApiPayParameters { AppId = parameters["appId"], TimeStamp = parameters["timeStamp"], NonceStr = parameters["nonceStr"], Package = parameters["package"], SignType = parameters["signType"], PaySign = parameters["paySign"] }; return result; } else { _logger.LogError($"微信 JSAPI 支付下单失败: {response.ErrorMessage}"); return new WeChatPayResult { Success = false, Message = response.ErrorMessage }; } } catch (Exception ex) { _logger.LogError(ex, "微信 JSAPI 支付下单异常"); return new WeChatPayResult { Success = false, Message = ex.Message }; } } /// /// 创建 Native 支付 /// /// 微信支付配置信息 /// Native 支付请求参数 /// 支付结果,包含二维码链接 /// /// Native 支付适用于生成二维码供用户扫描支付的场景 /// public async Task CreateNativePayAsync(WeChatPayConfig config, CreateNativePayRequest request) { try { var client = await CreateClientAsync(config); var tenpayRequest = new CreatePayTransactionNativeRequest() { OutTradeNumber = request.OutTradeNo, AppId = request.AppId, Description = request.Description, NotifyUrl = request.NotifyUrl, Amount = new CreatePayTransactionNativeRequest.Types.Amount() { Total = request.TotalAmount, Currency = request.Currency } }; if (request.ExpireTime.HasValue) { tenpayRequest.ExpireTime = request.ExpireTime.Value; } var response = await client.ExecuteCreatePayTransactionNativeAsync(tenpayRequest); if (response.IsSuccessful()) { return new WeChatPayResult { Success = true, TradeType = "NATIVE", CodeUrl = response.QrcodeUrl, QrcodeUrl = response.QrcodeUrl }; } else { _logger.LogError($"微信 Native 支付下单失败: {response.ErrorMessage}"); return new WeChatPayResult { Success = false, Message = response.ErrorMessage }; } } catch (Exception ex) { _logger.LogError(ex, "微信 Native 支付下单异常"); return new WeChatPayResult { Success = false, Message = ex.Message }; } } /// /// 查询订单状态 /// /// 微信支付配置信息 /// 商户订单号 /// 订单查询结果 /// /// 根据商户订单号查询微信支付订单的状态 /// public async Task QueryOrderAsync(WeChatPayConfig config, string outTradeNo) { try { var client = await CreateClientAsync(config); var request = new GetPayTransactionByOutTradeNumberRequest() { OutTradeNumber = outTradeNo, MerchantId = config.MerchantIdValue }; var response = await client.ExecuteGetPayTransactionByOutTradeNumberAsync(request); if (response.IsSuccessful()) { return new WeChatPayQueryResult { Success = true, TradeState = response.TradeState, TradeStateDesc = response.TradeStateDescription, TransactionId = response.TransactionId, OutTradeNo = response.OutTradeNumber, TotalAmount = response.Amount?.Total, PayerAmount = response.Amount?.PayerTotal, SuccessTime = response.SuccessTime }; } else { _logger.LogError($"查询订单失败: {response.ErrorMessage}"); return new WeChatPayQueryResult { Success = false, Message = response.ErrorMessage }; } } catch (Exception ex) { _logger.LogError(ex, "查询订单异常"); return new WeChatPayQueryResult { Success = false, Message = ex.Message }; } } /// /// 创建退款申请 /// /// 微信支付配置信息 /// 退款请求参数 /// 退款结果 public async Task CreateRefundAsync(WeChatPayConfig config, CreateRefundRequest request) { try { _logger.LogInformation($"微信退款申请: 商户单号={request.OutTradeNo}, 退款单号={request.OutRefundNo}, 退款金额={request.RefundAmount}"); var client = await CreateClientAsync(config); var tenpayRequest = new CreateRefundDomesticRefundRequest { OutTradeNumber = request.OutTradeNo, OutRefundNumber = request.OutRefundNo, Reason = request.Reason ?? "用户取消报名", NotifyUrl = request.NotifyUrl, Amount = new CreateRefundDomesticRefundRequest.Types.Amount() { Refund = request.RefundAmount, Total = request.TotalAmount, Currency = request.Currency ?? "CNY" } }; var response = await client.ExecuteCreateRefundDomesticRefundAsync(tenpayRequest); _logger.LogDebug($"微信退款响应: {System.Text.Json.JsonSerializer.Serialize(response)}"); if (response.IsSuccessful()) { _logger.LogInformation($"微信退款申请成功: 退款单号={response.OutRefundNumber}, 微信退款ID={response.RefundId}"); return new WeChatRefundResult { Success = true, RefundId = response.RefundId, OutRefundNo = response.OutRefundNumber, OutTradeNo = response.OutTradeNumber, RefundStatus = response.Status, Message = "退款申请已提交", WeChatResponse = System.Text.Json.JsonSerializer.Serialize(response) }; } else { _logger.LogError($"微信退款申请失败: Code={response.ErrorCode}, Message={response.ErrorMessage}"); return new WeChatRefundResult { Success = false, Message = response.ErrorMessage, ErrorCode = response.ErrorCode, WeChatResponse = System.Text.Json.JsonSerializer.Serialize(response) }; } } catch (HttpRequestException ex) { _logger.LogError(ex, "微信退款网络请求异常"); return new WeChatRefundResult { Success = false, Message = "网络请求失败,请稍后重试" }; } catch (Exception ex) { _logger.LogError(ex, "微信退款申请异常"); return new WeChatRefundResult { Success = false, Message = ex.Message }; } } /// /// 查询退款状态 /// /// 微信支付配置信息 /// 商户退款单号 /// 退款查询结果 public async Task QueryRefundAsync(WeChatPayConfig config, string outRefundNo) { try { _logger.LogInformation($"查询微信退款: 退款单号={outRefundNo}"); var client = await CreateClientAsync(config); var tenpayRequest = new GetRefundDomesticRefundByOutRefundNumberRequest { OutRefundNumber = outRefundNo }; var response = await client.ExecuteGetRefundDomesticRefundByOutRefundNumberAsync(tenpayRequest); _logger.LogDebug($"微信退款查询响应: {System.Text.Json.JsonSerializer.Serialize(response)}"); if (response.IsSuccessful()) { _logger.LogInformation($"查询微信退款成功: 退款单号={response.OutRefundNumber}, 状态={response.Status}"); return new WeChatRefundQueryResult { Success = true, RefundId = response.RefundId, OutRefundNo = response.OutRefundNumber, OutTradeNo = response.OutTradeNumber, RefundStatus = response.Status, RefundAmount = response.Amount?.Refund, PayerRefundAmount = response.Amount?.PayerRefund, SuccessTime = response.SuccessTime, Message = "查询成功", WeChatResponse = System.Text.Json.JsonSerializer.Serialize(response) }; } else { _logger.LogError($"查询微信退款失败: Code={response.ErrorCode}, Message={response.ErrorMessage}"); return new WeChatRefundQueryResult { Success = false, Message = response.ErrorMessage, WeChatResponse = System.Text.Json.JsonSerializer.Serialize(response) }; } } catch (HttpRequestException ex) { _logger.LogError(ex, "查询微信退款网络请求异常"); return new WeChatRefundQueryResult { Success = false, Message = "网络请求失败,请稍后重试" }; } catch (Exception ex) { _logger.LogError(ex, "查询退款异常"); return new WeChatRefundQueryResult { Success = false, Message = ex.Message }; } } /// /// 验证支付通知签名 /// /// 微信支付配置信息 /// 支付通知请求参数 /// 签名是否有效 /// /// 验证微信支付通知的签名是否有效 /// public async Task VerifyPayNotifyAsync(WeChatPayConfig config, WeChatPayNotifyRequest notifyRequest) { try { var client = await CreateClientAsync(config); bool valid = client.VerifyEventSignature( notifyRequest.Timestamp, notifyRequest.Nonce, notifyRequest.Body, notifyRequest.Signature, notifyRequest.SerialNumber ); return await Task.FromResult(valid); } catch (Exception ex) { _logger.LogError(ex, "验证支付通知签名异常"); return false; } } /// /// 验证退款通知签名 /// /// 微信支付配置信息 /// 退款通知请求参数 /// 签名是否有效 /// /// 验证微信退款通知的签名是否有效 /// public async Task VerifyRefundNotifyAsync(WeChatPayConfig config, WeChatRefundNotifyRequest notifyRequest) { try { var client = await CreateClientAsync(config); bool valid = client.VerifyEventSignature( notifyRequest.Timestamp, notifyRequest.Nonce, notifyRequest.Body, notifyRequest.Signature, notifyRequest.SerialNumber ); return await Task.FromResult(valid); } catch (Exception ex) { _logger.LogError(ex, "验证退款通知签名异常"); return false; } } /// /// 解密支付通知数据 /// /// 微信支付配置信息 /// 支付通知请求参数 /// 解密后的支付通知数据,失败返回 null /// /// 解密微信支付通知的加密数据,仅处理 TRANSACTION.SUCCESS 事件 /// public async Task DecryptPayNotifyDataAsync(WeChatPayConfig config, WeChatPayNotifyRequest notifyRequest) { try { var client = await CreateClientAsync(config); var callbackModel = client.DeserializeEvent(notifyRequest.Body); if (callbackModel.EventType == "TRANSACTION.SUCCESS") { var eventData = client.DecryptEventResource(callbackModel); return new WeChatPayNotifyData { EventType = callbackModel.EventType, OutTradeNo = eventData.OutTradeNumber, TransactionId = eventData.TransactionId, TradeState = eventData.TradeState, TotalAmount = eventData.Amount?.Total, PayerAmount = eventData.Amount?.PayerTotal, SuccessTime = eventData.SuccessTime }; } return null; } catch (Exception ex) { _logger.LogError(ex, "解密支付通知数据异常"); return null; } } /// /// 解密退款通知数据 /// /// 微信支付配置信息 /// 退款通知请求参数 /// 解密后的退款通知数据,失败返回 null /// /// 解密微信退款通知的加密数据,处理 REFUND.SUCCESS、REFUND.ABNORMAL、REFUND.CLOSED 事件 /// public async Task DecryptRefundNotifyDataAsync(WeChatPayConfig config, WeChatRefundNotifyRequest notifyRequest) { try { var client = await CreateClientAsync(config); var callbackModel = client.DeserializeEvent(notifyRequest.Body); if (callbackModel.EventType == "REFUND.SUCCESS" || callbackModel.EventType == "REFUND.ABNORMAL" || callbackModel.EventType == "REFUND.CLOSED") { var eventData = client.DecryptEventResource(callbackModel); return new WeChatRefundNotifyData { EventType = callbackModel.EventType, OutTradeNo = eventData.OutTradeNumber, OutRefundNo = eventData.OutRefundNumber, RefundId = eventData.RefundId, RefundStatus = eventData.RefundStatus, RefundAmount = eventData.Amount?.Refund, PayerRefundAmount = eventData.Amount?.PayerRefund, SuccessTime = eventData.SuccessTime }; } return null; } catch (Exception ex) { _logger.LogError(ex, "解密退款通知数据异常"); return null; } } }