.NET微信统一下单后台服务编程示例(2022年最新,亲测可用)
.NET微信统一下单后台服务编程示例(2022最新,亲测可用)
一、简述微信统一下单支付流程
1.APP前端调用后台服务的登录接口,获取到用户的OpenID(我们这一步骤让前端去获取)
2.服务端代码这边生成订单
3.服务端调用支付统一下单的Api
4.服务端将再次签名,返回5个参数(前端得到数据后可以调起支付)
5.微信后台会回调我们服务端,我们通过回调更新订单状态
6.前端也会调用服务端订单查询接口,服务端查询订单状态(防止微信回调这边的一个时间差),如果成功了,在这个接口里会向用户发送一个返回消息。
重点是步骤3和4,特别是签名那块的格式要求务必按照微信的要求来(具体可以参考微信统一下单官网)。
二、提前准备工作
2.1 微信开放平台已认证,【管理中心】获取“管理中心 / 应用详情”页面的AppID,AppSecret。
2.2进入微信支付下关联的商户号mch_id,以及商户平台中获取商户秘钥wx_key。
2.3阿里云或者华为云开通ECS服务,配置IIS7.0,asp.net微信支付后台建议安装.Net4.0或更高版本。
三、代码案例
本项目代码开发IDE采用Vs2019,新建项目WxAPI,创建两个aspx页面,名称分别为:AppServer.aspx(统一下单访问页面)和notify_url.aspx(返回结果页面)。
AppServer.aspx页面代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Net.Http; using System.Text; using System.Xml; using System.Net; using System.IO; using System.Data; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace WxAppServer { public partial class AppServer : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string Body = string.Empty; //订单描述 string Subject = string.Empty;//订单简介 string TotalAmount = string.Empty;//金额 string OutTradeNo = string.Empty;//订单号 string PayDdnom = string.Empty;//商户订单号,这里暂时由app前端传值 Body = "支付"; Subject = "微信支付"; //TotalAmount = 201; OutTradeNo = "100000" + DateTime.Now.ToString("yyyyMMddhhmm") + "887766"; if (!string.IsNullOrEmpty(Request.Params["TotalAmount"])) { TotalAmount = Request.Params["TotalAmount"]; //TotalAmount = (int.Parse(TotalAmount) * 100).ToString(); } else { TotalAmount = "0"; } if (!string.IsNullOrEmpty(Request.Params["PayDdnom"])) { PayDdnom = Request.Params["PayDdnom"]; } else { PayDdnom = ""; } //微信支付 基础配置信息 string wx_appid = System.Web.Configuration.WebConfigurationManager.AppSettings["wx_appid"].ToString();//微信开放平台审核通过的应用 string wx_mch_id = System.Web.Configuration.WebConfigurationManager.AppSettings["wx_mch_id"].ToString(); //微信支付分配的商户号 string wx_nonce_str = GetRandomString(16);//随机字符串,不长于32位 string aa = "***服务系统-" + Subject;////商品描述交易字段格式根据不同的应用场景按照以下格式:APP——需传入应用市场上的APP名字-实际商品名称,天昱凤凰城-物业充值。 string strcode = aa; byte[] buffer = Encoding.UTF8.GetBytes(strcode); string wx_body = Encoding.UTF8.GetString(buffer, 0, buffer.Length); //string wx_out_trade_no = DateTime.Now.ToString("yyyyMMddHHmmss") + GetRandomString(10);//商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号 string wx_out_trade_no = PayDdnom;//商户系统内部的订单号,32个字符内、可包含字母,这里暂时由APP前端传值 string wx_total_fee = Convert.ToString(TotalAmount);//;//订单总金额,单位为分,详见支付金额 string wx_spbill_create_ip = GetWebClientIp();//// 用户端实际ip string wx_notify_url = System.Web.Configuration.WebConfigurationManager.AppSettings["wxpay_notifyurl"].ToString();////接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 string wx_trade_type = "APP";////支付类型 string wx_sign = "";// 签名 string wx_key = System.Web.Configuration.WebConfigurationManager.AppSettings["wx_key"].ToString(); //密钥 var dic = new Dictionary<string, string> { {"appid", wx_appid}, {"mch_id", wx_mch_id}, {"nonce_str", wx_nonce_str}, {"body", wx_body}, {"out_trade_no", wx_out_trade_no},//商户自己的订单号码 {"total_fee", wx_total_fee}, {"spbill_create_ip",wx_spbill_create_ip},//服务器的IP地址 {"notify_url", wx_notify_url},//异步通知的地址,不能带参数 {"trade_type", wx_trade_type} }; //加入签名 dic.Add("sign", GetSignString(dic)); var sb = new StringBuilder(); sb.Append("<xml>"); foreach (var d in dic) { sb.Append("<" + d.Key + ">" + d.Value + "</" + d.Key + ">"); } sb.Append("</xml>"); var xml = new XmlDocument(); // xml.LoadXml(GetPostString("https://api.mch.weixin.qq.com/pay/unifiedorder", sb.ToString())); CookieCollection coo = new CookieCollection(); Encoding en = Encoding.GetEncoding("UTF-8"); HttpWebResponse response = CreatePostHttpResponse("https://api.mch.weixin.qq.com/pay/unifiedorder", sb.ToString(), en); //打印返回值 Stream stream = response.GetResponseStream(); //获取响应的字符串流 StreamReader sr = new StreamReader(stream); //创建一个stream读取流 string html = sr.ReadToEnd(); //从头读到尾,放到字符串html Console.WriteLine(html); xml.LoadXml(html); //对请求返回值 进行处理 var root = xml.DocumentElement; DataSet ds = new DataSet(); StringReader stram = new StringReader(html); XmlTextReader reader = new XmlTextReader(stram); ds.ReadXml(reader); string return_code = ds.Tables[0].Rows[0]["return_code"].ToString(); if (return_code.ToUpper() == "SUCCESS") { //通信成功 string result_code = ds.Tables[0].Rows[0]["result_code"].ToString();//业务结果 if (result_code.ToUpper() == "SUCCESS") { var res = new Dictionary<string, string> { {"appid", wx_appid}, {"partnerid", wx_mch_id}, {"prepayid", root.SelectSingleNode("/xml/prepay_id").InnerText}, {"noncestr", dic["nonce_str"]}, {"timestamp", GetTimeStamp()}, {"package", "Sign=WXPay"} }; //在服务器上签名 //res.Add("sign", GetSignString(res)); //string signapp = res.ToString(); //GetSignString1(res); //Response.End(); string json_str = "{'appid':'" + wx_appid + "','noncestr':'" + dic["nonce_str"] + "','package':'Sign=WXPay','partnerid':'" + wx_mch_id + "','prepayid':'" + root.SelectSingleNode("/xml/prepay_id").InnerText + "','timestamp':" + GetTimeStamp() + ",'sign':'" + GetSignString(res) + "'}"; json_str = json_str.Replace("'", "\""); Response.Write(json_str); // Response.Write("{\"appid\":\"wx0411fa6a39d61297\",\"noncestr\":\"T5Z0CQhdsErFb6TJ\", \"package\":\"Sign=WXPay\",\"partnerid\":\"1230636401\",\"prepayid\":\"wx20170918224227155b06a0c60785392660\",\"timestamp\":1505745747,\"sign\":\"F505772C941F240EECB25A943B079607\"}"); //官方的返回数据 // Response.Write("{\"appid\":\"wx92fe0ecdb6d0c5db\",\"noncestr\":\"M4OTEMYT43UBGFJ4\",\"package\":\"Sign=WXPay\",\"partnerid\":\"1487416022\",\"prepayid\":\"wx2017092313454242bf6e8d210030506192\",\"timestamp\":1506146531,\"sign\":\"3477EB571C3C65A17ED78621E5C7C63B\"}"); } } } /// <summary> /// 从字符串里随机得到,规定个数的字符串. /// </summary> /// <param name="allChar"></param> /// <param name="CodeCount"></param> /// <returns></returns> public static string GetRandomString(int CodeCount) { string allChar = "1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,i,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z"; string[] allCharArray = allChar.Split(','); string RandomCode = ""; int temp = -1; Random rand = new Random(); for (int i = 0; i < CodeCount; i++) { if (temp != -1) { rand = new Random(temp * i * ((int)DateTime.Now.Ticks)); } int t = rand.Next(allCharArray.Length - 1); while (temp == t) { t = rand.Next(allCharArray.Length - 1); } temp = t; RandomCode += allCharArray[t]; } return RandomCode; } public static string GetWebClientIp() { string userIP = "IP"; try { if (System.Web.HttpContext.Current == null || System.Web.HttpContext.Current.Request == null || System.Web.HttpContext.Current.Request.ServerVariables == null) return ""; string CustomerIP = ""; //CDN加速后取到的IP CustomerIP = System.Web.HttpContext.Current.Request.Headers["Cdn-Src-Ip"]; if (!string.IsNullOrEmpty(CustomerIP)) { return CustomerIP; } CustomerIP = System.Web.HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; if (!String.IsNullOrEmpty(CustomerIP)) return CustomerIP; if (System.Web.HttpContext.Current.Request.ServerVariables["HTTP_VIA"] != null) { CustomerIP = System.Web.HttpContext.Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"]; if (CustomerIP == null) CustomerIP = System.Web.HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]; } else { CustomerIP = System.Web.HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]; } if (string.Compare(CustomerIP, "unknown", true) == 0) return System.Web.HttpContext.Current.Request.UserHostAddress; return CustomerIP; } catch { } return userIP; } private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) { return true; //总是接受 } public static HttpWebResponse CreatePostHttpResponse(string url, string datas, Encoding charset) { HttpWebRequest request = null; //HTTPSQ请求 ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); request = WebRequest.Create(url) as HttpWebRequest; request.ProtocolVersion = HttpVersion.Version10; request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; //如果需要POST数据 //if (!(parameters == null || parameters.Count == 0)) //{ StringBuilder buffer = new StringBuilder(); //int i = 0; //foreach (string key in parameters.Keys) //{ // if (i > 0) // { // buffer.AppendFormat("&{0}={1}", key, parameters[key]); // } // else // { // buffer.AppendFormat("{0}={1}", key, parameters[key]); // } // i++; //} buffer.AppendFormat(datas); byte[] data = charset.GetBytes(buffer.ToString()); using (Stream stream = request.GetRequestStream()) { stream.Write(data, 0, data.Length); } //} return request.GetResponse() as HttpWebResponse; } public string GetSignString(Dictionary<string, string> dic) { string key = System.Web.Configuration.WebConfigurationManager.AppSettings["wx_key"].ToString(); ;//商户平台 API安全里面设置的KEY 32位长度 //排序 dic = dic.OrderBy(d => d.Key).ToDictionary(d => d.Key, d => d.Value); //连接字段 var sign = dic.Aggregate("", (current, d) => current + (d.Key + "=" + d.Value + "&")); sign += "key=" + key; //MD5 //Response.Write(sign+"<br/>"); sign = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sign, "MD5").ToUpper(); return sign; } public string GetSignString1(Dictionary<string, string> dic) { string key = System.Web.Configuration.WebConfigurationManager.AppSettings["wx_key"].ToString(); ;//商户平台 API安全里面设置的KEY 32位长度 //排序 dic = dic.OrderBy(d => d.Key).ToDictionary(d => d.Key, d => d.Value); //连接字段 var sign = dic.Aggregate("", (current, d) => current + (d.Key + "=" + d.Value + "&")); sign += "key=" + key; //MD5 Response.Write(sign + "<br/>" + System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sign, "MD5").ToUpper()); sign = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sign, "MD5").ToUpper(); return sign; } /// <summary> /// 获取时间戳 /// </summary> /// <returns></returns> public static string GetTimeStamp() { TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0); return Convert.ToInt64(ts.TotalSeconds).ToString(); } } }
notify_url.aspx页面代码如下:
using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Xml; namespace WxAppServer { public partial class notify_url : System.Web.UI.Page { public string return_result = ""; protected void Page_Load(object sender, EventArgs e) { String xmlData = getPostStr();//获取请求数据 if (xmlData == "") { } else { var dic = new Dictionary<string, string> { {"return_code", "SUCCESS"}, {"return_msg","OK"} }; var sb = new StringBuilder(); sb.Append("<xml>"); foreach (var d in dic) { sb.Append("<" + d.Key + ">" + d.Value + "</" + d.Key + ">"); } sb.Append("</xml>"); //把数据重新返回给客户端 DataSet ds = new DataSet(); StringReader stram = new StringReader(xmlData); XmlTextReader datareader = new XmlTextReader(stram); ds.ReadXml(datareader); if (ds.Tables[0].Rows[0]["return_code"].ToString() == "SUCCESS") { string wx_appid = "";//微信开放平台审核通过的应用APPID string wx_mch_id = "";//微信支付分配的商户号 string wx_nonce_str = "";// 随机字符串,不长于32位 string wx_sign = "";//签名,详见签名算法 string wx_result_code = "";//SUCCESS/FAIL string wx_return_code = ""; string wx_openid = "";//用户在商户appid下的唯一标识 string wx_is_subscribe = "";//用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效 string wx_trade_type = "";// APP string wx_bank_type = "";// 银行类型,采用字符串类型的银行标识,银行类型见银行列表 string wx_fee_type = "";// 货币类型,符合ISO4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 string wx_transaction_id = "";//微信支付订单号 string wx_out_trade_no = "";//商户系统的订单号,与请求一致。 string wx_time_end = "";// 支付完成时间,格式为yyyyMMddHHmmss,如2019年12月25日9点10分10秒表示为20191225091010。其他详见时间规则 int wx_total_fee = -1;// 订单总金额,单位为分 int wx_cash_fee = -1;//现金支付金额订单现金支付金额,详见支付金额 #region 数据解析 //列 是否存在 string signstr = "";//需要前面的字符串 //wx_appid if (ds.Tables[0].Columns.Contains("appid")) { wx_appid = ds.Tables[0].Rows[0]["appid"].ToString(); if (!string.IsNullOrEmpty(wx_appid)) { signstr += "appid=" + wx_appid; } } //wx_bank_type if (ds.Tables[0].Columns.Contains("bank_type")) { wx_bank_type = ds.Tables[0].Rows[0]["bank_type"].ToString(); if (!string.IsNullOrEmpty(wx_bank_type)) { signstr += "&bank_type=" + wx_bank_type; } } //wx_cash_fee if (ds.Tables[0].Columns.Contains("cash_fee")) { wx_cash_fee = Convert.ToInt32(ds.Tables[0].Rows[0]["cash_fee"].ToString()); signstr += "&cash_fee=" + wx_cash_fee; } //wx_fee_type if (ds.Tables[0].Columns.Contains("fee_type")) { wx_fee_type = ds.Tables[0].Rows[0]["fee_type"].ToString(); if (!string.IsNullOrEmpty(wx_fee_type)) { signstr += "&fee_type=" + wx_fee_type; } } //wx_is_subscribe if (ds.Tables[0].Columns.Contains("is_subscribe")) { wx_is_subscribe = ds.Tables[0].Rows[0]["is_subscribe"].ToString(); if (!string.IsNullOrEmpty(wx_is_subscribe)) { signstr += "&is_subscribe=" + wx_is_subscribe; } } //wx_mch_id if (ds.Tables[0].Columns.Contains("mch_id")) { wx_mch_id = ds.Tables[0].Rows[0]["mch_id"].ToString(); if (!string.IsNullOrEmpty(wx_mch_id)) { signstr += "&mch_id=" + wx_mch_id; } } //wx_nonce_str if (ds.Tables[0].Columns.Contains("nonce_str")) { wx_nonce_str = ds.Tables[0].Rows[0]["nonce_str"].ToString(); if (!string.IsNullOrEmpty(wx_nonce_str)) { signstr += "&nonce_str=" + wx_nonce_str; } } //wx_openid if (ds.Tables[0].Columns.Contains("openid")) { wx_openid = ds.Tables[0].Rows[0]["openid"].ToString(); if (!string.IsNullOrEmpty(wx_openid)) { signstr += "&openid=" + wx_openid; } } //wx_out_trade_no if (ds.Tables[0].Columns.Contains("out_trade_no")) { wx_out_trade_no = ds.Tables[0].Rows[0]["out_trade_no"].ToString(); if (!string.IsNullOrEmpty(wx_out_trade_no)) { signstr += "&out_trade_no=" + wx_out_trade_no; } } //wx_result_code if (ds.Tables[0].Columns.Contains("result_code")) { wx_result_code = ds.Tables[0].Rows[0]["result_code"].ToString(); if (!string.IsNullOrEmpty(wx_result_code)) { signstr += "&result_code=" + wx_result_code; } } //wx_result_code if (ds.Tables[0].Columns.Contains("return_code")) { wx_return_code = ds.Tables[0].Rows[0]["return_code"].ToString(); if (!string.IsNullOrEmpty(wx_return_code)) { signstr += "&return_code=" + wx_return_code; } } //wx_sign if (ds.Tables[0].Columns.Contains("sign")) { wx_sign = ds.Tables[0].Rows[0]["sign"].ToString(); //if (!string.IsNullOrEmpty(wx_sign)) //{ // signstr += "&sign=" + wx_sign; //} } //wx_time_end if (ds.Tables[0].Columns.Contains("time_end")) { wx_time_end = ds.Tables[0].Rows[0]["time_end"].ToString(); if (!string.IsNullOrEmpty(wx_time_end)) { signstr += "&time_end=" + wx_time_end; } } //wx_total_fee if (ds.Tables[0].Columns.Contains("total_fee")) { wx_total_fee = Convert.ToInt32(ds.Tables[0].Rows[0]["total_fee"].ToString()); signstr += "&total_fee=" + wx_total_fee; } //wx_trade_type if (ds.Tables[0].Columns.Contains("trade_type")) { wx_trade_type = ds.Tables[0].Rows[0]["trade_type"].ToString(); if (!string.IsNullOrEmpty(wx_trade_type)) { signstr += "&trade_type=" + wx_trade_type; } } //wx_transaction_id if (ds.Tables[0].Columns.Contains("transaction_id")) { wx_transaction_id = ds.Tables[0].Rows[0]["transaction_id"].ToString(); if (!string.IsNullOrEmpty(wx_transaction_id)) { signstr += "&transaction_id=" + wx_transaction_id; } } #endregion //追加key 密钥 signstr += "&key=" + System.Web.Configuration.WebConfigurationManager.AppSettings["wx_key"].ToString(); //签名正确 string orderStrwhere = "ordernumber='" + wx_out_trade_no + "'"; if (wx_sign == System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(signstr, "MD5").ToUpper()) { //签名正确 处理订单操作逻辑 } else { //追加备注信息 } } else { // 返回信息,如非空,为错误原因 签名失败 参数格式校验错误 string return_msg = ds.Tables[0].Rows[0]["return_msg"].ToString(); } return_result = sb.ToString(); } } //获得Post过来的数据 public string getPostStr() { Int32 intLen = Convert.ToInt32(Request.InputStream.Length); byte[] b = new byte[intLen]; Request.InputStream.Read(b, 0, intLen); return System.Text.Encoding.UTF8.GetString(b); } } }
Web.config配置代码如下:
<?xml version="1.0" encoding="utf-8"?> <!-- 有关如何配置 ASP.NET 应用程序的详细信息,请访问 http://go.microsoft.com/fwlink/?LinkId=169433 --> <configuration> <system.web> <compilation targetFramework="4.0" /> </system.web> <appSettings> <!--微信开放平台审核通过的应用APPID--> <add key="wx_appid" value="wx100000000000000000000" /> <!--微信支付分配的商户号APPID--> <add key="wx_mch_id" value="10000000000" /> <!--wx_key 微信key 密钥--> <add key="wx_key" value="000000000000000000000000000000" /> <!--wxpay_notifyurl 请求回调服务器网址--> <add key="wxpay_notifyurl" value="http://127.0.0.1:999/notify_url.aspx" /> </appSettings> </configuration> <!--ProjectGuid: {7F7F944F-4BEA-4542-A558-1ADCF0224F9C}-->
提示:前端访问Url格式为http://127.0.0.1:9999/AppServer.aspx?TotalAmount=100&PayDdnom=202222222222222222222222
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库