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


    一、背景介绍

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

    红包提现有两种场景:

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

    好处:不用自己开发红包功能,直接调用微信的API就可以完成红包的发放,只要用户关注公众号具有了唯一可识别的OpenId,并且公众号与商户平台绑定,就可以发放红包给用户,并且在公众号内会显示红包的图片,点击即可领取。

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

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

    好处:不需要用户关注公众号,只要用户登录微信小程序生成了一个唯一的OpenId,并且小程序和商户平台进行了绑定,就可以通过OpenId付款给用户,并且直接进入到用户的零钱当中。

    坏处:如果想让用户有点击红包的快感,需要自己在小程序或者APP内实现,有一定的开发成本

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

    二、实现

    1. 准备工作

    ①需要一个或多个测试用的微信账号,这个自己注册或者向别人索要就行了。

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

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

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

    2.流程

    过程1:首先用户需要在微信公众号进行关注或者登录微信小程序,此时我们可以获得到用户的OpenId,然后将该用户的OpenId、WeAppId以及我们自己应用的用户唯一标识进行保存。

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

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

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

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

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

    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也没必要必须是一个公众号的。调用方法和上述无异,只是参数和返回值有一些区别。

  • 相关阅读:
    swfupload多文件上传[附源码]
    C#函数式程序设计之泛型(下)
    ASP.NET MVC实现POST方式的Redirect
    使用Windows Azure的VM安装和配置CDH搭建Hadoop集群
    Asp.Net MVC 上传图片到数据库
    ASP.NET Web API标准的“管道式”设计
    如何捕获和分析 JavaScript Error
    快学Scala习题解答—第一章 基础
    职场人生
    合伙人的重要性超过了商业模式和行业选择(转)
  • 原文地址:https://www.cnblogs.com/dcz2015/p/10636540.html
Copyright © 2020-2023  润新知