大重构:weixin4j精简为weixin4j-base、weixin4j-mp、weixin4j-qy、weixin4j-server四个子工程。

This commit is contained in:
jinyu 2015-04-27 21:00:33 +08:00
parent e89e988c62
commit 69c9bdffa4
349 changed files with 450 additions and 27394 deletions

View File

@ -196,7 +196,7 @@
* 2015-03-25
+ **weixin4j-mp**: 根据《微信商户平台文档》修缮[Pay3Api](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java)类
+ **weixin4j-mp**: 根据《微信商户平台文档》修缮[Pay3Api](./weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java)类
+ **weixin4j-mp**: 新增客服创建、关闭、转接会话事件
@ -210,27 +210,27 @@
+ **weixin4j-mp**: 单行注释调整为多行文档注释
+ **weixin4j-mp**: 新增[CouponApi](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/CouponApi.java)代金券接口
+ **weixin4j-mp**: 新增[CouponApi](./weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CouponApi.java)代金券接口
+ **weixin4j-qy**: 单行注释调整为多行文档注释
* 2015-04-01
+ **weixin4j-mp**: 新增[CashApi](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/CashApi.java)发红包、企业付款接口
+ **weixin4j-mp**: 新增[CashApi](./weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CashApi.java)发红包、企业付款接口
+ **weixin4j-qy**: 新增[BatchApi](./weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/BatchApi.java)批量异步执行任务接口
+ **weixin4j-qy**: 新增[BatchApi](./weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/BatchApi.java)批量异步执行任务接口
+ **weixin4j-qy**: <font color="red">DepartApi命名为[PartyApi](./weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/PartyApi.java)</font>
* 2015-04-04
+ **weixin4j-qy**: [MediaApi](./weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/MediaApi.java)新增批量上传成员和部门接口
+ **weixin4j-qy**: [MediaApi](./weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/MediaApi.java)新增批量上传成员和部门接口
+ <font color="red">released 1.3</font>
* 2015-04-09
+ **weixin4j-qy**: [AgentApi](./weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/AgentApi.java)新增获取应用列表概况接口
+ **weixin4j-qy**: [AgentApi](./weixin4j-qy/src/main/java/com/foxinmy/weixin4j/qy/api/AgentApi.java)新增获取应用列表概况接口
* 2015-04-13
@ -244,12 +244,19 @@
* 2015-04-16
+ **weixin4j-mp**: <font color="red">调整[二维码参数](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/QRParameter.java)类</font>
+ **weixin4j-mp**: <font color="red">调整[二维码参数](./weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/model/QRParameter.java)类</font>
+ **weixin4j-mp**: 新增获取[自定义菜单配置、自动回复配置](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java)接口
+ **weixin4j-mp**: 新增获取[自定义菜单配置、自动回复配置](./weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java)接口
* 2015-04-18
+ **weixin4j-mp**: <font color="red">调整[客服接口](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java)类的方法名</font>
+ **weixin4j-mp**: <font color="red">调整[客服接口](./weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java)类的方法名</font>
+ **weixin4j-mp**: <font color="red">在[二维码接口](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/QRApi.java)类新增获取二维码url方法</font>
+ **weixin4j-mp**: <font color="red">在[二维码接口](./weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/QRApi.java)类新增获取二维码url方法</font>
* 2015-04-19
+ <font color="red">调整聚合方式,去除原先的weixin4j-mp和weixin4j-qy模块,相应的api模块直接继承weixin4j父模块</font>
+ **weixin4j-base**: <font color="red">删除ActionMapping相关类</font>

View File

@ -11,13 +11,13 @@ weixin4j
`公众平台API封装`
`微信支付(刷卡/扫码/公众号)`
`netty服务器&消息分发`
* **weixin4j-qy**
`企业号API封装`
* **weixin4j-server**
`netty服务器&消息分发`
项目说明
@ -58,13 +58,11 @@ https://github.com/foxinmy/weixin4j/releases
###3.从源码打包
`git clone`&`mvn package -Prelease`,到相应的target目录下将`weixin4j-[mp|qy]-full`包或者`weixin4j-base``weixin4j-[mp|qy]-api`引入到自己的工程.
`git clone`&`mvn package -Prelease`,到相应的target目录下将`weixin4j-[mp|qy]-full`包或者`weixin4j-base``weixin4j-[mp|qy]`引入到自己的工程.
如何获取netty部分
---------------
netty的代码没有放到maven中心仓库,也没什么意义,因为最终需要自己去实现具体的业务逻辑,
下载winxin4j-[mp|qy]-server项目的源代码复制到自己的工程内,当然也可以在上面直接开发.
正在构思中...
[更新LOG](./CHANGE.md)
----------------------

View File

@ -43,6 +43,7 @@
<module>weixin4j-base</module>
<module>weixin4j-mp</module>
<module>weixin4j-qy</module>
<module>weixin4j-server</module>
</modules>
<properties>
<jdk.version>1.7</jdk.version>

View File

@ -11,52 +11,5 @@ weixin4j-base
`通用消息实体`
更新LOG
-------
* 2014-10-31
+ `TokenApi`重命名为`TokenHolder`
+ 新增`WeixinConfig`等类
* 2014-11-06
+ 删除`WeixinConfig`类只保留`WeixinAccount`
* 2014-11-15
+ 新增`aes加密解密`函数
* 2014-11-19
+ 新增`WeixinQyAccount`企业号账号信息类
* 2014-11-23
+ 新增企业号消息体以及用`Responseable`,`Notifyable`,`Massable`三个接口标记不同的可接受的消息类型
* 2014-11-24
+ 将Action跟Mapping基础类并入到项目
* 2015-01-04
+ ConfigUtil类新增获取classpath目录下的资源路径的方法
* 2015-01-10
+ 重构token实现机制
+ 新增JSTICKET支持
* 2015-03-29
+ 单行注释调整为多行文档注释
* 2015-04-01
+ 新增异步消息事件[BatchjobresultMessage](./src/main/java/com/foxinmy/weixin4j/msg/event/BatchjobresultMessage.java)
* 2015-04-13
+ 删除WeixinTokenCreator与WeixinJSTicketCreator类
[更新LOG](./CHANGE.md)
---------------------

View File

@ -1,46 +0,0 @@
package com.foxinmy.weixin4j.action;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import org.dom4j.DocumentException;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.response.ResponseMessage;
import com.foxinmy.weixin4j.util.MessageUtil;
import com.foxinmy.weixin4j.xml.XmlStream;
/**
* 继承的类需实现execute(M inMessage)
*
* @className AbstractAction
* @author jy
* @date 2014年10月12日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.action.WeixinAction
*/
@SuppressWarnings("unchecked")
public abstract class AbstractAction<M extends BaseMsg> implements WeixinAction {
public abstract ResponseMessage execute(M inMessage);
@Override
public ResponseMessage execute(String msg) throws DocumentException {
BaseMsg message = MessageUtil.xml2msg(msg);
if (message == null) {
return execute(XmlStream.get(msg, getGenericType()));
}
return execute((M) message);
}
private Class<M> getGenericType() {
Class<M> clazz = null;
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType ptype = ((ParameterizedType) type);
Type[] args = ptype.getActualTypeArguments();
clazz = (Class<M>) args[0];
}
return clazz;
}
}

View File

@ -1,21 +0,0 @@
package com.foxinmy.weixin4j.action;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.response.ResponseMessage;
/**
* 回复一个空字符串 而不是一个XML结构体中content字段的内容为空
*
* @className BlankAction
* @author jy.hu
* @date 2014年10月2日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.action.AbstractAction
*/
public class BlankAction<M extends BaseMsg> extends AbstractAction<M> {
@Override
public ResponseMessage execute(M inMessage) {
return null;
}
}

View File

@ -1,22 +0,0 @@
package com.foxinmy.weixin4j.action;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.msg.model.Text;
import com.foxinmy.weixin4j.response.ResponseMessage;
/**
* 调试输出用户消息
*
* @className DebugAction
* @author jy
* @date 2014年10月8日
* @since JDK 1.7
* @see
*/
public class DebugAction<M extends BaseMsg> extends AbstractAction<M> {
@Override
public ResponseMessage execute(M message) {
return new ResponseMessage(new Text(message.toString()), message);
}
}

View File

@ -1,3 +0,0 @@
消息处理接口,与weixin4j-*-server配合使用
如果单纯只使用API包,则可以不关注

View File

@ -1,20 +0,0 @@
package com.foxinmy.weixin4j.action;
import org.dom4j.DocumentException;
import com.foxinmy.weixin4j.response.ResponseMessage;
/**
* 消息处理接口
*
* @className Action
* @author jy.hu
* @date 2014年10月2日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.action.AbstractAction
* @see com.foxinmy.weixin4j.action.BlankAction
* @see com.foxinmy.weixin4j.action.DebugAction
*/
public interface WeixinAction {
public ResponseMessage execute(String inMsg) throws DocumentException;
}

View File

@ -1,31 +0,0 @@
package com.foxinmy.weixin4j.action.mapping;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import com.foxinmy.weixin4j.type.MessageType;
/**
* 获取默认的Mapping Key 如text,event_click
*
* @className AbstractActionMapping
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
public abstract class AbstractActionMapping implements ActionMapping {
protected final static String DECOLLATOR = ":";
protected String getMappingKey(String xmlMsg) throws DocumentException {
Document doc = DocumentHelper.parseText(xmlMsg);
String msgType = doc.selectSingleNode("/xml/MsgType").getStringValue();
if (msgType.equalsIgnoreCase(MessageType.event.name())) {
msgType += DECOLLATOR
+ doc.selectSingleNode("/xml/Event").getStringValue();
}
return msgType.toLowerCase();
}
}

View File

@ -1,26 +0,0 @@
package com.foxinmy.weixin4j.action.mapping;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.type.MessageType;
/**
* 标注Action类来处理消息请求
* @className Action
* @author jy
* @date 2014年10月12日
* @since JDK 1.7
* @see
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionAnnotation {
MessageType msgType();
EventType[] eventType() default {};
}

View File

@ -1,18 +0,0 @@
package com.foxinmy.weixin4j.action.mapping;
import org.dom4j.DocumentException;
import com.foxinmy.weixin4j.action.WeixinAction;
/**
* 可扩展的Mapping接口
*
* @className ActionMapping
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
public interface ActionMapping {
public WeixinAction getAction(String xmlMsg) throws DocumentException;
}

View File

@ -1,58 +0,0 @@
package com.foxinmy.weixin4j.action.mapping;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.dom4j.DocumentException;
import com.foxinmy.weixin4j.action.WeixinAction;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.type.MessageType;
import com.foxinmy.weixin4j.util.ClassUtil;
/**
* 注解实现的Mapping
*
* @className AnnotationActionMapping
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.action.mapping.ActionAnnotation
*/
public class AnnotationActionMapping extends AbstractActionMapping {
private final Map<String, WeixinAction> actionMap;
public AnnotationActionMapping(Package actionPackage) {
actionMap = new HashMap<String, WeixinAction>();
Set<Class<?>> weixinActions = ClassUtil.getClasses(actionPackage);
for (Class<?> clazz : weixinActions) {
ActionAnnotation action = clazz
.getAnnotation(ActionAnnotation.class);
if (action == null) {
continue;
}
WeixinAction weixinAction = null;
try {
weixinAction = (WeixinAction) clazz.newInstance();
} catch (Exception e) {
continue;
}
MessageType msgType = action.msgType();
EventType[] eventTypes = action.eventType();
if (eventTypes != null && eventTypes.length > 0) {
for (EventType e : eventTypes) {
actionMap.put((msgType.name() + DECOLLATOR + e.name())
.toLowerCase(), weixinAction);
}
continue;
}
actionMap.put(msgType.name().toLowerCase(), weixinAction);
}
}
public WeixinAction getAction(String xmlMsg) throws DocumentException {
String key = getMappingKey(xmlMsg);
return actionMap.get(key);
}
}

View File

@ -1,7 +0,0 @@
消息处理与Action类的mapping对应(使用注解类的方式)
一般来说Action中应该有自己的实际业务处理类,那么上述方式可能不妥
推荐用org.springframework.context.ApplicationContext#getBeansWithAnnotation函数
当然,也可以重写AbstractActionMapping类实现自己的Mapping

View File

@ -297,6 +297,26 @@
<code>40074</code>
<text>news消息不支持指定为高保密消息</text>
</error>
<error>
<code>40117</code>
<text>分组名字不合法</text>
</error>
<error>
<code>40118</code>
<text>media_id大小不合法</text>
</error>
<error>
<code>40119</code>
<text>button类型错误</text>
</error>
<error>
<code>40120</code>
<text>button类型错误</text>
</error>
<error>
<code>40121</code>
<text>不合法的media_id类型</text>
</error>
<error>
<code>41001</code>
<desc>access_token missing</desc>

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.model;
package com.foxinmy.weixin4j.message;
import java.io.Serializable;
@ -7,12 +7,12 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 消息基类
*
* @className BaseMsg
* @className BaseMessage
* @author jy.hu
* @date 2014年4月6日
* @since JDK 1.7
*/
public class BaseMsg implements Serializable {
public class BaseMessage implements Serializable {
private static final long serialVersionUID = 7761192742840031607L;
@ -49,19 +49,19 @@ public class BaseMsg implements Serializable {
@XStreamAlias("AgentID")
private String agentId;
public BaseMsg() {
public BaseMessage() {
}
public BaseMsg(String msgType) {
public BaseMessage(String msgType) {
this.msgType = msgType;
}
public BaseMsg(String toUserName, String fromUserName) {
public BaseMessage(String toUserName, String fromUserName) {
this(null, toUserName, fromUserName);
}
public BaseMsg(String msgType, String toUserName, String fromUserName) {
public BaseMessage(String msgType, String toUserName, String fromUserName) {
this.msgType = msgType;
this.toUserName = toUserName;
this.fromUserName = fromUserName;
@ -124,8 +124,9 @@ public class BaseMsg implements Serializable {
@Override
public boolean equals(Object obj) {
if (obj instanceof BaseMsg) {
return ((BaseMsg) obj).getMsgId() == msgId;
if (obj instanceof BaseMessage) {
return ((BaseMessage) obj).getMsgId() == msgId
&& ((BaseMessage) obj).getCreateTime() == createTime;
}
return false;
}

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg;
package com.foxinmy.weixin4j.message;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.type.MessageType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -16,7 +15,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#image.E6.B6.88.E6.81.AF">企业号的图片消息</a>
*/
public class ImageMessage extends BaseMsg {
public class ImageMessage extends BaseMessage {
private static final long serialVersionUID = 8430800898756567016L;

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg;
package com.foxinmy.weixin4j.message;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.type.MessageType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -14,7 +13,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see <a
* href="http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html#.E9.93.BE.E6.8E.A5.E6.B6.88.E6.81.AF">订阅号服务号的链接消息</a>
*/
public class LinkMessage extends BaseMsg {
public class LinkMessage extends BaseMessage {
private static final long serialVersionUID = 754952745115497030L;

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg;
package com.foxinmy.weixin4j.message;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.type.MessageType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -16,7 +15,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#location.E6.B6.88.E6.81.AF">企业号的地理位置消息</a>
*/
public class LocationMessage extends BaseMsg {
public class LocationMessage extends BaseMessage {
private static final long serialVersionUID = 2866021596599237334L;

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg;
package com.foxinmy.weixin4j.message;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.type.MessageType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -16,7 +15,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#text.E6.B6.88.E6.81.AF">企业号的文本消息</a>
*/
public class TextMessage extends BaseMsg {
public class TextMessage extends BaseMessage {
private static final long serialVersionUID = -7018053906644190260L;

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg;
package com.foxinmy.weixin4j.message;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.type.MessageType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -16,7 +15,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#video.E6.B6.88.E6.81.AF">企业号的视频消息</a>
*/
public class VideoMessage extends BaseMsg {
public class VideoMessage extends BaseMessage {
private static final long serialVersionUID = -1013075358679078381L;

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg;
package com.foxinmy.weixin4j.message;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.type.MessageType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -19,7 +18,7 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF#voice.E6.B6.88.E6.81.AF">企业号的语音消息</a>
*/
public class VoiceMessage extends BaseMsg {
public class VoiceMessage extends BaseMessage {
private static final long serialVersionUID = -7988380977182214003L;

View File

@ -1,7 +1,6 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.message.event;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.message.BaseMessage;
import com.foxinmy.weixin4j.type.MessageType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -17,11 +16,11 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E4%BA%8B%E4%BB%B6">企业号的事件消息</a>
*/
public class EventMessage extends BaseMsg {
public class EventMessage extends BaseMessage {
private static final long serialVersionUID = 7703667223814088865L;
public EventMessage(EventType eventType) {
public EventMessage(String eventType) {
super(MessageType.event.name());
this.eventType = eventType;
}
@ -32,9 +31,9 @@ public class EventMessage extends BaseMsg {
* @see com.foxinmy.weixin4j.type.EventType
*/
@XStreamAlias("Event")
private EventType eventType;
private String eventType;
public EventType getEventType() {
public String getEventType() {
return eventType;
}

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.message.event;
import com.foxinmy.weixin4j.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -20,7 +20,7 @@ public class LocationEventMessage extends EventMessage {
private static final long serialVersionUID = -2030716800669824861L;
public LocationEventMessage() {
super(EventType.location);
super(EventType.location.name());
}
/**
* 地理位置纬度

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg.event.menu;
package com.foxinmy.weixin4j.message.event;
import com.foxinmy.weixin4j.msg.event.EventMessage;
import com.foxinmy.weixin4j.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -21,11 +20,11 @@ public class MenuEventMessage extends EventMessage {
private static final long serialVersionUID = -1049672447995366063L;
public MenuEventMessage() {
super(EventType.click);
super(EventType.click.name());
}
public MenuEventMessage(EventType eventType) {
super(eventType);
super(eventType.name());
}
/**

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.msg.event.menu;
package com.foxinmy.weixin4j.message.event;
import java.io.Serializable;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.msg.event.menu;
package com.foxinmy.weixin4j.message.event;
import java.io.Serializable;
import java.util.List;

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.msg.event.menu;
package com.foxinmy.weixin4j.message.event;
import java.io.Serializable;

View File

@ -1,3 +1,3 @@
菜单消息
菜单事件消息
用户点击自定义菜单后微信会把点击事件推送给开发者请注意点击菜单弹出子菜单不会产生上报。请注意第3个到第8个的所有事件仅支持微信iPhone5.4.1以上版本和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。

View File

@ -25,10 +25,6 @@ public final class Consts {
public static final String PROTOCOL_FILE = "file";
public static final String PROTOCOL_JAR = "jar";
/**
* oauth验证url
*/
public static final String OAUTH_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect";
/**
* 公众平台获取token的url
*/

View File

@ -1,12 +1,6 @@
package com.foxinmy.weixin4j.model;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.apache.commons.lang3.StringUtils;
import com.foxinmy.weixin4j.type.AccountType;
/**
* 微信账号信息
@ -17,7 +11,7 @@ import com.foxinmy.weixin4j.type.AccountType;
* @since JDK 1.7
* @see
*/
public abstract class WeixinAccount implements Serializable {
public class WeixinAccount implements Serializable {
private static final long serialVersionUID = -6001008896414323534L;
/**
@ -33,11 +27,6 @@ public abstract class WeixinAccount implements Serializable {
* 安全模式下的加密密钥
*/
private String encodingAesKey;
/**
* 账号类型
* @return
*/
public abstract AccountType getAccountType();
public WeixinAccount() {
}
@ -79,39 +68,6 @@ public abstract class WeixinAccount implements Serializable {
this.encodingAesKey = encodingAesKey;
}
/**
* 拼接授权URL
*
* @param redirectUri
* 授权后重定向的回调链接地址
* @param scope
* 应用授权作用域snsapi_base
* 不弹出授权页面直接跳转只能获取用户openidsnsapi_userinfo
* 弹出授权页面可通过openid拿到昵称性别所在地并且即使在未关注的情况下只要用户授权也能获取其信息
* @param state
* 重定向后会带上state参数开发者可以填写a-zA-Z0-9的参数值
* @return 授权URL
*/
public String getOauthAuthorizeUrl(String redirectUri, String scope,
String state) {
if (StringUtils.isBlank(scope)) {
scope = "snsapi_base";
}
if (StringUtils.isBlank(state)) {
state = "STATE";
}
try {
return String.format(
Consts.OAUTH_AUTHORIZE_URL,
URLEncoder.encode(redirectUri,
org.apache.http.Consts.UTF_8.name()), id, scope,
state);
} catch (UnsupportedEncodingException ignore) {
;
}
return "";
}
@Override
public String toString() {
return "id=" + id + ", secret=" + secret + ", token=" + token

View File

@ -1,10 +0,0 @@
事件消息
-------
当普通微信用户向公众账号发消息时微信服务器将POST消息的XML数据包到开发者填写的URL上。各消息类型的推送XML数据包结构如下
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
关于重试的消息排重推荐使用msgid排重。
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。

View File

@ -1,41 +0,0 @@
package com.foxinmy.weixin4j.msg.model;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* 消息对象基类
*
* @className Base
* @author jy
* @date 2015年3月21日
* @since JDK 1.7
* @see
*/
public class Base implements Serializable {
private static final long serialVersionUID = 8487251213352068227L;
/**
* 媒体类型
*/
@JSONField(serialize = false)
@XStreamOmitField
private MediaType mediaType;
public Base(MediaType mediaType) {
this.mediaType = mediaType;
}
public MediaType getMediaType() {
return mediaType;
}
@Override
public String toString() {
return "Base [mediaType=" + mediaType + "]";
}
}

View File

@ -1,18 +0,0 @@
package com.foxinmy.weixin4j.msg.model;
/**
* 标记群发消息
*
* @className Massable
* @author jy
* @date 2014年11月22日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.msg.model.Text
* @see com.foxinmy.weixin4j.msg.model.Image
* @see com.foxinmy.weixin4j.msg.model.Voice
* @see com.foxinmy.weixin4j.msg.model.MpVideo
* @see com.foxinmy.weixin4j.msg.model.MpNews
*/
public interface Massable {
}

View File

@ -1,19 +0,0 @@
package com.foxinmy.weixin4j.msg.model;
/**
* 标记客服消息
*
* @className Notifyable
* @author jy
* @date 2014年11月22日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.msg.model.Text
* @see com.foxinmy.weixin4j.msg.model.Image
* @see com.foxinmy.weixin4j.msg.model.Voice
* @see com.foxinmy.weixin4j.msg.model.Video
* @see com.foxinmy.weixin4j.msg.model.Music
* @see com.foxinmy.weixin4j.msg.model.News
*/
public interface Notifyable {
}

View File

@ -1,20 +0,0 @@
package com.foxinmy.weixin4j.msg.model;
/**
* 标记被动消息
*
* @className Responseable
* @author jy
* @date 2014年11月22日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.msg.model.Text
* @see com.foxinmy.weixin4j.msg.model.Image
* @see com.foxinmy.weixin4j.msg.model.Voice
* @see com.foxinmy.weixin4j.msg.model.Video
* @see com.foxinmy.weixin4j.msg.model.Music
* @see com.foxinmy.weixin4j.msg.model.News
* @see com.foxinmy.weixin4j.msg.model.Trans
*/
public interface Responseable {
}

View File

@ -1,122 +0,0 @@
package com.foxinmy.weixin4j.response;
import org.apache.commons.lang3.StringUtils;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.msg.model.Article;
import com.foxinmy.weixin4j.msg.model.Base;
import com.foxinmy.weixin4j.msg.model.News;
import com.foxinmy.weixin4j.msg.model.Responseable;
import com.foxinmy.weixin4j.util.ClassUtil;
import com.foxinmy.weixin4j.xml.TextConverter;
import com.foxinmy.weixin4j.xml.XmlStream;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 被动消息
* <p>
* <font color="red">回复图片等多媒体消息时需要预先上传多媒体文件到微信服务器,
* 假如服务器无法保证在五秒内处理并回复可以直接回复空串微信服务器不会对此作任何处理并且不会发起重试</font>
* </p>
*
* @className ResponseMessage
* @author jy.hu
* @date 2014年11月22日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.msg.model.Text
* @see com.foxinmy.weixin4j.msg.model.Image
* @see com.foxinmy.weixin4j.msg.model.Voice
* @see com.foxinmy.weixin4j.msg.model.Video
* @see com.foxinmy.weixin4j.msg.model.Music
* @see com.foxinmy.weixin4j.msg.model.News
* @see com.foxinmy.weixin4j.msg.model.Trans
* @see <a href=
* "http://mp.weixin.qq.com/wiki/9/2c15b20a16019ae613d413e30cac8ea1.html"
* >订阅号服务号的被动响应消息</a>
* @see <a
* href="http://qydev.weixin.qq.com/wiki/index.php?title=%E8%A2%AB%E5%8A%A8%E5%93%8D%E5%BA%94%E6%B6%88%E6%81%AF">企业号的被动响应消息</a>
*/
@XStreamAlias("xml")
public class ResponseMessage extends BaseMsg {
private static final long serialVersionUID = 7761192742840031607L;
protected final static XmlStream xmlStream = XmlStream.get();
static {
Class<?>[] classes = ClassUtil.getClasses(Base.class.getPackage())
.toArray(new Class[0]);
xmlStream.autodetectAnnotations(true);
xmlStream.processAnnotations(classes);
xmlStream.processAnnotations(ResponseMessage.class);
xmlStream.registerConverter(new TextConverter());
xmlStream.omitField(BaseMsg.class, "msgId");
xmlStream.alias("item", Article.class);
xmlStream.addImplicitCollection(News.class, "articles");
xmlStream.aliasSystemAttribute(null, "class");
}
/**
* 附加数据
*/
private String attach;
/**
* 消息对象
*
* @see com.foxinmy.weixin4j.msg.model.Responseable
*/
private final Base box;
public ResponseMessage(Base box) {
super(box.getMediaType().name());
this.box = box;
}
public ResponseMessage(Base box, BaseMsg inMessage) {
this(box, inMessage.getFromUserName(), inMessage.getToUserName());
}
public ResponseMessage(Base box, String toUserName, String fromUserName) {
super(box.getMediaType().name(), toUserName, fromUserName);
this.box = box;
}
public String getAttach() {
return attach;
}
public Base getBox() {
return box;
}
/**
* 消息对象转换为微信服务器接受的xml格式消息 </br> <font
* color="color">需Responseable标识,否则返回空</font>
*
* @see com.foxinmy.weixin4j.msg.model.Responseable
* @return xml字符串
*/
public String toXml() {
// check responseable
if (!(box instanceof Responseable)) {
return "";
}
String boxAlias = StringUtils.capitalize(getMsgType());
XStreamAlias alias = box.getClass().getAnnotation(XStreamAlias.class);
if (alias != null) {
boxAlias = alias.value();
}
xmlStream.aliasField(boxAlias, ResponseMessage.class, "box");
if (box instanceof News) {
attach = Integer.toString(((News) box).getArticles().size());
xmlStream.aliasField("ArticleCount", ResponseMessage.class,
"attach");
}
return xmlStream.toXML(this);
}
@Override
public String toString() {
return "ResponseMessage [ " + super.toString() + ", attach=" + attach
+ ", box=" + box + "]";
}
}

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import java.io.Serializable;

View File

@ -1,7 +1,6 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -16,9 +15,15 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @since JDK 1.7
* @see
*/
public class File extends Base implements Notifyable {
public class File implements NotifyTuple {
private static final long serialVersionUID = -8149837316289636110L;
@Override
public String getMessageType() {
return "file";
}
/**
* 上传后的微信返回的媒体ID
*/
@ -27,7 +32,6 @@ public class File extends Base implements Notifyable {
private String mediaId;
public File(String mediaId) {
super(MediaType.file);
this.mediaId = mediaId;
}

View File

@ -1,7 +1,6 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -16,10 +15,15 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @since JDK 1.7
* @see
*/
public class Image extends Base implements Responseable, Notifyable, Massable {
public class Image implements ResponseTuple, MassTuple, NotifyTuple {
private static final long serialVersionUID = 6928681900960656161L;
@Override
public String getMessageType() {
return "image";
}
/**
* 上传后的微信返回的媒体ID
*/
@ -28,12 +32,6 @@ public class Image extends Base implements Responseable, Notifyable, Massable {
private String mediaId;
public Image(String mediaId) {
super(MediaType.image);
this.mediaId = mediaId;
}
public Image(MediaType mediaType, String mediaId) {
super(mediaType);
this.mediaId = mediaId;
}

View File

@ -0,0 +1,18 @@
package com.foxinmy.weixin4j.tuple;
/**
* 群发消息元件
*
* @className MassTuple
* @author jy
* @date 2014年11月22日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.tuple.Text
* @see com.foxinmy.weixin4j.tuple.Image
* @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.MpVideo
* @see com.foxinmy.weixin4j.tuple.MpNews
*/
public interface MassTuple extends Tuple {
}

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import java.io.Serializable;

View File

@ -1,10 +1,9 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
@ -20,10 +19,15 @@ import com.thoughtworks.xstream.annotations.XStreamOmitField;
* @since JDK 1.7
* @see
*/
public class MpNews extends Base implements Massable, Notifyable {
public class MpNews implements MassTuple, NotifyTuple {
private static final long serialVersionUID = 8853054484809101524L;
@Override
public String getMessageType() {
return "mpnews";
}
/**
* 上传图文列表后微信返回的媒体ID
*/
@ -42,7 +46,6 @@ public class MpNews extends Base implements Massable, Notifyable {
}
public MpNews(String mediaId) {
super(MediaType.mpnews);
this.mediaId = mediaId;
this.articles = new ArrayList<MpArticle>();
}
@ -88,7 +91,6 @@ public class MpNews extends Base implements Massable, Notifyable {
@Override
public String toString() {
return "MpNews [articles=" + articles + ", mediaId=" + mediaId
+ ", getMediaType()=" + getMediaType() + "]";
return "MpNews [articles=" + articles + ", mediaId=" + mediaId + "]";
}
}

View File

@ -1,7 +1,6 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -16,10 +15,15 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @since JDK 1.7
* @see
*/
public class MpVideo extends Base implements Massable {
public class MpVideo implements MassTuple {
private static final long serialVersionUID = 2167437425244069128L;
@Override
public String getMessageType() {
return "mpvideo";
}
/**
* 上传视频后微信返回的媒体ID
*/
@ -28,7 +32,6 @@ public class MpVideo extends Base implements Massable {
private String mediaId;
public MpVideo(String mediaId) {
super(MediaType.mpvideo);
this.mediaId = mediaId;
}

View File

@ -1,7 +1,6 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -16,10 +15,15 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @since JDK 1.7
* @see
*/
public class Music extends Base implements Responseable, Notifyable {
public class Music implements ResponseTuple, NotifyTuple {
private static final long serialVersionUID = -5952134916367253297L;
@Override
public String getMessageType() {
return "music";
}
/**
* 音乐标题
*/
@ -56,7 +60,6 @@ public class Music extends Base implements Responseable, Notifyable {
public Music(String title, String desc, String musicUrl, String hqMusicUrl,
String thumbMediaId) {
super(MediaType.music);
this.title = title;
this.desc = desc;
this.musicUrl = musicUrl;

View File

@ -1,10 +1,9 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -20,21 +19,27 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see
*/
@XStreamAlias("Articles")
public class News extends Base implements Responseable, Notifyable {
private static final int MAX_ARTICLE_COUNT = 10;
public class News implements ResponseTuple, NotifyTuple {
private static final long serialVersionUID = 3348756809039388415L;
@Override
public String getMessageType() {
return "news";
}
private static final int MAX_ARTICLE_COUNT = 10;
/**
* 图文列表
*
* @see com.foxinmy.weixin4j.msg.model.Article
* @see com.foxinmy.weixin4j.tuple.Article
*/
@JSONField(name = "articles")
@XStreamAlias("Articles")
private List<Article> articles;
public News() {
super(MediaType.news);
this.articles = new ArrayList<Article>();
}

View File

@ -0,0 +1,19 @@
package com.foxinmy.weixin4j.tuple;
/**
* 客服消息元件
*
* @className Notifyable
* @author jy
* @date 2014年11月22日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.tuple.Text
* @see com.foxinmy.weixin4j.tuple.Image
* @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.Video
* @see com.foxinmy.weixin4j.tuple.Music
* @see com.foxinmy.weixin4j.tuple.News
*/
public interface NotifyTuple extends Tuple {
}

View File

@ -0,0 +1,20 @@
package com.foxinmy.weixin4j.tuple;
/**
* 被动消息元件
*
* @className ResponseTuple
* @author jy
* @date 2014年11月22日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.tuple.Text
* @see com.foxinmy.weixin4j.tuple.Image
* @see com.foxinmy.weixin4j.tuple.Voice
* @see com.foxinmy.weixin4j.tuple.Video
* @see com.foxinmy.weixin4j.tuple.Music
* @see com.foxinmy.weixin4j.tuple.News
* @see com.foxinmy.weixin4j.tuple.Trans
*/
public interface ResponseTuple extends Tuple {
}

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -16,17 +15,21 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see
*/
@XStreamAlias("Content")
public class Text extends Base implements Responseable, Notifyable, Massable {
public class Text implements ResponseTuple, MassTuple, NotifyTuple {
private static final long serialVersionUID = 520050144519064503L;
@Override
public String getMessageType() {
return "text";
}
/**
* 内容
*/
private String content;
public Text(String content) {
super(MediaType.text);
this.content = content;
}

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -16,10 +15,15 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
* @see
*/
@XStreamAlias("TransInfo")
public class Trans extends Base implements Responseable {
public class Trans implements ResponseTuple {
private static final long serialVersionUID = -214711609286629729L;
@Override
public String getMessageType() {
return "transfer_customer_service";
}
/**
* 指定会话接入的客服账号
*/
@ -31,7 +35,6 @@ public class Trans extends Base implements Responseable {
}
public Trans(String kfAccount) {
super(MediaType.transfer_customer_service);
this.kfAccount = kfAccount;
}
@ -43,4 +46,5 @@ public class Trans extends Base implements Responseable {
public String toString() {
return "Trans [kfAccount=" + kfAccount + "]";
}
}

View File

@ -1,7 +1,6 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
@ -17,10 +16,15 @@ import com.thoughtworks.xstream.annotations.XStreamOmitField;
* @since JDK 1.7
* @see
*/
public class Video extends Base implements Responseable, Notifyable {
public class Video implements ResponseTuple, NotifyTuple {
private static final long serialVersionUID = 2167437425244069128L;
@Override
public String getMessageType() {
return "video";
}
/**
* 上传视频微信返回的媒体ID
*/
@ -46,7 +50,6 @@ public class Video extends Base implements Responseable, Notifyable {
private String desc;
public Video(String mediaId) {
super(MediaType.video);
this.mediaId = mediaId;
}
@ -59,7 +62,6 @@ public class Video extends Base implements Responseable, Notifyable {
}
public Video(String mediaId, String thumbMediaId, String title, String desc) {
super(MediaType.video);
this.mediaId = mediaId;
this.thumbMediaId = thumbMediaId;
this.title = title;
@ -101,7 +103,6 @@ public class Video extends Base implements Responseable, Notifyable {
@Override
public String toString() {
return "Video [thumbMediaId=" + thumbMediaId + ", title=" + title
+ ", desc=" + desc + ", mediaId=" + mediaId
+ ", getMediaType()=" + getMediaType() + "]";
+ ", desc=" + desc + ", mediaId=" + mediaId + "]";
}
}

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.msg.model;
package com.foxinmy.weixin4j.tuple;
import com.foxinmy.weixin4j.type.MediaType;
/**
* 语音对象
@ -14,11 +13,16 @@ import com.foxinmy.weixin4j.type.MediaType;
* @since JDK 1.7
* @see
*/
public class Voice extends Image implements Responseable, Notifyable, Massable {
public class Voice extends Image implements ResponseTuple, NotifyTuple {
private static final long serialVersionUID = 8853054484809101524L;
@Override
public String getMessageType() {
return "voice";
}
public Voice(String mediaId) {
super(MediaType.voice, mediaId);
super(mediaId);
}
}

View File

@ -1,22 +0,0 @@
package com.foxinmy.weixin4j.type;
/**
* 账号类型
*
* @className AccountType
* @author jy
* @date 2014年11月18日
* @since JDK 1.7
* @see
*/
public enum AccountType {
/**
* 公众号
*/
MP,
/**
* 企业号
*/
QY;
}

View File

@ -1,21 +1,5 @@
package com.foxinmy.weixin4j.type;
import com.foxinmy.weixin4j.msg.event.BatchjobresultMessage;
import com.foxinmy.weixin4j.msg.event.EnterAgentEventMessage;
import com.foxinmy.weixin4j.msg.event.EventMessage;
import com.foxinmy.weixin4j.msg.event.KfCloseEventMessage;
import com.foxinmy.weixin4j.msg.event.KfCreateEventMessage;
import com.foxinmy.weixin4j.msg.event.KfSwitchEventMessage;
import com.foxinmy.weixin4j.msg.event.LocationEventMessage;
import com.foxinmy.weixin4j.msg.event.MassEventMessage;
import com.foxinmy.weixin4j.msg.event.ScanEventMessage;
import com.foxinmy.weixin4j.msg.event.ScribeEventMessage;
import com.foxinmy.weixin4j.msg.event.TemplatesendjobfinishMessage;
import com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage;
import com.foxinmy.weixin4j.msg.event.menu.MenuLocationEventMessage;
import com.foxinmy.weixin4j.msg.event.menu.MenuPhotoEventMessage;
import com.foxinmy.weixin4j.msg.event.menu.MenuScanEventMessage;
/**
* 事件类型
*
@ -29,125 +13,65 @@ public enum EventType {
/**
* 关注事件
*
* @see com.foxinmy.weixin4j.msg.event.ScribeEventMessage
*/
subscribe(ScribeEventMessage.class),
subscribe,
/**
* 取消关注事件
*
* @see com.foxinmy.weixin4j.msg.event.ScribeEventMessage
*/
unsubscribe(ScribeEventMessage.class),
/**
* 二维码扫描事件
*
* @see com.foxinmy.weixin4j.msg.event.ScanEventMessage
*/
scan(ScanEventMessage.class),
unsubscribe,
/**
* 上报地理位置事件
*
* @see com.foxinmy.weixin4j.msg.event.LocationEventMessage
*/
location(LocationEventMessage.class),
location,
/**
* 菜单扫描事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuScanEventMessage
*/
scancode_push(MenuScanEventMessage.class),
scancode_push,
/**
* 菜单点击关键字事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage
*/
view(MenuEventMessage.class),
view,
/**
* 菜单点击链接事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage
*/
click(MenuEventMessage.class),
click,
/**
* 菜单扫描并调出等待界面事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuScanEventMessage
*/
scancode_waitmsg(MenuScanEventMessage.class),
scancode_waitmsg,
/**
* 菜单弹出拍照事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuPhotoEventMessage
*/
pic_sysphoto(MenuPhotoEventMessage.class),
pic_sysphoto,
/**
* 菜单弹出发图事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuPhotoEventMessage
*/
pic_photo_or_album(MenuPhotoEventMessage.class),
pic_photo_or_album,
/**
* 菜单弹出发图事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuPhotoEventMessage
*/
pic_weixin(MenuPhotoEventMessage.class),
pic_weixin,
/**
* 菜单发送地理位置事件
*
* @see com.foxinmy.weixin4j.msg.event.menu.MenuLocationEventMessage
*/
location_select(MenuLocationEventMessage.class),
/**
* 群发消息事件
*
* @see com.foxinmy.weixin4j.msg.event.MassEventMessage
*/
masssendjobfinish(MassEventMessage.class),
/**
* 模板消息事件
*
* @see com.foxinmy.weixin4j.msg.event.TemplatesendjobfinishMessage
*/
templatesendjobfinish(TemplatesendjobfinishMessage.class),
/**
* 进入企业号应用事件
*
* @see com.foxinmy.weixin4j.msg.event.EnterAgentEventMessage
*/
enter_agent(EnterAgentEventMessage.class),
/**
* 客服接入会话事件
*
* @see com.foxinmy.weixin4j.msg.event.KfCreateEventMessage
*/
kf_create_session(KfCreateEventMessage.class),
/**
* 客服关闭会话事件
*
* @see com.foxinmy.weixin4j.msg.event.KfCloseEventMessage
*/
kf_close_session(KfCloseEventMessage.class),
/**
* 客服转接会话事件
*
* @see com.foxinmy.weixin4j.msg.event.KfSwitchEventMessage
*/
kf_switch_session(KfSwitchEventMessage.class),
/**
* 异步任务完成事件
*
* @see com.foxinmy.weixin4j.msg.event.BatchjobresultMessage
*/
batch_job_result(BatchjobresultMessage.class);
private Class<? extends EventMessage> eventClass;
EventType(Class<? extends EventMessage> eventClass) {
this.eventClass = eventClass;
}
public Class<? extends EventMessage> getEventClass() {
return eventClass;
}
location_select;
}

View File

@ -3,17 +3,13 @@ package com.foxinmy.weixin4j.type;
/**
* 上传的媒体类型</br>
* <p>
* 公众平台上传限制:</br>
* 图片(image): 128K,支持JPG格式</br>
* 语音(voice):256K,播放长度不超过60s,支持AMR\MP3格式</br>
* 视频(video):1MB,支持MP4格式</br>
* 公众平台上传限制:</br> 图片(image): 128K,支持JPG格式</br>
* 语音(voice):256K,播放长度不超过60s,支持AMR\MP3格式</br> 视频(video):1MB,支持MP4格式</br>
* 缩略图(thumb):64KB,支持JPG格式</br>
* </p>
* <p>
* 企业号上传限制:</br>
* 图片image:1MB支持JPG格式</br>
* 语音voice2MB播放长度不超过60s支持AMR格式</br>
* 视频video10MB支持MP4格式</br>
* 企业号上传限制:</br> 图片image:1MB支持JPG格式</br>
* 语音voice2MB播放长度不超过60s支持AMR格式</br> 视频video10MB支持MP4格式</br>
* 普通文件file10MB</br>
* </p>
* <p>
@ -25,8 +21,7 @@ package com.foxinmy.weixin4j.type;
* @since JDK 1.7
*/
public enum MediaType {
image("jpg"), voice("amr/mp3"), video("mp4"), thumb("jpg"), file("unknown"), text(
""), music(""), news(""), mpnews(""), mpvideo(""), transfer_customer_service(
image("jpg"), voice("amr/mp3"), video("mp4"), thumb("jpg"), file("unknown"), news(
"");
MediaType(String formatName) {

View File

@ -1,13 +1,5 @@
package com.foxinmy.weixin4j.type;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.msg.ImageMessage;
import com.foxinmy.weixin4j.msg.LinkMessage;
import com.foxinmy.weixin4j.msg.LocationMessage;
import com.foxinmy.weixin4j.msg.TextMessage;
import com.foxinmy.weixin4j.msg.VideoMessage;
import com.foxinmy.weixin4j.msg.VoiceMessage;
import com.foxinmy.weixin4j.msg.event.EventMessage;
/**
*
@ -22,56 +14,47 @@ public enum MessageType {
*
* @see com.foxinmy.weixin4j.msg.TextMessage
*/
text(TextMessage.class),
text,
/**
* 图片消息
*
* @see com.foxinmy.weixin4j.msg.ImageMessage
*/
image(ImageMessage.class),
image,
/**
* 语音消息
*
* @see com.foxinmy.weixin4j.msg.VoiceMessage
*/
voice(VoiceMessage.class),
voice,
/**
* 视频消息
*
* @see com.foxinmy.weixin4j.msg.VideoMessage
*/
video(VideoMessage.class),
video,
/**
* 小视频消息
*
* @see com.foxinmy.weixin4j.msg.VideoMessage
*/
shortvideo(VideoMessage.class),
shortvideo,
/**
* 位置消息
*
* @see com.foxinmy.weixin4j.msg.LocationMessage
*/
location(LocationMessage.class),
location,
/**
* 链接消息
*
* @see com.foxinmy.weixin4j.msg.LinkMessage
*/
link(LinkMessage.class),
link,
/**
* 事件消息
*
* @see com.foxinmy.weixin4j.msg.event.EventMessage
*/
event(EventMessage.class);
private Class<? extends BaseMsg> messageClass;
MessageType(Class<? extends BaseMsg> messageClass) {
this.messageClass = messageClass;
}
public Class<? extends BaseMsg> getMessageClass() {
return messageClass;
}
event;
}

View File

@ -6,8 +6,6 @@ import java.util.Set;
import com.alibaba.fastjson.JSON;
import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.model.WeixinQyAccount;
/**
* 商户配置工具类
@ -62,16 +60,8 @@ public class ConfigUtil {
CLASSPATH_VALUE)).getPath();
}
public static <T extends WeixinAccount> T getWeixinAccount(Class<T> clazz) {
public static WeixinAccount getWeixinAccount() {
String text = getValue("account");
return JSON.parseObject(text, clazz);
}
public static WeixinMpAccount getWeixinMpAccount() {
return getWeixinAccount(WeixinMpAccount.class);
}
public static WeixinQyAccount getWeixinQyAccount() {
return getWeixinAccount(WeixinQyAccount.class);
return JSON.parseObject(text, WeixinAccount.class);
}
}

View File

@ -1,6 +1,5 @@
package com.foxinmy.weixin4j.util;
import java.io.InputStream;
import java.util.Arrays;
import javax.crypto.Cipher;
@ -9,18 +8,9 @@ import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.io.SAXReader;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.model.BaseMsg;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.type.MessageType;
import com.foxinmy.weixin4j.xml.XmlStream;
/**
* 消息工具类
@ -165,62 +155,4 @@ public class MessageUtil {
}
return xmlContent;
}
/**
* xml消息转换为消息对象
*
* @param xmlMsg
* 消息字符串
* @return 消息对象
* @throws DocumentException
* @see <a
* href="http://mp.weixin.qq.com/wiki/4/2ccadaef44fe1e4b0322355c2312bfa8.html">验证消息的合法性</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html">普通消息</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html">事件消息</a>
* @see com.foxinmy.weixin4j.type.MessageType
* @see com.feican.weixin.msg.BaeMessage
* @see com.foxinmy.weixin4j.msg.TextMessage
* @see com.foxinmy.weixin4j.msg.ImageMessage
* @see com.foxinmy.weixin4j.msg.VoiceMessage
* @see com.foxinmy.weixin4j.msg.VideoMessage
* @see com.foxinmy.weixin4j.msg.LocationMessage
* @see com.foxinmy.weixin4j.msg.LinkMessage
* @see com.foxinmy.weixin4j.msg.event.ScribeEventMessage
* @see com.foxinmy.weixin4j.msg.event.ScanEventMessage
* @see com.foxinmy.weixin4j.msg.event.LocationEventMessage
* @see com.foxinmy.weixin4j.msg.event.menu.MenuEventMessage
*/
public static BaseMsg xml2msg(String xmlMsg) throws DocumentException {
Document doc = DocumentHelper.parseText(xmlMsg);
String type = doc.selectSingleNode("/xml/MsgType").getStringValue();
if (StringUtils.isBlank(type)) {
return null;
}
MessageType messageType = MessageType.valueOf(type.toLowerCase());
Class<? extends BaseMsg> messageClass = messageType.getMessageClass();
if (messageType == MessageType.event) {
type = doc.selectSingleNode("/xml/Event").getStringValue();
messageClass = EventType.valueOf(type.toLowerCase())
.getEventClass();
}
return XmlStream.get(xmlMsg, messageClass);
}
/**
* xml消息转换为消息对象
*
* @param inputStream
* 带消息字符串的输入流
* @return 消息对象
* @throws DocumentException
* @see {@link com.foxinmy.weixin4j.util.MessageUtil#xml2msg(String)}
*/
public static BaseMsg xml2msg(InputStream inputStream)
throws DocumentException {
SAXReader reader = new SAXReader();
Document doc = reader.read(inputStream);
return xml2msg(doc.asXML());
}
}

View File

@ -1,6 +1,6 @@
package com.foxinmy.weixin4j.xml;
import com.foxinmy.weixin4j.msg.model.Text;
import com.foxinmy.weixin4j.tuple.Text;
import com.thoughtworks.xstream.converters.SingleValueConverter;
/**

View File

@ -6,55 +6,48 @@ weixin4j-mp
功能列表
-------
* **weixin4j-mp-api**
+ MediaApi `上传/下载媒体文件API`
+ NotifyApi `客服消息API`
+ CustomApi `多客服API`
+ MassApi `群发消息API`
+ UserApi `用户管理API`
+ GroupApi `分组管理API`
+ MenuApi `底部菜单API`
+ QrApi `二维码API`
+ TmplApi `模板消息API`
+ HelperApi `辅助API`
+ Pay2Api `V2支付API`
+ Pay3Api `V3支付API`
+ CouponApi `代金券API`
+ DataApi `数据统计API`
+ OauthApi `oauth授权API`
+ CashApi `现金API`
* MediaApi `上传/下载媒体文件API`
* **weixin4j-mp-server**
* NotifyApi `客服消息API`
+ `netty服务器` & `消息分发`
* CustomApi `多客服API`
* MassApi `群发消息API`
* UserApi `用户管理API`
* GroupApi `分组管理API`
* MenuApi `底部菜单API`
* QrApi `二维码API`
* TmplApi `模板消息API`
* HelperApi `辅助API`
* Pay2Api `V2支付API`
+ 被动消息`AES`加密、解密
* Pay3Api `V3支付API`
项目说明
-------
1.`weixin4j-mp`包含「微信公众平台」的API封装以及一个半成品的netty服务实现.
* CouponApi `代金券API`
2.API的成功调用依赖于正确的appid等数据,创建(或者copy项目里面的)一个名为**weixin.properties**的资源文件放在自己工程中的classpath下.
* DataApi `数据统计API`
| 属性名 | 说明 |
| :---------- | :-------------- |
| account | 微信公众号信息 `json格式` |
* OauthApi `oauth授权API`
* CashApi `现金API`
如何使用
--------
1.API工程可以单独打包到其他项目中使用,需新增或拷贝`weixin.properties`文件到项目的`classpath`
weixin.properties说明
| 属性名 | 说明 |
| :---------- | :-------------- |
| account | 微信公众号信息 `json格式` |
| token_path | 使用FileTokenHolder时token保存的物理路径 |
| qr_path | 调用二维码接口时保存二维码图片的物理路径 |
| media_path | 调用媒体接口时保存媒体文件的物理路径 |
@ -84,148 +77,19 @@ weixin4j-mp
#微信登陆授权的重定向路径
redirect_uri=http://xxx
3.在项目根目录下执行`mvn package -Prelease`命令后得到jar包,将`weixin4j-mp-full`包或者`weixin4j-base``weixin4j-mp-api`两个包引入到自己的工程.
2.实例化一个`WeixinProxy`对象,调用API,需要强调的是如果只传入appid,appsecret两个参数将无法调用支付相关接口
WeixinProxy weixinProxy = new WeixinProxy();
// weixinProxy = new WeixinProxy(appid,appsecret);
// weixinProxy = new WeixinProxy(weixinAccount);
weixinProxy.getUser(openId);
4.如需使用netty服务,则可以在相应的action中实现自己的业务处理,打包后放到`正确的目录`下解压`weixin-*-server-bin.zip`执行`sh startup.sh start`便可启动服务.
3.针对`token`存储有两种方案,`File存储`/`Redis存储`,当然也可自己实现`TokenHolder`,默认使用文件(xml)的方式保存token,如果环境中支持`redis`,建议使用`RedisTokenHolder`.
@ActionAnnotation(msgType = MessageType.text)
public class TextAction extends AbstractAction<TextMessage> {
WeixinProxy weixinProxy = new WeixinProxy(new RedisTokenHolder());
// weixinProxy = new WeixinProxy(new RedisTokenHolder(weixinAccount));
4.`mvn package`.
@Override
public ResponseMessage execute(TextMessage inMessage) {
return new ResponseMessage(new Text("Hello World!"), inMessage);
}
}
更新LOG
-------
* 2014-10-27
+ 用netty构建http服务器&消息分发
* 2014-10-28
+ 调整`ActionMapping`抽象化
* 2014-10-31
+ `weixin.properties`切分为API调用地址和公众号appid等信息两部分
* 2014-11-03
+ `weixin-mp`分离为`weixin4j-mp-api``weixin4j-mp-server`两个工程
+ **weixin4j-mp**: 新增`支付`模块
* 2014-11-06
+ **weixin4j-mp-api**: 新增V3版本`退款接口`
* 2014-11-08
+ **weixin4j-mp-api**: 新增V2版本`退款申请``退款查询``对账单下载`三个接口
+ **weixin4j-mp-api**: 新增一个简单的`语义理解`接口
* 2014-11-11
+ **weixin4j-mp-api**: 自定义`assembly``weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`)
* 2014-11-15
+ **weixin4j-mp-api**: 新增获取`微信服务器IP地址接口`
+ **weixin4j-mp-server**: 解决`server工程`打包后不能运行问题(`ClassUtil`无法获取jar包里面的类)
+ **weixin4j-mp-server**: 新增被动消息的`加密`以及回复消息的`解密`
* 2014-11-16
+ **weixin4j-mp-api**: 新增`多客服`接口
* 2014-11-17
+ **weixin4j-mp-api**: 新增`冲正``被扫支付`接口
* 2014-11-23
+ **weixin4j-mp-api**: 重新定义(手贱)了「被动消息」「客服消息」「群发消息」的传输实体
+ **weixin4j-mp-server**: `WeixinServerBootstrap`重命名为`WeixinMpServerBootstrap`
* 2014-12-12
+ **weixin4j-mp-api**: 新增设置`模板消息所处行业``获取模板消息ID`接口
* 2014-12-16
+ **weixin4j-mp-api**: 调整方法上@see注解的文档说明接口url
+ **weixin4j-mp-api**: 新增群发消息预览、状态查询接口
+ **weixin4j-mp-api**: 新增多客服添加账号、更新账号、上传头像、删除账号接口
* 2015-01-04
+ **weixin4j-mp-api**: 支付模块拆分为V2跟V3,新增WeixinPayProxy类
+ **weixin4j-mp-api**: 退款相关类拆分为V2跟V3
+ **weixin4j-mp-api**: 新增接口上报接口
* 2015-01-31
+ **weixin4j-mp-api**: 新增数据分析接口
* 2015-03-06
+ **weixin4j-mp-api**: 新增oauth授权接口
* 2015-03-21
+ **weixin4j-mp-api**: 新增群发消息给所有人接口
+ **weixin4j-mp-api**: 新增素材管理多个接口
+ **weixin4j-mp-api**: 新增多客服会话管理多个接口
* 2015-03-25
+ **weixin4j-mp-api**: 根据《微信商户平台文档》修缮[Pay3Api](./weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java)类
+ **weixin4j-mp-server**: 新增客服创建、关闭、转接会话事件
+ **weixin4j-mp-server**: 新增deploy.xml远程部署ant脚本
* 2015-03-29
+ **weixin4j-mp-api**: 单行注释调整为多行文档注释
+ **weixin4j-mp-api**: 新增[CouponApi](./weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/CouponApi.java)代金券接口
* 2015-04-01
+ **weixin4j-mp-api**: 新增[CashApi](./weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/CashApi.java)发红包、企业付款接口
* 2015-04-13
+ **weixin4j-mp-api**: 新增WeixinTokenCreator与WeixinJSTicketCreator类
+ **weixin4j-mp-api**: 新增用户分组批量移动、删除组别接口
* 2015-04-16
+ **weixin4j-mp-api**: <font color="red">调整[二维码参数](./weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/QRParameter.java)类</font>
+ **weixin4j-mp-api**: 新增获取[自定义菜单配置、自动回复配置](./weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java)接口
* 2015-04-18
+ **weixin4j-mp-api**: <font color="red">调整[客服接口](./weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java)类的方法名</font>
+ **weixin4j-mp-api**: <font color="red">在[二维码接口](./weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/QRApi.java)类新增获取二维码url方法</font>
[更新LOG](./CHANGE.md)
----------------------

View File

@ -8,11 +8,58 @@
</parent>
<artifactId>weixin4j-mp</artifactId>
<name>weixin4j-mp</name>
<packaging>pom</packaging>
<url>https://github.com/foxinmy/weixin4j/tree/master/weixin4j-mp</url>
<description>微信公众平台工具包</description>
<modules>
<module>weixin4j-mp-api</module>
<module>weixin4j-mp-server</module>
</modules>
<description>微信公众平台API</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>weixin4j-mp-${project.version}</finalName>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${jcl.over.version}</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>${jaxen.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,6 +1,7 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.mp.event;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.message.event.EventMessage;
import com.foxinmy.weixin4j.mp.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -18,7 +19,7 @@ public class KfCloseEventMessage extends EventMessage {
private static final long serialVersionUID = 3644449346935205541L;
public KfCloseEventMessage() {
super(EventType.kf_close_session);
super(EventType.kf_close_session.name());
}
/**

View File

@ -1,6 +1,7 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.mp.event;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.message.event.EventMessage;
import com.foxinmy.weixin4j.mp.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -18,7 +19,7 @@ public class KfCreateEventMessage extends EventMessage {
private static final long serialVersionUID = -8968189700999202108L;
public KfCreateEventMessage() {
super(EventType.kf_create_session);
super(EventType.kf_create_session.name());
}
/**

View File

@ -1,6 +1,7 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.mp.event;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.message.event.EventMessage;
import com.foxinmy.weixin4j.mp.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -18,7 +19,7 @@ public class KfSwitchEventMessage extends EventMessage {
private static final long serialVersionUID = 4319501074109623413L;
public KfSwitchEventMessage() {
super(EventType.kf_switch_session);
super(EventType.kf_switch_session.name());
}
/**
* 来自的客服账号

View File

@ -1,9 +1,10 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.mp.event;
import java.util.HashMap;
import java.util.Map;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.message.event.EventMessage;
import com.foxinmy.weixin4j.mp.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
@ -22,7 +23,7 @@ public class MassEventMessage extends EventMessage {
private static final long serialVersionUID = -1660543255873723895L;
public MassEventMessage() {
super(EventType.masssendjobfinish);
super(EventType.masssendjobfinish.name());
}
/**

View File

@ -1,6 +1,7 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.mp.event;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.message.event.EventMessage;
import com.foxinmy.weixin4j.mp.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -18,10 +19,10 @@ public class ScanEventMessage extends EventMessage {
private static final long serialVersionUID = 8078674062833071562L;
public ScanEventMessage() {
super(EventType.scan);
super(EventType.scan.name());
}
public ScanEventMessage(EventType eventType) {
public ScanEventMessage(String eventType) {
super(eventType);
}

View File

@ -1,4 +1,4 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.mp.event;
import com.foxinmy.weixin4j.type.EventType;
@ -11,17 +11,13 @@ import com.foxinmy.weixin4j.type.EventType;
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html#.E5.85.B3.E6.B3.A8.2F.E5.8F.96.E6.B6.88.E5.85.B3.E6.B3.A8.E4.BA.8B.E4.BB.B6">订阅号服务号的关注/取消关注事件</a>
* @see <a href="http://qydev.weixin.qq.com/wiki/index.php?title=%
* E5%85%B3%E6%B3%A8%E4%B8%
* 8E%E5%8F%96%E6%B6%88%E5%85%B3%E6%B3%A8#.E5.85.B3.E6.B3.A8.2F.E5.8F.96.E6.B6.88.E5.85.B3.E6.B3.A8.E4.BA.8B.E4.BB.B6.E7.9A.84.E6.8E.A8.E9.80.81">企业号的关注/取消关注
* </a>
*/
public class ScribeEventMessage extends ScanEventMessage {
private static final long serialVersionUID = -6846321620262204915L;
public ScribeEventMessage() {
super(EventType.subscribe);
super(EventType.subscribe.name());
}
@Override

View File

@ -1,6 +1,7 @@
package com.foxinmy.weixin4j.msg.event;
package com.foxinmy.weixin4j.mp.event;
import com.foxinmy.weixin4j.type.EventType;
import com.foxinmy.weixin4j.message.event.EventMessage;
import com.foxinmy.weixin4j.mp.type.EventType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
@ -18,7 +19,7 @@ public class TemplatesendjobfinishMessage extends EventMessage {
private static final long serialVersionUID = -2903359365988594012L;
public TemplatesendjobfinishMessage() {
super(EventType.templatesendjobfinish);
super(EventType.templatesendjobfinish.name());
}
/**

View File

@ -1,9 +1,8 @@
package com.foxinmy.weixin4j.model;
package com.foxinmy.weixin4j.mp.model;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.AccountType;
import com.foxinmy.weixin4j.model.WeixinAccount;
/**
* 微信公众平台信息
@ -130,10 +129,14 @@ public class WeixinMpAccount extends WeixinAccount {
/**
* 商户平台版本(V3)字段
*
* @param appId 公众号唯一的身份ID
* @param appSecret 调用接口的凭证
* @param paySignKey 支付密钥字符串
* @param mchId 微信支付分配的商户号
* @param appId
* 公众号唯一的身份ID
* @param appSecret
* 调用接口的凭证
* @param paySignKey
* 支付密钥字符串
* @param mchId
* 微信支付分配的商户号
*/
public WeixinMpAccount(String appId, String appSecret, String paySignKey,
String mchId) {
@ -145,11 +148,16 @@ public class WeixinMpAccount extends WeixinAccount {
/**
* V2版本字段
*
* @param appId 公众号唯一的身份ID
* @param appSecret 调用接口的凭证
* @param paySignKey 支付密钥字符串
* @param partnerId 财付通账号的ID
* @param partnerKey 财付通账号的key
* @param appId
* 公众号唯一的身份ID
* @param appSecret
* 调用接口的凭证
* @param paySignKey
* 支付密钥字符串
* @param partnerId
* 财付通账号的ID
* @param partnerKey
* 财付通账号的key
*/
public WeixinMpAccount(String appId, String appSecret, String paySignKey,
String partnerId, String partnerKey) {
@ -159,12 +167,6 @@ public class WeixinMpAccount extends WeixinAccount {
this.partnerKey = partnerKey;
}
@Override
@JSONField(deserialize = false)
public AccountType getAccountType() {
return AccountType.MP;
}
@Override
public String toString() {
return "WeixinMpAccount [openId=" + openId + ", paySignKey="

View File

@ -1,32 +0,0 @@
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
*~
# eclipse ignore
*.settings/*
/.project
/.classpath
/.tomcatplugin
# idea ignore
/.idea
*.iml
# maven ignore
target/*
# other ignore
*.log
*.tmp
Thumbs.db
/target/
.DS_Store

View File

@ -1,206 +0,0 @@
weixin4j-mp-api
===============
[微信公众平台](http://mp.weixin.qq.com/wiki)开发工具包
----------------------------------------------------
功能列表
-------
* MediaApi `上传/下载媒体文件API`
* NotifyApi `客服消息API`
* CustomApi `多客服API`
* MassApi `群发消息API`
* UserApi `用户管理API`
* GroupApi `分组管理API`
* MenuApi `底部菜单API`
* QrApi `二维码API`
* TmplApi `模板消息API`
* HelperApi `辅助API`
* Pay2Api `V2支付API`
* Pay3Api `V3支付API`
* CouponApi `代金券API`
* DataApi `数据统计API`
* OauthApi `oauth授权API`
* CashApi `现金API`
如何使用
--------
1.API工程可以单独打包到其他项目中使用,需新增或拷贝`weixin.properties`文件到项目的`classpath`
weixin.properties说明
| 属性名 | 说明 |
| :---------- | :-------------- |
| account | 微信公众号信息 `json格式` |
| token_path | 使用FileTokenHolder时token保存的物理路径 |
| qr_path | 调用二维码接口时保存二维码图片的物理路径 |
| media_path | 调用媒体接口时保存媒体文件的物理路径 |
| bill_path | 调用下载对账单接口保存excel文件的物理路径 |
| ca_file | 调用某些接口(支付相关)强制需要auth的ca授权文件 |
| redirect_uri | 调用OauthApi接口时需要填写的重定向路径 |
示例(properties中换行用右斜杆\\)
account={"id":"appId","secret":"appSecret",\
"token":"开放者的token",\
"encodingAesKey":"公众号设置了加密方式且为「安全模式」时需要填入",\
"mchId":"V3.x版本下的微信商户号",\
"partnerId":"V2版本下的财付通的商户号",\
"partnerKey":"V2版本下的财付通商户权限密钥Key",\
"version":"针对微信支付的版本号(2,3),如果不填则按照mchId非空与否来判断",\
"paySignKey":"微信支付中调用API的密钥"}
token_path=/tmp/weixin/token
qr_path=/tmp/weixin/qr
media_path=/tmp/weixin/media
bill_path=/tmp/weixin/bill
# ca证书存放的完整路径 (V2版本后缀为*.pfx,V3版本后缀为*.p12)
ca_file=/tmp/weixin/xxxxx.p12
#classpath路径下:ca_file=classpath:xxxxx.p12
#微信登陆授权的重定向路径
redirect_uri=http://xxx
2.实例化一个`WeixinProxy`对象,调用API,需要强调的是如果只传入appid,appsecret两个参数将无法调用支付相关接口
WeixinProxy weixinProxy = new WeixinProxy();
// weixinProxy = new WeixinProxy(appid,appsecret);
// weixinProxy = new WeixinProxy(weixinAccount);
weixinProxy.getUser(openId);
3.针对`token`存储有两种方案,`File存储`/`Redis存储`,当然也可自己实现`TokenHolder`,默认使用文件(xml)的方式保存token,如果环境中支持`redis`,建议使用`RedisTokenHolder`.
WeixinProxy weixinProxy = new WeixinProxy(new RedisTokenHolder());
// weixinProxy = new WeixinProxy(new RedisTokenHolder(weixinAccount));
4.`mvn package`.
更新LOG
-------
* 2014-10-27
+ 用netty构建http服务器&消息分发
* 2014-10-28
+ 调整`ActionMapping`抽象化
* 2014-10-31
+ `weixin.properties`切分为API调用地址和公众号appid等信息两部分
* 2014-11-03
+ 分离为`weixin-mp-api``weixin-mp-server`两个工程
+ 新增`支付模块`
* 2014-11-06
+ 新增V3版本`退款申请`接口
* 2014-11-08
+ 新增V2版本`退款申请``退款查询``对账单下载`三个接口
+ 新增一个简单的`语义理解`接口
* 2014-11-11
+ 自定义`assembly``weixin4j-base`工程也一起打包(`weixin4j-mp-api-full.jar`)
* 2014-11-15
+ 新增获取`微信服务器IP地址接口`
* 2014-11-16
+ 新增`多客服`接口
* 2014-11-17
+ 新增`冲正``被扫支付`接口
* 2014-12-12
+ 新增设置`模板消息所处行业``获取模板消息ID`接口
* 2014-12-16
+ 调整方法上@see注解的文档说明接口url
+ 新增群发消息预览、状态查询接口
+ 新增多客服添加账号、更新账号、上传头像、删除账号接口
* 2015-01-04
+ 支付模块拆分为V2跟V3,新增WeixinPayProxy类
+ 退款相关类拆分为V2跟V3
+ 新增接口上报接口
* 2015-01-31
+ 新增数据分析接口
* 2015-03-06
+ 新增oauth授权接口
* 2015-03-21
+ 新增群发消息给所有人接口
+ 新增素材管理多个接口
+ 新增多客服会话管理多个接口
* 2015-03-25
+ 根据《微信商户平台文档》修缮[Pay3Api](./src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java)类
* 2015-03-29
+ 单行注释调整为多行文档注释
+ 新增[CouponApi](./src/main/java/com/foxinmy/weixin4j/mp/api/CouponApi.java)代金券接口
* 2015-04-01
+ 新增[CashApi](./src/main/java/com/foxinmy/weixin4j/mp/api/CashApi.java)发红包、企业付款接口
* 2015-04-13
+ 新增WeixinTokenCreator与WeixinJSTicketCreator类
+ 新增用户分组批量移动、删除组别接口
* 2015-04-16
+ **weixin4j-mp-api**: <font color="red">调整[二维码参数](./src/main/java/com/foxinmy/weixin4j/mp/model/QRParameter.java)类</font>
+ **weixin4j-mp-api**: 新增获取[自定义菜单配置、自动回复配置](./src/main/java/com/foxinmy/weixin4j/mp/api/HelperApi.java)接口
* 2015-04-18
+ <font color="red">调整[客服接口](./src/main/java/com/foxinmy/weixin4j/mp/api/CustomApi.java)类的方法名</font>
+ <font color="red">在[二维码接口](./src/main/java/com/foxinmy/weixin4j/mp/api/QRApi.java)类新增获取二维码url方法</font>

View File

@ -1,65 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j-mp</artifactId>
<version>1.3</version>
</parent>
<artifactId>weixin4j-mp-api</artifactId>
<name>weixin4j-mp-api</name>
<url>https://github.com/foxinmy/weixin4j/tree/master/weixin4j-mp/weixin4j-mp-api</url>
<description>微信公众平台API</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>weixin4j-mp-${project.version}</finalName>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.foxinmy</groupId>
<artifactId>weixin4j-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
<exclusions>
<exclusion>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</exclusion>
<exclusion>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${jcl.over.version}</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>${jaxen.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,31 +0,0 @@
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>full</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>target/classes</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>/**</include>
</includes>
<excludes>
<exclude>*.properties</exclude>
<exclude>*.xml</exclude>
</excludes>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<unpack>true</unpack>
<includes>
<include>com.foxinmy:weixin4j-base</include>
</includes>
</dependencySet>
</dependencySets>
</assembly>

View File

@ -1,622 +0,0 @@
package com.foxinmy.weixin4j.mp;
import java.io.File;
import java.util.Date;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.api.CashApi;
import com.foxinmy.weixin4j.mp.api.CouponApi;
import com.foxinmy.weixin4j.mp.api.Pay2Api;
import com.foxinmy.weixin4j.mp.api.Pay3Api;
import com.foxinmy.weixin4j.mp.api.PayApi;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponDetail;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponResult;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponStock;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.payment.v3.MPPayment;
import com.foxinmy.weixin4j.mp.payment.v3.MPPaymentResult;
import com.foxinmy.weixin4j.mp.payment.v3.Redpacket;
import com.foxinmy.weixin4j.mp.payment.v3.RedpacketSendResult;
import com.foxinmy.weixin4j.mp.token.WeixinTokenCreator;
import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.IdType;
import com.foxinmy.weixin4j.mp.type.RefundType;
import com.foxinmy.weixin4j.token.FileTokenHolder;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.ConfigUtil;
/**
* 微信支付接口实现
*
* @className WeixinPayProxy
* @author jy
* @date 2015年1月3日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a href="http://pay.weixin.qq.com/wiki/doc/api/index.html">商户平台支付API</a>
*/
public class WeixinPayProxy {
private final PayApi payApi;
private final Pay2Api pay2Api;
private final Pay3Api pay3Api;
private final CouponApi couponApi;
private final CashApi cashApi;
public WeixinPayProxy() {
this(ConfigUtil.getWeixinMpAccount(), new FileTokenHolder(
new WeixinTokenCreator()));
}
/**
* WeixinAccount对象
*
* @param weixinAccount
* 微信账户
*/
public WeixinPayProxy(WeixinMpAccount weixinAccount, TokenHolder tokenHolder) {
this.pay2Api = new Pay2Api(weixinAccount, tokenHolder);
this.pay3Api = new Pay3Api(weixinAccount, tokenHolder);
int version = weixinAccount.getVersion();
if (version == 2) {
this.payApi = this.pay2Api;
} else if (version == 3) {
this.payApi = this.pay3Api;
} else {
this.payApi = this.pay3Api;
}
this.couponApi = new CouponApi(weixinAccount);
this.cashApi = new CashApi(weixinAccount);
}
/**
* 发货通知
*
* @param openId
* 用户ID
* @param transid
* 交易单号
* @param outTradeNo
* 订单号
* @param status
* 成功|失败
* @param statusMsg
* status为失败时携带的信息
* @return 发货处理结果
* @since V2 & V3
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @throws WeixinException
*/
public JsonResult deliverNotify(String openId, String transid,
String outTradeNo, boolean status, String statusMsg)
throws WeixinException {
return payApi.deliverNotify(openId, transid, outTradeNo, status,
statusMsg);
}
/**
* 维权处理
*
* @param openId
* 用户ID
* @param feedbackId
* 维权单号
* @return 调用结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @since V2 & V3
* @throws WeixinException
*/
public JsonResult updateFeedback(String openId, String feedbackId)
throws WeixinException {
return payApi.updateFeedback(openId, feedbackId);
}
/**
* V2订单查询
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @since V2
* @see com.foxinmy.weixin4j.mp.payment.v2.Order
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @return 订单详情
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v2.Order orderQueryV2(
String outTradeNo) throws WeixinException {
return pay2Api.orderQuery(new IdQuery(outTradeNo, IdType.TRADENO));
}
/**
* V3订单查询
* <p>
* 当商户后台网络服务器等出现异常商户系统最终未接收到支付通知</br> 调用支付接口后返回系统错误或未知交易状态情况</br>
* 调用被扫支付API返回USERPAYING的状态</br> 调用关单或撤销接口API之前需确认支付状态
* </P>
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @since V3
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2">订单查询API</a>
* @return 订单详情
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v3.Order orderQueryV3(IdQuery idQuery)
throws WeixinException {
return pay3Api.orderQuery(idQuery);
}
/**
* V2申请退款(请求需要双向证书)</br>
* <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* </p>
*
* @param caFile
* 证书文件(后缀为*.pfx)
* @param idQuery
* ) 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
* @param opUserPasswd
* 操作员密码
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundResult
* @since V2
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v2.RefundResult refundV2(
File caFile, IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId, String opUserPasswd)
throws WeixinException {
return pay2Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, opUserId, opUserPasswd);
}
/**
* V2退款申请采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#refundV2(File, IdQuery, String, double, double, String,String)}
*/
public com.foxinmy.weixin4j.mp.payment.v2.RefundResult refundV2(
IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId, String opUserPasswd)
throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return refundV2(caFile, idQuery, outRefundNo, totalFee, refundFee,
opUserId, opUserPasswd);
}
/**
* V2退款申请
*
* @param caFile
* 证书文件(V2版本后缀为*.pfx)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
* @param opUserPasswd
* 操作员密码,默认为商户后台登录密码
* @param recvUserId
* 转账退款接收退款的财付通帐号 一般无需填写,只有退银行失败,资金转入商 户号现金账号时(即状态为转入代发,查询返 回的
* refund_status 7 11),填写原退款 单号并填写此字段,资金才会退到指定财付通
* 账号其他情况此字段忽略
* @param reccvUserName
* 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
* @param refundType
* 为空或者填 1:商户号余额退款;2:现金帐号 退款;3:优先商户号退款,若商户号余额不足, 再做现金帐号退款使用 2
* 3 ,需联系财 付通开通此功能
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundResult
* @return 退款结果
*/
public com.foxinmy.weixin4j.mp.payment.v2.RefundResult refundV2(
File caFile, IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId, String opUserPasswd,
String recvUserId, String reccvUserName, RefundType refundType)
throws WeixinException {
return pay2Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, opUserId, opUserPasswd, recvUserId, reccvUserName,
refundType);
}
/**
* V2退款查询</br> 退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态
*
* @param idQuery
* 单号 refund_idout_refund_no out_trade_no transaction_id
* 四个参数必填一个,优先级为:
* refund_id>out_refund_no>transaction_id>out_trade_no
* @return 退款记录
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @since V2
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v2.RefundRecord refundQueryV2(
IdQuery idQuery) throws WeixinException {
return pay2Api.refundQuery(idQuery);
}
/**
* V3申请退款(请求需要双向证书)</br>
* <p>
* 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家微信支付将在收到退款请求并且验证成功之后
* 按照退款规则将支付款按原路退到买家帐号上
* </p>
* <p style="color:red">
* 1.交易时间超过半年的订单无法提交退款
* 2.微信支付退款支持单笔交易分多次退款多次退款需要提交原支付订单的商户订单号和设置不同的退款单号一笔退款失败后重新提交
* 要采用原来的退款单号总退款金额不能超过用户实际支付金额
* </p>
*
* @param caFile
* 证书文件(后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param refundFeeType
* 货币类型符合ISO 4217标准的三位字母代码默认人民币CNY
* @param opUserId
* 操作员帐号, 默认为商户号
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">申请退款API</a>
* @since V3
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v3.RefundResult refundV3(
File caFile, IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, CurrencyType refundFeeType, String opUserId)
throws WeixinException {
return pay3Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, refundFeeType, opUserId);
}
/**
* V3退款申请采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#refundV3(File, IdQuery, String, double, double,CurrencyType, String)}
*/
public com.foxinmy.weixin4j.mp.payment.v3.RefundResult refundV3(
IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId) throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return pay3Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, CurrencyType.CNY, opUserId);
}
/**
* V3退款查询
* <p>
* 提交退款申请后通过调用该接口查询退款状态退款有一定延时用零钱支付的退款20分钟内到账银行卡支付的退款3个工作日后重新查询退款状态
* </p>
*
* @param idQuery
* 单号 refund_idout_refund_no out_trade_no transaction_id
* 四个参数必填一个,优先级为:
* refund_id>out_refund_no>transaction_id>out_trade_no
* @return 退款记录
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5">退款查询API</a>
* @since V3
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v3.RefundRecord refundQueryV3(
IdQuery idQuery) throws WeixinException {
return pay3Api.refundQuery(idQuery);
}
/**
* 下载对账单<br>
* 1.微信侧未成功下单的交易不会出现在对账单中支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type
* REVOKED;<br>
* 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;<br>
* 3.对账单中涉及金额的字段单位为<br>
*
* @param billDate
* 下载对账单的日期
* @param billType
* 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单
* @return excel表格
* @since V2 & V3
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">下载对账单API</a>
* @throws WeixinException
*/
public File downloadbill(Date billDate, BillType billType)
throws WeixinException {
return payApi.downloadbill(billDate, billType);
}
/**
* 冲正订单(需要证书)</br> 当支付返回失败,或收银系统超时需要取消交易,可以调用该接口</br> 接口逻辑:
* 付失败的关单,支付成功的撤销支付</br> <font color="red">7天以内的单可撤销,其他正常支付的单
* 如需实现相同功能请调用退款接口</font></br> <font
* color="red">调用扣款接口后请勿立即调用撤销,需要等待5秒以上先调用查单接口,如果没有确切的返回,再调用撤销</font></br>
*
* @param ca
* 证书文件(V2版本后缀为*.pfx,V3版本后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 撤销结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @since V3
* @throws WeixinException
*/
public ApiResult reverse(File caFile, IdQuery idQuery)
throws WeixinException {
return payApi.reverse(caFile, idQuery);
}
/**
* 冲正撤销:默认采用properties中配置的ca文件
*
* @param idQuery
* transaction_idout_trade_no 二选一
* @return 撤销结果
* @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#reverse(File, IdQuery)}
* @throws WeixinException
*/
public ApiResult reverse(IdQuery idQuery) throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return payApi.reverse(caFile, idQuery);
}
/**
* 关闭订单
* <p>
* 商户订单支付失败需要生成新单号重新发起支付要对原订单号调用关单避免重复支付系统下单后用户支付超时系统退出不再受理避免用户继续
* 请调用关单接口,如果关单失败,返回已完 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
* </p>
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 执行结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @since V3
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3">关闭订单API</a>
*/
public ApiResult closeOrder(String outTradeNo) throws WeixinException {
return payApi.closeOrder(outTradeNo);
}
/**
* native支付URL转短链接:用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX)减小二维码数据量
* 提升扫描速度和精确度
*
* @param url
* 具有native标识的支付URL
* @return 转换后的短链接
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_9">转换短链接API</a>
* @since V2 & V3
* @throws WeixinException
*/
public String getPayShorturl(String url) throws WeixinException {
return payApi.getShorturl(url);
}
/**
* 接口上报
*
* @param interfaceUrl
* 上报对应的接口的完整 URL, 类似: https://api.mch.weixin.q
* q.com/pay/unifiedorder
* @param executeTime
* 接口耗时情况,单位为毫秒
* @param outTradeNo
* 商户系统内部的订单号, 户可以在上报时提供相关商户订单号方便微信支付更好 的提高服务质量
* @param ip
* 发起接口调用时的机器 IP
* @param time
* 商户调用该接口时商户自己 系统的时间
* @param returnXml
* 调用接口返回的基本数据
* @return 处理结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_8">接口测试上报API</a>
* @throws WeixinException
*/
public XmlResult interfaceReport(String interfaceUrl, int executeTime,
String outTradeNo, String ip, Date time, XmlResult returnXml)
throws WeixinException {
return pay3Api.interfaceReport(interfaceUrl, executeTime, outTradeNo,
ip, time, returnXml);
}
/**
* 发放代金券(需要证书)
*
* @param caFile
* 证书文件(后缀为*.p12)
* @param couponStockId
* 代金券批次id
* @param partnerTradeNo
* 商户发放凭据号格式商户id+日期+流水号商户侧需保持唯一性
* @param openId
* 用户的openid
* @param opUserId
* 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限 可为空
* @return 发放结果
* @see com.foxinmy.weixin4j.mp.api.CouponApi
* @see com.foxinmy.weixin4j.mp.payment.coupon.CouponResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/sp_coupon.php?chapter=12_3">发放代金券接口</a>
* @throws WeixinException
*/
public CouponResult sendCoupon(File caFile, String couponStockId,
String partnerTradeNo, String openId, String opUserId)
throws WeixinException {
return couponApi.sendCoupon(caFile, couponStockId, partnerTradeNo,
openId, opUserId);
}
/**
* 发放代金券采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#sendCoupon(File, String, String, String, String)}
*/
public CouponResult sendCoupon(String couponStockId, String partnerTradeNo,
String openId) throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return couponApi.sendCoupon(caFile, couponStockId, partnerTradeNo,
openId, null);
}
/**
* 查询代金券批次
*
* @param couponStockId
* 代金券批次ID
* @return 代金券批次信息
* @see com.foxinmy.weixin4j.mp.api.CouponApi
* @see com.foxinmy.weixin4j.mp.payment.coupon.CouponStock
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/sp_coupon.php?chapter=12_4">查询代金券信息</a>
* @throws WeixinException
*/
public CouponStock queryCouponStock(String couponStockId)
throws WeixinException {
return couponApi.queryCouponStock(couponStockId);
}
/**
* 查询代金券详细
*
* @param couponId
* 代金券ID
* @return 代金券详细信息
* @see com.foxinmy.weixin4j.mp.api.CouponApi
* @see com.foxinmy.weixin4j.mp.payment.coupon.CouponDetail
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/sp_coupon.php?chapter=12_5">查询代金券详细信息</a>
* @throws WeixinException
*/
public CouponDetail queryCouponDetail(String couponId)
throws WeixinException {
return couponApi.queryCouponDetail(couponId);
}
/**
* 发放红包 企业向微信用户个人发现金红包
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param redpacket
* 红包信息
* @return 发放结果
* @see com.foxinmy.weixin4j.mp.api.CashApi
* @see com.foxinmy.weixin4j.mp.payment.v3.Redpacket
* @see com.foxinmy.weixin4j.mp.payment.v3.RedpacketSendResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5">红包接口说明</a>
* @throws WeixinException
*/
public RedpacketSendResult sendRedpack(File caFile, Redpacket redpacket)
throws WeixinException {
return cashApi.sendRedpack(caFile, redpacket);
}
/**
* 发放红包采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#sendRedpack(File, Redpacket)}
*/
public RedpacketSendResult sendRedpack(Redpacket redpacket)
throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return cashApi.sendRedpack(caFile, redpacket);
}
/**
* 企业付款 实现企业向个人付款针对部分有开发能力的商户 提供通过API完成企业付款的功能 比如目前的保险行业向客户退保给付理赔
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param mpPayment
* 付款信息
* @return 付款结果
* @see com.foxinmy.weixin4j.mp.api.CashApi
* @see com.foxinmy.weixin4j.mp.payment.v3.MPPayment
* @see com.foxinmy.weixin4j.mp.payment.v3.MPPaymentResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/mch_pay.php?chapter=14_1">企业付款</a>
* @throws WeixinException
*/
public MPPaymentResult mpPayment(File caFile, MPPayment mpPayment)
throws WeixinException {
return cashApi.mpPayment(caFile, mpPayment);
}
/**
* 企业付款采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#mpPayment(File, MPPayment)}
*/
public MPPaymentResult mpPayment(MPPayment mpPayment)
throws WeixinException {
File caFile = new File(ConfigUtil.getClassPathValue("ca_file"));
return cashApi.mpPayment(caFile, mpPayment);
}
}

View File

@ -1,146 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.http.SSLHttpRequest;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.v3.MPPayment;
import com.foxinmy.weixin4j.mp.payment.v3.MPPaymentResult;
import com.foxinmy.weixin4j.mp.payment.v3.Redpacket;
import com.foxinmy.weixin4j.mp.payment.v3.RedpacketSendResult;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.foxinmy.weixin4j.xml.XmlStream;
/**
* 现金API
*
* @className CashApi
* @author jy
* @date 2015年3月28日
* @since JDK 1.7
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_1">现金红包</a>
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/mch_pay.php?chapter=14_1">企业付款</a>
*/
public class CashApi extends MpApi {
private final WeixinMpAccount weixinAccount;
public CashApi(WeixinMpAccount weixinAccount) {
this.weixinAccount = weixinAccount;
}
/**
* 发放红包 企业向微信用户个人发现金红包
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param redpacket
* 红包信息
* @return 发放结果
* @see com.foxinmy.weixin4j.mp.payment.v3.Redpacket
* @see com.foxinmy.weixin4j.mp.payment.v3.RedpacketSendResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5">红包接口说明</a>
* @throws WeixinException
*/
public RedpacketSendResult sendRedpack(File caFile, Redpacket redpacket)
throws WeixinException {
JSONObject obj = (JSONObject) JSON.toJSON(redpacket);
obj.put("nonce_str", RandomUtil.generateString(16));
obj.put("mch_id", weixinAccount.getMchId());
obj.put("sub_mch_id", weixinAccount.getSubMchId());
obj.put("wxappid", weixinAccount.getId());
String sign = PayUtil.paysignMd5(obj, weixinAccount.getPaySignKey());
obj.put("sign", sign);
String param = map2xml(new HashMap<String, Object>(obj));
String redpack_send_uri = getRequestUri("redpack_send_uri");
Response response = null;
InputStream ca = null;
try {
ca = new FileInputStream(caFile);
SSLHttpRequest request = new SSLHttpRequest(
weixinAccount.getMchId(), ca);
response = request.post(redpack_send_uri, param);
} catch (WeixinException e) {
throw e;
} catch (Exception e) {
throw new WeixinException(e.getMessage());
} finally {
if (ca != null) {
try {
ca.close();
} catch (IOException e) {
;
}
}
}
return response.getAsObject(new TypeReference<RedpacketSendResult>() {
});
}
/**
* 企业付款 实现企业向个人付款针对部分有开发能力的商户 提供通过API完成企业付款的功能 比如目前的保险行业向客户退保给付理赔
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param mpPayment
* 付款信息
* @return 付款结果
* @see com.foxinmy.weixin4j.mp.payment.v3.MPPayment
* @see com.foxinmy.weixin4j.mp.payment.v3.MPPaymentResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/mch_pay.php?chapter=14_1">企业付款</a>
* @throws WeixinException
*/
public MPPaymentResult mpPayment(File caFile, MPPayment mpPayment)
throws WeixinException {
JSONObject obj = (JSONObject) JSON.toJSON(mpPayment);
obj.put("nonce_str", RandomUtil.generateString(16));
obj.put("mchid", weixinAccount.getMchId());
obj.put("sub_mch_id", weixinAccount.getSubMchId());
obj.put("mch_appid", weixinAccount.getId());
obj.put("device_info", weixinAccount.getDeviceInfo());
String sign = PayUtil.paysignMd5(obj, weixinAccount.getPaySignKey());
obj.put("sign", sign);
String param = map2xml(new HashMap<String, Object>(obj));
String mp_payment_uri = getRequestUri("mp_payment_uri");
Response response = null;
InputStream ca = null;
try {
ca = new FileInputStream(caFile);
SSLHttpRequest request = new SSLHttpRequest(
weixinAccount.getMchId(), ca);
response = request.post(mp_payment_uri, param);
} catch (WeixinException e) {
throw e;
} catch (Exception e) {
throw new WeixinException(e.getMessage());
} finally {
if (ca != null) {
try {
ca.close();
} catch (IOException e) {
;
}
}
}
String text = response.getAsString()
.replaceFirst("<mch_appid>", "<appid>")
.replaceFirst("</mch_appid>", "</appid>")
.replaceFirst("<mchid>", "<mch_id>")
.replaceFirst("</mchid>", "</mch_id>");
return XmlStream.get(text, MPPaymentResult.class);
}
}

View File

@ -1,169 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.http.SSLHttpRequest;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponDetail;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponResult;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponStock;
import com.foxinmy.weixin4j.util.RandomUtil;
/**
* 代金券API
*
* @className CouponApi
* @author jy
* @date 2015年3月25日
* @since JDK 1.7
* @see <a href="http://pay.weixin.qq.com/wiki/doc/api/sp_coupon.php">代金券文档</a>
*/
public class CouponApi extends MpApi {
private final WeixinMpAccount weixinAccount;
public CouponApi(WeixinMpAccount weixinAccount) {
this.weixinAccount = weixinAccount;
}
/**
* 发放代金券(需要证书)
*
* @param caFile
* 证书文件(后缀为*.p12)
* @param couponStockId
* 代金券批次id
* @param partnerTradeNo
* 商户发放凭据号格式商户id+日期+流水号商户侧需保持唯一性
* @param openId
* 用户的openid
* @param opUserId
* 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限 可为空
* @return 发放结果
* @see com.foxinmy.weixin4j.mp.payment.coupon.CouponResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/sp_coupon.php?chapter=12_3">发放代金券接口</a>
* @throws WeixinException
*/
public CouponResult sendCoupon(File caFile, String couponStockId,
String partnerTradeNo, String openId, String opUserId)
throws WeixinException {
Map<String, String> map = baseMap();
map.put("coupon_stock_id", couponStockId);
map.put("partner_trade_no", partnerTradeNo);
map.put("openid", openId);
// openid记录数目前支持num=1
map.put("openid_count", "1");
// 操作员帐号, 默认为商户号 可在商户平台配置操作员对应的api权限
if (StringUtils.isBlank(opUserId)) {
opUserId = weixinAccount.getMchId();
}
map.put("op_user_id", opUserId);
map.put("version", "1.0");
map.put("type", "XML");
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
String coupon_send_uri = getRequestUri("coupon_send_uri");
Response response = null;
InputStream ca = null;
try {
ca = new FileInputStream(caFile);
SSLHttpRequest request = new SSLHttpRequest(
weixinAccount.getMchId(), ca);
response = request.post(coupon_send_uri, param);
} catch (WeixinException e) {
throw e;
} catch (Exception e) {
throw new WeixinException(e.getMessage());
} finally {
if (ca != null) {
try {
ca.close();
} catch (IOException e) {
;
}
}
}
return response.getAsObject(new TypeReference<CouponResult>() {
});
}
/**
* 查询代金券批次
*
* @param couponStockId
* 代金券批次ID
* @return 代金券批次信息
* @see com.foxinmy.weixin4j.mp.payment.coupon.CouponStock
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/sp_coupon.php?chapter=12_4">查询代金券批次信息</a>
* @throws WeixinException
*/
public CouponStock queryCouponStock(String couponStockId)
throws WeixinException {
Map<String, String> map = baseMap();
map.put("coupon_stock_id", couponStockId);
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
String couponstock_query_uri = getRequestUri("couponstock_query_uri");
Response response = request.post(couponstock_query_uri, param);
return response.getAsObject(new TypeReference<CouponStock>() {
});
}
/**
* 查询代金券详细
*
* @param couponId
* 代金券ID
* @return 代金券详细信息
* @see com.foxinmy.weixin4j.mp.payment.coupon.CouponDetail
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/sp_coupon.php?chapter=12_5">查询代金券详细信息</a>
* @throws WeixinException
*/
public CouponDetail queryCouponDetail(String couponId)
throws WeixinException {
Map<String, String> map = baseMap();
map.put("coupon_id", couponId);
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
String coupondetail_query_uri = getRequestUri("coupondetail_query_uri");
Response response = request.post(coupondetail_query_uri, param);
return response.getAsObject(new TypeReference<CouponDetail>() {
});
}
/**
* 接口请求基本数据
*
* @return
*/
private Map<String, String> baseMap() {
Map<String, String> map = new HashMap<String, String>();
map.put("appid", weixinAccount.getId());
map.put("mch_id", weixinAccount.getMchId());
map.put("nonce_str", RandomUtil.generateString(16));
if (StringUtils.isNotBlank(weixinAccount.getDeviceInfo())) {
map.put("device_info", weixinAccount.getDeviceInfo());
}
if (StringUtils.isNotBlank(weixinAccount.getSubMchId())) {
map.put("sub_mch_id", weixinAccount.getSubMchId());
}
return map;
}
}

View File

@ -1,357 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.entity.mime.content.ByteArrayBody;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.PartParameter;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.model.CustomRecord;
import com.foxinmy.weixin4j.mp.model.KfAccount;
import com.foxinmy.weixin4j.mp.model.KfSession;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.IOUtil;
/**
* 多客服API
*
* @className CustomApi
* @author jy
* @date 2014年11月16日
* @since JDK 1.7
* @see <a href="http://dkf.qq.com">多客服说明</a>
* @see<a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html"
* >多客服说明</a>
*/
public class CustomApi extends MpApi {
private final TokenHolder tokenHolder;
public CustomApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 客服聊天记录
*
* @param openId
* 用户标识 可为空
* @param starttime
* 查询开始时间
* @param endtime
* 查询结束时间 每次查询不能跨日查询
* @param pagesize
* 每页大小 每页最多拉取50条
* @param pageindex
* 查询第几页 从1开始
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.CustomRecord
* @see <a href="http://dkf.qq.com/document-1_1.html">查询客服聊天记录</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/19/7c129ec71ddfa60923ea9334557e8b23.html">查询客服聊天记录</a>
*/
public List<CustomRecord> getCustomRecord(String openId, Date starttime,
Date endtime, int pagesize, int pageindex) throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("openId", openId == null ? "" : openId);
obj.put("starttime", starttime.getTime() / 1000);
obj.put("endtime", endtime.getTime() / 1000);
obj.put("pagesize", pagesize > 1000 ? 1000 : pagesize);
obj.put("pageindex", pageindex);
String custom_record_uri = getRequestUri("custom_record_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(custom_record_uri, token.getAccessToken()),
obj.toJSONString());
String text = response.getAsJson().getString("recordlist");
return JSON.parseArray(text, CustomRecord.class);
}
/**
* 获取公众号中所设置的客服基本信息包括客服工号客服昵称客服登录账号
*
* @param isOnline
* 是否在线 为ture时可以可以获取客服在线状态手机在线PC客户端在线手机和PC客户端全都在线客服自动接入最大值
* 客服当前接待客户数
* @return 多客服信息列表
* @see com.foxinmy.weixin4j.mp.model.KfAccount
* @see <a href="http://dkf.qq.com/document-3_1.html">获取客服基本信息</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E8.8E.B7.E5.8F.96.E5.AE.A2.E6.9C.8D.E5.9F.BA.E6.9C.AC.E4.BF.A1.E6.81.AF">获取客服基本信息</a>
* @see <a href="http://dkf.qq.com/document-3_2.html">获取在线客服接待信息</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E8.8E.B7.E5.8F.96.E5.9C.A8.E7.BA.BF.E5.AE.A2.E6.9C.8D.E6.8E.A5.E5.BE.85.E4.BF.A1.E6.81.AF">获取在线客服接待信息</a>
* @throws WeixinException
*/
public List<KfAccount> getKfAccountList(boolean isOnline)
throws WeixinException {
Token token = tokenHolder.getToken();
String text = "";
if (isOnline) {
String getonlinekflist_uri = getRequestUri("getonlinekflist_uri");
Response response = request.post(String.format(getonlinekflist_uri,
token.getAccessToken()));
text = response.getAsJson().getString("kf_online_list");
} else {
String getkflist_uri = getRequestUri("getkflist_uri");
Response response = request.post(String.format(getkflist_uri,
token.getAccessToken()));
text = response.getAsJson().getString("kf_list");
}
return JSON.parseArray(text, KfAccount.class);
}
/**
* 新增客服账号
*
* @param id
* 完整客服账号格式为账号前缀@公众号微信号账号前缀最多10个字符必须是英文或者数字字符如果没有公众号微信号
* 请前往微信公众平台设置
* @param name
* 客服昵称最长6个汉字或12个英文字符
* @param pwd
* 客服账号登录密码
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E5.AE.A2.E6.9C.8D.E7.AE.A1.E7.90.86.E6.8E.A5.E5.8F.A3.E8.BF.94.E5.9B.9E.E7.A0.81.E8.AF.B4.E6.98.8E">客服管理接口返回码</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E6.B7.BB.E5.8A.A0.E5.AE.A2.E6.9C.8D.E8.B4.A6.E5.8F.B7">新增客服账号</a>
*/
public JsonResult addAccount(String id, String name, String pwd)
throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("kf_account", id);
obj.put("nickname", name);
obj.put("password", DigestUtils.md5Hex(pwd));
String custom_add_uri = getRequestUri("custom_add_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(custom_add_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 更新客服账号
*
* @param id
* 完整客服账号格式为账号前缀@公众号微信号账号前缀最多10个字符必须是英文或者数字字符如果没有公众号微信号
* 请前往微信公众平台设置
* @param name
* 客服昵称最长6个汉字或12个英文字符
* @param pwd
* 客服账号登录密码
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E5.AE.A2.E6.9C.8D.E7.AE.A1.E7.90.86.E6.8E.A5.E5.8F.A3.E8.BF.94.E5.9B.9E.E7.A0.81.E8.AF.B4.E6.98.8E">客服管理接口返回码</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E8.AE.BE.E7.BD.AE.E5.AE.A2.E6.9C.8D.E4.BF.A1.E6.81.AF">新增客服账号</a>
*/
public JsonResult updateAccount(String id, String name, String pwd)
throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("kf_account", id);
obj.put("nickname", name);
obj.put("password", DigestUtils.md5Hex(pwd));
String custom_update_uri = getRequestUri("custom_update_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(custom_update_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 上传客服头像
*
* @param id
* 完整客服账号格式为账号前缀@公众号微信号
* @param headimg
* 头像图片文件必须是jpg格式推荐使用640*640大小的图片以达到最佳效果
* @return 处理结果
* @throws WeixinException
* @throws IOException
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E5.AE.A2.E6.9C.8D.E7.AE.A1.E7.90.86.E6.8E.A5.E5.8F.A3.E8.BF.94.E5.9B.9E.E7.A0.81.E8.AF.B4.E6.98.8E">客服管理接口返回码</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E4.B8.8A.E4.BC.A0.E5.AE.A2.E6.9C.8D.E5.A4.B4.E5.83.8F">上传客服头像</a>
*/
public JsonResult uploadAccountHeadimg(String id, File headimg)
throws WeixinException, IOException {
Token token = tokenHolder.getToken();
String custom_uploadheadimg_uri = getRequestUri("custom_uploadheadimg_uri");
byte[] bytes = IOUtil.toByteArray(new FileInputStream(headimg));
Response response = request.post(
String.format(custom_uploadheadimg_uri, token.getAccessToken(),
id),
new PartParameter("media", new ByteArrayBody(bytes, headimg
.getName())));
return response.getAsJsonResult();
}
/**
* 删除客服账号
*
* @param id
* 完整客服账号格式为账号前缀@公众号微信号
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E5.AE.A2.E6.9C.8D.E7.AE.A1.E7.90.86.E6.8E.A5.E5.8F.A3.E8.BF.94.E5.9B.9E.E7.A0.81.E8.AF.B4.E6.98.8E">客服管理接口返回码</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/9/6fff6f191ef92c126b043ada035cc935.html#.E5.88.A0.E9.99.A4.E5.AE.A2.E6.9C.8D.E8.B4.A6.E5.8F.B7">删除客服账号</a>
*/
public JsonResult deleteAccount(String id) throws WeixinException {
Token token = tokenHolder.getToken();
String custom_delete_uri = getRequestUri("custom_delete_uri");
Response response = request.get(String.format(custom_delete_uri,
token.getAccessToken(), id));
return response.getAsJsonResult();
}
/**
* 创建会话
* <p>
* 开发者可以使用本接口为多客服的客服工号创建会话将某个客户直接指定给客服工号接待需要注意此接口不会受客服自动接入数以及自动接入开关限制
* 只能为在线的客服PC客户端在线或者已绑定多客服助手创建会话
* </p>
*
* @param userOpenId
* 用户的userOpenId
* @param kfAccount
* 完整客服账号格式为账号前缀@公众号微信号
* @param text
* 附加信息文本会展示在客服人员的多客服客户端
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html#.E5.88.9B.E5.BB.BA.E4.BC.9A.E8.AF.9D">创建会话</a>
*/
public JsonResult createKfSession(String userOpenId, String kfAccount,
String text) throws WeixinException {
Token token = tokenHolder.getToken();
String kfsession_create_uri = getRequestUri("kfsession_create_uri");
JSONObject obj = new JSONObject();
obj.put("openid", userOpenId);
obj.put("kf_account", kfAccount);
obj.put("text", text);
Response response = request.post(
String.format(kfsession_create_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 关闭会话
*
* @param userOpenId
* 用户的userOpenId
* @param kfAccount
* 完整客服账号格式为账号前缀@公众号微信号
* @param text
* 附加信息文本会展示在客服人员的多客服客户端
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html#.E5.85.B3.E9.97.AD.E4.BC.9A.E8.AF.9D">创建会话</a>
*/
public JsonResult closeKfSession(String userOpenId, String kfAccount,
String text) throws WeixinException {
Token token = tokenHolder.getToken();
String kfsession_close_uri = getRequestUri("kfsession_close_uri");
JSONObject obj = new JSONObject();
obj.put("openid", userOpenId);
obj.put("kf_account", kfAccount);
obj.put("text", text);
Response response = request.post(
String.format(kfsession_close_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 获取客户的会话状态:获取客户当前的会话状态
*
* @param userOpenId
* 用户的openid
* @return 会话对象
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.KfSession
* @see <a
* href="http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html#.E8.8E.B7.E5.8F.96.E5.AE.A2.E6.88.B7.E7.9A.84.E4.BC.9A.E8.AF.9D.E7.8A.B6.E6.80.81">获取会话状态</a>
*/
public KfSession getKfSession(String userOpenId) throws WeixinException {
Token token = tokenHolder.getToken();
String kfsession_get_uri = getRequestUri("kfsession_get_uri");
Response response = request.get(String.format(kfsession_get_uri,
token.getAccessToken(), userOpenId));
KfSession session = response
.getAsObject(new TypeReference<KfSession>() {
});
session.setUserOpenId(userOpenId);
return session;
}
/**
* 获取客服的会话列表:获取某个客服正在接待的会话列表
*
* @param kfAccount
* 完整客服账号格式为账号前缀@公众号微信号账号前缀最多10个字符必须是英文或者数字字符
* @return 会话列表
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.KfSession
* @see <a
* href="http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html#.E8.8E.B7.E5.8F.96.E5.AE.A2.E6.9C.8D.E7.9A.84.E4.BC.9A.E8.AF.9D.E5.88.97.E8.A1.A8">获取客服的会话列表</a>
*/
public List<KfSession> getKfSessionList(String kfAccount)
throws WeixinException {
Token token = tokenHolder.getToken();
String kfsession_list_uri = getRequestUri("kfsession_list_uri");
Response response = request.get(String.format(kfsession_list_uri,
token.getAccessToken(), kfAccount));
List<KfSession> sessionList = JSON.parseArray(response.getAsJson()
.getString("sessionlist"), KfSession.class);
return sessionList;
}
/**
* 获取未接入会话列表:获取当前正在等待队列中的会话列表此接口最多返回最早进入队列的100个未接入会话</br>
* <font color="red">缺陷没有count字段</font>
* @return 会话列表
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.KfSession
* @see <a
* href="http://mp.weixin.qq.com/wiki/2/6c20f3e323bdf5986cfcb33cbd3b829a.html#.E8.8E.B7.E5.8F.96.E6.9C.AA.E6.8E.A5.E5.85.A5.E4.BC.9A.E8.AF.9D.E5.88.97.E8.A1.A8">获取客服的会话列表</a>
*/
public List<KfSession> getKfSessionWaitList() throws WeixinException {
Token token = tokenHolder.getToken();
String kfsession_wait_uri = getRequestUri("kfsession_wait_uri");
Response response = request.get(String.format(kfsession_wait_uri,
token.getAccessToken()));
List<KfSession> sessionList = JSON.parseArray(response.getAsJson()
.getString("waitcaselist"), KfSession.class);
return sessionList;
}
}

View File

@ -1,133 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.type.DatacubeType;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.DateUtil;
/**
* 数据分析API
* <p>
* 1接口侧的公众号数据的数据库中仅存储了2014年12月1日之后的数据将查询不到在此之前的日期即使有查到也是不可信的脏数据</br>
* 2请开发者在调用接口获取数据后将数据保存在自身数据库中即加快下次用户的访问速度也降低了微信侧接口调用的不必要损耗</br>
* </p>
*
* @className DataApi
* @author jy
* @date 2015年1月7日
* @since JDK 1.7
* @see
*/
public class DataApi extends MpApi {
private final TokenHolder tokenHolder;
public DataApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 数据统计
*
* @param datacubeType
* 统计类型
* @param beginDate
* 开始日期
* @param offset
* 增量 表示向前几天 比如 offset=1 则查询 beginDate的后一天之间的数据
* @see {@link com.foxinmy.weixin4j.mp.api.DataApi#datacube(DatacubeType, Date,Date)}
* @throws WeixinException
*/
public List<?> datacube(DatacubeType datacubeType, Date beginDate,
int offset) throws WeixinException {
Calendar ca = Calendar.getInstance();
ca.setTime(beginDate);
ca.add(Calendar.DAY_OF_MONTH, offset);
return datacube(datacubeType, beginDate, ca.getTime());
}
/**
* 数据统计
*
* @param datacubeType
* 统计类型
* @param offset
* 增量 表示向后几天 比如 offset=1 则查询 beginDate的前一天之间的数据
* @param endDate
* 截至日期
* @see {@link com.foxinmy.weixin4j.mp.api.DataApi#datacube(DatacubeType, Date,Date)}
* @throws WeixinException
*/
public List<?> datacube(DatacubeType datacubeType, int offset, Date endDate)
throws WeixinException {
Calendar ca = Calendar.getInstance();
ca.setTime(endDate);
ca.add(Calendar.DAY_OF_MONTH, 0 - offset);
return datacube(datacubeType, ca.getTime(), endDate);
}
/**
* 查询日期跨度为0的统计数据(当天)
*
* @param datacubeType
* 统计类型
* @param date
* 统计日期
* @see {@link com.foxinmy.weixin4j.mp.api.DataApi#datacube(DatacubeType, Date,Date)}
* @throws WeixinException
*/
public List<?> datacube(DatacubeType datacubeType, Date date)
throws WeixinException {
return datacube(datacubeType, date, date);
}
/**
* 数据统计
*
* @param datacubeType
* 数据统计类型
* @param beginDate
* 获取数据的起始日期begin_date和end_date的差值需小于最大时间跨度比如最大时间跨度为1时
* begin_date和end_date的差值只能为0才能小于1否则会报错
* @param endDate
* 获取数据的结束日期end_date允许设置的最大值为昨日
* @see com.foxinmy.weixin4j.mp.datacube.UserSummary
* @see com.foxinmy.weixin4j.mp.datacube.ArticleSummary
* @see com.foxinmy.weixin4j.mp.datacube.ArticleTotal
* @see com.foxinmy.weixin4j.mp.datacube.ArticleDatacubeShare
* @see com.foxinmy.weixin4j.mp.datacube.UpstreamMsg
* @see com.foxinmy.weixin4j.mp.datacube.UpstreamMsgDist
* @see com.foxinmy.weixin4j.mp.datacube.InterfaceSummary
* @return 统计结果
* @see <a
* href="http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html">用户分析</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/8/c0453610fb5131d1fcb17b4e87c82050.html">图文分析</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/12/32d42ad542f2e4fc8a8aa60e1bce9838.html">消息分析</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/8/30ed81ae38cf4f977194bf1a5db73668.html">接口分析</a>
* @throws WeixinException
*/
public List<?> datacube(DatacubeType datacubeType, Date beginDate,
Date endDate) throws WeixinException {
String datacube_uri = getRequestUri("datacube_uri");
Token token = tokenHolder.getToken();
JSONObject obj = new JSONObject();
obj.put("begin_date", DateUtil.fortmat2yyyy_MM_dd(beginDate));
obj.put("end_date", DateUtil.fortmat2yyyy_MM_dd(endDate));
Response response = request.post(String.format(datacube_uri,
datacubeType.name().toLowerCase(), token.getAccessToken()), obj
.toJSONString());
return JSON.parseArray(response.getAsJson().getString("list"),
datacubeType.getClazz());
}
}

View File

@ -1,189 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.model.Group;
import com.foxinmy.weixin4j.token.TokenHolder;
/**
* 分组相关API
*
* @className GroupApi
* @author jy.hu
* @date 2014年9月25日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/13/be5272dc4930300ba561d927aead2569.html">分组接口</a>
* @see com.foxinmy.weixin4j.mp.model.Group
*/
public class GroupApi extends MpApi {
private final TokenHolder tokenHolder;
public GroupApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 创建分组
*
* @param name
* 组名称
* @return group对象
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/13/be5272dc4930300ba561d927aead2569.html#.E5.88.9B.E5.BB.BA.E5.88.86.E7.BB.84">创建分组</a>
* @see com.foxinmy.weixin4j.mp.model.Group
* @see com.foxinmy.weixin4j.mp.model.Group#toCreateJson()
*/
public Group createGroup(String name) throws WeixinException {
String group_create_uri = getRequestUri("group_create_uri");
Token token = tokenHolder.getToken();
Group group = new Group(name);
Response response = request.post(
String.format(group_create_uri, token.getAccessToken()),
group.toCreateJson());
return response.getAsJson().getObject("group", Group.class);
}
/**
* 查询所有分组
*
* @return 组集合
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/13/be5272dc4930300ba561d927aead2569.html#.E6.9F.A5.E8.AF.A2.E6.89.80.E6.9C.89.E5.88.86.E7.BB.844">查询所有分组</a>
* @see com.foxinmy.weixin4j.mp.model.Group
*/
public List<Group> getGroups() throws WeixinException {
String group_get_uri = getRequestUri("group_get_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(group_get_uri,
token.getAccessToken()));
return JSON.parseArray(response.getAsJson().getString("groups"),
Group.class);
}
/**
* 查询用户所在分组
*
* @param openId
* 用户对应的ID
* @return 组ID
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/13/be5272dc4930300ba561d927aead2569.html#.E6.9F.A5.E8.AF.A2.E7.94.A8.E6.88.B7.E6.89.80.E5.9C.A8.E5.88.86.E7.BB.84">查询用户所在分组</a>
* @see com.foxinmy.weixin4j.mp.model.Group
*/
public int getGroupByOpenId(String openId) throws WeixinException {
String group_getid_uri = getRequestUri("group_getid_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(group_getid_uri, token.getAccessToken()),
String.format("{\"openid\":\"%s\"}", openId));
return response.getAsJson().getIntValue("groupid");
}
/**
* 修改分组名
*
* @param groupId
* 组ID
* @param name
* 组名称
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/13/be5272dc4930300ba561d927aead2569.html#.E4.BF.AE.E6.94.B9.E5.88.86.E7.BB.84.E5.90.8D">修改分组名</a>
* @see com.foxinmy.weixin4j.mp.model.Group
* @see com.foxinmy.weixin4j.mp.model.Group#toModifyJson()
*/
public JsonResult modifyGroup(int groupId, String name)
throws WeixinException {
String group_modify_uri = getRequestUri("group_modify_uri");
Token token = tokenHolder.getToken();
Group group = new Group(groupId, name);
Response response = request.post(
String.format(group_modify_uri, token.getAccessToken()),
group.toModifyJson());
return response.getAsJsonResult();
}
/**
* 移动用户到分组
*
* @param groupId
* 组ID
* @param openId
* 用户对应的ID
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/13/be5272dc4930300ba561d927aead2569.html#.E7.A7.BB.E5.8A.A8.E7.94.A8.E6.88.B7.E5.88.86.E7.BB.84">移动分组</a>
* @see com.foxinmy.weixin4j.mp.model.Group
*/
public JsonResult moveGroup(int groupId, String openId)
throws WeixinException {
String group_move_uri = getRequestUri("group_move_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(group_move_uri,
token.getAccessToken()), String.format(
"{\"openid\":\"%s\",\"to_groupid\":%d}", openId, groupId));
return response.getAsJsonResult();
}
/**
* 批量移动分组
*
* @param groupId
* 组ID
* @param openIds
* 用户ID列表(不能超过50个)
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html#.E6.89.B9.E9.87.8F.E7.A7.BB.E5.8A.A8.E7.94.A8.E6.88.B7.E5.88.86.E7.BB.84">批量移动分组</a>
* @see com.foxinmy.weixin4j.mp.model.Group
*/
public JsonResult moveGroup(int groupId, String... openIds)
throws WeixinException {
String group_batchmove_uri = getRequestUri("group_batchmove_uri");
Token token = tokenHolder.getToken();
JSONObject obj = new JSONObject();
obj.put("to_groupid", groupId);
obj.put("openid_list", openIds);
Response response = request.post(
String.format(group_batchmove_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 删除用户分组,所有该分组内的用户自动进入默认分组.
*
* @param groupId
* 组ID
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/0/56d992c605a97245eb7e617854b169fc.html#.E5.88.A0.E9.99.A4.E5.88.86.E7.BB.84">删除用户分组</a>
* @see com.foxinmy.weixin4j.mp.model.Group
*/
public JsonResult deleteGroup(int groupId) throws WeixinException {
String group_delete_uri = getRequestUri("group_delete_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(group_delete_uri, token.getAccessToken()),
String.format("{\"group\":{\"id\":%d}}", groupId));
return response.getAsJsonResult();
}
}

View File

@ -1,234 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.deserializer.ExtraProcessor;
import com.alibaba.fastjson.serializer.NameFilter;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Button;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.model.AutoReplySetting;
import com.foxinmy.weixin4j.mp.model.MenuSetting;
import com.foxinmy.weixin4j.mp.model.SemQuery;
import com.foxinmy.weixin4j.mp.model.SemResult;
import com.foxinmy.weixin4j.msg.model.MpArticle;
import com.foxinmy.weixin4j.token.TokenHolder;
/**
* 辅助相关API
*
* @className HelperApi
* @author jy.hu
* @date 2014年9月26日
* @since JDK 1.7
* @see
*/
public class HelperApi extends MpApi {
private final TokenHolder tokenHolder;
public HelperApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 长链接转短链接
*
* @param url
* 待转换的链接
* @return 短链接
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/10/165c9b15eddcfbd8699ac12b0bd89ae6.html">长链接转短链接</a>
*/
public String getShorturl(String url) throws WeixinException {
String shorturl_uri = getRequestUri("shorturl_uri");
Token token = tokenHolder.getToken();
JSONObject obj = new JSONObject();
obj.put("action", "long2short");
obj.put("long_url", url);
Response response = request.post(
String.format(shorturl_uri, token.getAccessToken()),
obj.toJSONString());
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/0/0ce78b3c9524811fee34aba3e33f3448.html">语义理解</a>
* @throws WeixinException
*/
public SemResult semantic(SemQuery semQuery) throws WeixinException {
String semantic_uri = getRequestUri("semantic_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(semantic_uri, token.getAccessToken()),
semQuery.toJson());
return response.getAsObject(new TypeReference<SemResult>() {
});
}
/**
* 获取微信服务器IP地址
*
* @return IP地址
* @see <a
* href="http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html">获取IP地址</a>
* @throws WeixinException
*/
public List<String> getcallbackip() throws WeixinException {
String getcallbackip_uri = getRequestUri("getcallbackip_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(getcallbackip_uri,
token.getAccessToken()));
return JSON.parseArray(response.getAsJson().getString("ip_list"),
String.class);
}
/**
* 获取公众号当前使用的自定义菜单的配置如果公众号是通过API调用设置的菜单则返回菜单的开发配置
* 而如果公众号是在公众平台官网通过网站功能发布菜单则本接口返回运营者设置的菜单配置
*
* @return 菜单集合
* @see {@link com.foxinmy.weixin4j.mp.api.MenuApi#getMenu()}
* @see <a
* href="http://mp.weixin.qq.com/wiki/17/4dc4b0514fdad7a5fbbd477aa9aab5ed.html">获取自定义菜单配置</a>
* @see com.foxinmy.weixin4j.model.Button
* @see com.foxinmy.weixin4j.mp.model.MenuSetting
* @see com.foxinmy.weixin4j.msg.model.MpArticle
* @throws WeixinException
*/
public MenuSetting getMenuSetting() throws WeixinException {
String menu_get_selfmenu_uri = getRequestUri("menu_get_selfmenu_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(menu_get_selfmenu_uri,
token.getAccessToken()));
JSONObject result = response.getAsJson();
JSONArray buttons = result.getJSONObject("selfmenu_info").getJSONArray(
"button");
List<Button> _buttons = new ArrayList<Button>();
JSONObject buttonObj = null;
Object subButton = null;
for (int i = 0; i < buttons.size(); i++) {
buttonObj = buttons.getJSONObject(i);
subButton = buttonObj.remove("sub_button");
if (subButton != null) {
buttonObj.put("sub_button",
((JSONObject) subButton).getJSONArray("list"));
}
Button button = JSON.parseObject(
JSON.toJSONString(buttonObj, ArticleNameFilter.global),
Button.class, NewsExtraProcessor.global);
_buttons.add(button);
}
return new MenuSetting(result.getBooleanValue("is_menu_open"), _buttons);
}
private static final class NewsExtraProcessor implements ExtraProcessor {
private static NewsExtraProcessor global = new NewsExtraProcessor();
private NewsExtraProcessor() {
}
@Override
public void processExtra(Object object, String key, Object value) {
if (key.equals("news_info")) {
List<MpArticle> news = JSON
.parseArray(((JSONObject) value).getString("list"),
MpArticle.class);
JSONPath.set(object, "$.content", news);
}
}
};
private static final class ArticleNameFilter implements NameFilter {
private static final ArticleNameFilter global = new ArticleNameFilter();
private ArticleNameFilter() {
}
@Override
public String process(Object object, String name, Object value) {
if (name.equals("url") || name.equals("key")) {
return "content";
}
if (name.equals("show_cover")) {
return "show_cover_pic";
}
if (name.equals("source_url")) {
return "content_source_url";
}
return name;
}
}
/**
* 获取公众号当前使用的自动回复规则包括关注后自动回复消息自动回复60分钟内触发一次关键词自动回复
*
* @see com.foxinmy.weixin4j.mp.model.AutoReplySetting
* @see <a
* href="http://mp.weixin.qq.com/wiki/7/7b5789bb1262fb866d01b4b40b0efecb.html">获取自动回复规则</a>
* @throws WeixinException
*/
public AutoReplySetting getAutoReplySetting() throws WeixinException {
String autoreply_setting_get_uri = getRequestUri("autoreply_setting_get_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(
autoreply_setting_get_uri, token.getAccessToken()));
JSONObject obj = response.getAsJson();
AutoReplySetting replySetting = JSON.toJavaObject(obj,
AutoReplySetting.class);
List<AutoReplySetting.Rule> ruleList = null;
if (obj.containsKey("keyword_autoreply_info")) {
JSONArray keywordList = obj.getJSONObject("keyword_autoreply_info")
.getJSONArray("list");
ruleList = new ArrayList<AutoReplySetting.Rule>(keywordList.size());
JSONObject keywordObj = null;
JSONArray replyList = null;
JSONObject replyObj = null;
for (int i = 0; i < keywordList.size(); i++) {
keywordObj = keywordList.getJSONObject(i);
AutoReplySetting.Rule rule = JSON.toJavaObject(keywordObj,
AutoReplySetting.Rule.class);
replyList = keywordObj.getJSONArray("reply_list_info");
List<AutoReplySetting.Entry> entryList = new ArrayList<AutoReplySetting.Entry>(
replyList.size());
for (int j = 0; j < replyList.size(); j++) {
replyObj = replyList.getJSONObject(j);
if (replyObj.getString("type").equals("news")) {
entryList.add(JSON.parseObject(JSON.toJSONString(
replyObj, ArticleNameFilter.global),
AutoReplySetting.Entry.class,
NewsExtraProcessor.global));
} else {
entryList.add(JSON.toJavaObject(replyObj,
AutoReplySetting.Entry.class));
}
}
rule.setReplyList(entryList);
ruleList.add(rule);
}
}
replySetting.setKeywordReplyList(ruleList);
return replySetting;
}
}

View File

@ -1,328 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.File;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.msg.model.Base;
import com.foxinmy.weixin4j.msg.model.Massable;
import com.foxinmy.weixin4j.msg.model.MpArticle;
import com.foxinmy.weixin4j.msg.model.MpNews;
import com.foxinmy.weixin4j.msg.model.Video;
import com.foxinmy.weixin4j.token.TokenHolder;
/**
* 群发相关API
*
* @className MassApi
* @author jy.hu
* @date 2014年9月25日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html">群发接口</a>
*/
public class MassApi extends MpApi {
private final TokenHolder tokenHolder;
public MassApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 上传图文消息,一个图文消息支持1到10条图文</br> <font
* color="red">具备微信支付权限的公众号在使用高级群发接口上传群发图文消息类型时可使用&lta&gt标签加入外链</font>
*
* @param articles
* 图片消息
* @return 媒体ID
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E4.B8.8A.E4.BC.A0.E5.9B.BE.E6.96.87.E6.B6.88.E6.81.AF.E7.B4.A0.E6.9D.90.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91">上传图文素材</a>
* @see com.foxinmy.weixin4j.msg.model.MpArticle
*/
public String uploadArticle(List<MpArticle> articles)
throws WeixinException {
String article_upload_uri = getRequestUri("article_upload_uri");
Token token = tokenHolder.getToken();
JSONObject obj = new JSONObject();
obj.put("articles", articles);
Response response = request.post(
String.format(article_upload_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJson().getString("media_id");
}
/**
* 上传分组群发的视频素材
*
* @param video
* 视频对象 其中mediaId媒体文件中上传得到的Id 不能为空
* @return 上传后的ID 可用于群发视频消息
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html">高级群发</a>
* @see com.foxinmy.weixin4j.msg.model.Video
* @see com.foxinmy.weixin4j.msg.model.MpVideo
* @see {@link com.foxinmy.weixin4j.mp.api.MediaApi#uploadMedia(File)}
*/
public String uploadVideo(Video video) throws WeixinException {
String video_upload_uri = getRequestUri("video_upload_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(video_upload_uri, token.getAccessToken()),
JSON.toJSONString(video));
return response.getAsJson().getString("media_id");
}
/**
* 分组群发
*
* @param box
* 消息对象
* @param groupId
* 分组ID
* @return 群发后的消息ID
* @see {@link com.foxinmy.weixin4j.mp.api.MassApi#massMessage(Base,boolean,int)}
* @throws WeixinException
*/
public String massByGroupId(Base box, int groupId) throws WeixinException {
return massMessage(box, false, groupId);
}
/**
* 群发消息
* <p>
* 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息,
* 如消息有时会进行审核服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕
* </p>
*
* @param box
* 消息对象
* @param isToAll
* 用于设定是否向全部用户发送值为true或false选择true该消息群发给所有用户
* 选择false可根据group_id发送给指定群组的用户
* @param groupId
* 分组ID
* @return 群发后的消息ID
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.Group
* @see com.foxinmy.weixin4j.msg.model.Text
* @see com.foxinmy.weixin4j.msg.model.Image
* @see com.foxinmy.weixin4j.msg.model.Voice
* @see com.foxinmy.weixin4j.msg.model.MpVideo
* @see com.foxinmy.weixin4j.msg.model.MpNews
* @see {@link com.foxinmy.weixin4j.mp.api.GroupApi#getGroups()}
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E6.A0.B9.E6.8D.AE.E5.88.86.E7.BB.84.E8.BF.9B.E8.A1.8C.E7.BE.A4.E5.8F.91.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91">根据分组群发</a>
*/
public String massMessage(Base box, boolean isToAll, int groupId)
throws WeixinException {
if (box instanceof MpNews) {
MpNews _news = (MpNews) box;
List<MpArticle> _articles = _news.getArticles();
if (StringUtils.isBlank(_news.getMediaId())
&& (_articles == null || _articles.isEmpty())) {
throw new WeixinException(
"mass fail:mediaId or articles is required");
}
box = new MpNews(uploadArticle(_articles));
}
if (!(box instanceof Massable)) {
throw new WeixinException(String.format(
"%s not implement Massable", box.getClass()));
}
String msgtype = box.getMediaType().name();
JSONObject obj = new JSONObject();
JSONObject item = new JSONObject();
item.put("is_to_all", isToAll);
if (!isToAll) {
item.put("group_id", groupId);
}
obj.put("filter", item);
obj.put(msgtype, JSON.toJSON(box));
obj.put("msgtype", msgtype);
String mass_group_uri = getRequestUri("mass_group_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(mass_group_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJson().getString("msg_id");
}
/**
* 分组ID群发图文消息
*
* @param articles
* 图文列表
* @param groupId
* 分组ID
* @return 群发后的消息ID
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E6.A0.B9.E6.8D.AE.E5.88.86.E7.BB.84.E8.BF.9B.E8.A1.8C.E7.BE.A4.E5.8F.91.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91">根据分组群发</a>
* @see {@link com.foxinmy.weixin4j.mp.api.MassApi#massByGroupId(Base,int)}
* @see com.foxinmy.weixin4j.msg.model.MpArticle
* @throws WeixinException
*/
public String massArticleByGroupId(List<MpArticle> articles, int groupId)
throws WeixinException {
String mediaId = uploadArticle(articles);
return massByGroupId(new MpNews(mediaId), groupId);
}
/**
* openId群发
*
* @param box
* 消息对象
* @param openIds
* openId列表
* @return 群发后的消息ID
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.User
* @see com.foxinmy.weixin4j.msg.model.Text
* @see com.foxinmy.weixin4j.msg.model.Image
* @see com.foxinmy.weixin4j.msg.model.Voice
* @see com.foxinmy.weixin4j.msg.model.MpVideo
* @see com.foxinmy.weixin4j.msg.model.MpNews
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E6.A0.B9.E6.8D.AEOpenID.E5.88.97.E8.A1.A8.E7.BE.A4.E5.8F.91.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8D.E5.8F.AF.E7.94.A8.EF.BC.8C.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.8F.AF.E7.94.A8.E3.80.91">根据openid群发</a>
* @see {@link com.foxinmy.weixin4j.mp.api.UserApi#getUser(String)}
*/
public String massByOpenIds(Base box, String... openIds)
throws WeixinException {
if (box instanceof MpNews) {
MpNews _news = (MpNews) box;
List<MpArticle> _articles = _news.getArticles();
if (StringUtils.isBlank(_news.getMediaId())
&& (_articles == null || _articles.isEmpty())) {
throw new WeixinException(
"mass fail:mediaId or articles is required");
}
box = new MpNews(uploadArticle(_articles));
}
if (!(box instanceof Massable)) {
throw new WeixinException(String.format(
"%s not implement Massable", box.getClass()));
}
String msgtype = box.getMediaType().name();
JSONObject obj = new JSONObject();
obj.put("touser", openIds);
obj.put(msgtype, JSON.toJSON(box));
obj.put("msgtype", msgtype);
String mass_openid_uri = getRequestUri("mass_openid_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(mass_openid_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJson().getString("msg_id");
}
/**
* 根据openid群发图文消息
*
* @param articles
* 图文列表
* @param openIds
* openId列表
* @return 群发后的消息ID
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E6.A0.B9.E6.8D.AEOpenID.E5.88.97.E8.A1.A8.E7.BE.A4.E5.8F.91.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8D.E5.8F.AF.E7.94.A8.EF.BC.8C.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.8F.AF.E7.94.A8.E3.80.91">根据openid群发</a>
* @see {@link com.foxinmy.weixin4j.mp.api.MassApi#massByOpenIds(Base,String...)}
* @see com.foxinmy.weixin4j.msg.model.MpArticle
* @throws WeixinException
*/
public String massArticleByOpenIds(List<MpArticle> articles,
String... openIds) throws WeixinException {
String mediaId = uploadArticle(articles);
return massByOpenIds(new MpNews(mediaId), openIds);
}
/**
* 删除群发消息
* <p>
* 请注意,只有已经发送成功的消息才能删除删除消息只是将消息的图文详情页失效,已经收到的用户,还是能在其本地看到消息卡片
* </p>
*
* @param msgid
* 发送出去的消息ID
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E5.88.A0.E9.99.A4.E7.BE.A4.E5.8F.91.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91">删除群发</a>
* @see {@link com.foxinmy.weixin4j.mp.api.MassApi#massByGroupId(Base, int)}
* @see {@link com.foxinmy.weixin4j.mp.api.MassApi#massByOpenIds(Base, String...)
*/
public JsonResult deleteMassNews(String msgid) throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("msgid", msgid);
String mass_delete_uri = getRequestUri("mass_delete_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(mass_delete_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 预览群发消息</br> 开发者可通过该接口发送消息给指定用户在手机端查看消息的样式和排版
*
* @param openId
* 接收用户的ID
* @param box
* 消息体
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E9.A2.84.E8.A7.88.E6.8E.A5.E5.8F.A3.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91">预览群发消息</a>
*/
public JsonResult previewMassNews(String openId, Base box)
throws WeixinException {
String msgtype = box.getMediaType().name();
JSONObject obj = new JSONObject();
obj.put("touser", openId);
obj.put(msgtype, JSON.toJSON(box));
obj.put("msgtype", msgtype);
String mass_preview_uri = getRequestUri("mass_preview_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(mass_preview_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 查询群发发送状态
*
* @param msgId
* 消息ID
* @return 消息发送状态
* @throws WeixinException
* @see {@link com.foxinmy.weixin4j.msg.event.MassEventMessage#getStatusDesc(String)}
* @see <a
* href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E6.9F.A5.E8.AF.A2.E7.BE.A4.E5.8F.91.E6.B6.88.E6.81.AF.E5.8F.91.E9.80.81.E7.8A.B6.E6.80.81.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91">查询群发状态</a>
*/
public String getMassNews(String msgId) throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("msg_id", msgId);
String mass_get_uri = getRequestUri("mass_get_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(mass_get_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJson().getString("msg_status");
}
}

View File

@ -1,474 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.StringBody;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.deserializer.ExtraProcessor;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.PartParameter;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.model.MediaCounter;
import com.foxinmy.weixin4j.mp.model.MediaItem;
import com.foxinmy.weixin4j.mp.model.MediaRecord;
import com.foxinmy.weixin4j.msg.model.MpArticle;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.type.MediaType;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.FileUtil;
import com.foxinmy.weixin4j.util.IOUtil;
/**
* 素材相关API
*
* @className MediaApi
* @author jy.hu
* @date 2014年9月25日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.type.MediaType
*/
public class MediaApi extends MpApi {
private final TokenHolder tokenHolder;
public MediaApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 上传媒体文件
*
* @param file
* 文件对象
* @param isMaterial
* 是否永久上传
* @return 上传到微信服务器返回的媒体标识
* @see {@link com.foxinmy.weixin4j.mp.api.MediaApi#uploadMedia(File, MediaType)}
* @throws WeixinException
* @throws IOException
*/
public String uploadMedia(File file, boolean isMaterial)
throws WeixinException, IOException {
String mediaTypeKey = IOUtil.getExtension(file.getName());
if (StringUtils.isBlank(mediaTypeKey)) {
mediaTypeKey = FileUtil.getFileType(file);
}
MediaType mediaType = MediaType.getMediaType(mediaTypeKey);
return uploadMedia(file, mediaType, isMaterial);
}
/**
* 上传媒体文件</br> <font color="red">此接口只包含图片语音缩略图三种媒体类型的上传</font>
*
* @param file
* 文件对象
* @param mediaType
* 媒体类型 image语音voice和缩略图thumb
* @param isMaterial
* 是否永久上传
* @return 上传到微信服务器返回的媒体标识
* @throws WeixinException
* @see com.foxinmy.weixin4j.type.MediaType
* @see {@link com.foxinmy.weixin4j.mp.api.MediaApi#uploadMedia(String, byte[],String,boolean)}
*/
public String uploadMedia(File file, MediaType mediaType, boolean isMaterial)
throws WeixinException, IOException {
byte[] datas = IOUtil.toByteArray(new FileInputStream(file));
return uploadMedia(file.getName(), datas, mediaType.name(), isMaterial);
}
/**
* 上传媒体文件 </br> <font color="red">此接口只包含图片语音缩略图视频(临时)四种媒体类型的上传</font>
* <p>
* 正常情况下返回{"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789},
* 否则抛出异常.
* </p>
*
* @param fileName
* 文件名
* @param bytes
* 媒体数据包
* @param mediaType
* 媒体文件类型分别有图片image语音voice视频(video)和缩略图thumb
* @param isMaterial
* 是否永久上传
* @return 上传到微信服务器返回的媒体标识
* @see <a
* href="http://mp.weixin.qq.com/wiki/5/963fc70b80dc75483a271298a76a8d59.html">上传临时素材</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html">上传永久素材</a>
* @throws WeixinException
*/
public String uploadMedia(String fileName, byte[] bytes, String mediaType,
boolean isMaterial) throws WeixinException {
if (",image,voice,video,thumb,".indexOf(String
.format(",%s,", mediaType)) < 0) {
throw new WeixinException(String.format(
"unsupported media type:%s", mediaType));
}
if (mediaType.equals(MediaType.video.name()) && isMaterial) {
throw new WeixinException(
"please invoke uploadMaterialVideo method");
}
Token token = tokenHolder.getToken();
Response response = null;
if (isMaterial) {
String material_media_upload_uri = getRequestUri("material_media_upload_uri");
try {
response = request.post(String.format(
material_media_upload_uri, token.getAccessToken()),
new PartParameter("media", new ByteArrayBody(bytes,
fileName)), new PartParameter("type",
new StringBody(mediaType, Consts.UTF_8)));
} catch (UnsupportedEncodingException e) {
; // ignore
}
} else {
String file_upload_uri = getRequestUri("file_upload_uri");
response = request.post(String.format(file_upload_uri,
token.getAccessToken(), mediaType), new PartParameter(
"media", new ByteArrayBody(bytes, fileName)));
}
return response.getAsJson().getString("media_id");
}
/**
* 下载媒体素材
* <p>
* 正常情况下返回表头如Content-Type: image/jpeg,否则抛出异常.
* </p>
*
* @param mediaId
* 存储在微信服务器上的媒体标识
* @param mediaType
* 媒体文件类型分别有图片image语音voice视频video和缩略图thumb
* @return 写入硬盘后的文件对象
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/11/07b6b76a6b6e8848e855a435d5e34a5f.html">下载临时媒体文件</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/4/b3546879f07623cb30df9ca0e420a5d0.html">下载永久媒体素材</a>
* @see com.foxinmy.weixin4j.type.MediaType
* @see {@link com.foxinmy.weixin4j.mp.api.MediaApi#downloadMedia(String,boolean)}
*/
public File downloadMedia(String mediaId, MediaType mediaType,
boolean isMaterial) throws WeixinException {
if (",image,voice,video,thumb,".indexOf(String.format(",%s,",
mediaType.name())) < 0) {
throw new WeixinException(String.format(
"unsupported media type:%s", mediaType.name()));
}
String media_path = ConfigUtil.getValue("media_path");
File file = new File(media_path + File.separator + mediaId + "."
+ mediaType.getFormatName());
if (file.exists()) {
return file;
}
byte[] datas = downloadMedia(mediaId, isMaterial);
OutputStream os = null;
try {
if (file.createNewFile()) {
os = new FileOutputStream(file);
os.write(datas);
} else {
throw new WeixinException(String.format("create file fail:%s",
file.getAbsolutePath()));
}
} catch (IOException e) {
throw new WeixinException(e.getMessage());
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException ignore) {
;
}
}
return file;
}
/**
* 下载媒体素材
*
* @param mediaId
* 媒体ID
* @param isMaterial
* 是否下载永久素材
* @return 二进制数据包
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/11/07b6b76a6b6e8848e855a435d5e34a5f.html">下载临时媒体素材</a>
* @see <a
* href="http://mp.weixin.qq.com/wiki/4/b3546879f07623cb30df9ca0e420a5d0.html">下载永久媒体素材</a>
*/
public byte[] downloadMedia(String mediaId, boolean isMaterial)
throws WeixinException {
Token token = tokenHolder.getToken();
Response response = null;
if (isMaterial) {
JSONObject media = new JSONObject();
media.put("media_id", mediaId);
String material_media_download_uri = getRequestUri("material_media_download_uri");
response = request.post(
String.format(material_media_download_uri,
token.getAccessToken()), media.toJSONString());
} else {
String file_download_uri = getRequestUri("file_download_uri");
response = request.get(String.format(file_download_uri,
token.getAccessToken(), mediaId));
}
return response.getBody();
}
/**
* 上传永久图文素材
* <p>
* 新增的永久素材也可以在公众平台官网素材管理模块中看到,永久素材的数量是有上限的请谨慎新增图文消息素材和图片素材的上限为5000
* 其他类型为1000
* </P>
*
* @param articles
* 图文列表
* @return 上传到微信服务器返回的媒体标识
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html">上传永久媒体素材</a>
* @see com.foxinmy.weixin4j.msg.model.MpArticle
*/
public String uploadMaterialArticle(List<MpArticle> articles)
throws WeixinException {
Token token = tokenHolder.getToken();
String material_article_upload_uri = getRequestUri("material_article_upload_uri");
JSONObject obj = new JSONObject();
obj.put("articles", articles);
Response response = request.post(
String.format(material_article_upload_uri,
token.getAccessToken()), obj.toJSONString());
return response.getAsJson().getString("media_id");
}
/**
* 下载永久图文素材
*
* @param mediaId
* 媒体ID
* @return 图文列表
* @throws WeixinException
* @see <a href=
* "http://mp.weixin.qq.com/wiki/4/b3546879f07623cb30df9ca0e420a5d0.html">下载永久媒体素材</a>
* @see com.foxinmy.weixin4j.msg.model.MpArticle
*/
public List<MpArticle> downloadArticle(String mediaId)
throws WeixinException {
byte[] bytes = downloadMedia(mediaId, true);
JSONObject obj = JSON.parseObject(bytes, 0, bytes.length,
Consts.UTF_8.newDecoder(), JSONObject.class);
return JSON.parseArray(obj.getString("news_item"), MpArticle.class);
}
/**
* 更新永久图文素材
*
* @param mediaId
* 要修改的图文消息的id
* @param index
* 要更新的文章在图文消息中的位置多图文消息时此字段才有意义第一篇为0
* @param articles
* 图文列表
* @return 处理结果
* @throws WeixinException
* @see com.foxinmy.weixin4j.msg.model.MpArticle
* @see <a
* href="http://mp.weixin.qq.com/wiki/4/19a59cba020d506e767360ca1be29450.html">更新永久图文素材</a>
*/
public JsonResult updateMaterialArticle(String mediaId, int index,
List<MpArticle> articles) throws WeixinException {
Token token = tokenHolder.getToken();
String material_article_update_uri = getRequestUri("material_article_update_uri");
JSONObject obj = new JSONObject();
obj.put("articles", articles);
obj.put("media_id", mediaId);
obj.put("index", index);
Response response = request.post(
String.format(material_article_update_uri,
token.getAccessToken()), obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 删除永久媒体素材
*
* @param mediaId
* 媒体素材的media_id
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/5/e66f61c303db51a6c0f90f46b15af5f5.html">删除永久媒体素材</a>
*/
public JsonResult deleteMaterialMedia(String mediaId)
throws WeixinException {
Token token = tokenHolder.getToken();
String material_media_del_uri = getRequestUri("material_media_del_uri");
JSONObject obj = new JSONObject();
obj.put("media_id", mediaId);
Response response = request.post(
String.format(material_media_del_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
/**
* 上传永久视频素材
*
* @param file
* 大小不超过1M且格式为MP4的视频文件
* @param title
* 视频标题
* @param introduction
* 视频描述
* @return 上传到微信服务器返回的媒体标识
* @see <a
* href="http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html">上传永久媒体素材</a>
* @throws WeixinException
* @throws IOException
*/
public String uploadMaterialVideo(File file, String title,
String introduction) throws WeixinException, IOException {
String material_media_upload_uri = getRequestUri("material_media_upload_uri");
Token token = tokenHolder.getToken();
try {
JSONObject description = new JSONObject();
description.put("title", title);
description.put("introduction", introduction);
byte[] bytes = IOUtil.toByteArray(new FileInputStream(file));
Response response = request.post(
String.format(material_media_upload_uri,
token.getAccessToken()),
new PartParameter("media", new ByteArrayBody(bytes, file
.getName())),
new PartParameter("type", new StringBody(MediaType.video
.name(), Consts.UTF_8)),
new PartParameter("description", new StringBody(description
.toJSONString(), Consts.UTF_8)));
return response.getAsJson().getString("media_id");
} catch (UnsupportedEncodingException e) {
throw new WeixinException("unsupported encoding");
}
}
/**
* 获取永久媒体素材的总数</br> .图片和图文消息素材包括单图文和多图文的总数上限为5000其他素材的总数上限为1000
*
* @return 总数对象
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.MediaCounter
* @see <a
* href="http://mp.weixin.qq.com/wiki/16/8cc64f8c189674b421bee3ed403993b8.html">获取素材总数</a>
*/
public MediaCounter countMaterialMedia() throws WeixinException {
Token token = tokenHolder.getToken();
String material_media_count_uri = getRequestUri("material_media_count_uri");
Response response = request.get(String.format(material_media_count_uri,
token.getAccessToken()));
return response.getAsObject(new TypeReference<MediaCounter>() {
});
}
/**
* 获取媒体素材记录列表
*
* @param mediaType
* 素材的类型图片image视频video语音 voice图文news
* @param offset
* 从全部素材的该偏移位置开始返回0表示从第一个素材返回
* @param count
* 返回素材的数量取值在1到20之间
* @return 媒体素材的记录对象
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.MediaRecord
* @see com.foxinmy.weixin4j.type.MediaType
* @see com.foxinmy.weixin4j.mp.model.MediaItem
* @see <a
* href="http://mp.weixin.qq.com/wiki/12/2108cd7aafff7f388f41f37efa710204.html">获取素材列表</a>
*/
public MediaRecord listMaterialMedia(MediaType mediaType, int offset,
int count) throws WeixinException {
Token token = tokenHolder.getToken();
String material_media_list_uri = getRequestUri("material_media_list_uri");
JSONObject obj = new JSONObject();
obj.put("type", mediaType.name());
obj.put("offset", offset);
obj.put("count", count);
Response response = request.post(
String.format(material_media_list_uri, token.getAccessToken()),
obj.toJSONString());
MediaRecord mediaRecord = null;
if (mediaType == MediaType.news) {
mediaRecord = JSON.parseObject(response.getAsString(),
MediaRecord.class, new ExtraProcessor() {
@Override
public void processExtra(Object object, String key,
Object value) {
if (key.equals("content")) {
((MediaItem) object).setArticles(JSON
.parseArray(((JSONObject) value)
.getString("news_item"),
MpArticle.class));
}
}
});
} else {
mediaRecord = response
.getAsObject(new TypeReference<MediaRecord>() {
});
}
mediaRecord.setMediaType(mediaType);
return mediaRecord;
}
/**
* 获取全部的媒体素材
*
* @param mediaType
* 媒体类型
* @return 素材列表
* @see {@link com.foxinmy.weixin4j.mp.api.MediaApi#listMaterialMedia(MediaType, int, int)}
* @throws WeixinException
*/
public List<MediaItem> listAllMaterialMedia(MediaType mediaType)
throws WeixinException {
int offset = 0;
int count = 20;
List<MediaItem> mediaList = new ArrayList<MediaItem>();
MediaRecord mediaRecord = null;
for (;;) {
mediaRecord = listMaterialMedia(mediaType, offset, count);
mediaList.addAll(mediaRecord.getItems());
if (offset >= mediaRecord.getTotalCount()) {
break;
}
offset += count;
}
return mediaList;
}
}

View File

@ -1,114 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.NameFilter;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Button;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.type.ButtonType;
/**
* 菜单相关API
*
* @className MenuApi
* @author jy.hu
* @date 2014年9月25日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.model.Button
*/
public class MenuApi extends MpApi {
private final TokenHolder tokenHolder;
public MenuApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 自定义菜单
*
* @param btnList
* 菜单列表
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html">创建自定义菜单</a>
* @see com.foxinmy.weixin4j.model.Button
*/
public JsonResult createMenu(List<Button> btnList) throws WeixinException {
String menu_create_uri = getRequestUri("menu_create_uri");
Token token = tokenHolder.getToken();
JSONObject obj = new JSONObject();
obj.put("button", btnList);
Response response = request.post(
String.format(menu_create_uri, token.getAccessToken()),
JSON.toJSONString(obj, new NameFilter() {
@Override
public String process(Object object, String name,
Object value) {
if (object instanceof Button && name.equals("content")) {
if (((Button) object).getFormatType() == ButtonType.view) {
return "url";
} else {
return "key";
}
}
return name;
}
}));
return response.getAsJsonResult();
}
/**
* 查询菜单
*
* @return 菜单集合
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/16/ff9b7b85220e1396ffa16794a9d95adc.html">查询菜单</a>
* @see com.foxinmy.weixin4j.model.Button
*/
public List<Button> getMenu() throws WeixinException {
String menu_get_uri = getRequestUri("menu_get_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(menu_get_uri,
token.getAccessToken()));
String text = JSON.toJSONString(
response.getAsJson().getJSONObject("menu")
.getJSONArray("button"), new NameFilter() {
@Override
public String process(Object object, String name,
Object value) {
if (name.equals("url") || name.equals("key")) {
return "content";
}
return name;
}
});
return JSON.parseArray(text, Button.class);
}
/**
* 删除菜单
*
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/16/8ed41ba931e4845844ad6d1eeb8060c8.html">删除菜单</a>
* @return 处理结果
*/
public JsonResult deleteMenu() throws WeixinException {
String menu_delete_uri = getRequestUri("menu_delete_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(menu_delete_uri,
token.getAccessToken()));
return response.getAsJsonResult();
}
}

View File

@ -1,28 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.ResourceBundle;
import com.foxinmy.weixin4j.api.BaseApi;
/**
* 微信公众平台API
*
* @className MpApi
* @author jy.hu
* @date 2014年9月26日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.api.BaseApi
* @see <a href="http://mp.weixin.qq.com/wiki/index.php">api文档</a>
*/
public class MpApi extends BaseApi {
private final static ResourceBundle weixinBundle;
static {
weixinBundle = ResourceBundle
.getBundle("com/foxinmy/weixin4j/mp/api/weixin");
}
@Override
protected ResourceBundle getWeixinBundle() {
return weixinBundle;
}
}

View File

@ -1,91 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.message.NotifyMessage;
import com.foxinmy.weixin4j.msg.model.Base;
import com.foxinmy.weixin4j.msg.model.Notifyable;
import com.foxinmy.weixin4j.token.TokenHolder;
/**
* 客服消息API
*
* @className NotifyApi
* @author jy.hu
* @date 2014年9月26日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html#.E5.AE.A2.E6.9C.8D.E6.8E.A5.E5.8F.A3-.E5.8F.91.E6.B6.88.E6.81.AF">客服消息</a>
* @see com.foxinmy.weixin4j.mp.message.NotifyMessage
*/
public class NotifyApi extends MpApi {
private final TokenHolder tokenHolder;
public NotifyApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 发送客服消息(在48小时内不限制发送次数)
*
* @param notify
* 客服消息对象
* @return 处理结果
* @see {@link com.foxinmy.weixin4j.mp.api.NotifyApi#sendNotify(NotifyMessage, String)}
* @throws WeixinException
*/
public JsonResult sendNotify(NotifyMessage notify) throws WeixinException {
return sendNotify(notify, null);
}
/**
* 发送客服消息(在48小时内不限制发送次数)
*
* @param notify
* 客服消息对象
* @param kfAccount
* 客服账号 可为空
* @throws WeixinException
* @return 处理结果
* @see <a
* href="http://mp.weixin.qq.com/wiki/1/70a29afed17f56d537c833f89be979c9.html#.E5.AE.A2.E6.9C.8D.E6.8E.A5.E5.8F.A3-.E5.8F.91.E6.B6.88.E6.81.AF">发送客服消息</a>
* @see com.foxinmy.weixin4j.msg.model.Text
* @see com.foxinmy.weixin4j.msg.model.Image
* @see com.foxinmy.weixin4j.msg.model.Voice
* @see com.foxinmy.weixin4j.msg.model.Video
* @see com.foxinmy.weixin4j.msg.model.Music
* @see com.foxinmy.weixin4j.msg.model.News
* @see com.foxinmy.weixin4j.mp.message.NotifyMessage
*/
public JsonResult sendNotify(NotifyMessage notify, String kfAccount)
throws WeixinException {
Base box = notify.getBox();
if (!(box instanceof Notifyable)) {
throw new WeixinException(String.format(
"%s not implement Notifyable", box.getClass()));
}
String msgtype = box.getMediaType().name();
JSONObject obj = new JSONObject();
obj.put("touser", notify.getTouser());
obj.put("msgtype", msgtype);
obj.put(msgtype, box);
if (StringUtils.isNotBlank(kfAccount)) {
JSONObject kf = new JSONObject();
kf.put("kf_account", kfAccount);
obj.put("customservice", kf);
}
String custom_notify_uri = getRequestUri("custom_notify_uri");
Token token = tokenHolder.getToken();
Response response = request.post(
String.format(custom_notify_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
}

View File

@ -1,173 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.model.OauthToken;
import com.foxinmy.weixin4j.mp.model.User;
import com.foxinmy.weixin4j.util.ConfigUtil;
/**
* oauth授权
*
* @className OauthApi
* @author jy
* @date 2015年3月6日
* @since JDK 1.7
* @see <a
* href="https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&lang=zh_CN">微信登陆</a>
*/
public class OauthApi extends MpApi {
/**
* @see {@link com.foxinmy.weixin4j.mp.api.OauthApi#getAuthorizeURL(String, String,String)}
*
* @return
*/
public String getAuthorizeURL() {
return getAuthorizeURL("state");
}
/**
* @see {@link com.foxinmy.weixin4j.mp.api.OauthApi#getAuthorizeURL(String, String,String)}
*
* @return
*/
public String getAuthorizeURL(String state) {
String appId = ConfigUtil.getWeixinMpAccount().getId();
String redirectUri = ConfigUtil.getValue("redirect_uri");
return getAuthorizeURL(appId, redirectUri, state);
}
/**
* 请求CODE
*
* @param appId
* 应用ID
* @param redirectUri
* 重定向地址
* @param state
* 用于保持请求和回调的状态授权请求后原样带回给第三方
* @return 请求的URL
*/
public String getAuthorizeURL(String appId, String redirectUri, String state) {
String sns_user_auth_uri = getRequestUri("sns_user_auth_uri");
try {
return String.format(sns_user_auth_uri, appId,
URLEncoder.encode(redirectUri, Consts.UTF_8.name()),
"snsapi_login", state);
} catch (UnsupportedEncodingException e) {
;
}
return "";
}
/**
* @see {@link com.foxinmy.weixin4j.mp.api.OauthApi#getOauthToken(String, String,String)}
*
* @return
*/
public OauthToken getOauthToken(String code) throws WeixinException {
WeixinMpAccount account = ConfigUtil.getWeixinMpAccount();
return getOauthToken(code, account.getId(), account.getSecret());
}
/**
* oauth授权code获取token
*
* @param code
* 用户授权后返回的code
* @param appid
* 应用ID
* @param appsecret
* 应用密钥
* @return token对象
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.OauthToken
*/
public OauthToken getOauthToken(String code, String appid, String appsecret)
throws WeixinException {
String user_token_uri = getRequestUri("sns_user_token_uri");
Response response = request.get(String.format(user_token_uri, appid,
appsecret, code));
return response.getAsObject(new TypeReference<OauthToken>() {
});
}
/**
* @see {@link com.foxinmy.weixin4j.mp.api.OauthApi#getOauthToken(String, String,String)}
*
* @return
*/
public OauthToken refreshToken(String refreshToken) throws WeixinException {
WeixinMpAccount account = ConfigUtil.getWeixinMpAccount();
return refreshToken(account.getId(), refreshToken);
}
/**
* 刷新token
*
* @param appId
* 应用ID
* @param refreshToken
* 填写通过access_token获取到的refresh_token参数
* @return token对象
* @throws WeixinException
*/
public OauthToken refreshToken(String appId, String refreshToken)
throws WeixinException {
String sns_token_refresh_uri = getRequestUri("sns_token_refresh_uri");
Response response = request.get(String.format(sns_token_refresh_uri,
appId, refreshToken));
return response.getAsObject(new TypeReference<OauthToken>() {
});
}
/**
* 验证access_token是否正确
*
* @param accessToken
* 接口调用凭证
* @param openId
* 用户标识
* @return 验证结果
*/
public boolean authAccessToken(String accessToken, String openId) {
String sns_auth_token_uri = getRequestUri("sns_auth_token_uri");
try {
request.get(String.format(sns_auth_token_uri, accessToken, openId));
return true;
} catch (WeixinException e) {
;
}
return false;
}
/**
* oauth获取用户信息(需scope为 snsapi_userinfo)
*
* @param token
* 授权票据
* @return 用户对象
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html">拉取用户信息</a>
* @see com.foxinmy.weixin4j.mp.model.User
* @see com.foxinmy.weixin4j.mp.model.OauthToken
* @see {@link com.foxinmy.weixin4j.mp.api.UserApi#getOauthToken(String)}
*/
public User getUser(OauthToken token) throws WeixinException {
String user_info_uri = getRequestUri("sns_user_info_uri");
Response response = request.get(String.format(user_info_uri,
token.getAccessToken(), token.getOpenid()));
return response.getAsObject(new TypeReference<User>() {
});
}
}

View File

@ -1,466 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
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.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.fastjson.parser.Feature;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.http.SSLHttpRequest;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.conver.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.v2.Order;
import com.foxinmy.weixin4j.mp.payment.v2.RefundRecord;
import com.foxinmy.weixin4j.mp.payment.v2.RefundResult;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.RefundType;
import com.foxinmy.weixin4j.mp.type.SignType;
import com.foxinmy.weixin4j.mp.util.ExcelUtil;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.MapUtil;
/**
* V2支付API
*
* @className Pay2Api
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
public class Pay2Api extends PayApi {
private final HelperApi helperApi;
public Pay2Api(WeixinMpAccount weixinAccount, TokenHolder tokenHolder) {
super(weixinAccount, tokenHolder);
this.helperApi = new HelperApi(tokenHolder);
}
/**
* 订单查询
*
* @param idQuery
* 订单号
* @return 订单信息
* @see com.foxinmy.weixin4j.mp.payment.v2.Order
* @since V2
* @throws WeixinException
*/
public Order orderQuery(IdQuery idQuery) throws WeixinException {
String orderquery_uri = getRequestUri("orderquery_uri");
Token token = tokenHolder.getToken();
StringBuilder sb = new StringBuilder();
sb.append(idQuery.getType().getName()).append("=")
.append(idQuery.getId());
sb.append("&partner=").append(weixinAccount.getPartnerId());
String part = sb.toString();
sb.append("&key=").append(weixinAccount.getPartnerKey());
String sign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
sb.delete(0, sb.length());
sb.append(part).append("&sign=").append(sign);
String timestamp = DateUtil.timestamp2string();
JSONObject obj = new JSONObject();
obj.put("appid", weixinAccount.getId());
obj.put("appkey", weixinAccount.getPaySignKey());
obj.put("package", sb.toString());
obj.put("timestamp", timestamp);
String signature = PayUtil.paysignSha(obj);
obj.clear();
obj.put("appid", weixinAccount.getId());
obj.put("package", sb.toString());
obj.put("timestamp", timestamp);
obj.put("app_signature", signature);
obj.put("sign_method", SignType.SHA1.name().toLowerCase());
Response response = request.post(
String.format(orderquery_uri, token.getAccessToken()),
obj.toJSONString());
String order_info = response.getAsJson().getString("order_info");
Order order = JSON.parseObject(order_info, Order.class,
Feature.IgnoreNotMatch);
if (order.getRetCode() != 0) {
throw new WeixinException(Integer.toString(order.getRetCode()),
order.getRetMsg());
}
return order;
}
/**
* 申请退款(需要证书)</br>
* <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* </p>
*
* @param caFile
* 证书文件(V2版本后缀为*.pfx)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
* @param mopara
* opUserPasswd
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundResult
* @since V2
* @throws WeixinException
*/
@Override
protected RefundResult refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
String opUserId, Map<String, String> mopara) throws WeixinException {
String refund_uri = getRequestUri("refund_v2_uri");
Response response = null;
InputStream ca = null;
try {
ca = new FileInputStream(caFile);
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);
if (mopara != null && !mopara.isEmpty()) {
map.putAll(mopara);
}
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPartnerKey());
map.put("sign", sign.toUpperCase());
SSLContext ctx = null;
KeyStore ks = null;
String jksPwd = "";
File jksFile = new File(String.format("%s/tenpay_cacert.jks",
caFile.getParent()));
// 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());
SSLHttpRequest request = new SSLHttpRequest(ctx);
response = request.get(refund_uri, map);
} catch (WeixinException e) {
throw e;
} catch (Exception e) {
throw new WeixinException(e.getMessage());
} finally {
if (ca != null) {
try {
ca.close();
} catch (IOException e) {
;
}
}
}
return response.getAsObject(new TypeReference<RefundResult>() {
});
}
/**
* 退款申请
*
* @param caFile
* 证书文件(V2版本后缀为*.pfx)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
* @param opUserPasswd
* 操作员密码,默认为商户后台登录密码
* @see {@link com.foxinmy.weixin4j.mp.api.Pay2Api#refund(File, IdQuery, String, double, double, String, Map)}
*/
public RefundResult refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
String opUserId, String opUserPasswd) throws WeixinException {
Map<String, String> mopara = new HashMap<String, String>();
mopara.put("op_user_passwd", DigestUtils.md5Hex(opUserPasswd));
return refund(caFile, idQuery, outRefundNo, totalFee, refundFee,
opUserId, mopara);
}
/**
* 退款申请
*
* @param caFile
* 证书文件(V2版本后缀为*.pfx)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
* @param opUserPasswd
* 操作员密码,默认为商户后台登录密码
* @param recvUserId
* 转账退款接收退款的财付通帐号 一般无需填写,只有退银行失败,资金转入商 户号现金账号时(即状态为转入代发,查询返 回的
* refund_status 7 11),填写原退款 单号并填写此字段,资金才会退到指定财付通
* 账号其他情况此字段忽略
* @param reccvUserName
* 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
* @param refundType
* 为空或者填 1:商户号余额退款;2:现金帐号 退款;3:优先商户号退款,若商户号余额不足, 再做现金帐号退款使用 2
* 3 ,需联系财 付通开通此功能
* @see {@link com.foxinmy.weixin4j.mp.api.Pay2Api#refund(File, IdQuery, String, double, double, String, Map)}
* @return 退款结果
*/
public RefundResult refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
String opUserId, String opUserPasswd, String recvUserId,
String reccvUserName, RefundType refundType) throws WeixinException {
Map<String, String> mopara = new HashMap<String, String>();
mopara.put("op_user_passwd", DigestUtils.md5Hex(opUserPasswd));
if (StringUtils.isNotBlank(recvUserId)) {
mopara.put("recv_user_id", recvUserId);
}
if (StringUtils.isNotBlank(reccvUserName)) {
mopara.put("reccv_user_name", reccvUserName);
}
if (refundType != null) {
mopara.put("refund_type", Integer.toString(refundType.getVal()));
}
return refund(caFile, idQuery, outRefundNo, totalFee, refundFee,
opUserId, mopara);
}
/**
* 冲正订单(需要证书)</br><font color="red">V2暂不支持</font>
*
* @param caFile
* 证书文件(V2版本后缀为*.pfx)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @since V2
* @return 撤销结果
* @throws WeixinException
*/
public ApiResult reverse(File caFile, IdQuery idQuery)
throws WeixinException {
throw new WeixinException("V2 unsupport reverse api");
}
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 处理结果
* @since V2
* @throws WeixinException
*/
public ApiResult closeOrder(String outTradeNo) throws WeixinException {
throw new WeixinException("V2 unsupport closeOrder api");
}
/**
* 下载对账单<br>
* 1.微信侧未成功下单的交易不会出现在对账单中支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type
* REVOKED;<br>
* 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;<br>
* 3.对账单中涉及金额的字段单位为<br>
*
* @param billDate
* 下载对账单的日期 为空则取前一天
* @param billType
* 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单
* @return excel表格
* @since V2
* @throws WeixinException
*/
public File downloadbill(Date billDate, BillType billType)
throws WeixinException {
if (billDate == null) {
Calendar now = Calendar.getInstance();
now.add(Calendar.DAY_OF_MONTH, -1);
billDate = now.getTime();
}
if (billType == null) {
billType = BillType.ALL;
}
String formatBillDate = DateUtil.fortmat2yyyyMMdd(billDate);
String bill_path = ConfigUtil.getValue("bill_path");
String fileName = String.format("%s_%s_%s.xls", formatBillDate,
billType.name().toLowerCase(), weixinAccount.getId());
File file = new File(String.format("%s/%s", bill_path, fileName));
if (file.exists()) {
return file;
}
String downloadbill_uri = getRequestUri("downloadbill_v2_uri");
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("spid", weixinAccount.getPartnerId());
map.put("trans_time", DateUtil.fortmat2yyyy_MM_dd(billDate));
map.put("stamp", DateUtil.timestamp2string());
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 response = request.get(downloadbill_uri, map);
BufferedReader reader = null;
OutputStream os = null;
try {
reader = new BufferedReader(
new InputStreamReader(response.getStream(),
com.foxinmy.weixin4j.model.Consts.GBK));
String line = null;
List<String[]> bills = new LinkedList<String[]>();
while ((line = reader.readLine()) != null) {
bills.add(line.replaceAll("`", "").split(","));
}
List<String> headers = Arrays.asList(bills.remove(0));
List<String> totalDatas = Arrays
.asList(bills.remove(bills.size() - 1));
List<String> totalHeaders = Arrays
.asList(bills.remove(bills.size() - 1));
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet(formatBillDate + "对账单");
ExcelUtil.list2excel(wb, headers, bills);
ExcelUtil.list2excel(wb, totalHeaders, totalDatas);
os = new FileOutputStream(file);
wb.write(os);
} catch (IOException e) {
throw new WeixinException(e.getMessage());
} finally {
try {
if (reader != null) {
reader.close();
}
if (os != null) {
os.close();
}
} catch (IOException ignore) {
;
}
}
return file;
}
/**
* 退款查询</br> 退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态
*
* @param idQuery
* 单号 refund_idout_refund_no out_trade_no transaction_id
* 四个参数必填一个,优先级为:
* refund_id>out_refund_no>transaction_id>out_trade_no
* @return 退款记录
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundDetail
* @since V2
* @throws WeixinException
*/
public RefundRecord refundQuery(IdQuery idQuery) throws WeixinException {
String refundquery_uri = getRequestUri("refundquery_v2_uri");
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 response = request.get(refundquery_uri, map);
return RefundConverter.fromXML(response.getAsString(),
RefundRecord.class);
}
@Override
public String getShorturl(String url) throws WeixinException {
return helperApi.getShorturl(url);
}
}

View File

@ -1,470 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.http.SSLHttpRequest;
import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.conver.CouponConverter;
import com.foxinmy.weixin4j.mp.payment.conver.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.payment.v3.Order;
import com.foxinmy.weixin4j.mp.payment.v3.RefundRecord;
import com.foxinmy.weixin4j.mp.payment.v3.RefundResult;
import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.IdType;
import com.foxinmy.weixin4j.mp.util.ExcelUtil;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
/**
* V3(商户平台版)支付API
*
* @className Pay3Api
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see <a href="http://pay.weixin.qq.com/wiki/doc/api/index.html">商户平台API</a>
*/
public class Pay3Api extends PayApi {
public Pay3Api(WeixinMpAccount weixinAccount, TokenHolder tokenHolder) {
super(weixinAccount, tokenHolder);
}
/**
* 订单查询
* <p>
* 当商户后台网络服务器等出现异常商户系统最终未接收到支付通知</br> 调用支付接口后返回系统错误或未知交易状态情况</br>
* 调用被扫支付API返回USERPAYING的状态</br> 调用关单或撤销接口API之前需确认支付状态
* </P>
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 订单信息
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2">订单查询API</a>
* @since V3
* @throws WeixinException
*/
public Order orderQuery(IdQuery idQuery) throws WeixinException {
Map<String, String> map = baseMap(idQuery);
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
String orderquery_uri = getRequestUri("orderquery_v3_uri");
Response response = request.post(orderquery_uri, param);
return CouponConverter.fromXML(response.getAsString(), Order.class);
}
/**
* 申请退款(请求需要双向证书)
* <p>
* 当交易发生之后一段时间内由于买家或者卖家的原因需要退款时卖家可以通过退款接口将支付款退还给买家微信支付将在收到退款请求并且验证成功之后
* 按照退款规则将支付款按原路退到买家帐号上
* </p>
* <p style="color:red">
* 1.交易时间超过半年的订单无法提交退款
* 2.微信支付退款支持单笔交易分多次退款多次退款需要提交原支付订单的商户订单号和设置不同的退款单号一笔退款失败后重新提交
* 要采用原来的退款单号总退款金额不能超过用户实际支付金额
* </p>
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4">申请退款API</a>
* @since V3
* @throws WeixinException
*/
protected RefundResult refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
String opUserId, Map<String, String> mopara) throws WeixinException {
String refund_uri = getRequestUri("refund_v3_uri");
Response response = null;
InputStream ca = null;
try {
ca = new FileInputStream(caFile);
Map<String, String> map = baseMap(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);
if (mopara != null && !mopara.isEmpty()) {
map.putAll(mopara);
}
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);
} catch (WeixinException e) {
throw e;
} catch (Exception e) {
throw new WeixinException(e.getMessage());
} finally {
if (ca != null) {
try {
ca.close();
} catch (IOException e) {
;
}
}
}
return CouponConverter.fromXML(response.getAsString(),
RefundResult.class);
}
/**
* 退款申请
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param refundFeeType
* 货币类型符合ISO 4217标准的三位字母代码默认人民币CNY
* @param opUserId
* 操作员帐号, 默认为商户号
* @see {@link com.foxinmy.weixin4j.mp.api.Pay3Api#refund(File, IdQuery, String, double, double, String, Map)}
*/
public RefundResult refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
CurrencyType refundFeeType, String opUserId) throws WeixinException {
Map<String, String> mopara = new HashMap<String, String>();
if (refundFeeType == null) {
refundFeeType = CurrencyType.CNY;
}
mopara.put("refund_fee_type", refundFeeType.name());
return refund(caFile, idQuery, outRefundNo, totalFee, refundFee,
opUserId, mopara);
}
/**
* 冲正订单(需要证书)</br> 当支付返回失败,或收银系统超时需要取消交易,可以调用该接口</br> 接口逻辑:
* 付失败的关单,支付成功的撤销支付</br> <font color="red">7天以内的单可撤销,其他正常支付的单
* 如需实现相同功能请调用退款接口</font></br> <font
* color="red">调用扣款接口后请勿立即调用撤销,需要等待5秒以上先调用查单接口,如果没有确切的返回,再调用撤销</font></br>
*
* @param caFile
* 证书文件(V3版本后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 撤销结果
* @since V3
* @throws WeixinException
*/
public ApiResult reverse(File caFile, IdQuery idQuery)
throws WeixinException {
InputStream ca = null;
try {
ca = new FileInputStream(caFile);
SSLHttpRequest request = new SSLHttpRequest(
weixinAccount.getMchId(), ca);
String reverse_uri = getRequestUri("reverse_uri");
Map<String, String> map = baseMap(idQuery);
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
Response response = request.post(reverse_uri, param);
return response.getAsObject(new TypeReference<ApiResult>() {
});
} catch (IOException e) {
throw new WeixinException(e.getMessage());
} finally {
if (ca != null) {
try {
ca.close();
} catch (IOException e) {
;
}
}
}
}
/**
* native支付URL转短链接用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX)减小二维码数据量
* 提升扫描速度和精确度
*
* @param url
* 具有native标识的支付URL
* @return 转换后的短链接
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_9">转换短链接API</a>
*/
public String getShorturl(String url) throws WeixinException {
Map<String, String> map = baseMap(null);
try {
map.put("long_url", URLEncoder.encode(url, Consts.UTF_8.name()));
} catch (UnsupportedEncodingException ignore) {
;
}
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
String shorturl_uri = getRequestUri("p_shorturl_uri");
Response response = request.post(shorturl_uri, param);
map = xml2map(response.getAsString());
return map.get("short_url");
}
/**
* 关闭订单
* <p>
* 商户订单支付失败需要生成新单号重新发起支付要对原订单号调用关单避免重复支付系统下单后用户支付超时系统退出不再受理避免用户继续
* 请调用关单接口,如果关单失败,返回已完 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
* </p>
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 处理结果
* @since V3
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3">关闭订单API</a>
*/
public ApiResult closeOrder(String outTradeNo) throws WeixinException {
Map<String, String> map = baseMap(new IdQuery(outTradeNo,
IdType.TRADENO));
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
String closeorder_uri = getRequestUri("closeorder_uri");
Response response = request.post(closeorder_uri, param);
return response.getAsObject(new TypeReference<ApiResult>() {
});
}
/**
* 下载对账单<br>
* 1.微信侧未成功下单的交易不会出现在对账单中支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type
* REVOKED;<br>
* 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;<br>
* 3.对账单中涉及金额的字段单位为<br>
*
* @param billDate
* 下载对账单的日期
* @param billType
* 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单
* @return excel表格
* @since V3
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">下载对账单API</a>
* @throws WeixinException
*/
public File downloadbill(Date billDate, BillType billType)
throws WeixinException {
if (billDate == null) {
Calendar now = Calendar.getInstance();
now.add(Calendar.DAY_OF_MONTH, -1);
billDate = now.getTime();
}
if (billType == null) {
billType = BillType.ALL;
}
String formatBillDate = DateUtil.fortmat2yyyyMMdd(billDate);
String bill_path = ConfigUtil.getValue("bill_path");
String fileName = String.format("%s_%s_%s.xls", formatBillDate,
billType.name().toLowerCase(), weixinAccount.getId());
File file = new File(String.format("%s/%s", bill_path, fileName));
if (file.exists()) {
return file;
}
String downloadbill_uri = getRequestUri("downloadbill_v3_uri");
Map<String, String> map = baseMap(null);
map.put("bill_date", formatBillDate);
map.put("bill_type", billType.name());
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
Response response = request.post(downloadbill_uri, param);
BufferedReader reader = null;
OutputStream os = null;
try {
reader = new BufferedReader(new InputStreamReader(
response.getStream(), Consts.UTF_8));
String line = null;
List<String[]> bills = new LinkedList<String[]>();
while ((line = reader.readLine()) != null) {
bills.add(line.replaceAll("`", "").split(","));
}
List<String> headers = Arrays.asList(bills.remove(0));
List<String> totalDatas = Arrays
.asList(bills.remove(bills.size() - 1));
List<String> totalHeaders = Arrays
.asList(bills.remove(bills.size() - 1));
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet(formatBillDate + "对账单");
ExcelUtil.list2excel(wb, headers, bills);
ExcelUtil.list2excel(wb, totalHeaders, totalDatas);
os = new FileOutputStream(file);
wb.write(os);
} catch (IOException e) {
throw new WeixinException(e.getMessage());
} finally {
try {
if (reader != null) {
reader.close();
}
if (os != null) {
os.close();
}
} catch (IOException ignore) {
;
}
}
return file;
}
/**
* 退款查询
*
* <p>
* 提交退款申请后通过调用该接口查询退款状态退款有一定延时用零钱支付的退款20分钟内到账银行卡支付的退款3个工作日后重新查询退款状态
* </p>
*
* @param idQuery
* 单号 refund_idout_refund_no out_trade_no transaction_id
* 四个参数必填一个,优先级为:
* refund_id>out_refund_no>transaction_id>out_trade_no
* @return 退款记录
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5">退款查询API</a>
* @since V3
* @throws WeixinException
*/
public RefundRecord refundQuery(IdQuery idQuery) throws WeixinException {
String refundquery_uri = getRequestUri("refundquery_v3_uri");
Map<String, String> map = baseMap(idQuery);
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
Response response = request.post(refundquery_uri, param);
return RefundConverter.fromXML(response.getAsString(),
RefundRecord.class);
}
/**
* 接口上报
*
* @param interfaceUrl
* 上报对应的接口的完整 URL, 类似: https://api.mch.weixin.q
* q.com/pay/unifiedorder
* @param executeTime
* 接口耗时情况,单位为毫秒
* @param outTradeNo
* 商户系统内部的订单号, 户可以在上报时提供相关商户订单号方便微信支付更好 的提高服务质量
* @param ip
* 发起接口调用时的机器 IP
* @param time
* 商户调用该接口时商户自己 系统的时间
* @param returnXml
* 调用接口返回的基本数据
* @return 处理结果
* @throws WeixinException
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_8">接口测试上报API</a>
*/
@SuppressWarnings("unchecked")
public XmlResult interfaceReport(String interfaceUrl, int executeTime,
String outTradeNo, String ip, Date time, XmlResult returnXml)
throws WeixinException {
String pay_report_uri = getRequestUri("pay_report_uri");
Map<String, String> map = baseMap(null);
map.put("interface_url", interfaceUrl);
map.put("execute_time_", Integer.toString(executeTime));
map.put("out_trade_no", outTradeNo);
map.put("user_ip", ip);
map.put("time", DateUtil.fortmat2yyyyMMddHHmmss(time));
map.putAll((Map<String, String>) JSON.toJSON(returnXml));
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
Response response = request.post(pay_report_uri, param);
return response.getAsXmlResult();
}
/**
* V3接口请求基本数据
*
* @return
*/
private Map<String, String> baseMap(IdQuery idQuery) {
Map<String, String> map = new HashMap<String, String>();
map.put("appid", weixinAccount.getId());
map.put("mch_id", weixinAccount.getMchId());
map.put("nonce_str", RandomUtil.generateString(16));
if (StringUtils.isNotBlank(weixinAccount.getDeviceInfo())) {
map.put("device_info", weixinAccount.getDeviceInfo());
}
if (idQuery != null) {
map.put(idQuery.getType().getName(), idQuery.getId());
}
return map;
}
}

View File

@ -1,213 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.SignType;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.DateUtil;
/**
* 支付API
*
* @className PayApi
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
*/
public abstract class PayApi extends MpApi {
protected final WeixinMpAccount weixinAccount;
protected final TokenHolder tokenHolder;
public PayApi(WeixinMpAccount weixinAccount, TokenHolder tokenHolder) {
this.weixinAccount = weixinAccount;
this.tokenHolder = tokenHolder;
}
/**
* 发货通知
*
* @param openId
* 用户ID
* @param transid
* 交易单号
* @param outTradeNo
* 订单号
* @param status
* 成功|失败
* @param statusMsg
* status为失败时携带的信息
* @return 发货处理结果
* @throws WeixinException
*/
public JsonResult deliverNotify(String openId, String transid,
String outTradeNo, boolean status, String statusMsg)
throws WeixinException {
String delivernotify_uri = getRequestUri("delivernotify_uri");
Token token = tokenHolder.getToken();
Map<String, String> map = new HashMap<String, String>();
map.put("appid", weixinAccount.getId());
map.put("appkey", weixinAccount.getPaySignKey());
map.put("openid", openId);
map.put("transid", transid);
map.put("out_trade_no", outTradeNo);
map.put("deliver_timestamp", DateUtil.timestamp2string());
map.put("deliver_status", status ? "1" : "0");
map.put("deliver_msg", statusMsg);
map.put("app_signature", PayUtil.paysignSha(map));
map.put("sign_method", SignType.SHA1.name().toLowerCase());
Response response = request.post(
String.format(delivernotify_uri, token.getAccessToken()),
JSON.toJSONString(map));
return response.getAsJsonResult();
}
/**
* 维权处理
*
* @param openId
* 用户ID
* @param feedbackId
* 维权单号
* @return 维权处理结果
* @throws WeixinException
*/
public JsonResult updateFeedback(String openId, String feedbackId)
throws WeixinException {
String payfeedback_update_uri = getRequestUri("payfeedback_update_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(payfeedback_update_uri,
token.getAccessToken(), openId, feedbackId));
return response.getAsJsonResult();
}
/**
* 订单查询
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 订单信息
* @see com.foxinmy.weixin4j.mp.payment.v2.Order
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @throws WeixinException
*/
public abstract Object orderQuery(IdQuery idQuery) throws WeixinException;
/**
* 申请退款(请求需要双向证书)</br>
* <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* </p>
*
* @param caFile
* 证书文件(V2版本后缀为*.pfx,V3版本后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
* @param mopara
* 更多参数 如V2版本的opUserPasswd
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundResult
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult
* @throws WeixinException
*/
protected abstract Object refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
String opUserId, Map<String, String> mopara) throws WeixinException;
/**
* 退款查询</br> 退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态
*
* @param idQuery
* 单号 refund_idout_refund_no out_trade_no transaction_id
* 四个参数必填一个,优先级为:
* refund_id>out_refund_no>transaction_id>out_trade_no
* @return 退款记录
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @throws WeixinException
*/
public abstract Object refundQuery(IdQuery idQuery) throws WeixinException;
/**
* 冲正订单(需要证书)
*
* @param caFile
* 证书文件 (V2版本后缀为*.pfx,V3版本后缀为*.p12)
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 撤销结果
* @throws WeixinException
*/
public abstract ApiResult reverse(File caFile, IdQuery idQuery)
throws WeixinException;
/**
* 下载对账单<br>
* 1.微信侧未成功下单的交易不会出现在对账单中支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type
* REVOKED;<br>
* 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;<br>
* 3.对账单中涉及金额的字段单位为<br>
*
* @param billDate
* 下载对账单的日期
* @param billType
* 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单
* @return excel表格
* @throws WeixinException
*/
public abstract File downloadbill(Date billDate, BillType billType)
throws WeixinException;
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 处理结果
* @throws WeixinException
*/
public abstract ApiResult closeOrder(String outTradeNo)
throws WeixinException;
/**
* native支付URL转短链接
*
* @param url
* 具有native标识的支付URL
* @return 转换后的短链接
* @throws WeixinException
*/
public abstract String getShorturl(String url) throws WeixinException;
}

View File

@ -1,122 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.model.QRParameter;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.ConfigUtil;
/**
* 二维码相关API
*
* @className QrApi
* @author jy.hu
* @date 2014年9月25日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html">二维码支持</a>
*/
public class QrApi extends MpApi {
private final TokenHolder tokenHolder;
public QrApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 生成带参数的二维码
*
* @param parameter
* 二维码参数
* @return 二维码图片解析后的地址 开发者可根据该地址自行生成需要的二维码图片
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html">生成二维码</a>
*/
public String getQRUrl(QRParameter parameter) throws WeixinException {
return doQR(parameter).getString("url");
}
private JSONObject doQR(QRParameter parameter) throws WeixinException {
Token token = tokenHolder.getToken();
String qr_uri = getRequestUri("qr_ticket_uri");
Response response = request.post(
String.format(qr_uri, token.getAccessToken()),
parameter.getContent());
return response.getAsJson();
}
/**
* 生成带参数的二维码
*
* @param parameter
* 二维码参数
* @return byte数据包
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.QRParameter
* @see <a
* href="http://mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html">生成二维码</a>
*/
public byte[] getQRData(QRParameter parameter) throws WeixinException {
String ticket = doQR(parameter).getString("ticket");
String qr_uri = getRequestUri("qr_image_uri");
Response response = request.get(String.format(qr_uri, ticket));
return response.getBody();
}
/**
* 生成带参数的二维码
* <p>
* 二维码分为临时跟永久两种,扫描时触发推送带参数事件
* </p>
*
* @param parameter
* 二维码参数
* @return 硬盘存储的文件对象
* @throws WeixinException
* @see <a
* href="mp.weixin.qq.com/wiki/18/28fc21e7ed87bec960651f0ce873ef8a.html">二维码</a>
* @see com.foxinmy.weixin4j.mp.model.QRParameter
*/
public File getQRFile(QRParameter parameter) throws WeixinException {
String qr_path = ConfigUtil.getValue("qr_path");
String filename = String.format("%s_%s_%d.jpg", parameter.getQrType()
.name(), parameter.getSceneValue(), parameter
.getExpireSeconds());
File file = new File(qr_path + File.separator + filename);
if (parameter.getQrType().ordinal() > 0 && file.exists()) {
return file;
}
byte[] datas = getQRData(parameter);
OutputStream os = null;
try {
if (file.createNewFile()) {
os = new FileOutputStream(file);
os.write(datas);
} else {
throw new WeixinException(String.format("create file fail:%s",
file.getAbsolutePath()));
}
} catch (IOException e) {
throw new WeixinException(e.getMessage());
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException ignore) {
;
}
}
return file;
}
}

View File

@ -1,31 +0,0 @@
* MediaApi `上传/下载媒体文件API`
* NotifyApi `客服消息API`
* CustomApi `多客服API`
* MassApi `群发消息API`
* UserApi `用户管理API`
* GroupApi `分组管理API`
* MenuApi `底部菜单API`
* QrApi `二维码API`
* TmplApi `模板消息API`
* HelperApi `辅助API`
* Pay2Api `V2支付API`
* Pay3Api `V3支付API`
* CouponApi `代金券API`
* DataApi `数据统计API`
* OauthApi `oauth授权API`
* CashApi `现金API`

View File

@ -1,99 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.message.TemplateMessage;
import com.foxinmy.weixin4j.mp.type.IndustryType;
import com.foxinmy.weixin4j.token.TokenHolder;
/**
* 模板消息相关API
*
* @className TemplApi
* @author jy
* @date 2014年9月30日
* @since JDK 1.7
* @see
*/
public class TmplApi extends MpApi {
private final TokenHolder tokenHolder;
public TmplApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 设置所属行业(每月可修改行业1次账号仅可使用所属行业中相关的模板)
*
* @param industryType
* 所处行业 目前不超过两个
* @return 操作结果
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.type.IndustryType
* @see <a
* href="http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html#.E8.AE.BE.E7.BD.AE.E6.89.80.E5.B1.9E.E8.A1.8C.E4.B8.9A">设置所处行业</a>
*/
public JsonResult setTmplIndustry(IndustryType... industryType)
throws WeixinException {
JSONObject obj = new JSONObject();
for (int i = 0; i < industryType.length; i++) {
obj.put(String.format("industry_id%d", i + 1),
String.valueOf(industryType[i].getValue()));
}
Token token = tokenHolder.getToken();
String template_set_industry_uri = getRequestUri("template_set_industry_uri");
Response response = request.post(String.format(
template_set_industry_uri, token.getAccessToken()), obj
.toJSONString());
return response.getAsJsonResult();
}
/**
* 获取模板ID
*
* @param shortId
* 模板库中模板的编号TM**OPENTMTM**等形式
* @return 模板ID
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html#.E8.8E.B7.E5.BE.97.E6.A8.A1.E6.9D.BFID">获得模板ID</a>
*/
public String getTemplateId(String shortId) throws WeixinException {
Token token = tokenHolder.getToken();
String template_getid_uri = getRequestUri("template_getid_uri");
Response response = request.post(
String.format(template_getid_uri, token.getAccessToken()),
String.format("{\"template_id_short\":\"%s\"}", shortId));
return response.getAsJson().getString("template_id");
}
/**
* 发送模板消息
*
* @param tplMessage
* 消息对象
* @return 发送结果
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html#.E5.8F.91.E9.80.81.E6.A8.A1.E6.9D.BF.E6.B6.88.E6.81.AF">模板消息</a>
* @see com.foxinmy.weixin4j.mp.message.TemplateMessage
* @seee com.foxinmy.weixin4j.msg.event.TemplatesendjobfinishMessage
*/
public JsonResult sendTmplMessage(TemplateMessage tplMessage)
throws WeixinException {
Token token = tokenHolder.getToken();
String template_send_uri = getRequestUri("template_send_uri");
Response response = request.post(
String.format(template_send_uri, token.getAccessToken()),
JSON.toJSONString(tplMessage));
return response.getAsJsonResult();
}
}

View File

@ -1,165 +0,0 @@
package com.foxinmy.weixin4j.mp.api;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.mp.model.Following;
import com.foxinmy.weixin4j.mp.model.User;
import com.foxinmy.weixin4j.mp.type.Lang;
import com.foxinmy.weixin4j.token.TokenHolder;
/**
* 用户相关API
*
* @className UserApi
* @author jy.hu
* @date 2014年9月25日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.model.User
*/
public class UserApi extends MpApi {
private final TokenHolder tokenHolder;
public UserApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder;
}
/**
* 获取用户信息
*
* @param openId
* 用户对应的ID
* @return 用户对象
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html">获取用户信息</a>
* @see com.foxinmy.weixin4j.mp.model.User
* @see {@link com.foxinmy.weixin4j.mp.api.UserApi#getUser(String,Lang)}
*/
public User getUser(String openId) throws WeixinException {
return getUser(openId, Lang.zh_CN);
}
/**
* 获取用户信息
* <p>
* 在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID加密后的微信号,每个用户对每个公众号的OpenID是唯一的,对于不同公众号,
* 同一用户的openid不同,公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称头像性别所在城市语言和关注时间
* </p>
*
* @param openId
* 用户对应的ID
* @param lang
* 国家地区语言版本
* @return 用户对象
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html">获取用户信息</a>
* @see com.foxinmy.weixin4j.mp.type.Lang
* @see com.foxinmy.weixin4j.mp.model.User
*/
public User getUser(String openId, Lang lang) throws WeixinException {
String user_info_uri = getRequestUri("api_user_info_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(user_info_uri,
token.getAccessToken(), openId, lang.name()));
return response.getAsObject(new TypeReference<User>() {
});
}
/**
* 获取用户一定数量(10000)的关注者列表
*
* @param nextOpenId
* 下一次拉取数据的openid
* @return 关注信息
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/3/17e6919a39c1c53555185907acf70093.html">获取关注者列表</a>
* @see com.foxinmy.weixin4j.mp.model.Following
*/
public Following getFollowing(String nextOpenId) throws WeixinException {
String following_uri = getRequestUri("following_uri");
Token token = tokenHolder.getToken();
Response response = request.get(String.format(following_uri,
token.getAccessToken(), nextOpenId == null ? "" : nextOpenId));
Following following = response
.getAsObject(new TypeReference<Following>() {
});
if (following.getCount() > 0) {
List<String> openIds = JSON.parseArray(following.getDataJson()
.getString("openid"), String.class);
List<User> userList = new ArrayList<User>();
for (String openId : openIds) {
userList.add(getUser(openId));
}
following.setUserList(userList);
}
return following;
}
/**
* 获取用户全部的关注者列表
* <p>
* 当公众号关注者数量超过10000时,可通过填写next_openid的值,从而多次拉取列表的方式来满足需求,
* 将上一次调用得到的返回中的next_openid值,作为下一次调用中的next_openid值
* </p>
*
* @return 用户对象集合
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/3/17e6919a39c1c53555185907acf70093.html">获取关注者列表</a>
* @see com.foxinmy.weixin4j.mp.model.Following
* @see com.foxinmy.weixin4j.mp.api.UserApi#getFollowing(String)
*/
public List<User> getAllFollowing() throws WeixinException {
List<User> userList = new ArrayList<User>();
String nextOpenId = null;
Following f = null;
for (;;) {
f = getFollowing(nextOpenId);
if (f.getCount() == 0) {
break;
}
userList.addAll(f.getUserList());
nextOpenId = f.getNextOpenId();
}
return userList;
}
/**
* 设置用户备注名
*
* @param openId
* 用户ID
* @param remark
* 备注名
* @throws WeixinException
* @see <a
* href="http://mp.weixin.qq.com/wiki/10/bf8f4e3074e1cf91eb6518b6d08d223e.html">设置用户备注名</a>
*/
public JsonResult remarkUserName(String openId, String remark)
throws WeixinException {
String updateremark_uri = getRequestUri("updateremark_uri");
Token token = tokenHolder.getToken();
JSONObject obj = new JSONObject();
obj.put("openid", openId);
obj.put("remark", remark);
Response response = request.post(
String.format(updateremark_uri, token.getAccessToken()),
obj.toJSONString());
return response.getAsJsonResult();
}
}

View File

@ -1,181 +0,0 @@
# ----------------------------------------------------------------------------
# \u5fae\u4fe1\u516c\u4f17\u5e73\u53f0\u6587\u6863\u8bf4\u660e
# http://mp.weixin.qq.com/wiki/index.php
# http://mp.weixin.qq.com/wiki/0/2e2239fa5f49388d5b5136ecc8e0e440.html
# ----------------------------------------------------------------------------
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
file_base_url=http://file.api.weixin.qq.com/cgi-bin
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
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
sns_user_auth_uri=https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect
sns_user_token_uri=https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code
sns_token_refresh_uri=https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s
sns_auth_token_uri=https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s
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
api_user_info_uri={api_cgi_url}/user/info?access_token=%s&openid=%s&lang=%s
# \u83b7\u53d6token
api_token_uri={api_cgi_url}/token?grant_type=client_credential&appid=%s&secret=%s
# \u83b7\u53d6\u4e8c\u7ef4\u7801
qr_ticket_uri={api_cgi_url}/qrcode/create?access_token=%s
qr_image_uri={mp_base_url}/showqrcode?ticket=%s
# \u4e0a\u4f20\u5a92\u4f53\u6587\u4ef6
file_upload_uri={file_base_url}/media/upload?access_token=%s&type=%s
# \u4e0b\u8f7d\u5a92\u4f53\u6587\u4ef6
file_download_uri={file_base_url}/media/get?access_token=%s&media_id=%s
# \u53d1\u9001\u5ba2\u670d\u6d88\u606f
custom_notify_uri={api_cgi_url}/message/custom/send?access_token=%s
# \u521b\u5efa\u5206\u7ec4
group_create_uri={api_cgi_url}/groups/create?access_token=%s
# \u67e5\u8be2\u5206\u7ec4
group_get_uri={api_cgi_url}/groups/get?access_token=%s
# \u67e5\u8be2\u7528\u6237\u6240\u5728\u5206\u7ec4
group_getid_uri={api_cgi_url}/groups/getid?access_token=%s
# \u4fee\u6539\u5206\u7ec4\u540d
group_modify_uri={api_cgi_url}/groups/update?access_token=%s
# \u79fb\u52a8\u7528\u6237\u5206\u7ec4
group_move_uri={api_cgi_url}/groups/members/update?access_token=%s
# \u6279\u91cf\u79fb\u52a8\u7528\u6237\u5206\u7ec4
group_batchmove_uri={api_cgi_url}/groups/members/batchupdate?access_token=%s
# \u5220\u9664\u7528\u6237\u5206\u7ec4
group_delete_uri={api_cgi_url}/groups/delete?access_token=%s
# \u83b7\u53d6\u5173\u6ce8\u7740
following_uri={api_cgi_url}/user/get?access_token=%s&next_openid=%s
# \u81ea\u5b9a\u4e49\u83dc\u5355
menu_create_uri={api_cgi_url}/menu/create?access_token=%s
# \u67e5\u8be2\u83dc\u5355
menu_get_uri={api_cgi_url}/menu/get?access_token=%s
# \u67e5\u8be2\u901a\u8fc7\u63a5\u53e3\u6216\u8005\u5728\u516c\u4f17\u5e73\u53f0\u4e0a\u8bbe\u7f6e\u7684\u83dc\u5355\u914d\u7f6e\u4fe1\u606f
menu_get_selfmenu_uri={api_cgi_url}/get_current_selfmenu_info?access_token=%s
# \u5220\u9664\u83dc\u5355
menu_delete_uri={api_cgi_url}/menu/delete?access_token=%s
# \u4e0a\u4f20\u56fe\u6587
article_upload_uri={api_cgi_url}/media/uploadnews?access_token=%s
# \u4e0a\u4f20\u89c6\u9891
video_upload_uri={file_base_url}/media/uploadvideo?access_token=%s
# \u5206\u7ec4\u7fa4\u53d1
mass_group_uri={api_cgi_url}/message/mass/sendall?access_token=%s
# openId\u7fa4\u53d1
mass_openid_uri={api_cgi_url}/message/mass/send?access_token=%s
# \u5220\u9664\u7fa4\u53d1
mass_delete_uri={api_cgi_url}/message/mass/delete?access_token=%s
# \u7fa4\u53d1\u9884\u89c8
mass_preview_uri={api_cgi_url}/message/mass/preview?access_token=%s
# \u67e5\u8be2\u7fa4\u53d1\u72b6\u6001
mass_get_uri={api_cgi_url}/message/mass/get?access_token=%s
# \u5ba2\u670d\u804a\u5929\u8bb0\u5f55
custom_record_uri={api_cgi_url}/customservice/getrecord?access_token=%s
# \u5ba2\u670d\u57fa\u672c\u4fe1\u606f
getkflist_uri={api_cgi_url}/customservice/getkflist?access_token=%s
# \u65b0\u589e\u591a\u5ba2\u670d\u8d26\u53f7
custom_add_uri={api_base_url}/customservice/kfaccount/add?access_token=%s
# \u66f4\u65b0\u591a\u5ba2\u670d\u8d26\u53f7
custom_update_uri={api_base_url}/customservice/kfaccount/update?access_token=%s
# \u4e0a\u4f20\u5ba2\u670d\u5934\u50cf
custom_uploadheadimg_uri={api_base_url}/customservice/kfacount/uploadheadimg?access_token=%s&kf_account=%s
# \u5220\u9664\u5ba2\u670d\u8d26\u53f7
custom_delete_uri={api_base_url}/customservice/kfaccount/del?access_token=%s&kf_account=%s
# \u5728\u7ebf\u5ba2\u670d\u57fa\u672c\u4fe1\u606f
getonlinekflist_uri={api_cgi_url}/customservice/getonlinekflist?access_token=%s
# \u521b\u5efa\u5ba2\u670d\u4f1a\u8bdd
kfsession_create_uri={api_base_url}/customservice/kfsession/create?access_token=%s
# \u5173\u95ed\u5ba2\u670d\u4f1a\u8bdd
kfsession_close_uri={api_base_url}/customservice/kfsession/close?access_token=%s
# \u83b7\u53d6\u5ba2\u670d\u4f1a\u8bdd\u72b6\u6001
kfsession_get_uri={api_base_url}/customservice/kfsession/getsession?access_token=%s&openid=%s
# \u83b7\u53d6\u5ba2\u670d\u7684\u4f1a\u8bdd\u5217\u8868
kfsession_list_uri={api_base_url}/customservice/kfsession/getsessionlist?access_token=%s&kf_account=%s
# \u83b7\u53d6\u672a\u63a5\u5165\u4f1a\u8bdd\u5217\u8868
kfsession_wait_uri={api_base_url}/customservice/kfsession/getwaitcase?access_token=%s
# \u957f\u94fe\u63a5\u8f6c\u77ed\u94fe\u63a5
shorturl_uri={api_cgi_url}/shorturl?access_token=%s
p_shorturl_uri={mch_base_url}/tools/shorturl
# \u8bbe\u7f6e\u5907\u6ce8\u540d
updateremark_uri={api_cgi_url}/user/info/updateremark?access_token=%s
# \u8bbe\u7f6e\u6a21\u677f\u6d88\u606f\u6240\u5904\u884c\u4e1a
template_set_industry_uri={api_cgi_url}/template/api_set_industry?access_token=%s
# \u83b7\u53d6\u6a21\u677f\u6d88\u606fID
template_getid_uri={api_cgi_url}/template/api_add_template?access_token=%s
# \u53d1\u9001\u6a21\u677f\u6d88\u606f
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
# \u5fae\u4fe1\u670d\u52a1\u5730\u5740
getcallbackip_uri={api_cgi_url}/getcallbackip?access_token=%s
# \u8ba2\u5355\u67e5\u8be2
orderquery_uri={api_base_url}/pay/orderquery?access_token=%s
# \u53d1\u8d27\u901a\u77e5
delivernotify_uri={api_base_url}/pay/delivernotify?access_token=%s
# \u7ef4\u6743\u5904\u7406
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
orderquery_v3_uri={mch_base_url}/pay/orderquery
# \u5173\u95ed\u8ba2\u5355
closeorder_uri={mch_base_url}/pay/closeorder
# \u5bf9\u8d26\u5355\u4e0b\u8f7d
downloadbill_v3_uri={mch_base_url}/pay/downloadbill
# \u9000\u6b3e\u67e5\u8be2
refundquery_v3_uri={mch_base_url}/pay/refundquery
# \u9000\u6b3e\u7533\u8bf7
refund_v3_uri={mch_base_url}/secapi/pay/refund
# \u51b2\u6b63\u64a4\u9500
reverse_uri={mch_base_url}/secapi/pay/reverse
# \u88ab\u626b\u652f\u4ed8
micropay_uri={mch_base_url}/pay/micropay
# \u63a5\u53e3\u4e0a\u62a5
pay_report_uri={mch_base_url}/payitil/report
# \u7edf\u4e00\u8ba2\u5355\u751f\u6210
unifiedorder_uri={mch_base_url}/pay/unifiedorder
# native\u652f\u4ed8\u94fe\u63a5
nativepay_v2_uri=weixin://wxpay/bizpayurl?sign=%s&appid=%s&productid=%s&timestamp=%s&noncestr=%s
nativepay_v3_uri=weixin://wxpay/bizpayurl?sign=%s&appid=%s&mch_id=%s&product_id=%s&time_stamp=%s&nonce_str=%s
# \u6570\u636e\u7edf\u8ba1
datacube_uri={api_base_url}/datacube/%s?access_token=%s
# \u4e0a\u4f20\u6c38\u4e45\u56fe\u6587\u7d20\u6750
material_article_upload_uri={api_cgi_url}/material/add_news?access_token=%s
# \u4e0a\u4f20\u6c38\u4e45\u5a92\u4f53\u7d20\u6750
material_media_upload_uri={api_cgi_url}/material/add_material?access_token=%s
# \u4e0b\u8f7d\u6c38\u4e45\u5a92\u4f53\u7d20\u6750
material_media_download_uri={api_cgi_url}/material/get_material?access_token=%s
# \u66f4\u65b0\u6c38\u4e45\u56fe\u6587\u7d20\u6750
material_article_update_uri={api_cgi_url}/material/update_news?access_token=%s
# \u5220\u9664\u6c38\u4e45\u5a92\u4f53\u7d20\u6750
material_media_del_uri={api_cgi_url}/material/del_material?access_token=%s
# \u83b7\u53d6\u5a92\u4f53\u7d20\u6750\u603b\u6570
material_media_count_uri={api_cgi_url}/material/get_materialcount?access_token=%s
# \u83b7\u53d6\u5a92\u4f53\u7d20\u6750\u5217\u8868
material_media_list_uri={api_cgi_url}/material/batchget_material?access_token=%s
# \u81ea\u52a8\u56de\u590d\u89c4\u5219
autoreply_setting_get_uri={api_cgi_url}/get_current_autoreply_info?access_token=%s
# \u53d1\u653e\u4ee3\u91d1\u5238
coupon_send_uri={mch_base_url}/mmpaymkttransfers/send_coupon
# \u67e5\u8be2\u4ee3\u91d1\u5238\u6279\u6b21\u4fe1\u606f
couponstock_query_uri={mch_base_url}/mmpaymkttransfers/query_coupon_stock
# \u67e5\u8be2\u4ee3\u91d1\u5238\u8be6\u7ec6\u4fe1\u606f
coupondetail_query_uri={mch_base_url}/promotion/query_coupon
# \u53d1\u653e\u73b0\u91d1\u7ea2\u5305
redpack_send_uri={mch_base_url}/mmpaymkttransfers/sendredpack
# \u4f01\u4e1a\u5411\u4e2a\u4eba\u4ed8\u6b3e
mp_payment_uri={mch_base_url}/mmpaymkttransfers/promotion/transfers

View File

@ -1,133 +0,0 @@
package com.foxinmy.weixin4j.mp.datacube;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 图文数据
*
* @className ArticleDatacube1
* @author jy
* @date 2015年3月29日
* @since JDK 1.7
* @see
*/
public class ArticleDatacube1 implements Serializable {
private static final long serialVersionUID = 4140706754295502971L;
/**
* 图文页点击群发图文卡片进入的页面的阅读人数
*/
@JSONField(name = "int_page_read_user")
private int intPageReadUser;
/**
* 图文页的阅读次数
*/
@JSONField(name = "int_page_read_count")
private int intPageReadCount;
/**
* 原文页点击图文页阅读原文进入的页面的阅读人数无原文页时此处数据为0
*/
@JSONField(name = "ori_page_read_user")
private int oriPageReadUser;
/**
* 原文页的阅读次数
*/
@JSONField(name = "ori_page_read_count")
private int oriPageReadCount;
/**
* 分享的人数
*/
@JSONField(name = "shareUser")
private int shareUser;
/**
* 分享的次数
*/
@JSONField(name = "shareCount")
private int shareCount;
/**
* 收藏的人数
*/
@JSONField(name = "add_to_fav_user")
private int favUser;
/**
* 收藏的次数
*/
@JSONField(name = "add_to_fav_count")
private int favCount;
public int getIntPageReadUser() {
return intPageReadUser;
}
public void setIntPageReadUser(int intPageReadUser) {
this.intPageReadUser = intPageReadUser;
}
public int getIntPageReadCount() {
return intPageReadCount;
}
public void setIntPageReadCount(int intPageReadCount) {
this.intPageReadCount = intPageReadCount;
}
public int getOriPageReadUser() {
return oriPageReadUser;
}
public void setOriPageReadUser(int oriPageReadUser) {
this.oriPageReadUser = oriPageReadUser;
}
public int getOriPageReadCount() {
return oriPageReadCount;
}
public void setOriPageReadCount(int oriPageReadCount) {
this.oriPageReadCount = oriPageReadCount;
}
public int getShareUser() {
return shareUser;
}
public void setShareUser(int shareUser) {
this.shareUser = shareUser;
}
public int getShareCount() {
return shareCount;
}
public void setShareCount(int shareCount) {
this.shareCount = shareCount;
}
public int getFavUser() {
return favUser;
}
public void setFavUser(int favUser) {
this.favUser = favUser;
}
public int getFavCount() {
return favCount;
}
public void setFavCount(int favCount) {
this.favCount = favCount;
}
@Override
public String toString() {
return " intPageReadUser=" + intPageReadUser + ", intPageReadCount="
+ intPageReadCount + ", oriPageReadUser=" + oriPageReadUser
+ ", oriPageReadCount=" + oriPageReadCount + ", share_user="
+ shareUser + ", share_count=" + shareCount + ", favUser="
+ favUser + ", favCount=" + favCount;
}
}

View File

@ -1,51 +0,0 @@
package com.foxinmy.weixin4j.mp.datacube;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 图文数据
*
* @className ArticleDatacube2
* @author jy
* @date 2015年3月29日
* @since JDK 1.7
* @see
*/
public class ArticleDatacube2 extends ArticleDatacube1 {
private static final long serialVersionUID = -2924534868674264316L;
/**
* 统计的日期
*/
@JSONField(name = "stat_date")
private Date statDate;
/**
* 送达人数一般约等于总粉丝数需排除黑名单或其他异常情况下无法收到消息的粉丝
*/
@JSONField(name = "target_user")
private int targetUser;
public Date getStatDate() {
return statDate;
}
public void setStatDate(Date statDate) {
this.statDate = statDate;
}
public int getTargetUser() {
return targetUser;
}
public void setTargetUser(int targetUser) {
this.targetUser = targetUser;
}
@Override
public String toString() {
return "statDate=" + statDate + ", targetUser=" + targetUser + ", "
+ super.toString();
}
}

View File

@ -1,101 +0,0 @@
package com.foxinmy.weixin4j.mp.datacube;
import java.io.Serializable;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.ShareSourceType;
/**
* 数据统计:图文分享数据
*
* @className ArticleDatacubeShare
* @author jy
* @date 2015年1月30日
* @since JDK 1.7
* @see
*/
public class ArticleDatacubeShare implements Serializable {
private static final long serialVersionUID = 3841239305410294553L;
/**
* 数据的日期
*/
@JSONField(name = "ref_date")
private Date refDate;
/**
* 数据的小时包括从000到2300分别代表的是[000,100)[2300,2400)即每日的第1小时和最后1小时
*/
@JSONField(name = "ref_hour")
private int refHour;
/**
* 分享的人数
*/
@JSONField(name = "shareUser")
private int shareUser;
/**
* 分享的次数
*/
@JSONField(name = "shareCount")
private int shareCount;
/**
* 分享的场景
*/
@JSONField(name = "share_scene")
private int shareScene;
public Date getRefDate() {
return refDate;
}
public void setRefDate(Date refDate) {
this.refDate = refDate;
}
public int getRefHour() {
return refHour;
}
public void setRefHour(int refHour) {
this.refHour = refHour;
}
public int getShareUser() {
return shareUser;
}
public void setShareUser(int shareUser) {
this.shareUser = shareUser;
}
public int getShareCount() {
return shareCount;
}
public void setShareCount(int shareCount) {
this.shareCount = shareCount;
}
public ShareSourceType getShareScene() {
if (shareScene == 1) {
return ShareSourceType.FRIENDFORWARD;
} else if (shareScene == 2) {
return ShareSourceType.FRIENDSCIRCLE;
} else if (shareScene == 3) {
return ShareSourceType.TENCENTWEIBO;
} else {
return ShareSourceType.OTHER;
}
}
public void setShareScene(int shareScene) {
this.shareScene = shareScene;
}
@Override
public String toString() {
return "ArticleDatacubeShare [refDate=" + refDate + ", refHour="
+ refHour + ", shareUser=" + shareUser + ", shareCount="
+ shareCount + ", shareScene=" + shareScene + "]";
}
}

Some files were not shown because too many files have changed in this diff Show More