本人水平有限,编写中有错在所难免,如果有错误的地方望各位批评指正。

废话不多说: 先来看看实际效果图
ceshi

  • 开发使用的架构为:
  • 前端: 微信小程序uniapp(原生通用 )
  • 后端: koa
  • 架构结构图:

官方架构

注: 此次开发使用的API调用,而非公众号制作卡券, 开发进行API调用相对公众号上制作卡券更加的灵活和高度定制化

架构

  • 开发准备:
  • 开发者须有一个有卡券权限的公众号(服务号)和认证后的小程序账号【前提】
  • 公众号1
  • 公众号2
  • 准备好卡券的店铺logo,店铺门面图,制作哪种类型的卡券资料等等
  • 需要打通微信小程序和微信公众号。 打通方法是使用微信开放平台,进行注册认证。
  • access_token的获取、node服务器的搭建、小程序的开发等其他功能不在本文讨论范围
  • 认真阅读官方文档中制作卡券接口说明。(小程序中只有调用卡券的接口说明) 制卡官方链接文档微信小程序调用卡券链接文档
  • 开发调用:
  • 上传logo素材到微信服务器:
    • node服务器
    • 注意:1. 上传的图片限制文件大小限制1MB,仅支持JPG、PNG格式。2.调用接口获取图片url仅支持在微信相关业务下使用。
  1. function uploadwxlogo( token ,files) {
  2. return new Promise ((resolve , reject) =>{
  3. request.post({
  4. url: `https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=${token}`, //微信上传卡券素材的接口地址
  5. formData:{
  6. buffer:{
  7. // value: fs.readFileSync(path.join(__dirname,"./070.png")) ,
  8. value: files.data, //必须使用formData格式进行buffer流上传
  9. options :{
  10. filename: files.name,
  11. contentType: files.type
  12. }
  13. }
  14. }
  15. },(err , res) =>{
  16. if(err){
  17. reject(err)
  18. }try{
  19. const reData = JSON.parse(res.body)
  20. resolve(reData)
  21. }catch(e){
  22. // console.log(e)
  23. }
  24. })
  25. })}
token提示: 只能使用公众号的access_token ,而非小程序的 access_token
  • 上传门面素材到微信服务器(只能临时地址):
  • node服务器
  1. function uploadwxtemp( token ,files) {
  2. // var formData = new formData()
  3. return new Promise ((resolve , reject) =>{
  4. request.post({
  5. url: `https://api.weixin.qq.com/cgi-bin/media/upload?access_token=${token}&type=image`, //Url临时路径
  6. formData:{
  7. buffer:{
  8. // value: fs.readFileSync(path.join(__dirname,"./070.png")) ,
  9. value: files.data, //图片文件流
  10. options :{
  11. filename: files.name,
  12. contentType: files.type
  13. }
  14. }
  15. }
  16. },(err , res) =>{
  17. if(err){
  18. reject(err)
  19. }try{
  20. const reData = JSON.parse(res.body)
  21. resolve(reData)
  22. }catch(e){
  23. // console.log(e)
  24. }
  25. })
  26. })}
  • 先上代码:

使用原生的 HTTP请求方式: POSTURL: https://api.weixin.qq.com/card/create?access_token=ACCESS_TOKEN 在post传递data数据给微信端的时候总是报错json格式不对,使用egg.js上传就没有问题,由于架构已定,所以退而使用的第三方的卡券npm包。 npm install wechat-cards –save

  1. const wechatcard = require(\'wechat-cards\')
  2. async function postaddCard( ctx ) {
  3. const {
  4. token,card_type,merchant_id,logo,brand_name,CODE_TYPE_TEXT,title,color,notice,description,quantity,get_limit,bind_openid,center_title,center_app_brand_user_name
  5. ,center_app_brand_pass,promotion_app_brand_user_name,promotion_app_brand_pass,activate_app_brand_user_name,activate_app_brand_pass ,abstract ,icon_url_list ,service_phone,condition ,begin, end ,shop_id
  6. }= ctx.request.body
  7. wechatcard.setConfig({
  8. accessTokenService: {
  9. "access_token": token,
  10. "expires_in": 11199 //token的过期时间
  11. }
  12. })
  13. let ok = (begin/1000).toString() //单位为秒 。。。 重点啊 !!!! 转换为字符串 优惠券有效期开始
  14. let ends = (end/1000).toString() //单位为秒 。。。 重点啊 !!!! 转换为字符串 优惠券有效期截至
  15. let card = {
  16. "card_type" : card_type, //我们传入通用券, 就是优惠券
  17. "base_info" : {
  18. "sub_merchant_info":{
  19. "merchant_id": merchant_id, //可选,如果有子商铺,可以定义子商铺的id(需要审核),如果不需要,可以不用这个参数
  20. },
  21. "logo_url":logo , //卡券的商户logo,建议像素为300*300 //直接调用商场的logo ? 先传递好再说
  22. "brand_name":brand_name, // "微信餐厅",商户名字,字数上限为12个汉字
  23. "code_type":CODE_TYPE_TEXT, // 码型: "CODE_TYPE_TEXT"文 本 ; "CODE_TYPE_BARCODE"一维码 "CODE_TYPE_QRCODE"二维码
  24. "title": title, // 卡券名,字数上限为9个汉字。(建议涵盖卡券属性、服务及金额)。
  25. "color" :color,
  26. // "color":color, //"Color010",卡券背景颜色
  27. "notice":notice, //卡券使用提醒,字数上限为16个汉字。
  28. "service_phone":service_phone, //****非必填字段 || 客服电话。
  29. "description":"可与其他优惠同享", //卡券使用说明,字数上限为1024个汉字。
  30. "date_info": { //使用日期,有效期的信息。
  31. "type": "DATE_TYPE_FIX_TIME_RANGE", //"DATE_TYPE_FIX_TIME_RANGE" , DATE_TYPE_FIX TIME_RANGE 表示固定日期区间,DATE_TYPE FIX_TERM 表示固定时长 (自领取后按天算。
  32. "begin_timestamp": ok, //type为DATE_TYPE_FIX_TIME_RANGE时专用,表示起用时间。从1970年1月1日00:00:00至起用时间的秒数,最终需转换为字符串形态传入。(东八区时间,UTC+8,单位为秒)
  33. "end_timestamp": ends //结束时间戳 ****||表示结束时间 , 建议设置为截止日期的23:59:59过期 。 ( 东八区时间,UTC+8,单位为秒 )
  34. // "type":"DATE_TYPE_FIX_TERM",
  35. // "fixed_term" : 2,
  36. // "fixed_begin_term": 1
  37. },
  38. "sku": { //商品信息
  39. "quantity":quantity //卡券库存的数量,上限为100000000
  40. },
  41. "use_limit":get_limit, //每人可核销的数量限制,不填写默认为50。
  42. "get_limit": get_limit, //每人可领券的数量限制,不填写默认为50。
  43. "use_custom_code":false, //是否自定义Code码 。填写true或false,默认为false。 通常自有优惠码系统的开发者选择 自定义Code码,并在卡券投放时带入 Code码,详情见 是否自定义Code码 。
  44. "bind_openid":false, //是否指定用户领取,填写true或false 。默认为false。通常指定特殊用户群体 投放卡券或防止刷券时选择指定用户领取。
  45. "can_share":false, // 卡券领取页面是否可分享。*****||非必填
  46. "can_give_friend":false,
  47. "use_all_locations":false, //卡券是否可转赠。*****||非必填
  48. // "location_id_list": [ //*****||非必填||门店位置poiid。 调用 POI门店管理接 口 获取门店位置poiid。具备线下门店 的商户为必填。
  49. // 123,
  50. // 12321,
  51. // 345345
  52. // ],
  53. //"center_title":center_title, //卡券顶部居中的按钮,仅在卡券状 态正常(可以核销)时显示*****||非必填
  54. // "center_sub_title": center_sub_title, //显示在入口下方的提示语 ,仅在卡券状态正常(可以核销)时显示。*****||非必填
  55. // "source": "大众点评" //???
  56. // "fixed_term": //type为DATE_TYPE_FIX_TERM时专用,表示自领取后多少天内有效,不支持填写0。||如果填写为:DATE_TYPE FIX_TERM, 则必填
  57. // fixed_begin_term: //type为DATE_TYPE_FIX_TERM时专用,表示自领取后多少天开始生效,领取后当天生效填写0。(单位为天)
  58. // end_time stamp: //可用于DATE_TYPE_FIX_TERM时间类型,表示卡券统一过期时间 , 建议设置为截止日期的23:59:59过期 。 ( 东八区时间,UTC+8,单位为秒 ),设置了fixed_term卡券,当时间达到end_timestamp时卡券统一过期
  59. // "center_app_brand_user_name":center_app_brand_user_name, //自定义使用入口跳转小程序的user_name,格式为原始id+@app
  60. //" center_app_brand_pass" :center_app_brand_pass, //
  61. "custom_url_name": center_title, //自定义跳转的URL。
  62. "custom_url": "http://www.qq.com", //自定义跳转外链的入口名字。
  63. "custom_app_brand_user_name": center_app_brand_user_name,
  64. "custom_app_brand_pass":center_app_brand_pass,
  65. //"custom_url_sub_title": "6个汉字tips", //显示在入口右侧的提示语。
  66. //"promotion_url_name": "更多优惠", //营销场景的自定义入口名称。
  67. // "promotion_url": "http://www.qq.com", //营销入口跳转外链的地址链接。
  68. // "promotion_app_brand_user_name":promotion_app_brand_user_name, //卡券跳转的小程序的user_name,仅可跳转该 公众号绑定的小程序 。格式:gh_86a091e50ad4@app
  69. //"promotion_app_brand_pass":promotion_app_brand_pass, //promotion _app_brand_pass .格式:"API/cardPa
  70. },
  71. "advanced_info": { //创建优惠券特有的高级字段
  72. "use_condition": { //使用门槛(条件)字段,若不填写使用条件则在券面拼写 :无最低消费限制,全场通用,不限品类;并在使用说明显示: 可与其他优惠共享
  73. // "accept_category": accept_category, //指定可用的商品类目,仅用于代金券类型 ,填入后将在券面拼写适用于xxx "鞋类"
  74. // "reject_category": reject_category, //指定不可用的商品类目,仅用于代金券类型 ,填入后将在券面拼写不适用于xxxx ,"阿迪达斯"
  75. "can_use_with_other_discount": true, //不可以与其他类型共享门槛 ,填写false时系统将在使用须知里 拼写“不可与其他优惠共享”, 填写true时系统将在使用须知里 拼写“可与其他优惠共享”, 默认为true
  76. },
  77. "abstract": { //封面摘要结构体名称
  78. "abstract":abstract, //封面摘要简介
  79. "icon_url_list": [
  80. icon_url_list //封面图片列表,仅支持填入一 个封面图片链接, 上传图片接口 上传获取图片获得链接,填写 非CDN链接会报错,并在此填入。 建议图片尺寸像素850*350 "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sjpiby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0"
  81. ]
  82. },
  83. // "text_image_list": [ //图文列表,显示在详情内页 ,优惠券券开发者须至少传入 一组图文列表
  84. // {
  85. // "image_url": image_url, //图片链接,必须调用 上传图片接口 上传图片获得链接,并在此填入, 否则报错
  86. // "text": text //图文描述
  87. // }],
  88. "time_limit": [ //使用时段限制,包含以下字段
  89. // {
  90. // "type": "MONDAY", //限制类型枚举值:支持填入 MONDAY 周一 TUESDAY 周二 WEDNESDAY 周三 THURSDAY 周四 FRIDAY 周五 SATURDAY 周六 SUNDAY 周日 此处只控制显示, 不控制实际使用逻辑,不填默认不显示
  91. // "begin_hour":0, // 当前type类型下的起始时间(小时) ,如当前结构体内填写了MONDAY, 此处填写了10,则此处表示周一 10:00可用
  92. // "end_hour":10, //当前type类型下的结束时间(小时) ,如当前结构体内填写了MONDAY, 此处填写了20, 则此处表示周一 10:00-20:00可用
  93. // // "begin_minute":10,
  94. // // "end_minute":59
  95. // },
  96. {
  97. "type": "HOLIDAY"
  98. }
  99. ],
  100. "business_service": [ //商家服务类型: BIZ_SERVICE_DELIVER 外卖服务; BIZ_SERVICE_FREE_PARK 停车位; BIZ_SERVICE_WITH_PET 可带宠物; BIZ_SERVICE_FREE_WIFI 免费wifi, 可多选
  101. "BIZ_SERVICE_FREE_WIFI",
  102. "BIZ_SERVICE_WITH_PET",
  103. "BIZ_SERVICE_FREE_PARK",
  104. "BIZ_SERVICE_DELIVER"
  105. ]
  106. },
  107. // "deal_detail" : "123"
  108. "default_detail" : description
  109. }
  110. const p = await wechatcard.card.createCard(card)
  111. .then(result => {
  112. return result
  113. })
  114. .catch(e => console.log(\'创建失败\', e))
  115. if(p.card_id){
  116. ctx.body={
  117. code:200,
  118. p
  119. }
  120. }
  121. }
  1. const wechatcard = require(\'wechat-cards\')
  2. async function getCardColor (ctx) {
  3. const token = ctx.query.token
  4. wechatcard.setConfig({
  5. accessTokenService: {
  6. "access_token": token,
  7. "expires_in": 17199
  8. }
  9. })
  10. const result = await wechatcard.basic.getColorList()
  11. let data = result.colors
  12. if(result.errmsg == \'ok\'){
  13. ctx.body={
  14. code: 200,
  15. msg:\'success!\',
  16. data
  17. }
  18. }
  19. }

如果知道卡券背景色的编号, 当然也可以不需要调用上面的代码: 卡券背景色调用的就是color010 color020。。。这个值

卡券api-ticket

使用的官方接口为:https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=wx_card
API-ticket也有时效性, 可以根据自己需要进行数据保存

  1. /*
  2. 获取卡券API-TICKET
  3. */
  4. async function getApiTicketToken (ctx) {
  5. const { token} = ctx.request.body
  6. var url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${token}&type=wx_card`
  7. //JSON格式的ticket值数据包
  8. var TempTicket = \'\'
  9. //说明:随时调用的没失效的ticket的值
  10. var curTicket=\'\'
  11. var cardTicket = await mysql("cardTicket").select(\'*\').first() //后端数据库调用查看ticket数据, 没有过期就调用数据库中的数据
  12. if(cardTicket !== undefined) { //判断数据库是否存在这个数据,这个是有这个数据。
  13. //判断是否过期
  14. var oldTime = cardTicket.create_time
  15. var p = oldTime
  16. oldTime = new Date(oldTime).getTime()
  17. var newTime = new Date().getTime()
  18. var resut = parseInt((newTime - oldTime) / 1000 / 60 / 60 )
  19. if ( resut > 1 ) {
  20. //重新获取access_token的值
  21. TempTicket = await getHttpToken(url)
  22. //存储MYSQL数据库中
  23. if(TempTicket.errcode != 42001) {
  24. await mysql(\'cardTicket\').update({
  25. \'ticket \': TempTicket.ticket,
  26. }).where(\'id\',cardTicket.id)
  27. curTicket = TempTicket.ticket,
  28. ctx.state.data = {
  29. code : 200 ,
  30. curTicket ,
  31. update: cardTicket.create_time,
  32. msg:\'更新成功!\'
  33. }
  34. return
  35. }else {
  36. ctx.state.data = {
  37. code : -1 ,
  38. msg:\'获取失败!\'
  39. }
  40. return
  41. }
  42. }else {
  43. //没有过期,继续使用数据库中的access_token的值
  44. curTicket = cardTicket.ticket
  45. ctx.state.data = {
  46. code: 200 ,
  47. curTicket ,
  48. update: cardTicket.create_time,
  49. msg:\'获取成功!\'
  50. }
  51. return
  52. }
  53. }else {
  54. //如果数据库里面没有access_token的这条记录,就是第一次获取的情况。重新请求并插入一条新的数据
  55. TempTicket = await getHttpToken(url)
  56. if(TempTicket) {
  57. await mysql(\'cardTicket\').insert({
  58. \'ticket \' : TempTicket.ticket
  59. })
  60. curTicket = TempTicket.ticket
  61. ctx.state.data = {
  62. curTicket ,
  63. code: 200 ,
  64. msg:\'插入成功!\'
  65. }
  66. }else {
  67. ctx.state.data = {
  68. code : -1 ,
  69. msg:\'获取失败!\'
  70. }
  71. }
  72. }
  73. }
  1. 进行微信领券和核销使用的API签名接口
  2. @param :
  3. 1.card_id:
  4. 2.timestamp:
  5. 3.随机字符串
  6. */
  7. async function ticketSignature (ctx){
  8. const { card_id ,api_ticket } = ctx.request.body
  9. const nonce_str = "rainbowstar" // 随机字符串, 欢迎关注瑞宝星工作室
  10. let timestamp = Date.parse(new Date());
  11. timestamp = timestamp / 1000 + \'\'; //注意 时间戳也是字符串类型
  12. const data = [api_ticket,timestamp,card_id ,nonce_str] //需要按照顺序进行排列
  13. let result = await wechatcard.basic.getSignature(data) //以sha1进行编码,这个就是signature标签, 回调给微信小程序端
  14. if(result){
  15. ctx.body={
  16. code:200 ,
  17. result,
  18. timestamp,
  19. nonce_str
  20. }
  21. }
  22. }
  1. getCouponCard(res) {
  2. let that = this
  3. let cardlist={
  4. cardId:that.benefit.card_id,
  5. cardExt:JSON.stringify({timestamp:res.timestamp,nonce_str:res.nonce_str,signature:res.result}) //timestamp 回调的时间戳(服务器端统一获取), nonce_str: 服务器端传递的随机字符串, signature : 服务器根据api-ticket等参数获取回调的加密签名
  6. }
  7. wx.addCard({
  8. cardList: [cardlist], //代表领取的是cardlist这个优惠? 团购卡券
  9. success(re){
  10. let Code =\'\'
  11. let encry_code={
  12. token: that.tokens, //注意,此token一样是携带的公众号的token
  13. encry_code: re.cardList[0].code //微信回调固定写法
  14. }
  15. })
  16. // encry_code 也是加密code 还需要解密后才可以领取。
  17. }

再次强调token是公众号里面的token, 不是小程序的

  1. //===============================
  2. /*
  3. 解码加密code,获得真实的code
  4. */
  5. //================
  6. async function postTrueCode(ctx){
  7. const { token , encry_code } = ctx.request.body
  8. const url = `https://api.weixin.qq.com/card/code/decrypt?access_token=${token}`
  9. const temp ={
  10. "encrypt_code": encry_code
  11. }
  12. let code = await Post(url, {form:JSON.stringify(temp)})
  13. if(code.errcode !=0){
  14. ctx.body={
  15. code:-1,
  16. msg:\'fail\',
  17. code
  18. }
  19. }else{
  20. ctx.body={
  21. code:200,
  22. msg:\'成功\',
  23. code
  24. }
  25. }
  26. }

huidiao

版权声明:本文为xochin001原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xochin001/p/12775536.html