C#开发微信门户及应用(38)--微信摇一摇红包功能

906 查看

摇一摇周边红包接口是为线下商户提供的发红包功能。用户可以在商家门店等线下场所通过摇一摇周边领取商家发放的红包。我曾经在《C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现》介绍过微信摇一摇的相关管理,包括页面、设备之间的关系,以及使用等方面内容。本篇继续介绍摇一摇设备的另外一项功能,摇一摇红包功能,介绍如何利用微信摇摇周边的后台配置好页面及地址,然后通过微信JSSDK的方式,摇一摇获取红包的整个流程功能。

1、微信摇一摇红包功能介绍

功能说明
摇一摇周边红包接口是为线下商户提供的发红包功能。用户可以在商家门店等线下场所通过摇一摇周边领取商家发放的红包,在线上转发分享无效。
开发者可通过接口开发摇一摇红包功能,特点包括:
可选择使用模板加载页或自定义Html5页面调起微信原生红包页面(详见创建红包活动中use_template字段,1为使用模板,2为使用自定义Html5页面)
原生红包页面拆红包,无需通过公众号消息下发
提供关注公众号能力,用户可自行选择是否关注(裂变红包分享时无效)
完成页面可配置跳转链接,可跳转商户的其他自定义Html5页面
同一个用户在单个红包活动中只能领取1次红包

用户侧交互流程
常规的摇一摇红包的流程如下所示,这里没有使用用户自定义的模板,也就是使用系统内置的(努力加载中。。。)的页面,红包需要自己拆开。



红包组件接口调用流程
申请红包接口权限:登录摇一摇周边商户后台https://zb.weixin.qq.com ,进入开发者支持,申请开通摇一摇红包组件接口;
红包预下单:调用微信支付的api进行红包预下单,告知需要发放的红包金额,人数,生成红包ticket;
创建活动并录入红包信息:调用摇周边平台的api录入创建红包活动并录入信息,传入预下单时生成的红包ticket;
调用jsapi抽红包:在摇出的页面中通过调用jsapi抽红包,抽中红包的用户可以拆红包;
调用以上接口时,红包提供商户和红包发放商户公众号要求一致。

摇一摇红包的处理过程可以先的流程说明,申请权限后,需要在摇一摇后台配置相关的红包处理页面,然后通过红包接口处理提交红包数据,最后通过摇一摇的设备摇出界面,使用JSAPI实现抽取红包的操作,具体过程如下所示。



其中红包接口处理,是调用一系列的红包接口实现的,包括红包预下单、创建红包活动、录入红包信息等操作,如下所示。



摇一摇红包功能开通在后台获取接口即可,如下所示。



假设已经有相关的摇一摇设备(如果没有或者需要加入新的摇一摇设备,请参考上篇随笔《C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现》的相关介绍,有了设备后在页面管理中添加一个页面,用来处理摇一摇的信息的。



页面的信息,就是添加自定义链接界面,如下所示。



然后配置好URL地址接口,URL地址还需要考虑如何获取用户的openid,因为摇一摇红包接口必须要获得当前用户的openid信息,我们可以通过重定向的方式,使用code获取对应的openid信息,具体后面详细介绍。


2、红包接口处理

前面介绍了,红包接口处理,是调用一系列的红包接口实现的,包括红包预下单、创建红包活动、录入红包信息等操作,如下所示。



那么我们就需要分别对这些操作进行封装,并提交相关的数据了。
根据前面随笔《C#开发微信门户及应用(33)--微信现金红包的封装及使用》里面的规则,我们把摇一摇的相关接口也放在这个ILotteryApi接口和LotteryApi实现类里面,如下所示。



下面的摇一摇红包接口全部是基于上面的接口和实现类进行完善处理的。

1)红包预下单
接口说明
设置单个红包的金额,类型等,生成红包信息。预下单完成后,需要在72小时内调用jsapi完成抽红包的操作。(红包过期失效后,资金会退回到商户财付通帐号。)
接口调用说明

服务器端调用
http请求方式: POST
https://api.mch.weixin.qq.com/mmpaymkttransfers/hbpreorder
POST数据格式:XML
需要商户证书

请求示例

<xml>     
<sign><![CDATA[E1EE61A91C8E90F299DE6AE075D60A2D]]></sign>     
<mch_billno><![CDATA[0010010404201411170000046545]]></mch_billno>     
<mch_id><![CDATA[10000097]]></mch_id>     
<wxappid><![CDATA[wxcbda96de0b165486]]></wxappid>     
<send_name><![CDATA[send_name]]></send_name>     
<hb_type><![CDATA[NORMAL]]></hb_type>     
<auth_mchid><![CDATA[10000098]]></auth_mchid>     
<auth_appid><![CDATA[wx7777777]]></auth_appid>     
<total_amount><![CDATA[200]]></total_amount>     
<amt_type><![CDATA[ALL_RAND]]></amt_type>     
<total_num><![CDATA[3]]></total_num>     
<wishing><![CDATA[恭喜发财 ]]></wishing>     
<act_name><![CDATA[ 新年红包 ]]></act_name>     
<remark><![CDATA[新年红包 ]]></remark>     
<risk_cntl><![CDATA[NORMAL]]></risk_cntl>     
<nonce_str><![CDATA[50780e0cca98c8c8e814883e5caa672e]]></nonce_str>
</xml>

返回数据说明
返回格式为xml
成功示例

<xml>     
<return_code><![CDATA[SUCCESS]]></return_code>     
<return_msg><![CDATA[发放成功.]]></return_msg> 
<result_code><![CDATA[SUCCESS]]></result_code> 
<err_code><![CDATA[0]]></err_code>     
<err_code_des><![CDATA[发放成功.]]></err_code_des>     
<mch_billno><![CDATA[0010010404201411170000046545]]></mch_billno> 
<mch_id>10010404</mch_id>     
<wxappid><![CDATA[wx6fa7e3bab7e15415]]></wxappid>     
<sp_ticket><![CDATA[0cca98c8c8e814883]]></sp_ticket> 
<total_amount>3</total_amount>     
<detail_id><![CDATA[001001040420141117000004888]]></detail_id> 
<send_time><![CDATA[20150101080000]]></send_time> 
</xml>

失败示例

<xml>     
<return_code><![CDATA[FAIL]]></return_code>     
<return_msg><![CDATA[系统繁忙,请稍后再试.]]></return_msg>     
<result_code><![CDATA[FAIL]]></result_code>     
<err_code><![CDATA[268458547]]></err_code>     
<err_code_des><![CDATA[系统繁忙,请稍后再试.]]></err_code_des>     
<mch_billno><![CDATA[0010010404201411170000046542]]></mch_billno>             
<mch_id>10010404</mch_id>     
<wxappid><![CDATA[wx6fa7e3bab7e15415]]></wxappid>      
<total_amount>3</total_amount> 
</xml>

根据请求参数的说明,以及返回的结果,我们可以分别定义它们的传入参数和传出参数,具体的红包预下单的接口定义如下

/// <summary>
/// 红包预下单接口。需要商户证书
/// 设置单个红包的金额,类型等,生成红包信息。预下单完成后,需要在72小时内调用jsapi完成抽红包的操作。(红包过期失效后,资金会退回到商户财付通帐号。)
/// </summary>
/// <param name="data">传入参数数据</param>
/// <returns></returns>
LotteryPreOrderResult LotteryPreOrder(LotteryPreOrderData info);

其中LotteryPreOrderResult返回的对象结果如下所示,主要的信息是要记录sp_ticket,目前没有通过API接口获取已经预下单红包的sp_ticket信息,所以一定要先记录好,后面在录入红包的时候,需要使用到这个票据。

public class LotteryPreOrderResult : PayResult
{
    /// <summary>
    /// 商户订单号
    /// </summary>
    public string mch_billno { get; set; }
    /// <summary>
    /// 公众账号appid
    /// </summary>
    public string wxappid { get; set; }
    /// <summary>
    /// 总付款金额,单位分
    /// </summary>
    public int total_amount { get; set; }
    /// <summary>
    /// 一个普通红包对应一个ticket
    /// </summary>
    public string sp_ticket { get; set; }
    /// <summary>
    /// 红包内部订单号
    /// </summary>
    public string detail_id { get; set; }
    /// <summary>
    /// 红包发放时间 
    /// </summary>
    public string send_time { get; set; }
}

2)创建红包活动
接口说明
创建红包活动,设置红包活动有效期,红包活动开关等基本信息,返回活动id
接口调用说明

服务器端调用http请求方式: POST
URL: https://api.weixin.qq.com/shakearound/lottery/addlotteryinfo?access_token=ACCESSTOKEN&use_template=1&logo_url=”LOGO_URL”

请求参数说明
参数 类型 说明
access_token string
accesstoken,以参数的形式拼装在url后
use_template int
是否使用模板,1:使用,2:不使用,以参数的形式拼装在url后。(模版即交互流程图中的红包加载页,使用模板用户不需要点击可自动打开红包;不使用模版需自行开发HTML5页面,并在页面调用红包jsapi)
logo_url string
使用模板页面的logo_url,不使用模板时可不加。展示在摇一摇界面的消息图标。图片尺寸为120x120。

POST BODY:JSON格式的结构体,具体信息不在赘述,不过值得说明的是其中的Key是需要注意的,这个值一定需要使用一个确定的值,因为需要和后面的抽取红包的签名处理一致,否则会出错无法抽取红包。


请求示例

Content-Type: application/json Post Body:
{                                                            
 "title": "title",                           
 "desc": "desc",                             
 "onoff": 1,                                 
 "begin_time": 1428854400,                            
 "expire_time": 1428940800,                           
 "sponsor_appid": "wxxxxxxxxxxxxxx",
 "total": 10,
 "jump_url": JUMP_URL,     
 "key": "keyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"                            
}

返回数据说明
示例

{     
 "errcode":0,     
 "errmsg":"",     
 "lottery_id":"xxxxxxllllll", 
 "page_id":1, 
}

根据上面的接口定义和接口参数说明,我们可以定义创建红包活动的接口定义,如下所示

/// <summary>
/// 创建红包活动,设置红包活动有效期,红包活动开关等基本信息,返回活动id
/// </summary>
/// <param name="accessToken">调用接口凭证</param>
/// <param name="useTempate">是否使用模板</param>
/// <param name="login_url">使用模板页面的logo_url,不使用模板时可不加。展示在摇一摇界面的消息图标。图片尺寸为120x120。</param>
/// <returns></returns>
AddLotterResult AddLotteryInfo(string accessToken, bool useTempate, string login_url, AddLotteryJson json);

其中AddLotteryResult为我们定义的接口处理结果,主要需要记录其中的LotteryID,这个值需要在后面的录入红包接口使用到。

/// <summary>
/// 添加红包活动的结果
/// </summary>
public class AddLotterResult : ErrorJsonResult
{
    /// <summary>
    /// 生成的红包活动id
    /// </summary>
    public string lottery_id { get; set; }
    /// <summary>
    /// 生成的模板页面ID  
    /// </summary>
    public int page_id { get; set; }
}

3)录入红包信息
在红包预下单,以及创建红包活动后,就需要把前面两者的信息关联起来,这个操作就是录入红包信息,录入红包信息包括提交红包活动的ID,以及红包预下单的sp_ticket的票据信息,这样就可以让用户抽取具体的红包信息了。
接口说明
在调用"创建红包活动"接口之后,调用此接口录入红包信息。注意,此接口每次调用,都会向某个活动新增一批红包信息,如果红包数少于100个,请通过一次调用添加所有红包信息。如果红包数大于100,可以多次调用接口添加。请注意确保多次录入的红包ticket总的数目不大于创建该红包活动时设置的total值。
接口调用说明

服务器端调用http请求方式: POST
URL:https://api.weixin.qq.com/shakearound/lottery/setprizebucket?access_token=ACCESSTOKEN

POST BODY:JSON格式的结构体



请求示例

Content-Type: application/json Post Body:
{     
"lottery_id": "xxxxxxllllll",     
"mchid": "10000098",     
"sponsor_appid": "wx8888888888888888"     
"prize_info_list": [         
     {            
   "ticket": "v1|ZiPs2l0hpMBp3uwGI1rwp45vOdz/V/zQ/00jP9MeWT+e47/q1FJjwCIP34frSjzOxAEzJ7k2CtAg1pmcShvkChBWqbThxPm6MBuzceoHtj79iHuHaEn0WAO+j4sXnXnbGswFOlDYWg1ngvrRYnCY3g==",                                   
     }
   ]
}

返回数据说明
示例

{         
"errcode":0,     
"errmsg":"",     
"repeat_ticket_list":[         
     {            
"ticket": "v1|ZiPs2l0hpMBp3uwGI1rwp45vOdz/V/zQ/00jP9MeWT+e47/q1FJjwCIP34frSjzOxAEzJ7k2CtAg1pmcShvkChBWqbThxPm6MBuzceoHtj79iHuHaEn0WAO+j4sXnXnbGswFOlDYWg1ngvrRYnCY3g==",                       
     }            
  ]     
"success_num":100 
}

根据这些接口定义和传递参数信息,我们可以定义录入红包的接口。

/// <summary>
/// 录入红包信息
/// 在调用"创建红包活动"接口之后,调用此接口录入红包信息。
/// 注意,此接口每次调用,都会向某个活动新增一批红包信息,如果红包数少于100个,请通过一次调用添加所有红包信息。
/// 如果红包数大于100,可以多次调用接口添加。请注意确保多次录入的红包ticket总的数目不大于创建该红包活动时设置的total值。
/// </summary>
/// <param name="accessToken">调用接口凭证</param>
/// <param name="json">录入红包信息</param>
/// <returns></returns>
SetPrizeBucketResult SetPrizeBucket(string accessToken, SetPrizeBucketJson json);

其中录入红包返回的结果类SetPrizeBucketResult 定义如下所示。

/// <summary>
/// 录入红包返回的结果
/// </summary>
public class SetPrizeBucketResult : ErrorJsonResult
{
    /// <summary>
    /// 重复使用的ticket列表,如为空,将不返回
    /// </summary>
    public List<PrizeTicket> repeat_ticket_list { get; set; }
    /// <summary>
    /// 过期的ticket列表,如为空,将不返回
    /// </summary>
    public List<PrizeTicket> expire_ticket_list { get; set; }
    /// <summary>
    /// 金额不在大于1元,小于1000元的ticket列表,如为空,将不返回
    /// </summary>
    public List<PrizeTicket> invalid_amount_ticket_list { get; set; }
    /// <summary>
    /// 原因:生成红包的时候,授权商户号auth_mchid和auth_appid没有写摇周边的商户号
    /// </summary>
    public List<PrizeTicket> wrong_authmchid_ticket_list { get; set; }
    /// <summary>
    /// ticket解析失败,可能有错别字符或不完整    
    /// </summary>
    public List<PrizeTicket> invalid_ticket_list { get; set; }

    /// <summary>
    /// 成功录入的红包数量
    /// </summary>
    public int success_num { get; set; }
}

3、红包接口的使用及JSAPI的摇一摇页面处理

前面说明了相关的接口信息和对应的结果类的定义,我们具体在根据相关的说明,实现接口的处理就可以了,这个小节介绍如何使用这些接口,实现我们对摇一摇红包的整个流程的处理。

如第一步,调用红包预下单处理。

string sp_ticket = "";
/// <summary>
/// 红包预下单,并记录红包的ticket信息
/// </summary>
private void btnHbPreOrder_Click(object sender, EventArgs e)
{
    LotteryPreOrderData info = new LotteryPreOrderData()
    {
        total_amount = 100,
        total_num = 1,
        act_name = "恭喜发财",
        remark = "恭喜发财",
        wishing = "恭喜发财",
    };
    var result = hbApi.LotteryPreOrder(info);
    sp_ticket = result.sp_ticket;//赋值,方便下面的录入红包操作
    Console.WriteLine(XmlConvertor.FormatXml(result.ToXml()));
}

第二部创建红包活动,如下所示。

string lotteryId = "";
/// <summary>
/// 创建红包活动,并记录活动的ID
/// </summary>
private void btnCreateLottery_Click(object sender, EventArgs e)
{
    AddLotteryJson json = new AddLotteryJson()
    {
        begin_time = DateTime.Now.DateTimeToInt(),
        expire_time = DateTime.Now.AddDays(90).DateTimeToInt(),
        title = "测试红包",
        desc = "测试红包",
        sponsor_appid = this.AppId,
        onoff = 1,
        total = 1, 
        jump_url = "http://www.iqidi.com",
        key = accountInfo.PayAPIKey
    };

    var result = hbApi.AddLotteryInfo(token, false, null, json);
    lotteryId = result.lottery_id;//赋值方便查询红包

    Console.WriteLine(result.ToJson());
}

第三步,根据红包预下单结果和红包活动创建结果,录入红包信息

/// <summary>
/// 录入红包信息,供使用摇一摇页面处理
/// </summary>
private void btnSetHB_Click(object sender, EventArgs e)
{
    if (string.IsNullOrEmpty(sp_ticket))
    {
        MessageUtil.ShowError("红包票据ticket为空");
        return;
    }

    SetPrizeBucketJson json = new SetPrizeBucketJson()
    {
        lottery_id = lotteryId, //使用前面的红包活动
        mchid = accountInfo.MchID,
        sponsor_appid = accountInfo.AppID,
        prize_info_list = new List<PrizeTicket>()
        {
            new PrizeTicket() { ticket = sp_ticket},//使用前面的红包预下单ticket
        }
    };
    var result = hbApi.SetPrizeBucket(this.token, json);
    Console.WriteLine("录入红包的结果:");
    Console.WriteLine(result.ToJson());
}

第四步,配置好重定向的页面,方便获取用户的openid
1)用户同意授权,获取code
这个步骤,我们利用的是“网页授权获取用户基本信息”操作,其中相关的信息说明如下所示。
在确保微信公众账号拥有授权作用域(scope参数)的权限的前提下(服务号获得高级接口后,默认拥有scope参数中的snsapi_base和snsapi_userinfo),引导关注者打开如下页面:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect若提示“该链接无法访问”,请检查参数是否填写错误,是否拥有scope参数对应的授权作用域权限。

尤其注意:由于授权操作安全等级较高,所以在发起授权请求时,微信会对授权链接做正则强匹配校验,如果链接的参数顺序不对,授权页面将无法正常访问。

参考链接(请在微信客户端中打开此链接体验)
Scope为snsapi_base
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
Scope为snsapi_userinfo
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

尤其注意:跳转回调redirect_uri,应当使用https链接来确保授权code的安全性。
这样,如果我们配置的连接为http://www.iqidi.com/JSSDKTest/RedPack?uid=iqidiSoftware ,其中uid为我们对应的账号名称。
那么我们根据上面规则,得到重定向的连接地址就是如下所示。

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx3d81fc2886d86526&redirect_uri=http%3a%2f%2fwww.iqidi.com%2fJSSDKTest%2fRedPack%3fuid%3diqidiSoftware&response_type=code&scope=snsapi_base&state=state#wechat_redirect

2)通过code换取网页授权access_token
首先请注意,这里通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
尤其注意:由于公众号的secret和获取到的access_token安全级别都非常高,必须只保存在服务器,不允许传给客户端。后续刷新access_token、通过access_token获取用户信息等步骤,也必须从服务器发起。
请求方法
获取code后,请求以下链接获取access_token:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
参数说明



在具体的页面里面,我们可以获得相关的参数,如uid我们可以把它转换为我们对应的账号信息,如下所示。

/// <summary>
/// 根据账号名获取对应的对象
/// </summary>
/// <returns></returns>
private AccountInfo GetAccount()
{
    AccountInfo accountInfo = null;
    string accountNo = Request.QueryString["uid"];
    if (!string.IsNullOrEmpty(accountNo))
    {
        accountInfo = BLLFactory<Account>.Instance.FindByAccountNo(accountNo);
    }
    return accountInfo;
}

而其中的code,我们根据这个信息,也可以获得用户的相关信息,我们需要获得访问用户的openid,如下所示。

string code = Request.QueryString["code"];
IBasicApi baseApi = new BasicApi();
var result = baseApi.GetAuthToken(accountInfo.UniteAppId, accountInfo.UniteAppSecret, code);
if (result != null && !string.IsNullOrEmpty(result.openid))
{
    var openid = result.openid;

有了这些信息,我们就可以构建我们的红包参数,并封装好签名了。

WxPayData data = new WxPayData();
data.SetValue("openid", openid);
data.SetValue("lottery_id", lottery_id);
data.SetValue("noncestr", data.GenerateNonceStr());
data.SetValue("sign", data.MakeSign(accountInfo.PayAPIKey));

然后,我们在后台,把这些信息赋值给ViewBag,就可以在页面上顺利使用了。

var sign = data.GetString("sign");//获取生成的sign
var noncestr = data.GetString("noncestr");//获取生成的sign

ViewBag.sign = sign;
ViewBag.lottery_id = lottery_id;
ViewBag.openid = openid;
ViewBag.noncestr2 = noncestr;

在摇一摇红包的处理视图页面里面,我们先引入对应的js文件,如下所示

<title>摇一摇红包页面</title>
<link rel="stylesheet" href="http://demo.open.weixin.qq.com/jssdk/css/style.css?ts=1420774989">
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>
<script type="text/javascript" src="http://zb.weixin.qq.com/app/shakehb/BeaconShakehbJsBridge.js"></script>

然后在JS里面添加相关的处理函数,并赋值给对应的接口参数。

<script type="text/javascript">
BeaconShakehbJsBridge.ready(function () {
    //跳转到抽红包页面
    BeaconShakehbJsBridge.invoke('jumpHongbao', {
        lottery_id: '@ViewBag.lottery_id',
        noncestr: '@ViewBag.noncestr2',
        openid: '@ViewBag.openid',
        sign: '@ViewBag.sign'
    });
});
</script>

最后,我们测试相关的结果,使用手机摇一摇获得红包的过程界面效果如下所示。





如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章.