最近做了個領取微信卡券的小程序,看了很多文檔資料以及花了很多時間才算搞定的,不過也算是好事多磨,這邊記錄分享一下,也算給一點提升。
一、開發前準備
1:申請微信公眾號 和 微信小程序,這是兩個不同的東西,都需要單獨申請、不同的帳號;
2:微信公眾號需要開通微信卡券的功能;
3:在微信公眾號里面去綁定小程序;
4:申請微信開放平臺,并將微信公眾號 和 微信小程序綁定到該開放平臺。(注:綁定到開發平臺下的作用只是為了獲取unionid,因為同一用戶在 公眾號 和 小程序下獲得的openid是不一樣的,如果公眾號 和 小程序都需要領取卡券,則最好通過unionid來跟蹤用戶;如果你只是開發微信小程序的領取卡券,則完全可以忽略第4點,博主本人也沒有去綁定到微信開放平臺,感覺步驟好多,特別麻煩!)
二、開始開發
1:獲取微信卡券
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272
這邊可以直接通過微信公眾號提供的接口獲取或者創建微信的卡券,此處不過多介紹,只是提一下這邊要獲取的access_token,網址如下https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183,代碼直接如下:
-
private static String grantType = "client_credential";
-
public static String appId = "";
-
public static String secret = "";
-
public static AccessToken token = null;
-
public static AccessToken getToken() throws WeixinException, JsonParseException, JsonMappingException, IOException{
-
if(token == null || token.getExpires_in() < System.currentTimeMillis()){
-
-
String param = "?grant_type=" + grantType + "&appid=" + appId + "&secret=" + secret;
-
-
HttpsClient http = new HttpsClient();
-
-
Response res = http.get("https://api.weixin.qq.com/cgi-bin/token" + param);
-
System.out.println(res.asString());
-
ObjectMapper mapper = new ObjectMapper();
-
token = mapper.readValue(res.asString(),AccessToken.class);
-
}
-
return token;
-
}
其中需要jackson和weixin4j的jar包,比較普遍,請自行下載;而AccessToken對象也比較簡單,就errcode、errmsg、access_token、expires_in這四個參數,比較簡單,在文章結尾貼代碼
2:升級微信卡券
其實這個步驟也可以省略,升級微信卡券的目的是可以直接從微信卡券跳轉到對應的小程序,博主就偷懶了,直接跳過了這個步驟;
不過升級卡券也比較簡單,就是調用調用微信公眾號的更改微信卡券接口(URL:https://api.weixin.qq.com/card/update?access_token=TOKEN),添加幾個字段,可以參考微信官方文檔3.1,鏈接如下:https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&key=1490190158&version=1&lang=zh_CN&platform=2
3:領取卡券
3.1:先獲取openId
小程序端代碼,通過調用wx.login獲取code,再調用https://api.weixin.qq.com/sns/jscode2session接口獲取openid,博主看到很多例子是直接從小程序端調用這個接口,但我事實中發現是行不通的,因為這個域名無法添加到小程序的request合法域名中,微信給的說明是不要在前端調用這個接口,需要通過后臺,那沒辦法嘍
-
wx.login({
-
success: function (res) {
-
var service_url = 'https://???/???/weixin/api/login?code=' + res.code;//需要將服務器域名添加到小程序的request合法域名中,而且必須是https開頭
-
wx.request({
-
url: l,
-
data: {},
-
method: 'GET',
-
success: function (res) {
-
console.log(res);
-
if (res.data != null && res.data != undefined && res.data != '') {
-
wx.setStorageSync("openid", res.data.openid);
-
}
-
}
-
});
-
}
-
});
后端java代碼
-
-
-
-
-
-
-
-
-
-
@RequestMapping("login")
-
@ResponseBody
-
public Map<String, Object> login(String code, HttpServletRequest request) throws WeixinException, JsonParseException, JsonMappingException, IOException {
-
if (code == null || code.equals("")) {
-
throw new WeixinException("invalid null, code is null.");
-
}
-
-
Map<String, Object> ret = new HashMap<String, Object>();
-
-
String param = "?grant_type=" + grant_type + "&appid=" + appid + "&secret=" + secret + "&js_code=" + code;
-
-
System.out.println("https://api.weixin.qq.com/sns/jscode2session" + param);
-
-
-
HttpsClient http = new HttpsClient();
-
-
Response res = http.get("https://api.weixin.qq.com/sns/jscode2session" + param);
-
-
JSONObject jsonObj = res.asJSONObject();
-
if (jsonObj != null) {
-
Object errcode = jsonObj.get("errcode");
-
if (errcode != null) {
-
-
throw new WeixinException(getCause(Integer.parseInt(errcode.toString())));
-
}
-
-
ObjectMapper mapper = new ObjectMapper();
-
OAuthJsToken oauthJsToken = mapper.readValue(jsonObj.toJSONString(),OAuthJsToken.class);
-
ret.put("openid", oauthJsToken.getOpenid());
-
}
-
return ret;
-
}
其中OAuthJsToken對象的字段為:openid、expires_in、session_key(會話密鑰) ,在文章結尾貼代碼;
3.2:生成領取卡券的簽名,并調用wx.addCard方法領取卡券
這邊寫貼java后端代碼
-
public static ApiTicket ticket = null;
-
-
-
-
-
-
-
-
-
-
@RequestMapping("getCardSign")
-
@ResponseBody
-
public Map<String, String> getCardSign(String cardId) throws WeixinException, JsonParseException, JsonMappingException, IOException{
-
Map<String, String> ret = new HashMap<String, String>();
-
-
if(ticket == null || ticket.getExpires_in() < System.currentTimeMillis()){
-
-
HttpsClient http = new HttpsClient();
-
-
ObjectMapper mapper = new ObjectMapper();
-
-
AccessToken token = OpenApi.getToken();
-
-
-
Response res = http.get("https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + token.getAccess_token() + "&type=wx_card");
-
System.out.println(res.asString());
-
ticket = mapper.readValue(res.asString(), ApiTicket.class);
-
}
-
-
ret = sign(ticket.getTicket(), cardId);
-
-
for (Map.Entry entry : ret.entrySet()) {
-
System.out.println(entry.getKey() + ", " + entry.getValue());
-
}
-
return ret;
-
}
-
-
-
-
-
-
-
public static Map<String, String> sign(String api_ticket, String cardId) {
-
Map<String, String> ret = new HashMap<String, String>();
-
String nonce_str = create_nonce_str();
-
String timestamp = create_timestamp();
-
String signature = "";
-
-
String param[] = new String[4];
-
-
param[0] = nonce_str;
-
param[1] = timestamp;
-
param[2] = api_ticket;
-
param[3] = cardId;
-
-
Arrays.sort(param);
-
-
StringBuilder sb = new StringBuilder();
-
for(String b : param){
-
sb.append(b);
-
}
-
System.out.println(sb);
-
-
try{
-
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
-
crypt.reset();
-
crypt.update(sb.toString().getBytes("UTF-8"));
-
signature = byteToHex(crypt.digest());
-
}catch (NoSuchAlgorithmException e){
-
e.printStackTrace();
-
}catch (UnsupportedEncodingException e){
-
e.printStackTrace();
-
}
-
-
-
ret.put("card_id", cardId);
-
ret.put("api_ticket", api_ticket);
-
ret.put("nonceStr", nonce_str);
-
ret.put("timestamp", timestamp);
-
ret.put("signature", signature);
-
-
return ret;
-
}
其中ApiTicket對象的屬性有:errcode、errmsg、ticket、expires_in,在文章結尾貼出該代碼
再貼小程序端代碼
-
var that = this;
-
var service_url = 'https://???/???/weixin/api/getCardSign?cardId=' + cardId;//需要將服務器域名添加到小程序的request合法域名中,而且必須是https開頭
-
wx.request({
-
url: service_url,
-
data: {},
-
method: 'GET',
-
success: function (res) {
-
console.log(res);
-
wx.addCard({
-
cardList: [{
-
cardId: that.data.cardId,
-
cardExt: '{"code":"","openid":"","timestamp":' + res.data.timestamp + ',"nonce_str":"' + res.data.nonceStr + '","signature":"' + res.data.signature + '"}'
-
}],
-
success: function (result) {
-
console.log(res);
-
-
wx.showToast({
-
title: '領取成功',
-
icon: 'success',
-
duration: 2000
-
});
-
},
-
fail: function (res) {
-
console.log('領取失敗');
-
console.log(res);
-
}
-
})
-
-
}
-
});
ok,如果領取成功,可以直接到微信卡包里面查看。下面貼出AccessToken、ApiTicket、OAuthJsToken的java模型代碼
-
public class BaseResponse {
-
private int errcode;
-
private String errmsg;
-
-
public int getErrcode() {
-
return errcode;
-
}
-
public void setErrcode(int errcode) {
-
this.errcode = errcode;
-
}
-
public String getErrmsg() {
-
return errmsg;
-
}
-
public void setErrmsg(String errmsg) {
-
this.errmsg = errmsg;
-
}
-
}
-
public class AccessToken extends BaseResponse{
-
private String access_token;
-
private long expires_in;
-
-
public String getAccess_token() {
-
return access_token;
-
}
-
public void setAccess_token(String access_token) {
-
this.access_token = access_token;
-
}
-
public long getExpires_in() {
-
return expires_in;
-
}
-
public void setExpires_in(long expires_in) {
-
this.expires_in = System.currentTimeMillis() + (expires_in - 100) * 1000;
-
}
-
}
-
public class ApiTicket extends BaseResponse{
-
private String ticket;
-
private long expires_in;
-
-
public String getTicket() {
-
return ticket;
-
}
-
public void setTicket(String ticket) {
-
this.ticket = ticket;
-
}
-
public long getExpires_in() {
-
return expires_in;
-
}
-
public void setExpires_in(long expires_in) {
-
this.expires_in = System.currentTimeMillis() + (expires_in - 100) * 1000;
-
}
-
}
-
public class OAuthJsToken {
-
private String openid;
-
private int expires_in = 7200;
-
private String session_key;
-
private long exprexpiredTime;
-
-
public String getOpenid() {
-
return openid;
-
}
-
public void setOpenid(String openid) {
-
this.openid = openid;
-
}
-
public int getExpires_in() {
-
return expires_in;
-
}
-
public void setExpires_in(int expires_in) {
-
this.expires_in = expires_in;
-
this.exprexpiredTime = System.currentTimeMillis() + expires_in * 1000;
-
}
-
public String getSession_key() {
-
return session_key;
-
}
-
public void setSession_key(String session_key) {
-
this.session_key = session_key;
-
}
-
-
public long getExprexpiredTime() {
-
return exprexpiredTime;
-
}
-
public void setExprexpiredTime(long exprexpiredTime) {
-
this.exprexpiredTime = exprexpiredTime;
-
}
-
-
-
-
-
-
public boolean isExprexpired() {
-
return System.currentTimeMillis() >= this.exprexpiredTime;
-
}
-
}
-