一、企业微信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,"");
var openInfo = JsonConvert.DeserializeObject<U_OpenInfo>(openid);
data.SetValue("re_openid", openInfo.openid);                           //用户openid
data.SetValue("total_amount", );                           //付款金额,单位分 最低一元钱
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 ( == 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());
} /// <summary>
/// 生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数
/// </summary>
/// <returns>@return 时间戳</returns>
public static string GenerateTimeStamp()
{
TimeSpan ts = DateTime.UtcNow - new DateTime(, , , , , , );
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 = ""; //全部写你自己的 public static readonly string APPSECRET = ""; public static readonly string PAYMENTSECRET =""; public static readonly string MCHID = ""; //商户id号 public static readonly string KEY = ""; //=======【证书路径设置】=====================================
/* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要)
*/
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": ,
"errmsg": "",
"type": "image",
"media_id": "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0",
"created_at": ""
}

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, * * );
}
}
return mediaId;
}
}

四、实现效果

有需要的可以下载源码

企业微信开发之发放企业红包(C#)的更多相关文章

  1. Java企业微信开发_15_查询企业微信域名对应的所有ip

    一.前言 二.方法 1.在线网站 百度搜索"域名查IP",可查到如下网站,输入域名即可查到所有IP: 站长工具 site.ip138.com tools.ipip.net 2.li ...

  2. 如何用php开启企业微信开发的回调模式

    猜想: 懵逼 实践: 微信公众号开发的手册中甚至给出了只需要修改几个参数就能使用的范例.企业微信开发中在一个很不显眼的地方放了一个sample. https://work.weixin.qq.com/ ...

  3. Java企业微信开发_03_通讯录同步

    一.本节要点 1.获取通讯录密钥 获取方式: 登录企业微信—>管理工具—>通讯录同步助手—>开启“API接口同步”  ; 开启后,即可看到通讯录密钥,也可设置通讯录API的权限:读取 ...

  4. Java企业微信开发_07_素材管理之上传本地临时素材文件

    一.本节要点 1.临时素材有效期 media_id是可复用的,同一个media_id可用于消息的多次发送(3天内有效) 2.上传文件时的http请求里都有啥 具体原理可参看: 为什么上传文件的表单需要 ...

  5. Java企业微信开发_05_消息推送之发送消息(主动)

    一.本节要点 1.发送消息与被动回复消息 (1)流程不同:发送消息是第三方服务器主动通知微信服务器向用户发消息.而被动回复消息是 用户发送消息之后,微信服务器将消息传递给 第三方服务器,第三方服务器接 ...

  6. Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)

    注: 源码已上传github: https://github.com/shirayner/WeiXin_QiYe_Demo 一.本节要点 1.1 授权回调域(可信域名) 在开始使用网页授权之前,需要先 ...

  7. Java企业微信开发_06_素材管理之上传本地临时素材文件至微信服务器

    一.本节要点 1.临时素材有效期 media_id是可复用的,同一个media_id可用于消息的多次发送(3天内有效) 2.上传文件时的http请求里都有啥 具体原理可参看: 为什么上传文件的表单需要 ...

  8. Java企业微信开发_05_消息推送之被动回复消息

    一.本节要点 1.消息的加解密 微信加解密包 下载地址:http://qydev.weixin.qq.com/java.zip      ,此包中封装好了AES加解密方法,直接调用方法即可. 其中,解 ...

  9. Java企业微信开发_04_消息推送之发送消息(主动)

    源码请见: Java企业微信开发_00_源码及资源汇总贴 一.本节要点 1.发送消息与被动回复消息 (1)流程不同:发送消息是第三方服务器主动通知微信服务器向用户发消息.而被动回复消息是 用户发送消息 ...

随机推荐

  1. 201521123065《Java程序设计》第2周学习总结

    1.本周学习总结 字符串创建后是不可变的,可以使用加号进行字符串之间的拼接. 使用for循环对字符串进行修正会不断产生新的字符串,应使用StringBuilder. 字符串内容的比较要用equal. ...

  2. 201521123033《Java程序设计》第1周学习总结

    1. 本周学习总结 (1)了解了JVM,JRE,JDK (2)配置java环境变量 (3)编译一些简单的java代码 2. 书面作业 为什么java程序可以跨平台运行?执行java程序的步骤是什么?( ...

  3. 201521123074 《Java程序设计》第9周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常相关内容. 2. 书面作业 本次PTA作业题集异常 Q1.常用异常 题目5-1 1.1 截图你的提交结果(出现学号) 1.2 自 ...

  4. Java: server/client 心跳机制实现 示例

    心跳机制 心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制. 大部分CS的应用需要心跳机制.心跳机制一般在Server和Client都要实现,两者实现原理 ...

  5. Python接口测试自动化说明及代码实例:含get、post、put、delete等方法

    一.接口说明文档 环境准备: 安装火狐 安装插件: httprequester https://addons.mozilla.org/en-US/firefox/addon/httprequester ...

  6. Jquery一些常用的方法

    整理以前的笔记,在学习JavaScript时候,经常会用到一些方法,但是有时忘掉了具体用法,因此记下.方便以后查阅. 这篇博文先说明这些方法的用途: removeClass().remove().cs ...

  7. Redis学习笔记之二 :在Java项目中使用Redis

    成功配置redis之后,便来学习使用redis.首先了解下redis的数据类型. Redis的数据类型 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set( ...

  8. 【Python练习1】统计一串字符中英文字母、空格、数字和其他字符的个数

    练习思路: 1.输入一串字符 2.筛选出字符中的英文字母并统计 3.筛选出字符中的空格并统计 4.筛选出字符中的数字并统计 5.筛选出字符中的其他字符并统计 代码实现: def msg(s): abc ...

  9. hdu 5937 -- Equation(搜索)

    题目链接 problem description Little Ruins is a studious boy, recently he learned addition operation! He ...

  10. 用python的TK模块实现猜成语游戏(附源码)

    说明:本游戏使用到的python模块有tkinter,random,hashlib:整个游戏分为四个窗口,一个进入游戏的窗口.一个选关窗口.一个游戏进行窗口和一个游戏结束的窗口. 源码有两个主要的py ...