using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Xml; using MiAssessment.Core.Interfaces; using MiAssessment.Model.Data; using MiAssessment.Model.Entities; using MiAssessment.Model.Models.Auth; using MiAssessment.Model.Models.Payment; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace MiAssessment.Core.Services; /// /// 微信支付服务实现 /// public class WechatPayService : IWechatPayService { private readonly MiAssessmentDbContext _dbContext; private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly IWechatPayConfigService _configService; private readonly IWechatService _wechatService; private readonly IRedisService _redisService; private readonly WechatPaySettings _settings; private readonly AppSettings _appSettings; private readonly Lazy? _v3ServiceLazy; /// /// 微信统一下单API地址 /// private const string UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /// /// 微信发货通知API地址 /// private const string SHIPPING_NOTIFY_URL = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info"; /// /// 发货失败订单Redis键前缀 /// private const string FAILED_SHIPPING_KEY_PREFIX = "post_order:"; /// /// 发货失败订单Redis过期时间(3天) /// private static readonly TimeSpan FAILED_SHIPPING_EXPIRY = TimeSpan.FromDays(3); /// /// 测试用户支付金额(分) /// private const int TEST_USER_PAY_AMOUNT = 1; // 0.01元 = 1分 public WechatPayService( MiAssessmentDbContext dbContext, HttpClient httpClient, ILogger logger, IWechatPayConfigService configService, IWechatService wechatService, IRedisService redisService, IOptions settings, AppSettings appSettings, Lazy? v3ServiceLazy = null) { _dbContext = dbContext; _httpClient = httpClient; _logger = logger; _configService = configService; _wechatService = wechatService; _redisService = redisService; _settings = settings.Value; _appSettings = appSettings; _v3ServiceLazy = v3ServiceLazy; } /// public async Task CreatePaymentAsync(WechatPayRequest request) { try { _logger.LogInformation("开始创建微信支付订单: OrderNo={OrderNo}, UserId={UserId}, Amount={Amount}", request.OrderNo, request.UserId, request.Amount); // 1. 根据订单号获取商户配置,判断支付版本 var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo); // 2. 版本路由:如果配置为 V3 且 V3 服务可用,则使用 V3 流程 if (merchantConfig.PayVersion == "V3" && _v3ServiceLazy != null) { _logger.LogInformation("商户配置为 V3 版本,路由到 V3 流程: MchId={MchId}", merchantConfig.MchId); return await _v3ServiceLazy.Value.CreateJsapiOrderAsync(request); } // 3. 使用 V2 流程 _logger.LogDebug("使用 V2 支付流程: MchId={MchId}, PayVersion={PayVersion}", merchantConfig.MchId, merchantConfig.PayVersion); // 4. 获取用户信息和OpenId var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == request.UserId); if (user == null) { _logger.LogWarning("用户不存在: UserId={UserId}", request.UserId); return new WechatPayResult { Status = 0, Msg = "用户不存在" }; } var openId = string.IsNullOrEmpty(request.OpenId) ? user.OpenId : request.OpenId; if (string.IsNullOrEmpty(openId)) { _logger.LogWarning("用户OpenId为空: UserId={UserId}", request.UserId); return new WechatPayResult { Status = 0, Msg = "用户OpenId不存在" }; } // 5. 使用已获取的商户配置 var appId = merchantConfig.AppId; var mchId = merchantConfig.MchId; var merchantKey = merchantConfig.Key; _logger.LogDebug("使用商户配置: MchId={MchId}, AppId={AppId}", mchId, appId); // 6. 生成随机字符串 var nonceStr = GenerateNonceStr(); var callbackNonceStr = GenerateNonceStr(); // 7. 生成回调通知URL var notifyUrl = GenerateNotifyUrl(request.Attach, request.UserId, request.OrderNo, callbackNonceStr); // 验证回调通知URL if (string.IsNullOrEmpty(notifyUrl)) { _logger.LogError("支付回调URL未配置(NotifyBaseUrl为空),请在运营管理-支付配置中设置回调URL"); return new WechatPayResult { Status = 0, Msg = "支付回调URL未配置,请联系管理员在后台配置" }; } // 8. 保存通知记录(order_notify) await SaveOrderNotifyAsync(request.OrderNo, notifyUrl, callbackNonceStr, request.Amount, request.Attach, openId); // 9. 构建统一下单参数 var body = TruncateBody(request.Body, 30); var totalFee = (int)Math.Round(request.Amount * 100); // 转换为分 // 测试环境下,IsTest=2 的用户支付金额为 0.01 元 if (_appSettings.IsTestEnvironment && user.IsTest == 2) { _logger.LogInformation("测试用户支付金额调整: UserId={UserId}, 原金额={OriginalAmount}分, 调整为={TestAmount}分", request.UserId, totalFee, TEST_USER_PAY_AMOUNT); totalFee = TEST_USER_PAY_AMOUNT; } var unifiedOrderParams = new Dictionary { { "appid", appId }, { "mch_id", mchId }, { "nonce_str", nonceStr }, { "body", body }, { "attach", request.Attach }, { "out_trade_no", request.OrderNo }, { "notify_url", notifyUrl }, { "total_fee", totalFee.ToString() }, { "spbill_create_ip", GetClientIp() }, { "trade_type", "JSAPI" }, { "openid", openId } }; // 10. 生成签名 unifiedOrderParams["sign"] = MakeSign(unifiedOrderParams, merchantKey); // 11. 转换为XML并调用微信API var requestXml = DictionaryToXml(unifiedOrderParams); _logger.LogDebug("统一下单请求XML: {Xml}", requestXml); var responseXml = await PostXmlAsync(UNIFIED_ORDER_URL, requestXml); _logger.LogDebug("统一下单响应XML: {Xml}", responseXml); // 12. 解析响应 var responseData = XmlToDictionary(responseXml); if (responseData == null) { _logger.LogError("解析微信响应失败"); return new WechatPayResult { Status = 0, Msg = "网络故障,请稍后重试(解析响应失败)" }; } // 13. 检查返回结果 if (!responseData.TryGetValue("return_code", out var returnCode) || returnCode != "SUCCESS") { var returnMsg = responseData.GetValueOrDefault("return_msg", "未知错误"); _logger.LogWarning("统一下单失败: return_code={ReturnCode}, return_msg={ReturnMsg}", returnCode, returnMsg); return new WechatPayResult { Status = 0, Msg = $"网络故障,请稍后重试({returnMsg})" }; } if (!responseData.TryGetValue("result_code", out var resultCode) || resultCode != "SUCCESS") { var errCode = responseData.GetValueOrDefault("err_code", ""); var errCodeDes = responseData.GetValueOrDefault("err_code_des", "未知错误"); _logger.LogWarning("统一下单业务失败: err_code={ErrCode}, err_code_des={ErrCodeDes}", errCode, errCodeDes); return new WechatPayResult { Status = 0, Msg = $"支付失败({GetErrorMessage(errCode, errCodeDes)})" }; } // 14. 获取prepay_id if (!responseData.TryGetValue("prepay_id", out var prepayId) || string.IsNullOrEmpty(prepayId)) { _logger.LogError("统一下单成功但prepay_id为空"); return new WechatPayResult { Status = 0, Msg = "网络故障,请稍后重试(prepay_id为空)" }; } // 15. 构建返回给前端的支付参数 var timeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var payNonceStr = GenerateNonceStr(); var payParams = new Dictionary { { "appId", appId }, { "timeStamp", timeStamp }, { "nonceStr", payNonceStr }, { "package", $"prepay_id={prepayId}" }, { "signType", "MD5" } }; // 16. 生成支付签名 var paySign = MakeSign(payParams, merchantKey); _logger.LogInformation("微信支付订单创建成功: OrderNo={OrderNo}, PrepayId={PrepayId}", request.OrderNo, prepayId); return new WechatPayResult { Status = 1, Msg = "success", Data = new WechatPayData { AppId = appId, TimeStamp = timeStamp, NonceStr = payNonceStr, Package = $"prepay_id={prepayId}", SignType = "MD5", PaySign = paySign, IsWeixin = 1 } }; } catch (Exception ex) { _logger.LogError(ex, "创建微信支付订单异常: OrderNo={OrderNo}", request.OrderNo); return new WechatPayResult { Status = 0, Msg = "系统错误,请稍后重试" }; } } /// /// 生成回调通知URL /// private string GenerateNotifyUrl(string attach, long userId, string orderNo, string nonceStr) { var baseUrl = _settings.NotifyBaseUrl; if (string.IsNullOrEmpty(baseUrl)) { return string.Empty; } return $"{baseUrl.TrimEnd('/')}/api/notify/{attach}/{userId}/{orderNo}/{nonceStr}"; } /// /// 保存订单通知记录 /// private async Task SaveOrderNotifyAsync(string orderNo, string notifyUrl, string nonceStr, decimal amount, string attach, string openId) { var orderNotify = new OrderNotify { OrderNo = orderNo, NotifyUrl = notifyUrl, NonceStr = nonceStr, PayTime = DateTime.Now, PayAmount = amount, Status = 0, RetryCount = 0, Attach = attach, OpenId = openId, CreateTime = DateTime.Now, UpdateTime = DateTime.Now }; _dbContext.OrderNotifies.Add(orderNotify); await _dbContext.SaveChangesAsync(); _logger.LogDebug("保存订单通知记录: OrderNo={OrderNo}, NotifyUrl={NotifyUrl}", orderNo, notifyUrl); } /// /// 截断商品描述(微信有最大长度限制) /// private static string TruncateBody(string body, int maxLength) { if (string.IsNullOrEmpty(body)) { return "商品购买"; } if (body.Length <= maxLength) { return body; } return body.Substring(0, maxLength); } /// /// 生成32位随机字符串 /// private static string GenerateNonceStr(int length = 32) { const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var random = new Random(); var result = new char[length]; for (int i = 0; i < length; i++) { result[i] = chars[random.Next(chars.Length)]; } return new string(result); } /// /// 获取客户端IP /// private static string GetClientIp() { // 在实际环境中应该从HttpContext获取 // 这里返回默认值,实际使用时需要通过依赖注入获取IHttpContextAccessor return "127.0.0.1"; } /// /// 将字典转换为XML /// private static string DictionaryToXml(Dictionary parameters) { var sb = new StringBuilder(); sb.Append(""); foreach (var kvp in parameters) { if (string.IsNullOrEmpty(kvp.Value)) { continue; } // 数字类型不需要CDATA if (int.TryParse(kvp.Value, out _) || decimal.TryParse(kvp.Value, out _)) { sb.Append($"<{kvp.Key}>{kvp.Value}"); } else { sb.Append($"<{kvp.Key}>"); } } sb.Append(""); return sb.ToString(); } /// /// 将XML转换为字典 /// private static Dictionary? XmlToDictionary(string xml) { if (string.IsNullOrEmpty(xml)) { return null; } try { var doc = new XmlDocument(); doc.LoadXml(xml); var root = doc.DocumentElement; if (root == null) { return null; } var result = new Dictionary(); foreach (XmlNode node in root.ChildNodes) { if (node.NodeType == XmlNodeType.Element) { result[node.Name] = node.InnerText; } } return result; } catch { return null; } } /// /// 发送XML POST请求 /// private async Task PostXmlAsync(string url, string xml, int timeout = 30) { try { var content = new StringContent(xml, Encoding.UTF8, "application/xml"); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeout)); var response = await _httpClient.PostAsync(url, content, cts.Token); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); } catch (Exception ex) { _logger.LogError(ex, "发送XML请求失败: Url={Url}", url); throw; } } /// /// 获取错误信息 /// private static string GetErrorMessage(string errCode, string errCodeDes) { var errorMessages = new Dictionary { { "NOAUTH", "商户未开通此接口权限" }, { "NOTENOUGH", "用户账号余额不足" }, { "ORDERNOTEXIST", "订单号不存在" }, { "ORDERPAID", "商户订单已支付,请勿重复提交" }, { "ORDERCLOSED", "当前订单已关闭,无法支付" }, { "SYSTEMERROR", "系统错误!系统超时" }, { "APPID_NOT_EXIST", "参数中缺少APPID" }, { "MCHID_NOT_EXIST", "参数中缺少MCHID" }, { "APPID_MCHID_NOT_MATCH", "appid和mch_id不匹配" }, { "LACK_PARAMS", "缺少必要的请求参数" }, { "OUT_TRADE_NO_USED", "同一笔交易不能多次提交" }, { "SIGNERROR", "参数签名结果不正确" }, { "XML_FORMAT_ERROR", "XML格式错误" }, { "REQUIRE_POST_METHOD", "未使用post传递参数" }, { "POST_DATA_EMPTY", "post数据不能为空" }, { "NOT_UTF8", "未使用指定编码格式" } }; if (!string.IsNullOrEmpty(errCode) && errorMessages.TryGetValue(errCode, out var message)) { return message; } return errCodeDes; } /// public bool VerifySign(Dictionary parameters, string sign, string? merchantKey = null) { if (string.IsNullOrEmpty(sign)) { _logger.LogWarning("签名验证失败:签名为空"); return false; } var calculatedSign = MakeSign(parameters, merchantKey); var isValid = string.Equals(calculatedSign, sign, StringComparison.OrdinalIgnoreCase); if (!isValid) { _logger.LogWarning("签名验证失败:计算签名={CalculatedSign},传入签名={Sign}", calculatedSign, sign); } return isValid; } /// public string MakeSign(Dictionary parameters, string? merchantKey = null) { // 获取商户密钥 var key = merchantKey ?? _settings.DefaultMerchant.Key; // 签名步骤一:将字典按参数名ASCII码从小到大排序 // 过滤掉空值和sign字段 var sortedParams = parameters .Where(p => !string.IsNullOrEmpty(p.Value) && !string.Equals(p.Key, "sign", StringComparison.OrdinalIgnoreCase)) .OrderBy(p => p.Key, StringComparer.Ordinal) .ToList(); // 签名步骤二:将参数拼接为URL格式 key=value&key=value var urlParams = ToUrlParams(sortedParams); // 签名步骤三:在string后面加上KEY var signString = $"{urlParams}&key={key}"; // 签名步骤四:MD5加密 using var md5 = MD5.Create(); var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(signString)); // 签名步骤五:所有字符转为大写 return BitConverter.ToString(hashBytes).Replace("-", "").ToUpper(); } /// /// 将参数拼接为URL格式: key=value&key=value /// /// 排序后的参数列表 /// URL格式字符串 private static string ToUrlParams(IEnumerable> parameters) { var parts = parameters.Select(p => $"{p.Key}={p.Value}"); return string.Join("&", parts); } /// /// 根据订单号获取商户密钥 /// /// 订单号 /// 商户密钥 public string GetMerchantKeyByOrderNo(string orderNo) { var merchant = GetMerchantByOrderNo(orderNo); return merchant.Key; } /// /// 验证微信回调签名 /// /// 回调数据 /// 是否验证通过 public bool VerifyNotifySign(WechatNotifyData notifyData) { // 从回调数据构建参数字典 var parameters = new Dictionary { { "return_code", notifyData.ReturnCode }, { "return_msg", notifyData.ReturnMsg }, { "result_code", notifyData.ResultCode }, { "appid", notifyData.AppId }, { "mch_id", notifyData.MchId }, { "nonce_str", notifyData.NonceStr }, { "openid", notifyData.OpenId }, { "trade_type", notifyData.TradeType }, { "bank_type", notifyData.BankType }, { "total_fee", notifyData.TotalFee.ToString() }, { "fee_type", notifyData.FeeType }, { "cash_fee", notifyData.CashFee.ToString() }, { "transaction_id", notifyData.TransactionId }, { "out_trade_no", notifyData.OutTradeNo }, { "attach", notifyData.Attach }, { "time_end", notifyData.TimeEnd } }; // 添加可选字段 if (!string.IsNullOrEmpty(notifyData.ErrCode)) parameters["err_code"] = notifyData.ErrCode; if (!string.IsNullOrEmpty(notifyData.ErrCodeDes)) parameters["err_code_des"] = notifyData.ErrCodeDes; if (!string.IsNullOrEmpty(notifyData.SignType)) parameters["sign_type"] = notifyData.SignType; // 根据商户号获取对应的密钥 var merchantKey = GetMerchantKeyByMchId(notifyData.MchId); return VerifySign(parameters, notifyData.Sign, merchantKey); } /// /// 根据商户号获取商户密钥 /// /// 商户号 /// 商户密钥 private string GetMerchantKeyByMchId(string mchId) { // 先从配置的商户列表中查找 var merchant = _settings.Merchants.FirstOrDefault(m => m.MchId == mchId); if (merchant != null) { return merchant.Key; } // 如果没找到,检查默认商户 if (_settings.DefaultMerchant.MchId == mchId) { return _settings.DefaultMerchant.Key; } // 返回默认商户密钥 _logger.LogWarning("未找到商户号 {MchId} 的配置,使用默认商户密钥", mchId); return _settings.DefaultMerchant.Key; } /// public async Task PostOrderShippingAsync(OrderShippingNotifyRequest request) { try { _logger.LogInformation("开始发送订单发货通知: OrderNo={OrderNo}, OpenId={OpenId}", request.OrderNo, request.OpenId); // 1. 根据订单号获取商户配置 var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo); var mchId = merchantConfig.MchId; var appId = merchantConfig.AppId; _logger.LogDebug("使用商户配置: MchId={MchId}, AppId={AppId}", mchId, appId); // 2. 获取access_token var accessToken = await _wechatService.GetAccessTokenAsync(appId); if (string.IsNullOrEmpty(accessToken)) { _logger.LogError("获取access_token失败: AppId={AppId}", appId); // 保存到自动重试队列 await SaveFailedShippingOrderAsync(request, merchantConfig, -1, "获取access_token失败"); return new OrderShippingNotifyResult { Success = false, ErrCode = -1, ErrMsg = "获取access_token失败", QueuedForRetry = true }; } // 3. 构建发货通知消息 var itemDesc = GetShippingItemDesc(request); // 4. 构建请求参数 var uploadTime = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00"; var requestBody = new { order_key = new { order_number_type = 1, // 使用商户订单号 mchid = mchId, out_trade_no = request.OrderNo }, logistics_type = request.LogisticsType, // 物流类型,4=虚拟商品 delivery_mode = request.DeliveryMode, // 发货模式,1=统一发货 shipping_list = new[] { new { item_desc = itemDesc } }, upload_time = uploadTime, payer = new { openid = request.OpenId } }; var requestJson = JsonSerializer.Serialize(requestBody); // 5. 记录请求日志 _logger.LogDebug("发货通知请求: OrderNo={OrderNo}, MchId={MchId}, Request={Request}", request.OrderNo, mchId, requestJson); // 6. 调用微信API var requestUrl = $"{SHIPPING_NOTIFY_URL}?access_token={accessToken}"; var content = new StringContent(requestJson, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync(requestUrl, content); var responseContent = await response.Content.ReadAsStringAsync(); // 7. 记录响应日志 _logger.LogDebug("发货通知响应: OrderNo={OrderNo}, Response={Response}", request.OrderNo, responseContent); // 8. 解析响应 using var jsonDoc = JsonDocument.Parse(responseContent); var root = jsonDoc.RootElement; var errCode = root.TryGetProperty("errcode", out var errCodeProp) ? errCodeProp.GetInt32() : -1; var errMsg = root.TryGetProperty("errmsg", out var errMsgProp) ? errMsgProp.GetString() ?? "unknown" : "unknown"; // 9. 判断结果 if (errCode == 0 && errMsg == "ok") { _logger.LogInformation("发货通知成功: OrderNo={OrderNo}", request.OrderNo); return new OrderShippingNotifyResult { Success = true, ErrCode = 0, ErrMsg = "ok" }; } else { _logger.LogWarning("发货通知失败: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}", request.OrderNo, errCode, errMsg); // 保存到自动重试队列 await SaveFailedShippingOrderAsync(request, merchantConfig, errCode, errMsg); return new OrderShippingNotifyResult { Success = false, ErrCode = errCode, ErrMsg = errMsg, QueuedForRetry = true }; } } catch (Exception ex) { _logger.LogError(ex, "发货通知异常: OrderNo={OrderNo}", request.OrderNo); // 尝试保存到自动重试队列 try { var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo); await SaveFailedShippingOrderAsync(request, merchantConfig, -1, ex.Message); } catch (Exception saveEx) { _logger.LogError(saveEx, "保存失败订单到自动重试队列时发生错误: OrderNo={OrderNo}", request.OrderNo); } return new OrderShippingNotifyResult { Success = false, ErrCode = -1, ErrMsg = ex.Message, QueuedForRetry = true }; } } /// /// 获取发货商品描述 /// private static string GetShippingItemDesc(OrderShippingNotifyRequest request) { // 如果请求中指定了描述,直接使用 if (!string.IsNullOrEmpty(request.ItemDesc)) { return request.ItemDesc; } // 根据订单前缀判断消息内容 if (request.OrderNo.StartsWith("FH_")) { // 发货订单 return "您购买的实物商品正在处理中,请联系客服获取物流信息"; } else { // 虚拟商品(测评服务等) return "您购买的测评服务已开通,请在小程序中查看"; } } /// /// 保存发货失败订单到Redis自动重试队列 /// /// 发货通知请求 /// 商户配置 /// 错误码 /// 错误信息 private async Task SaveFailedShippingOrderAsync( OrderShippingNotifyRequest request, WechatPayMerchantConfig merchantConfig, int errCode, string errMsg) { try { var itemDesc = GetShippingItemDesc(request); var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var failedOrder = new FailedShippingOrderData { OpenId = request.OpenId, AppId = merchantConfig.AppId, OrderNo = request.OrderNo, MchId = merchantConfig.MchId, ItemDesc = itemDesc, ErrorCode = errCode, ErrorMsg = errMsg, RetryCount = 0, LastRetryTime = now, CreateTime = now }; var redisKey = $"{FAILED_SHIPPING_KEY_PREFIX}{request.OrderNo}"; var json = JsonSerializer.Serialize(failedOrder); await _redisService.SetStringAsync(redisKey, json, FAILED_SHIPPING_EXPIRY); _logger.LogInformation("已保存发货失败订单到重试队列: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}", request.OrderNo, errCode, errMsg); } catch (Exception ex) { _logger.LogError(ex, "保存发货失败订单到Redis异常: OrderNo={OrderNo}", request.OrderNo); } } /// public WechatPayMerchantConfig GetMerchantByOrderNo(string orderNo) { return _configService.GetMerchantByOrderNo(orderNo); } /// public WechatNotifyData ParseNotifyXml(string xmlData) { var notifyData = new WechatNotifyData(); if (string.IsNullOrEmpty(xmlData)) { _logger.LogWarning("回调XML数据为空"); return notifyData; } try { var doc = new XmlDocument(); doc.LoadXml(xmlData); var root = doc.DocumentElement; if (root == null) { _logger.LogWarning("回调XML根节点为空"); return notifyData; } notifyData.ReturnCode = GetXmlNodeValue(root, "return_code"); notifyData.ReturnMsg = GetXmlNodeValue(root, "return_msg"); notifyData.ResultCode = GetXmlNodeValue(root, "result_code"); notifyData.ErrCode = GetXmlNodeValue(root, "err_code"); notifyData.ErrCodeDes = GetXmlNodeValue(root, "err_code_des"); notifyData.AppId = GetXmlNodeValue(root, "appid"); notifyData.MchId = GetXmlNodeValue(root, "mch_id"); notifyData.NonceStr = GetXmlNodeValue(root, "nonce_str"); notifyData.Sign = GetXmlNodeValue(root, "sign"); notifyData.SignType = GetXmlNodeValue(root, "sign_type"); notifyData.OpenId = GetXmlNodeValue(root, "openid"); notifyData.TradeType = GetXmlNodeValue(root, "trade_type"); notifyData.BankType = GetXmlNodeValue(root, "bank_type"); notifyData.FeeType = GetXmlNodeValue(root, "fee_type"); notifyData.TransactionId = GetXmlNodeValue(root, "transaction_id"); notifyData.OutTradeNo = GetXmlNodeValue(root, "out_trade_no"); notifyData.Attach = GetXmlNodeValue(root, "attach"); notifyData.TimeEnd = GetXmlNodeValue(root, "time_end"); // 解析金额字段 var totalFeeStr = GetXmlNodeValue(root, "total_fee"); if (int.TryParse(totalFeeStr, out var totalFee)) { notifyData.TotalFee = totalFee; } var cashFeeStr = GetXmlNodeValue(root, "cash_fee"); if (int.TryParse(cashFeeStr, out var cashFee)) { notifyData.CashFee = cashFee; } _logger.LogDebug("解析回调XML成功: OutTradeNo={OutTradeNo}, TransactionId={TransactionId}", notifyData.OutTradeNo, notifyData.TransactionId); } catch (Exception ex) { _logger.LogError(ex, "解析回调XML异常"); } return notifyData; } /// public string GenerateNotifyResponseXml(string returnCode, string returnMsg) { return $""; } /// /// 获取XML节点值 /// /// XML根节点 /// 节点名称 /// 节点值,不存在则返回空字符串 private static string GetXmlNodeValue(XmlElement root, string nodeName) { var node = root.SelectSingleNode(nodeName); return node?.InnerText ?? string.Empty; } }