• 企业微信开发之发放企业红包(C#)


    一、企业微信API

    地址:http://work.weixin.qq.com/api/doc#11543

    二、参数说明

    1、发送企业红包

    请求方式:POST(HTTPS)
    请求地址:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendworkwxredpack
    是否需要证书:是
    数据格式:xml

    具体参数说明请参见API接口文档

    2、具体实现代码

    WxPayData data = new WxPayData();
    data.SetValue("nonce_str", WxPayApi.GenerateNonceStr());                                //随机字符串
    data.SetValue("mch_billno", WxPayApi.GenerateOutTradeNo());                  //商户订单号
    data.SetValue("mch_id", WxPayConfig.MCHID);                           //商户号
    data.SetValue("wxappid", WxPayConfig.APPID);                          //公众账号ID
    data.SetValue("sender_name", "ly");                                  //发送者名称
    data.SetValue("sender_header_media_id", "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0"); //发送者头像,此id为微信默认的头像(如果想自定义头像,请参见第三部分)
    string openid = ConvertToOpenidByUserId(_accessToken,"13212345678");  
    var openInfo =  JsonConvert.DeserializeObject<U_OpenInfo>(openid);
    data.SetValue("re_openid", openInfo.openid);                            //用户openid   
    data.SetValue("total_amount", 100);                                     //付款金额,单位分  最低一元钱
    data.SetValue("wishing", "七夕情人节快乐!");                             //红包祝福语
    data.SetValue("act_name", "XX活动");                                    //活动名称
    data.SetValue("remark", "快来抢");                                    //备注
    data.SetValue("scene_id", "PRODUCT_4");                                 //场景(金额大于200元时必填)
    data.SetValue("workwx_sign", data.MakeWorkWxSign("redPacket"));                 //企业微信签名
    data.SetValue("sign", data.MakeSign());                                         //微信支付签名
    string xml = data.ToXml();
    const string postUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendworkwxredpack";  //发送企业红包接口地址
    string response = PostWebRequest(postUrl, xml, Encoding.UTF8, true);                         //调用HTTP通信接口提交数据到API
    WxPayData result = new WxPayData();
    result.FromXml(response);
    
    
    public class WxPayData
    {
        //采用排序的Dictionary的好处是方便对数据包进行签名,不用再签名之前再做一次排序
        private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();
    
        /// <summary>
        /// 设置某个字段的值
        /// </summary>
        /// <param name="key">字段名</param>
        /// <param name="value">字段值</param>
        public void SetValue(string key, object value)
        {
            m_values[key] = value;
        }
    
        /// <summary>
        /// 根据字段名获取某个字段的值
        /// </summary>
        /// <param name="key">字段名</param>
        /// <returns>对应的字段值</returns>
        public object GetValue(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            return o;
        }
    
        /// <summary>
        /// 判断某个字段是否已设置
        /// </summary>
        /// <param name="key">字段名</param>
        /// <returns>若字段key已被设置,则返回true,否则返回false</returns>
        public bool IsSet(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            if (null != o)
                return true;
            else
                return false;
        }
        /// <summary>
        /// 将Dictionary转成xml
        /// </summary>
        /// <returns>经转换得到的xml串</returns>
        public string ToXml()
        {
            //数据为空时不能转化为xml格式
            if (0 == m_values.Count)
            {
                LogHelper.LogHelper.WriteLog("WxPayData数据为空!");
                throw new WxPayException("WxPayData数据为空!");
            }
    
            string xml = "<xml>";
            foreach (KeyValuePair<string, object> pair in m_values)
            {
                //字段值不能为null,会影响后续流程
                if (pair.Value == null)
                {
                    LogHelper.LogHelper.WriteLog("WxPayData内部含有值为null的字段!" + pair.Key + ":" + pair.Value);
                    throw new WxPayException("WxPayData内部含有值为null的字段!");
                }
    
                if (pair.Value is int)
                {
                    xml += "<" + pair.Key + ">" + pair.Value + "</" + pair.Key + ">";
                }
                else if (pair.Value is string)
                {
                    xml += "<" + pair.Key + ">" + "<![CDATA[" + pair.Value + "]]></" + pair.Key + ">";
                }
                else//除了string和int类型不能含有其他数据类型
                {
                    LogHelper.LogHelper.WriteLog("WxPayData字段数据类型错误!");
                    throw new WxPayException("WxPayData字段数据类型错误!");
                }
            }
            xml += "</xml>";
            return xml;
        }
    
        /// <summary>
        /// 将xml转为WxPayData对象并返回对象内部的数据
        /// </summary>
        /// <param name="xml">待转换的xml串</param>
        /// <returns>经转换得到的Dictionary</returns>
        public SortedDictionary<string, object> FromXml(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                LogHelper.LogHelper.WriteLog("将空的xml串转换为WxPayData不合法!");
                throw new WxPayException("将空的xml串转换为WxPayData不合法!");
            }
    
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
            XmlNodeList nodes = xmlNode.ChildNodes;
            foreach (XmlNode xn in nodes)
            {
                XmlElement xe = (XmlElement)xn;
                m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
            }
    
            try
            {
                //2015-06-29 错误是没有签名
                if (m_values["return_code"].ToString() != "SUCCESS")
                {
                    return m_values;
                }
                CheckSign();//验证签名,不通过会抛异常
            }
            catch (WxPayException ex)
            {
                throw new WxPayException(ex.Message);
            }
    
            return m_values;
        }
    
        /// <summary>
        /// 将xml转为WxPayData对象并返回对象内部的数据
        /// </summary>
        /// <param name="xml">待转换的xml串</param>
        /// <returns>经转换得到的Dictionary</returns>
        public SortedDictionary<string, object> XmlToEntity(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                LogHelper.LogHelper.WriteLog("将空的xml串转换为WxPayData不合法!");
                throw new WxPayException("将空的xml串转换为WxPayData不合法!");
            }
    
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
            XmlNodeList nodes = xmlNode.ChildNodes;
            foreach (XmlNode xn in nodes)
            {
                XmlElement xe = (XmlElement)xn;
                m_values[xe.Name] = xe.InnerText;//获取xml的键值对到WxPayData内部的数据中
            }
    
            try
            {
                //2015-06-29 错误是没有签名
                if (m_values["return_code"].ToString() != "SUCCESS")
                {
                    return m_values;
                }
                // CheckSign();//验证签名,不通过会抛异常
            }
            catch (WxPayException ex)
            {
                throw new WxPayException(ex.Message);
            }
    
            return m_values;
        }
    
        /// <summary>
        /// Dictionary格式转化成url参数格式
        /// </summary>
        /// <returns>url格式串, 该串不包含sign字段值</returns>
        public string ToUrl()
        {
            string buff = "";
            foreach (KeyValuePair<string, object> pair in m_values)
            {
                if (pair.Value == null)
                {
                    LogHelper.LogHelper.WriteLog("WxPayData内部含有值为null的字段!" + pair.Key + ":" + pair.Value);
                    throw new WxPayException("WxPayData内部含有值为null的字段!");
                }
    
                if (pair.Key != "sign" && pair.Value.ToString() != "")
                {
                    buff += pair.Key + "=" + pair.Value + "&";
                }
            }
            buff = buff.Trim('&');
            return buff;
        }
    
        /// <summary>
        /// 生成签名,详见签名生成算法
        /// </summary>
        /// <returns>签名, sign字段不参加签名</returns>
        public string MakeSign()
        {
            //转url格式
            string str = ToUrl();
            //在string后加入API KEY
            str += "&key=" + WxPayConfig.KEY;
            //MD5加密
            var md5 = MD5.Create();
            var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
            var sb = new StringBuilder();
            foreach (byte b in bs)
            {
                sb.Append(b.ToString("x2"));
            }
            //所有字符转为大写
            return sb.ToString().ToUpper();
        }
    
        /// <summary>
        /// 检测签名是否正确
        /// </summary>
        /// <returns>正确返回true,错误抛异常</returns>
        public bool CheckSign()
        {
            //如果没有设置签名,则跳过检测
            if (!IsSet("sign"))
            {
                LogHelper.LogHelper.WriteLog("WxPayData签名存在但不合法!");
                throw new WxPayException("WxPayData签名存在但不合法!");
            }
            //如果设置了签名但是签名为空,则抛异常
            else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
            {
                LogHelper.LogHelper.WriteLog("WxPayData签名存在但不合法!");
                throw new WxPayException("WxPayData签名存在但不合法!");
            }
    
            //获取接收到的签名
            string return_sign = GetValue("sign").ToString();
    
            //在本地计算新的签名
            string cal_sign = MakeSign();
    
            if (cal_sign == return_sign)
            {
                return true;
            }
    
            LogHelper.LogHelper.WriteLog("WxPayData签名验证错误!");
            throw new WxPayException("WxPayData签名验证错误!");
        }
    
        /// <summary>
        /// 获取Dictionary
        /// </summary>
        /// <returns></returns>
        public SortedDictionary<string, object> GetValues()
        {
            return m_values;
        }
    }
    WxPayData类
    public class WxPayException:Exception
    {
        public WxPayException(string msg)
            : base(msg)
        {
    
        }
    }
    WxPayException类

     

    public class WxPayApi
    {
        protected Hashtable Parameters = new Hashtable();
        /// <summary>
        /// 根据当前系统时间加随机序列来生成订单号
        /// </summary>
        /// <returns>@return 订单号</returns>
        public static string GenerateOutTradeNo()
        {
            var ran = new Random();
            return string.Format("{0}{1:yyyyMMddHHmmss}{2}", WxPayConfig.MCHID, DateTime.Now, ran.Next(999));
        }
    
    
        /// <summary>
        /// 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
        /// </summary>
        /// <returns>@return 时间戳</returns>
        public static string GenerateTimeStamp()
        {
            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }
    
        /// <summary>
        /// 生成随机串,随机串包含字母或数字
        /// </summary>
        /// <returns> @return 随机串</returns>
        public static string GenerateNonceStr()
        {
            //Random random = new Random();
            //return GetMD5(random.Next(1000).ToString(), "GBK");
             return Guid.NewGuid().ToString().Replace("-", "");
        }
        /// <summary>
        /// 获取md5加密字符串
        /// </summary>
        /// <param name="encypStr"></param>
        /// <param name="charset"></param>
        /// <returns></returns>
        protected static string GetMD5(string encypStr, string charset)
        {
            byte[] bytes;
            //创建md5对象
            MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
            //使用GB2312编码方式把字符串转化为字节数组.
            try
            {
                bytes = Encoding.GetEncoding(charset).GetBytes(encypStr);
            }
            catch (Exception)
            {
                bytes = Encoding.GetEncoding("GB2312").GetBytes(encypStr);
            }
            return BitConverter.ToString(provider.ComputeHash(bytes)).Replace("-", "").ToUpper();
        }
      
     protected void SetParameter(string parameter, string parameterValue)
            {
                if (!string.IsNullOrEmpty(parameter))
                {
                    if (this.Parameters.Contains(parameter))
                    {
                        this.Parameters.Remove(parameter);
                    }
                    this.Parameters.Add(parameter, parameterValue);
                }
            }
    
    }
    WxPayApi类
    public class WxPayConfig
     {
    
            //=======【基本信息设置】=====================================
            /* 微信公众号信息配置
            * APPID:绑定支付的APPID(必须配置)
            * MCHID:商户号(必须配置)
            * KEY:商户支付密钥,参考开户邮件设置(必须配置)
            * APPSECRET:公众帐号secert(仅JSAPI支付的时候需要配置)
            */
            public static readonly string APPID = "111111111111";  //全部写你自己的
    
            public static readonly string APPSECRET = "111111";
    
            public static readonly string PAYMENTSECRET ="111111";
    
            public static readonly string MCHID = "111111";    //商户id号
    
            public static readonly string KEY = "111111111111";
    
    
            //=======【证书路径设置】===================================== 
            /* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要)
            */
            public static readonly string SSLCERT_PATH = "cert/apiclient_cert.p12";
            public static readonly string SSLCERT_PASSWORD =MCHID ;
      
    }
    WxPayConfig类

    3、注意事项

    (1)计算企业微信签名

    字符串最后拼的secret是企业微信管理端支付应用页面的secret(见下图)

    而不是企业微信的secret。(如下图)切记!!!

    (2)还是计算企业微信签名

    发红包ap有且仅有如下几个字段参与签名(这点代码里有体现):
      act_name
      mch_billno
      mch_id
      nonce_str
      re_openid
      total_amount
      wxappid

    不要将参数全部参与计算签名,否则会返回微信签名错误!

    三、上传临时素材

    1、在发红包的API接口中有一个参数为"sender_header_media_id"即发送者头像,可以通过企业微信开放上传素材接口获取

    请求方式:POST(HTTPS)
    请求地址:https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE

    使用http multipart/form-data上传文件, 文件标识名为”media”.
    参数说明:

    参数必须说明
    access_token 调用接口凭证
    type 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file)
    media form-data中媒体文件标识,有filename、filelength、content-type等信息

    权限说明:完全公开,media_id在同一企业内应用之间可以共享。

    返回数据:

    {
       "errcode": 0,
       "errmsg": """type": "image",
       "media_id": "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0",
       "created_at": "1380000000"
    }

    2、具体实现

    /// <summary>
    /// 上传临时素材
    /// </summary>
    /// <param name="filePath"></param>
    /// <returns></returns>
    public string UploadTempResource(string filePath)
    {
        const string url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token={0}&type=image";
        var uploadUrl = string.Format(url, _accessToken);
        var mediaId = "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0";
        using (var client = new WebClient())
        {
            var cm = CacheManager<string>.GetInstance();
            if (cm.Get("media_id") == null)
            {
                byte[] resource = client.UploadFile(new Uri(uploadUrl), filePath);
                string retdata = Encoding.UTF8.GetString(resource);
                var data = JsonConvert.DeserializeObject(retdata) as JObject;
                if (data != null)
                {
                    mediaId = data["media_id"].ToString();
                    cm.Add("media_id", mediaId, 3 * 24 * 3600);
                }
            }
            return mediaId;
        }
    }

    四、实现效果

    有需要的可以下载源码

  • 相关阅读:
    java.lang.NoSuchMethodError
    asm相关内容想下载(包括 jar 包)
    Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/objectweb/asm/Type
    用Navicat连接mysql报错:2003-Can't connect to MySql server on '10.100.0.109'(10039)
    The type java.lang.reflect.AnnotatedElement cannot be resolved. It is indirectly referenced from required .class files
    The type java.lang.CharSequence cannot be resolved. It is indirectly referenced from required .class files
    交通测速方式
    卡口和电子警察的区别
    Myeclipse连接Mysql数据库时报错:Error while performing database login with the pro driver:unable
    在window上安装mysql
  • 原文地址:https://www.cnblogs.com/liuyoung/p/7445990.html
Copyright © 2020-2023  润新知