Release Enterprise Red Bag (C#) for Enterprise WeChat Development

1. Enterprise WeChat API

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

2. Explanation of parameters

1. Send enterprise red envelopes

Request method: POST (HTTPS)
Request address: https://api.mch.weixin.qq.com/mmpaymkttransfers/sendworkwxredpack
Is Certificate Required: Yes
Data format: xml

See API interface documentation for detailed parameter descriptions

2. Specific implementation code

                        WxPayData data = new WxPayData();
                        data.SetValue("nonce_str", WxPayApi.GenerateNonceStr());//Random String
                        data.SetValue("mch_billno", WxPayApi.GenerateOutTradeNo()); //Merchant Order Number
                        data.SetValue("mch_id", WxPayConfig.MCHID);//Business Number
                        data.SetValue("wxappid", WxPayConfig.APPID);//Public Account ID
                        data.SetValue("sender_name", "ly");      //Sender Name
                        data.SetValue("sender_header_media_id", "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0"); //Sender's avatar, which is the default avatar for WeChat (see Part 3 if you want to customize your avatar)
                        string openid = ConvertToOpenidByUserId(_accessToken,"13212345678");  
                        var openInfo =  JsonConvert.DeserializeObject<U_OpenInfo>(openid);
                        data.SetValue("re_openid", openInfo.openid);  //User openid   
                        data.SetValue("total_amount", 100);     //Amount of payment, minimum one dollar per unit
                        data.SetValue("wishing", "Happy Chinese Valentine's Day!");       //Red envelope greeting
                        data.SetValue("act_name", "XX activity");      //Activity Name
                        data.SetValue("remark", "Come and grab");  //Remarks
                        data.SetValue("scene_id", "PRODUCT_4");           //Scene (required if amount is greater than 200 yuan)
                        data.SetValue("workwx_sign", data.MakeWorkWxSign("redPacket"));  //Enterprise WeChat Signature
                        data.SetValue("sign", data.MakeSign());                   //WeChat Payment Signature
                        string xml = data.ToXml();
                        const string postUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendworkwxredpack ";//Send Enterprise Red Packet Interface Address
                        string response = PostWebRequest(postUrl, xml, Encoding.UTF8, true);//Call HTTP communication interface to submit data to API
                        WxPayData result = new WxPayData();
                        result.FromXml(response);

  

  

public class WxPayData
    {
        //The advantage of using sorted Dictionary is that it is easy to sign packets without having to sort them again before signing them again.
        private SortedDictionary<string, object> m_values = new SortedDictionary<string, object>();

        /// <summary>
        ///Set the value of a field
        /// </summary>
        /// <param name="key">field name</param>
        /// <param name="value">field value</param>
        public void SetValue(string key, object value)
        {
            m_values[key] = value;
        }

        /// <summary>
        ///Get the value of a field based on its name
        /// </summary>
        /// <param name="key">field name</param>
        /// <returns>corresponding field value </returns>
        public object GetValue(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            return o;
        }

        /// <summary>
        ///Determine if a field is set
        /// </summary>
        /// <param name="key">field name</param>
        /// <returns>Returns true if the field key is set, false</returns if not
        public bool IsSet(string key)
        {
            object o = null;
            m_values.TryGetValue(key, out o);
            if (null != o)
                return true;
            else
                return false;
        }
        /// <summary>
        ///Convert Dictionary to xml
        /// </summary>
        /// <returns>Converted xml string </returns>
        public string ToXml()
        {
            //Cannot convert to xml format when data is empty
            if (0 == m_values.Count)
            {
                LogManager.WriteLog(LogFile.Error, "WxPayData Data is empty!");
                throw new WxPayException("WxPayData Data is empty!");
            }

            string xml = "<xml>";
            foreach (KeyValuePair<string, object> pair in m_values)
            {
                //Field value cannot be null, affecting subsequent processes
                if (pair.Value == null)
                {
                    throw new WxPayException("WxPayData Internal Containment Value null Fields of!");
                }

                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//No other data types except string and int
                {
                    throw new WxPayException("WxPayData Field data type error!");
                }
            }
            xml += "</xml>";
            return xml;
        }

        /// <summary>
        ///Convert xml to a WxPayData object and return data inside the object
        /// </summary>
        /// <param name="xml">xml string to be converted </param>
        /// <returns>Converted Dictionary</returns>
        public SortedDictionary<string, object> FromXml(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                throw new WxPayException("Will be empty xml Convert string to WxPayData Wrongful!");
            }

            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(xml);
            XmlNode xmlNode = xmlDoc.FirstChild;//Get root node <xml>
            XmlNodeList nodes = xmlNode.ChildNodes;
            foreach (XmlNode xn in nodes)
            {
                XmlElement xe = (XmlElement)xn;
                m_values[xe.Name] = xe.InnerText;//Get xml key-value pairs to data inside WxPayData
            }

            try
            {
                if (m_values["return_code"].ToString() != "SUCCESS")
                {
                    return m_values;
                }
                // CheckSign(); //Verify signature, failing throws an exception
            }
            catch (WxPayException ex)
            {
                throw new WxPayException(ex.Message);
            }

            return m_values;
        }

        /// <summary>
        /// Dictionary format to url parameter format
        /// </summary>
        /// <returns>url format string, which does not contain the sign field value </returns>
        public string ToUrl()
        {
            string buff = "";
            foreach (KeyValuePair<string, object> pair in m_values)
            {
                if (pair.Value == null)
                {
                    LogManager.WriteLog(LogFile.Error, "WxPayData Internal Containment Value null Fields of!");
                    throw new WxPayException("WxPayData Internal Containment Value null Fields of!");
                }

                if (pair.Key != "sign" && pair.Value.ToString() != "")
                {
                    buff += pair.Key + "=" + pair.Value + "&";
                }
            }
            buff = buff.Trim('&');
            return buff;
        }

        /// <summary>
        /// Dictionary format to url parameter format
        /// </summary>
        /// <returns>url format string, which does not contain the sign field value </returns>
        public string ToWorkWxUrl(string type)
        {
            string buff = "";
            foreach (KeyValuePair<string, object> pair in m_values)
            {
                if (pair.Value == null)
                {
                    throw new WxPayException("WxPayData Internal Containment Value null Fields of!");
                }
                var paras = new List<string>();
                switch (type)
                {
                    case "redPacket":
                        paras = new[] { "act_name", "mch_billno", "mch_id", "nonce_str", "re_openid", "total_amount", "wxappid" }.ToList();
                        break;
                    case "payment":
                        paras = new[] { "amount", "desc", "mch_id", "nonce_str", "openid", "partner_trade_no", "appid", "ww_msg_type" }.ToList();
                        break;
                }
                if (paras.Contains(pair.Key))
                {
                    buff += pair.Key + "=" + pair.Value + "&";
                }
            }
            buff = buff.Trim('&');
            return buff;
        }
        /// <summary>
        ///Generate signatures as detailed in the signature generation algorithm
        /// </summary>
        /// <returns>Signature, sign field does not participate in signature </returns>
        public string MakeSign()
        {
            //To url format
            string str = ToUrl();
            //Add API KEY after string
            str += "&key=" + WxPayConfig.KEY;
            //MD5 Encryption
            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"));
            }
            //All characters uppercase
            return sb.ToString().ToUpper();
        }
        /// <summary>
        ///Generate Enterprise WeChat Signature
        /// </summary>
        /// <returns></returns>
        public string MakeWorkWxSign(string type)
        {
            //To url format
            string str = ToWorkWxUrl(type);
            //Add secret after string
            str += "&secret=" + WxPayConfig.PAYMENTSECRET;
            //MD5 Encryption
            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"));
            }
            //All characters uppercase
            return sb.ToString().ToUpper();
        }
        /// <summary>
        ///Check if the signature is correct
        /// </summary>
        /// <returns>Returns true correctly, throws an exception by error </returns>
        public bool CheckSign()
        {
            //Skip detection if no signature is set
            if (!IsSet("sign"))
            {
                LogManager.WriteLog(LogFile.Error, "WxPayData Signature exists but is not legal!");
                throw new WxPayException("WxPayData Signature exists but is not legal!");
            }
            //If a signature is set but the signature is empty, an exception is thrown
            else if (GetValue("sign") == null || GetValue("sign").ToString() == "")
            {
                LogManager.WriteLog(LogFile.Error, "WxPayData Signature exists but is not legal!");
                throw new WxPayException("WxPayData Signature exists but is not legal!");
            }

            //Get the received signature
            string return_sign = GetValue("sign").ToString();

            //Calculate new signatures locally
            string cal_sign = MakeSign();

            if (cal_sign == return_sign)
            {
                return true;
            }

            LogManager.WriteLog(LogFile.Error, "WxPayData Signature verification error!");
            throw new WxPayException("WxPayData Signature verification error!");
        }

        /// <summary>
        ///Get Dictionary
        /// </summary>
        /// <returns></returns>
        public SortedDictionary<string, object> GetValues()
        {
            return m_values;
        }
    }

  

 public class WxPayException : Exception
    {
        public WxPayException(string msg)
            : base(msg)
        {

        }
    }

  

public class WxPayApi
    {

        protected Hashtable Parameters = new Hashtable();
        /// <summary>
        ///Generate order number based on current system time plus random sequence
        /// </summary>
        /// <returns>@return order number </returns>
        public static string GenerateOutTradeNo()
        {
            var ran = new Random();
            return string.Format("{0}{1:yyyyMMddHHmmss}{2}", WxPayConfig.MCHID, DateTime.Now, ran.Next(999));
        }



        /// <summary>
        ///Generate timestamp, Standard Beijing Time, Time Zone East Eight, seconds since 10:0 minutes and 0 seconds on January 1, 1970
        /// </summary>
        /// <returns>@return timestamp</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>
        ///Generate a random string containing letters or numbers
        /// </summary>
        /// <returns> @return random string </returns>
        public static string GenerateNonceStr()
        {
            //Random random = new Random();
            //return GetMD5(random.Next(1000).ToString(), "GBK");
            return Guid.NewGuid().ToString().Replace("-", "");
        }
        /// <summary>
        ///Get md5 encrypted string
        /// </summary>
        /// <param name="encypStr"></param>
        /// <param name="charset"></param>
        /// <returns></returns>
        protected static string GetMD5(string encypStr, string charset)
        {
            byte[] bytes;
            //Create md5 object
            MD5CryptoServiceProvider provider = new MD5CryptoServiceProvider();
            //Converts a string to a byte array using GB2312 encoding.
            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);
            }
        }

    }

  

 public class WxPayConfig
    {

        //=======[Basic Information Settings]============================================
        /* WeChat Public Number Information Configuration
        * APPID: APPID for Binding Payment (must be configured)
        * MCHID: Business Number (must be configured)
        * KEY: Merchant Payment Key, refer to Opening Mail Settings (must be configured)
        * APPSECRET: Public account secert (configure only when JSAPI pays)
        */
        public static readonly string APPID = "111111111111";  //Write all about yourself

        public static readonly string APPSECRET = "111111";

        public static readonly string PAYMENTSECRET ="111111";

        public static readonly string MCHID = "111111";    //Merchant id Number

        public static readonly string KEY = "111111111111";


        //=======[Certificate Path Settings]========================================= 
        /* Certificate path, note that absolute path should be filled in (only for refunds, cancellations of orders)
        */
        public static readonly string SSLCERT_PATH = "cert/apiclient_cert.p12";
        public static readonly string SSLCERT_PASSWORD =MCHID ;
  
    }

3. Notes

(1) Calculate Enterprise WeChat Signature

The secret at the end of the string is the secret of the payment application page on the management side of Enterprise WeChat (see below)

Instead of Enterprise WeChat's secret.(Picture below) Remember!!!

(2) Or calculate the signature of Enterprise WeChat

The RedPack ap has and only has the following fields participating in the signature (as reflected in the code):
   act_name
  mch_billno
  mch_id
  nonce_str
  re_openid
  total_amount
  wxappid

Do not involve all parameters in calculating signatures, otherwise the WeChat signature error will be returned!

3. Uploading Temporary Materials

1. There is a parameter in the API interface of RedPack that is "sender_header_media_id", which is the sender's avatar, which can be obtained through the open upload material interface of Enterprise WeChat.

Request method: POST (HTTPS)
Request address: https://qyapi.weixin.qq.com/cgi-bin/media/upload?Access_token=ACCESS_TOKEN&type=TYPE

Upload the file using http multipart/form-data with the file ID "media".
Parameter description:

parameter Must Explain
access_token yes Call interface credentials
type yes Media file types, such as picture, voice, video and file.
media yes Identification of media file in form-data with information such as filename, filelength, content-type, etc.

Permission Instructions:

Fully public, media_id s can be shared between applications within the same enterprise.

Return data:

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

2. Specific implementation

 /// <summary>
        ///Upload temporary material
        /// </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())
            {
                    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();
                    }
                }
                return mediaId;
            }
        }

4. Achieving Effect

Keywords: C# xml encoding

Added by belayet on Wed, 29 May 2019 20:16:40 +0300