From eecdcf6f19689b60306406b0e5addfa26fa10040 Mon Sep 17 00:00:00 2001 From: zpc Date: Wed, 18 Mar 2026 00:15:19 +0800 Subject: [PATCH] 21 --- .../Services/WechatPayService.cs | 395 ++++++++++-------- 1 file changed, 210 insertions(+), 185 deletions(-) diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs index 2aa15dd..05b5263 100644 --- a/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs +++ b/server/MiAssessment/src/MiAssessment.Core/Services/WechatPayService.cs @@ -14,7 +14,7 @@ using Microsoft.Extensions.Options; namespace MiAssessment.Core.Services; /// -/// ΢��֧������ʵ�� +/// 微信支付服务实现 /// public class WechatPayService : IWechatPayService { @@ -29,29 +29,29 @@ public class WechatPayService : IWechatPayService private readonly Lazy? _v3ServiceLazy; /// - /// ΢��ͳһ�µ�API��ַ + /// 微信统一下单API地址 /// private const string UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /// - /// ΢�ŷ���֪ͨAPI��ַ + /// 微信发货通知API地址 /// private const string SHIPPING_NOTIFY_URL = "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info"; /// - /// ����ʧ�ܶ���Redis��ǰ׺ + /// 发货失败订单Redis键前缀 /// private const string FAILED_SHIPPING_KEY_PREFIX = "post_order:"; /// - /// ����ʧ�ܶ���Redis����ʱ�䣨3�죩 + /// 发货失败订单Redis过期时间(3天) /// private static readonly TimeSpan FAILED_SHIPPING_EXPIRY = TimeSpan.FromDays(3); /// - /// �����û�֧�����֣� + /// 测试用户支付金额(分) /// - private const int TEST_USER_PAY_AMOUNT = 1; // 0.01Ԫ = 1�� + private const int TEST_USER_PAY_AMOUNT = 1; // 0.01元 = 1分 public WechatPayService( MiAssessmentDbContext dbContext, @@ -80,58 +80,58 @@ public class WechatPayService : IWechatPayService { try { - _logger.LogInformation("��ʼ����΢��֧������: OrderNo={OrderNo}, UserId={UserId}, Amount={Amount}", + _logger.LogInformation("开始创建微信支付订单: OrderNo={OrderNo}, UserId={UserId}, Amount={Amount}", request.OrderNo, request.UserId, request.Amount); - // 1. ���ݶ����Ż�ȡ�̻����ã����֧���汾 + // 1. 根据订单号获取商户配置,判断支付版本 var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo); - // 2. �汾·�ɣ��������Ϊ V3 �� V3 ������ã���ʹ�� V3 ���� + // 2. 版本路由:如果配置为 V3 且 V3 服务可用,则使用 V3 流程 if (merchantConfig.PayVersion == "V3" && _v3ServiceLazy != null) { - _logger.LogInformation("�̻�����Ϊ V3 �汾��·�ɵ� V3 ����: MchId={MchId}", merchantConfig.MchId); + _logger.LogInformation("商户配置为 V3 版本,路由到 V3 流程: MchId={MchId}", merchantConfig.MchId); return await _v3ServiceLazy.Value.CreateJsapiOrderAsync(request); } - // 3. ʹ�� V2 ���� - _logger.LogDebug("ʹ�� V2 ֧������: MchId={MchId}, PayVersion={PayVersion}", + // 3. 使用 V2 流程 + _logger.LogDebug("使用 V2 支付流程: MchId={MchId}, PayVersion={PayVersion}", merchantConfig.MchId, merchantConfig.PayVersion); - // 4. ��ȡ�û���Ϣ��OpenId + // 4. 获取用户信息和OpenId var user = await _dbContext.Users.FirstOrDefaultAsync(u => u.Id == request.UserId); if (user == null) { - _logger.LogWarning("�û�������: UserId={UserId}", request.UserId); + _logger.LogWarning("用户不存在: UserId={UserId}", request.UserId); return new WechatPayResult { Status = 0, - Msg = "�û�������" + Msg = "用户不存在" }; } var openId = string.IsNullOrEmpty(request.OpenId) ? user.OpenId : request.OpenId; if (string.IsNullOrEmpty(openId)) { - _logger.LogWarning("�û�OpenIdΪ��: UserId={UserId}", request.UserId); + _logger.LogWarning("用户OpenId为空: UserId={UserId}", request.UserId); return new WechatPayResult { Status = 0, - Msg = "�û�OpenId������" + Msg = "用户OpenId不存在" }; } - // 5. ʹ���ѻ�ȡ���̻����� + // 5. 使用已获取的商户配置 var appId = merchantConfig.AppId; var mchId = merchantConfig.MchId; var merchantKey = merchantConfig.Key; - _logger.LogDebug("ʹ���̻�����: MchId={MchId}, AppId={AppId}", mchId, appId); + _logger.LogDebug("使用商户配置: MchId={MchId}, AppId={AppId}", mchId, appId); - // 3. ��������ַ��� + // 6. 生成随机字符串 var nonceStr = GenerateNonceStr(); var callbackNonceStr = GenerateNonceStr(); - // 4. ���ɻص�֪ͨURL + // 7. 生成回调通知URL var notifyUrl = GenerateNotifyUrl(request.Attach, request.UserId, request.OrderNo, callbackNonceStr); // 验证回调通知URL @@ -141,17 +141,17 @@ public class WechatPayService : IWechatPayService return new WechatPayResult { Status = 0, Msg = "支付回调URL未配置,请联系管理员在后台配置" }; } - // 5. ����֪ͨ��¼��order_notify�� + // 8. 保存通知记录(order_notify) await SaveOrderNotifyAsync(request.OrderNo, notifyUrl, callbackNonceStr, request.Amount, request.Attach, openId); - // 6. ����ͳһ�µ����� + // 9. 构建统一下单参数 var body = TruncateBody(request.Body, 30); - var totalFee = (int)Math.Round(request.Amount * 100); // ת��Ϊ�� + var totalFee = (int)Math.Round(request.Amount * 100); // 转换为分 - // ���Ի����£�IsTest=2 ���û�֧������Ϊ 0.01 Ԫ + // 测试环境下,IsTest=2 的用户支付金额为 0.01 元 if (_appSettings.IsTestEnvironment && user.IsTest == 2) { - _logger.LogInformation("�����û�֧��������: UserId={UserId}, ԭ���={OriginalAmount}��, ����Ϊ={TestAmount}��", + _logger.LogInformation("测试用户支付金额调整: UserId={UserId}, 原金额={OriginalAmount}分, 调整为={TestAmount}分", request.UserId, totalFee, TEST_USER_PAY_AMOUNT); totalFee = TEST_USER_PAY_AMOUNT; } @@ -171,64 +171,64 @@ public class WechatPayService : IWechatPayService { "openid", openId } }; - // 7. ����ǩ�� + // 10. 生成签名 unifiedOrderParams["sign"] = MakeSign(unifiedOrderParams, merchantKey); - // 8. ת��ΪXML������΢��API + // 11. 转换为XML并调用微信API var requestXml = DictionaryToXml(unifiedOrderParams); - _logger.LogDebug("ͳһ�µ�����XML: {Xml}", requestXml); + _logger.LogDebug("统一下单请求XML: {Xml}", requestXml); var responseXml = await PostXmlAsync(UNIFIED_ORDER_URL, requestXml); - _logger.LogDebug("ͳһ�µ���ӦXML: {Xml}", responseXml); + _logger.LogDebug("统一下单响应XML: {Xml}", responseXml); - // 9. ������Ӧ + // 12. 解析响应 var responseData = XmlToDictionary(responseXml); if (responseData == null) { - _logger.LogError("����΢����Ӧʧ��"); + _logger.LogError("解析微信响应失败"); return new WechatPayResult { Status = 0, - Msg = "������ϣ����Ժ�����(������Ӧʧ��)" + Msg = "网络故障,请稍后重试(解析响应失败)" }; } - // 10. ��鷵�ؽ�� + // 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); + var returnMsg = responseData.GetValueOrDefault("return_msg", "未知错误"); + _logger.LogWarning("统一下单失败: return_code={ReturnCode}, return_msg={ReturnMsg}", returnCode, returnMsg); return new WechatPayResult { Status = 0, - Msg = $"������ϣ����Ժ�����({returnMsg})" + 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); + 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)})" + Msg = $"支付失败({GetErrorMessage(errCode, errCodeDes)})" }; } - // 11. ��ȡprepay_id + // 14. 获取prepay_id if (!responseData.TryGetValue("prepay_id", out var prepayId) || string.IsNullOrEmpty(prepayId)) { - _logger.LogError("ͳһ�µ��ɹ���prepay_idΪ��"); + _logger.LogError("统一下单成功但prepay_id为空"); return new WechatPayResult { Status = 0, - Msg = "������ϣ����Ժ�����(prepay_idΪ��)" + Msg = "网络故障,请稍后重试(prepay_id为空)" }; } - // 12. �������ظ�ǰ�˵�֧������ + // 15. 构建返回给前端的支付参数 var timeStamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); var payNonceStr = GenerateNonceStr(); @@ -241,10 +241,10 @@ public class WechatPayService : IWechatPayService { "signType", "MD5" } }; - // 13. ����֧��ǩ�� + // 16. 生成支付签名 var paySign = MakeSign(payParams, merchantKey); - _logger.LogInformation("΢��֧�����������ɹ�: OrderNo={OrderNo}, PrepayId={PrepayId}", request.OrderNo, prepayId); + _logger.LogInformation("微信支付订单创建成功: OrderNo={OrderNo}, PrepayId={PrepayId}", request.OrderNo, prepayId); return new WechatPayResult { @@ -264,17 +264,17 @@ public class WechatPayService : IWechatPayService } catch (Exception ex) { - _logger.LogError(ex, "����΢��֧�������쳣: OrderNo={OrderNo}", request.OrderNo); + _logger.LogError(ex, "创建微信支付订单异常: OrderNo={OrderNo}", request.OrderNo); return new WechatPayResult { Status = 0, - Msg = "ϵͳ�������Ժ�����" + Msg = "系统错误,请稍后重试" }; } } /// - /// ���ɻص�֪ͨURL + /// 生成回调通知URL /// private string GenerateNotifyUrl(string attach, long userId, string orderNo, string nonceStr) { @@ -288,7 +288,7 @@ public class WechatPayService : IWechatPayService } /// - /// ���涩��֪ͨ��¼ + /// 保存订单通知记录 /// private async Task SaveOrderNotifyAsync(string orderNo, string notifyUrl, string nonceStr, decimal amount, string attach, string openId) { @@ -310,20 +310,19 @@ public class WechatPayService : IWechatPayService _dbContext.OrderNotifies.Add(orderNotify); await _dbContext.SaveChangesAsync(); - _logger.LogDebug("���涩��֪ͨ��¼: OrderNo={OrderNo}, NotifyUrl={NotifyUrl}", orderNo, notifyUrl); + _logger.LogDebug("保存订单通知记录: OrderNo={OrderNo}, NotifyUrl={NotifyUrl}", orderNo, notifyUrl); } /// - /// �ض���Ʒ������΢��������󳤶ȣ� + /// 截断商品描述(微信有最大长度限制) /// private static string TruncateBody(string body, int maxLength) { if (string.IsNullOrEmpty(body)) { - return "��Ʒ����"; + return "商品购买"; } - // ʹ���ַ����������ֽ��� if (body.Length <= maxLength) { return body; @@ -333,7 +332,7 @@ public class WechatPayService : IWechatPayService } /// - /// ����32λ����ַ��� + /// 生成32位随机字符串 /// private static string GenerateNonceStr(int length = 32) { @@ -350,17 +349,17 @@ public class WechatPayService : IWechatPayService } /// - /// ��ȡ�ͻ���IP + /// 获取客户端IP /// private static string GetClientIp() { - // ��ʵ�ʻ�����Ӧ�ô�HttpContext��ȡ - // ���ﷵ��Ĭ��ֵ��ʵ��ʹ��ʱ��Ҫͨ������ע���ȡIHttpContextAccessor + // 在实际环境中应该从HttpContext获取 + // 这里返回默认值,实际使用时需要通过依赖注入获取IHttpContextAccessor return "127.0.0.1"; } /// - /// ���ֵ�ת��ΪXML + /// 将字典转换为XML /// private static string DictionaryToXml(Dictionary parameters) { @@ -374,7 +373,7 @@ public class WechatPayService : IWechatPayService continue; } - // �������Ͳ���ҪCDATA + // 数字类型不需要CDATA if (int.TryParse(kvp.Value, out _) || decimal.TryParse(kvp.Value, out _)) { sb.Append($"<{kvp.Key}>{kvp.Value}"); @@ -390,7 +389,7 @@ public class WechatPayService : IWechatPayService } /// - /// ��XMLת��Ϊ�ֵ� + /// 将XML转换为字典 /// private static Dictionary? XmlToDictionary(string xml) { @@ -428,7 +427,7 @@ public class WechatPayService : IWechatPayService } /// - /// ����XML POST���� + /// 发送XML POST请求 /// private async Task PostXmlAsync(string url, string xml, int timeout = 30) { @@ -444,34 +443,34 @@ public class WechatPayService : IWechatPayService } catch (Exception ex) { - _logger.LogError(ex, "����XML����ʧ��: Url={Url}", url); + _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", "δʹ��ָ�������ʽ" } + { "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)) @@ -487,7 +486,7 @@ public class WechatPayService : IWechatPayService { if (string.IsNullOrEmpty(sign)) { - _logger.LogWarning("ǩ����֤ʧ�ܣ�ǩ��Ϊ��"); + _logger.LogWarning("签名验证失败:签名为空"); return false; } @@ -496,7 +495,7 @@ public class WechatPayService : IWechatPayService if (!isValid) { - _logger.LogWarning("ǩ����֤ʧ�ܣ�����ǩ��={CalculatedSign}������ǩ��={Sign}", calculatedSign, sign); + _logger.LogWarning("签名验证失败:计算签名={CalculatedSign},传入签名={Sign}", calculatedSign, sign); } return isValid; @@ -505,35 +504,35 @@ public class WechatPayService : IWechatPayService /// public string MakeSign(Dictionary parameters, string? merchantKey = null) { - // ��ȡ�̻���Կ + // 获取商户密钥 var key = merchantKey ?? _settings.DefaultMerchant.Key; - // ǩ������һ�����ֵ����������������ASCII���С���� - // ���˵���ֵ��sign�ֶ� + // 签名步骤一:将字典按参数名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 + // 签名步骤二:将参数拼接为URL格式 key=value&key=value var urlParams = ToUrlParams(sortedParams); - // ǩ������������string�����KEY + // 签名步骤三:在string后面加上KEY var signString = $"{urlParams}&key={key}"; - // ǩ�������ģ�MD5���� + // 签名步骤四: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格式: key=value&key=value /// - /// ������IJ����б� - /// URL��ʽ�ַ��� + /// 排序后的参数列表 + /// URL格式字符串 private static string ToUrlParams(IEnumerable> parameters) { var parts = parameters.Select(p => $"{p.Key}={p.Value}"); @@ -541,10 +540,10 @@ public class WechatPayService : IWechatPayService } /// - /// ���ݶ����Ż�ȡ�̻���Կ + /// 根据订单号获取商户密钥 /// - /// ������ - /// �̻���Կ + /// 订单号 + /// 商户密钥 public string GetMerchantKeyByOrderNo(string orderNo) { var merchant = GetMerchantByOrderNo(orderNo); @@ -552,13 +551,13 @@ public class WechatPayService : IWechatPayService } /// - /// ��֤΢�Żص�ǩ�� + /// 验证微信回调签名 /// - /// �ص����� - /// �Ƿ���֤ͨ�� + /// 回调数据 + /// 是否验证通过 public bool VerifyNotifySign(WechatNotifyData notifyData) { - // �ӻص����ݹ��������ֵ� + // 从回调数据构建参数字典 var parameters = new Dictionary { { "return_code", notifyData.ReturnCode }, @@ -579,7 +578,7 @@ public class WechatPayService : IWechatPayService { "time_end", notifyData.TimeEnd } }; - // ���ӿ�ѡ�ֶ� + // 添加可选字段 if (!string.IsNullOrEmpty(notifyData.ErrCode)) parameters["err_code"] = notifyData.ErrCode; if (!string.IsNullOrEmpty(notifyData.ErrCodeDes)) @@ -587,34 +586,34 @@ public class WechatPayService : IWechatPayService 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); + // 返回默认商户密钥 + _logger.LogWarning("未找到商户号 {MchId} 的配置,使用默认商户密钥", mchId); return _settings.DefaultMerchant.Key; } @@ -623,49 +622,49 @@ public class WechatPayService : IWechatPayService { try { - _logger.LogInformation("��ʼ���Ͷ�������֪ͨ: OrderNo={OrderNo}, OpenId={OpenId}", + _logger.LogInformation("开始发送订单发货通知: OrderNo={OrderNo}, OpenId={OpenId}", request.OrderNo, request.OpenId); - // 1. ���ݶ����Ż�ȡ�̻����� + // 1. 根据订单号获取商户配置 var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo); var mchId = merchantConfig.MchId; var appId = merchantConfig.AppId; - _logger.LogDebug("ʹ���̻�����: MchId={MchId}, AppId={AppId}", mchId, appId); + _logger.LogDebug("使用商户配置: MchId={MchId}, AppId={AppId}", mchId, appId); - // 2. ��ȡaccess_token + // 2. 获取access_token var accessToken = await _wechatService.GetAccessTokenAsync(appId); if (string.IsNullOrEmpty(accessToken)) { - _logger.LogError("��ȡaccess_tokenʧ��: AppId={AppId}", appId); + _logger.LogError("获取access_token失败: AppId={AppId}", appId); - // �������Զ��� - await SaveFailedShippingOrderAsync(request, merchantConfig, -1, "��ȡaccess_tokenʧ��"); + // 保存到自动重试队列 + await SaveFailedShippingOrderAsync(request, merchantConfig, -1, "获取access_token失败"); return new OrderShippingNotifyResult { Success = false, ErrCode = -1, - ErrMsg = "��ȡaccess_tokenʧ��", + ErrMsg = "获取access_token失败", QueuedForRetry = true }; } - // 3. ��������֪ͨ��Ϣ + // 3. 构建发货通知消息 var itemDesc = GetShippingItemDesc(request); - // 4. ����������� + // 4. 构建请求参数 var uploadTime = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss") + "+08:00"; var requestBody = new { order_key = new { - order_number_type = 1, // ʹ���̻������� + order_number_type = 1, // 使用商户订单号 mchid = mchId, out_trade_no = request.OrderNo }, - logistics_type = request.LogisticsType, // �������ͣ�4=������Ʒ - delivery_mode = request.DeliveryMode, // ����ģʽ��1=ͳһ���� + logistics_type = request.LogisticsType, // 物流类型,4=虚拟商品 + delivery_mode = request.DeliveryMode, // 发货模式,1=统一发货 shipping_list = new[] { new @@ -682,32 +681,32 @@ public class WechatPayService : IWechatPayService var requestJson = JsonSerializer.Serialize(requestBody); - // 5. ��¼������־ - _logger.LogDebug("����֪ͨ����: OrderNo={OrderNo}, MchId={MchId}, Request={Request}", + // 5. 记录请求日志 + _logger.LogDebug("发货通知请求: OrderNo={OrderNo}, MchId={MchId}, Request={Request}", request.OrderNo, mchId, requestJson); - // 6. ����΢��API + // 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}", + // 7. 记录响应日志 + _logger.LogDebug("发货通知响应: OrderNo={OrderNo}, Response={Response}", request.OrderNo, responseContent); - // 8. ������Ӧ + // 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. �жϽ�� + // 9. 判断结果 if (errCode == 0 && errMsg == "ok") { - _logger.LogInformation("����֪ͨ�ɹ�: OrderNo={OrderNo}", request.OrderNo); + _logger.LogInformation("发货通知成功: OrderNo={OrderNo}", request.OrderNo); return new OrderShippingNotifyResult { Success = true, @@ -717,10 +716,10 @@ public class WechatPayService : IWechatPayService } else { - _logger.LogWarning("����֪ͨʧ��: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}", + _logger.LogWarning("发货通知失败: OrderNo={OrderNo}, ErrCode={ErrCode}, ErrMsg={ErrMsg}", request.OrderNo, errCode, errMsg); - // �������Զ��� + // 保存到自动重试队列 await SaveFailedShippingOrderAsync(request, merchantConfig, errCode, errMsg); return new OrderShippingNotifyResult @@ -734,9 +733,9 @@ public class WechatPayService : IWechatPayService } catch (Exception ex) { - _logger.LogError(ex, "����֪ͨ�쳣: OrderNo={OrderNo}", request.OrderNo); + _logger.LogError(ex, "发货通知异常: OrderNo={OrderNo}", request.OrderNo); - // ���Դ������Զ��� + // 尝试保存到自动重试队列 try { var merchantConfig = _configService.GetMerchantByOrderNo(request.OrderNo); @@ -744,7 +743,7 @@ public class WechatPayService : IWechatPayService } catch (Exception saveEx) { - _logger.LogError(saveEx, "����ʧ�ܶ��������Զ���ʱ��������: OrderNo={OrderNo}", request.OrderNo); + _logger.LogError(saveEx, "保存失败订单到自动重试队列时发生错误: OrderNo={OrderNo}", request.OrderNo); } return new OrderShippingNotifyResult @@ -758,41 +757,45 @@ public class WechatPayService : IWechatPayService } /// - /// ��ȡ������Ʒ���� + /// 获取发货商品描述 /// private static string GetShippingItemDesc(OrderShippingNotifyRequest request) { - // ���������ָ����������ֱ��ʹ�� + // 如果请求中指定了描述,直接使用 if (!string.IsNullOrEmpty(request.ItemDesc)) { return request.ItemDesc; } - // ���ݶ���ǰ׺�ж���Ϣ���� + // 根据订单前缀判断消息内容 if (request.OrderNo.StartsWith("FH_")) { - // �������� - return "�����������Ʒ���ڴ��������ϵ�ͷ���ȡ������Ϣ"; + // 发货订单 + return "您购买的实物商品正在处理中,请联系客服获取物流信息"; } else { - // ������Ʒ����������ȣ� - return "��������IJ��������ѿ�ͨ������С�����в鿴"; + // 虚拟商品(测评服务等) + return "您购买的测评服务已开通,请在小程序中查看"; } } /// - /// ���淢��ʧ�ܵĶ�����Redis���Զ��� + /// 保存发货失败订单到Redis自动重试队列 /// + /// 发货通知请求 + /// 商户配置 + /// 错误码 + /// 错误信息 private async Task SaveFailedShippingOrderAsync( - OrderShippingNotifyRequest request, + OrderShippingNotifyRequest request, WechatPayMerchantConfig merchantConfig, - int errorCode, - string errorMsg) + int errCode, + string errMsg) { try { - var key = $"{FAILED_SHIPPING_KEY_PREFIX}{request.OrderNo}"; + var itemDesc = GetShippingItemDesc(request); var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var failedOrder = new FailedShippingOrderData @@ -801,38 +804,44 @@ public class WechatPayService : IWechatPayService AppId = merchantConfig.AppId, OrderNo = request.OrderNo, MchId = merchantConfig.MchId, - ItemDesc = GetShippingItemDesc(request), - ErrorCode = errorCode, - ErrorMsg = errorMsg, + 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(key, json, FAILED_SHIPPING_EXPIRY); - _logger.LogInformation("�ѽ�����ʧ�ܶ����������Զ���: OrderNo={OrderNo}, Key={Key}", - request.OrderNo, key); + await _redisService.SetAsync(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); - throw; + _logger.LogError(ex, "保存发货失败订单到Redis异常: OrderNo={OrderNo}", request.OrderNo); } } /// public WechatPayMerchantConfig GetMerchantByOrderNo(string orderNo) { - // ʹ�����÷����ȡ�̻����� return _configService.GetMerchantByOrderNo(orderNo); } /// public WechatNotifyData ParseNotifyXml(string xmlData) { - var result = new WechatNotifyData(); + var notifyData = new WechatNotifyData(); + + if (string.IsNullOrEmpty(xmlData)) + { + _logger.LogWarning("回调XML数据为空"); + return notifyData; + } try { @@ -840,43 +849,53 @@ public class WechatPayService : IWechatPayService doc.LoadXml(xmlData); var root = doc.DocumentElement; - if (root == null) return result; - - result.ReturnCode = GetXmlNodeValue(root, "return_code"); - result.ReturnMsg = GetXmlNodeValue(root, "return_msg"); - result.ResultCode = GetXmlNodeValue(root, "result_code"); - result.ErrCode = GetXmlNodeValue(root, "err_code"); - result.ErrCodeDes = GetXmlNodeValue(root, "err_code_des"); - result.AppId = GetXmlNodeValue(root, "appid"); - result.MchId = GetXmlNodeValue(root, "mch_id"); - result.NonceStr = GetXmlNodeValue(root, "nonce_str"); - result.Sign = GetXmlNodeValue(root, "sign"); - result.SignType = GetXmlNodeValue(root, "sign_type"); - result.OpenId = GetXmlNodeValue(root, "openid"); - result.TradeType = GetXmlNodeValue(root, "trade_type"); - result.BankType = GetXmlNodeValue(root, "bank_type"); - result.FeeType = GetXmlNodeValue(root, "fee_type"); - result.TransactionId = GetXmlNodeValue(root, "transaction_id"); - result.OutTradeNo = GetXmlNodeValue(root, "out_trade_no"); - result.Attach = GetXmlNodeValue(root, "attach"); - result.TimeEnd = GetXmlNodeValue(root, "time_end"); - - // ��������λ���֣� - if (int.TryParse(GetXmlNodeValue(root, "total_fee"), out var totalFee)) + if (root == null) { - result.TotalFee = totalFee; + _logger.LogWarning("回调XML根节点为空"); + return notifyData; } - if (int.TryParse(GetXmlNodeValue(root, "cash_fee"), out var cashFee)) + + 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)) { - result.CashFee = cashFee; + 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ʧ��: {XmlData}", xmlData); + _logger.LogError(ex, "解析回调XML异常"); } - return result; + return notifyData; } /// @@ -885,6 +904,12 @@ public class WechatPayService : IWechatPayService return $""; } + /// + /// 获取XML节点值 + /// + /// XML根节点 + /// 节点名称 + /// 节点值,不存在则返回空字符串 private static string GetXmlNodeValue(XmlElement root, string nodeName) { var node = root.SelectSingleNode(nodeName);