微信支付:JSAPI或APP拉起支付,return_msg=签名错误

本文详细解析微信支付接口中常见的签名错误问题,阐述了appsecret与支付总账户api密钥的区别,以及如何正确使用api密钥进行签名,避免下单接口返回签名错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 发送给微信的参数xml, 下图中的商户key就是微信后台设置的appsecret

2. 复制到 微信支付接口签名校验工具,  本文适用如下图是检测通过的

如果跟我一样, 检测通过了, 但下单接口仍返回 "签名错误", 唯一的问题就是:

签名方法传的key用的是appsecret, 而正确的应该用支付总账户的api密钥,

// 从数据库获取微信支付配置(换成自己的配置来源即可)
        WxPayConfig wxPayConfig1 = wxPayConfigMapper.selectById(wxId);
// payKey就是支付总账户的api密钥
        String payKey = wxPayConfig1.getApiKey();
        
        SortedMap<Object, Object> paramMap = new TreeMap<Object, Object>();
        paramMap.put("appid", wxPayConfig.getAppId());//公众号ID
        paramMap.put("mch_id", wxPayConfig.getMchId());//商户号
        paramMap.put("nonce_str", RandomUtil.createRandomString(32));//32位随机字符串
        paramMap.put("body", body);//商品描述
        paramMap.put("out_trade_no", out_trade_no);//商户订单号
        paramMap.put("total_fee", StringUtils.substringBefore(String.valueOf(total_fee.multiply(new BigDecimal(100))), "."));//设置交易金额 金额为分
        paramMap.put("spbill_create_ip", ipInfoUtil.getIpAddr(request));// 发起请求的客户机IP
        paramMap.put("notify_url", notifyUrl);//通知地址
        paramMap.put("sign_type", "MD5");//通知地址
        paramMap.put("trade_type", "JSAPI");//支付方式 小程序支付
        paramMap.put("openid", openid);//微信用户在商户对应appid下的唯一标识,trade_type=JSAPI时(即JSAPI支付),此参数必传
        paramMap.put("sign", SignUtil.createSign(paramMap, payKey));// 签名

图下的appsecret是校验工具用来验证签名的,而apikey是下单接口签名用的

apikey是对应微信支付平台的,而微信支付平台可以绑定小程序和app,小程序或app调用统一下单时,参数sign,创建签名时传入的apikey就是它,千万不要传appsecret,我就是因为这个原因导致签名错误。

 

using CY_PUBLIC_LIBRARY; using CY_PUBLIC_LIBRARY.AppConfigurtaion; using CY_PUBLIC_LIBRARY.BaseModels; using CY_PUBLIC_LIBRARY.Extension; using CY_PUBLIC_LIBRARY.Middleware; using CY_PUBLIC_LIBRARY.WebRequestHelp; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Xml; using System.Drawing.Imaging; using DoMain; using Org.BouncyCastle.Asn1.X509; using System.Diagnostics; using Newtonsoft.Json; using Model.cxbd; using Model.sys_model; using Model.hospital_model; namespace APP_API.Controllers.wechatControllers { /// <summary> /// 微信支付 /// </summary> [Route("api/[controller]")] [ApiController] [Authorize] [ApiExplorerSettings(GroupName = "base_data")] public class wechat_payController : BaseApiController { public wechat_payController(IFreeSql fsql) : base(fsql) { } /// <summary> /// 微信支付下单 /// </summary> /// <param name="payItem"></param> /// <returns></returns> [HttpPost("pay")] [Authorize] public ActionResult<retObject<payRetModel>> Pay([FromBody] payOrderModel payItem) { var app_user = _fsql.Select<app_user>(_jwt.user_info.user_id).ToOne(); var order = _fsql.Select<sys_order>(payItem.order_id).ToOne(); if (order == null) { throw new ServiceException("订单不存在"); } if (order.status != (int)wechat_refund_status.待付款) { throw new ServiceException("订单状态有误"); } order.guid = Guid.NewGuid().ToString("N"); //order.status = (int)wechat_refund_status.待接单; _fsql.Update<sys_order>(order.order_id).Set(x=>x.guid,order.guid).ExecuteAffrows(); var domain = new orderDomain(_fsql); var payfee = Convert.ToInt32(payItem.pay * 100); var order_pay = new order_pay() { amount = payItem.pay, rl = false, pay_type_id = 2, is_app = true, order_id = payItem.order_id }; var pay_id = _fsql.Insert(order_pay).ExecuteIdentity(); var name = app_user.name; if (name.Length > 40) { name = name.Substring(0, 40) + "..."; } name = name.Replace("<", "").Replace(">", ""); var nonStr = CommandHelp.GetGuid(); var PayNotifyUrl = AppConfigurtaionServices.Configuration["Wechat:PayNotifyUrl"]; var appid = AppConfigurtaionServices.Configuration["Wechat:AppId"]; var mch_id = AppConfigurtaionServices.Configuration["Wechat:MchId"]; var ipStr = AppConfigurtaionServices.Configuration["Wechat:Ip"]; var key = AppConfigurtaionServices.Configuration["Wechat:Key"]; var postData = "appid=" + appid + "&attach=" + pay_id + "&body=" + name + "&mch_id=" + mch_id + "&nonce_str=" + nonStr + "&notify_url=" + PayNotifyUrl + "&openid=" + app_user.open_id + "&out_trade_no=" + pay_id + "&spbill_create_ip=" + ipStr + "&total_fee=" + payfee + "&trade_type=JSAPI"; postData += "&key=" + key; CyLogHelp.WriteLog(postData, "微信下单"); var md5 = MD5.Create(); var bs2 = md5.ComputeHash(Encoding.UTF8.GetBytes(postData)); var sb2 = new StringBuilder(); foreach (byte bb in bs2) { sb2.Append(bb.ToString("x2")); } postData = sb2.ToString().ToUpper(); var orderXml = "<xml>" + "<appid>" + appid + "</appid>" + "<attach>" + pay_id + "</attach>" + "<body>" + name + "</body>" + "<mch_id>" + mch_id + "</mch_id>" + "<nonce_str>" + nonStr + "</nonce_str>" + "<notify_url>" + PayNotifyUrl + "</notify_url>" + "<openid>" + app_user.open_id + "</openid>" + "<out_trade_no>" + pay_id + "</out_trade_no>" + "<spbill_create_ip>" + ipStr + "</spbill_create_ip>" + "<total_fee>" + payfee + "</total_fee>" + "<trade_type>JSAPI</trade_type>" + "<sign>" + postData + "</sign>" + "</xml>"; var retStr = WebRequsetHelp.PostRequestData("https://api.mch.weixin.qq.com/pay/unifiedorder", orderXml, WebRequsetContentType.Form表单); if (retStr != "" && retStr.IndexOf("prepay_id") > 0) { XmlDocument doc = new XmlDocument(); doc.LoadXml(retStr); XmlNodeList list = doc.GetElementsByTagName("xml"); XmlNode xn = list[0]; var prepay_id = xn.SelectSingleNode("//prepay_id").InnerText; if (!string.IsNullOrEmpty(prepay_id)) { var timeSpanStr = DateTime.Now.ConvertShortDateTimeInt().ToString(); var postStr = "appId=" + appid + "&nonceStr=" + nonStr + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeSpanStr; postStr += "&key=" + key; var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(postStr)); var sb = new StringBuilder(); foreach (byte bb in bs) { sb.Append(bb.ToString("x2")); } var signStr = sb.ToString().ToUpper(); var ret = new payRetModel() { appId = appid, nonceStr = nonStr, package = "prepay_id=" + prepay_id, paySign = signStr, signType = "MD5", timeStamp = timeSpanStr }; return Ok(ret.GetRetObject()); } else { CyLogHelp.WriteLog(retStr + "\r\n" + orderXml, "微信下单失败"); throw new ServiceException("微信下单失败", 201); } } else { CyLogHelp.WriteLog(retStr + "\r\n" + orderXml, "微信下单失败"); throw new ServiceException("微信下单失败", 201); } } /// <summary> /// 微信支付回调 /// </summary> /// <returns></returns> [HttpPost("gspayback")] public ActionResult<string> PayNotify() { var successStr = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; var failStr = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数错误]]></return_msg></xml>"; var postStr = ""; Request.Body.Seek(0,SeekOrigin.Begin); using (var reader = new StreamReader(Request.Body, Encoding.UTF8)) { postStr = reader.ReadToEndAsync().Result; } _fsql.Insert(new wechat_request() { content = postStr }).ExecuteAffrows(); if (!string.IsNullOrEmpty(postStr)) { XmlDocument doc = new XmlDocument(); doc.XmlResolver = null; doc.LoadXml(postStr); XmlNodeList list = doc.GetElementsByTagName("xml"); XmlNode xn = list[0]; string ret = xn.SelectSingleNode("//result_code").InnerText; string attach = xn.SelectSingleNode("//attach").InnerText; string mch_id = xn.SelectSingleNode("//mch_id").InnerText; string out_trade_no = xn.SelectSingleNode("//out_trade_no").InnerText; string transaction_id = xn.SelectSingleNode("//transaction_id").InnerText; string totalFee = xn.SelectSingleNode("//total_fee").InnerText; string openid = xn.SelectSingleNode("//openid").InnerText; decimal price = 0; if (decimal.TryParse(totalFee, out price)) { price = price / 100; } if (_fsql.Select<wechat_pay_log>().Where(x => x.transaction_id == transaction_id && x.result_code == "SUCCESS").Count() > 0) return Ok(successStr); //再去判断是否收到订单 var nonStr = CommandHelp.GetGuid(); var appid = AppConfigurtaionServices.Configuration["Wechat:AppId"]; var mymch_id = AppConfigurtaionServices.Configuration["Wechat:MchId"]; var key = AppConfigurtaionServices.Configuration["Wechat:Key"]; var postData = "appid=" + appid + "&mch_id=" + mymch_id + "&nonce_str=" + nonStr + "&out_trade_no=" + out_trade_no; postData += "&key=" + key; var md5 = MD5.Create(); var bs2 = md5.ComputeHash(Encoding.UTF8.GetBytes(postData)); var sb2 = new StringBuilder(); foreach (byte bb in bs2) { sb2.Append(bb.ToString("x2")); } postData = sb2.ToString().ToUpper(); var orderXml = "<xml>" + "<appid>" + appid + "</appid>" + "<mch_id>" + mymch_id + "</mch_id>" + "<nonce_str>" + nonStr + "</nonce_str>" + "<out_trade_no>" + out_trade_no + "</out_trade_no>" + "<sign>" + postData + "</sign>" + "</xml>"; var retStr = WebRequsetHelp.PostRequestData("https://api.mch.weixin.qq.com/pay/orderquery", orderXml, WebRequsetContentType.Form表单); if (!retStr.Contains("支付成功")) { CyLogHelp.WriteLog(retStr + "\r\n" + orderXml, "微信支付回调求失败"); throw new ServiceException("订单未支付", 201); }; _fsql.Transaction(() => { var order = _fsql.Select<sys_order>().Where(e => e.guid == out_trade_no).ToOne(); //添加支付记录 var _paylog = new wechat_pay_log() { openid = openid, order_id = order.order_id, out_trade_no = out_trade_no, postStr = postStr, result_code = ret, total_fee = Convert.ToDecimal(totalFee), transaction_id = transaction_id, }; if (_fsql.Insert(_paylog).ExecuteAffrows() <= 0) throw new ServiceException("添加支付记录失败", 201); if (_paylog.result_code == "SUCCESS") { //支付逻辑 var domain = new orderDomain(_fsql); domain.order_pay(order.order_id); } }); return Ok(successStr); } return Ok(failStr); } /// <summary> /// 微信退款回调 /// </summary> /// <returns></returns> [HttpPost("refundnotify")] public ActionResult<string> RefundNotify() { string WechatDecrypt(string decryptStr, string key) { byte[] keyArray = Encoding.UTF8.GetBytes(key); byte[] toEncryptArray = Convert.FromBase64String(decryptStr); RijndaelManaged rDel = new RijndaelManaged(); rDel.Key = keyArray; rDel.Mode = CipherMode.ECB; rDel.Padding = PaddingMode.PKCS7; ICryptoTransform cTransform = rDel.CreateDecryptor(); byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length); return Encoding.UTF8.GetString(resultArray); } var successStr = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; var failStr = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数错误]]></return_msg></xml>"; var postStr = ""; Request.Body.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(Request.Body, Encoding.UTF8)) { postStr = reader.ReadToEndAsync().Result; } _fsql.Insert(new wechat_request_refund() { content = postStr }).ExecuteAffrows(); if (string.IsNullOrEmpty(postStr)) return Ok(failStr); try { XmlDocument doc = new XmlDocument(); doc.XmlResolver = null; doc.LoadXml(postStr); XmlNodeList list = doc.GetElementsByTagName("xml"); XmlNode xn = list[0]; var retcode = xn.SelectSingleNode("//return_code").InnerText; var req_info = xn.SelectSingleNode("//req_info").InnerText; if (!string.IsNullOrEmpty(req_info)) { //解密 var key = AppConfigurtaionServices.Configuration["Wechat:Key"]; var md5 = MD5.Create(); var bs2 = md5.ComputeHash(Encoding.UTF8.GetBytes(key)); var sb2 = new StringBuilder(); foreach (byte bb in bs2) { sb2.Append(bb.ToString("x2")); } var temp_key = sb2.ToString().ToLower(); var refund_info = WechatDecrypt(req_info, temp_key); if (!string.IsNullOrEmpty(refund_info)) { doc.LoadXml(refund_info); if (doc != null) { list = doc.GetElementsByTagName("root"); xn = list[0]; string transaction_id = xn.SelectSingleNode("//transaction_id").InnerText; string out_trade_no = xn.SelectSingleNode("//out_trade_no").InnerText; string refund_id = xn.SelectSingleNode("//refund_id").InnerText; string out_refund_no = xn.SelectSingleNode("//out_refund_no").InnerText; string refund_fee = xn.SelectSingleNode("//refund_fee").InnerText; string refund_status = xn.SelectSingleNode("//refund_status").InnerText; string totalFee = xn.SelectSingleNode("//total_fee").InnerText; decimal price = 0; if (decimal.TryParse(refund_fee, out price)) { price = price / 100; } if (_fsql.Select<wechat_refund_log>().Where(x => x.refund_id == refund_id && x.refund_status == "SUCCESS").Count() > 0) return Ok(successStr); _fsql.Transaction(() => { var _refundlog = new wechat_refund_log() { out_refund_no = out_refund_no, out_trade_no = out_trade_no, postStr = postStr, refund_fee = Convert.ToDecimal(price), refund_status = refund_status, refund_id = refund_id, transaction_id = transaction_id }; var _r = _fsql.Insert(_refundlog).ExecuteAffrows(); if (_r <= 0) throw new ServiceException("添加退款日志失败", 201); if (_refundlog.refund_status == "SUCCESS") { var _order = _fsql.Select<sys_order>().Where(x => x.guid == _refundlog.out_trade_no).ToOne(); if (_order == null) { throw new ServiceException("订单不存在", 201); } //找到退款记录 var refund_id = Convert.ToInt32(out_refund_no); _fsql.Update<sys_order>(refund_id).Set(e => e.status, (int)DoMain.Enum.wechat_refund_status.已取消).ExecuteAffrows(); } }); return Ok(successStr); } } } } catch { } return Ok(failStr); } } public class payRetModel { public string appId { get; set; } public string timeStamp { get; set; } public string nonceStr { get; set; } public string package { get; set; } public string signType { get; set; } public string paySign { get; set; } } public class payOrderModel { /// <summary> /// 订单ID/购物车订单id /// </summary> [Required] public int order_id { get; set; } = 0; /// <summary> /// 支付金额 /// </summary> [Required] public decimal pay { get; set; } [Required] public bool from_cart { get; set; } } } 我这个为啥状态不修改而且支付也报Object reference not set to an instance of an object.(在小程序上支付)怎么解决
最新发布
08-17
<think>我们正在处理微信支付回调时出现的两个问题:订单状态未更新和空引用异常(Object reference not set to an instance of an object)。根据提供的引用内容,我们有一个支付服务的装饰器模式实现,但它是Java的,而我们需要C# ASP.NET Core的解决方案。不过,我们可以借鉴其设计思想。 问题分析: 1. 订单状态未更新:可能由于回调处理逻辑中的某些错误导致更新操作未执行。 2. 空引用异常:在回调处理代码中尝试访问了未初始化的对象(null)的成员。 解决方案步骤: 1. **检查回调处理逻辑**: 确保回调处理代码能够正确解析微信支付回调的求数据,并验证签名。然后根据回调数据更新订单状态。 2. **避免空引用异常**: - 在访问对象成员之前,检查对象是否为null。 - 使用安全导航操作符(?.)条件判断。 - 确保依赖注入的服务实例被正确注入。 3. **事务处理**: 更新订单状态通常涉及数据库操作,确保使用事务以保证数据一致性。 4. **日志记录**: 在关键步骤添加日志,以便于追踪问题。 5. **重试机制**: 微信支付回调可能会多次调用,确保处理逻辑的幂等性。 代码结构示例(C#): 我们可以参考装饰器模式来增强支付回调处理,例如添加参数校验、发送通知等,但首先解决核心问题。 以下是一个简单的回调处理控制器示例: ```csharp [ApiController] [Route("api/[controller]")] public class WechatPayCallbackController : ControllerBase { private readonly IOrderService _orderService; private readonly ILogger<WechatPayCallbackController> _logger; public WechatPayCallbackController(IOrderService orderService, ILogger<WechatPayCallbackController> logger) { _orderService = orderService; _logger = logger; } [HttpPost] public async Task<IActionResult> PostCallback() { try { // 读取求体 using (var reader = new StreamReader(Request.Body)) { var body = await reader.ReadToEndAsync(); _logger.LogInformation("Received wechat pay callback: {body}", body); // 解析XML数据 var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(body); var root = xmlDoc.DocumentElement; // 获取关键字段 string returnCode = root?.SelectSingleNode("return_code")?.InnerText; string outTradeNo = root?.SelectSingleNode("out_trade_no")?.InnerText; string transactionId = root?.SelectSingleNode("transaction_id")?.InnerText; // 验证返回码 if (returnCode != "SUCCESS") { _logger.LogError($"Wechat pay callback failed: {body}"); return Ok(new { return_code = "FAIL", return_msg = "失败" }); } // 验证签名(此处省略具体实现,需根据微信支付文档实现) // 根据商户订单号获取订单 var order = await _orderService.GetOrderByOutTradeNoAsync(outTradeNo); if (order == null) { _logger.LogError($"Order not found: {outTradeNo}"); return Ok(new { return_code = "FAIL", return_msg = "订单不存在" }); } // 检查订单状态是否已经更新过(避免重复更新) if (order.Status == OrderStatus.Paid) { _logger.LogInformation($"Order already paid: {outTradeNo}"); return Ok(new { return_code = "SUCCESS", return_msg = "OK" }); } // 更新订单状态 order.TransactionId = transactionId; order.Status = OrderStatus.Paid; // 其他更新... // 使用事务保存 bool success = await _orderService.UpdateOrderAsync(order); if (!success) { _logger.LogError($"Update order failed: {outTradeNo}"); return Ok(new { return_code = "FAIL", return_msg = "更新订单失败" }); } // 可以在这里触发领域事件,例如发送通知等(装饰器模式可以在这里应用,但C#中常用中间件过滤器) return Ok(new { return_code = "SUCCESS", return_msg = "OK" }); } } catch (Exception ex) { _logger.LogError(ex, "Wechat pay callback error"); return Ok(new { return_code = "FAIL", return_msg = "处理失败" }); } } } ``` 针对空引用异常,我们需要注意: - 在解析XML时,使用`?.`来避免空引用,例如:`root?.SelectSingleNode("return_code")?.InnerText`。 - 在获取订单后,检查`order`是否为null。 对于订单状态未更新,我们需要注意: - 确保`UpdateOrderAsync`方法正确执行,没有异常抛出。 - 确保数据库操作成功(事务提交)。 另外,我们可以考虑使用装饰器模式来增强回调处理,例如: ```csharp public interface IOrderProcessor { Task<bool> ProcessCallbackAsync(string outTradeNo, string transactionId); } public class OrderProcessor : IOrderProcessor { private readonly IOrderService _orderService; public OrderProcessor(IOrderService orderService) { _orderService = orderService; } public async Task<bool> ProcessCallbackAsync(string outTradeNo, string transactionId) { // 获取订单,更新状态等核心逻辑 // ... return true; } } // 装饰器:添加参数校验 public class OrderProcessorParamChecker : IOrderProcessor { private readonly IOrderProcessor _inner; private readonly ILogger<OrderProcessorParamChecker> _logger; public OrderProcessorParamChecker(IOrderProcessor inner, ILogger<OrderProcessorParamChecker> logger) { _inner = inner; _logger = logger; } public async Task<bool> ProcessCallbackAsync(string outTradeNo, string transactionId) { // 参数校验 if (string.IsNullOrEmpty(outTradeNo) || string.IsNullOrEmpty(transactionId)) { _logger.LogError("Invalid parameters"); return false; } return await _inner.ProcessCallbackAsync(outTradeNo, transactionId); } } // 装饰器:添加发送通知 public class OrderProcessorNotifier : IOrderProcessor { private readonly IOrderProcessor _inner; private readonly INotificationService _notificationService; public OrderProcessorNotifier(IOrderProcessor inner, INotificationService notificationService) { _inner = inner; _notificationService = notificationService; } public async Task<bool> ProcessCallbackAsync(string outTradeNo, string transactionId) { var result = await _inner.ProcessCallbackAsync(outTradeNo, transactionId); if (result) { // 发送通知 await _notificationService.SendPaymentSuccessNotificationAsync(outTradeNo); } return result; } } ``` 然后在控制器中注入装饰后的`IOrderProcessor`(通过依赖注入配置装饰链)。 但是,在ASP.NET Core中,我们更常用中间件过滤器来实现横切关注点,者使用装饰器模式在服务内部组合。另外,也可以使用MediatR库来实现管道行为(类似装饰器)。 总结解决方案: 1. 在回调处理中,仔细检查可能为null的对象,并添加必要的检查。 2. 确保更新订单状态的数据库操作正确执行。 3. 使用日志记录关键步骤和异常。 4. 考虑使用设计模式(如装饰器)来分离关注点,使代码更健壮。 如果问题仍然存在,检查: - 依赖注入的配置,确保服务被正确注入。 - 数据库连接和事务是否正常。 - 微信回调的XML格式是否正确,字段名是否匹配。 最后,微信支付要求在处理回调后返回特定的XML响应,否则微信会重复发送回调。因此,确保在成功和失败情况下都返回正确的响应。 以上代码示例仅供参考,根据实际项目调整。</think>### 微信支付回调中订单状态未更新与空引用异常的解决方案 在 C# ASP.NET Core 中处理微信支付回调时遇到"订单状态未更新"和"Object reference not set to an instance of an object"异常,通常由以下原因导致: #### 问题原因分析 1. **空引用异常根源** - 未正确处理微信回调的 XML/JSON 数据解析 - 依赖服务(如数据库上下文、订单服务)未正确注入 - 尝试访问未初始化的对象属性(如 `order.xxx` 当 `order` 为 null 时) 2. **订单状态未更新原因** - 事务未正确提交 - 并发冲突导致更新失败 - 异常中断了更新流程 - 未正确处理幂等性(重复回调) --- ### 解决方案(完整代码示例) ```csharp [ApiController] [Route("api/payment/wechat")] public class WechatPayCallbackController : ControllerBase { private readonly IOrderService _orderService; private readonly ILogger<WechatPayCallbackController> _logger; public WechatPayCallbackController( IOrderService orderService, ILogger<WechatPayCallbackController> logger) { _orderService = orderService; _logger = logger; } [HttpPost("notify")] public async Task<IActionResult> HandleCallback() { try { // 1. 读取并验证回调数据 using var reader = new StreamReader(Request.Body); var xmlData = await reader.ReadToEndAsync(); _logger.LogInformation($"微信回调原始数据: {xmlData}"); // 2. 解析XML数据(使用安全解析方法) var doc = new XmlDocument(); doc.LoadXml(xmlData); // 安全获取节点值(避免空引用) var outTradeNo = doc.SelectSingleNode("/xml/out_trade_no")?.InnerText; var transactionId = doc.SelectSingleNode("/xml/transaction_id")?.InnerText; var resultCode = doc.SelectSingleNode("/xml/result_code")?.InnerText; // 3. 关键参数检查(预防空引用) if (string.IsNullOrEmpty(outTradeNo) || string.IsNullOrEmpty(transactionId) || resultCode != "SUCCESS") { _logger.LogWarning("无效回调参数"); return WechatErrorResponse("参数校验失败"); } // 4. 处理订单更新(包含事务和并发控制) var (success, error) = await _orderService.HandlePaymentCallbackAsync( outTradeNo, transactionId); return success ? WechatSuccessResponse() : WechatErrorResponse(error); } catch (Exception ex) { // 5. 异常处理(记录完整错误) _logger.LogError(ex, "支付回调处理失败"); return WechatErrorResponse("系统异常"); } } private IActionResult WechatSuccessResponse() => Content( "<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>", "application/xml"); private IActionResult WechatErrorResponse(string msg) => Content( $"<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[{msg}]]></return_msg></xml>", "application/xml"); } ``` ```csharp // 订单服务实现 public class OrderService : IOrderService { private readonly AppDbContext _context; private readonly ILogger<OrderService> _logger; public OrderService(AppDbContext context, ILogger<OrderService> logger) { _context = context; _logger = logger; } public async Task<(bool success, string error)> HandlePaymentCallbackAsync( string orderNumber, string transactionId) { // 使用事务保证原子操作 await using var transaction = await _context.Database.BeginTransactionAsync(); try { // 1. 安全获取订单(避免空引用) var order = await _context.Orders .Where(o => o.OrderNumber == orderNumber) .FirstOrDefaultAsync(); if (order == null) { _logger.LogWarning($"订单不存在: {orderNumber}"); return (false, "订单不存在"); } // 2. 幂等性检查(防止重复处理) if (order.Status == OrderStatus.Paid) { _logger.LogInformation($"订单已支付: {orderNumber}"); return (true, string.Empty); } // 3. 更新订单状态 order.Status = OrderStatus.Paid; order.PaymentTime = DateTime.UtcNow; order.TransactionId = transactionId; // 4. 添加并发控制(防止状态覆盖) _context.Entry(order).Property(x => x.Version).OriginalValue = order.Version; order.Version++; await _context.SaveChangesAsync(); await transaction.CommitAsync(); return (true, string.Empty); } catch (DbUpdateConcurrencyException ex) { // 处理并发冲突 _logger.LogWarning(ex, $"订单并发冲突: {orderNumber}"); await transaction.RollbackAsync(); return (false, "并发更新冲突"); } catch (Exception ex) { _logger.LogError(ex, $"订单更新失败: {orderNumber}"); await transaction.RollbackAsync(); return (false, "系统异常"); } } } ``` --- ### 关键优化点说明 1. **空引用异常预防** - 使用安全导航操作符 `?.` 访问可能为 null 的对象 - 对关键参数进行空值检查(如 `string.IsNullOrEmpty`) - 使用 `FirstOrDefaultAsync()` 代替 `FirstAsync()` 避免查询为空时异常 2. **订单更新保障** - **事务处理**:确保更新操作的原子性 - **并发控制**:通过版本号防止状态覆盖 - **幂等性设计**:检查订单状态避免重复更新 - **错误回滚**:异常时显式回滚事务 3. **日志记录** - 记录原始回调数据便于排查 - 区分警告和错误级别日志 - 记录完整异常堆栈(包括内部异常) 4. **微信规范** - 严格返回 XML 格式响应(SUCCESS/FAIL) - 在 5 秒内返回处理结果,避免微信重试 --- ### 常见问题排查清单 1. **空引用检查** - 检查 `Startup.cs` 中是否注册了 `IOrderService` - 验证数据库连接字符串是否正确 - 在回调方法入口添加断点检查参数 2. **订单未更新检查** - 检查数据库事务隔离级别(建议 Read Committed) - 验证实体是否被其他进程锁定 - 检查是否有数据库触发器/约束阻止更新 3. **微信交互验证** ```bash # 使用 ngrok 测试回调 ngrok http 5000 ``` - 在微信商户平台配置正确的回调 URL - 使用微信沙箱环境测试 --- ### 进阶优化建议 1. **引入重试机制** ```csharp // 使用Polly实现重试 var retryPolicy = Policy .Handle<DbUpdateConcurrencyException>() .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); ``` 2. **添加签名验证** ```csharp // 验证微信签名(防止伪造求) var sign = doc.SelectSingleNode("/xml/sign")?.InnerText; var calculatedSign = CalculateWechatSign(doc); if (sign != calculatedSign) return WechatErrorResponse("签名无效"); ``` 3. **使用领域事件** ```csharp // 更新成功后发布领域事件 order.AddDomainEvent(new OrderPaidEvent(order.Id)); ``` --- ### 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值