.Net后台实现微信小程序支付
最近一直再研究微信支付和支付宝支付,官方支付文档中一直在讲与第三方支付打交道的原理,却没有介绍我们自己项目中的APP与后台该怎么交互(哈哈,人家也没必要介绍这一块)。拜读了官方文档和前辈们的佳作,自己在这里做一些总结。
不管是微信支付还是支付宝支付,使用的是小程序、APP或网页都可以用以下示例图来说明。
支付流程:
① 支付端将订单号(小程序中还需要传递登录凭证)传递至后台商户。
②后台验证订单、统计订单总价,请求第三方获取下单参数。
③第三方返回下单参数。
④后台将从第三方返回的参数按需要返回至支付端。
⑤支付端拿着后台返回的参数下单。
⑥第三方返回支付结果。
⑦支付成功后,第三方发起支付回调通知商户后台,在这一步,商户可在回调中修改订单以及用户的相关支付状态。
微信小程序支付:
先放一张官方的图。
具体实现:
新建App.Pay项目,在新项目中新建Log类,记录操作过程中的日志。
1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 using System.Web; 8 9 namespace App.Pay 10 { 11 public class Log 12 { 13 //在网站根目录下创建日志目录 14 public string path; 15 16 public Log(string path) 17 { 18 this.path = HttpContext.Current.Request.PhysicalApplicationPath + path; 19 } 20 /** 21 * 向日志文件写入调试信息 22 * @param className 类名 23 * @param content 写入内容 24 */ 25 public void Debug(string className, string content) 26 { 27 WriteLog("DEBUG", className, content); 28 } 29 30 /** 31 * 向日志文件写入运行时信息 32 * @param className 类名 33 * @param content 写入内容 34 */ 35 public void Info(string className, string content) 36 { 37 WriteLog("INFO", className, content); 38 } 39 40 /** 41 * 向日志文件写入出错信息 42 * @param className 类名 43 * @param content 写入内容 44 */ 45 public void Error(string className, string content) 46 { 47 WriteLog("ERROR", className, content); 48 } 49 50 /** 51 * 实际的写日志操作 52 * @param type 日志记录类型 53 * @param className 类名 54 * @param content 写入内容 55 */ 56 protected void WriteLog(string type, string className, string content) 57 { 58 if (!Directory.Exists(path))//如果日志目录不存在就创建 59 { 60 Directory.CreateDirectory(path); 61 } 62 63 string time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");//获取当前系统时间 64 string filename = path + "/" + DateTime.Now.ToString("yyyy-MM-dd") + ".log";//用日期对日志文件命名 65 66 //创建或打开日志文件,向日志文件末尾追加记录 67 StreamWriter mySw = File.AppendText(filename); 68 69 //向日志文件写入内容 70 string write_content = time + " " + type + " " + className + ": " + content; 71 mySw.WriteLine(write_content); 72 73 //关闭日志文件 74 mySw.Close(); 75 } 76 } 77 }
新建WePay文件夹,新建Config基类,存放微信支付的公共配置参数。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace App.Pay.WePay 8 { 9 /** 10 * 配置账号信息 11 */ 12 public class WePayConfig 13 { 14 //=======【商户系统后台机器IP】===================================== 15 /* 此参数可手动配置也可在程序中自动获取 16 */ 17 public const string IP = "8.8.8.8"; 18 19 20 //=======【代理服务器设置】=================================== 21 /* 默认IP和端口号分别为0.0.0.0和0,此时不开启代理(如有需要才设置) 22 */ 23 public const string PROXY_URL = ""; 24 25 //=======【上报信息配置】=================================== 26 /* 测速上报等级,0.关闭上报; 1.仅错误时上报; 2.全量上报 27 */ 28 public const int REPORT_LEVENL = 1; 29 30 //=======【日志级别】=================================== 31 /* 日志等级,0.不输出日志;1.只输出错误信息; 2.输出错误和正常信息; 3.输出错误信息、正常信息和调试信息 32 */ 33 public const int LOG_LEVENL = 3; 34 } 35 }
新建Exception类,捕获微信支付过程中的异常。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace App.Pay.WePay 8 { 9 public class WePayException : Exception 10 { 11 public WePayException(string msg) : base(msg) 12 { 13 14 } 15 } 16 }
新建SafeXMLDocument类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Xml; 7 8 namespace App.Pay.WePay 9 { 10 public class SafeXmlDocument : XmlDocument 11 { 12 public SafeXmlDocument() 13 { 14 this.XmlResolver = null; 15 } 16 } 17 }
新建WeHelper类,目前只有一个方法,微信小程序支付中将登录凭证转换为openId。
1 using App.Common.Extension; 2 using Newtonsoft.Json.Linq; 3 using System; 4 using System.Collections.Generic; 5 using System.Configuration; 6 using System.Linq; 7 using System.Net; 8 using System.Security.Cryptography; 9 using System.Text; 10 using System.Threading.Tasks; 11 12 namespace App.Pay.WePay 13 { 14 public class WeHelper 15 { 16 // 小程序 17 private static string _appid = ConfigurationManager.AppSettings["wxAPPID"]; 18 // 小程序 19 private static string _appSecret = ConfigurationManager.AppSettings["wxAppSecret"]; 20 21 public static WxSession Code2Session(string code) 22 { 23 var url = $"https://api.weixin.qq.com/sns/jscode2session?appid={_appid}&secret={_appSecret}&js_code={code}&grant_type=authorization_code"; 24 try 25 { 26 var request = WebRequest.Create(url); 27 using (var response = request.GetResponse()) 28 { 29 using (var rs = response.GetResponseStream()) 30 { 31 using (var s = new System.IO.StreamReader(rs)) 32 { 33 return s.ReadToEnd().JsonTo<WxSession>(); 34 } 35 } 36 } 37 } 38 catch (Exception) 39 { 40 return null; 41 } 42 } 43 } 44 45 public class WxSession 46 { 47 public string openid { get; set; } 48 public string session_key { get; set; } 49 public string errcode { get; set; } 50 public string errMsg { get; set; } 51 public string unionid { get; set; } 52 } 53 54 }
以上四个类是微信支付通用的,因此统一放在了微信支付文件夹下。
新建XcxPay文件夹,用于存放微信小程序支付的文件,新建XcxPayConfig类,存放关于小程序支付参数,小程序APPID、账号Secert、商户号、商户支付密钥、支付回调地址。我把这些参数值都放在了解决方案的config配置文件中。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Web.Configuration; 7 8 namespace App.Pay.WePay.XcxPay 9 { 10 public class XcxPayConfig : WePayConfig 11 { 12 //=======【基本信息设置】===================================== 13 /* 微信公众号信息配置 14 * APPID:绑定支付的APPID(必须配置) 15 * MCHID:商户号(必须配置) 16 * KEY:商户支付密钥,参考开户邮件设置(必须配置) 17 * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置) 18 */ 19 /// 小程序支付 20 public static string APPID = WebConfigurationManager.AppSettings["XcxAppID"].ToString(); 21 public static string MCHID = WebConfigurationManager.AppSettings["XcxMchID"].ToString(); 22 public static string KEY = WebConfigurationManager.AppSettings["XcxKey"].ToString(); 23 public static string APPSECRET = WebConfigurationManager.AppSettings["XcxAppSecret"].ToString(); 24 25 //=======【证书路径设置】===================================== 26 /* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要) 27 */ 28 public const string SSLCERT_PATH = "cert/apiclient_cert.p12"; 29 public const string SSLCERT_PASSWORD = "1233410002"; 30 31 //=======【支付结果通知url】===================================== 32 /* 支付结果通知回调url,用于商户接收支付结果 33 */ 34 public static string NOTIFY_URL = WebConfigurationManager.AppSettings["XcxNotifyUrl"].ToString(); 35 36 // log记录 37 public static string LogPath = WebConfigurationManager.AppSettings["XcxLog"].ToString(); 38 } 39 }
<!--小程序支付--> <add key="XcxAppID" value="" /> <add key="XcxAppSecret" value="" /> <add key="XcxMchID" value="" /> <add key="XcxKey" value="" /> <!--回调通知--> <add key="XcxNotifyUrl" value="" />
新建WeXcxPayApi类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace App.Pay.WePay.XcxPay 8 { 9 public class XcxPayApi 10 { 11 public static Log Log = new Log(XcxPayConfig.LogPath); 12 13 /** 14 * 提交被扫支付API 15 * 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台, 16 * 由商户收银台或者商户后台调用该接口发起支付。 17 * @param WxPayData inputObj 提交给被扫支付API的参数 18 * @param int timeOut 超时时间 19 * @throws WePayException 20 * @return 成功时返回调用结果,其他抛异常 21 */ 22 public static XcxPayData Micropay(XcxPayData inputObj, int timeOut = 10) 23 { 24 string url = "https://api.mch.weixin.qq.com/pay/micropay"; 25 //检测必填参数 26 if (!inputObj.IsSet("body")) 27 { 28 throw new WePayException("提交被扫支付API接口中,缺少必填参数body!"); 29 } 30 else if (!inputObj.IsSet("out_trade_no")) 31 { 32 throw new WePayException("提交被扫支付API接口中,缺少必填参数out_trade_no!"); 33 } 34 else if (!inputObj.IsSet("total_fee")) 35 { 36 throw new WePayException("提交被扫支付API接口中,缺少必填参数total_fee!"); 37 } 38 else if (!inputObj.IsSet("auth_code")) 39 { 40 throw new WePayException("提交被扫支付API接口中,缺少必填参数auth_code!"); 41 } 42 43 inputObj.SetValue("spbill_create_ip", WePayConfig.IP);//终端ip 44 inputObj.SetValue("appid", XcxPayConfig.APPID);//公众账号ID 45 inputObj.SetValue("mch_id", XcxPayConfig.MCHID);//商户号 46 inputObj.SetValue("nonce_str", Guid.NewGuid().ToString().Replace("-", ""));//随机字符串 47 inputObj.SetValue("sign", inputObj.MakeSign());//签名 48 string xml = inputObj.ToXml(); 49 50 var start = DateTime.Now;//请求开始时间 51 52 Log.Info("XcxPayApi", "MicroPay request : " + xml); 53 string response = XcxPayHttpService.Post(xml, url, false, timeOut);//调用HTTP通信接口以提交数据到API 54 Log.Info("XcxPayApi", "MicroPay response : " + response); 55 56 var end = DateTime.Now; 57 int timeCost = (int)((end - start).TotalMilliseconds);//获得接口耗时 58 59 //将xml格式的结果转换为对象以返回 60 XcxPayData result = new XcxPayData(); 61 result.FromXml(response); 62 63 ReportCostTime(url, timeCost, result);//测速上报 64 65 return result; 66 } 67 68 69 /** 70 * 71 * 查询订单 72 * @param WxPayData inputObj 提交给查询订单API的参数 73 * @param int timeOut 超时时间 74 * @throws WePayException 75 * @return 成功时返回订单查询结果,其他抛异常 76 */ 77 public static XcxPayData OrderQuery(XcxPayData inputObj, int timeOut = 6) 78 { 79 string url = "https://api.mch.weixin.qq.com/pay/orderquery"; 80 //检测必填参数 81 if (!inputObj.IsSet("out_trade_no") && !inputObj.IsSet("transaction_id")) 82 { 83 throw new WePayException("订单查询接口中,out_trade_no、transaction_id至少填一个!"); 84 } 85 86 inputObj.SetValue("appid", XcxPayConfig.APPID);
请发表评论