新增V2版本"退款申请","退款查询","对账单下载"三个接口以及语义理解接口鸡肋实现

This commit is contained in:
jy.hu 2014-11-08 21:12:03 +08:00
parent 1371d1d5c7
commit ff436b6f03
41 changed files with 1037 additions and 248 deletions

View File

@ -32,7 +32,7 @@ weixin4j
* 2014-10-31 * 2014-10-31
+ **weixin4j-mp**: `weixin.properties`切分为API调用地址/公众号信息两部分 + **weixin4j-mp**: `weixin.properties`切分为API调用地址公众号信息两部分
+ **weixin4j-base**: `TokenApi`重命名为`TokenHolder` + **weixin4j-base**: `TokenApi`重命名为`TokenHolder`
@ -54,6 +54,12 @@ weixin4j
+ **weixin-mp**: 新增`退款接口` + **weixin-mp**: 新增`退款接口`
* 2014-11-08
+ **weixin-mp**: 新增V2版本`退款申请``退款查询``对账单下载`三个接口
+ **weixin-mp**: 新增一个简单的`语义理解`接口
接下来 接下来
------ ------
* 公众号智能接口 * 公众号智能接口

View File

@ -103,5 +103,10 @@
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>
<version>${commons.codec.version}</version> <version>${commons.codec.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -22,6 +22,7 @@ public class WeixinException extends Exception {
} }
public WeixinException(String errorMsg) { public WeixinException(String errorMsg) {
this.errorCode = "";
this.errorMsg = errorMsg; this.errorMsg = errorMsg;
} }

View File

@ -3,10 +3,11 @@ package com.foxinmy.weixin4j.http;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts; import org.apache.http.Consts;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
@ -32,9 +33,13 @@ import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONException;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.util.MapUtil;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
/** /**
* 调用微信相关接口的HttpRequest,对于其他请求可能并不试用 * 调用微信相关接口的HttpRequest,对于其他请求可能并不试用
@ -46,7 +51,7 @@ import com.foxinmy.weixin4j.exception.WeixinException;
* @see * @see
*/ */
public class HttpRequest { public class HttpRequest {
private final String SUCCESS = "success";
protected AbstractHttpClient client; protected AbstractHttpClient client;
public HttpRequest() { public HttpRequest() {
@ -84,6 +89,14 @@ public class HttpRequest {
return get(url, (Parameter[]) null); return get(url, (Parameter[]) null);
} }
public Response get(String url, Map<String, String> para)
throws WeixinException {
return get(
String.format("%s?%s", url,
MapUtil.toJoinString(para, false, false, null)),
(Parameter[]) null);
}
public Response get(String url, Parameter... parameters) public Response get(String url, Parameter... parameters)
throws WeixinException { throws WeixinException {
StringBuilder sb = new StringBuilder(url); StringBuilder sb = new StringBuilder(url);
@ -106,11 +119,13 @@ public class HttpRequest {
public Response post(String url, Parameter... parameters) public Response post(String url, Parameter... parameters)
throws WeixinException { throws WeixinException {
HttpPost method = new HttpPost(url); HttpPost method = new HttpPost(url);
List<NameValuePair> params = new ArrayList<NameValuePair>(); if (parameters != null && parameters.length > 0) {
for (Parameter parameter : parameters) { List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(parameter.toPostPara()); for (Parameter parameter : parameters) {
params.add(parameter.toPostPara());
}
method.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));
} }
method.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));
return doRequest(method); return doRequest(method);
} }
@ -167,42 +182,39 @@ public class HttpRequest {
String.format("the page was redirected to %s", String.format("the page was redirected to %s",
httpResponse.getFirstHeader("location"))); httpResponse.getFirstHeader("location")));
} }
byte[] data = EntityUtils.toByteArray(httpEntity); byte[] data = EntityUtils.toByteArray(httpEntity);
response = new Response(); response = new Response();
response.setBody(data); response.setBody(data);
response.setStatusCode(status); response.setStatusCode(status);
response.setStatusText(statusLine.getReasonPhrase()); response.setStatusText(statusLine.getReasonPhrase());
response.setStream(new ByteArrayInputStream(data)); response.setStream(new ByteArrayInputStream(data));
response.setText(new String(data, Consts.UTF_8));
EntityUtils.consume(httpEntity); EntityUtils.consume(httpEntity);
Header contentType = httpResponse Header contentType = httpResponse
.getFirstHeader(HttpHeaders.CONTENT_TYPE); .getFirstHeader(HttpHeaders.CONTENT_TYPE);
// error with html
if (contentType.getValue().contains( if (contentType.getValue().contains(
ContentType.APPLICATION_JSON.getMimeType()) ContentType.TEXT_HTML.getMimeType())) {
|| contentType.getValue().contains( response.setText(new String(data, "gbk"));
ContentType.TEXT_PLAIN.getMimeType())) { Document doc = Jsoup.parse(response.getAsString());
response.setText(new String(data, StandardCharsets.UTF_8)); throw new WeixinException(doc.body().text());
} else if (contentType.getValue().contains(
ContentType.APPLICATION_JSON.getMimeType())) {
checkJson(response);
} else if (contentType.getValue().contains(
ContentType.TEXT_XML.getMimeType())) {
checkXml(response);
} else if (contentType.getValue().contains(
ContentType.TEXT_PLAIN.getMimeType())) {
try { try {
JsonResult jsonResult = response.getAsJsonResult(); checkJson(response);
response.setJsonResult(true);
if (jsonResult.getCode() != 0) {
throw new WeixinException(Integer.toString(jsonResult
.getCode()), jsonResult.getDesc());
}
return response; return response;
} catch (JSONException e) { } catch (JSONException e) {
; ;
} }
XmlResult xmlResult = response.getAsXmlResult(); checkXml(response);
response.setXmlResult(true);
if (!xmlResult.getReturnCode().equalsIgnoreCase(SUCCESS)) {
throw new WeixinException(xmlResult.getReturnCode(),
xmlResult.getReturnMsg());
}
if (!xmlResult.getResultCode().equalsIgnoreCase(SUCCESS)) {
throw new WeixinException(xmlResult.getErrCode(),
xmlResult.getErrCodeDes());
}
} }
} catch (IOException e) { } catch (IOException e) {
throw new WeixinException("-1", e.getMessage()); throw new WeixinException("-1", e.getMessage());
@ -211,4 +223,48 @@ public class HttpRequest {
} }
return response; return response;
} }
private void checkJson(Response response) throws WeixinException {
response.setJsonResult(true);
JsonResult jsonResult = response.getAsJsonResult();
if (jsonResult.getCode() != 0) {
if (StringUtils.isBlank(jsonResult.getDesc())) {
jsonResult = response.getTextError(jsonResult.getCode());
}
throw new WeixinException(Integer.toString(jsonResult.getCode()),
jsonResult.getDesc());
}
}
private void checkXml(Response response) throws WeixinException {
response.setXmlResult(true);
XmlResult xmlResult = null;
try {
xmlResult = response.getAsXmlResult();
} catch (CannotResolveClassException ex) {
// <?xml><root><data..../data></root>
String newXml = response.getAsString()
.replaceFirst("<root>", "<xml>")
.replaceFirst("<retcode>", "<return_code>")
.replaceFirst("</retcode>", "</return_code>")
.replaceFirst("<retmsg>", "<return_msg>")
.replaceFirst("</retmsg>", "</return_msg>")
.replaceFirst("</root>", "</xml>");
response.setText(newXml);
xmlResult = response.getAsXmlResult();
}
if (xmlResult.getReturnCode().equals("0")) {
return;
}
if (!xmlResult.getReturnCode().equalsIgnoreCase(
com.foxinmy.weixin4j.model.Consts.SUCCESS)) {
throw new WeixinException(xmlResult.getReturnCode(),
xmlResult.getReturnMsg());
}
if (!xmlResult.getResultCode().equalsIgnoreCase(
com.foxinmy.weixin4j.model.Consts.SUCCESS)) {
throw new WeixinException(xmlResult.getErrCode(),
xmlResult.getErrCodeDes());
}
}
} }

View File

@ -2,15 +2,13 @@ package com.foxinmy.weixin4j.http;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import org.apache.http.Consts;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
public class Parameter { public class Parameter {
private final static String CHARSET = StandardCharsets.UTF_8.name();
private String name; private String name;
private String value; private String value;
@ -41,7 +39,8 @@ public class Parameter {
public String toGetPara() { public String toGetPara() {
try { try {
return String.format("&%s=%s", name, URLEncoder.encode(value, CHARSET)); return String.format("&%s=%s", name,
URLEncoder.encode(value, Consts.UTF_8.name()));
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
return String.format("&%s=%s", name, value); return String.format("&%s=%s", name, value);
} }
@ -49,7 +48,8 @@ public class Parameter {
public NameValuePair toPostPara() { public NameValuePair toPostPara() {
try { try {
return new BasicNameValuePair(name, URLEncoder.encode(value, CHARSET)); return new BasicNameValuePair(name, URLEncoder.encode(value,
Consts.UTF_8.name()));
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
return new BasicNameValuePair(name, value); return new BasicNameValuePair(name, value);
} }

View File

@ -2,6 +2,7 @@ package com.foxinmy.weixin4j.http;
import java.io.InputStream; import java.io.InputStream;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document; import org.dom4j.Document;
import org.dom4j.DocumentException; import org.dom4j.DocumentException;
import org.dom4j.Node; import org.dom4j.Node;
@ -73,18 +74,41 @@ public class Response {
* @return * @return
* @throws DocumentException * @throws DocumentException
*/ */
public JsonResult getTextError() throws DocumentException { public JsonResult getTextError(int code) {
JsonResult result = getAsJsonResult(); JsonResult result = new JsonResult();
if (result.getCode() != 0) { result.setCode(code);
SAXReader reader = new SAXReader(); SAXReader reader = new SAXReader();
Document doc = reader.read(Response.class Document doc = null;
.getResourceAsStream("error.xml")); try {
Node node = doc.getRootElement().selectSingleNode( doc = reader.read(Response.class.getResourceAsStream("error.xml"));
String.format("error/code[text()='%d']", result.getCode())); } catch (DocumentException e) {
if (node != null) { e.printStackTrace();
result.setText(node.getParent().selectSingleNode("text") }
.getStringValue()); Node node = doc.getRootElement().selectSingleNode(
String.format("error/code[text()=%d]", code));
if (node != null) {
node = node.getParent();
String desc = null;
Node _node = node.selectSingleNode("desc");
if (_node != null) {
desc = _node.getStringValue();
} }
String text = null;
_node = node.selectSingleNode("text");
if (_node != null) {
text = _node.getStringValue();
}
if (StringUtils.isBlank(desc) && StringUtils.isNotBlank(text)) {
desc = text;
}
if (StringUtils.isBlank(text) && StringUtils.isNotBlank(desc)) {
text = desc;
}
result.setDesc(desc);
result.setText(text);
} else {
result.setDesc("unknown error");
result.setText("未知错误");
} }
return result; return result;
} }

View File

@ -6,7 +6,10 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.conn.ssl.SSLSocketFactory;
/** /**
@ -27,7 +30,8 @@ public class SSLHttpRequest extends HttpRequest {
public SSLHttpRequest(String password, InputStream inputStream) { public SSLHttpRequest(String password, InputStream inputStream) {
super(); super();
try { try {
KeyStore trustStore = KeyStore.getInstance("PKCS12"); KeyStore trustStore = KeyStore
.getInstance(com.foxinmy.weixin4j.model.Consts.PKCS12);
trustStore.load(inputStream, password.toCharArray()); trustStore.load(inputStream, password.toCharArray());
SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore, SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore,
password); password);
@ -45,4 +49,11 @@ public class SSLHttpRequest extends HttpRequest {
} }
} }
} }
public SSLHttpRequest(SSLContext sslContext) {
super();
SchemeSocketFactory socketFactory = new SSLSocketFactory(sslContext);
client.getConnectionManager().getSchemeRegistry()
.register(new Scheme("https", 443, socketFactory));
}
} }

View File

@ -2,6 +2,7 @@ package com.foxinmy.weixin4j.http;
import java.io.Serializable; import java.io.Serializable;
import com.foxinmy.weixin4j.model.Consts;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
@ -16,8 +17,6 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
public class XmlResult implements Serializable { public class XmlResult implements Serializable {
private static final long serialVersionUID = -6185313616955051150L; private static final long serialVersionUID = -6185313616955051150L;
public static final String SUCCESS = "SUCCESS";
public static final String FAIL = "FAIL";
@XStreamAlias("return_code") @XStreamAlias("return_code")
private String returnCode;// 此字段是通信标识,非交易 标识,交易是否成功需要查 result_code 来判断 非空 private String returnCode;// 此字段是通信标识,非交易 标识,交易是否成功需要查 result_code 来判断 非空
@ -71,15 +70,15 @@ public class XmlResult implements Serializable {
} }
public XmlResult() { public XmlResult() {
this(SUCCESS.toLowerCase(), ""); this(Consts.SUCCESS.toLowerCase(), "");
} }
public XmlResult(String returnCode, String returnMsg) { public XmlResult(String returnCode, String returnMsg) {
this.returnCode = returnCode; this.returnCode = returnCode;
this.returnMsg = returnMsg; this.returnMsg = returnMsg;
if (returnCode.equalsIgnoreCase(SUCCESS)) { if (returnCode.equalsIgnoreCase(Consts.SUCCESS)) {
this.resultCode = SUCCESS.toLowerCase(); this.resultCode = Consts.SUCCESS.toLowerCase();
this.errCode = SUCCESS.toLowerCase(); this.errCode = Consts.SUCCESS.toLowerCase();
this.errCodeDes = ""; this.errCodeDes = "";
} }
} }

View File

@ -362,6 +362,83 @@
<desc>api unauthorized</desc> <desc>api unauthorized</desc>
<text>接口未授权</text> <text>接口未授权</text>
</error> </error>
<!-- 语义理解API错误 -->
<error>
<code>7000000</code>
<text>请求正常,无语义结果</text>
</error>
<error>
<code>7000001</code>
<text>缺失请求参数</text>
</error>
<error>
<code>7000002</code>
<text>signature 参数无效</text>
</error>
<error>
<code>7000003</code>
<text>地理位置相关配置 1 无效</text>
</error>
<error>
<code>7000004</code>
<text>地理位置相关配置 2 无效</text>
</error>
<error>
<code>7000005</code>
<text>请求地理位置信息失败</text>
</error>
<error>
<code>7000006</code>
<text>地理位置结果解析失败</text>
</error>
<error>
<code>7000007</code>
<text>内部初始化失败</text>
</error>
<error>
<code>7000008</code>
<text>非法 appid(获取密钥失败)</text>
</error>
<error>
<code>7000009</code>
<text>请求语义服务失败</text>
</error>
<error>
<code>7000010</code>
<text>非法 post 请求</text>
</error>
<error>
<code>7000011</code>
<text>post 请求 json 字段无效</text>
</error>
<error>
<code>7000030</code>
<text>查询 query 太短</text>
</error>
<error>
<code>7000031</code>
<text>查询 query 太长</text>
</error>
<error>
<code>7000032</code>
<text>城市、经纬度信息缺失</text>
</error>
<error>
<code>7000033</code>
<text>query 请求语义处理失败</text>
</error>
<error>
<code>7000034</code>
<text>获取天气信息失败</text>
</error>
<error>
<code>7000035</code>
<text>获取股票信息失败</text>
</error>
<error>
<code>7000036</code>
<text>utf8 编码转换失败</text>
</error>
<!-- 微信支付API错误 --> <!-- 微信支付API错误 -->
<error> <error>
<code>SYSTEMERROR</code> <code>SYSTEMERROR</code>

View File

@ -0,0 +1,11 @@
package com.foxinmy.weixin4j.model;
public final class Consts {
public static final String SUCCESS = "SUCCESS";
public static final String FAIL = "FAIL";
public static final String SunX509 = "SunX509";
public static final String JKS = "JKS";
public static final String PKCS12 = "PKCS12";
public static final String TLS = "TLS";
public static final String X509 = "X.509";
}

View File

@ -2,6 +2,8 @@ package com.foxinmy.weixin4j.model;
import java.io.Serializable; import java.io.Serializable;
import org.apache.commons.lang3.StringUtils;
/** /**
* 微信账户信息 * 微信账户信息
* *
@ -31,6 +33,8 @@ public class WeixinAccount implements Serializable {
private String mchId; private String mchId;
// 微信支付分配的设备号 // 微信支付分配的设备号
private String deviceInfo; private String deviceInfo;
// 微信支付版本号(如果无则按照mchId来做判断)
private int version;
// 是否已经认证 // 是否已经认证
private boolean isAlive; private boolean isAlive;
@ -135,6 +139,17 @@ public class WeixinAccount implements Serializable {
this.isSubscribe = isSubscribe; this.isSubscribe = isSubscribe;
} }
public int getVersion() {
if (version == 0) {
return StringUtils.isNotBlank(mchId) ? 3 : 2;
}
return version;
}
public void setVersion(int version) {
this.version = version;
}
public WeixinAccount() { public WeixinAccount() {
} }

View File

@ -19,7 +19,6 @@ public class ScanEventMessage extends EventMessage {
} }
private static final long serialVersionUID = 8078674062833071562L; private static final long serialVersionUID = 8078674062833071562L;
private static final String PARA_PREFIX = "qrscene_";
@XStreamAlias("EventKey") @XStreamAlias("EventKey")
private String eventKey; // 事件KEY值是一个32位无符号整数即创建二维码时的二维码scene_id private String eventKey; // 事件KEY值是一个32位无符号整数即创建二维码时的二维码scene_id
@ -35,7 +34,7 @@ public class ScanEventMessage extends EventMessage {
} }
public String getParameter() { public String getParameter() {
return eventKey.replace(PARA_PREFIX, ""); return eventKey.replace("qrscene_", "");
} }
@Override @Override

View File

@ -16,12 +16,15 @@ import java.util.Date;
*/ */
public class DateUtil { public class DateUtil {
private static final String yyyyMMdd = "yyyyMMdd"; private static final String yyyyMMdd = "yyyyMMdd";
private static final String yyyy_MM_dd = "yyyy-MM-dd";
private static final String yyyyMMddHHmmss = "yyyyMMddHHmmss"; private static final String yyyyMMddHHmmss = "yyyyMMddHHmmss";
public static String fortmat2yyyyMMdd(Date date) { public static String fortmat2yyyyMMdd(Date date) {
return new SimpleDateFormat(yyyyMMdd).format(date); return new SimpleDateFormat(yyyyMMdd).format(date);
} }
public static String fortmat2yyyy_MM_dd(Date date) {
return new SimpleDateFormat(yyyy_MM_dd).format(date);
}
public static String fortmat2yyyyMMddHHmmss(Date date) { public static String fortmat2yyyyMMddHHmmss(Date date) {
return new SimpleDateFormat(yyyyMMddHHmmss).format(date); return new SimpleDateFormat(yyyyMMddHHmmss).format(date);
} }

View File

@ -2,19 +2,19 @@ package com.foxinmy.weixin4j.util;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import org.apache.http.Consts;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference; import com.alibaba.fastjson.TypeReference;
/** /**
* 签名工具类 * 签名工具类
*
* @className MapUtil * @className MapUtil
* @author jy * @author jy
* @date 2014年10月31日 * @date 2014年10月31日
@ -22,10 +22,8 @@ import com.alibaba.fastjson.TypeReference;
* @see * @see
*/ */
public class MapUtil { public class MapUtil {
private final static Charset charset = StandardCharsets.UTF_8;
public static String toJoinString(Object object, boolean encoder, public static String toJoinString(Object object, boolean encoder,
boolean lowerCase, JSONObject extra) { boolean lowerCase, Map<String, String> extra) {
String text = JSON.toJSONString(object); String text = JSON.toJSONString(object);
Map<String, String> map = new TreeMap<String, String>( Map<String, String> map = new TreeMap<String, String>(
new Comparator<String>() { new Comparator<String>() {
@ -34,14 +32,18 @@ public class MapUtil {
return o1.compareTo(o2); return o1.compareTo(o2);
} }
}); });
map.putAll(JSON.parseObject(text, map.putAll(JSON.parseObject(text,
new TypeReference<Map<String, String>>() { new TypeReference<Map<String, String>>() {
})); }));
if (extra != null && !extra.isEmpty()) { if (extra != null && !extra.isEmpty()) {
for (String key : extra.keySet()) { map.putAll(extra);
map.put(key, extra.getString(key));
}
} }
return toJoinString(map, encoder, lowerCase);
}
public static String toJoinString(Map<String, String> map, boolean encoder,
boolean lowerCase) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
Set<Map.Entry<String, String>> set = map.entrySet(); Set<Map.Entry<String, String>> set = map.entrySet();
try { try {
@ -50,14 +52,14 @@ public class MapUtil {
sb.append(entry.getKey().toLowerCase()) sb.append(entry.getKey().toLowerCase())
.append("=") .append("=")
.append(URLEncoder.encode(entry.getValue(), .append(URLEncoder.encode(entry.getValue(),
charset.name())).append("&"); Consts.UTF_8.name())).append("&");
} }
} else if (encoder) { } else if (encoder) {
for (Map.Entry<String, String> entry : set) { for (Map.Entry<String, String> entry : set) {
sb.append(entry.getKey()) sb.append(entry.getKey())
.append("=") .append("=")
.append(URLEncoder.encode(entry.getValue(), .append(URLEncoder.encode(entry.getValue(),
charset.name())).append("&"); Consts.UTF_8.name())).append("&");
} }
} else if (lowerCase) { } else if (lowerCase) {
for (Map.Entry<String, String> entry : set) { for (Map.Entry<String, String> entry : set) {

View File

@ -52,4 +52,10 @@ weixin4j-mp
* 2014-11-06 * 2014-11-06
+ **weixin-mp**: 新增`退款接口` + **weixin-mp-api**: 新增`退款接口`
* 2014-11-08
+ **weixin-mp-api**: 新增V2版本`退款申请``退款查询``对账单下载`三个接口
+ **weixin-mp-api**: 新增一个简单的`语义理解`接口

View File

@ -92,4 +92,10 @@ weixin.properties说明
* 2014-11-06 * 2014-11-06
+ 新增`退款申请`接口 + 新增`退款申请`接口
* 2014-11-08
+ 新增V2版本`退款申请``退款查询``对账单下载`三个接口
+ 新增一个简单的`语义理解`接口

View File

@ -14,21 +14,8 @@
<description>微信公众号API</description> <description>微信公众号API</description>
<build> <build>
<finalName>weixin4j-mp-api</finalName> <finalName>weixin4j-mp-api</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>*.*</exclude>
</excludes>
</resource>
</resources>
</build> </build>
<dependencies> <dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<dependency> <dependency>
<groupId>jaxen</groupId> <groupId>jaxen</groupId>
<artifactId>jaxen</artifactId> <artifactId>jaxen</artifactId>

View File

@ -32,9 +32,9 @@ import com.foxinmy.weixin4j.mp.model.User;
import com.foxinmy.weixin4j.mp.msg.model.Article; import com.foxinmy.weixin4j.mp.msg.model.Article;
import com.foxinmy.weixin4j.mp.msg.model.BaseMsg; import com.foxinmy.weixin4j.mp.msg.model.BaseMsg;
import com.foxinmy.weixin4j.mp.msg.notify.BaseNotify; import com.foxinmy.weixin4j.mp.msg.notify.BaseNotify;
import com.foxinmy.weixin4j.mp.payment.Refund;
import com.foxinmy.weixin4j.mp.payment.RefundResult;
import com.foxinmy.weixin4j.mp.payment.v2.Order; import com.foxinmy.weixin4j.mp.payment.v2.Order;
import com.foxinmy.weixin4j.mp.payment.v3.Refund;
import com.foxinmy.weixin4j.mp.payment.v3.RefundResult;
import com.foxinmy.weixin4j.mp.response.TemplateMessage; import com.foxinmy.weixin4j.mp.response.TemplateMessage;
import com.foxinmy.weixin4j.mp.type.BillType; import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.IdQuery; import com.foxinmy.weixin4j.mp.type.IdQuery;
@ -740,15 +740,16 @@ public class WeixinProxy {
* 用户ID * 用户ID
* @param transid * @param transid
* 交易单号 * 交易单号
* @param orderNo * @param outTradeNo
* 订单号 * 订单号
* @param status * @param status
* 成功|失败 * 成功|失败
* @param statusMsg * @param statusMsg
* status为失败时携带的信息 * status为失败时携带的信息
* @return 调用结果 * @return 发货处理结果
* @throws WeixinException * @since V2 & V3
* @see com.foxinmy.weixin4j.mp.api.PayApi * @see com.foxinmy.weixin4j.mp.api.PayApi
* @throws WeixinException
*/ */
public JsonResult deliverNotify(String openId, String transid, public JsonResult deliverNotify(String openId, String transid,
String outTradeNo, boolean status, String statusMsg) String outTradeNo, boolean status, String statusMsg)
@ -780,6 +781,7 @@ public class WeixinProxy {
* 维权单号 * 维权单号
* @return 调用结果 * @return 调用结果
* @see com.foxinmy.weixin4j.mp.api.PayApi * @see com.foxinmy.weixin4j.mp.api.PayApi
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public JsonResult updateFeedback(String openId, String feedbackId) public JsonResult updateFeedback(String openId, String feedbackId)
@ -814,6 +816,7 @@ public class WeixinProxy {
* 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单 * 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单 * REFUND,返回当日退款订单
* @return excel表格 * @return excel表格
* @since V2 & V3
* @see com.foxinmy.weixin4j.mp.api.PayApi * @see com.foxinmy.weixin4j.mp.api.PayApi
* @throws WeixinException * @throws WeixinException
* @throws IOException * @throws IOException
@ -824,7 +827,7 @@ public class WeixinProxy {
} }
/** /**
* 申请退款(请求需要双向证书)<br/> * 申请退款(V3请求需要双向证书)<br/>
* <p style="color:red"> * <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; <br/> * 交易时间超过 1 年的订单无法提交退款; <br/>
* 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失 败后重新提交,要采用原来的 * 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失 败后重新提交,要采用原来的
@ -832,7 +835,7 @@ public class WeixinProxy {
* </p> * </p>
* *
* @param ca * @param ca
* 证书文件 * 证书文件<font color="red">V2版本时无需传入</font>
* @param idQuery * @param idQuery
* ) 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级: * ) 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no * transaction_id> out_trade_no
@ -844,28 +847,36 @@ public class WeixinProxy {
* 退款总金额,单位为元,可以做部分退款 * 退款总金额,单位为元,可以做部分退款
* @param opUserId * @param opUserId
* 操作员帐号, 默认为商户号 * 操作员帐号, 默认为商户号
* @param opUserPasswd
* <font color="red">V3版本留空,V2版本需传入值</font>
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.api.PayApi * @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.payment.RefundResult
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
* @throws IOException * @throws IOException
* @return 退款结果
*/ */
public RefundResult refund(InputStream ca, IdQuery idQuery, public RefundResult refund(InputStream ca, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee, String outRefundNo, double totalFee, double refundFee,
String opUserId) throws WeixinException, IOException { String opUserId, String opUserPasswd) throws WeixinException,
return payApi.refund(idQuery, outRefundNo, totalFee, refundFee, IOException {
opUserId); return payApi.refund(ca, idQuery, outRefundNo, totalFee, refundFee,
opUserId, opUserPasswd);
} }
/** /**
* 使用properties中配置的ca文件 * 不同的退款接口选择<br/>
* V3支付则采用properties中配置的ca文件<br/>
* V2支付则需要传入opUserPasswd参数<br/>
* *
* @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#refund(InputStream, IdQuery, String, double, double, String)} * @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#refund(InputStream, IdQuery, String, double, double, String,String)}
*/ */
public RefundResult refund(IdQuery idQuery, String outRefundNo, public RefundResult refund(IdQuery idQuery, String outRefundNo,
double totalFee, double refundFee, String opUserId) double totalFee, double refundFee, String opUserId,
throws WeixinException, IOException { String opUserPasswd) throws WeixinException, IOException {
return payApi.refund(idQuery, outRefundNo, totalFee, refundFee, return payApi.refund(idQuery, outRefundNo, totalFee, refundFee,
opUserId); opUserId, opUserPasswd);
} }
/** /**
@ -876,8 +887,10 @@ public class WeixinProxy {
* 单号 refund_idout_refund_no out_trade_no transaction_id * 单号 refund_idout_refund_no out_trade_no transaction_id
* 四个参数必填一个,优先级为: * 四个参数必填一个,优先级为:
* refund_id>out_refund_no>transaction_id>out_trade_no * refund_id>out_refund_no>transaction_id>out_trade_no
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @return 退款记录 * @return 退款记录
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.payment.Refund
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public Refund refundQuery(IdQuery idQuery) throws WeixinException { public Refund refundQuery(IdQuery idQuery) throws WeixinException {
@ -893,6 +906,7 @@ public class WeixinProxy {
* 商户系统内部的订单号 * 商户系统内部的订单号
* @return 执行结果 * @return 执行结果
* @see com.foxinmy.weixin4j.mp.api.PayApi * @see com.foxinmy.weixin4j.mp.api.PayApi
* @since V3
* @throws WeixinException * @throws WeixinException
*/ */
public XmlResult closeOrder(String outTradeNo) throws WeixinException { public XmlResult closeOrder(String outTradeNo) throws WeixinException {

View File

@ -1,7 +1,5 @@
package com.foxinmy.weixin4j.mp.api; package com.foxinmy.weixin4j.mp.api;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map; import java.util.Map;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -24,7 +22,6 @@ import com.thoughtworks.xstream.mapper.DefaultMapper;
public class BaseApi { public class BaseApi {
protected final HttpRequest request = new HttpRequest(); protected final HttpRequest request = new HttpRequest();
protected final static XStream mapXstream = XStream.get(); protected final static XStream mapXstream = XStream.get();
protected final Charset utf8 = StandardCharsets.UTF_8;
private final static ResourceBundle weixinBundle; private final static ResourceBundle weixinBundle;
static { static {
weixinBundle = ResourceBundle weixinBundle = ResourceBundle
@ -40,7 +37,7 @@ public class BaseApi {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected Map<String, String> xml2map(String xml) { protected Map<String, String> xml2map(String xml) {
return mapXstream.fromXML(xml,Map.class); return mapXstream.fromXML(xml, Map.class);
} }
protected String getRequestUri(String key) { protected String getRequestUri(String key) {

View File

@ -1,9 +1,13 @@
package com.foxinmy.weixin4j.mp.api; package com.foxinmy.weixin4j.mp.api;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response; import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token; import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.mp.model.SemQuery;
import com.foxinmy.weixin4j.mp.model.SemResult;
import com.foxinmy.weixin4j.token.TokenHolder; import com.foxinmy.weixin4j.token.TokenHolder;
/** /**
@ -44,4 +48,28 @@ public class HelperApi extends BaseApi {
return response.getAsJson().getString("short_url"); return response.getAsJson().getString("short_url");
} }
/**
* 语义理解
*
* @param semQuery
* 语义理解协议
* @return 语义理解结果
* @see com.foxinmy.weixin4j.mp.model.SemQuery
* @see com.foxinmy.weixin4j.mp.model.SemResult
* @see <a
* href="http://mp.weixin.qq.com/wiki/index.php?title=%E8%AF%AD%E4%B9%89%E7%90%86%E8%A7%A3">语义理解</a>
* @throws WeixinException
*/
public SemResult semantic(SemQuery semQuery) throws WeixinException {
WeixinAccount weixinAccount = tokenHolder.getAccount();
String semantic_uri = getRequestUri("semantic_uri");
Token token = tokenHolder.getToken();
semQuery.appid(weixinAccount.getAppId());
Response response = request.post(
String.format(semantic_uri, token.getAccessToken()),
semQuery.toJson());
return response.getAsObject(new TypeReference<SemResult>() {
});
}
} }

View File

@ -6,19 +6,29 @@ import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader; import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
@ -33,10 +43,10 @@ import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.Token; import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.WeixinAccount; import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil; import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.Refund;
import com.foxinmy.weixin4j.mp.payment.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.RefundResult;
import com.foxinmy.weixin4j.mp.payment.v2.Order; import com.foxinmy.weixin4j.mp.payment.v2.Order;
import com.foxinmy.weixin4j.mp.payment.v3.Refund;
import com.foxinmy.weixin4j.mp.payment.v3.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.v3.RefundResult;
import com.foxinmy.weixin4j.mp.type.BillType; import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.IdQuery; import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.IdType; import com.foxinmy.weixin4j.mp.type.IdType;
@ -77,6 +87,7 @@ public class PayApi extends BaseApi {
* @param statusMsg * @param statusMsg
* status为失败时携带的信息 * status为失败时携带的信息
* @return 发货处理结果 * @return 发货处理结果
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public JsonResult deliverNotify(String openId, String transid, public JsonResult deliverNotify(String openId, String transid,
@ -96,8 +107,7 @@ public class PayApi extends BaseApi {
param.put("deliver_timestamp", System.currentTimeMillis() / 1000 + ""); param.put("deliver_timestamp", System.currentTimeMillis() / 1000 + "");
param.put("deliver_status", status ? "1" : "0"); param.put("deliver_status", status ? "1" : "0");
param.put("deliver_msg", statusMsg); param.put("deliver_msg", statusMsg);
param.put("app_signature", DigestUtils.sha1Hex(MapUtil.toJoinString( param.put("app_signature", PayUtil.paysignSha(param, null));
param, false, true, null)));
param.put("sign_method", "sha1"); param.put("sign_method", "sha1");
Response response = request.post( Response response = request.post(
@ -135,10 +145,9 @@ public class PayApi extends BaseApi {
obj.put("appkey", weixinAccount.getPaySignKey()); obj.put("appkey", weixinAccount.getPaySignKey());
obj.put("package", sb.toString()); obj.put("package", sb.toString());
obj.put("timestamp", timestamp); obj.put("timestamp", timestamp);
String signature = DigestUtils.sha1Hex(MapUtil.toJoinString(obj, false, String signature = PayUtil.paysignSha(obj, null);
true, null));
obj = new JSONObject(); obj.clear();
obj.put("appid", weixinAccount.getAppId()); obj.put("appid", weixinAccount.getAppId());
obj.put("package", sb.toString()); obj.put("package", sb.toString());
obj.put("timestamp", timestamp); obj.put("timestamp", timestamp);
@ -170,6 +179,7 @@ public class PayApi extends BaseApi {
* @param feedbackId * @param feedbackId
* 维权单号 * 维权单号
* @return 维权处理结果 * @return 维权处理结果
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public JsonResult updateFeedback(String openId, String feedbackId) public JsonResult updateFeedback(String openId, String feedbackId)
@ -226,46 +236,146 @@ public class PayApi extends BaseApi {
* 退款总金额,单位为元,可以做部分退款 * 退款总金额,单位为元,可以做部分退款
* @param opUserId * @param opUserId
* 操作员帐号, 默认为商户号 * 操作员帐号, 默认为商户号
* @param opUserPasswd
* <font color="red">V3版本留空,V2版本需传入值</font>
* *
* @return 退款申请结果 * @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult * @see com.foxinmy.weixin4j.mp.payment.RefundResult
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
* @throws IOException * @throws IOException
*/ */
public RefundResult refund(InputStream ca, IdQuery idQuery, public RefundResult refund(InputStream ca, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee, String outRefundNo, double totalFee, double refundFee,
String opUserId) throws WeixinException, IOException { String opUserId, String opUserPasswd) throws WeixinException,
IOException {
WeixinAccount weixinAccount = tokenHolder.getAccount(); WeixinAccount weixinAccount = tokenHolder.getAccount();
Map<String, String> map = baseMap2V3(idQuery);
map.put("out_refund_no", outRefundNo); int version = weixinAccount.getVersion();
map.put("total_fee", DateUtil.formaFee2Fen(totalFee)); String refund_uri = getRequestUri(String.format("refund_v%d_uri",
map.put("refund_fee", DateUtil.formaFee2Fen(refundFee)); version));
if (StringUtils.isBlank(opUserId)) { Response response = null;
opUserId = weixinAccount.getMchId(); if (version == 2) {
Map<String, String> map = new HashMap<String, String>();
map.put("input_charset", Consts.UTF_8.name());
// 版本号
// 填写为 1.0 ,操作员密码为明文
// 填写为 1.1 ,操作员密码为 MD5(密码)
map.put("service_version", "1.1");
map.put("partner", weixinAccount.getPartnerId());
map.put("out_refund_no", outRefundNo);
map.put("total_fee", DateUtil.formaFee2Fen(totalFee));
map.put("refund_fee", DateUtil.formaFee2Fen(refundFee));
map.put(idQuery.getType().getName(), idQuery.getId());
if (StringUtils.isBlank(opUserId)) {
opUserId = weixinAccount.getPartnerId();
}
map.put("op_user_id", opUserId);
map.put("op_user_passwd", DigestUtils.md5Hex(opUserPasswd));
// 以下几个字段可能用不到 记录下来
// [接收人帐号]
// recv_user_id
// 转账退款接收退款的财付通帐号
// 一般无需填写,只有退银行失败,资金转入商 户号现金账号时(即状态为转入代发,查询返 回的 refund_status 7
// 11),填写原退款 单号并填写此字段,资金才会退到指定财付通 账号其他情况此字段忽略
// ---------------------------------------------------------------------------------
// [接收人姓名]
// reccv_user_name
// 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
// ---------------------------------------------------------------------------------
// [通过商户订单号退款]
// use_spbill_no_flag
// 若通过接口 (https://www.tenpay.com/cgi-bin/v1.0/pay _gate.cgi)
// 支付的商户订单号来退款,则取值 1;而通过本文档支付接口的,则无需传值
// ---------------------------------------------------------------------------------
// [退款类型]
// refund_type
// 为空或者填 1:商户号余额退款;2:现金帐号 退款;3:优先商户号退款,若商户号余额不足, 再做现金帐号退款使用 2 3
// ,需联系财 付通开通此功能
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPartnerKey());
map.put("sign", sign.toLowerCase());
SSLContext ctx = null;
try {
File file = new File(ConfigUtil.getValue("ca_file"));
String jksPwd = "";
File jksFile = new File(String.format("%s/tenpay_cacert.jks",
file.getParent()));
KeyStore ks = null;
// create jks ca
if (!jksFile.exists()) {
CertificateFactory cf = CertificateFactory
.getInstance(com.foxinmy.weixin4j.model.Consts.X509);
java.security.cert.Certificate cert = cf
.generateCertificate(PayUtil.class
.getResourceAsStream("cacert.pem"));
ks = KeyStore
.getInstance(com.foxinmy.weixin4j.model.Consts.JKS);
ks.load(null, null);
ks.setCertificateEntry("tenpay", cert);
ks.store(new FileOutputStream(jksFile),
jksPwd.toCharArray());
}
// load jks ca
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(com.foxinmy.weixin4j.model.Consts.SunX509);
ks = KeyStore
.getInstance(com.foxinmy.weixin4j.model.Consts.JKS);
ks.load(new FileInputStream(jksFile), jksPwd.toCharArray());
tmf.init(ks);
// load pfx ca
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(com.foxinmy.weixin4j.model.Consts.SunX509);
ks = KeyStore
.getInstance(com.foxinmy.weixin4j.model.Consts.PKCS12);
ks.load(ca, weixinAccount.getPartnerId().toCharArray());
kmf.init(ks, weixinAccount.getPartnerId().toCharArray());
ctx = SSLContext
.getInstance(com.foxinmy.weixin4j.model.Consts.TLS);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
new SecureRandom());
} catch (Exception e) {
throw new WeixinException(e.getMessage());
}
SSLHttpRequest request = new SSLHttpRequest(ctx);
response = request.get(refund_uri, map);
} else if (version == 3) {
Map<String, String> map = baseMap2V3(idQuery);
map.put("out_refund_no", outRefundNo);
map.put("total_fee", DateUtil.formaFee2Fen(totalFee));
map.put("refund_fee", DateUtil.formaFee2Fen(refundFee));
if (StringUtils.isBlank(opUserId)) {
opUserId = weixinAccount.getMchId();
}
map.put("op_user_id", opUserId);
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
SSLHttpRequest request = new SSLHttpRequest(
weixinAccount.getMchId(), ca);
response = request.post(refund_uri, param);
} }
map.put("op_user_id", opUserId);
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
String refund_uri = getRequestUri("refund_uri");
SSLHttpRequest request = new SSLHttpRequest(weixinAccount.getMchId(),
ca);
Response response = request.post(refund_uri, param);
return response.getAsObject(new TypeReference<RefundResult>() { return response.getAsObject(new TypeReference<RefundResult>() {
}); });
} }
/** /**
* 使用properties中配置的ca文件 * 不同的退款接口选择<br/>
* 默认采用properties中配置的ca文件<br/>
* V2支付则需要传入opUserPasswd参数<br/>
* *
* @see {@link com.foxinmy.weixin4j.mp.api.PayApi#refund(InputStream, IdQuery, String, double, double, String)} * @see {@link com.foxinmy.weixin4j.mp.api.PayApi#refund(InputStream, IdQuery, String, double, double, String, String)}
*/ */
public RefundResult refund(IdQuery idQuery, String outRefundNo, public RefundResult refund(IdQuery idQuery, String outRefundNo,
double totalFee, double refundFee, String opUserId) double totalFee, double refundFee, String opUserId,
throws WeixinException, IOException { String opUserPasswd) throws WeixinException, IOException {
File caFile = new File(ConfigUtil.getValue("ca_file")); File ca = new File(ConfigUtil.getValue("ca_file"));
return refund(new FileInputStream(caFile), idQuery, outRefundNo, return refund(new FileInputStream(ca), idQuery, outRefundNo, totalFee,
totalFee, refundFee, opUserId); refundFee, opUserId, opUserPasswd);
} }
/** /**
@ -283,8 +393,8 @@ public class PayApi extends BaseApi {
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey()); String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign); map.put("sign", sign);
try { try {
map.put("long_url", URLEncoder.encode(url, utf8.name())); map.put("long_url", URLEncoder.encode(url, Consts.UTF_8.name()));
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException ignore) {
; ;
} }
String param = map2xml(map); String param = map2xml(map);
@ -302,6 +412,7 @@ public class PayApi extends BaseApi {
* @param outTradeNo * @param outTradeNo
* 商户系统内部的订单号 * 商户系统内部的订单号
* @return 处理结果 * @return 处理结果
* @since V3
* @throws WeixinException * @throws WeixinException
*/ */
public XmlResult closeOrder(String outTradeNo) throws WeixinException { public XmlResult closeOrder(String outTradeNo) throws WeixinException {
@ -329,6 +440,7 @@ public class PayApi extends BaseApi {
* 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单 * 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单 * REFUND,返回当日退款订单
* @return excel表格 * @return excel表格
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
* @throws IOException * @throws IOException
*/ */
@ -337,7 +449,7 @@ public class PayApi extends BaseApi {
WeixinAccount weixinAccount = tokenHolder.getAccount(); WeixinAccount weixinAccount = tokenHolder.getAccount();
if (billDate == null) { if (billDate == null) {
Calendar now = Calendar.getInstance(); Calendar now = Calendar.getInstance();
now.add(Calendar.DAY_OF_MONTH, -10); now.add(Calendar.DAY_OF_MONTH, -1);
billDate = now.getTime(); billDate = now.getTime();
} }
if (billType == null) { if (billType == null) {
@ -351,17 +463,37 @@ public class PayApi extends BaseApi {
if (file.exists()) { if (file.exists()) {
return file; return file;
} }
Map<String, String> map = baseMap2V3(null); int version = weixinAccount.getVersion();
map.put("bill_date", _billDate); String downloadbill_uri = getRequestUri(String.format(
map.put("bill_type", billType.name()); "downloadbill_v%d_uri", version));
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey()); Response response = null;
map.put("sign", sign); Charset charset = Consts.UTF_8;
String param = map2xml(map); if (version == 2) {
String downloadbill_uri = getRequestUri("downloadbill_uri"); Map<String, String> map = new LinkedHashMap<String, String>();
Response response = request.post(downloadbill_uri, param); map.put("spid", weixinAccount.getPartnerId());
map.put("trans_time", DateUtil.fortmat2yyyy_MM_dd(billDate));
map.put("stamp", Long.toString(System.currentTimeMillis() / 100));
map.put("cft_signtype", "0");
map.put("mchtype", Integer.toString(billType.getVal()));
map.put("key", weixinAccount.getPartnerKey());
String sign = DigestUtils.md5Hex(MapUtil.toJoinString(map, false,
false));
map.put("sign", sign.toLowerCase());
response = request.get(downloadbill_uri, map);
charset = Charset.forName("GBK");
} else if (version == 3) {
Map<String, String> map = baseMap2V3(null);
map.put("bill_date", _billDate);
map.put("bill_type", billType.name());
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
response = request.post(downloadbill_uri, param);
}
BufferedReader reader = new BufferedReader(new StringReader( BufferedReader reader = new BufferedReader(new InputStreamReader(
response.getAsString())); response.getStream(), charset));
String line = null; String line = null;
List<String[]> bills = new LinkedList<String[]>(); List<String[]> bills = new LinkedList<String[]>();
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
@ -389,17 +521,33 @@ public class PayApi extends BaseApi {
* 四个参数必填一个,优先级为: * 四个参数必填一个,优先级为:
* refund_id>out_refund_no>transaction_id>out_trade_no * refund_id>out_refund_no>transaction_id>out_trade_no
* @return 退款记录 * @return 退款记录
* @see com.foxinmy.weixin4j.mp.payment.v3.Refund * @see com.foxinmy.weixin4j.mp.payment.Refund
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public Refund refundQuery(IdQuery idQuery) throws WeixinException { public Refund refundQuery(IdQuery idQuery) throws WeixinException {
WeixinAccount weixinAccount = tokenHolder.getAccount(); WeixinAccount weixinAccount = tokenHolder.getAccount();
Map<String, String> map = baseMap2V3(idQuery); int version = weixinAccount.getVersion();
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey()); String refundquery_uri = getRequestUri(String.format(
map.put("sign", sign); "refundquery_v%d_uri", version));
String param = map2xml(map); Response response = null;
String refundquery_uri = getRequestUri("refundquery_uri"); if (version == 2) {
Response response = request.post(refundquery_uri, param); Map<String, String> map = new HashMap<String, String>();
map.put("input_charset", Consts.UTF_8.name());
map.put("partner", weixinAccount.getPartnerId());
map.put(idQuery.getType().getName(), idQuery.getId());
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPartnerKey());
map.put("sign", sign.toLowerCase());
response = request.get(refundquery_uri, map);
} else if (version == 3) {
Map<String, String> map = baseMap2V3(idQuery);
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
response = request.post(refundquery_uri, param);
}
return new RefundConverter().fromXML(response.getAsString()); return new RefundConverter().fromXML(response.getAsString());
} }

View File

@ -5,10 +5,15 @@
# http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E5%8F%A3%E9%A2%91%E7%8E%87%E9%99%90%E5%88%B6%E8%AF%B4%E6%98%8E # http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E5%8F%A3%E9%A2%91%E7%8E%87%E9%99%90%E5%88%B6%E8%AF%B4%E6%98%8E
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
api_base_url=https://api.weixin.qq.com/cgi-bin api_base_url=https://api.weixin.qq.com
api_cgi_url={api_base_url}/cgi-bin
mp_base_url=https://mp.weixin.qq.com/cgi-bin mp_base_url=https://mp.weixin.qq.com/cgi-bin
file_base_url=http://file.api.weixin.qq.com/cgi-bin file_base_url=http://file.api.weixin.qq.com/cgi-bin
mch_base_url=https://api.mch.weixin.qq.com mch_base_url=https://api.mch.weixin.qq.com
tenpay_base_url=http://mch.tenpay.com
tenpay_ssl_base_url=https://mch.tenpay.com
tenpay_gw_base_url=https://gw.tenpay.com
# \u7f51\u9875\u6388\u6743\u83b7\u53d6\u7528\u6237\u4fe1\u606f # \u7f51\u9875\u6388\u6743\u83b7\u53d6\u7528\u6237\u4fe1\u606f
user_auth_uri=https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect user_auth_uri=https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect
@ -16,68 +21,78 @@ sns_user_token_uri=https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&se
sns_user_info_uri=https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN sns_user_info_uri=https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN
# \u76f4\u63a5\u83b7\u53d6\u7528\u6237\u4fe1\u606f # \u76f4\u63a5\u83b7\u53d6\u7528\u6237\u4fe1\u606f
api_user_info_uri={api_base_url}/user/info?access_token=%s&openid=%s&lang=zh_CN api_user_info_uri={api_cgi_url}/user/info?access_token=%s&openid=%s&lang=zh_CN
# \u83b7\u53d6token # \u83b7\u53d6token
api_token_uri={api_base_url}/token?grant_type=client_credential&appid=%s&secret=%s api_token_uri={api_cgi_url}/token?grant_type=client_credential&appid=%s&secret=%s
# \u83b7\u53d6\u4e8c\u7ef4\u7801 # \u83b7\u53d6\u4e8c\u7ef4\u7801
qr_ticket_uri={api_base_url}/qrcode/create?access_token=%s qr_ticket_uri={api_cgi_url}/qrcode/create?access_token=%s
qr_image_uri={mp_base_url}/showqrcode?ticket=%s qr_image_uri={mp_base_url}/showqrcode?ticket=%s
# \u4e0a\u4f20\u5a92\u4f53\u6587\u4ef6 # \u4e0a\u4f20\u5a92\u4f53\u6587\u4ef6
file_upload_uri={file_base_url}/media/upload?access_token=%s&type=%s file_upload_uri={file_base_url}/media/upload?access_token=%s&type=%s
# \u4e0b\u8f7d\u5a92\u4f53\u6587\u4ef6 # \u4e0b\u8f7d\u5a92\u4f53\u6587\u4ef6
file_download_uri={file_base_url}/media/get?access_token=%s&media_id=%s file_download_uri={file_base_url}/media/get?access_token=%s&media_id=%s
# \u53d1\u9001\u5ba2\u670d\u6d88\u606f # \u53d1\u9001\u5ba2\u670d\u6d88\u606f
custom_notify_uri={api_base_url}/message/custom/send?access_token=%s custom_notify_uri={api_cgi_url}/message/custom/send?access_token=%s
# \u521b\u5efa\u5206\u7ec4 # \u521b\u5efa\u5206\u7ec4
group_create_uri={api_base_url}/groups/create?access_token=%s group_create_uri={api_cgi_url}/groups/create?access_token=%s
# \u67e5\u8be2\u5206\u7ec4 # \u67e5\u8be2\u5206\u7ec4
group_get_uri={api_base_url}/groups/get?access_token=%s group_get_uri={api_cgi_url}/groups/get?access_token=%s
# \u67e5\u8be2\u7528\u6237\u6240\u5728\u5206\u7ec4 # \u67e5\u8be2\u7528\u6237\u6240\u5728\u5206\u7ec4
group_getid_uri={api_base_url}/groups/getid?access_token=%s group_getid_uri={api_cgi_url}/groups/getid?access_token=%s
# \u4fee\u6539\u5206\u7ec4\u540d # \u4fee\u6539\u5206\u7ec4\u540d
group_modify_uri={api_base_url}/groups/update?access_token=%s group_modify_uri={api_cgi_url}/groups/update?access_token=%s
# \u79fb\u52a8\u7528\u6237\u5206\u7ec4 # \u79fb\u52a8\u7528\u6237\u5206\u7ec4
group_move_uri={api_base_url}/groups/members/update?access_token=%s group_move_uri={api_cgi_url}/groups/members/update?access_token=%s
# \u83b7\u53d6\u5173\u6ce8\u7740 # \u83b7\u53d6\u5173\u6ce8\u7740
following_uri={api_base_url}/user/get?access_token=%s&next_openid=%s following_uri={api_cgi_url}/user/get?access_token=%s&next_openid=%s
# \u81ea\u5b9a\u4e49\u83dc\u5355 # \u81ea\u5b9a\u4e49\u83dc\u5355
menu_create_uri={api_base_url}/menu/create?access_token=%s menu_create_uri={api_cgi_url}/menu/create?access_token=%s
# \u67e5\u8be2\u83dc\u5355 # \u67e5\u8be2\u83dc\u5355
menu_get_uri={api_base_url}/menu/get?access_token=%s menu_get_uri={api_cgi_url}/menu/get?access_token=%s
# \u5220\u9664\u83dc\u5355 # \u5220\u9664\u83dc\u5355
menu_delete_uri={api_base_url}/menu/delete?access_token=%s menu_delete_uri={api_cgi_url}/menu/delete?access_token=%s
# \u4e0a\u4f20\u56fe\u6587 # \u4e0a\u4f20\u56fe\u6587
article_upload_uri={api_base_url}/media/uploadnews?access_token=%s article_upload_uri={api_cgi_url}/media/uploadnews?access_token=%s
# \u4e0a\u4f20\u89c6\u9891 # \u4e0a\u4f20\u89c6\u9891
video_upload_uri={file_base_url}/media/uploadvideo?access_token=%s video_upload_uri={file_base_url}/media/uploadvideo?access_token=%s
# \u5206\u7ec4\u7fa4\u53d1 # \u5206\u7ec4\u7fa4\u53d1
mass_group_uri={api_base_url}/message/mass/sendall?access_token=%s mass_group_uri={api_cgi_url}/message/mass/sendall?access_token=%s
# openId\u7fa4\u53d1 # openId\u7fa4\u53d1
mass_openid_uri={api_base_url}/message/mass/send?access_token=%s mass_openid_uri={api_cgi_url}/message/mass/send?access_token=%s
# \u5220\u9664\u7fa4\u53d1 # \u5220\u9664\u7fa4\u53d1
mass_delete_uri={api_base_url}/message/mass/delete?access_token=%s mass_delete_uri={api_cgi_url}/message/mass/delete?access_token=%s
# \u5ba2\u670d\u804a\u5929\u8bb0\u5f55 # \u5ba2\u670d\u804a\u5929\u8bb0\u5f55
custom_record_uri={api_base_url}/customservice/getrecord?access_token=%s custom_record_uri={api_cgi_url}/customservice/getrecord?access_token=%s
# \u957f\u94fe\u63a5\u8f6c\u77ed\u94fe\u63a5 # \u957f\u94fe\u63a5\u8f6c\u77ed\u94fe\u63a5
shorturl_uri={api_base_url}/shorturl?access_token=%s shorturl_uri={api_cgi_url}/shorturl?access_token=%s
p_shorturl_uri={mch_base_url}/tools/shorturl p_shorturl_uri={mch_base_url}/tools/shorturl
# \u8bbe\u7f6e\u5907\u6ce8\u540d # \u8bbe\u7f6e\u5907\u6ce8\u540d
updateremark_uri={api_base_url}/user/info/updateremark?access_token=%s updateremark_uri={api_cgi_url}/user/info/updateremark?access_token=%s
# \u6a21\u677f\u6d88\u606f # \u6a21\u677f\u6d88\u606f
template_send_uri={api_base_url}/message/template/send?access_token=%s template_send_uri={api_cgi_url}/message/template/send?access_token=%s
# \u8bed\u4e49\u7406\u89e3
semantic_uri={api_base_url}/semantic/semproxy/search?access_token=%s
# \u8ba2\u5355\u67e5\u8be2 # \u8ba2\u5355\u67e5\u8be2
orderquery_uri=https://api.weixin.qq.com/pay/orderquery?access_token=%s orderquery_uri={api_base_url}/pay/orderquery?access_token=%s
# \u53d1\u8d27\u901a\u77e5 # \u53d1\u8d27\u901a\u77e5
delivernotify_uri=https://api.weixin.qq.com/pay/delivernotify?access_token=%s delivernotify_uri={api_base_url}/pay/delivernotify?access_token=%s
# \u7ef4\u6743\u5904\u7406 # \u7ef4\u6743\u5904\u7406
payfeedback_update_uri=https://api.weixin.qq.com/payfeedback/update?access_token=%s&openid=%s&feedbackid=%s payfeedback_update_uri={api_base_url}/payfeedback/update?access_token=%s&openid=%s&feedbackid=%s
# \u5bf9\u8d26\u5355\u4e0b\u8f7d
downloadbill_v2_uri={tenpay_base_url}/cgi-bin/mchdown_real_new.cgi
# \u9000\u6b3e\u67e5\u8be2
refundquery_v2_uri={tenpay_gw_base_url}/gateway/normalrefundquery.xml
# \u9000\u6b3e\u7533\u8bf7
refund_v2_uri={tenpay_ssl_base_url}/refundapi/gateway/refund.xml
# \u8ba2\u5355\u67e5\u8be2 # \u8ba2\u5355\u67e5\u8be2
orderquery_v3_uri={mch_base_url}/pay/orderquery orderquery_v3_uri={mch_base_url}/pay/orderquery
# \u5173\u95ed\u8ba2\u5355 # \u5173\u95ed\u8ba2\u5355
closeorder_uri={mch_base_url}/pay/closeorder closeorder_uri={mch_base_url}/pay/closeorder
# \u5bf9\u8d26\u5355\u4e0b\u8f7d # \u5bf9\u8d26\u5355\u4e0b\u8f7d
downloadbill_uri={mch_base_url}/pay/downloadbill downloadbill_v3_uri={mch_base_url}/pay/downloadbill
# \u9000\u6b3e\u67e5\u8be2 # \u9000\u6b3e\u67e5\u8be2
refundquery_uri={mch_base_url}/pay/refundquery refundquery_v3_uri={mch_base_url}/pay/refundquery
# \u9000\u6b3e\u7533\u8bf7 # \u9000\u6b3e\u7533\u8bf7
refund_uri={mch_base_url}/secapi/pay/refund refund_v3_uri={mch_base_url}/secapi/pay/refund

View File

@ -38,10 +38,10 @@ public class Button implements Serializable {
public Button(String name, String value, ButtonType buttonType) { public Button(String name, String value, ButtonType buttonType) {
this.name = name; this.name = name;
this.type = buttonType; this.type = buttonType;
if (buttonType == ButtonType.click) { if (buttonType == ButtonType.view) {
this.key = value;
} else if (buttonType == ButtonType.view) {
this.url = value; this.url = value;
} else {
this.key = value;
} }
} }

View File

@ -0,0 +1,88 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.mp.type.SemCategory;
/**
* 语义理解参数
*
* @className SemQuery
* @author jy
* @date 2014年11月7日
* @since JDK 1.7
* @see
*/
public class SemQuery implements Serializable {
private static final long serialVersionUID = 679548284525912436L;
private JSONObject jsonObj;
// 输入文本串
public SemQuery(String query) {
jsonObj = new JSONObject();
jsonObj.put("query", query);
}
// 城市名称,与经纬度二选一传入
public SemQuery city(String city) {
jsonObj.put("city", city);
return this;
}
// 需要使用的服务类别,多个用,隔开,不能为空
public SemQuery category(SemCategory... categorys) {
String category = "";
if (categorys.length == 1) {
category = categorys[0].name();
} else {
for (int i = 0; i < categorys.length - 1; i++) {
category += categorys[i].name() + ",";
}
category += categorys[categorys.length - 1].name();
}
jsonObj.put("category", category);
return this;
}
// App id,开发者的唯一标识,用于区分开放者, 如果为空,则没法使用上下文理解功能
public SemQuery appid(String appid) {
jsonObj.put("appid", appid);
return this;
}
// 用户唯一 id(并非开发者 id),用于区分该开发者下不同用户,如果为空,则没法使用上下文理解功能appid uid
// 同时存在的情况下,才可以使用上下文理解功能
public SemQuery uid(String uid) {
jsonObj.put("uid", uid);
return this;
}
// 区域名称,在城市存在的情况下可省;与经纬度 二选一传入
public SemQuery region(String region) {
jsonObj.put("region", region);
return this;
}
// 纬度经度;与城市二选一传入
public SemQuery location(float latitude, float longitude) {
jsonObj.put("latitude", latitude);
jsonObj.put("longitude", longitude);
return this;
}
// 输入文本串
public static SemQuery build(String query) {
return new SemQuery(query);
}
public String toJson() {
return jsonObj.toJSONString();
}
@Override
public String toString() {
return "SemQuery " + jsonObj;
}
}

View File

@ -0,0 +1,88 @@
package com.foxinmy.weixin4j.mp.model;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.http.JsonResult;
/**
* 语义理解结果
*
* @className SemResult
* @author jy
* @date 2014年11月7日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/index.php?title=%E8%AF%AD%E4%B9%89%E7%90%86%E8%A7%A3">语义理解</a>
*/
public class SemResult extends JsonResult {
private static final long serialVersionUID = 9051214458161068387L;
// 用户的输入字符串
private String query;
// 服务的全局类型id详见协议文档中垂直服务协议定义
private String type;
// 语义理解后的结构化标识各服务不同
private JSONObject semantic;
// 部分类别的结果
private JSONArray result;
// 部分类别的结果html5展示目前不支持
private String answer;
// 特殊回复说明
private String text;
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public JSONObject getSemantic() {
return semantic;
}
public void setSemantic(JSONObject semantic) {
this.semantic = semantic;
}
public JSONArray getResult() {
return result;
}
public void setResult(JSONArray result) {
this.result = result;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public String toString() {
return "SemResult [query=" + query + ", type=" + type + ", semantic="
+ semantic + ", result=" + result + ", answer=" + answer
+ ", text=" + text + ", getCode()=" + getCode()
+ ", getDesc()=" + getDesc() + "]";
}
}

View File

@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.PayException; import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.http.XmlResult; import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.model.WeixinAccount; import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.mp.payment.v2.NativePayNotifyV2; import com.foxinmy.weixin4j.mp.payment.v2.NativePayNotifyV2;
import com.foxinmy.weixin4j.mp.payment.v2.NativePayResponseV2; import com.foxinmy.weixin4j.mp.payment.v2.NativePayResponseV2;
@ -163,7 +164,7 @@ public class PayAction {
weixinAccount.getPaySignKey()); weixinAccount.getPaySignKey());
log.info("微信签名----->sign={},vaild_sign={}", sign, valid_sign); log.info("微信签名----->sign={},vaild_sign={}", sign, valid_sign);
if (!sign.equals(valid_sign)) { if (!sign.equals(valid_sign)) {
return XStream.to(new XmlResult(XmlResult.FAIL, "签名错误")); return XStream.to(new XmlResult(Consts.FAIL, "签名错误"));
} }
return XStream.to(new XmlResult()); return XStream.to(new XmlResult());
} }

View File

@ -122,9 +122,9 @@ public class PayUtil {
* @return * @return
*/ */
public static String paysignSha(Object obj, String paySignKey) { public static String paysignSha(Object obj, String paySignKey) {
JSONObject extra = null; Map<String, String> extra = null;
if (StringUtils.isNotBlank(paySignKey)) { if (StringUtils.isNotBlank(paySignKey)) {
extra = new JSONObject(); extra = new HashMap<String, String>();
extra.put("appKey", paySignKey); extra.put("appKey", paySignKey);
} }
return DigestUtils.sha1Hex(MapUtil return DigestUtils.sha1Hex(MapUtil
@ -255,7 +255,7 @@ public class PayUtil {
Map<String, String> param = new HashMap<String, String>(); Map<String, String> param = new HashMap<String, String>();
param.put("appId", appId); param.put("appId", appId);
param.put("url", url); param.put("url", url);
param.put("timeStamp", System.currentTimeMillis() / 1000 + ""); param.put("timeStamp", Long.toString(System.currentTimeMillis() / 1000));
param.put("nonceStr", RandomUtil.generateString(16)); param.put("nonceStr", RandomUtil.generateString(16));
param.put("accessToken", accessToken); param.put("accessToken", accessToken);
String sign = paysignSha(param, null); String sign = paysignSha(param, null);

View File

@ -1,8 +1,7 @@
package com.foxinmy.weixin4j.mp.payment.v3; package com.foxinmy.weixin4j.mp.payment;
import java.util.List; import java.util.List;
import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
@ -12,7 +11,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @author jy * @author jy
* @date 2014年11月1日 * @date 2014年11月1日
* @since JDK 1.7 * @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail * @see com.foxinmy.weixin4j.mp.payment.RefundDetail
*/ */
@XStreamAlias("xml") @XStreamAlias("xml")
public class Refund extends ApiResult { public class Refund extends ApiResult {
@ -27,6 +26,7 @@ public class Refund extends ApiResult {
private String subMchId; // private String subMchId; //
@XStreamAlias("refund_count") @XStreamAlias("refund_count")
private int count;// 退款笔数 private int count;// 退款笔数
private String partner; // 商户号V2
private List<RefundDetail> details; private List<RefundDetail> details;
public String getTransactionId() { public String getTransactionId() {
@ -45,6 +45,10 @@ public class Refund extends ApiResult {
return count; return count;
} }
public String getPartner() {
return partner;
}
public List<RefundDetail> getDetails() { public List<RefundDetail> getDetails() {
return details; return details;
} }
@ -57,12 +61,13 @@ public class Refund extends ApiResult {
public String toString() { public String toString() {
return "Refund [transactionId=" + transactionId + ", orderNo=" return "Refund [transactionId=" + transactionId + ", orderNo="
+ orderNo + ", subMchId=" + subMchId + ", count=" + count + orderNo + ", subMchId=" + subMchId + ", count=" + count
+ ", details=" + details + ", getAppId()=" + getAppId() + ", partner=" + partner + ", details=" + details
+ ", getMchId()=" + getMchId() + ", getNonceStr()=" + ", getAppId()=" + getAppId() + ", getMchId()=" + getMchId()
+ getNonceStr() + ", getSign()=" + getSign() + ", getNonceStr()=" + getNonceStr() + ", getSign()="
+ ", getDeviceInfo()=" + getDeviceInfo() + ", getReturnCode()=" + getSign() + ", getDeviceInfo()=" + getDeviceInfo()
+ getReturnCode() + ", getReturnMsg()=" + getReturnMsg() + ", getReturnCode()=" + getReturnCode() + ", getReturnMsg()="
+ ", getResultCode()=" + getResultCode() + ", getErrCode()=" + getReturnMsg() + ", getResultCode()=" + getResultCode()
+ getErrCode() + ", getErrCodeDes()=" + getErrCodeDes() + "]"; + ", getErrCode()=" + getErrCode() + ", getErrCodeDes()="
+ getErrCodeDes() + "]";
} }
} }

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.mp.payment.v3; package com.foxinmy.weixin4j.mp.payment;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashMap; import java.util.HashMap;
@ -26,8 +26,8 @@ import com.thoughtworks.xstream.mapper.Mapper;
* @author jy * @author jy
* @date 2014年11月2日 * @date 2014年11月2日
* @since JDK 1.7 * @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v3.Refund * @see com.foxinmy.weixin4j.mp.payment.Refund
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail * @see com.foxinmy.weixin4j.mp.payment.RefundDetail
*/ */
public class RefundConverter { public class RefundConverter {
private final static XStream xStream = XStream.get(); private final static XStream xStream = XStream.get();

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.mp.payment.v3; package com.foxinmy.weixin4j.mp.payment;
import java.io.Serializable; import java.io.Serializable;
@ -32,7 +32,15 @@ public class RefundDetail implements Serializable {
@XStreamAlias("coupon_refund_fee") @XStreamAlias("coupon_refund_fee")
private int couponRefundFee; // 现金券退款金额<=退款金额,退款金额-现金券退款金额为现金 private int couponRefundFee; // 现金券退款金额<=退款金额,退款金额-现金券退款金额为现金
@XStreamAlias("refund_status") @XStreamAlias("refund_status")
private RefundStatus refundStatus; // 退款状态 private String refundStatus; // 退款状态
@XStreamAlias("recv_user_id")
private String recvUserId;// 转账退款接收退款的财付通帐号
@XStreamAlias("reccv_user_name")
private String reccvUserName;// 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
@XStreamAlias("sign_key_index")
private String signKeyIndex;// 多密钥支持的密钥序号,默认 1
@XStreamAlias("sign_type")
private String signType;// 签名类型,取值:MD5RSA,默认:MD5
public String getOutRefundNo() { public String getOutRefundNo() {
return outRefundNo; return outRefundNo;
@ -43,8 +51,16 @@ public class RefundDetail implements Serializable {
} }
public String getRefundChannel() { public String getRefundChannel() {
return StringUtils.isBlank(refundChannel) ? RefundChannel.BALANCE if (StringUtils.isBlank(refundChannel)) {
.name() : refundChannel; return RefundChannel.BALANCE.name();
}
// V2
if (refundChannel.equals("0")) {
return RefundChannel.TENPAY.name();
} else if (refundChannel.equals("1")) {
return RefundChannel.BALANCE.name();
}
return refundChannel;
} }
/** /**
@ -66,7 +82,36 @@ public class RefundDetail implements Serializable {
} }
public RefundStatus getRefundStatus() { public RefundStatus getRefundStatus() {
return refundStatus; // V2
if ("4,10,".contains(refundStatus + ",")) {
return RefundStatus.SUCCES;
} else if ("3,5,6,".contains(refundStatus + ",")) {
return RefundStatus.FAIL;
} else if ("8,9,10,".contains(refundStatus + ",")) {
return RefundStatus.PROCESSING;
} else if ("1,2,".contains(refundStatus + ",")) {
return RefundStatus.NOTSURE;
} else if ("7,".contains(refundStatus + ",")) {
return RefundStatus.CHANGE;
} else {
return RefundStatus.valueOf(refundStatus);
}
}
public String getRecvUserId() {
return recvUserId;
}
public String getReccvUserName() {
return reccvUserName;
}
public String getSignKeyIndex() {
return signKeyIndex;
}
public String getSignType() {
return signType;
} }
@Override @Override
@ -74,6 +119,9 @@ public class RefundDetail implements Serializable {
return "RefundDetail [outRefundNo=" + outRefundNo + ", refundId=" return "RefundDetail [outRefundNo=" + outRefundNo + ", refundId="
+ refundId + ", refundChannel=" + refundChannel + refundId + ", refundChannel=" + refundChannel
+ ", refundFee=" + refundFee + ", couponRefundFee=" + ", refundFee=" + refundFee + ", couponRefundFee="
+ couponRefundFee + ", refundStatus=" + refundStatus + "]"; + couponRefundFee + ", refundStatus=" + refundStatus
+ ", recvUserId=" + recvUserId + ", reccvUserName="
+ reccvUserName + ", signKeyIndex=" + signKeyIndex
+ ", signType=" + signType + "]";
} }
} }

View File

@ -1,8 +1,7 @@
package com.foxinmy.weixin4j.mp.payment.v3; package com.foxinmy.weixin4j.mp.payment;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.foxinmy.weixin4j.mp.type.RefundChannel; import com.foxinmy.weixin4j.mp.type.RefundChannel;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -41,6 +40,14 @@ public class RefundResult extends ApiResult {
// 现金券退款金额<=退款金 ,退款金额-现金券退款金 额为现金 // 现金券退款金额<=退款金 ,退款金额-现金券退款金 额为现金
@XStreamAlias("coupon_refund_fee") @XStreamAlias("coupon_refund_fee")
private int couponRefundFee; private int couponRefundFee;
@XStreamAlias("recv_user_id")
private String recvUserId;// 转账退款接收退款的财付通帐号
@XStreamAlias("reccv_user_name")
private String reccvUserName;// 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
@XStreamAlias("sign_key_index")
private String signKeyIndex;// 多密钥支持的密钥序号,默认 1
@XStreamAlias("sign_type")
private String signType;// 签名类型,取值:MD5RSA,默认:MD5
public String getTransactionId() { public String getTransactionId() {
return transactionId; return transactionId;
@ -59,8 +66,16 @@ public class RefundResult extends ApiResult {
} }
public String getRefundChannel() { public String getRefundChannel() {
return StringUtils.isBlank(refundChannel) ? RefundChannel.BALANCE if (StringUtils.isBlank(refundChannel)) {
.name() : refundChannel; return RefundChannel.BALANCE.name();
}
// V2
if (refundChannel.equals("0")) {
return RefundChannel.TENPAY.name();
} else if (refundChannel.equals("1")) {
return RefundChannel.BALANCE.name();
}
return refundChannel;
} }
/** /**
@ -81,14 +96,25 @@ public class RefundResult extends ApiResult {
return couponRefundFee / 100d; return couponRefundFee / 100d;
} }
public String getSignKeyIndex() {
return signKeyIndex;
}
public String getSignType() {
return signType;
}
@Override @Override
public String toString() { public String toString() {
return "RefundResult [transactionId=" + transactionId + ", outTradeNo=" return "RefundResult [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", outRefundNo=" + outRefundNo + ", refundId=" + outTradeNo + ", outRefundNo=" + outRefundNo + ", refundId="
+ refundId + ", refundChannel=" + refundChannel + refundId + ", refundChannel=" + refundChannel
+ ", refundFee=" + refundFee + ", couponRefundFee=" + ", refundFee=" + refundFee + ", couponRefundFee="
+ couponRefundFee + ", getAppId()=" + getAppId() + couponRefundFee + ", recvUserId=" + recvUserId
+ ", getMchId()=" + getMchId() + ", getNonceStr()=" + ", reccvUserName=" + reccvUserName + ", signKeyIndex="
+ signKeyIndex + ", signType=" + signType + ", getAppId()="
+ getAppId() + ", getMchId()=" + getMchId()
+ ", getSubMchId()=" + getSubMchId() + ", getNonceStr()="
+ getNonceStr() + ", getSign()=" + getSign() + getNonceStr() + ", getSign()=" + getSign()
+ ", getDeviceInfo()=" + getDeviceInfo() + ", getReturnCode()=" + ", getDeviceInfo()=" + getDeviceInfo() + ", getReturnCode()="
+ getReturnCode() + ", getReturnMsg()=" + getReturnMsg() + getReturnCode() + ", getReturnMsg()=" + getReturnMsg()

View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDezCCAuSgAwIBAgIJAI7aVO7iYwcQMA0GCSqGSIb3DQEBBAUAMIGGMQswCQYD
VQQGEwJDTjESMBAGA1UECBMJR1VBTkdET05HMREwDwYDVQQHEwhTSEVOWkhFTjEQ
MA4GA1UEChMHVEVOQ0VOVDEMMAoGA1UECxMDT1NTMQwwCgYDVQQDEwNDRlQxIjAg
BgkqhkiG9w0BCQEWE3RpcHlsdW9AdGVuY2VudC5jb20wHhcNMDYwMzE0MTQyMjIz
WhcNMTYwMzExMTQyMjIzWjCBhjELMAkGA1UEBhMCQ04xEjAQBgNVBAgTCUdVQU5H
RE9ORzERMA8GA1UEBxMIU0hFTlpIRU4xEDAOBgNVBAoTB1RFTkNFTlQxDDAKBgNV
BAsTA09TUzEMMAoGA1UEAxMDQ0ZUMSIwIAYJKoZIhvcNAQkBFhN0aXB5bHVvQHRl
bmNlbnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZF0wb1UIgAbwq
RgnQtGRZon1LG1NLd1CiFQO41aESrJ/3hWvnzlpxepIwi9H3xUuMeoP3kOSJoT5O
EKOVkcgkzVcPebfBgnnLToMTduuHHJ+iWWPmzFsJQB5xetJXD1lqsxlDLZk2+eiD
FuGfPKDaW76V24YPLCQ6uzaRMiFDaQIDAQABo4HuMIHrMB0GA1UdDgQWBBTi3asX
klhRZA2dvy0onxvowmx2YzCBuwYDVR0jBIGzMIGwgBTi3asXklhRZA2dvy0onxvo
wmx2Y6GBjKSBiTCBhjELMAkGA1UEBhMCQ04xEjAQBgNVBAgTCUdVQU5HRE9ORzER
MA8GA1UEBxMIU0hFTlpIRU4xEDAOBgNVBAoTB1RFTkNFTlQxDDAKBgNVBAsTA09T
UzEMMAoGA1UEAxMDQ0ZUMSIwIAYJKoZIhvcNAQkBFhN0aXB5bHVvQHRlbmNlbnQu
Y29tggkAjtpU7uJjBxAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQCM
VOZNmGO9XAtEPFuA3Q42LkfcwHK9Q0CAMbMPA4pNADxBWaQvv5okE3YG1RNGLp/y
wyxXDs62qpEHcDPQh5porQKRP3PB54pL3AFLouEcuH4j7e8f8tqokOMmtTfeSUTJ
wYWlbwUEu53AhNBlQl1zATkGmeURsmXg5GnuNAWafg==
-----END CERTIFICATE-----

View File

@ -3,6 +3,7 @@ package com.foxinmy.weixin4j.mp.payment.v3;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.foxinmy.weixin4j.exception.PayException; import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.mp.payment.ApiResult; import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.foxinmy.weixin4j.mp.payment.PayUtil; import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.util.RandomUtil; import com.foxinmy.weixin4j.util.RandomUtil;
@ -27,9 +28,11 @@ public class NativePayResponseV3 extends ApiResult {
public NativePayResponseV3(PayPackageV3 payPackage, String returnMsg, public NativePayResponseV3(PayPackageV3 payPackage, String returnMsg,
String resultMsg) throws PayException { String resultMsg) throws PayException {
super.setReturnMsg(returnMsg); super.setReturnMsg(returnMsg);
super.setReturnCode(StringUtils.isNotBlank(returnMsg) ? FAIL : SUCCESS); super.setReturnCode(StringUtils.isNotBlank(returnMsg) ? Consts.FAIL
: Consts.SUCCESS);
this.setErrCodeDes(resultMsg); this.setErrCodeDes(resultMsg);
this.setResultCode(StringUtils.isNotBlank(resultMsg) ? FAIL : SUCCESS); this.setResultCode(StringUtils.isNotBlank(resultMsg) ? Consts.FAIL
: Consts.SUCCESS);
this.setMchId(payPackage.getMch_id()); this.setMchId(payPackage.getMch_id());
this.setAppId(payPackage.getAppid()); this.setAppId(payPackage.getAppid());
this.setNonceStr(RandomUtil.generateString(16)); this.setNonceStr(RandomUtil.generateString(16));

View File

@ -2,7 +2,7 @@ package com.foxinmy.weixin4j.mp.payment.v3;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.exception.PayException; import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.http.XmlResult; import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.mp.payment.PayRequest; import com.foxinmy.weixin4j.mp.payment.PayRequest;
import com.thoughtworks.xstream.annotations.XStreamOmitField; import com.thoughtworks.xstream.annotations.XStreamOmitField;
@ -32,11 +32,11 @@ public class PayRequestV3 extends PayRequest {
private PrePay prePay; private PrePay prePay;
public PayRequestV3(PrePay prePay) throws PayException { public PayRequestV3(PrePay prePay) throws PayException {
if (!prePay.getReturnCode().equalsIgnoreCase(XmlResult.SUCCESS)) { if (!prePay.getReturnCode().equalsIgnoreCase(Consts.SUCCESS)) {
throw new PayException(prePay.getReturnMsg(), throw new PayException(prePay.getReturnMsg(),
prePay.getReturnCode()); prePay.getReturnCode());
} }
if (!prePay.getResultCode().equalsIgnoreCase(XmlResult.SUCCESS)) { if (!prePay.getResultCode().equalsIgnoreCase(Consts.SUCCESS)) {
throw new PayException(prePay.getResultCode(), throw new PayException(prePay.getResultCode(),
prePay.getErrCodeDes()); prePay.getErrCodeDes());
} }

View File

@ -2,8 +2,6 @@ package com.foxinmy.weixin4j.mp.spider;
import java.io.Serializable; import java.io.Serializable;
import java.net.URI; import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -12,6 +10,7 @@ import java.util.Map;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -59,8 +58,6 @@ public class WeixinExecutor implements Serializable {
private final Logger logger = LoggerFactory.getLogger(getClass()); private final Logger logger = LoggerFactory.getLogger(getClass());
private final static Charset charset = StandardCharsets.UTF_8;
private final static Map<String, String> accountMap = new HashMap<String, String>() { private final static Map<String, String> accountMap = new HashMap<String, String>() {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -186,13 +183,13 @@ public class WeixinExecutor implements Serializable {
if (!StringUtils.isBlank(imgcode)) { if (!StringUtils.isBlank(imgcode)) {
method.addHeader("Cookie", "sig=" + sig); method.addHeader("Cookie", "sig=" + sig);
} }
method.setEntity(new UrlEncodedFormEntity(parameters, charset)); method.setEntity(new UrlEncodedFormEntity(parameters, Consts.UTF_8));
method.addHeader("Referer", weixin.getString("base")); method.addHeader("Referer", weixin.getString("base"));
HttpResponse response = client.execute(host, method); HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(), charset.name(), Document root = Jsoup.parse(entity.getContent(),
weixin.getString("base")); Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine(); StatusLine line = response.getStatusLine();
logger.info("step1_login--->status={},body=\n{}", line, logger.info("step1_login--->status={},body=\n{}", line,
root.toString()); root.toString());
@ -270,7 +267,8 @@ public class WeixinExecutor implements Serializable {
.getValue()).append(";base64,"); .getValue()).append(";base64,");
base64.append(new String( base64.append(new String(
Base64.encodeBase64(IOUtil.toByteArray(response Base64.encodeBase64(IOUtil.toByteArray(response
.getEntity().getContent())), charset)); .getEntity().getContent())),
Consts.UTF_8));
weixin.put("verifydata", base64.toString()); weixin.put("verifydata", base64.toString());
List<Cookie> cookieList = client.getCookieStore() List<Cookie> cookieList = client.getCookieStore()
.getCookies(); .getCookies();
@ -311,8 +309,8 @@ public class WeixinExecutor implements Serializable {
HttpResponse response = client.execute(host, method); HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(), charset.name(), Document root = Jsoup.parse(entity.getContent(),
weixin.getString("base")); Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine(); StatusLine line = response.getStatusLine();
logger.info("step2_setting--->status={},body=\n{}", line, logger.info("step2_setting--->status={},body=\n{}", line,
root.toString()); root.toString());
@ -331,7 +329,7 @@ public class WeixinExecutor implements Serializable {
response = client.execute(host, method); response = client.execute(host, method);
entity = response.getEntity(); entity = response.getEntity();
root = Jsoup.parse(entity.getContent(), charset.name(), root = Jsoup.parse(entity.getContent(), Consts.UTF_8.name(),
weixin.getString("base")); weixin.getString("base"));
line = response.getStatusLine(); line = response.getStatusLine();
weixin.put("step", "2-1"); weixin.put("step", "2-1");
@ -378,8 +376,8 @@ public class WeixinExecutor implements Serializable {
response = client.execute(host, method); response = client.execute(host, method);
entity = response.getEntity(); entity = response.getEntity();
root = Jsoup.parse(entity.getContent(), charset.name(), root = Jsoup.parse(entity.getContent(),
weixin.getString("base")); Consts.UTF_8.name(), weixin.getString("base"));
line = response.getStatusLine(); line = response.getStatusLine();
if (line.getStatusCode() == HttpStatus.SC_OK) { if (line.getStatusCode() == HttpStatus.SC_OK) {
@ -402,11 +400,12 @@ public class WeixinExecutor implements Serializable {
System.currentTimeMillis() + "")); System.currentTimeMillis() + ""));
post.setEntity(new UrlEncodedFormEntity(parameters, post.setEntity(new UrlEncodedFormEntity(parameters,
charset)); Consts.UTF_8));
response = client.execute(host, post); response = client.execute(host, post);
entity = response.getEntity(); entity = response.getEntity();
root = Jsoup.parse(entity.getContent(), root = Jsoup.parse(entity.getContent(),
charset.name(), weixin.getString("base")); Consts.UTF_8.name(),
weixin.getString("base"));
line = response.getStatusLine(); line = response.getStatusLine();
logger.info( logger.info(
"step2_bedeveloper--->status={},body=\n{}", "step2_bedeveloper--->status={},body=\n{}",
@ -422,7 +421,7 @@ public class WeixinExecutor implements Serializable {
response = client.execute(host, method); response = client.execute(host, method);
entity = response.getEntity(); entity = response.getEntity();
root = Jsoup.parse(entity.getContent(), root = Jsoup.parse(entity.getContent(),
charset.name(), Consts.UTF_8.name(),
weixin.getString("base")); weixin.getString("base"));
} else { } else {
weixin.put("code", "-100"); weixin.put("code", "-100");
@ -495,13 +494,13 @@ public class WeixinExecutor implements Serializable {
.add(new BasicNameValuePair("callback_encrypt_mode", "0")); .add(new BasicNameValuePair("callback_encrypt_mode", "0"));
parameters.add(new BasicNameValuePair("operation_seq", RandomUtil parameters.add(new BasicNameValuePair("operation_seq", RandomUtil
.generateStringByNumberChar(9))); .generateStringByNumberChar(9)));
method.setEntity(new UrlEncodedFormEntity(parameters, charset)); method.setEntity(new UrlEncodedFormEntity(parameters, Consts.UTF_8));
method.addHeader("Referer", weixin.getString("developerModifyUrl")); method.addHeader("Referer", weixin.getString("developerModifyUrl"));
HttpResponse response = client.execute(host, method); HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(), charset.name(), Document root = Jsoup.parse(entity.getContent(),
weixin.getString("base")); Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine(); StatusLine line = response.getStatusLine();
logger.info("step3_setting--->status={},body=\n{}", line, logger.info("step3_setting--->status={},body=\n{}", line,
root.toString()); root.toString());
@ -565,13 +564,13 @@ public class WeixinExecutor implements Serializable {
.currentTimeMillis() + "")); .currentTimeMillis() + ""));
method.setEntity(new UrlEncodedFormEntity(parameters, method.setEntity(new UrlEncodedFormEntity(parameters,
charset)); Consts.UTF_8));
method.setURI(URI.create(weixin.getString("start"))); method.setURI(URI.create(weixin.getString("start")));
response = client.execute(host, method); response = client.execute(host, method);
entity = response.getEntity(); entity = response.getEntity();
root = Jsoup.parse(entity.getContent(), charset.name(), root = Jsoup.parse(entity.getContent(),
weixin.getString("base")); Consts.UTF_8.name(), weixin.getString("base"));
line = response.getStatusLine(); line = response.getStatusLine();
logger.info("step3_setting--->status={},body=\n{}", logger.info("step3_setting--->status={},body=\n{}",
line, root.toString()); line, root.toString());
@ -624,13 +623,14 @@ public class WeixinExecutor implements Serializable {
parameters.add(new BasicNameValuePair("lang", "zh_CN")); parameters.add(new BasicNameValuePair("lang", "zh_CN"));
parameters.add(new BasicNameValuePair("random", System parameters.add(new BasicNameValuePair("random", System
.currentTimeMillis() + "")); .currentTimeMillis() + ""));
method.setEntity(new UrlEncodedFormEntity(parameters, charset)); method.setEntity(new UrlEncodedFormEntity(parameters,
Consts.UTF_8));
method.addHeader("Referer", weixin.getString("developerUrl")); method.addHeader("Referer", weixin.getString("developerUrl"));
HttpResponse response = client.execute(host, method); HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(), Document root = Jsoup.parse(entity.getContent(),
charset.name(), weixin.getString("base")); Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine(); StatusLine line = response.getStatusLine();
logger.info("step4_back--->status={},body=\n{}", line, logger.info("step4_back--->status={},body=\n{}", line,
root.toString()); root.toString());

View File

@ -2,6 +2,7 @@ package com.foxinmy.weixin4j.mp.type;
/** /**
* 对账单类型 * 对账单类型
*
* @className BillType * @className BillType
* @author jy * @author jy
* @date 2014年10月31日 * @date 2014年10月31日
@ -9,5 +10,13 @@ package com.foxinmy.weixin4j.mp.type;
* @see * @see
*/ */
public enum BillType { public enum BillType {
ALL, SUCCESS, REFUND ALL(0), SUCCESS(1), REFUND(2);
private int val;
BillType(int val) {
this.val = val;
}
public int getVal() {
return val;
}
} }

View File

@ -11,5 +11,8 @@ package com.foxinmy.weixin4j.mp.type;
*/ */
public enum RefundChannel { public enum RefundChannel {
ORIGINAL, // 原路退款 ORIGINAL, // 原路退款
BALANCE;// 退回到余额 BALANCE,// 退回到余额
TENPAY, // 财付通
BANK; // 银行
} }

View File

@ -14,7 +14,6 @@ public enum RefundStatus {
FAIL, // 退款失败 FAIL, // 退款失败
PROCESSING, // 退款处理中 PROCESSING, // 退款处理中
NOTSURE, // 未确定,需要商户 原退款单号重新发起 NOTSURE, // 未确定,需要商户 原退款单号重新发起
// 转入代发,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干 CHANGE; // 转入代发,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,资金回流到商户的现金帐号,需要商户人工干预,通过线下或者财付通转
// ,通过线下或者财付通转 账的方式进行退款 // 账的方式进行退款
CHANGE;
} }

View File

@ -0,0 +1,61 @@
package com.foxinmy.weixin4j.mp.type;
/**
* 垂直服务协议
*
* @className SemCategory
* @author jy
* @date 2014年11月7日
* @since JDK 1.7
* @see
*/
public enum SemCategory {
// 生活类
restaurant("生活类", "餐馆", "查询餐馆的服务,例如:中关村附近的面馆"), map("生活类", "地图",
"查询地图服务,例如:从银科大厦到天坛公园怎么走"), nearby("生活类", "周边",
"查询周边的服务,例如:我想去打保龄球"), coupon("生活类", "优惠券/团购",
"查询优惠券/团购的服务,例如:“附 近有什么优惠券”;如果查已有类别 的优惠券,比如:“附近有什么酒店 优惠券”,那么就会优先是酒店类。"),
// 旅行类
hotel("旅行类", "酒店", "查询酒店服务,例如:查一下中关村附近有没有七天酒店"), travel("旅行类", "旅游",
"查询旅游服务,例如:故宫门票多少钱"), flight("旅行类", "航班", "查询航班的服务,例如:明天从北京到上海的机票"), train(
"旅行类", "火车", "查询火车服务,例如:查一下从北京到西安的火车"),
// 娱乐类
movie("娱乐类", "上映电影", "查询上映电影的服务,例如:最近有什么好看的电影"), music("娱乐类", "音乐",
"查询音乐的服务,例如:来点刘德华的歌"), video("娱乐类", "视频", "查询视频服务,例如:我想看甄嬛传"), novel(
"娱乐类", "小说", "查询小说的服务,例如:来点言情小说看看"),
// 工具类
weather("工具类", "天气", "查询天气的服务,例如:明天北京天气"), stock("工具类", "股票",
"查询股票的服务,例如:腾讯股价多少了"), remind("工具类", "提醒", "提醒服务,例如:提醒我明天上午十点开会"), telephone(
"工具类", "常用电影", "查询常用电话号码服务,例如:查询一下招行信用卡的电话"),
// 知识类
cookbook("知识类", "菜谱", "查询菜谱服务,例如:宫保鸡丁怎么做"), baike("知识类", "百科",
"查询百科服务,例如:查一下刘德华的百科资料"), news("知识类", "资讯", "查询新闻服务,例如:今天有什么新闻"),
// 其他类
tv("其他类", "电视节目预告", "查询电视节目服务,例如:湖南台今晚有什么节目"), instruction("其他类", "通用指令",
"通用指令服务,例如:把声音调高一点"), tv_instruction("其他类", "电视指令",
"电视指令服务,例如:切换到中央五台"), car_instruction("其他类", "车载指令",
"车载指令服务,例如:把空调设为 25 度"), app("其他类", "应用", "查询应用服务,例如:打开愤怒的小鸟"), website(
"其他类", "网址", "查询网址服务,例如:帮我打开腾讯网"), search("其他类", "网页搜索",
"网页搜索服务,例如:百度一下意大利对乌拉圭");
SemCategory(String main, String desc, String remark) {
this.main = main;
this.desc = desc;
this.remark = remark;
}
private String main;
private String desc;
private String remark;
public String getMain() {
return main;
}
public String getDesc() {
return desc;
}
public String getRemark() {
return remark;
}
}

View File

@ -15,4 +15,4 @@ media_path=/tmp/weixin/media
# \u5bf9\u8d26\u5355\u4fdd\u5b58\u8def\u5f84 # \u5bf9\u8d26\u5355\u4fdd\u5b58\u8def\u5f84
bill_path=/tmp/weixin/bill bill_path=/tmp/weixin/bill
# ca\u8bc1\u4e66\u5b58\u653e\u7684\u5b8c\u6574\u8def\u5f84 # ca\u8bc1\u4e66\u5b58\u653e\u7684\u5b8c\u6574\u8def\u5f84
ca_file=/tmp/weixin/xxxxx.p12 ca_file=/Users/jy/Downloads/1221928801.pfx

View File

@ -0,0 +1,27 @@
package com.foxinmy.weixin4j.mp.test;
import org.junit.Before;
import org.junit.Test;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.mp.api.HelperApi;
import com.foxinmy.weixin4j.mp.model.SemQuery;
import com.foxinmy.weixin4j.mp.type.SemCategory;
public class SemanticTest extends TokenTest {
private HelperApi helperApi;
@Before
public void init() {
helperApi = new HelperApi(tokenHolder);
}
@Test
public void testApi() throws WeixinException {
SemQuery query = SemQuery.build("附近有啥 100 元以内的保龄球馆");
query.city("北京").category(SemCategory.nearby);
query.uid("opKwyt6IhrqPmTTZshyqH5W9gIVo");
System.out.println(query);
System.out.println(helperApi.semantic(query));
}
}