企业微信支付的发送红包及相关接口使用

企业微信的支付自从企业号变化为企业微信后,增加了一些支付接口以及对很多接口进行了调整,企业微信的支付处理也是变化了不少,往往有时候碰到不少坑,一步一个脚印趟过来的;企业微信支付是需要结合微信商户后台进行处理,有时候也需要设置好商户平台的相关处理,才能进行发送红包、支付到个人等等支付处理。本篇随笔介绍在企业微信的支付处理中的发送红包的操作相关内容。

1、企业微信的支付接口

我们查看企业微信API的目录,可以看到企业微信支付的相关介绍,如下所示。

image

1)常见错误处理

企业微信支付,经常见到的错误信息,就是签名错误这个操作,这个很多人出招,解决方法各种各样,其实很多可能是不符合的,这样排查问题起来就很吃力。

这里需要遵循官方的解析进行排查,特别对参数的顺序和数量进行核对,注意不要增加多一个参数,否则都容易出现签名错误。

我就是在官方需要参数都有了,打印输出的格式也没问题,就是不小心多了一个参数(还是升级前有的一个),导致错误很难排查,弄得很头大。

一般来说发送企业红包,很容易发生签名错误的情况,请检查以下内容
1、企业微信的CorpID/CorpSecret
2、企业微信的支付Secret和商户的API支付秘钥
3、参数不能多也不能少(重要),如很多时候由于版本原因这里不小心多了一个total_num导致签名错误
4、商户平台的证书和密码是否正确

另外,除了这些问题外,重要的问题就是签名的处理了,微信支付除了有一个常规的签名sign参数外,还增加了一个workwx_sign的参数,两者的规则是不同的。

workwx_sign参数在前,使用系统给出的计算方式计算后,然后在计算sign参数,sign参数的计算是包含本身之外的所有参数进行计算,包括了workwx_sign参数。

2)签名参数处理

对于企业微信的签名workwx_sign参数,不要将参数全部参与计算签名,否则会返回微信签名错误!

发红包api固定如下几个字段参与签名:
act_name
mch_billno
mch_id
nonce_str
re_openid
total_amount
wxappid

付款api固定如下几个字段参与签名:
amount
appid
desc
mch_id
nonce_str
openid
partner_trade_no
ww_msg_type

计算企业微信签名的字符串最后拼的secret是企业微信管理端支付应用页面的secret,如下图所示。

image

示例:请求内容:
act_name XXX
mch_billno 11111234567890
mch_id 10000098
nonce_str qFKEgfig76DF9912fewmkp
re_openid oxTWIuGaIt6gTKsQRLau2M0yL16E
total_amount 100
wxappid wx123456789

第一步: 对参数按照key=value的格式,并按照参数名ASCII字典序排序如下
stringA=”act_name=XXX&mch_billno=11111234567890&mch_id=10000098&nonce_str=qFKEgfig76DF9912fewmkp&re_openid=oxTWIuGaIt6gTKsQRLau2M0yL16E&total_amount=100&wxappid=wx123456789
第二步:拼接企业微信支付应用secret(参见企业微信管理端支付应用页面):
stringSignTemp=”stringA&secret=192006250b4c09247ec02edce69f6a2d”
sign=MD5(stringSignTemp).toUpperCase()

2、企业微信发送红包

测试企业微信发送红包和直接支付的接口,响应效果如下所示

image
image

在企业微信中,常用到的企业微信的userid,不过发送红包则需要把userid转换为微信的openid进行使用,转换函数根据UserID 换取用户的OpenId 如下。

image

一般封装一个函数来使用即可。

private string GetOpenId(string userid)
{
    //根据UserID 换取用户的OpenId
    ICorpBasicApi basicAPi = new CorpBasicApi();
    return basicAPi.ConvertToOpenId(this.token, userid);
}

发送企业红包调用如下代码所示

//构建发送红包的参数信息
SendRedPackJson packJson = new SendRedPackJson()
{ 
    act_name = "恭喜发财",
    client_ip = NetworkUtil.GetIPAddress(),
    remark = "企业红包",
    wishing = "企业红包",
    total_amount = 100,
    total_num = 1,
    re_openid = openid //发送给用户的OpenID
};

//调用发送企业红包接口发送
var result = hbApi.SendWorkRedPack(packJson);

函数SendWorkRedPack的实现内容如下所示。

/// <summary>
/// 发放企业红包。需要商户证书
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
public SendRedPackResult SendWorkRedPack(SendRedPackJson json)
{
    CheckAccount();//检查AccountInfo的对象属性值

    //加入常规的参数
    WxPayData data = new WxPayData();
    data.SetValue("nonce_str", data.GenerateNonceStr());//随机字符串
    //商户订单号(每个订单号必须唯一) 组成:mch_id+yyyymmdd+10位一天内不能重复的数字。
    //接口根据商户订单号支持重入,如出现超时可再调用。
    data.SetValue("mch_billno", data.GenerateOutTradeNo(AccountInfo.MchID));
    data.SetValue("mch_id", AccountInfo.MchID);//商户号
    data.SetValue("wxappid", AccountInfo.AppID);//公众账号appid

    data.SetValue("sender_name", AccountInfo.Name);//红包发送者名称
    //发送者头像,此id为微信默认的头像(如果想自定义头像,请参见第三部分)
    data.SetValue("sender_header_media_id", "1G6nrLmr5EC3MMb_-zK1dDdzmd0p7cNliYu9V5w7o8K0"); 

    //以企业应用的名义发红包,企业应用id,整型,
    //可在企业微信管理端应用的设置页面查看。与sender_name互斥,二者只能填一个。
    //data.SetValue("agentid", "3010046");//    企业应用id

    //发放红包使用场景,红包金额大于200时必传 
    if (!string.IsNullOrEmpty(json.scene_id))
    {
        data.SetValue("scene_id", json.scene_id);
    }
    
    data.SetValue("re_openid", json.re_openid);//接受红包的用户.用户在wxappid下的openid。
    data.SetValue("total_amount", json.total_amount);//金额
    data.SetValue("wishing", json.wishing);//红包祝福语
    data.SetValue("act_name", json.act_name);//项目名称
    data.SetValue("remark", json.remark);//备注

    data.SetValue("workwx_sign", data.MakeWorkWxSign(AccountInfo.CorpPaySecret));//企业微信签名
    data.SetValue("sign", data.MakeSign(AccountInfo.PayAPIKey));

    //发送企业红包,很容易发生签名错误的情况,请检查以下内容
    //1、企业微信的CorpID/CorpSecret
    //2、企业微信的支付Secret和商户的API支付秘钥
    //3、参数不能多也不能少(重要),很多时候由于版本原因这里不小心多了一个total_num导致签名错误
    //4、商户平台的证书和密码是否正确

    var url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendworkwxredpack";
    return Helper.GetPayResultWithCert<SendRedPackResult>(data, url, AccountInfo.CertPath, AccountInfo.CertPassword);
}

其实以上很多参数大家应该都很了解,相对于来说MakeWorkWxSign 和 MakeSign 就是这里的关键处理,而前者正是很多人没有处理好的问题所在。

下面把相关函数贴出来,方便对照了解下吧,其实下面这些函数是放在WxPayData类里面,统一管理处理对应的签名的。

/// <summary>
/// 拼接用来签名的几个参数,发送红包和付款的签名字段不同
/// </summary>
/// <param name="isRedPack">是否为发送红包操作,或者是付款操作,两者需要签名的字段不同</param>
/// <returns></returns>
private string ToWorkWxUrl(bool isRedPack)
{
    List<string> paramRedPack = new List<string>() { "act_name", "mch_billno", "mch_id", "nonce_str", "re_openid", "total_amount", "wxappid" };
    List<string> paramPay = new List<string>() { "amount", "appid", "desc", "mch_id", "nonce_str", "openid", "partner_trade_no", "ww_msg_type" };

    string buff = "";
    foreach (KeyValuePair<string, object> pair in Values)
    {
        if (pair.Value != null && pair.Value.ToString() != "")
        {
            if (isRedPack)
            {
                //发送红包的签名字段
                if (paramRedPack.Contains(pair.Key))
                {

                    buff += pair.Key + "=" + pair.Value + "&";
                }
            }
            else
            {
                //付款的签名字段
                if (paramPay.Contains(pair.Key))
                {
                    buff += pair.Key + "=" + pair.Value + "&";
                }
            }
        }
    }
    buff = buff.Trim('&');
    return buff;
}

/// <summary>
/// 生成企业微信签名
/// </summary>
/// <param name="corpPaySecret">企业支付的Secret</param>
/// <param name="isRedPack">是否为发送红包操作,或者是付款操作,两者需要签名的字段不同</param>
/// <returns></returns>
public string MakeWorkWxSign(string corpPaySecret, bool isRedPack = true)
{
    //转url格式
    string str = ToWorkWxUrl(isRedPack);
    //在string后加入secret
    str += "&secret=" + corpPaySecret;
    //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>签名, sign字段不参加签名</returns>
public string MakeSign(string payAPIKey)
{
    //转url格式
    string str = ToUrl();
    //在string后加入API KEY
    str += "&key=" + payAPIKey;
    //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"));
    }
    //所有字符转为大写
    var result = sb.ToString().ToUpper();
    return result;
}

3、企业微信直接支付的接口

image
image

对照这些官方资料,我们可以编写对应的接口API来处理。

/// <summary>
/// 企业付款(请求需要双向证书)
/// 企业付款业务是基于微信支付商户平台的资金管理能力,为了协助商户方便地实现企业向个人付款,
/// 针对部分有开发能力的商户,提供通过API完成企业付款的功能。 比如目前的保险行业向客户退保、给付、理赔。
/// 企业付款将使用商户的可用余额,需确保可用余额充足。查看可用余额、充值、提现请登录商户平台“资金管理”进行操作。https://pay.weixin.qq.com/ 
/// 注意:与商户微信支付收款资金并非同一账户,需要单独充值。
/// </summary>
/// <param name="json">企业支付数据</param>
/// <returns></returns>
public CorpPayResult CorpPay(CorpPayJson json)
{
    CheckAccount();//检查AccountInfo的对象属性值

    WxPayData data = new WxPayData();
    data.SetValue("mch_appid", AccountInfo.AppID);//公众账号appid, 注意是mch_appid,而非wxappid
    data.SetValue("mchid", AccountInfo.MchID);//商户号, 注意是mchid而非mch_id
    data.SetValue("nonce_str", data.GenerateNonceStr());//随机字符串
    data.SetValue("spbill_create_ip", NetworkUtil.GetIPAddress());//终端ip      
    data.SetValue("partner_trade_no", data.GenerateOutTradeNo(AccountInfo.MchID));//随机字符串

    data.SetValue("device_info", json.device_info);//终端ip            
    data.SetValue("openid", json.openid);
    data.SetValue("check_name", json.check_name);
    data.SetValue("re_user_name", json.re_user_name);
    data.SetValue("amount", json.amount);
    data.SetValue("desc", json.desc);

    data.SetValue("sign", data.MakeSign(AccountInfo.PayAPIKey));//最后生成签名

    var url = string.Format("https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers");
    return Helper.GetPayResultWithCert<CorpPayResult>(data, url, AccountInfo.CertPath, AccountInfo.CertPassword);
}

其中里面的很多参数的处理是和前面支付差不多的,因此不再赘述

调用的处理代码如下所示

//构建处理信息
CorpPayJson json = new CorpPayJson()
{
    amount = 100,
    check_name = PayCheckName.FORCE_CHECK.ToString(),
    desc = "测试退款",
    openid = openid,
    device_info = "",
    re_user_name = "伍华聪",
    spbill_create_ip = NetworkUtil.GetIPAddress()
};
//直接付款到员工账号
var result = api.CorpPay(json);
image

最新的第一条就是直接付款的信息提示。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容