ASP.NET微信JSAPI支付完整实现与签名验签详解
简介本资源是一份基于ASP.NETC#实现微信JSAPI支付接口的完整可运行Demo面向Web开发初学者及企业级支付功能集成开发者解决微信公众号内H5页面调用统一下单、支付回调、退款及查询等核心流程的落地难题。压缩包共122个文件含44个C#业务逻辑文件如JsApiPayPage.aspx.cs、24个ASPX前端页面覆盖商品页、支付页、退款页、查询页等全流程界面、11个关键DLL依赖、9个配置文件含微信商户号、密钥等敏感信息模板整体仅2.36MB轻量易部署。已有1071人学习下载说明其在实际项目中具备较强参考价值。代码基于微信官方V3版支付接口编写无需额外引入JS SDK结构清晰、注释充分规避了旧版Demo中繁琐写法与WAP支付等常见误区特别适合快速验证支付链路、理解签名生成、异步通知处理及证书p12加载等关键环节。1. ASP.NET微信支付JSAPI接口调用Demo不是配个URL就能回调成功的黑匣子你写完ASP.NET MVC站点接入微信JSAPI支付时是不是也经历过——前端调起wx.chooseWXPay后页面白屏、控制台报config:invalid signature、后台NotifyUrl收不到任何回调、甚至微信商户平台里连一笔测试订单都查不到这不是玄学是签名生成逻辑、时间戳同步、nonceStr复用、证书路径绑定、异步通知验签这五道关卡全在暗处咬人。这个ASP.NET微信支付JSAPI Demo不是教你怎么点“立即支付”按钮而是把从统一下单unifiedorder到签名拼接、从JSAPI参数注入到服务端异步通知NotifyUrl的完整链路用C#原生代码可调试断点真实日志输出的方式拆开给你看。它专为已具备ASP.NET Web Forms或MVC基础、正在对接微信支付但卡在“能下单不能回调”阶段的开发者准备——不讲OAuth2.0授权流程不绕弯子讲公众号配置只聚焦JSAPI支付这一条通路的落地细节。2. JSAPI支付核心流程与ASP.NET实现选型依据为什么必须手写签名而不能依赖SDK微信JSAPI支付不是简单发个HTTP请求就完事。它要求前后端严格协同前端需通过后端获取jsapi_ticket和access_token生成签名后端需在统一下单时传入正确的openid并返回prepay_id再由前端二次签名调起支付。整个链路中签名生成逻辑的可控性直接决定成败。很多团队早期用NuGet引入的第三方SDK结果在Sign方法里发现它自动拼接了appid、mch_id、nonce_str、body等字段顺序但微信官方文档明确要求所有参与签名的参数必须按字典序升序排列且空值不参与排序。一旦SDK内部排序逻辑与微信最新规范错位比如某次微信悄悄调整了timeStamp字段名大小写你的支付就会静默失败日志里连错误码都不返回。2.1 微信JSAPI支付四步闭环从用户点击到服务器验签微信JSAPI支付本质是“前端发起、后端驱动、微信中转、服务端终验”的四段式流程用户触发支付用户在ASP.NET页面点击“支付”前端JavaScript向后端/Pay/GetJsApiParameters接口发起AJAX请求后端统一下单 签名准备ASP.NET Controller接收请求校验用户登录态、查询商品信息调用微信unifiedorder接口获取prepay_id再按规则拼接appId、timeStamp、nonceStr、package、signType生成paySign前端调起支付将appId、timeStamp、nonceStr、package、signType、paySign注入wx.config和wx.chooseWXPay参数对象服务端异步通知验签用户支付成功后微信服务器主动POST数据到你配置的NotifyUrl如/Pay/Notify你必须用MD5对原始XML数据重新签名并与sign字段比对且必须忽略XML中所有空节点和CDATA包裹内容。提示微信官方强调NotifyUrl必须是外网可访问的绝对路径且不能带查询参数如/Pay/Notify?tokenabc会失败。ASP.NET中建议用RouteConfig注册无参路由避免IIS URL重写干扰。2.2 ASP.NET Web Forms vs MVCController层签名逻辑为何更可控本Demo基于ASP.NET MVC 5兼容.NET Framework 4.7.2原因很实际Web Forms的Page_Load事件生命周期混乱Request.Form在异步通知中可能为空HttpContext.Current.Request.InputStream读取一次即耗尽导致验签时无法重复读取原始XMLMVC的ActionFilter可统一拦截我们可在[HttpPost]的NotifyAction上加自定义WeChatNotifyFilter在进入Action前完成XML流读取、字符编码转换微信通知默认GBKASP.NET默认UTF-8、验签前置校验Model Binding天然适配XML反序列化微信通知XML结构固定我们定义WeChatNotifyRequest类用XmlRoot(xml)标记配合XmlSerializer一行代码即可映射避免手动XDocument.Load()解析出错。// WeChatNotifyRequest.cs - 微信异步通知XML反序列化模型 [XmlRoot(xml)] public class WeChatNotifyRequest { [XmlElement(return_code)] public string ReturnCode { get; set; } [XmlElement(return_msg)] public string ReturnMsg { get; set; } [XmlElement(result_code)] public string ResultCode { get; set; } [XmlElement(err_code)] public string ErrCode { get; set; } [XmlElement(err_code_des)] public string ErrCodeDes { get; set; } [XmlElement(appid)] public string AppId { get; set; } [XmlElement(mch_id)] public string MchId { get; set; } [XmlElement(nonce_str)] public string NonceStr { get; set; } [XmlElement(sign)] public string Sign { get; set; } [XmlElement(sign_type)] public string SignType { get; set; } [XmlElement(transaction_id)] public string TransactionId { get; set; } [XmlElement(out_trade_no)] public string OutTradeNo { get; set; } [XmlElement(attach)] public string Attach { get; set; } [XmlElement(time_end)] public string TimeEnd { get; set; } }这段代码的关键在于[XmlElement]属性强制指定了XML节点名与C#属性的映射关系完全规避了大小写敏感问题微信有时返回transaction_id有时是TransactionId靠XElement.Element(transaction_id)容易空引用。XmlSerializer在反序列化时会自动跳过未标记的节点保证模型干净。2.3 签名算法必须手写MD5签名的三个致命陷阱微信JSAPI支付签名算法虽标为MD5但实际是对拼接字符串做MD5哈希后再转大写且拼接规则极苛刻。常见错误包括❌ 错误1用HttpUtility.UrlEncode()对参数值编码后再拼接 → 微信要求仅对特殊字符如空格、、做URL编码且编码后小写UrlEncode默认大写%20应改用WebUtility.UrlEncode()❌ 错误2拼接时漏掉key商户API密钥→ 正确顺序是key1value1key2value2keyYOUR_API_KEY且key必须放在最后❌ 错误3nonce_str在统一下单和JSAPI签名中混用 → 微信要求nonce_str必须每次请求唯一统一下单用一个JSAPI签名用另一个否则会被判定为重放攻击。// Utils/WeChatSignHelper.cs - 手写签名核心方法 public static string GenerateSign(Dictionarystring, string parameters, string apiKey) { // 1. 参数过滤移除值为空的项微信明文规定 var filtered parameters.Where(kv !string.IsNullOrEmpty(kv.Value)) .ToDictionary(kv kv.Key, kv kv.Value); // 2. 字典序升序排序关键 var sorted filtered.OrderBy(kv kv.Key).ToDictionary(kv kv.Key, kv kv.Value); // 3. 拼接字符串key1value1key2value2keyYOUR_API_KEY var builder new StringBuilder(); foreach (var kv in sorted) { builder.Append(${kv.Key}{WebUtility.UrlEncode(kv.Value)}); } builder.Append($key{apiKey}); // 4. MD5哈希 转大写 using (var md5 MD5.Create()) { var hashBytes md5.ComputeHash(Encoding.UTF8.GetBytes(builder.ToString())); return BitConverter.ToString(hashBytes).Replace(-, ).ToUpper(); } }这段代码里WebUtility.UrlEncode()替代了老旧的HttpUtility.UrlEncode()解决编码大小写问题OrderBy(kv kv.Key)确保字典序builder.Append($key{apiKey})严格把key放末尾。注意此方法不处理中文参数——微信要求所有参数值必须是UTF-8编码的字符串但WebUtility.UrlEncode()对中文会生成%E4%BD%A0%E5%A5%BD而微信后台接收时会自动解码所以无需额外Encoding.UTF8.GetBytes()。这是血泪经验曾有项目因手动Encoding.GetEncoding(GBK).GetBytes()导致签名永远不匹配。3. 统一下单接口unifiedorder实现从OpenID获取到PrepayId封装统一下单是JSAPI支付的起点也是最容易翻车的第一环。它要求你必须提供用户的openid非unionid且该openid必须属于当前公众号而非小程序或APP。很多开发者卡在这里是因为误以为“用户已关注公众号openid自然就有”却忽略了网页授权获取openid必须走OAuth2.0流程且scope必须为snsapi_base静默授权或snsapi_userinfo用户确认。本Demo采用snsapi_base不弹窗用户体验好但只能拿到openid拿不到用户信息。3.1 静默授权获取OpenID避免用户跳出页面的302跳转设计ASP.NET MVC中我们不直接在支付按钮点击时发起OAuth跳转而是提前在用户进入支付页时用RedirectToAction(Auth, Pay)跳转到授权地址。微信会重定向回你配置的redirect_uri并附带code参数。此时Controller接收code调用微信oauth2/access_token接口换取openid。// Controllers/PayController.cs public ActionResult Auth(string code, string state) { if (string.IsNullOrEmpty(code)) { // 未获取到code说明用户拒绝授权或网络异常 ViewBag.Error 授权失败请重试; return View(Error); } // 1. 用code换取access_token和openid var tokenUrl $https://api.weixin.qq.com/sns/oauth2/access_token? $appid{WeChatConfig.AppId} $secret{WeChatConfig.AppSecret} $code{code} $grant_typeauthorization_code; var client new WebClient(); client.Encoding Encoding.UTF8; var response client.DownloadString(tokenUrl); var tokenResult JsonConvert.DeserializeObjectAccessTokenResponse(response); if (tokenResult.errcode ! 0) { ViewBag.Error $获取OpenID失败{tokenResult.errmsg}; return View(Error); } // 2. 将openid存入Session生产环境建议用Redis Session[OpenId] tokenResult.openid; return RedirectToAction(Index); // 回到支付页 } public class AccessTokenResponse { public int errcode { get; set; } public string errmsg { get; set; } public string access_token { get; set; } public int expires_in { get; set; } public string refresh_token { get; set; } public string openid { get; set; } public string scope { get; set; } }这里的关键点client.Encoding Encoding.UTF8必须显式设置否则微信返回的JSON中文会乱码AccessTokenResponse类属性名必须与微信返回字段名完全一致包括大小写否则JsonConvert.DeserializeObject会赋值失败。state参数在此未使用但生产环境务必校验防止CSRF攻击。3.2 统一下单请求构造XML格式、证书路径、超时控制三要素统一下单接口https://api.mch.weixin.qq.com/pay/unifiedorder要求POST XML数据且必须使用HTTPS 双向证书认证即上传商户证书p12文件到服务器。很多团队在本地开发时忽略证书导致The request was aborted: Could not create SSL/TLS secure channel错误。// Services/WeChatPayService.cs public async TaskUnifiedOrderResponse UnifiedOrderAsync(string openId, string outTradeNo, decimal totalFee, string body) { var xml $xml appid{WeChatConfig.AppId}/appid mch_id{WeChatConfig.MchId}/mch_id nonce_str{Guid.NewGuid().ToString(N)}/nonce_str sign_typeMD5/sign_type body![CDATA[{body}]]/body out_trade_no{outTradeNo}/out_trade_no total_fee{(int)(totalFee * 100)}/total_fee spbill_create_ip{HttpContext.Current.Request.UserHostAddress}/spbill_create_ip notify_url{WeChatConfig.NotifyUrl}/notify_url trade_typeJSAPI/trade_type openid{openId}/openid /xml; // 1. 生成签名 var dict XmlHelper.XmlToDictionary(xml); dict[sign] WeChatSignHelper.GenerateSign(dict, WeChatConfig.ApiKey); // 2. 重新生成带sign的XML var signedXml XmlHelper.DictionaryToXml(dict); // 3. 发送HTTPS请求含证书 var handler new WebRequestHandler(); handler.ClientCertificates.Add(new X509Certificate2( WeChatConfig.CertPath, WeChatConfig.CertPassword, X509KeyStorageFlags.MachineKeySet)); using (var client new HttpClient(handler)) { client.Timeout TimeSpan.FromSeconds(15); // 必须设超时否则卡死 var content new StringContent(signedXml, Encoding.UTF8, application/xml); var response await client.PostAsync(https://api.mch.weixin.qq.com/pay/unifiedorder, content); var resultXml await response.Content.ReadAsStringAsync(); return XmlHelper.XmlToUnifiedOrderResponse(resultXml); } }参数说明total_fee单位为分必须是整数所以(int)(totalFee * 100)强制转换避免小数点精度丢失spbill_create_ip必须是用户真实IPHttpContext.Current.Request.UserHostAddress在IIS部署时可能拿到的是负载均衡IP此时需从X-Forwarded-For头中提取CertPath和CertPassword从web.config读取证书密码绝不能硬编码HttpClient必须用WebRequestHandler加载证书new HttpClient()默认不支持客户端证书。3.3 PrepayId提取与JSAPI参数组装前端能调起支付的关键一步统一下单成功后微信返回XML包含prepay_id我们需要将其封装成前端wx.chooseWXPay所需的6个参数。注意timeStamp必须是秒级时间戳10位数字不是毫秒package值固定为prepay_idxxx不能加其他字段。// Controllers/PayController.cs [HttpPost] public JsonResult GetJsApiParameters(string outTradeNo) { var openId Session[OpenId] as string; if (string.IsNullOrEmpty(openId)) return Json(new { success false, message 未获取到用户OpenID }); var service new WeChatPayService(); var result service.UnifiedOrderAsync(openId, outTradeNo, 0.01m, 测试商品).Result; if (result.return_code ! SUCCESS || result.result_code ! SUCCESS) { return Json(new { success false, message result.return_msg ?? result.err_code_des }); } // 组装JSAPI参数 var timeStamp DateTimeOffset.Now.ToUnixTimeSeconds().ToString(); // 10位时间戳 var nonceStr Guid.NewGuid().ToString(N); var package $prepay_id{result.prepay_id}; var jsApiParams new Dictionarystring, string { [appId] WeChatConfig.AppId, [timeStamp] timeStamp, [nonceStr] nonceStr, [package] package, [signType] MD5 }; jsApiParams[paySign] WeChatSignHelper.GenerateSign(jsApiParams, WeChatConfig.ApiKey); return Json(new { success true, data jsApiParams }); }这里有个易错点timeStamp用DateTimeOffset.Now.ToUnixTimeSeconds()而非DateTime.Now.ToUniversalTime().Ticks / 10000000 - 62135596800后者是.NET旧式时间戳计算易出错。Guid.NewGuid().ToString(N)生成32位无横线nonce_str符合微信要求。4. 异步通知NotifyUrl验签与订单状态更新支付成功的最后一道门微信支付成功后会向你配置的NotifyUrl发送XML格式的异步通知。这不是可选功能而是支付闭环的强制环节。如果你不处理或验签失败微信会持续重试最多10次且商户平台中该订单状态永远是“支付中”。更严重的是用户看到“支付成功”页面但你的数据库没更新会导致发货、库存、对账全部错乱。4.1 NotifyUrl路由与原始XML流读取避开ASP.NET MVC的自动模型绑定陷阱ASP.NET MVC默认会尝试将POST数据绑定到Action参数但微信通知是XML不是JSON或表单直接绑定会失败。我们必须手动读取Request.InputStream且只能读一次流是向前只读的。因此我们在Action开头就用ReadAsStringAsync()一次性读取并缓存到HttpContext.Items供后续使用。// Controllers/PayController.cs [HttpPost] [ValidateInput(false)] // 关闭请求验证否则含xml会报404.19 public async TaskActionResult Notify() { try { // 1. 读取原始XML流关键 var stream Request.InputStream; stream.Seek(0, SeekOrigin.Begin); var reader new StreamReader(stream, Encoding.GetEncoding(GB2312)); // 微信通知默认GBK编码 var xml await reader.ReadToEndAsync(); // 2. 反序列化为对象 var notifyReq XmlHelper.XmlToNotifyRequest(xml); // 3. 验签 if (!WeChatSignHelper.VerifySign(xml, notifyReq.Sign, WeChatConfig.ApiKey)) { return Content(xmlreturn_code![CDATA[FAIL]]/return_codereturn_msg![CDATA[签名失败]]/return_msg/xml, text/xml); } // 4. 处理业务逻辑更新订单状态、发短信、减库存等 if (notifyReq.ResultCode SUCCESS) { UpdateOrderStatus(notifyReq.OutTradeNo, notifyReq.TransactionId, notifyReq.TimeEnd); } // 5. 返回成功响应必须是XML且return_codeSUCCESS return Content(xmlreturn_code![CDATA[SUCCESS]]/return_codereturn_msg![CDATA[OK]]/return_msg/xml, text/xml); } catch (Exception ex) { // 记录异常日志但不要抛出否则微信会认为失败而重试 Log.Error(微信Notify异常, ex); return Content(xmlreturn_code![CDATA[FAIL]]/return_codereturn_msg![CDATA[系统异常]]/return_msg/xml, text/xml); } }注意Encoding.GetEncoding(GB2312)是关键。微信官方文档写“通知编码为UTF-8”但实测大量商户收到的是GBK用Encoding.UTF8读取会导致中文乱码进而验签失败。GB2312兼容性最好。4.2 验签逻辑详解为什么必须用原始XML字符串而非反序列化后的对象微信验签要求对原始XML字符串不含BOM头做MD5签名而不是对反序列化后的对象属性拼接。因为XML中可能存在CDATA节、空格缩进、属性顺序差异这些在反序列化时都会丢失。例如xml return_code![CDATA[SUCCESS]]/return_code result_code![CDATA[SUCCESS]]/result_code out_trade_no![CDATA[20231001123456789]]/out_trade_no /xml如果用XDocument解析再拼return_codeSUCCESSresult_codeSUCCESS...CDATA标签内的![CDATA[和]]会被丢弃导致签名不匹配。正确做法是在读取InputStream后立即将原始字符串传给VerifySign方法。// Utils/WeChatSignHelper.cs public static bool VerifySign(string xmlString, string wechatSign, string apiKey) { // 1. 移除XML声明如?xml version1.0 encodingGBK?和换行缩进 var cleanXml Regex.Replace(xmlString, \?xml[^]*\?, ).Trim(); // 2. 解析XML提取所有keyvalue/key节点构造成字典 var doc XDocument.Parse(cleanXml); var dict doc.Root.Elements() .Where(e !string.IsNullOrEmpty(e.Value)) // 过滤空节点 .ToDictionary(e e.Name.LocalName, e e.Value.Trim()); // 3. 移除sign节点验签时不参与 dict.Remove(sign); // 4. 按字典序拼接 生成签名 var generatedSign GenerateSign(dict, apiKey); return generatedSign wechatSign.ToUpper(); }这段代码中Regex.Replace清除XML声明XDocument.Parse安全解析ToDictionary提取键值对dict.Remove(sign)确保验签时排除自身。注意e.Value.Trim()必须调用否则微信返回的return_code SUCCESS /return_code前后空格会导致签名不匹配——这是线上最隐蔽的坑之一。4.3 订单状态幂等更新防止微信重试导致重复发货微信通知可能重试你的UpdateOrderStatus方法必须是幂等的。即同一out_trade_no多次调用结果一致且不产生副作用如重复扣款、重复发货。private void UpdateOrderStatus(string outTradeNo, string transactionId, string timeEnd) { using (var db new ApplicationDbContext()) { var order db.Orders.FirstOrDefault(o o.OutTradeNo outTradeNo o.Status ! PAID); if (order null) return; // 已处理过直接返回 // 使用数据库行锁避免并发更新 var sql UPDATE Orders SET Status PAID, TransactionId p0, PayTime p1 WHERE Id p2 AND Status ! PAID; db.Database.ExecuteSqlCommand(sql, transactionId, DateTime.ParseExact(timeEnd, yyyyMMddHHmmss, null), order.Id); // 检查是否真更新了行数 if (db.Entry(order).State EntityState.Detached) { // 更新成功执行后续业务如发短信、减库存 SendPaymentSuccessSms(order.UserId, order.Amount); ReduceInventory(order.Items); } } }这里用原生SQL加WHERE Status ! PAID条件确保只有未支付的订单才被更新。ExecuteSqlCommand返回影响行数我们可以据此判断是否真正处理了新订单避免重复操作。5. 常见问题排查五个让开发者凌晨三点还在抓头发的典型现象微信支付接入不是“写完就跑”而是“跑通就崩”。以下五个现象是我在线上项目中反复踩过的坑每一条都附带真实日志片段和定位方法。5.1 现象前端wx.chooseWXPay报错config:invalid signature但后端签名逻辑无异常原因前端传入wx.config的nonceStr和timestamp与后端生成paySign时用的nonceStr和timeStamp不一致。常见于前端JS手动拼接参数或后端Action返回JSON时timeStamp被序列化为科学计数法如1.7e9。解决在GetJsApiParametersAction中timeStamp必须用.ToString()转字符串且前端必须用parseInt()确保是整数nonceStr前后端必须用同一个值——建议后端生成后同时返回给前端前端勿自行生成。5.2 现象统一下单返回return_codeSUCCESS但result_codeFAILerr_codeINVALID_OPENID原因openid不是当前公众号的用户。可能情况1用户是通过小程序访问网页openid属于小程序而非公众号2公众号配置了JS接口安全域名但当前页面域名未添加3appid填错了用了小程序的appid。解决在获取openid后先调用微信https://api.weixin.qq.com/cgi-bin/user/info?access_tokenxxxopenidxxxlangzh_CN接口检查返回的subscribe字段是否为1已关注且openid与appid匹配。5.3 现象NotifyUrl收不到任何请求商户平台显示“通知URL不可达”原因NotifyUrl配置了带参数的URL如/Pay/Notify?id123或服务器防火墙屏蔽了微信IP段微信官方公布IP段需定期更新或IIS启用了“请求筛选”模块拦截了XML POST。解决NotifyUrl必须是纯路径如https://yoursite.com/Pay/Notify在IIS管理器中进入“请求筛选”→“允许的URL”→添加/Pay/Notify在Windows防火墙高级设置中放行入站TCP 443端口并添加微信IP段如182.254.0.0/16,123.125.0.0/16。5.4 现象异步通知验签通过但result_codeFAILerr_codeORDERPAID原因该订单已被支付成功微信再次通知。常见于1你返回了非SUCCESS的XML如少写了![CDATA[OK]]2服务器处理超时微信等待5秒导致微信重发。解决在NotifyAction开头加日志记录DateTime.Now和xml长度确保返回XML严格符合格式return_code![CDATA[SUCCESS]]/return_code业务处理逻辑必须超快复杂操作如调第三方API放入消息队列异步执行。5.5 现象本地调试一切正常上线后unifiedorder返回curl error:77SSL证书问题原因服务器未安装微信商户证书p12文件或证书密码错误或证书私钥权限不足IIS_IUSRS组无读取权限。解决将p12证书导入Windows证书管理器本地计算机在“个人”→“证书”中找到它右键→“所有任务”→“管理私钥”添加IIS_IUSRS用户并赋予“读取”权限在代码中X509Certificate2构造函数第二个参数必须是字符串密码不能为null。6. 生产环境加固与调试技巧从“能跑”到“稳跑”的三个实战习惯上线不是终点而是稳定运行的起点。微信支付涉及资金任何疏漏都可能引发客诉甚至资损。我从某跨平台系统维护三年的经验中提炼出三个必须落地的习惯它们不增加代码量但能让你少熬一半夜。6.1 日志分级把微信交互日志单独剥离用ELK实时监控微信接口调用日志必须独立于普通业务日志且包含完整上下文。我在WeChatPayService中统一添加日志切面// 日志格式示例Serilog Log.Information(WeChat:UnifiedOrder:Start {Request}, new { OpenId openId, OutTradeNo outTradeNo, TotalFee totalFee, Body body }); // 调用后 Log.Information(WeChat:UnifiedOrder:Success {Response}, result); // 失败时 Log.Error(WeChat:UnifiedOrder:Fail {Request} {Exception}, new { openId, outTradeNo }, ex);关键点Request和Response用结构化日志ELK中可直接按OutTradeNo聚合所有日志Exception记录完整堆栈但绝不记录apiKey等敏感字段。生产环境日志级别设为Information调试时临时调为Debug查看HTTP请求详情。6.2 支付结果轮询兜底当Notify失效时的后悔药微信通知可能因网络抖动丢失而用户已看到“支付成功”。我们不能坐等必须主动轮询。在用户点击支付后前端启动定时器每5秒调用/Pay/Query?out_trade_noxxx直到返回trade_stateSUCCESS或超时120秒。// Controllers/PayController.cs [HttpGet] public JsonResult Query(string outTradeNo) { var result _weChatPayService.QueryOrderAsync(outTradeNo).Result; if (result.trade_state SUCCESS) { // 更新订单状态返回成功 UpdateOrderStatus(outTradeNo, result.transaction_id, result.time_end); return Json(new { success true, data result }); } return Json(new { success false, message result.trade_state_desc }); }QueryOrderAsync调用微信https://api.mch.weixin.qq.com/pay/orderquery接口。此方案成本低单次查询0.1元远低于资损且用户无感知。从那以后我每次上线新支付通道都强制走一遍轮询兜底逻辑哪怕Notify从未失败过。6.3 敏感配置分离web.config中的微信参数必须加密AppId、MchId、ApiKey、CertPassword绝不能明文写在web.config。我们用.NET Framework内置的aspnet_regiis.exe工具加密connectionStrings和appSettings节# 加密appSettings节在项目目录下执行 aspnet_regiis -pef appSettings D:\YourProjectPath加密后web.config中该节变为appSettings configProtectionProviderRsaProtectedConfigurationProvider EncryptedData CipherData CipherValueAQAAANCMnd8BFdERjHoAwE/ClsBAAAA.../CipherValue /CipherData /EncryptedData /appSettingsIIS运行时自动解密无需修改代码。注意加密必须在目标服务器上执行密钥容器是机器级的不能拷贝到其他服务器。若需多机部署应导出密钥容器并导入。希望帮到你。本文还有配套的精品资源点击获取