微信商户平台【现金红包】和【企业支付】的一些总结

1、背景介绍php

项目中须要开发一个经过微信红包提现的功能,调查一下,目前已经简单实现了功能。如今总结一下开发过程当中遇到的一些问题。html

红包提现有两种场景:数据库

场景一:使用微信的【现金红包】功能  https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_1小程序

好处:不用本身开发红包功能,直接调用微信的API就能够完成红包的发放,只要用户关注公众号具备了惟一可识别的OpenId,而且公众号与商户平台绑定,就能够发放红包给用户,而且在公众号内会显示红包的图片,点击便可领取。微信小程序

坏处:必需要关注公众号,若是是微信小程序或者其余应用想要发放红包,并且并不想让用户先关注公众号,这时候这种场景就不适合了。api

 

场景二:使用微信的【企业付款】功能  https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1数组

好处:不须要用户关注公众号,只要用户登陆微信小程序生成了一个惟一的OpenId,而且小程序和商户平台进行了绑定,就能够经过OpenId付款给用户,而且直接进入到用户的零钱当中。安全

坏处:若是想让用户有点击红包的快感,须要本身在小程序或者APP内实现,有必定的开发成本微信

 

下面我就简单介绍一下两种方式是如何实现的。cookie

 

2、实现

1. 准备工做

①须要一个或多个测试用的微信帐号,这个本身注册或者向别人索要就好了。

②须要一个公众号或者小程序(换句话说,须要他们的wxAppId)

③须要一个商户平台,申请的话比较繁琐,能够查看相关的文档进行。

④商户平台须要开通【现金红包】、【企业支付功能】这个很是重要。

 

2.流程

过程1:首先用户须要在微信公众号进行关注或者登陆微信小程序,此时咱们能够得到到用户的OpenId,而后将该用户的OpenId、WeAppId以及咱们本身应用的用户惟一标识进行保存。

过程2:用户在咱们本身的应用或者小程序中领取红包,首先根据上面过程1绑定的关系,可以得到该用户的OpenId,而后就能够调用微信商户平台的接口发放红包或者零钱了,具体的实现下面会详细说。

3、编码(代码作过删减,只保留的一些关键方法)

有了基本的流程和微信的接口文档,咱们就能够去实现这个红包功能了,首先以微信【现金红包-普通红包】为例。

①对于微信红包而言,咱们能够将一些不变的信息做为配置信息保存到配置文件当中,其余的信息例如祝福语、数量、金额等等均可以在做为请求的参数。例以下面的配置文件:

其中有几项是在咱们发送红包构建发放的参数的时候使用的,

I.商户支付的密钥PayKey:是在构建参数sign的时候加在字符串末尾的,这个后面会讲到。这个的设置是在商户后台【key设置路径:微信商户平台(pay.weixin.qq.com)-->帐户设置-->API安全-->密钥设置】

II.证书的地址和证书的密码:这两个是在构建发放请求参数的时候添加证书时候使用的。此外证书的默认密码是商户号。

②构建发放的参数,上面说道构建sign,这个应该是整个调用过程当中的关键了,咱们能够将输入的参数包装成类以下:

public class PayRedPack { // 随机字符串,不长于32位
    public string NonceStr { get; set; } // 签名
    public string Sign { get; set; } // 商户订单号(每一个订单号必须惟一)组成:mch_id+yyyymmdd+10位一天内不能重复的数字。 // 接口根据商户订单号支持重入,如出现超时可再调用。
    public string MchBillno { get; set; } // 微信支付分配的商户号
    public string MchId { get; set; } // 公众帐号appid
    public string WxAppid { get; set; } // 商户名称
    public string SendName { get; set; } // 用户openid
    public string ReOpenId { get; set; } // 付款金额 付款金额,单位分
    public int TotalAmount { get; set; } //红包发放总人数
    public int TotalNum { get; set; } // 红包祝福语
    public string Wishing { get; set; } // Ip地址
    public string ClientIp { get; set; } // 活动名称
    public string ActName { get; set; } // 备注
    public string Remark { get; set; } // 场景id
    public string SceneId { get; set; } // 活动信息
    public string RiskInfo { get; set; } // 资金受权商户号
    public string ConsumeMchId { get; set; } }

②构建发放的参数,上面说道构建sign,这个应该是整个调用过程当中的关键了,咱们能够将输入的参数包装成类以下:

而后根据必要的信息生成签名、构建请求的数据

public const string NOTICE_STR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public const string RANDOM_STR = "0123456789"; //发放红包待构建的xml数据
public const string SEND_REDPACK_XML_STR = @"<xml> <mch_billno>{0}</mch_billno> <mch_id>{1}</mch_id> <wxappid>{2}</wxappid> <send_name>{3}</send_name> <re_openid>{4}</re_openid> <total_amount>{5}</total_amount> <total_num>{6}</total_num> <wishing>{7}</wishing> <client_ip>{8}</client_ip> <act_name>{9}</act_name> <remark>{10}</remark> <nonce_str>{11}</nonce_str> <sign>{12}</sign> </xml>";

生成请求参数的方法以下:

//生成请求的参数
private string GeneratePostData(SendRedPackInput input, string openId) { string randomNum = ConfigStrHelper.RandomStr(RANDOM_STR, 10); //初始化调用微信商户平台的输入参数
    PayRedPack payRedPack = new PayRedPack() { ActName = input.ActName, ClientIp = input.ClientIp, MchId = _weChatSettings.MchId, Remark = input.Remark, WxAppid = input.WxAppid, SendName = input.SenderName, Wishing = input.Wishing, TotalNum = input.TotalNum, ReOpenId = input.OpenId, TotalAmount = input.TotalAmount, MchBillno = _weChatSettings.MchId + System.DateTime.Now.ToString("yyyyMMdd") + ConfigStrHelper.RandomStr(RANDOM_STR, 10), NonceStr = ConfigStrHelper.RandomStr(NOTICE_STR, 16) }; //原始传入参数数组
    string[] signTemp = { "mch_billno=" + payRedPack.MchBillno, "mch_id=" + payRedPack.MchId, "wxappid=" + payRedPack.WxAppid, "send_name=" + payRedPack.SendName, "re_openid=" + payRedPack.ReOpenId, "total_amount=" + payRedPack.TotalAmount, "total_num=" + payRedPack.TotalNum, "wishing=" + payRedPack.Wishing, "client_ip=" + payRedPack.ClientIp, "act_name=" + payRedPack.ActName, "remark=" + payRedPack.Remark, "nonce_str=" + payRedPack.NonceStr }; //生成具体的签名
    var sign = RedPackMakeSign(signTemp, _weChatSettings.PayKey); //返回待请求的xml数据
    return string.Format(SEND_REDPACK_XML_STR, payRedPack.MchBillno, payRedPack.MchId, payRedPack.WxAppid, payRedPack.SendName, payRedPack.ReOpenId, payRedPack.TotalAmount, payRedPack.TotalNum, payRedPack.Wishing, payRedPack.ClientIp, payRedPack.ActName, payRedPack.Remark, payRedPack.NonceStr, sign ); }

里面涉及到了一些字符串操做和签名操做的主要方法以下:

//生成随机长度字符串
public string RandomStr(string str, int length) { var result = new StringBuilder(); var rd = new Random(); for (int i = 0; i < length; i++) { result.Append(str[rd.Next(str.Length)]); } return result.ToString(); } //生成签名
public string RedPackMakeSign(string[] signTemp, string key) { List<string> signList = signTemp.ToList(); //对signList按照ASCII码从小到大的顺序排序
 signList.Sort(); var signOld = new StringBuilder(""); foreach (string temp in signList) { signOld.Append(temp + "&"); } signOld = new StringBuilder(signOld.ToString().Substring(0, signOld.Length - 1)); //拼接Key
    signOld.Append("&key=" + key); //处理支付签名
    var sign = SignHelper.CreateSign(signOld.ToString(), SignTypeEnum.MD5, Encoding.UTF8).ToUpper(CultureInfo.InvariantCulture); return sign; } /// <summary>
/// 尝试32字符长度md5值,小写. 举例: 543d9a4a308856458ebd4dac83f4277a /// 输入的中文字符将使用UTF8编码计算字节 /// </summary>
/// <param name="strSource"></param>
/// <returns></returns>
public static string GetMd5(string strSource) { System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider(); //获取密文字节数组, 固定使用UTF8
    var bytResult = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(strSource)); var strResult = BitConverter.ToString(bytResult).ToLower(); //BitConverter转换出来的字符串会在每一个字符中间产生一个分隔符,须要去除掉
    strResult = strResult.Replace("-", ""); return strResult; }

③ 最后咱们就能够将生成的字符串,做为请求的数据,发送到微信商户平台,执行红包的发放,而后将微信返回的信息在进行进一步的处理。

同发放,咱们能够将发放结果封装成类,以下:

public class PayRedPackResult { public string return_code { get; set; } public string return_msg { get; set; } public string sign { get; set; } public string result_code { get; set; } public string err_code { get; set; } public string err_code_des { get; set; } public string mch_billno { get; set; } public string mch_id { get; set; } public string wxappid { get; set; } public string re_openid { get; set; } public string total_amount { get; set; } public string send_listid { get; set; } }

下面是执行发放操做的主要方法

/// <summary>
/// 发送红包 /// </summary>
/// <param name="input"></param>
public void SendRedPackToUser(SendRedPackToUserInput input) { var encoding = Encoding.UTF8; var data = encoding.GetBytes(input.PostData); //配置文件中取出待请求的url
    var url = _weChatSettings.RedPackUrl; var content = RedPackHttpClinetInvoke(data, encoding, url); //反序列化成返回的对象
    var payResult = _readXmlService.Deserialize(typeof(PayRedPackResult), content, nameof(PayRedPackResult)) as PayRedPackResult; //发放成功、作一些事情
    if (payResult != null && payResult.result_code == "SUCCESS" && payResult.return_code == "SUCCESS") { //保存发放记录 记录到数据库当中 或者其余什么操做
 } } /// <summary>
/// 调用微信接口发送红包 /// </summary>
/// <param name="data"></param>
/// <param name="encoding"></param>
/// <param name="url"></param>
/// <returns></returns>
private string RedPackHttpClinetInvoke(byte[] data, Encoding encoding, string url) { //CerPath证书路径
    string certPath = _weChatSettings.CertPath; //证书密码
    string password = _weChatSettings.Password; X509Certificate2 cert = new X509Certificate2(certPath, password, X509KeyStorageFlags.MachineKeySet); // 设置参数 
    HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest; CookieContainer cookieContainer = new CookieContainer(); request.CookieContainer = cookieContainer; request.AllowAutoRedirect = true; request.Method = "POST"; request.ContentType = "text/xml"; request.ContentLength = data.Length; request.ClientCertificates.Add(cert); Stream outstream = request.GetRequestStream(); outstream.Write(data, 0, data.Length); outstream.Close(); //发送请求并获取相应回应数据 
    HttpWebResponse response = request.GetResponse() as HttpWebResponse; //直到request.GetResponse()程序才开始向目标网页发送Post请求 
    Stream instream = response.GetResponseStream(); StreamReader sr = new StreamReader(instream, encoding); //返回结果网页(html)代码 
    string content = sr.ReadToEnd(); return content; } /// <summary>
/// 反序列化 /// </summary>
/// <param name="type">类型</param>
/// <param name="xml">XML字符串</param>
/// <param name="className">类名</param>
/// <returns></returns>
public object Deserialize(Type type, string xml, string className) { xml = xml.Replace("xml", className); try { using (StringReader sr = new StringReader(xml)) { XmlSerializer xmldes = new XmlSerializer(type); return xmldes.Deserialize(sr); } } catch (Exception ex) { throw; } }

以上就是普通红包的流程,发放成功以后,应该会在公众号上面收到一个红包,点击便可领取对应的零钱。

 

④发放失败和异常

咱们在调用微信的时候,有些用户会反馈红包收到不,此时就要根据微信返回的信息,进行判断了,有些是咱们的配置没有配好,有些则是用户微信帐号的问题。下面总结了几个常见的问题:

I.发放失败,此请求可能存在风险,已被微信拦截 (NO_AUTH) : 出现这个问题,多是用户没有实名认证,或者刚刚实名不久,不是一个活跃帐号。

II.商户API证书校验出错(CA_ERROR) : 出现这个问题,是因为你发放的时候使用的Ca文件过时或者不是该商户的Ca,此时你能够在商户平台上面下载一个最新的替换掉。

下载:微信商户平台(pay.weixin.qq.com)-->帐户中心-->帐户设置-->API安全 

III.输入xml参数格式错误(XML_ERROR) : 这是你post过去的xml对,我在作企业支付的过程当中发现,腾讯官方提供的文档上面,也存在错误,以下图mch_id多了一个空格,致使我在请求的时候报这个错误

 

 IV: 在作【企业支付】的时候出现了 NO_AUTH,返回的消息是【产品权限验证失败,请查看您当前是否具备该产品的权限】: 这个问题是没有开通企业支付这个功能,开通一下就能够了。

 V: 有时候咱们【企业支付】发放的金额想要发放小于1元的金额,这个能够在商户平台上面进行设置以下图所示:产品中心 -> 企业付款到零钱 -> 产品设置

 

 

总结:

1.对于微信的红包咱们须要一个公众号去接收红包,因此上面的AppId必须是一个公众号的,若是你拿了一个小程序的可能没有地方用于展现红包。

这也是关键的地方吧,流程上面就是先让用户关注,而后绑定本身应用的惟一标识,创建起本身应用用户的Id和微信公众号下用户OpenId的绑定关系,而后经过该openId以及公众号的微信AppId,发放奖励。

此外默认的普通红包金额最低为1元,若是须要发放1元如下的,能够经过配置场景来实现。调用的方法和上面无异,只是更加复杂了一些。

 

2.对于咱们本身设计红包,而后直接给用户发零钱的方式,咱们能够采用【企业支付】的方式,这种方式不须要一个接收红包的地方,对于AppId也不必必须是一个公众号的。调用方法和上述无异,只是参数和返回值有一些区别。

相关文章
相关标签/搜索