微信小程式支付--企業付款到使用者微信零錢
內容摘要:本案例客戶端支付方式為微信小程式支付(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; }
&n