內容摘要:本案例客戶端支付方式為微信小程序支付(JSAPI)。商戶運營一段時間后,在微信商戶平台開通企業支付服務后,即可調用微信支付提供的企業付款接口將佣金等金額通過微信零錢返現給C端用戶零錢。
服務端開發環境:.NET MVC 開發語言C#;
一、准備工作:
1、微信商戶平台企業付款服務的開通;開通規則如下:
a、 商戶號(或同主體其他非服務商商戶號)已入駐90日;
b、截止今日回推30天,商戶號(或同主體其他非服務商商戶號)連續不間斷保持有交易;
c、所有交易必須為真實交易,最好是金額在10元以上,本人的開通經歷為:開通日反推30天,每日有正常的購買訂單支付流水,期滿30天后,商戶平台企業支付選項出現,點擊開通即可使用此服務;
2、微信公眾平台下載商戶證書(證書下載參考路徑:微信商戶平台(pay.weixin.qq.com)-->賬戶中心-->賬戶設置-->API安全),並將證書上傳至目標服務器(本人為阿里雲ECS服務器,IIS 10.0);
3、微信支付相關數據:微信商戶號、微信商戶密鑰等等
二、官方文檔參考路徑(https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=1_2)
三、查詢企業付款接口
1、企業支付接口路徑:https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers
2、企業查詢支付結果接口路徑:https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo
企業發起付款到用戶零錢
/// <summary> /// 企業付款至用戶微信零錢 /// </summary> /// <param name="openId">待付款的用戶openId</param> /// <param name="applyNo">提現訂單號,系統內保持唯一</param> /// <param name="price">提現金額,單位為“分”</param> /// <returns></returns> public JsonResult EnterprisePayToStaffWechatChange(string openId, string applyNo, string price) { Result result = new Result(); try { #region 參數准備 Dictionary<string, string> parameters = new Dictionary<string, string>(); // 支付金額 parameters.Add("amount", price); // 是否強制校驗用戶真是姓名 parameters.Add("check_name", WxPayModel.NOCheckRealName); // 企業付款備注 parameters.Add("desc", WxPayModel.PayDescription); // 小程序APPID parameters.Add("mch_appid", WxPayModel.AppID); // 微信商戶號 parameters.Add("mchid", WxPayModel.mchid); // 隨機32位字符串 parameters.Add("nonce_str", WxPayModel.nonceStr); // 用戶openid parameters.Add("openid", openId); // 商戶訂單號 parameters.Add("partner_trade_no", applyNo + ""); // 企業付款IP地址,當前商家接口服務所在IP地址 parameters.Add("spbill_create_ip", WxPayModel.EnterpriseIPAddress); // 簽名信息 WxPayModel.WxMerchantKey為獲取微信商戶平台密鑰 parameters.Add("sign", WxPayCore.GetSignInfo(parameters, WxPayModel.WxMerchantKey)); #endregion #region 向微信提交付款申請,獲取返回值,並解析返回值 // 記錄一些日志信息... // 獲取接口返回xml字符串,WxPayModel.EnterpriseWxPay為企業支付接口地址,即:https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers string return_xml_msg = WxPayCore.PostHttpResponseWithCertificate(WxPayModel.EnterpriseWxPay, WxPayCore.CreateXmlParam(parameters)); // 獲取提交狀態 string return_code = WxPayCore.GetXmlValue(return_xml_msg, "return_code"); // 獲取返回信息 string return_msg = WxPayCore.GetXmlValue(return_xml_msg, "return_msg"); // 獲取商戶appid string mch_appid = WxPayCore.GetXmlValue(return_xml_msg, "mch_appid"); // 獲取商戶號 string mchid = WxPayCore.GetXmlValue(return_xml_msg, "mchid"); // 獲取隨機數 string nonce_str = WxPayCore.GetXmlValue(return_xml_msg, "nonce_str"); // 獲取業務結果 string result_code = WxPayCore.GetXmlValue(return_xml_msg, "result_code"); // 獲取業務結果錯誤代碼 string err_code = WxPayCore.GetXmlValue(return_xml_msg, "err_code"); // 獲取業務結果錯誤描述 string err_code_des = WxPayCore.GetXmlValue(return_xml_msg, "err_code_des"); // 獲取業務結果商戶訂單號 string partner_trade_no = WxPayCore.GetXmlValue(return_xml_msg, "partner_trade_no"); // 獲取業務結果微信付款單號 string payment_no = WxPayCore.GetXmlValue(return_xml_msg, "payment_no"); // 獲取業務結果微信付款成功時間 string payment_time = WxPayCore.GetXmlValue(return_xml_msg, "payment_time"); // 針對提交結果進行判斷,是否成功提交至微信后台 if (return_code == "SUCCESS") { // 記錄一些日志信息.... // 微信是否返回付款成功業務處理結果 if (result_code == "SUCCESS") { // 記錄一些日志信息....
result.ResultCode = Infrastructure.Enum.ReusltCode.OK; result.ResultMsg = "提現申請已受理,款項預計1-2個工作日到賬,請提醒相關人員注意核對!"; } else { // 記錄一些日志信息....
result.ResultCode = Infrastructure.Enum.ReusltCode.Fail; result.ResultMsg = "微信支付業務處理錯誤,錯誤代碼:【" + err_code + "】,錯誤信息:【" + err_code_des + "】"; } } else { // 記錄一些日志信息....
result.ResultCode = Infrastructure.Enum.ReusltCode.Fail; result.ResultMsg = return_msg; } #endregion } catch (Exception ex) { // 記錄一些日志信息.... result.ResultCode = Infrastructure.Enum.ReusltCode.OK; result.ResultMsg = ex.Message; } return new JsonResult(result); }
當然,企業操作打款成功后,需要校驗本次業務操作時候已完成,此時,需要使用查詢企業付款API
/// <summary> /// 查詢企業是否成功付款至用戶微信零錢 /// </summary> /// <param name="businessNo">提現訂單號</param> /// <returns></returns> public static JsonResult GetTransferinfo(string businessNo) { Result result = new Result(); try { #region 參數准備 Dictionary<string, string> parameters = new Dictionary<string, string>(); // 小程序ID parameters.Add("appid", WxPayModel.AppID); // 商戶號 parameters.Add("mch_id", WxPayModel.mchid); // 隨機32位字符串 parameters.Add("nonce_str", WxPayModel.nonceStr); // 查詢單號 parameters.Add("partner_trade_no", TZR_WxWithdrawalNo); // 簽名信息 parameters.Add("sign", WxPayCore.GetSignInfo(parameters, WxPayModel.WxMerchantKey)); #endregion #region 向微信提交查詢申請,獲取返回值,並解析返回值 // 獲取接口返回xml字符串 string return_xml_msg = WxPayCore.PostHttpResponseWithCertificate(WxPayModel.EnterpriseWxPayResultQuery, WxPayCore.CreateXmlParam(parameters),1); // 獲取提交狀態 string return_code = WxPayCore.GetXmlValue(return_xml_msg, "return_code"); // 獲取返回信息 string return_msg = WxPayCore.GetXmlValue(return_xml_msg, "return_msg"); // 獲取業務結果 string result_code = WxPayCore.GetXmlValue(return_xml_msg, "result_code"); // 獲取業務結果錯誤代碼 string err_code = WxPayCore.GetXmlValue(return_xml_msg, "err_code"); // 獲取業務結果錯誤描述 string err_code_des = WxPayCore.GetXmlValue(return_xml_msg, "err_code_des"); // 加入一些日志信息 if(return_code == "SUCCESS" && result_code == "SUCCESS") { // 獲取業務結果商戶訂單號 string partner_trade_no = WxPayCore.GetXmlValue(return_xml_msg, "partner_trade_no"); // 獲取商戶appid string mch_appid = WxPayCore.GetXmlValue(return_xml_msg, "appid"); // 獲取商戶號 string mchid = WxPayCore.GetXmlValue(return_xml_msg, "mch_id"); // 獲取付款單號 string detail_id = WxPayCore.GetXmlValue(return_xml_msg, "detail_id"); // 獲取付款單號 string status = WxPayCore.GetXmlValue(return_xml_msg, "status"); // 獲取失敗原因 string reason = WxPayCore.GetXmlValue(return_xml_msg, "reason"); // 獲取收款用戶openid string openid = WxPayCore.GetXmlValue(return_xml_msg, "openid"); // 獲取收款用戶姓名 string transfer_name = WxPayCore.GetXmlValue(return_xml_msg, "transfer_name"); // 獲取付款金額(付款金額單位為“分”) string payment_amount = WxPayCore.GetXmlValue(return_xml_msg, "payment_amount"); // 獲取轉賬時間 string transfer_time = WxPayCore.GetXmlValue(return_xml_msg, "transfer_time"); // 獲取付款成功時間 string payment_time = WxPayCore.GetXmlValue(return_xml_msg, "payment_time"); // 獲取企業付款備注 string desc = WxPayCore.GetXmlValue(return_xml_msg, "desc"); //獲取付款單號 string statusName = "處理中"; //判斷狀態 if(status== "SUCCESS") { // 加入一些日志信息 statusName = "成功,到賬時間:"+ payment_time; } else if (status == "FAILED") { // 加入一些日志信息 statusName = "轉賬失敗,原因:"+ reason; } else { // 加入一些日志信息 statusName = "處理中,請耐心等待,提交時間:" + transfer_time; } //處理金額 decimal price = 0M; decimal.TryParse(payment_amount, out price); price = price / 100; // 微信是否返回付款成功業務處理結果 if (result_code == "SUCCESS") { // 加入一些日志信息 result.ResultCode = Infrastructure.Enum.ReusltCode.OK; result.ResultMsg = "查詢成功,訂單【"+ partner_trade_no + "】,金額【"+ price + "】,提現"+ statusName; } else { // 加入一些日志信息 result.ResultCode = Infrastructure.Enum.ReusltCode.Fail; result.ResultMsg = "微信支付業務處理錯誤,錯誤代碼:【" + err_code + "】,錯誤信息:【" + err_code_des + "】"; } } else { // 加入一些日志信息 result.ResultCode = Infrastructure.Enum.ReusltCode.Fail; result.ResultMsg = return_msg; } #endregion } catch (Exception ex) { // 加入一些日志信息 result.ResultCode = Infrastructure.Enum.ReusltCode.OK; result.ResultMsg = ex.Message; } return new JsonResult(result); }
至此,企業付款到用戶零錢業務基本走完。
其實,微信還開放了付款至用戶銀行卡服務接口,操作流程與上述流程大同小異,在此不再贅述。
四、公共方法
字典轉換XML數據 (拼接成微信要求的XML請求數據格式)
/// <summary> /// 集合轉換XML數據 (拼接成XML請求數據) /// </summary> /// <param name="strParam">參數</param> /// <returns></returns> public static string CreateXmlParam(Dictionary<string, string> strParam) { StringBuilder sb = new StringBuilder(); try { sb.Append("<xml>"); foreach (KeyValuePair<string, string> k in strParam) { if (k.Key == "attach" || k.Key == "body" || k.Key == "sign") { sb.Append("<" + k.Key + "><![CDATA[" + k.Value + "]]></" + k.Key + ">"); } else { sb.Append("<" + k.Key + ">" + k.Value + "</" + k.Key + ">"); } } sb.Append("</xml>"); } catch (Exception ex) { throw ex; // AddLog("PayHelper", "CreateXmlParam", ex.Message, ex); } var a = sb.ToString(); return sb.ToString(); }
生成隨機32位字符串
/// <summary> /// 隨機字符串不長於 32 位 /// </summary> public static string nonceStr = RandomNum.CreateRandomNum(32).ToUpper(); /// <summary> /// 生產隨機數 /// </summary> /// <param name="NumCount"></param> /// <returns></returns> public static string CreateRandomNum(int NumCount) { string allChar = "2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,J,K,P,Q,R,S,T,U,W,X,Y,Z,a,b,c,d,e,f,g,h,j,k,m,n,o,p,q,s,t,u,w,x,y,z"; string[] allCharArray = allChar.Split(',');//拆分成數組 string randomNum = ""; int temp = -1; //記錄上次隨機數的數值,盡量避免產生幾個相同的隨機數 Random rand = new Random(); for (int i = 0; i < NumCount; i++) { if (temp != -1) { rand = new Random(i * temp * ((int)DateTime.Now.Ticks)); } int t = rand.Next(35); if (temp == t) { return CreateRandomNum(NumCount); } temp = t; randomNum += allCharArray[t]; } return randomNum; }
獲取簽名
/// <summary> /// 獲取簽名數據 ///</summary> /// <param name="strParam">支付參數</param> /// <param name="key">商戶密鑰</param> /// <returns></returns> public static string GetSignInfo(Dictionary<string, string> strParam, string key) { int i = 0; string sign = string.Empty; StringBuilder sb = new StringBuilder(); try { foreach (KeyValuePair<string, string> temp in strParam) { if (temp.Value == "" || temp.Value == null || temp.Key.ToLower() == "sign") { continue; } i++; sb.Append(temp.Key.Trim() + "=" + temp.Value.Trim() + "&"); } sb.Append("key=" + key.Trim() + ""); sign = CryptoService.Md5EncryptStr(sb.ToString()).ToUpper(); } catch (Exception ex) { throw ex; } return sign; }
POST方式向微信服務器提交數據
/// <summary> /// post提交至微信服務器(需要微信API證書) /// </summary> /// <param name="url">微信企業支付路徑</param> /// <param name="xmlParam">服務提交的簽名參數</param> /// <returns></returns> public static string PostHttpResponseWithCertificate(string url, string xmlParam) { //微信API證書相對路徑 string SSLCERT_PATH = ""; string SSLCERT_PASSWORD = ""; //微信API證書相對路徑 SSLCERT_PATH = WxPayModel.SSLCERT_PATH; //垃圾回收,回收沒有正常關閉的http連接 System.GC.Collect(); //微信服務器返回結果 string result = ""; //HTTP請求對象 HttpWebRequest request = null; //HTTP響應對象 HttpWebResponse response = null; //數據流對象 Stream reqStream = null; try { //設置最大連接數 ServicePointManager.DefaultConnectionLimit = 200; //設置https驗證方式,是否為https請求 if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase)) { ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult); } /*************************************************************** * 下面設置HttpWebRequest的相關屬性 * ************************************************************/ request = (HttpWebRequest)WebRequest.Create(url); request.Method = "POST"; //設置超時時間 默認時長為100秒(100,000 ms) request.Timeout = 60000; //設置POST的數據類型和長度 request.ContentType = "text/xml"; byte[] data = System.Text.Encoding.UTF8.GetBytes(xmlParam); request.ContentLength = data.Length; //使用證書 string path = HttpContext.Current.Request.PhysicalApplicationPath; X509Certificate2 cert = new X509Certificate2(SSLCERT_PATH, SSLCERT_PASSWORD, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet); //添加證書憑據 request.ClientCertificates.Add(cert); //往服務器寫入數據 reqStream = request.GetRequestStream(); reqStream.Write(data, 0, data.Length); reqStream.Close(); //獲取服務端返回 response = (HttpWebResponse)request.GetResponse(); //獲取服務端返回數據 StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8); result = sr.ReadToEnd().Trim(); sr.Close(); } catch (System.Threading.ThreadAbortException e) { System.Threading.Thread.ResetAbort(); } catch (WebException e) { throw new Exception(e.ToString()); } catch (Exception e) { throw new Exception(e.ToString()); } finally { //關閉連接和流 if (response != null) { response.Close(); } if (request != null) { request.Abort(); } } return result; }
解析微信返回的XML對象
/// <summary> /// 獲取XML值 /// </summary> /// <param name="strXml">XML字符串</param> /// <param name="strData">節點值</param> /// <returns></returns> public static string GetXmlValue(string strXml, string strData) { string xmlValue = string.Empty; XmlDocument xmlDocument = new XmlDocument(); xmlDocument.LoadXml(strXml); var selectSingleNode = xmlDocument.DocumentElement.SelectSingleNode(strData); if (selectSingleNode != null) { xmlValue = selectSingleNode.InnerText; } return xmlValue; }