Ver código fonte

卡券接口代码提出单独类中维护,并添加单元测试

master
BinaryWang 8 anos atrás
pai
commit
1e4dd17ff5
7 arquivos alterados com 574 adições e 425 exclusões
  1. +68
    -60
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxCardApiSignature.java
  2. +115
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java
  3. +33
    -135
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
  4. +248
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
  5. +14
    -222
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java
  6. +3
    -8
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpCardResult.java
  7. +93
    -0
      weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java

+ 68
- 60
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxCardApiSignature.java Ver arquivo

@@ -1,5 +1,8 @@
package me.chanjar.weixin.common.bean;

import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.io.Serializable;

/**
@@ -10,95 +13,100 @@ import java.io.Serializable;
*/
public class WxCardApiSignature implements Serializable {

private static final long serialVersionUID = 158176707226975979L;
private static final long serialVersionUID = 158176707226975979L;

private String appId;
private String appId;

private String cardId;
private String cardId;

private String cardType;
private String cardType;

private String locationId;
private String locationId;

private String code;
private String code;

private String openId;
private String openId;

private Long timestamp;
private Long timestamp;

private String nonceStr;
private String nonceStr;

private String signature;
private String signature;

public String getAppId() {
return appId;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

public void setAppId(String appId) {
this.appId = appId;
}
public String getAppId() {
return appId;
}

public void setAppId(String appId) {
this.appId = appId;
}

public String getCardId() {
return cardId;
}
public String getCardId() {
return cardId;
}

public void setCardId(String cardId) {
this.cardId = cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}

public String getCardType() {
return cardType;
}
public String getCardType() {
return cardType;
}

public void setCardType(String cardType) {
this.cardType = cardType;
}
public void setCardType(String cardType) {
this.cardType = cardType;
}

public String getLocationId() {
return locationId;
}
public String getLocationId() {
return locationId;
}

public void setLocationId(String locationId) {
this.locationId = locationId;
}

public String getCode() {
return code;
}
return code;
}

public void setCode(String code) {
this.code = code;
}
public void setCode(String code) {
this.code = code;
}

public String getOpenId() {
return openId;
}
public String getOpenId() {
return openId;
}

public void setOpenId(String openId) {
this.openId = openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}

public Long getTimestamp() {
return timestamp;
}
public Long getTimestamp() {
return timestamp;
}

public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}

public String getNonceStr() {
return nonceStr;
}
public String getNonceStr() {
return nonceStr;
}

public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}

public String getSignature() {
return signature;
}
public String getSignature() {
return signature;
}

public void setSignature(String signature) {
this.signature = signature;
}
public void setSignature(String signature) {
this.signature = signature;
}
}

+ 115
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java Ver arquivo

@@ -0,0 +1,115 @@
package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.mp.bean.result.WxMpCardResult;

/**
* 卡券相关接口
* Created by Binary Wang on 2016/7/27.
* @author binarywang(https://github.com/binarywang)
*/
public interface WxMpCardService {

/**
* 获得卡券api_ticket,不强制刷新卡券api_ticket
*
* @return 卡券api_ticket
* @throws WxErrorException
* @see #getCardApiTicket(boolean)
*/
String getCardApiTicket() throws WxErrorException;

/**
* <pre>
* 获得卡券api_ticket
* 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94.9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param forceRefresh 强制刷新
* @return 卡券api_ticket
* @throws WxErrorException
*/
String getCardApiTicket(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
* 创建调用卡券api时所需要的签名
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
* .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
* .9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param optionalSignParam 参与签名的参数数组。
* 可以为下列字段:app_id, card_id, card_type, code, openid, location_id
* </br>注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空
* @return 卡券Api签名对象
*/
WxCardApiSignature createCardApiSignature(String... optionalSignParam) throws
WxErrorException;

/**
* 卡券Code解码
*
* @param encryptCode 加密Code,通过JSSDK的chooseCard接口获得
* @return 解密后的Code
*/
String decryptCardCode(String encryptCode) throws WxErrorException;

/**
* 卡券Code查询
*
* @param cardId 卡券ID代表一类卡券
* @param code 单张卡券的唯一标准
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同
* @return WxMpCardResult对象
*/
WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume)
throws WxErrorException;

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
*/
String consumeCardCode(String code) throws WxErrorException;

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @param cardId 当自定义Code卡券时需要传入card_id
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
*/
String consumeCardCode(String code, String cardId) throws WxErrorException;

/**
* 卡券Mark接口。
* 开发者在帮助消费者核销卡券之前,必须帮助先将此code(卡券串码)与一个openid绑定(即mark住),
* 才能进一步调用核销接口,否则报错。
*
* @param code 卡券的code码
* @param cardId 卡券的ID
* @param openId 用券用户的openid
* @param isMark 是否要mark(占用)这个code,填写true或者false,表示占用或解除占用
*/
void markCardCode(String code, String cardId, String openId, boolean isMark) throws
WxErrorException;

/**
* 查看卡券详情接口
* 详见 https://mp.weixin.qq.com/wiki/14/8dd77aeaee85f922db5f8aa6386d385e.html#.E6.9F.A5.E7.9C.8B.E5.8D.A1.E5.88.B8.E8.AF.A6.E6.83.85
*
* @param cardId 卡券的ID
* @return 返回的卡券详情JSON字符串
* <br> [注] 由于返回的JSON格式过于复杂,难以定义其对应格式的Bean并且难以维护,因此只返回String格式的JSON串。
* <br> 可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。
*/
String getCardDetail(String cardId) throws WxErrorException;
}

+ 33
- 135
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java Ver arquivo

@@ -1,6 +1,5 @@
package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestExecutor;
@@ -15,7 +14,7 @@ import java.util.Map;
*/
public interface WxMpService {

public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");

/**
* <pre>
@@ -23,14 +22,14 @@ public interface WxMpService {
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=验证消息真实性
* </pre>
*/
public boolean checkSignature(String timestamp, String nonce, String signature);
boolean checkSignature(String timestamp, String nonce, String signature);

/**
* 获取access_token, 不强制刷新access_token
*
* @see #getAccessToken(boolean)
*/
public String getAccessToken() throws WxErrorException;
String getAccessToken() throws WxErrorException;

/**
* <pre>
@@ -46,14 +45,14 @@ public interface WxMpService {
*
* @param forceRefresh 强制刷新
*/
public String getAccessToken(boolean forceRefresh) throws WxErrorException;
String getAccessToken(boolean forceRefresh) throws WxErrorException;

/**
* 获得jsapi_ticket,不强制刷新jsapi_ticket
*
* @see #getJsapiTicket(boolean)
*/
public String getJsapiTicket() throws WxErrorException;
String getJsapiTicket() throws WxErrorException;

/**
* <pre>
@@ -65,7 +64,7 @@ public interface WxMpService {
*
* @param forceRefresh 强制刷新
*/
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException;
String getJsapiTicket(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
@@ -74,7 +73,7 @@ public interface WxMpService {
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95
* </pre>
*/
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException;
WxJsapiSignature createJsapiSignature(String url) throws WxErrorException;

/**
* <pre>
@@ -82,7 +81,7 @@ public interface WxMpService {
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息
* </pre>
*/
public void customMessageSend(WxMpCustomMessage message) throws WxErrorException;
void customMessageSend(WxMpCustomMessage message) throws WxErrorException;

/**
* <pre>
@@ -96,7 +95,7 @@ public interface WxMpService {
* @see #massGroupMessageSend(me.chanjar.weixin.mp.bean.WxMpMassGroupMessage)
* @see #massOpenIdsMessageSend(me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage)
*/
public WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException;
WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException;

/**
* <pre>
@@ -107,7 +106,7 @@ public interface WxMpService {
* @see #massGroupMessageSend(me.chanjar.weixin.mp.bean.WxMpMassGroupMessage)
* @see #massOpenIdsMessageSend(me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage)
*/
public WxMpMassUploadResult massVideoUpload(WxMpMassVideo video) throws WxErrorException;
WxMpMassUploadResult massVideoUpload(WxMpMassVideo video) throws WxErrorException;

/**
* <pre>
@@ -117,7 +116,7 @@ public interface WxMpService {
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
* </pre>
*/
public WxMpMassSendResult massGroupMessageSend(WxMpMassGroupMessage message) throws WxErrorException;
WxMpMassSendResult massGroupMessageSend(WxMpMassGroupMessage message) throws WxErrorException;

/**
* <pre>
@@ -127,7 +126,7 @@ public interface WxMpService {
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
* </pre>
*/
public WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException;
WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException;

/**
* <pre>
@@ -137,7 +136,7 @@ public interface WxMpService {
*
* @param long_url
*/
public String shortUrl(String long_url) throws WxErrorException;
String shortUrl(String long_url) throws WxErrorException;

/**
* <pre>
@@ -149,7 +148,7 @@ public interface WxMpService {
* @return msgid
* @throws WxErrorException
*/
public String templateSend(WxMpTemplateMessage templateMessage) throws WxErrorException;
String templateSend(WxMpTemplateMessage templateMessage) throws WxErrorException;

/**
* <pre>
@@ -169,7 +168,7 @@ public interface WxMpService {
* @param state
* @return url
*/
public String oauth2buildAuthorizationUrl(String scope, String state);
String oauth2buildAuthorizationUrl(String scope, String state);

/**
* <pre>
@@ -182,7 +181,7 @@ public interface WxMpService {
* @param state
* @return url
*/
public String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state);
String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state);

/**
* <pre>
@@ -190,14 +189,14 @@ public interface WxMpService {
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息
* </pre>
*/
public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException;
WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException;

/**
* <pre>
* 刷新oauth2的access token
* </pre>
*/
public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException;
WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException;

/**
* <pre>
@@ -207,7 +206,7 @@ public interface WxMpService {
* @param oAuth2AccessToken
* @param lang zh_CN, zh_TW, en
*/
public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException;
WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException;

/**
* <pre>
@@ -216,7 +215,7 @@ public interface WxMpService {
*
* @param oAuth2AccessToken
*/
public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken);
boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken);

/**
* <pre>
@@ -243,12 +242,12 @@ public interface WxMpService {
* 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法
* </pre>
*/
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException;
<T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException;

/**
* 注入 {@link WxMpConfigStorage} 的实现
*/
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider);
void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider);

/**
* <pre>
@@ -361,7 +360,7 @@ public interface WxMpService {
* @return 退款操作结果
* @throws WxErrorException
*/
public WxMpPayRefundResult refundPay(Map<String, String> parameters) throws WxErrorException;
WxMpPayRefundResult refundPay(Map<String, String> parameters) throws WxErrorException;

/**
* <pre>
@@ -372,7 +371,7 @@ public interface WxMpService {
* @param kvm
* @param signature
*/
public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature);
boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature);

/**
* 发送微信红包给个人用户
@@ -395,115 +394,7 @@ public interface WxMpService {
*
* @param parameters
*/
public WxRedpackResult sendRedpack(Map<String, String> parameters) throws WxErrorException;

/**
* 获得卡券api_ticket,不强制刷新卡券api_ticket
*
* @return 卡券api_ticket
* @throws WxErrorException
* @see #getCardApiTicket(boolean)
*/
public String getCardApiTicket() throws WxErrorException;

/**
* <pre>
* 获得卡券api_ticket
* 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94.9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param forceRefresh 强制刷新
* @return 卡券api_ticket
* @throws WxErrorException
*/
public String getCardApiTicket(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
* 创建调用卡券api时所需要的签名
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
* .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
* .9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param optionalSignParam 参与签名的参数数组。
* 可以为下列字段:app_id, card_id, card_type, code, openid, location_id
* </br>注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空
* @return 卡券Api签名对象
*/
public WxCardApiSignature createCardApiSignature(String... optionalSignParam) throws
WxErrorException;

/**
* 卡券Code解码
*
* @param encryptCode 加密Code,通过JSSDK的chooseCard接口获得
* @return 解密后的Code
* @throws WxErrorException
*/
public String decryptCardCode(String encryptCode) throws WxErrorException;

/**
* 卡券Code查询
*
* @param cardId 卡券ID代表一类卡券
* @param code 单张卡券的唯一标准
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同
* @return WxMpCardResult对象
* @throws WxErrorException
*/
public WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume)
throws WxErrorException;

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
* @throws WxErrorException
*/
public String consumeCardCode(String code) throws WxErrorException;

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @param cardId 当自定义Code卡券时需要传入card_id
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
* @throws WxErrorException
*/
public String consumeCardCode(String code, String cardId) throws WxErrorException;

/**
* 卡券Mark接口。
* 开发者在帮助消费者核销卡券之前,必须帮助先将此code(卡券串码)与一个openid绑定(即mark住),
* 才能进一步调用核销接口,否则报错。
*
* @param code 卡券的code码
* @param cardId 卡券的ID
* @param openId 用券用户的openid
* @param isMark 是否要mark(占用)这个code,填写true或者false,表示占用或解除占用
* @throws WxErrorException
*/
public void markCardCode(String code, String cardId, String openId, boolean isMark) throws
WxErrorException;

/**
* 查看卡券详情接口
* 详见 https://mp.weixin.qq.com/wiki/14/8dd77aeaee85f922db5f8aa6386d385e.html#.E6.9F.A5.E7.9C.8B.E5.8D.A1.E5.88.B8.E8.AF.A6.E6.83.85
*
* @param cardId 卡券的ID
* @return 返回的卡券详情JSON字符串
* <br> [注] 由于返回的JSON格式过于复杂,难以定义其对应格式的Bean并且难以维护,因此只返回String格式的JSON串。
* <br> 可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。
* @throws WxErrorException
*/
public String getCardDetail(String cardId) throws WxErrorException;
WxRedpackResult sendRedpack(Map<String, String> parameters) throws WxErrorException;

/**
* <pre>
@@ -515,7 +406,7 @@ public interface WxMpService {
* @return wxMpMassSendResult
* @throws WxErrorException
*/
public WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception;
WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception;

/**
* <pre>
@@ -589,4 +480,11 @@ public interface WxMpService {
* @return WxMpQrcodeService
*/
WxMpQrcodeService getQrcodeService();

/**
* 返回卡券相关接口的方法实现类,以方便调用个其各种接口
*
* @return WxMpCardService
*/
WxMpCardService getCardService();
}

+ 248
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java Ver arquivo

@@ -0,0 +1,248 @@
package me.chanjar.weixin.mp.api.impl;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import me.chanjar.weixin.mp.api.WxMpCardService;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpCardResult;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.StringReader;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
* Created by Binary Wang on 2016/7/27.
*/
public class WxMpCardServiceImpl implements WxMpCardService {

private final Logger log = LoggerFactory.getLogger(WxMpCardServiceImpl.class);

/**
* 全局的是否正在刷新卡券api_ticket的锁
*/
private final Object globalCardApiTicketRefreshLock = new Object();

private WxMpService wxMpService;

WxMpCardServiceImpl(WxMpService wxMpService) {
this.wxMpService = wxMpService;
}

/**
* 获得卡券api_ticket,不强制刷新卡券api_ticket
*
* @return 卡券api_ticket
* @see #getCardApiTicket(boolean)
*/
@Override
public String getCardApiTicket() throws WxErrorException {
return getCardApiTicket(false);
}

/**
* <pre>
* 获得卡券api_ticket
* 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
* .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
* .9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param forceRefresh 强制刷新
* @return 卡券api_ticket
*/
@Override
public String getCardApiTicket(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.wxMpService.getWxMpConfigStorage().expireCardApiTicket();
}
if (this.wxMpService.getWxMpConfigStorage().isCardApiTicketExpired()) {
synchronized (this.globalCardApiTicketRefreshLock) {
if (this.wxMpService.getWxMpConfigStorage().isCardApiTicketExpired()) {
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card";
String responseContent = this.wxMpService.execute(new SimpleGetRequestExecutor(), url, null);
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
String cardApiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.wxMpService.getWxMpConfigStorage().updateCardApiTicket(cardApiTicket, expiresInSeconds);
}
}
}
return this.wxMpService.getWxMpConfigStorage().getCardApiTicket();
}

/**
* <pre>
* 创建调用卡券api时所需要的签名
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
* .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
* .9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param optionalSignParam 参与签名的参数数组。
* 可以为下列字段:app_id, card_id, card_type, code, openid, location_id
* </br>注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空
* @return 卡券Api签名对象
*/
@Override
public WxCardApiSignature createCardApiSignature(String... optionalSignParam) throws
WxErrorException {
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = RandomUtils.getRandomStr();
String cardApiTicket = getCardApiTicket(false);

String[] signParam = Arrays.copyOf(optionalSignParam, optionalSignParam.length + 3);
signParam[optionalSignParam.length] = String.valueOf(timestamp);
signParam[optionalSignParam.length + 1] = nonceStr;
signParam[optionalSignParam.length + 2] = cardApiTicket;
try {
String signature = SHA1.gen(signParam);
WxCardApiSignature cardApiSignature = new WxCardApiSignature();
cardApiSignature.setTimestamp(timestamp);
cardApiSignature.setNonceStr(nonceStr);
cardApiSignature.setSignature(signature);
return cardApiSignature;
} catch (NoSuchAlgorithmException e) {
throw new WxErrorException(WxError.newBuilder().setErrorMsg(e.getMessage()).build());
}
}

/**
* 卡券Code解码
*
* @param encryptCode 加密Code,通过JSSDK的chooseCard接口获得
* @return 解密后的Code
*/
@Override
public String decryptCardCode(String encryptCode) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/code/decrypt";
JsonObject param = new JsonObject();
param.addProperty("encrypt_code", encryptCode);
String responseContent = this.wxMpService.post(url, param.toString());
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
JsonPrimitive jsonPrimitive = tmpJsonObject.getAsJsonPrimitive("code");
return jsonPrimitive.getAsString();
}

/**
* 卡券Code查询
*
* @param cardId 卡券ID代表一类卡券
* @param code 单张卡券的唯一标准
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同
* @return WxMpCardResult对象
*/
@Override
public WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/code/get";
JsonObject param = new JsonObject();
param.addProperty("card_id", cardId);
param.addProperty("code", code);
param.addProperty("check_consume", checkConsume);
String responseContent = this.wxMpService.post(url, param.toString());
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement,
new TypeToken<WxMpCardResult>() {
}.getType());
}

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
*/
@Override
public String consumeCardCode(String code) throws WxErrorException {
return consumeCardCode(code, null);
}

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @param cardId 当自定义Code卡券时需要传入card_id
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
*/
@Override
public String consumeCardCode(String code, String cardId) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/code/consume";
JsonObject param = new JsonObject();
param.addProperty("code", code);

if (cardId != null && !"".equals(cardId)) {
param.addProperty("card_id", cardId);
}

return this.wxMpService.post(url, param.toString());
}

/**
* 卡券Mark接口。
* 开发者在帮助消费者核销卡券之前,必须帮助先将此code(卡券串码)与一个openid绑定(即mark住),
* 才能进一步调用核销接口,否则报错。
*
* @param code 卡券的code码
* @param cardId 卡券的ID
* @param openId 用券用户的openid
* @param isMark 是否要mark(占用)这个code,填写true或者false,表示占用或解除占用
*/
@Override
public void markCardCode(String code, String cardId, String openId, boolean isMark) throws
WxErrorException {
String url = "https://api.weixin.qq.com/card/code/mark";
JsonObject param = new JsonObject();
param.addProperty("code", code);
param.addProperty("card_id", cardId);
param.addProperty("openid", openId);
param.addProperty("is_mark", isMark);
String responseContent = this.wxMpService.post(url, param.toString());
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
WxMpCardResult cardResult = WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement,
new TypeToken<WxMpCardResult>() { }.getType());
if (!cardResult.getErrorCode().equals("0")) {
this.log.warn("朋友的券mark失败:{}", cardResult.getErrorMsg());
}
}

@Override
public String getCardDetail(String cardId) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/get";
JsonObject param = new JsonObject();
param.addProperty("card_id", cardId);
String responseContent = this.wxMpService.post(url, param.toString());

// 判断返回值
JsonObject json = (new JsonParser()).parse(responseContent).getAsJsonObject();
String errcode = json.get("errcode").getAsString();
if (!"0".equals(errcode)) {
String errmsg = json.get("errmsg").getAsString();
WxError error = new WxError();
error.setErrorCode(Integer.valueOf(errcode));
error.setErrorMsg(errmsg);
throw new WxErrorException(error);
}

return responseContent;
}
}

+ 14
- 222
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java Ver arquivo

@@ -1,12 +1,12 @@
package me.chanjar.weixin.mp.api.impl;

import com.google.gson.*;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.internal.Streams;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.thoughtworks.xstream.XStream;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
@@ -20,7 +20,6 @@ import me.chanjar.weixin.common.util.xml.XStreamInitializer;
import me.chanjar.weixin.mp.api.*;
import me.chanjar.weixin.mp.bean.*;
import me.chanjar.weixin.mp.bean.result.*;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;
@@ -39,8 +38,11 @@ import org.slf4j.helpers.MessageFormatter;
import java.io.IOException;
import java.io.StringReader;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;

public class WxMpServiceImpl implements WxMpService {

@@ -56,11 +58,6 @@ public class WxMpServiceImpl implements WxMpService {
*/
protected final Object globalJsapiTicketRefreshLock = new Object();

/**
* 全局的是否正在刷新卡券api_ticket的锁
*/
protected final Object globalCardApiTicketRefreshLock = new Object();

protected WxMpConfigStorage wxMpConfigStorage;
protected WxMpKefuService kefuService = new WxMpKefuServiceImpl(this);
@@ -75,6 +72,8 @@ public class WxMpServiceImpl implements WxMpService {

protected WxMpQrcodeService qrCodeService = new WxMpQrcodeServiceImpl(this);

protected WxMpCardService cardService = new WxMpCardServiceImpl(this);

protected CloseableHttpClient httpClient;

protected HttpHost httpProxy;
@@ -782,218 +781,6 @@ public class WxMpServiceImpl implements WxMpService {
}
}

/**
* 获得卡券api_ticket,不强制刷新卡券api_ticket
*
* @return 卡券api_ticket
* @throws WxErrorException
* @see #getCardApiTicket(boolean)
*/
@Override
public String getCardApiTicket() throws WxErrorException {
return getCardApiTicket(false);
}

/**
* <pre>
* 获得卡券api_ticket
* 获得时会检查卡券apiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
* .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
* .9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param forceRefresh 强制刷新
* @return 卡券api_ticket
* @throws WxErrorException
*/
@Override
public String getCardApiTicket(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.wxMpConfigStorage.expireCardApiTicket();
}
if (this.wxMpConfigStorage.isCardApiTicketExpired()) {
synchronized (this.globalCardApiTicketRefreshLock) {
if (this.wxMpConfigStorage.isCardApiTicketExpired()) {
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card";
String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
String cardApiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.wxMpConfigStorage.updateCardApiTicket(cardApiTicket, expiresInSeconds);
}
}
}
return this.wxMpConfigStorage.getCardApiTicket();
}

/**
* <pre>
* 创建调用卡券api时所需要的签名
*
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD
* .954-.E5.8D.A1.E5.88.B8.E6.89.A9.E5.B1.95.E5.AD.97.E6.AE.B5.E5.8F.8A.E7.AD.BE.E5.90.8D.E7.94
* .9F.E6.88.90.E7.AE.97.E6.B3.95
* </pre>
*
* @param optionalSignParam 参与签名的参数数组。
* 可以为下列字段:app_id, card_id, card_type, code, openid, location_id
* </br>注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空
* @return 卡券Api签名对象
*/
@Override
public WxCardApiSignature createCardApiSignature(String... optionalSignParam) throws
WxErrorException {
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = RandomUtils.getRandomStr();
String cardApiTicket = getCardApiTicket(false);

String[] signParam = Arrays.copyOf(optionalSignParam, optionalSignParam.length + 3);
signParam[optionalSignParam.length] = String.valueOf(timestamp);
signParam[optionalSignParam.length + 1] = nonceStr;
signParam[optionalSignParam.length + 2] = cardApiTicket;
try {
String signature = SHA1.gen(signParam);
WxCardApiSignature cardApiSignature = new WxCardApiSignature();
cardApiSignature.setTimestamp(timestamp);
cardApiSignature.setNonceStr(nonceStr);
cardApiSignature.setSignature(signature);
return cardApiSignature;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

/**
* 卡券Code解码
*
* @param encryptCode 加密Code,通过JSSDK的chooseCard接口获得
* @return 解密后的Code
* @throws WxErrorException
*/
@Override
public String decryptCardCode(String encryptCode) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/code/decrypt";
JsonObject param = new JsonObject();
param.addProperty("encrypt_code", encryptCode);
String responseContent = post(url, param.toString());
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
JsonPrimitive jsonPrimitive = tmpJsonObject.getAsJsonPrimitive("code");
return jsonPrimitive.getAsString();
}

/**
* 卡券Code查询
*
* @param cardId 卡券ID代表一类卡券
* @param code 单张卡券的唯一标准
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同
* @return WxMpCardResult对象
* @throws WxErrorException
*/
@Override
public WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/code/get";
JsonObject param = new JsonObject();
param.addProperty("card_id", cardId);
param.addProperty("code", code);
param.addProperty("check_consume", checkConsume);
String responseContent = post(url, param.toString());
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement,
new TypeToken<WxMpCardResult>() {
}.getType());
}

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
* @throws WxErrorException
*/
@Override
public String consumeCardCode(String code) throws WxErrorException {
return consumeCardCode(code, null);
}

/**
* 卡券Code核销。核销失败会抛出异常
*
* @param code 单张卡券的唯一标准
* @param cardId 当自定义Code卡券时需要传入card_id
* @return 调用返回的JSON字符串。
* <br>可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。
* @throws WxErrorException
*/
@Override
public String consumeCardCode(String code, String cardId) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/code/consume";
JsonObject param = new JsonObject();
param.addProperty("code", code);
if (cardId != null && !"".equals(cardId)) {
param.addProperty("card_id", cardId);
}
String responseContent = post(url, param.toString());
return responseContent;
}

/**
* 卡券Mark接口。
* 开发者在帮助消费者核销卡券之前,必须帮助先将此code(卡券串码)与一个openid绑定(即mark住),
* 才能进一步调用核销接口,否则报错。
*
* @param code 卡券的code码
* @param cardId 卡券的ID
* @param openId 用券用户的openid
* @param isMark 是否要mark(占用)这个code,填写true或者false,表示占用或解除占用
* @throws WxErrorException
*/
@Override
public void markCardCode(String code, String cardId, String openId, boolean isMark) throws
WxErrorException {
String url = "https://api.weixin.qq.com/card/code/mark";
JsonObject param = new JsonObject();
param.addProperty("code", code);
param.addProperty("card_id", cardId);
param.addProperty("openid", openId);
param.addProperty("is_mark", isMark);
String responseContent = post(url, param.toString());
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
WxMpCardResult cardResult = WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement,
new TypeToken<WxMpCardResult>() { }.getType());
if (!cardResult.getErrorCode().equals("0")) {
this.log.warn("朋友的券mark失败:{}", cardResult.getErrorMsg());
}
}

@Override
public String getCardDetail(String cardId) throws WxErrorException {
String url = "https://api.weixin.qq.com/card/get";
JsonObject param = new JsonObject();
param.addProperty("card_id", cardId);
String responseContent = post(url, param.toString());
// 判断返回值
JsonObject json = (new JsonParser()).parse(responseContent).getAsJsonObject();
String errcode = json.get("errcode").getAsString();
if (!"0".equals(errcode)) {
String errmsg = json.get("errmsg").getAsString();
WxError error = new WxError();
error.setErrorCode(Integer.valueOf(errcode));
error.setErrorMsg(errmsg);
throw new WxErrorException(error);
}
return responseContent;
}

@Override
public WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception {
String url = "https://api.weixin.qq.com/cgi-bin/message/mass/preview";
@@ -1048,4 +835,9 @@ public class WxMpServiceImpl implements WxMpService {
return this.qrCodeService;
}

@Override
public WxMpCardService getCardService() {
return this.cardService;
}

}

+ 3
- 8
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpCardResult.java Ver arquivo

@@ -1,6 +1,8 @@
package me.chanjar.weixin.mp.bean.result;

import me.chanjar.weixin.mp.bean.WxMpCard;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.io.Serializable;

@@ -58,14 +60,7 @@ public class WxMpCardResult implements Serializable {

@Override
public String toString() {
return "WxMpCardResult{" +
"errorCode='" + errorCode + '\'' +
", errorMsg='" + errorMsg + '\'' +
", openId='" + openId + '\'' +
", card=" + card +
", userCardStatus='" + userCardStatus + '\'' +
", canConsume=" + canConsume +
'}';
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

public String getUserCardStatus() {


+ 93
- 0
weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java Ver arquivo

@@ -0,0 +1,93 @@
package me.chanjar.weixin.mp.api.impl;

import com.google.inject.Inject;
import me.chanjar.weixin.common.bean.WxCardApiSignature;
import me.chanjar.weixin.mp.api.ApiTestModule;
import me.chanjar.weixin.mp.bean.result.WxMpCardResult;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import static org.testng.AssertJUnit.assertNotNull;

/**
* 测试代码仅供参考,未做严格测试,因原接口作者并未提供单元测试代码
* Created by Binary Wang on 2016/7/27.
* @author binarywang (https://github.com/binarywang)
*/
@Test
@Guice(modules = ApiTestModule.class)
public class WxMpCardServiceImplTest {

@Inject
protected WxMpServiceImpl wxService;
private String cardId = "123";
private String code = "good";
private String openid = "abc";

@Test
public void testGetCardApiTicket() throws Exception {
String cardApiTicket = this.wxService.getCardService().getCardApiTicket();
assertNotNull(cardApiTicket);
System.out.println(cardApiTicket);
}

@Test
public void testGetCardApiTicketWithParam() throws Exception {
String cardApiTicket = this.wxService.getCardService().getCardApiTicket(true);
assertNotNull(cardApiTicket);
System.out.println(cardApiTicket);
}

@Test
public void testCreateCardApiSignature() throws Exception {
//app_id, card_id, card_type, code, openid, location_id

String[] param = {"123", cardId, "", code, openid, ""};
WxCardApiSignature cardApiSignature = this.wxService.getCardService().createCardApiSignature(param);
assertNotNull(cardApiSignature);
System.out.println(cardApiSignature);
}

@Test
public void testDecryptCardCode() throws Exception {
String encryptCode = "pd0vTUHSHc9tMUCL2gXABcUmINm6yxqJh0y9Phsy63E=";
String cardCode = this.wxService.getCardService().decryptCardCode(encryptCode);
assertNotNull(cardCode);
System.out.println(cardCode);
}

@Test
public void testQueryCardCode() throws Exception {
WxMpCardResult wxMpCardResult = this.wxService.getCardService().queryCardCode(cardId, code, false);
assertNotNull(wxMpCardResult);
System.out.println(wxMpCardResult);
}

@Test
public void testConsumeCardCode() throws Exception {
String result = this.wxService.getCardService().consumeCardCode(code);
assertNotNull(result);
System.out.println(result);
}

@Test
public void testConsumeCardCodeWithCardId() throws Exception {
String result = this.wxService.getCardService().consumeCardCode(code, cardId);
assertNotNull(result);
System.out.println(result);
}

@Test
public void testMarkCardCode() throws Exception {
this.wxService.getCardService().markCardCode(code, cardId, openid, true);
System.out.println("done");
}

@Test
public void testGetCardDetail() throws Exception {
String result = this.wxService.getCardService().getCardDetail(cardId);
assertNotNull(result);
System.out.println(result);
}

}

Carregando…
Cancelar
Salvar