#320 增加“拉取订单评价数据“接口方法

This commit is contained in:
Binary Wang 2017-09-02 23:48:33 +08:00
parent 434703327b
commit a5c61268ef
8 changed files with 292 additions and 31 deletions

View File

@ -95,6 +95,19 @@ public abstract class WxPayBaseRequest {
@XStreamAlias("sign")
protected String sign;
/**
* <pre>
* 签名类型
* sign_type
*
* String(32)
* HMAC-SHA256
* 签名类型目前支持HMAC-SHA256和MD5
* </pre>
*/
@XStreamAlias("sign_type")
private String signType;
/**
* 将单位为元转换为单位为分
*
@ -187,6 +200,14 @@ public abstract class WxPayBaseRequest {
this.subMchId = subMchId;
}
public String getSignType() {
return signType;
}
public void setSignType(String signType) {
this.signType = signType;
}
@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
@ -230,9 +251,8 @@ public abstract class WxPayBaseRequest {
if (StringUtils.isBlank(getNonceStr())) {
this.setNonceStr(String.valueOf(System.currentTimeMillis()));
}
//设置签名字段的值
this.setSign(SignUtils.createSign(this, config.getMchKey()));
this.setSign(SignUtils.createSign(this, config.getMchKey(), this.signType));
}
}

View File

@ -0,0 +1,112 @@
package com.github.binarywang.wxpay.bean.request;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import me.chanjar.weixin.common.annotation.Required;
/**
* <pre>
* 拉取订单评价数据接口的请求参数封装类
* Created by BinaryWang on 2017/9/2.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@XStreamAlias("xml")
public class WxPayQueryCommentRequest extends WxPayBaseRequest {
/**
* <pre>
* 字段名开始时间
* 变量名begin_time
* 是否必填
* 类型String(19)
* 示例值20170724000000
* 描述按用户评论时间批量拉取的起始时间格式为yyyyMMddHHmmss
* </pre>
*/
@Required
@XStreamAlias("begin_time")
private String beginTime;
/**
* <pre>
* 字段名结束时间
* 变量名end_time
* 是否必填
* 类型String(19)
* 示例值20170725000000
* 描述按用户评论时间批量拉取的结束时间格式为yyyyMMddHHmmss
* </pre>
*/
@Required
@XStreamAlias("end_time")
private String endTime;
/**
* <pre>
* 字段名位移
* 变量名offset
* 是否必填
* 类型uint(64)
* 示例值0
* 描述指定从某条记录的下一条开始返回记录接口调用成功时会返回本次查询最后一条数据的offset商户需要翻页时应该把本次调用返回的offset 作为下次调用的入参注意offset是评论数据在微信支付后台保存的索引未必是连续的
* </pre>
*/
@Required
@XStreamAlias("offset")
private Integer offset;
/**
* <pre>
* 字段名条数
* 变量名limit
* 是否必填
* 类型uint(32)
* 示例值100
* 描述一次拉取的条数, 最大值是200默认是200
* </pre>
*/
@XStreamAlias("limit")
private Integer limit;
/**
* 检查约束情况
*/
@Override
protected void checkConstraints() throws WxPayException {
}
public String getBeginTime() {
return beginTime;
}
public void setBeginTime(String beginTime) {
this.beginTime = beginTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
public Integer getOffset() {
return offset;
}
public void setOffset(Integer offset) {
this.offset = offset;
}
public Integer getLimit() {
return limit;
}
public void setLimit(Integer limit) {
this.limit = limit;
}
}

View File

@ -1,5 +1,7 @@
package com.github.binarywang.wxpay.constant;
import java.text.SimpleDateFormat;
/**
* <pre>
* 微信支付常量类
@ -9,6 +11,12 @@ package com.github.binarywang.wxpay.constant;
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxPayConstants {
/**
* 拉取订单评价数据接口的参数中日期格式
*/
public static final SimpleDateFormat QUERY_COMMENT_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss");
/**
* 校验用户姓名选项企业付款时使用
*/

View File

@ -10,6 +10,7 @@ import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import java.io.File;
import java.util.Date;
import java.util.Map;
/**
@ -115,8 +116,8 @@ public interface WxPayService {
throws WxPayException;
/**
* @deprecated use WxPayService#parseOrderNotifyResult(String) instead
* @see WxPayService#parseOrderNotifyResult(String)
* @deprecated use WxPayService#parseOrderNotifyResult(String) instead
*/
@Deprecated
WxPayOrderNotifyResult getOrderNotifyResult(String xmlData) throws WxPayException;
@ -389,4 +390,23 @@ public interface WxPayService {
* 获取微信请求数据方便接口调用方获取处理
*/
WxPayApiData getWxApiData();
/**
* <pre>
* 拉取订单评价数据
* 商户可以通过该接口拉取用户在微信支付交易记录中针对你的支付记录进行的评价内容商户可结合商户系统逻辑对该内容数据进行存储分析展示客服回访以及其他使用如商户业务对评价内容有依赖可主动引导用户进入微信支付交易记录进行评价
* 注意
* 1. 该内容所有权为提供内容的微信用户商户在使用内容的过程中应遵从用户意愿
* 2. 一次最多拉取200条评价数据可根据时间区间分批次拉取
* 3. 接口只能拉取最近三个月以内的评价数据
* 接口链接https://api.mch.weixin.qq.com/billcommentsp/batchquerycomment
* 是否需要证书需要
* 文档地址https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_17&index=10
* </pre>
* @param beginDate 开始时间
* @param endDate 结束时间
* @param offset 位移
* @param limit 条数
*/
String queryComment(Date beginDate, Date endDate, Integer offset, Integer limit) throws WxPayException;
}

View File

@ -23,10 +23,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT;
/**
* <pre>
@ -237,7 +236,7 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
configMap.put("appid", appId);
payResult = WxPayAppOrderResult.newBuilder()
.sign(SignUtils.createSign(configMap, this.getConfig().getMchKey()))
.sign(SignUtils.createSign(configMap, this.getConfig().getMchKey(), null))
.prepayId(prepayId)
.partnerId(partnerId)
.appId(appId)
@ -256,7 +255,7 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
.signType(SignType.MD5)
.build();
((WxPayMpOrderResult) payResult)
.setPaySign(SignUtils.createSign(payResult, this.getConfig().getMchKey()));
.setPaySign(SignUtils.createSign(payResult, this.getConfig().getMchKey(), null));
break;
}
}
@ -303,7 +302,7 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
configMap.put("noncestr", nonceStr);
configMap.put("appid", appId);
// 此map用于客户端与微信服务器交互
payInfo.put("sign", SignUtils.createSign(configMap, this.getConfig().getMchKey()));
payInfo.put("sign", SignUtils.createSign(configMap, this.getConfig().getMchKey(), null));
payInfo.put("prepayId", prepayId);
payInfo.put("partnerId", partnerId);
payInfo.put("appId", appId);
@ -317,7 +316,7 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
payInfo.put("nonceStr", nonceStr);
payInfo.put("package", "prepay_id=" + prepayId);
payInfo.put("signType", SignType.MD5);
payInfo.put("paySign", SignUtils.createSign(payInfo, this.getConfig().getMchKey()));
payInfo.put("paySign", SignUtils.createSign(payInfo, this.getConfig().getMchKey(), null));
}
return payInfo;
@ -364,7 +363,7 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
params.put("time_stamp", String.valueOf(System.currentTimeMillis() / 1000));//这里需要秒10位数字
params.put("nonce_str", String.valueOf(System.currentTimeMillis()));
String sign = SignUtils.createSign(params, this.getConfig().getMchKey());
String sign = SignUtils.createSign(params, this.getConfig().getMchKey(), null);
params.put("sign", sign);
for (String key : params.keySet()) {
@ -411,15 +410,13 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
String url = this.getPayBaseUrl() + "/pay/downloadbill";
String responseContent = this.post(url, request.toXML(), false);
if (responseContent.startsWith("<")) {
WxPayCommonResult result = WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class);
result.checkResult(this);
return null;
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class));
} else {
return billInformationDeal(responseContent);
return this.handleBillInformation(responseContent);
}
}
private WxPayBillResult billInformationDeal(String responseContent) {
private WxPayBillResult handleBillInformation(String responseContent) {
WxPayBillResult wxPayBillResult = new WxPayBillResult();
String listStr = "";
@ -597,4 +594,25 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService {
wxApiData.remove();
}
}
@Override
public String queryComment(Date beginDate, Date endDate, Integer offset, Integer limit) throws WxPayException {
WxPayQueryCommentRequest request = new WxPayQueryCommentRequest();
request.setBeginTime(QUERY_COMMENT_DATE_FORMAT.format(beginDate));
request.setEndTime(QUERY_COMMENT_DATE_FORMAT.format(endDate));
request.setOffset(offset);
request.setLimit(limit);
request.setSignType(SignType.HMAC_SHA256);
request.checkAndSign(this.getConfig());
String url = this.getPayBaseUrl() + "/billcommentsp/batchquerycomment";
String responseContent = this.post(url, request.toXML(), true);
if (responseContent.startsWith("<")) {
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class));
}
return responseContent;
}
}

View File

@ -1,9 +1,15 @@
package com.github.binarywang.wxpay.util;
import com.github.binarywang.wxpay.constant.WxPayConstants.SignType;
import me.chanjar.weixin.common.util.BeanUtils;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
@ -22,10 +28,11 @@ public class SignUtils {
*
* @param xmlBean Bean需要标记有XML注解
* @param signKey 签名Key
* @param signType 签名类型如果为空则默认为MD5
* @return 签名字符串
*/
public static String createSign(Object xmlBean, String signKey) {
return createSign(BeanUtils.xmlBean2Map(xmlBean), signKey);
public static String createSign(Object xmlBean, String signKey, String signType) {
return createSign(BeanUtils.xmlBean2Map(xmlBean), signKey, signType);
}
/**
@ -33,9 +40,10 @@ public class SignUtils {
*
* @param params 参数信息
* @param signKey 签名Key
* @param signType 签名类型如果为空则默认为md5
* @return 签名字符串
*/
public static String createSign(Map<String, String> params, String signKey) {
public static String createSign(Map<String, String> params, String signKey, String signType) {
// if (this.getConfig().useSandbox()) {
// //使用仿真测试环境
// //TODO 目前测试发现以下两行代码都会出问题所以暂不建议使用仿真测试环境
@ -48,14 +56,33 @@ public class SignUtils {
StringBuilder toSign = new StringBuilder();
for (String key : sortedMap.keySet()) {
String value = params.get(key);
if (StringUtils.isNotEmpty(value) && !"sign".equals(key) && !"key".equals(key)) {
if (StringUtils.isNotEmpty(value)
&& !StringUtils.equalsAny(key, "sign", "key", "sign_type")) {
toSign.append(key).append("=").append(value).append("&");
}
}
toSign.append("key=").append(signKey);
if (SignType.HMAC_SHA256.equals(signType)) {
return createHMACSha256Sign(toSign.toString(), signKey);
} else {
return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
}
}
private static String createHMACSha256Sign(String message, String key) {
try {
Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256");
hmacSHA256.init(secretKeySpec);
byte[] bytes = hmacSHA256.doFinal(message.getBytes());
return Hex.encodeHexString(bytes).toUpperCase();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
return null;
}
/**
* 校验签名是否正确
@ -78,7 +105,7 @@ public class SignUtils {
* @see #checkSign(Map, String)
*/
public static boolean checkSign(Map<String, String> params, String signKey) {
String sign = createSign(params, signKey);
String sign = createSign(params, signKey, null);
return sign.equals(params.get("sign"));
}
}

View File

@ -2,7 +2,6 @@ package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.utils.qrcode.QrcodeUtils;
import com.github.binarywang.wxpay.bean.coupon.*;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.constant.WxPayConstants;
@ -16,10 +15,13 @@ import com.github.binarywang.wxpay.testbase.XmlWxPayConfig;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.*;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import static org.testng.Assert.*;
@ -90,7 +92,7 @@ public class WxPayServiceAbstractImplTest {
@Test
public void testDownloadBill() throws Exception {
WxPayBillResult wxPayBillResult = this.payService.downloadBill("20170101", BillType.ALL, "GZIP", "1111111");
WxPayBillResult wxPayBillResult = this.payService.downloadBill("20170831", BillType.ALL, null, "1111111");
//前一天没有账单记录返回null
assertNotNull(wxPayBillResult);
//必填字段为空时抛出异常
@ -313,10 +315,24 @@ public class WxPayServiceAbstractImplTest {
@Test
public void testQueryCouponInfo() throws Exception {
WxPayCouponInfoQueryResult result = this.payService.queryCouponInfo(WxPayCouponInfoQueryRequest.newBuilder()
.openid("onqOjjrXT-776SpHnfexGm1_P7iE")
.openid("ojOQA0y9o-Eb6Aep7uVTdbkJqrP4")
.couponId("11")
.stockId("1121")
.build());
this.logger.info(result.toString());
}
/**
* 目前调用接口总报系统繁忙清稍后再试怀疑根本没法使用
*/
@Test
public void testQueryComment() throws Exception {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -2);
Date beginDate = calendar.getTime();
calendar.add(Calendar.MONTH, -1);
Date endDate = calendar.getTime();
this.payService.queryComment(beginDate, endDate, 0, null);
}
}

View File

@ -0,0 +1,40 @@
package com.github.binarywang.wxpay.util;
import com.google.common.base.Splitter;
import org.testng.annotations.Test;
import static com.github.binarywang.wxpay.constant.WxPayConstants.SignType.HMAC_SHA256;
import static org.testng.Assert.assertEquals;
/**
* <pre>
* 测试中使用的测试数据参考的是官方文档地址
* https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
* Created by BinaryWang on 2017/9/2.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class SignUtilsTest {
@Test
public void testCreateSign() throws Exception {
String signKey = "192006250b4c09247ec02edce69f6a2d";
String message = "appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
assertEquals(SignUtils.createSign((Splitter.on("&").withKeyValueSeparator("=").split(message)), signKey, null),
"9A0A8659F005D6984697E2CA0A9CF3B7");
}
@Test
public void testCreateSign_HMACSHA256() throws Exception {
String signKey = "192006250b4c09247ec02edce69f6a2d";
final String message = "appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
String sign = SignUtils.createSign(Splitter.on("&").withKeyValueSeparator("=").split(message),
signKey, HMAC_SHA256);
assertEquals(sign, "6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6");
}
@Test
public void testCheckSign() throws Exception {
}
}