2023年2月

前面几篇介绍了微信支付方面的内容,本篇继续微信接口的一些其他方面的内容:卡劵管理。卡劵管理是微信接口里面非常复杂的一个部分,里面的接口非常多,我花了不少时间对它进行了封装处理,重构优化等等工作,卡劵在营销方面是一个比较好的途径,可以应用在会员管理、店铺促销等方面的活动,不过万层高楼从底起,我们需要把卡劵管理的相关接口夯实完善,才能在它的基础上进行更进一步的应用操作。

1、微信卡券接口说明

微信卡券功能是腾讯为商户提供的一套完整的电子卡券解决方案,商户可在法律允许的范围内通过该功能实现电子卡券生成、下发、领取、核销的闭环,并使用对账、卡券管理等配套功能。微信卡券功能可分为API接口功能和公众平台卡券功能,使用两种功能均可实现卡券生成、下发、领取、核销,有开发意愿的商户可使用API接口功能,无开发意愿商户可使用公众平台卡券功能。

微信公众平台本次增加了微信卡券功能,开放接口供商家使用。 支持开发者调用接口创建多种类型的卡券,通过下发消息、二维码、JS-SDK等方式进行投放,在用户使用时通过API接口或卡券商户助手完成核销。 同时支持接口获取统计数据,以及各个环节给予开发者事件推送。

目前支持优惠券(代金券、折扣券、礼品券、团购券)、会员卡、景点门票、电影票、飞机票、红包、会议门票等多种卡券类型。 开发者可以通过卡券接口快速完成制券、发券及销券流程:

1、创建卡券接口
开发者可通过该接口,创建卡券,导入/拉取卡券适用门店、获取卡券颜色列表。

2、卡券投放接口
开发者可通过该接口,生成卡券领取二维码,也可在网页内调用JavaScript接口,引导用户领取卡券。

3、卡券核销接口

调用核销接口可对指定卡券进行核销。支持网页内调用JavaScript接口拉取卡券列表,用户选择卡券后即可完成核销。

4、卡券管理接口
开发者可通过该接口,对已创建的卡券进行查询、删除、更改、设置失效等操作。同时,在卡券通过审核、卡券被领取、卡券被删除时,均会推送事件通知开发者。

5、特殊卡票接口
支持特殊卡票券(会员卡、电影票、飞机票、红包、会议门票)的适用场景,提供相应的接口能力,包括激活/绑定会员卡、会员卡交易、更新电影票、在线选座、更新红包余额、更新会议门票等接口。

6、设置测试用户白名单
开发者可设置测试用户白名单,无论卡券是否通过审核均可领取卡券,测试整个卡券的使用流程。

为了了解这个卡劵的复杂性,我们先来看看它的官方的卡劵内容流程图

这个图里面涉及的内容很多,同样卡劵管理的API接口也很多,不过我们总是希望化繁为简,因此我们可以一步步来了解整个卡劵的内容。

2、卡劵的事件通知

卡劵的相关事件,会由微信后台通知我们的服务后台,因此我们可以对卡劵的创建、使用等各个方面都有相关的事件通知,我们在对应的事件上实现我们的卡劵管理逻辑也是很方便的。

下面列出卡劵管理里面的后台消息通知分类。

这些消息对应的事件,我们可以放到请求的事件类型里面,这样我们在统一调用事件的时候,就可以对他们进行区分了。

这样我们在微信消息处理的入口,就可以分别对这些事件进行处理了。WeixinApiDispatch就是一个分发的管理类,它提取请求消息的内容,并构建不同类型的消息参数,传递给不同的响应函数进行处理,然后返回封装好的XML内容,作为响应。

具体的代码处理逻辑如下图所示。

这样我们在代码里面就可以对相应个事件进行处理了。

其中我们注意到,我们对卡劵的不同事件,把它们的事件信息对象化后进行相应的处理的,如下代码所示。

                            case RequestEvent.card_pass_check:      //卡劵通过审核
                            case RequestEvent.card_not_pass_check:  //卡劵未通过审核
{//卡券通过审核(或审核不通过)
                                    RequestEventCardCheck info = XmlConvertor.XmlToObject<RequestEventCardCheck>(postStr);if (info != null)
{
}
LogTextHelper.Info(eventName
+ ((info == null) ? "info is null": info.ToJson()));
}
break;

3、卡劵的分类及创建操作

1)卡劵分类

前面介绍了,微信卡劵目前支持优惠券(代金券、折扣券、礼品券、团购券)、会员卡、景点门票、电影票、飞机票、红包、会议门票等多种卡券类型。我们在微信后台,可以手工创建优惠卷,如下图所示。

由于各个卡劵之间的数据有相同的部分,也有部分的部分,我们需要在类的层面上对他们进行不同的信息建模。

我们再来定义一个卡劵类型的枚举,方便我们在代码中使用,这个枚举对象也包含了我们前面介绍到的那些卡劵类型了。

    /// <summary>
    ///卡券类型/// </summary>
    public enumCardType
{
/// <summary> ///折扣券/// </summary> DISCOUNT = 0,/// <summary> ///代金券/// </summary> CASH = 1,/// <summary> ///礼品劵、兑换券/// </summary> GIFT = 2,/// <summary> ///优惠券/通用券/// </summary> GENERAL_COUPON = 3,/// <summary> ///团购券/// </summary> GROUPON = 4,/// <summary> ///会员卡/// </summary> MEMBER_CARD = 5,/// <summary> ///门票/// </summary> SCENIC_TICKET = 6,/// <summary> ///电影票/// </summary> MOVIE_TICKET = 7,/// <summary> ///飞机票/// </summary> BOARDING_PASS = 8,/// <summary> ///红包/// </summary> LUCKY_MONEY = 9,/// <summary> ///会议门票/// </summary> MEETING_TICKET = 10,/// <summary> ///汽车票/// </summary> BUS_TICKET,
}

由于不同类型卡劵的信息不同,因此我们需要封闭创建这些对应的卡劵类,以方便构建对应的信息用于创建操作。

2)创建卡券

创建卡券的接口调用顺序

其中上传图片,就是采用通用的图片上传接口上传即可,上传后获得对应的图片URL地址。

上传图片接口调用请求说明

HTTP请求方式: POST/FROM
URL:https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN

优惠劵的背景色,在微信里面有一些参考色样,如下图所示。

创建卡券接口是微信卡券的基础接口,用于创建一类新的卡券,获取card_id,创建成功并通过审核后,商家可以通过文档提供的其他接口将卡券下发给用户,每次成功领取,库存数量相应扣除。

接口调用请求说明

HTTP请求方式: POST
URL: https://api.weixin.qq.com/card/create?access_token=ACCESS_TOKEN

参数说明

参数 是否必须 说明
access_token 调用接口凭证
POST数据 Json数据

几种不同类型的卡劵,他们POST的JSON对象信息都是不太一样的,如团购劵的实体信息如下所示。

团购券

参数名 必填 类型 示例值 描述
card_type string(24) GROUPON 团购券类型。
base_info JSON结构 见上述示例。 基本的卡券数据
,见下表,所有卡券类型通用。
deal_detail string(3072) 双人套餐\n -进口红酒一支。\n孜然牛肉一份。 团购券专用,团购详情。

团购劵JSON示例

{"card": {"card_type": "GROUPON","groupon": {"base_info": {
················
},
"advanced_info": {
················
},
"deal_detail": "示例"}
}
}

而代金券提交的信息如下所示。

代金券

参数名 必填 类型 示例值 描述
card_type string(24) CASH 代金券类型。
base_info JSON结构 见上述示例。 基本的卡券数据,见下表,所有卡券通用。
least_cost int 10000 代金券专用,表示起用金额(单位为分),如果无起用门槛则填0。
reduce_cost int 10000 代金券专用,表示减免金额。(单位为分)

代金券JSON示例

{"card": {"card_type": "CASH","cash": {"base_info": {
················
},
"advanced_info": {
················
},
"least_cost": 1000,"reduce_cost": 100,
}
}
}

当前其他几种类型个卡劵也各有不同,不在一一赘述,可以看到每种卡劵携带的信息,有部分一样,有部分不同,但是它们创建卡劵的时候,使用的是同一个接口,这种接口方式在卡劵接口里面很常见。

其中卡劵里面的base_info(卡券基础信息)字段-必填字段、base_info(卡券基础信息)字段-非必填字段、Advanced_info(卡券高级信息)字段比较复杂,具体请参考相关的字段说明列表。

创建卡劵的返回说明

数据示例:

{"errcode":0,"errmsg":"ok","card_id":"p1Pj9jr90_SQRaVqYI239Ka1erkI"}

参数名 描述
errcode 错误码,0为正常。
errmsg 错误信息。
card_id 卡券ID。

4、创建卡劵的类定义和API封装

根据这些信息,我们创建卡劵的时候,我们可以定义不同的信息实体,如下所示是卡劵基类和折扣劵的类定义信息。

    /// <summary>
    ///卡劵基类信息/// </summary>
    public classCardJson
{
/// <summary> ///基础信息/// </summary> public CardBaseInfo base_info { get; set; }/// <summary> ///高级字段/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public CardAdvanceInfo advanced_info{ get; set; }
}
/// <summary> ///折扣券数据/// </summary> public classDisCountCardJson : CardJson
{
/// <summary> ///折扣券专用,表示打折额度(百分比)。填30就是七折。/// </summary> [JsonProperty(DefaultValueHandling =DefaultValueHandling.Ignore)]public int discount { get; set; }
}

其他卡劵的信息也是类似,根据需要扩展即可,如会员卡的信息,我们可以按照上面的继承关系进行字段的补充即可。

    /// <summary>
    ///会员卡的详细信息,是CardDetailJson的子类/// </summary>
    public classMemberCardJson : CardJson
{
/// <summary> ///显示积分,填写true或false,如填写true,积分相关字段均为必填。/// </summary> [JsonProperty(DefaultValueHandling =DefaultValueHandling.Ignore)]public bool supply_bonus { get; set; }/// <summary> ///是否支持储值,填写true或false。如填写true,储值相关字段均为必填。/// </summary> [JsonProperty(DefaultValueHandling =DefaultValueHandling.Ignore)]public bool supply_balance { get; set; }/// <summary> ///特权说明///非必填/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public string prerogative { get; set; }/// <summary> ///设置为true时用户领取会员卡后系统自动将其激活,无需调用激活接口/// </summary> [JsonProperty(DefaultValueHandling =DefaultValueHandling.Ignore)]public bool auto_activate { get; set; }/// <summary> ///设置为true时会员卡支持一键激活,不允许同时传入activate_url字段,否则设置wx_activate失效。///非必填/// </summary> [JsonProperty(DefaultValueHandling =DefaultValueHandling.Ignore)]public bool wx_activate { get; set; }

..........

还有其他类型的数据,如会议卡劵,电影卡劵信息等类库也一样处理,其他的依照此规则扩展即可。

    /// <summary>
    ///会议门票数据/// </summary>
    public classMettingTicketJson : CardJson
{
/// <summary> ///会议详情/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public string meeting_detail { get; set; }/// <summary> ///会场导览图/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public string map_url { get; set; }
}
/// <summary> ///门票数据/// </summary> public classScenicTicketJson : CardJson
{
/// <summary> ///票类型,例如平日全票,套票等///非必填/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public string ticket_class { get; set; }/// <summary> ///导览图url///非必填/// </summary> [JsonProperty(NullValueHandling =NullValueHandling.Ignore)]public string guide_url { get; set; }
}

有了这些信息,我们就可以通过统一的接口函数进行卡劵的创建操作了。

在实现函数的最后,我们就是调用接口的URL,提交对应的数据就可以了

            var url = string.Format("https://api.weixin.qq.com/card/create?access_token={0}", accessToken);var result = JsonHelper<CardCreateResultJson>.ConvertJson(url, cardData);return result != null ? result.card_id : null;

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

C#开发微信门户及应用(36)--微信卡劵管理的封装操作

C#开发微信门户及应用(35)--微信支付之企业付款封装操作

C#开发微信门户及应用(34)--微信裂变红包

C#开发微信门户及应用(33)--微信现金红包的封装及使用

C#开发微信门户及应用(32)--微信支付接入和API封装使用

C#开发微信门户及应用(31)--微信语义理解接口的实现和处理

C#开发微信门户及应用(30)--消息的群发处理和预览功能

C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现

C#开发微信门户及应用(27)-公众号模板消息管理

C#开发微信门户及应用(26)-公众号微信素材管理

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

微信公众号,仿照企业号的思路,增加了标签管理的功能,对关注的粉丝可以设置标签管理,实现更加方便的分组管理功能。开发者可以使用用户标签管理的相关接口,实现对公众号的标签进行创建、查询、修改、删除等操作,也可以对用户进行打标签、取消标签等操作。本篇随笔主要介绍如何利用C#对公众号这个较新的特性进行封装,实现对标签的管理功能。

1、标签功能介绍

1)标签功能替代分组功能,支持多维度定义用户属性

运营者可登录公众平台后台,点击左侧菜单“用户管理”后管理已关注用户,点击其中一个用户右侧的“标签”小角标可弹出复选框快速为该用户添加标签,且添加标签复选框内已支持直接新建标签。

另外,标签功能接口也已更新,开发者可调用接口,实现对标签和用户操作。同时,高级群发接口和个性化菜单接口等已支持根据标签实现操作。详情可查看
微信公众平台开发者文档

2)优化用户卡片,支持多场景查看用户头像大图与互动数据,增强运营者对用户熟悉度与管理效率

本期新增消息数、留言数和赞赏数等互动数据,且显示用户关注时间和支持查看用户头像大图,有利于拉近运营者与用户的距离。另外, 支持“赞赏功能”和“留言管理”场景直接管理用户,提升管理效率。

2、标签的接口封装

标签的接口分为两部分:标签管理和用户管理,一个公众号,最多可以创建100个标签。标签功能目前支持公众号为用户打上最多三个标签。

其中标签管理包括:

1) 创建标签  2)获取公众号已创建的标签  3)编辑标签  4)删除标签  5)获取标签下粉丝列表

标签的用户管理包括:
1)批量为用户打标签  2)批量为用户取消标签  3)获取用户身上的标签列表

下面分别为几种接口封装进行相关的介绍。

首先按照常规的方式 ,我们定义好相关的接口和实现关系,如下图所示。

根据相关的接口说明,我们可以实现标签接口的定义,C#代码如下所示。

    /// <summary>
    ///微信标签管理的API接口///开发者可以使用用户标签管理的相关接口,实现对公众号的标签进行创建、查询、修改、删除等操作,也可以对用户进行打标签、取消标签等操作。/// </summary>
    public interfaceITagApi
{
/// <summary> ///创建标签///一个公众号,最多可以创建100个标签。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="name">标签名(30个字符以内)</param> /// <returns></returns> TagJson CreateTag(string accessToken, stringname);/// <summary> ///获取公众号已创建的标签/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <returns></returns> List<TagCountJson> GetTagList(stringaccessToken);/// <summary> ///编辑标签/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="id">标签ID</param> /// <param name="name">标签名称</param> /// <returns></returns> CommonResult UpdateTag(string accessToken, int id, stringname);/// <summary> ///删除标签/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="id">标签ID</param> /// <returns></returns> CommonResult DeleteTag(string accessToken, intid);/// <summary> ///获取标签下粉丝列表/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="id">标签ID</param> /// <param name="name">标签名称</param> /// <returns></returns> GetTagResult GetTag(string accessToken, int id, string next_openid = null);/// <summary> ///批量为用户打标签///标签功能目前支持公众号为用户打上最多三个标签。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="tagid">标签ID</param> /// <param name="openid_list">粉丝列表</param> /// <returns></returns> CommonResult BatchTagging(string accessToken, int tagid, List<string>openid_list);/// <summary> ///批量为用户取消标签///标签功能目前支持公众号为用户打上最多三个标签。/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="tagid">标签ID</param> /// <param name="openid_list">粉丝列表</param> /// <returns></returns> CommonResult BatchUntagging(string accessToken, int tagid, List<string>openid_list);/// <summary> ///获取用户身上的标签列表/// </summary> /// <param name="accessToken">调用接口凭证</param> /// <param name="openid">用户OpenID</param> /// <returns></returns> List<int> GetIdList(string accessToken, stringopenid);
}

具体我们来看看几个接口的官方定义数据。

1)创建标签的接口

接口调用请求说明

http请求方式:POST(请使用https协议)
https:
//api.weixin.qq.com/cgi-bin/tags/create?access_token=ACCESS_TOKEN POST数据格式:JSON
POST数据例子:
{
"tag": {"name" : "广东"//标签名 }
}

返回说明(正常时返回的json数据包示例)

{"tag":{"id":134,//标签id
"name":"广东"}
}

这样我们就可以定义一个实体类来承载这个返回的数据了。

    /// <summary>
    ///标签信息/// </summary>
    public classTagJson
{
/// <summary> ///标签id,由微信分配/// </summary> public int id { get; set; }/// <summary> ///标签名,UTF8编码/// </summary> public string name { get; set; }
}

这样,创建标签的完整实现代码如下所示

        /// <summary>
        ///创建标签///一个公众号,最多可以创建100个标签。/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <param name="name">标签名(30个字符以内)</param>
        /// <returns></returns>
        public TagJson CreateTag(string accessToken, stringname)
{
var url = string.Format("https://api.weixin.qq.com/cgi-bin/tags/create?access_token={0}", accessToken);var data = new{
tag
= new{
name
=name
}
};
var postData =data.ToJson();var result = JsonHelper<TagCreateResult>.ConvertJson(url, postData);return result != null ? result.tag : null;
}

2)获取公众号已创建的标签

接口调用请求说明

http请求方式:GET(请使用https协议)
https:
//api.weixin.qq.com/cgi-bin/tags/get?access_token=ACCESS_TOKEN
返回数据格式:JSON
返回的数据例子:
{
"tags":[{"id":1,"name":"黑名单","count":0 //此标签下粉丝数 },{"id":2,"name":"星标组","count":0},{"id":127,"name":"广东","count":5}
]
}

这样我们就可以定义其返回值为List<TagCountJson>了,其中TagCountJson为对应类的属性即可。

    /// <summary>
    ///标签信息和统计数量/// </summary>
    public classTagCountJson : TagJson
{
/// <summary> ///此标签下粉丝数/// </summary> public int count { get; set; }
}

这样我们就可以通过JSON的解析返回对应的数据了,相关操作和上面的接口类似,不在赘述。

本篇提供了一个管理标签的相关接口思路和相关实现代码,希望大家可以有所启发和帮助。

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

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

C#开发微信门户及应用(37)--微信公众号标签管理功能

C#开发微信门户及应用(36)--微信卡劵管理的封装操作

C#开发微信门户及应用(35)--微信支付之企业付款封装操作

C#开发微信门户及应用(34)--微信裂变红包

C#开发微信门户及应用(33)--微信现金红包的封装及使用

C#开发微信门户及应用(32)--微信支付接入和API封装使用

C#开发微信门户及应用(31)--微信语义理解接口的实现和处理

C#开发微信门户及应用(30)--消息的群发处理和预览功能

C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现

C#开发微信门户及应用(27)-公众号模板消息管理

C#开发微信门户及应用(26)-公众号微信素材管理

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

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

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

功能说明

摇一摇周边红包接口是为线下商户提供的发红包功能。用户可以在商家门店等线下场所通过摇一摇周边领取商家发放的红包,在线上转发分享无效。

开发者可通过接口开发摇一摇红包功能,特点包括:

  1. 可选择使用模板加载页或自定义Html5页面调起微信原生红包页面(详见创建红包活动中use_template字段,1为使用模板,2为使用自定义Html5页面)
  2. 原生红包页面拆红包,无需通过公众号消息下发
  3. 提供关注公众号能力,用户可自行选择是否关注(裂变红包分享时无效)
  4. 完成页面可配置跳转链接,可跳转商户的其他自定义Html5页面
  5. 同一个用户在单个红包活动中只能领取1次红包

用户侧交互流程

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

红包组件接口调用流程

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

摇一摇红包的处理过程可以先的流程说明,申请权限后,需要在摇一摇后台配置相关的红包处理页面,然后通过红包接口处理提交红包数据,最后通过摇一摇的设备摇出界面,使用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[0010010404201466666670000046545]]></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[0010010404201466666670000046545]]></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[001001040420146666667000004888]]></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[0010010404201466666670000046542]]></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 classLotteryPreOrderResult : 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是需要注意的,这个值一定需要使用一个确定的值,因为需要和后面的抽取红包的签名处理一致,否则会出错无法抽取红包。

参数 类型 说明
title string 抽奖活动名称(选择使用模板时,也作为摇一摇消息主标题),最长6个汉字,12个英文字母。
desc string 抽奖活动描述(选择使用模板时,也作为摇一摇消息副标题),最长7个汉字,14个英文字母。
onoff int 抽奖开关。0关闭,1开启,默认为1
begin_time long 抽奖活动开始时间,unix时间戳,单位秒
expire_time long 抽奖活动结束时间,unix时间戳,单位秒,红包活动有效期最长为91天
sponsor_appid string 红包提供商户公众号的appid,需与预下单中的公众账号appid(wxappid)一致
total long 红包总数,红包总数是录入红包ticket总数的上限,因此红包总数应该大于等于预下单时红包ticket总数。
jump_url string 红包关注界面后可以跳转到第三方自定义的页面
key string 开发者自定义的key,用来生成活动抽奖接口的签名参数,长度32位。使用方式见sign生成规则

请求示例

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 classAddLotterResult : 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格式的结构体

参数 类型 说明
lottery_id string 红包抽奖id,来自addlotteryinfo返回的lottery_id
mchid string 红包提供者的商户号,,需与预下单中的商户号mch_id一致
sponsor_appid string 红包提供商户公众号的appid,需与预下单中的公众账号appid(wxappid)一致
prize_info_list json数组 红包ticket列表,如果红包数较多,可以一次传入多个红包,批量调用该接口设置红包信息。每次请求传入的红包个数上限为100
ticket string 预下单时返回的红包ticket,单个活动红包ticket数量上限为100000个,可添加多次。

请求示例

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 classSetPrizeBucketResult : 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(objectsender, EventArgs e)
{
LotteryPreOrderData info
= newLotteryPreOrderData()
{
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(objectsender, EventArgs e)
{
AddLotteryJson json
= newAddLotteryJson()
{
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(objectsender, EventArgs e)
{
if (string.IsNullOrEmpty(sp_ticket))
{
MessageUtil.ShowError(
"红包票据ticket为空");return;
}

SetPrizeBucketJson json
= newSetPrizeBucketJson()
{
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>
        privateAccountInfo GetAccount()
{
AccountInfo accountInfo
= null;string accountNo = Request.QueryString["uid"];if (!string.IsNullOrEmpty(accountNo))
{
accountInfo
= BLLFactory<Account>.Instance.FindByAccountNo(accountNo);}returnaccountInfo;
}

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

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

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

                    WxPayData data = newWxPayData();
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>
    <linkrel="stylesheet"href="http://demo.open.weixin.qq.com/jssdk/css/style.css?ts=1420774989">
    <scripttype="text/javascript"src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>
    <scripttype="text/javascript"src="http://zb.weixin.qq.com/app/shakehb/BeaconShakehbJsBridge.js"></script>

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

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

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



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

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

C#开发微信门户及应用(37)--微信公众号标签管理功能

C#开发微信门户及应用(36)--微信卡劵管理的封装操作

C#开发微信门户及应用(35)--微信支付之企业付款封装操作

C#开发微信门户及应用(34)--微信裂变红包

C#开发微信门户及应用(33)--微信现金红包的封装及使用

C#开发微信门户及应用(32)--微信支付接入和API封装使用

C#开发微信门户及应用(31)--微信语义理解接口的实现和处理

C#开发微信门户及应用(30)--消息的群发处理和预览功能

C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现

C#开发微信门户及应用(27)-公众号模板消息管理

C#开发微信门户及应用(26)-公众号微信素材管理

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

随着微信开逐步开放更多JSSDK的接口,我们可以利用自定义网页的方式来调用更多微信的接口,实现我们更加丰富的界面功能和效果,例如我们可以在页面中调用各种手机的硬件来获取信息,如摄像头拍照,GPS信息、扫描二维码等等,本篇介绍如何利用这些JSSDK接口实现签到的功能,其中签到需要报送地理坐标和地址,调用摄像头实时拍照,以及获取当前用户的相关信息等等。

1、JSSDK的说明

微信JS-SDK是
微信公众平台
面向网页开发者提供的基于微信内的网页开发工具包。通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照、选图、语音、位置等手机系统的能力,同时可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力,为微信用户提供更优质的网页体验。

目前JSSDK支持的接口分类包括下面几类:基础接口、分享接口、图像接口、音频接口、智能接口、设备信息、地理位置、摇一摇周边、界面操作、微信扫一扫、微信小店、微信卡券、微信支付,随着微信功能的全部整合,估计更多的接口会陆续开放出来。

在微信的后台进入【开发者文档】模块,我们可以看到对应的JSSDK的功能分类和介绍,如下所示。

从右侧我们可以详细看到各个接口的使用说明,基本上JSSDK的使用方法都类似,因此调试通过并掌握其中一两个,其他的也就依葫芦画瓢,照着做就可以了。

1)JSSDK使用步骤

步骤一:绑定域名

先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。如下所示,在公众平台进行设置。

备注:登录后可在“开发者中心”查看对应的接口权限。

步骤二:引入JS文件

在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js

如需使用摇一摇周边功能,请引入 http://res.wx.qq.com/open/js/jweixin-1.1.0.js

备注:支持使用 AMD/CMD 标准模块加载方法加载

当然,我们一般编辑页面,为了方便实现更多的效果,可能还会引入其他JS,如JQuery的类库等等。还有,我们还可以基于WeUI的jquery-weui类库,实现更加丰富的功能,如下是我们案例代码里面的JS引用。

    <scriptsrc="~/Content/wechat/jquery-weui/lib/jquery-2.1.4.js"></script>
    <scriptsrc="~/Content/wechat/jquery-weui/js/jquery-weui.js"></script>
    <scripttype="text/javascript"src="http://res.wx.qq.com/open/js/jweixin-1.1.0.js"></script>

步骤三:通过config接口注入权限验证配置

所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。

wx.config({
debug:
true, //开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。appId:'', //必填,公众号的唯一标识timestamp: ,//必填,生成签名的时间戳nonceStr:'', //必填,生成签名的随机串signature:'',//必填,签名,见附录1jsApiList: []//必填,需要使用的JS接口列表,所有JS接口列表见附录2 });

以上的配置就是JSSDK的核心所在,里面需要配置好对应的appid,还有timestamp,nonceStr这些都没有特别之处,最值得注意的是signature的实现机制,这样我们在后台生成好对应的值,赋给JS页面就可以了,这样也是最为安全的做法。

如下代码是我们实际项目里面,在Asp.net的视图页面里面的HTML代码,如下所示。

    <scriptlanguage="javascript">
    varappid= '@ViewBag.appid';varnoncestr= '@ViewBag.noncestr';varsignature= '@ViewBag.signature';vartimestamp= '@ViewBag.timestamp';

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp,//必填,生成签名的时间戳 nonceStr: noncestr,//必填,生成签名的随机串 signature: signature,//必填,签名,见附录1 jsApiList: ['checkJsApi','onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone','hideMenuItems','showMenuItems','hideAllNonBaseMenuItem','showAllNonBaseMenuItem','translateVoice','startRecord','stopRecord','onVoiceRecordEnd','playVoice','onVoicePlayEnd','pauseVoice','stopVoice','uploadVoice','downloadVoice','chooseImage','previewImage','uploadImage','downloadImage','getNetworkType','openLocation','getLocation','hideOptionMenu','showOptionMenu','closeWindow','scanQRCode','chooseWXPay','openProductSpecificView','addCard','chooseCard','openCard']
});

步骤四:通过ready接口处理成功验证

wx.ready(function(){//config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,
//则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});

这个ready的接口,也就是在页面顺利加载完毕后的处理内容了,一般我们需要做很多操作,都是需要在页面加载完毕后才能调用相关的对象进行赋值、处理等操作。

例如我们在页面ready后,获取对应的GPS坐标等操作,可以用下面的JS代码实现。

        wx.ready(function() {
wx.getLocation({
type:
'wgs84', //默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02' success: function(res) {var latitude = res.latitude; //纬度,浮点数,范围为90 ~ -90 var longitude = res.longitude; //经度,浮点数,范围为180 ~ -180。 var speed = res.speed; //速度,以米/每秒计 var accuracy = res.accuracy; //位置精度 $("#lblLoacation").text(latitude + "," +longitude);
}
});
});

步骤五:通过error接口处理失败验证

wx.error(function(res){//config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,
    //也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
});

这个error接口也就是用来处理异常信息的,一般情况下可以在这里提示用户出现的错误。

2)、签名算法

签名生成规则如下:参与签名的字段包括noncestr(随机字符串), 有效的jsapi_ticket, timestamp(时间戳), url(当前网页的URL,不包含#及其后面部分) 。对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1。这里需要注意的是所有参数名均为小写字符。对string1作sha1加密,字段名和字段值都采用原始值,不进行URL 转义。

即signature=sha1(string1)。 示例:

noncestr=Wm3WZYTPz0wzccnW

jsapi_ticket
=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg

timestamp
=1414587457url=http://mp.weixin.qq.com?params=value

步骤1. 对所有待签名参数按照字段名的ASCII 码从小到大排序(字典序)后,使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1:

jsapi_ticket=sM4AOVdWfPE4DxkXGEs8VMCPGGVi4C3VM0P37wVUCFvkVAy_90u5h9nbSlYy3-Sl-HhTdfl2fzFy1AOcHKP7qg&noncestr=Wm3WZYTPz0wzccnW&timestamp=1414587457&url=http://mp.weixin.qq.com?params=value

步骤2. 对string1进行sha1签名,得到signature:

0f9de62fce790f9a083d5c99e95740ceb90c27ed

注意事项

1.签名用的noncestr和timestamp必须与wx.config中的nonceStr和timestamp相同。

2.签名用的url必须是调用JS接口页面的完整URL。

3.出于安全考虑,开发者必须在服务器端实现签名的逻辑。

如出现invalid signature 等错误详见附录5常见错误及解决办法。

以上就是JSSDK总体的使用流程,虽然看起来比较抽象,但是基本上也就是这些步骤了。

上面的过程是具体的参数处理逻辑,我们要对应到C#代码的签名实现,需要对几个变量进行处理,下面是对应的生成noncestr、timestamp、以及签名等操作的代码。

        /// <summary>
        ///生成时间戳,标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数/// </summary>
        /// <returns>时间戳</returns>
        private static stringGetTimeStamp()
{
TimeSpan ts
= DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);returnConvert.ToInt64(ts.TotalSeconds).ToString();
}
/// <summary> ///生成随机串,随机串包含字母或数字/// </summary> /// <returns>随机串</returns> private static stringGetNonceStr()
{
return Guid.NewGuid().ToString().Replace("-", "");
}

还有我们要实现JSSDK签名的处理,必须先根据几个变量,构建好URL字符串,具体的处理过程,我们可以把它们逐一放在一个Hashtable里面,如下代码所示。

        /// <summary>
        ///获取JSSDK所需要的参数信息,返回Hashtable结合/// </summary>
        /// <param name="appId">微信AppID</param>
        /// <param name="jsTicket">根据Token获取到的JSSDK ticket</param>
        /// <param name="url">页面URL</param>
        /// <returns></returns>
        public static Hashtable GetParameters(string appId, string jsTicket, stringurl)
{
string timestamp =GetTimeStamp();string nonceStr =GetNonceStr();//这里参数的顺序要按照 key 值 ASCII 码升序排序 string rawstring = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url + "";string signature =GetSignature(rawstring);
Hashtable signPackage
= newHashtable();
signPackage.Add(
"appid", appId);
signPackage.Add(
"noncestr", nonceStr);
signPackage.Add(
"timestamp", timestamp);
signPackage.Add(
"url", url);
signPackage.Add(
"signature", signature);
signPackage.Add(
"jsapi_ticket", jsTicket);
signPackage.Add(
"rawstring", rawstring);returnsignPackage;
}

我们注意到URL参数的字符串组合:

string rawstring = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url + "";

这里我们拼接好URL参数后,就需要使用签名的规则来实现签名的处理了,签名的代码如下所示,注释代码和上面代码等同。

        /// <summary>
        ///使用SHA1哈希加密算法生成签名/// </summary>
        /// <param name="rawstring">待处理的字符串</param>
        /// <returns></returns>
        private static string GetSignature(stringrawstring)
{
return FormsAuthentication.HashPasswordForStoringInConfigFile(rawstring, "SHA1").ToLower();////下面和上面代码等价 //SHA1 sha1 = new SHA1CryptoServiceProvider();//byte[] bytes_sha1_in = System.Text.UTF8Encoding.Default.GetBytes(rawstring);//byte[] bytes_sha1_out = sha1.ComputeHash(bytes_sha1_in);//string signature = BitConverter.ToString(bytes_sha1_out);//signature = signature.Replace("-", "").ToLower();//return signature; }

这样我们有了对应的值后,我们就可以把它们的参数全部放在集合里面了供使用。

        /// <summary>
        ///获取用于JS-SDK的相关参数列表(该方法对accessToken和JSTicket都进行了指定时间的缓存处理,多次调用不会重复生成)///集合里面包括jsapi_ticket、noncestr、timestamp、url、signature、appid、rawstring/// </summary>
        /// <param name="appid">应用ID</param>
        /// <param name="appSecret">开发者凭据</param>
        /// <param name="url">页面URL</param>
        /// <returns></returns>
        public Hashtable GetJSAPI_Parameters(string appid, string appSecret, stringurl)
{
string accessToken =GetAccessToken(appid, appSecret);string jsTicket =GetJSAPI_Ticket(accessToken);returnJSSDKHelper.GetParameters(appid, jsTicket, url);
}

下面我们通过具体的代码案例来介绍使用的过程。

2、签到功能的实现处理

其实签到,都可以在微信公众号和企业号实现,微信的企业号可能实现更佳一些,不过他们使用JSSDK的接口操作是一样的,我们可以拓展过去就可以了。这里介绍微信公众号JSSDK实现签到的功能处理。

签到的功能,我们希望记录用户的GPS位置信息,还有就是利用拍照功能,拍一个照片同时上传到服务器,这样我们就可以实现整个业务效果了。

首先我们来设计签到的界面,代码及效果如下所示。

界面预览效果如下所示:

我们来看看微信JSSDK里面对于【获取地理位置接口】的说明:

wx.getLocation({
type:
'wgs84', //默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02' success: function(res) {var latitude = res.latitude; //纬度,浮点数,范围为90 ~ -90 var longitude = res.longitude; //经度,浮点数,范围为180 ~ -180。 var speed = res.speed; //速度,以米/每秒计 var accuracy = res.accuracy; //位置精度 }
});

以及图形接口里面【拍照或从手机相册中选图接口】的说明:

wx.chooseImage({
count:
1, //默认9 sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], //可以指定来源是相册还是相机,默认二者都有 success: function(res) {var localIds = res.localIds; //返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片 }
});

上传图片到微信服务器接口如下所示。

wx.uploadImage({
localId:
'', //需要上传的图片的本地ID,由chooseImage接口获得 isShowProgressTips: 1, //默认为1,显示进度提示 success: function(res) {var serverId = res.serverId; //返回图片的服务器端ID }
});

备注:上传图片有效期3天,可用微信多媒体接口下载图片到自己的服务器,此处获得的 serverId 即 media_id。

根据这几个接口,我们来对它们进行包装,以实现我们的业务需求。根据我们的需要,我们对JSSDK接口进行了调用,如下所示。

    <scriptlanguage="javascript">
    varappid= '@ViewBag.appid';varnoncestr= '@ViewBag.noncestr';varsignature= '@ViewBag.signature';vartimestamp= '@ViewBag.timestamp';

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp,//必填,生成签名的时间戳 nonceStr: noncestr,//必填,生成签名的随机串 signature: signature,//必填,签名,见附录1 jsApiList: ['checkJsApi','chooseImage','previewImage','uploadImage','downloadImage','getNetworkType','openLocation','getLocation']
});

wx.ready(
function() {
wx.getLocation({
type:
'wgs84',//默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02' success:function(res) {varlatitude=res.latitude;//纬度,浮点数,范围为90 ~ -90 varlongitude=res.longitude;//经度,浮点数,范围为180 ~ -180。 varspeed=res.speed;//速度,以米/每秒计 varaccuracy=res.accuracy;//位置精度 $("#lblLoacation").text(latitude+ "," +longitude);//解析坐标地址 varlocation=latitude+ "," +longitude;
$.ajax({
type:
'GET',
url:
'/JSSDKTest/GetAddress?location=' +location,//async: false, //同步 //dataType: 'json', success:function(json) {
$(
"#lblAddress").text(json);
},
error:
function(xhr, status, error) {
$.messager.alert(
"提示","操作失败" +xhr.responseText);//xhr.responseText }
});
}
});
wx.getNetworkType({
success:
function(res) {varnetworkType=res.networkType;//返回网络类型2g,3g,4g,wifi $("#lblNetwork").text(networkType);
}
});

chooseImage();
});
</script>

其中的chooseImage()是我们在页面开始的时候,让用户拍照的操作,具体JS代码如下所示。

        //拍照显示
        varlocalIds;functionchooseImage() {
wx.chooseImage({
count:
1, //默认9 sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有 sourceType: ['camera'], //可以指定来源是相册还是相机,默认二者都有 success: function(res) {
localIds
= res.localIds; //返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片 $("#imgUpload").attr("src", localIds);
}
});
}

但用户使用摄像头拍照后,就会返回一个res.localIds集合,因为我们拍照一个,那么可以把它直接赋值给图片对象,让它显示当前拍照的图片。

拍照完成,我们单击【签到】应该把图片和相关的坐标等信息上传到服务器的,图片首先是保存在微信服务器的,上传图片有效期3天,可用微信多媒体接口下载图片到自己的服务器,此处获得的 serverId 即 media_id。

为了实现我们自己的业务数据,我们需要把图片集相关信息存储在自己的服务器,这样才可以实现信息的保存,最后提示【签到操作成功】,具体过程如下所示。

        //上传图片
        varserverId;functionupload() {
wx.uploadImage({
localId: localIds[
0],
success:
function(res) {
serverId
=res.serverId;//提交数据到服务器 //提示信息 $.toast("签到操作成功");
},
fail:
function(res) {
alert(JSON.stringify(res));
}
});
}

另外,我们为了实现单击图片控件,实现重新拍照的操作,以及签到的事件处理,我们对控件的单击处理进行了绑定,如下代码所示。

        document.querySelector('#imgUpload').onclick = function() {
chooseImage();
};

$(document).on(
"click", "#btnSignIn", function() {if (localIds == undefined || localIds== null) {
$.toast(
'请先拍照', "forbidden");return;
}
//调用上传图片获得媒体ID upload();
});

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

C#开发微信门户及应用(39)--使用微信JSSDK实现签到的功能

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

C#开发微信门户及应用(37)--微信公众号标签管理功能

C#开发微信门户及应用(36)--微信卡劵管理的封装操作

C#开发微信门户及应用(35)--微信支付之企业付款封装操作

C#开发微信门户及应用(34)--微信裂变红包

C#开发微信门户及应用(33)--微信现金红包的封装及使用

C#开发微信门户及应用(32)--微信支付接入和API封装使用

C#开发微信门户及应用(31)--微信语义理解接口的实现和处理

C#开发微信门户及应用(30)--消息的群发处理和预览功能

C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现

C#开发微信门户及应用(27)-公众号模板消息管理

C#开发微信门户及应用(26)-公众号微信素材管理

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口

在我前面的几篇博客,有介绍了微信支付、微信红包、企业付款等各种和支付相关的操作,不过上面都是基于微信普通API的封装,本篇随笔继续微信支付这一主题,继续介绍基于微信网页JSAPI的方式发起的微信支付功能实现,微信的JSAPI相对于普通的API操作,调试没有那么方便,而且有时候有些错误需要反复核实。本篇随笔基于实际的微信网页支付案例,对微信JSAPI的支付实现进行介绍。

1、微信JS-SDK的知识

在我前面《
C#开发微信门户及应用(39)--使用微信JSSDK实现签到的功能
》介绍的内容里面,有介绍了很多JS-SDK的基础知识,我们基于网页发起的微信支付,我们也是基于JS-SDK的基础上进行发起的,因此需要了解这些JS-SDK的使用步骤。

一般来说,我们网页JSAPI发起的微信支付,需要使用wx.chooseWXPay的操作方法,而这个方法也是需要在完成wx.config初始化的时候,由界面元素进行触发处理的。

例如我们可以这样实现整个微信支付的处理过程:

1)先使用JS对API进行初始化配置

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp, //必填,生成签名的时间戳 nonceStr: noncestr, //必填,生成签名的随机串 signature: signature, //必填,签名,见附录1 jsApiList: ['checkJsApi','chooseWXPay','hideOptionMenu']
});

2)使用wx.chooseWXPay发起微信支付

wx.chooseWXPay({
timestamp:
0, //支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 nonceStr: '', //支付签名随机串,不长于 32 位 package: '', //统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***) signType: '', //签名方式,默认为'SHA1',使用新版支付需传入'MD5' paySign: '', //支付签名 success: function(res) {//支付成功后的回调函数 }
});

备注:prepay_id 通过微信支付统一下单接口拿到,paySign 采用统一的微信支付 Sign 签名生成方法,注意这里 appId 也要参与签名,appId 与 config 中传入的 appId 一致,即最后参与签名的参数有appId, timeStamp, nonceStr, package, signType。

3)获取用户的openid

在一些JSAPI操作里面,有时候需要传入当前用户的openid,由于这个值,一般是不能直接获得的,但可以通过用户授权代码获取,因此我们可以在菜单中配置好重定向的URL,根据URL获取对应的code,然后解析code为对应的openid即可。

通过code换取的是一个特殊的网页授权access_token,与基础支持中的access_token(该access_token用于调用其他接口)不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。

获取code后,请求以下链接获取access_token: 
https:
//api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

2、微信支付JSAPI初始化的参数处理

要获取相关的JS-SDK的相关接口参数,我们需要先生成JSAPI-Ticket凭证,生成这个凭证代码接口实现如下所示。一般来说,这个接口的数据需要缓存起来的,具体可以自己实现处理。

        /// <summary>
        ///获取JSAPI_TICKET接口/// </summary>
        /// <param name="accessToken">调用接口凭证</param>
        /// <returns></returns>
        public string GetJSAPI_Ticket(stringaccessToken)
{
var url = string.Format("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={0}&type=jsapi", accessToken);var result = JsonHelper<GetTicketResult>.ConvertJson(url);return result != null ? result.ticket : null;
}

我们要实现JSSDK签名的处理,必须先根据几个变量,构建好URL字符串,具体的处理过程,我们可以把它们逐一放在一个Hashtable里面,如下代码所示。

        /// <summary>
        ///获取JSSDK所需要的参数信息,返回Hashtable结合/// </summary>
        /// <param name="appId">微信AppID</param>
        /// <param name="jsTicket">根据Token获取到的JSSDK ticket</param>
        /// <param name="url">页面URL</param>
        /// <returns></returns>
        public static Hashtable GetParameters(string appId, string jsTicket, stringurl)
{
string timestamp =GetTimeStamp();string nonceStr =GetNonceStr();//这里参数的顺序要按照 key 值 ASCII 码升序排序 string rawstring = "jsapi_ticket=" + jsTicket + "&noncestr=" + nonceStr + "&timestamp=" + timestamp + "&url=" + url + "";string signature =GetSignature(rawstring);
Hashtable signPackage
= newHashtable();
signPackage.Add(
"appid", appId);
signPackage.Add(
"noncestr", nonceStr);
signPackage.Add(
"timestamp", timestamp);
signPackage.Add(
"url", url);
signPackage.Add(
"signature", signature);
signPackage.Add(
"jsapi_ticket", jsTicket);
signPackage.Add(
"rawstring", rawstring);returnsignPackage;
}

这样把它们放在哈希表里面,方便我们提取出来使用。

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp, //必填,生成签名的时间戳 nonceStr: noncestr, //必填,生成签名的随机串 signature: signature, //必填,签名,见附录1 jsApiList: ['checkJsApi','chooseWXPay','hideOptionMenu']
});

为了在MVC视图页面里面,设置我们计算出来的值,一般我们需要在后台进行计算好,并把它们放在ViewBag变量中就可以在页面前端使用了,如下所示是MVC视图页面的后台代码。

        /// <summary>
        ///刷新JS-SDK的票据/// </summary>
        protected virtual voidRefreshTicket(AccountInfo accountInfo)
{
Hashtable ht
=baseApi.GetJSAPI_Parameters(accountInfo.AppID, accountInfo.AppSecret, Request.Url.AbsoluteUri);
ViewBag.appid
= ht["appid"].ToString();
ViewBag.nonceStr
= ht["noncestr"].ToString();
ViewBag.timestamp
= ht["timestamp"].ToString();
ViewBag.signature
= ht["signature"].ToString();
}

这样,在MVC的视图页面里面,我们的代码可以这样实现JSAPI变量的初始化。

    <scriptlanguage="javascript">
    varopenid= '@ViewBag.openid';varappid= '@ViewBag.appid';varnoncestr= '@ViewBag.noncestr';varsignature= '@ViewBag.signature';vartimestamp= '@ViewBag.timestamp';

wx.config({
debug:
false,
appId: appid,
//必填,公众号的唯一标识 timestamp: timestamp,//必填,生成签名的时间戳 nonceStr: noncestr,//必填,生成签名的随机串 signature: signature,//必填,签名,见附录1 jsApiList: ['checkJsApi','chooseWXPay','hideOptionMenu']
});

3、微信支付JSAPI发起微信支付的参数处理

在第一小节里面,我提到了,初始化JS-API后,还需要使用wx.chooseWXPay发起微信支付,这个接口也有几个相关的参数。

wx.chooseWXPay({
timestamp:
0, //支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 nonceStr: '', //支付签名随机串,不长于 32 位 package: '', //统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***) signType: '', //签名方式,默认为'SHA1',使用新版支付需传入'MD5' paySign: '', //支付签名 success: function(res) {//支付成功后的回调函数 }
});

其中这里的timestamp和nonceStr的规则和前面初始化操作的参数规则一样,但是注意不能和初始化接口的timestamp和nonceStr保持一样,否则发起支付会出现【
支付验证签名失败
】的错误。

package的变量就是我们调用统一下单接口的获得的预下单id,格式如下所示:

prepay_id=wx2016051517463160322779de0375788970

而为了获得这个预下单的ID,我们先需要根据统一下单接口的需要,构建一个数据对象,如下所示。

                PayOrderData data = newPayOrderData()
{
product_id
=id,
body
= "测试支付" +id,
attach
= "爱奇迪技术支持",
detail
= "测试JSAPI支付" +id,
total_fee
= 1,
goods_tag
= "test" +id,
trade_type
= "JSAPI",
openid
=openid
};

然后调用前面封装过的统一下单接口API获取对应的统一下单ID

TenPayApi api = new TenPayApi(accountInfo); var orderResult =api.UnifiedOrder(data);
LogHelper.Debug(
string.Format("统一下单结果:{0}", (orderResult != null) ? orderResult.ToJson() : "为空值"));if (string.IsNullOrEmpty(orderResult.prepay_id) || string.IsNullOrEmpty(orderResult.appid))
{
throw new WeixinException("统一下单结果返回失败!");
}

signType固定为MD5,

最后剩下paySign这个比较复杂的参数了,这个参数就是需要根据前面这些参数进行签名的值。微信支付的签名还是和普通API的做法(在前面介绍微信支付的时候,有介绍过相关的规则,具体可以看看《
C#开发微信门户及应用(32)--微信支付接入和API封装使用
》),引入实体类
WxPayData
来存储一些业务参数,以及实现参数的签名处理。

值得注意的是,使用普通API的签名为Sign,而使用JSAPI的签名变量名称为paySign,两者处理逻辑一样,只是名称不同。

这样我们在后台处理相关的变量的代码如下所示。

        /// <summary>
        ///获取JSAPI方式的微信字符串参数对象/// </summary>
        /// <param name="accountInfo">当前账号</param>
        /// <param name="prepay_id">统一下单ID</param>
        /// <returns></returns>
        private WxPayData GetJSPayParam(AccountInfo accountInfo, stringprepay_id)
{
WxPayData data
= newWxPayData();
data.SetValue(
"appId", ViewBag.appId);
data.SetValue(
"timeStamp", data.GenerateTimeStamp());
data.SetValue(
"nonceStr", data.GenerateNonceStr());
data.SetValue(
"signType", "MD5");
data.SetValue(
"package", string.Format("prepay_id={0}", prepay_id));

data.SetValue(
"paySign", data.MakeSign(accountInfo.PayAPIKey));//签名 returndata;
}

然后,再定义一个控制器接口,返回相关的参数数据,部分逻辑代码如下所示。这样方便前端通过JSON的格式获取对应的变量值。

                //支付需要的参数
                WxPayData param =GetJSPayParam(accountInfo, orderResult.prepay_id);
LogHelper.Debug(
"GetJSPayParam:" +param.ToJson());var obj = new{
timeStamp
= param.GetString("timeStamp"),
nonceStr
= param.GetString("nonceStr"),
signType
= param.GetString("signType"),
package
= param.GetString("package"),
paySign
= param.GetString("paySign")
};
return Content(obj.ToJson());

在前面页面,通过ajax方式获得发起微信支付的相关参数,代码如下所示。

    //发起一个微信支付
    functionchooseWXPay(id) {//alert(window.location.href);
        var uid = getUrlVars()["uid"];
$.ajax({
type:
'POST',
url:
'/JSSDKTest/GetWXPayData',//async: false, //同步 dataType: 'json',
data : {
id: id,
uid: uid,
openid : openid
},
success:
function(json) {
wx.chooseWXPay({
appId: appid,
timestamp: json.timeStamp,
//支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 nonceStr: json.nonceStr, //支付签名随机串,不长于 32 位 package: json.package, //统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***) signType: json.signType, //签名方式,默认为'SHA1',使用新版支付需传入'MD5' paySign: json.paySign, //支付签名 success: function (res) { //支付成功后的回调函数 if (res.errMsg == 'chooseWXPay:ok') {
$.toast(
'支付成功');//setTimeout(function () { //window.location.href = "/";//这里默认跳转到主页 //}, 2000); //window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); } else if (res.errMsg == 'chooseWXPay:cancel' || res.errMsg == 'chooseWXPay:fail') {
$.toast(
"支付失败");//window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); }
},
cancel:
function() {
$.toast(
"用户取消了支付");//window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); }
});
wx.error(
function(res) {
$.toast(
"调用支付出现异常");//window.location.href = "/Pay/order_details?orderId=" + $("#orderId").val(); })
},
error:
function(xhr, status, error) {
$.toast(
"操作失败" + xhr.responseText); //xhr.responseText }
});
};

4、微信支付JSAPI发起微信支付的界面效果

通过上面的代码,我们可以顺利发起微信支付,并可以看到具体的测试结果了,读者可以关注我们的公众号【广州爱奇迪】对其中微信支付进行测试了解。





微信支付成功后,我们同样可以在微信支付的对话里面看到响应的结果了。

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

C#开发微信门户及应用(40)--使用微信JSAPI实现微信支付功能

C#开发微信门户及应用(39)--使用微信JSSDK实现签到的功能

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

C#开发微信门户及应用(37)--微信公众号标签管理功能

C#开发微信门户及应用(36)--微信卡劵管理的封装操作

C#开发微信门户及应用(35)--微信支付之企业付款封装操作

C#开发微信门户及应用(34)--微信裂变红包

C#开发微信门户及应用(33)--微信现金红包的封装及使用

C#开发微信门户及应用(32)--微信支付接入和API封装使用

C#开发微信门户及应用(31)--微信语义理解接口的实现和处理

C#开发微信门户及应用(30)--消息的群发处理和预览功能

C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现

C#开发微信门户及应用(27)-公众号模板消息管理

C#开发微信门户及应用(26)-公众号微信素材管理

C#开发微信门户及应用(25)-微信企业号的客户端管理功能

C#开发微信门户及应用(24)-微信小店货架信息管理

C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试

C#开发微信门户及应用(22)-微信小店的开发和使用

C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密

C#开发微信门户及应用(20)-微信企业号的菜单管理

C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)

C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理

C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理

C#开发微信门户及应用(16)-微信企业号的配置和使用

C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能

C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据

C#开发微信门户及应用(13)-使用地理位置扩展相关应用

C#开发微信门户及应用(12)-使用语音处理

C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍

C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息

C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器

C#开发微信门户及应用(8)-微信门户应用管理系统功能介绍

C#开发微信门户及应用(7)-微信多客服功能及开发集成

C#开发微信门户及应用(6)--微信门户菜单的管理操作

C#开发微信门户及应用(5)--用户分组信息管理

C#开发微信门户及应用(4)--关注用户列表及详细信息管理

C#开发微信门户及应用(3)--文本消息和图文消息的应答


C#开发微信门户及应用(2)--微信消息的处理和应答


C#开发微信门户及应用(1)--开始使用微信接口