1.在小程序中支付成功后获得一张卡券
2.用户凭卡券在店内核销
根据以上需求,折腾了我好几天(客户一直忙照顾店里生意,扫个码都要等两天)。
首先客户提供了微信公众号的账号密码给我。我登录进去,也把自己添加为运营者了。
首先声明我这里不是用接口创建卡券的
然后在左侧选择添加功能插件,当然客户是已经完成商户号、小程序和公众号的认证。

找到下面这个功能插件

点进去首先是应该提交商户信息,因为我的已经提交了所以没有办法截图了,这也没什么难点。提交之后会在3个工作日内审核。
审核通过之后左侧栏就有卡券功能了。点击进入。

注意上方的右侧有一个代制模式或者自制模式,我们选择自制模式。
然后选择“优惠券”。

点击在下方的新建优惠券。然后让你选择券的类型。按我客户的需求,我选择兑换券。

然后填入参数,注意他有个个人领取数量限制和一个库存量。填完之后提交审核,然后秒通过。
然后回到“优惠券”那里就会像我的一样多了一行,我们还可以对其进行修改。
我们先点详情,获取卡号

拿到卡号存好。接下来我们要获取公众号开发的权限。
在左侧的最底下

存好appid和秘钥(需要管理员扫码)

然后注意我们还要添加IP白名单,不然是获取不到token的。我们先把自己的服务器的IP(注意是IP不是域名)加进去,然后再把本地开发的IP加进去,当然本地的IP我们现在可能还不知道(并不是192.168什么的,是公网IP)。没关系,先保存。
以上操作都是在公众平台上面的,现在我们回到代码开发。我们理一下卡券从生成到核销的整个过程:
我们先获取公众号的token(不是小程序的),然后用这个token获得ticket,然后用ticket去加密获取签名,得到签名之后发送给前端,前端调用wx.addCard()方法来进入一个领取页面,然后用户点击领取之后前端会获得一个加密过的code,让前端把code发送给我们,我们拿到code之后再发送请求到微信服务器去解密,解密后拿到真实的code(就是卡券上面那个号码),当用户拿着卡券到店里核销的时候,相当于把code告诉我们,我们根据code去查到这个订单的内容,然后发起核销请求到微信服务器。
1.数据准备,我们先准备一个用来接收返回结果的实体(getset省写)
publicclassXcxJson{Stringsession_key;Stringopenid;//小程序的openidStringaccess_token;//小程序或公众号的tokenStringticket;//卡券Stringexpires_in;//有效时间:秒Stringcode;//卡券的codeIntegererrcode;//错误码Stringerrmsg;//错误信息}2.编写获取token的方法(JSON用的是阿里巴巴的FastJson)
/获取公众号的token@paramappid公众号的appid@paramsecret公众号的秘钥@return/staticpublicStringselectToken(Stringappid,Stringsecret){HttpRequestorrequestor=newHttpRequestor();MapString,Stringmap=newHashMapString,String();map.put("grant_type","client_credential");map.put("appid",appid);map.put("secret",secret);Stringres=null;Stringtoken="error";Stringurl="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";url+="&appid="+appid;url+="&secret="+secret;try{res=requestor.doGet(url);System.out.println(res);XcxJsonjson=JSON.parseObject(res,XcxJson.class);token=json.getAccess_token();}catch(Exceptione){e.printStackTrace();}returntoken;}3.编写获取ticket的方法(JSON用的是阿里巴巴的FastJson)
/获取公众号的ticket@paramtoken公众号的token@returnerror-获取失败/staticpublicStringselectTicket(Stringtoken){Stringticket="error";try{Stringres=HttpRequestor.doGet("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+token+"&type=wx_card");System.out.println(res);XcxJsonjson=JSON.parseObject(res,XcxJson.class);if(json.getErrcode()==0){ticket=json.getTicket();}}catch(Exceptione){e.printStackTrace();}returnticket;}4.有了ticket之后,我们提供给前端小程序card_id、timestamp、nonce_str、signature。当然如果需要指定领取用户就让前端先发openid过来。然后我们要计算signature。下面是我用java.util.UUID随机生成nonce_str,然后时间戳要除以1000,之后用nonce_str、ticket、timestamp、card_id计算签名,当然我这里是限定了领取用户的,所以加了openid,如果不需要,请把所有openid都去掉。
publicJSONObjectselect(Stringopenid){Stringtoken=xcxService.getGzhToken("gzh_token");Stringticket=xcxService.getTicket("gzh_ticket",token);Longtimestamp=System.currentTimeMillis()/1000;Stringcard_id="";Stringnoncestr=UUID.randomUUID().toString().replaceAll("-","");Stringsign=getSignature(noncestr,ticket,timestamp,card_id,openid);JSONObjectobject=newJSONObject();object.put("noncestr",noncestr);object.put("timestamp",timestamp);object.put("sign",sign);object.put("card_id",card_id);returnobject;}5.生成签名必须先将值进行排序然后拼接在一起,最后用SHA1加密。生成签名之后连其他参数应该传给前端
/生成加密签名,用于微信卡券@paramnoncestr随机字符串@paramjsapi_ticket小程序ticket@paramtimestamp时间戳@paramcard_id卡券ID@paramopenid领券用户的Openid@return/publicStringgetSignature(Stringnoncestr,Stringjsapi_ticket,Longtimestamp,Stringcard_id,Stringopenid){ListStringparams=newArrayListString();params.add(jsapi_ticket);params.add(timestamp+"");params.add(noncestr);params.add(card_id);params.add(openid);Collections.sort(params);Stringsign="";for(Stringstr:params){sign+=str;}//System.out.println(sign);Stringsha=org.apache.commons.codec.digest.DigestUtils.sha1Hex(sign);returnsha;}6.前端拿到这几个参数之后调用wx.addCard()方法,如下(openid没有返回去,因为本身openid就是前端给我的),
注意cardExt是经过JSON.stringify()转成字符串的。还有加密时和这里的参数必须对应上,除了ticket以外,所有加密时的参数都必须在这里对应上,不能多也不能少,card_id单独拿出来。可以在验证你的签名是不是算对了。这里很容易出现签名错误的问题。只要确保token和ticket都是用公众号的、加密没有错,参数对应得上、cardExt转json字符串,就不会签名出错。
varcard={
cardId:res.data.data.card_id,
cardExt:JSON.stringify({
openid:globalData.openid,
timestamp:res.data.data.timestamp,
nonce_str:res.data.data.noncestr,
signature:res.data.data.sign
})
}
wx.addCard({
cardList:[card],
success:res={
console.log(res);
}
})
7.在上面小程序代码中,我们只打印了领取结果res。如果领取成功了,我们需要前端把res.cardList[0].code发给服务器,当然最好带上其他参数,比如我还需要带上订单ID,否则都不知道这个code属于哪单。
后端拿到code之后用下面的方法进行解码
/解码卡券领取后获得的code@paramcode前端传回来的code@paramtoken公众号的token@return/publicstaticStringdecode(Stringcode,Stringtoken){Stringurl="https://api.weixin.qq.com/card/code/decrypt?access_token="+token;JSONObjectobject=newJSONObject();object.put("encrypt_code",code);Stringres=HttpRequestor.postJson(url,object.toJSONString());XcxJsonjson=JSON.parseObject(res,XcxJson.class);if(json.getErrcode()==0){returnjson.getCode();}else{return"error";}}8.解码之后保存好code,并把code绑定到相关的数据库表中,方便下次查。当用户要核销卡券的时候,
扫码枪扫出来的或者用户展示的code和我们存的code是一直的,我们把相关的东西展示到前台,然后执行核销(最好让前台员工确认后)。
/核销卡券@paramcode卡券的code@paramcard_id卡券的ID@paramtoken公众号的token@return0-失败,1-成功/publicstaticIntegercancel(Stringcode,Stringcard_id,Stringtoken){Stringurl="https://api.weixin.qq.com/card/code/consume?access_token="+token;JSONObjectobject=newJSONObject();object.put("code",code);object.put("card_id",card_id);Stringres=HttpRequestor.postJson(url,object.toJSONString());XcxJsonjson=JSON.parseObject(res,XcxJson.class);if(json.getErrcode()==0){return1;}else{return0;}}核销完之后卡包里面的卡券就会变成已使用。这样我们的整个逻辑链就完整了。
我这里主要讲的是在公众平台生成的卡券,如果需要自定义程度高的卡券,则需要调用创建卡券的接口。然后投放和核销会多一些参数。













