微信小程序支付功能 C# .NET开发
程序员文章站
2022-05-29 10:12:36
微信小程序支付功能的开发的时候坑比较多,不过对于钱的事谨慎也是好事。网上关于小程序支付的实例很多,但是大多多少有些问题,C#开发的更少。此篇文档的目的是讲开发过程中遇到的问题做一个备注,也方便其他开发的同学作为参考! 1、首先建议把官方文档支付部分看上三遍,每个细节都不要放过,因为任何一个点和微信要 ......
微信小程序支付功能的开发的时候坑比较多,不过对于钱的事谨慎也是好事。网上关于小程序支付的实例很多,但是大多多少有些问题,c#开发的更少。此篇文档的目的是讲开发过程中遇到的问题做一个备注,也方便其他开发的同学作为参考!
1、首先建议把官方文档支付部分看上三遍,每个细节都不要放过,因为任何一个点和微信要求不符都会导致支付不成功。https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=3_1
2、经过验证的微信支付功能,会需要一些商户号、支付秘钥等,不要搞混。
3、经常遇到的是“签名错误”,请仔细看需要传送的xml参数及取值规则是否符合微信规则。微信有个验证工具可以验证发送的xml字段是否合法。
下面上代码:
web.config
<add key="connectionstring" value="server=127.0.0.1;database=;uid=sa;pwd="/> <add key="connectionstring2" value="server=127.0.0.1;database=codematic2;uid=sa;pwd=1"/> <add key="appid" value=""/>//appid <add key="secret" value=""/>//小程序秘钥 <add key="mch_id" value=""/>//商户号 <add key="key" value=""/>//支付秘钥 <add key="ip" value=""/>//服务器ip <add key="payresulturl" value=""/>//微信返回接收信息的url地址 </appsettings>
支付后台xiadan.ashx
<%@ webhandler language="c#" class="xiadan" %> using system; using system.web; using system.net; using system.io; using system.configuration; using maticsoft.model; using maticsoft.bll; using system.security.cryptography; using system.text; using system.xml.serialization; using system.xml; using system.collections.generic; using system.data; using system.net.security; using system.security.cryptography.x509certificates; using system.linq; using newtonsoft.json; public class xiadan : ihttphandler { public void processrequest(httpcontext context) { context.response.contenttype = "text/plain"; string openid = context.request.params["openid"]; string ordertime = context.request.params["ordertime"]; string appid = configurationmanager.appsettings["appid"]; string secret = configurationmanager.appsettings["secret"]; string key = configurationmanager.appsettings["key"]; string mch_id = configurationmanager.appsettings["mch_id"]; string ip = configurationmanager.appsettings["ip"]; string payresulturl = configurationmanager.appsettings["payresulturl"]; string roomid = context.request.params["roomid"]; string aa = "-押金";////商品描述交易字段格式根据不同的应用场景按照以下格式:app——需传入应用市场上的app名字-实际商品名称,天天爱消除-游戏充值。 string strcode = aa; byte[] buffer = encoding.utf8.getbytes(strcode); string body = encoding.utf8.getstring(buffer, 0, buffer.length); string totalfee = context.request.params["totalfee"]; string output = ""; if ((context.request.params["openid"] != null) && (context.request.params["openid"] != "")) { //orderinfo order = new orderinfo(); //order.appid = appid; system.random random = new system.random(); var dic = new dictionary<string, string> { {"appid", appid}, {"mch_id", mch_id}, {"nonce_str", getrandomstring(20)/*random.next().tostring()*/}, {"body",body}, {"out_trade_no",roomid + datetime.now.tostring("yyyymmddhhmmssfff") + random.next(999).tostring()},//商户自己的订单号码 {"total_fee",totalfee}, {"spbill_create_ip",ip},//服务器的ip地址 {"notify_url",payresulturl},//异步通知的地址,不能带参数 {"trade_type","jsapi" }, {"openid",openid} }; //加入签名 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", appid}, {"timestamp", gettimestamp()}, {"noncestr", dic["nonce_str"]}, {"package", "prepay_id="+ds.tables[0].rows[0]["prepay_id"].tostring()}, {"signtype", "md5"} }; //在服务器上签名 res.add("paysign", getsignstring(res)); // string signapp = res.tostring(); string signapp = jsonconvert.serializeobject(res); if ((context.request.params["openid"] != null) && (context.request.params["openid"] != "")) { //存储订单信息 maticsoft.model.order_history oh = new maticsoft.model.order_history(); //oh.shop_id = oh.room_id = convert.toint32(roomid); oh.pay_price = convert.todecimal(totalfee); oh.out_trade_no = dic["out_trade_no"]; oh.order_timestart = convert.todatetime(ordertime); oh.openid = openid; oh.creating_date = datetime.now; maticsoft.bll.order_history bll = new maticsoft.bll.order_history(); bll.add(oh); } context.response.write(signapp); } } } context.response.write(output); } public bool isreusable { get { return false; } } public string getmd5hash(string input) { if (input == null) { return null; } md5 md5hash = md5.create(); // 将输入字符串转换为字节数组并计算哈希数据 byte[] data = md5hash.computehash(encoding.utf8.getbytes(input)); // 创建一个 stringbuilder 来收集字节并创建字符串 stringbuilder sbuilder = new stringbuilder(); // 循环遍历哈希数据的每一个字节并格式化为十六进制字符串 for (int i = 0; i < data.length; i++) { sbuilder.append(data[i].tostring()); } // 返回十六进制字符串 return sbuilder.tostring(); } /// <summary> /// 对象序列化成 xml string /// </summary> public static string xmlserialize<t>(t obj) { string xmlstring = string.empty; xmlserializer xmlserializer = new xmlserializer(typeof(t)); using (memorystream ms = new memorystream()) { xmlserializer.serialize(ms, obj); xmlstring = encoding.utf8.getstring(ms.toarray()); } return xmlstring; } /// <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["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 // sign = system.web.security.formsauthentication.hashpasswordforstoringinconfigfile(sign, "md5").toupper(); system.security.cryptography.md5 md5 = system.security.cryptography.md5.create(); sign = bitconverter.tostring(md5.computehash(encoding.utf8.getbytes(sign))).replace("-", null); 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.ashx
<%@ webhandler language="c#" class="notify_url" %> using system; using system.web; using system.collections.generic; using system.data; using system.io; using system.text; using system.xml; using system.net; public class notify_url : ihttphandler { public string return_result = ""; public void processrequest(httpcontext context) { context.response.contenttype = "text/plain"; context.response.write("hello world"); 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,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 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["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(); } } public bool isreusable { get { return false; } } //获得post过来的数据 public string getpoststr() { int32 intlen = convert.toint32(system.web.httpcontext.current.request.inputstream.length); byte[] b = new byte[intlen]; system.web.httpcontext.current.request.inputstream.read(b, 0, intlen); return system.text.encoding.utf8.getstring(b); } }