内容摘要:本案例客户端支付方式为微信小程序支付(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; }
请发表评论