客户的要求如下:
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;
}
}
核销完之后卡包里面的卡券就会变成已使用。这样我们的整个逻辑链就完整了。
我这里主要讲的是在公众平台生成的卡券,如果需要自定义程度高的卡券,则需要调用创建卡券的接口。然后投放和核销会多一些参数。