客户的要求如下:
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省写)
public class XcxJson { String session_key; String openid;// 小程序的openid String access_token;// 小程序或公众号的token String ticket;//卡券 String expires_in;//有效时间:秒 String code;//卡券的code Integer errcode;//错误码 String errmsg;//错误信息}2.编写获取token的方法(JSON用的是阿里巴巴的FastJson)
/** * 获取公众号的token * @param appid 公众号的appid * @param secret 公众号的秘钥 * * @return */static public String selectToken(String appid, String secret) { HttpRequestor requestor = new HttpRequestor(); Map<String, String> map = new HashMap<String, String>(); map.put("grant_type", "client_credential"); map.put("appid", appid); map.put("secret", secret); String res = null; String token = "error"; String url="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); XcxJson json = JSON.parseObject(res, XcxJson.class); token = json.getAccess_token(); } catch (Exception e) { e.printStackTrace(); } return token;}3.编写获取ticket的方法(JSON用的是阿里巴巴的FastJson)
/** * 获取公众号的ticket * @param token 公众号的token * * @return error-获取失败 */static public String selectTicket(String token) { String ticket ="error"; try { String res = HttpRequestor.doGet("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+token+"&type=wx_card"); System.out.println(res); XcxJson json = JSON.parseObject(res, XcxJson.class); if(json.getErrcode()==0){ ticket = json.getTicket(); } } catch (Exception e) { e.printStackTrace(); } return ticket;}4.有了ticket之后,我们提供给前端小程序card_id、timestamp、nonce_str、signature。当然如果需要指定领取用户就让前端先发openid过来。然后我们要计算signature。下面是我用java.util.UUID随机生成nonce_str,然后时间戳要除以1000,之后用nonce_str、ticket、timestamp、card_id计算签名,当然我这里是限定了领取用户的,所以加了openid,如果不需要,请把所有openid都去掉。
public JSONObject select(String openid) { String token = xcxService.getGzhToken("gzh_token"); String ticket =xcxService.getTicket("gzh_ticket",token); Long timestamp = System.currentTimeMillis()/1000; String card_id=""; String noncestr = UUID.randomUUID().toString().replaceAll("-",""); String sign = getSignature(noncestr,ticket,timestamp,card_id,openid); JSONObject object = new JSONObject(); object.put("noncestr",noncestr); object.put("timestamp",timestamp); object.put("sign",sign); object.put("card_id",card_id); return object;}5.生成签名必须先将值进行排序然后拼接在一起,最后用SHA1加密。生成签名之后连其他参数应该传给前端
/** * 生成加密签名,用于微信卡券 * @param noncestr 随机字符串 * @param jsapi_ticket 小程序ticket * @param timestamp 时间戳 * @param card_id 卡券ID * @param openid 领券用户的Openid * @return */public String getSignature(String noncestr,String jsapi_ticket,Long timestamp,String card_id,String openid){ List<String> params=new ArrayList<String>(); params.add(jsapi_ticket); params.add(timestamp+""); params.add(noncestr); params.add(card_id); params.add(openid); Collections.sort(params); String sign = ""; for(String str:params){ sign+=str; } // System.out.println(sign); String sha = org.apache.commons.codec.digest.DigestUtils.sha1Hex(sign); return sha;}6.前端拿到这几个参数之后调用wx.addCard()方法,如下(openid没有返回去,因为本身openid就是前端给我的),
注意cardExt是经过JSON.stringify() 转成字符串的。还有加密时和这里的参数必须对应上,除了ticket以外,所有加密时的参数都必须在这里对应上,不能多也不能少,card_id单独拿出来。可以在https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=cardsign验证你的签名是不是算对了。这里很容易出现签名错误的问题。只要确保token和ticket都是用公众号的、加密没有错,参数对应得上、cardExt转json字符串,就不会签名出错。
var card = {
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 * @param code 前端传回来的code * @param token 公众号的token * @return */public static String decode(String code,String token){ String url = "https://api.weixin.qq.com/card/code/decrypt?access_token="+token; JSONObject object = new JSONObject(); object.put("encrypt_code",code); String res = HttpRequestor.postJson(url,object.toJSONString()); XcxJson json = JSON.parseObject(res,XcxJson.class); if(json.getErrcode()==0){ return json.getCode(); }else{ return "error"; }}8.解码之后保存好code,并把code绑定到相关的数据库表中,方便下次查。当用户要核销卡券的时候,
扫码枪扫出来的或者用户展示的code和我们存的code 是一直的,我们把相关的东西展示到前台,然后执行核销(最好让前台员工确认后)。
/** * 核销卡券 * @param code 卡券的code * @param card_id 卡券的ID * @param token 公众号的token * * @return 0-失败,1-成功 */public static Integer cancel(String code,String card_id,String token){ String url = "https://api.weixin.qq.com/card/code/consume?access_token="+token; JSONObject object = new JSONObject(); object.put("code",code); object.put("card_id",card_id); String res = HttpRequestor.postJson(url,object.toJSONString()); XcxJson json = JSON.parseObject(res,XcxJson.class); if(json.getErrcode()==0){ return 1; }else{ return 0; }}核销完之后卡包里面的卡券就会变成已使用。这样我们的整个逻辑链就完整了。
我这里主要讲的是在公众平台生成的卡券,如果需要自定义程度高的卡券,则需要调用创建卡券的接口。然后投放和核销会多一些参数。
微信小程序













