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

This commit is contained in:
jinyu 2015-04-27 21:02:51 +08:00
parent 69c9bdffa4
commit 784d1f33ea
221 changed files with 24504 additions and 0 deletions

51
weixin4j-base/CHANGE.md Normal file
View File

@ -0,0 +1,51 @@
* 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类
* 2015-04-19
+ 删除ActionMapping相关类

View File

@ -0,0 +1,27 @@
package com.foxinmy.weixin4j.tuple;
import java.beans.Transient;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 消息元件
*
* @className Tuple
* @author jy
* @date 2015年4月19日
* @since JDK 1.7
* @see
*/
public interface Tuple extends Serializable {
/**
* 消息类型
*
* @return
*/
@Transient
@JSONField(deserialize = false, serialize = false)
public String getMessageType();
}

View File

@ -0,0 +1,90 @@
package com.foxinmy.weixin4j.util;
import java.io.Serializable;
import com.foxinmy.weixin4j.util.Sort.Direction;
/**
* @className Pageable
* @author jy
* @date 2014年12月27日
* @since JDK 1.7
* @see org.springframework.data.domain.Pageable
*/
public class Pageable implements Serializable {
private static final long serialVersionUID = -8051554878205756307L;
private final int page;
private final int size;
private Sort sort;
/**
*
* @param page
* must not be less than one.
* @param size
* must not be less than one.
*/
public Pageable(int page, int size) {
if (page < 1) {
throw new IllegalArgumentException(
"Page index must not be less than one!");
}
if (size < 1) {
throw new IllegalArgumentException(
"Page size must not be less than one!");
}
this.page = page;
this.size = size;
}
public Pageable(int page, int size, Direction direction,
String... properties) {
this(page, size, new Sort(direction, properties));
}
public Pageable(int page, int size, Sort sort) {
this.page = page;
this.size = size;
this.sort = sort;
}
public int getPageSize() {
return size;
}
public int getPageNumber() {
return page;
}
public Sort getSort() {
return sort;
}
public void setSort(Sort sort) {
this.sort = sort;
}
public int getOffset() {
return (page - 1) * size;
}
public boolean hasPrevious() {
return page > 1;
}
public Pageable next() {
return new Pageable(getPageNumber() + 1, getPageSize(), getSort());
}
public Pageable previous() {
return getPageNumber() == 1 ? this : new Pageable(getPageNumber() - 1,
getPageSize(), getSort());
}
public Pageable first() {
return new Pageable(0, getPageSize(), getSort());
}
@Override
public String toString() {
return "Pageable [page=" + page + ", size=" + size + ", sort=" + sort
+ "]";
}
}

View File

@ -0,0 +1,67 @@
package com.foxinmy.weixin4j.util;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
public class Pagedata<T> implements Serializable, Iterable<T> {
private static final long serialVersionUID = 163159826528502864L;
private final int total;
private final Pageable pageable;
private final List<T> content;
public Pagedata(Pageable pageable, int total, List<T> content) {
this.pageable = pageable;
this.total = total;
this.content = content;
}
public int getNumber() {
return pageable == null ? 0 : pageable.getPageNumber();
}
public int getSize() {
return pageable == null ? 0 : pageable.getPageSize();
}
public int getTotalPages() {
return getSize() == 0 ? 1 : (int) Math.ceil((double) total
/ (double) getSize());
}
public int getTotalElements() {
return total;
}
public int getNumberOfElements() {
return hasContent() ? 0 : content.size();
}
public boolean hasContent() {
return content != null && !content.isEmpty();
}
public boolean hasPrevious() {
return pageable == null ? false : pageable.hasPrevious();
}
public boolean hasNext() {
return getNumber() + 1 < getTotalPages();
}
public Sort getSort() {
return pageable == null ? null : pageable.getSort();
}
@Override
public Iterator<T> iterator() {
return hasContent() ? content.iterator() : null;
}
@Override
public String toString() {
return "Pagedata [total=" + total + ", pageable=" + pageable + "]";
}
}

View File

@ -0,0 +1,65 @@
package com.foxinmy.weixin4j.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class Sort implements Serializable {
private static final long serialVersionUID = -4298853295391613880L;
public static final Direction DEFAULT_DIRECTION = Direction.ASC;
private Map<Direction, List<String>> orders;
public Sort() {
}
public Sort(String... properties) {
this(DEFAULT_DIRECTION, properties);
}
public Sort(Direction direction, String... properties) {
this(direction, properties == null ? new ArrayList<String>() : Arrays
.asList(properties));
}
public Sort(Direction direction, List<String> properties) {
if (properties == null || properties.isEmpty()) {
throw new IllegalArgumentException(
"You have to provide at least one property to sort by!");
}
this.orders = new LinkedHashMap<Direction, List<String>>(
properties.size());
this.orders.put(direction, properties);
}
public Map<Direction, List<String>> getOrders() {
return orders;
}
public Map.Entry<String, String> getFirst() {
if (hasSort()) {
Entry<Direction, List<String>> firstEntry = orders.entrySet()
.iterator().next();
Map<String, String> firstMap = new HashMap<String, String>();
firstMap.put(firstEntry.getKey().name().toLowerCase(), firstEntry
.getValue().get(0));
return firstMap.entrySet().iterator().next();
}
return null;
}
public boolean hasSort() {
return orders != null && !orders.isEmpty();
}
public static enum Direction {
ASC, DESC;
}
@Override
public String toString() {
return "Sort [" + orders + "]";
}
}

111
weixin4j-mp/CHANGE.md Normal file
View File

@ -0,0 +1,111 @@
* 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

@ -0,0 +1,31 @@
<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

@ -0,0 +1,624 @@
package com.foxinmy.weixin4j.mp;
import java.io.File;
import java.util.Date;
import com.alibaba.fastjson.JSON;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.XmlResult;
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.model.WeixinMpAccount;
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(JSON.parseObject(ConfigUtil.getValue("account"),
WeixinMpAccount.class), 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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
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.mp.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

@ -0,0 +1,169 @@
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.mp.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

@ -0,0 +1,357 @@
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

@ -0,0 +1,133 @@
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

@ -0,0 +1,189 @@
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

@ -0,0 +1,234 @@
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.token.TokenHolder;
import com.foxinmy.weixin4j.tuple.MpArticle;
/**
* 辅助相关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.tuple.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

@ -0,0 +1,324 @@
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.token.TokenHolder;
import com.foxinmy.weixin4j.tuple.MassTuple;
import com.foxinmy.weixin4j.tuple.MpArticle;
import com.foxinmy.weixin4j.tuple.MpNews;
import com.foxinmy.weixin4j.tuple.Tuple;
import com.foxinmy.weixin4j.tuple.Video;
/**
* 群发相关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.tuple.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.tuple.Video
* @see com.foxinmy.weixin4j.tuple.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 tuple
* 消息元件
* @param groupId
* 分组ID
* @return 群发后的消息ID
* @see {@link com.foxinmy.weixin4j.mp.api.MassApi#massMessage(MassTuple,boolean,int)}
* @throws WeixinException
*/
public String massByGroupId(MassTuple tuple, int groupId)
throws WeixinException {
return massMessage(tuple, false, groupId);
}
/**
* 群发消息
* <p>
* 在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息,
* 如消息有时会进行审核服务器不稳定等,此外,群发任务一般需要较长的时间才能全部发送完毕
* </p>
*
* @param tuple
* 消息元件
* @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.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
* @see com.foxinmy.weixin4j.tuple.MassTuple
* @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(MassTuple tuple, boolean isToAll, int groupId)
throws WeixinException {
if (tuple instanceof MpNews) {
MpNews _news = (MpNews) tuple;
List<MpArticle> _articles = _news.getArticles();
if (StringUtils.isBlank(_news.getMediaId())
&& (_articles == null || _articles.isEmpty())) {
throw new WeixinException(
"mass fail:mediaId or articles is required");
}
tuple = new MpNews(uploadArticle(_articles));
}
String msgtype = tuple.getMessageType();
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(tuple));
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(Tuple,int)}
* @see com.foxinmy.weixin4j.tuple.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 tuple
* 消息元件
* @param openIds
* openId列表
* @return 群发后的消息ID
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.model.User
* @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
* @see com.foxinmy.weixin4j.tuple.MassTuple
* @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(MassTuple tuple, String... openIds)
throws WeixinException {
if (tuple instanceof MpNews) {
MpNews _news = (MpNews) tuple;
List<MpArticle> _articles = _news.getArticles();
if (StringUtils.isBlank(_news.getMediaId())
&& (_articles == null || _articles.isEmpty())) {
throw new WeixinException(
"mass fail:mediaId or articles is required");
}
tuple = new MpNews(uploadArticle(_articles));
}
String msgtype = tuple.getMessageType();
JSONObject obj = new JSONObject();
obj.put("touser", openIds);
obj.put(msgtype, JSON.toJSON(tuple));
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(Tuple,String...)}
* @see com.foxinmy.weixin4j.tuple.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(Tuple, int)}
* @see {@link com.foxinmy.weixin4j.mp.api.MassApi#massByOpenIds(Tuple, 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 tuple
* 消息元件
* @return 处理结果
* @throws WeixinException
* @see com.foxinmy.weixin4j.tuple.MassTuple
* @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, MassTuple tuple)
throws WeixinException {
String msgtype = tuple.getMessageType();
JSONObject obj = new JSONObject();
obj.put("touser", openId);
obj.put(msgtype, JSON.toJSON(tuple));
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.mp.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

@ -0,0 +1,474 @@
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.token.TokenHolder;
import com.foxinmy.weixin4j.tuple.MpArticle;
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.tuple.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.tuple.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.tuple.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

@ -0,0 +1,114 @@
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

@ -0,0 +1,28 @@
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

@ -0,0 +1,86 @@
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.token.TokenHolder;
import com.foxinmy.weixin4j.tuple.NotifyTuple;
/**
* 客服消息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.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.mp.message.NotifyMessage
*/
public JsonResult sendNotify(NotifyMessage notify, String kfAccount)
throws WeixinException {
NotifyTuple tuple = notify.getTuple();
String msgtype = tuple.getMessageType();
JSONObject obj = new JSONObject();
obj.put("touser", notify.getTouser());
obj.put("msgtype", msgtype);
obj.put(msgtype, tuple);
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

@ -0,0 +1,173 @@
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.WeixinAccount;
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.getWeixinAccount().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 {
WeixinAccount account = ConfigUtil.getWeixinAccount();
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 {
WeixinAccount account = ConfigUtil.getWeixinAccount();
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

@ -0,0 +1,466 @@
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.mp.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

@ -0,0 +1,470 @@
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.mp.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

@ -0,0 +1,213 @@
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.mp.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

@ -0,0 +1,122 @@
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

@ -0,0 +1,31 @@
* 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

@ -0,0 +1,99 @@
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

@ -0,0 +1,165 @@
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

@ -0,0 +1,181 @@
# ----------------------------------------------------------------------------
# \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

@ -0,0 +1,133 @@
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

@ -0,0 +1,51 @@
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

@ -0,0 +1,101 @@
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 + "]";
}
}

View File

@ -0,0 +1,76 @@
package com.foxinmy.weixin4j.mp.datacube;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 数据统计:图文群发每日数据
*
* @className ArticleSummary
* @author jy
* @date 2015年1月30日
* @since JDK 1.7
* @see
*/
public class ArticleSummary extends ArticleDatacube1 {
private static final long serialVersionUID = 4820605570501368550L;
/**
* 数据的日期
*/
@JSONField(name = "ref_date")
private Date refDate;
/**
* 数据的小时包括从000到2300分别代表的是[000,100)[2300,2400)即每日的第1小时和最后1小时
*/
@JSONField(name = "ref_hour")
private int refHour;
/**
* 这里的msgid实际上是由msgid图文消息id和index消息次序索引组成 例如12003_3
* 其中12003是msgid即一次群发的id消息的 3为index假设该次群发的图文消息共5个文章因为可能为多图文 3表示5个中的第3个
*/
private String msgid;
/**
* 图文消息的标题
*/
private String title;
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 String getMsgid() {
return msgid;
}
public void setMsgid(String msgid) {
this.msgid = msgid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "ArticleSummary [refDate=" + refDate + ", refHour=" + refHour
+ ", msgid=" + msgid + ", title=" + title + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,77 @@
package com.foxinmy.weixin4j.mp.datacube;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 数据统计:图文群发总数据
*
* @className ArticleTotal
* @author jy
* @date 2015年1月30日
* @since JDK 1.7
* @see
*/
public class ArticleTotal implements Serializable {
private static final long serialVersionUID = -6820948857241500950L;
/**
* 数据的日期
*/
@JSONField(name = "ref_date")
private Date refDate;
/**
* 这里的msgid实际上是由msgid图文消息id和index消息次序索引组成 例如12003_3
* 其中12003是msgid即一次群发的id消息的 3为index假设该次群发的图文消息共5个文章因为可能为多图文 3表示5个中的第3个
*/
private String msgid;
/**
* 图文消息的标题
*/
private String title;
/**
* 详细信息
*/
private List<ArticleDatacube2> details;
public Date getRefDate() {
return refDate;
}
public void setRefDate(Date refDate) {
this.refDate = refDate;
}
public String getMsgid() {
return msgid;
}
public void setMsgid(String msgid) {
this.msgid = msgid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<ArticleDatacube2> getDetails() {
return details;
}
public void setDetails(List<ArticleDatacube2> details) {
this.details = details;
}
@Override
public String toString() {
return "ArticleTotal [refDate=" + refDate + ", msgid=" + msgid
+ ", title=" + title + ", details=" + details + "]";
}
}

View File

@ -0,0 +1,107 @@
package com.foxinmy.weixin4j.mp.datacube;
import java.io.Serializable;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 数据统计:接口分析数据
*
* @className InterfaceSummary
* @author jy
* @date 2015年1月30日
* @since JDK 1.7
* @see
*/
public class InterfaceSummary implements Serializable {
private static final long serialVersionUID = -8812979112580350988L;
/**
* 引用的日期
*/
@JSONField(name = "ref_date")
private Date refDate;
/**
* 数据的小时包括从000到2300分别代表的是[000,100)[2300,2400)即每日的第1小时和最后1小时
*/
@JSONField(name = "ref_hour")
private int refHour;
/**
* 通过服务器配置地址获得消息后被动回复用户消息的次数
*/
@JSONField(name = "callback_count")
private int callbackCount;
/**
* 上述动作的失败次数
*/
@JSONField(name = "fail_count")
private int failCount;
/**
* 总耗时除以callback_count即为平均耗时
*/
@JSONField(name = "total_time_cost")
private int totalTimeCost;
/**
* 最大耗时
*/
@JSONField(name = "max_time_cost")
private int maxTimeCost;
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 getCallbackCount() {
return callbackCount;
}
public void setCallbackCount(int callbackCount) {
this.callbackCount = callbackCount;
}
public int getFailCount() {
return failCount;
}
public void setFailCount(int failCount) {
this.failCount = failCount;
}
public int getTotalTimeCost() {
return totalTimeCost;
}
public void setTotalTimeCost(int totalTimeCost) {
this.totalTimeCost = totalTimeCost;
}
public int getMaxTimeCost() {
return maxTimeCost;
}
public void setMaxTimeCost(int maxTimeCost) {
this.maxTimeCost = maxTimeCost;
}
@Override
public String toString() {
return "InterfaceSummary [refDate=" + refDate + ", refHour=" + refHour
+ ", callbackCount=" + callbackCount + ", failCount="
+ failCount + ", totalTimeCost=" + totalTimeCost
+ ", maxTimeCost=" + maxTimeCost + "]";
}
}

View File

@ -0,0 +1,108 @@
package com.foxinmy.weixin4j.mp.datacube;
import java.io.Serializable;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MessageType;
/**
* 数据统计:消息发送概况数据
*
* @className UpstreamMsg
* @author jy
* @date 2015年1月30日
* @since JDK 1.7
* @see
*/
public class UpstreamMsg implements Serializable {
private static final long serialVersionUID = -2605207523094962029L;
/**
* 引用的日期
*/
@JSONField(name = "ref_date")
private Date refDate;
/**
* 数据的小时包括从000到2300分别代表的是[000,100)[2300,2400)即每日的第1小时和最后1小时
*/
@JSONField(name = "ref_hour")
private int refHour;
/**
* 消息类型
*/
@JSONField(name = "msg_type")
private int msgType;
/**
* 上行发送了向公众号发送了消息的用户数
*/
@JSONField(name = "msg_user")
private int msgUser;
/**
* 上行发送了消息的消息总数
*/
@JSONField(name = "msg_count")
private int msgCount;
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 MessageType getMsgType() {
// 1代表文字 2代表图片 3代表语音 4代表视频 6代表第三方应用消息链接消息
switch (msgType) {
case 1:
return MessageType.text;
case 2:
return MessageType.image;
case 3:
return MessageType.voice;
case 4:
return MessageType.video;
case 6:
return MessageType.link;
default:
return null;
}
}
public void setMsgType(int msgType) {
this.msgType = msgType;
}
public int getMsgUser() {
return msgUser;
}
public void setMsgUser(int msgUser) {
this.msgUser = msgUser;
}
public int getMsgCount() {
return msgCount;
}
public void setMsgCount(int msgCount) {
this.msgCount = msgCount;
}
@Override
public String toString() {
return "UpstreamMsg [refDate=" + refDate + ", refHour=" + refHour
+ ", msgType=" + msgType + ", msgUser=" + msgUser
+ ", msgCount=" + msgCount + "]";
}
}

View File

@ -0,0 +1,66 @@
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.DatacuteCountIntervalType;
/**
* 数据统计:消息发送分布数据
*
* @className UpstreamMsgDist
* @author jy
* @date 2015年1月30日
* @since JDK 1.7
* @see
*/
public class UpstreamMsgDist implements Serializable {
private static final long serialVersionUID = -2605207523094962029L;
/**
* 引用的日期
*/
@JSONField(name = "ref_date")
private Date refDate;
/**
* 上行发送了向公众号发送了消息的用户数
*/
@JSONField(name = "msg_user")
private int msgUser;
/**
* 当日发送消息量分布的区间0代表 01代表1-52代表6-103代表10次以上
*/
@JSONField(name = "count_interval")
private int countInterval;
public Date getRefDate() {
return refDate;
}
public void setRefDate(Date refDate) {
this.refDate = refDate;
}
public int getMsgUser() {
return msgUser;
}
public void setMsgUser(int msgUser) {
this.msgUser = msgUser;
}
public DatacuteCountIntervalType getCountInterval() {
return DatacuteCountIntervalType.values()[countInterval];
}
public void setCountInterval(int countInterval) {
this.countInterval = countInterval;
}
@Override
public String toString() {
return "UpstreamMsgDist [refDate=" + refDate + ", msgUser=" + msgUser
+ ", countInterval=" + countInterval + "]";
}
}

View File

@ -0,0 +1,105 @@
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.UserSourceType;
/**
* 数据统计:用户增减
*
* @className UserSummary
* @author jy
* @date 2015年1月25日
* @since JDK 1.7
* @see
*/
public class UserSummary implements Serializable {
private static final long serialVersionUID = 5303181828798568052L;
/**
* 数据的日期
*/
@JSONField(name = "ref_date")
private Date refDate;
/**
* 用户的渠道
*/
@JSONField(name = "user_source")
private int userSource;
/**
* 新增的用户数量
*/
@JSONField(name = "new_user")
private int newUser;
/**
* 取消关注的用户数量new_user减去cancel_user即为净增用户数量
*/
@JSONField(name = "cancel_user")
private int cancelUser;
/**
* 总用户量
*/
@JSONField(name = "cumulate_user")
private int cumulateUser;
public Date getRefDate() {
return refDate;
}
public void setRefDate(Date refDate) {
this.refDate = refDate;
}
public UserSourceType getUserSource() {
if (userSource == 30) {
return UserSourceType.QRCODE;
} else if (userSource == 17) {
return UserSourceType.CARDSHARE;
} else if (userSource == 35) {
return UserSourceType.SONUMBER;
} else if (userSource == 39) {
return UserSourceType.SOMPACCOUNT;
} else if (userSource == 43) {
return UserSourceType.ARTICLEMENU;
} else {
return UserSourceType.OTHER;
}
}
public void setUserSource(int userSource) {
this.userSource = userSource;
}
public int getNewUser() {
return newUser;
}
public void setNewUser(int newUser) {
this.newUser = newUser;
}
public int getCancelUser() {
return cancelUser;
}
public void setCancelUser(int cancelUser) {
this.cancelUser = cancelUser;
}
public int getCumulateUser() {
return cumulateUser;
}
public void setCumulateUser(int cumulateUser) {
this.cumulateUser = cumulateUser;
}
@Override
public String toString() {
return "UserSummary [refDate=" + refDate + ", userSource="
+ getUserSource() + ", newUser=" + newUser + ", cancelUser="
+ cancelUser + ", cumulateUser=" + cumulateUser + "]";
}
}

View File

@ -0,0 +1,60 @@
package com.foxinmy.weixin4j.mp.message;
import java.io.Serializable;
import com.foxinmy.weixin4j.tuple.NotifyTuple;
/**
* 客服消息(48小时内不限制发送次数)
*
* @author jy.hu
* @date 2014年4月4日
* @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 <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>
*/
public class NotifyMessage implements Serializable {
private static final long serialVersionUID = 7190233634431087729L;
/**
* 用户的openid
*/
private String touser;
/**
* 消息对象
*/
private NotifyTuple tuple;
public NotifyMessage(String touser, NotifyTuple tuple) {
this.touser = touser;
this.tuple = tuple;
}
public String getTouser() {
return touser;
}
public void setTouser(String touser) {
this.touser = touser;
}
public NotifyTuple getTuple() {
return tuple;
}
public void setTuple(NotifyTuple tuple) {
this.tuple = tuple;
}
@Override
public String toString() {
return "NotifyMessage [touser=" + touser + ", tuple=" + tuple + "]";
}
}

View File

@ -0,0 +1,3 @@
NotifyMessage: (客服消息)[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]
TemplateMessage: (模板消息)[http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html]

View File

@ -0,0 +1,144 @@
package com.foxinmy.weixin4j.mp.message;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
/**
* 模板消息
*
* @className TemplateMessage
* @author jy
* @date 2014年9月29日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html">模板消息</a>
*/
public class TemplateMessage implements Serializable {
private static final long serialVersionUID = 7950608393821661436L;
/**
* 用户的openid
*/
private String touser;
/**
* 模板ID
*/
private String template_id;
/**
* 点击消息跳转的url
*/
private String url;
/**
* 顶部的颜色值
*/
private String topcolor = "#FF0000";
/**
* 数据项
*/
private Map<String, Item> data;
public void pushData(String key, String value) {
this.data.put(key, new Item(value));
}
public TemplateMessage(String touser, String template_id, String title,
String url) {
this.touser = touser;
this.template_id = template_id;
this.url = url;
this.data = new HashMap<String, Item>();
pushData("first", title);
}
/**
* 模板消息的数据项
*
* @className Item
* @author jy
* @date 2015年3月29日
* @since JDK 1.7
* @see
*/
private static class Item implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 字段值
*/
private String value;
/**
* 颜色值
*/
private String color;
public Item(String value) {
this(value, "#173177");
}
public Item(String value, String color) {
this.value = value;
this.color = color;
}
public String getValue() {
return value;
}
public String getColor() {
return color;
}
@Override
public String toString() {
return "$ [value=" + getValue() + ", color=" + getColor() + "]";
}
}
public String getTouser() {
return touser;
}
public void setTouser(String touser) {
this.touser = touser;
}
public String getTemplate_id() {
return template_id;
}
public void setTemplate_id(String template_id) {
this.template_id = template_id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTopcolor() {
return topcolor;
}
public void setTopcolor(String topcolor) {
this.topcolor = topcolor;
}
public Map<String, Item> getData() {
return data;
}
public void setData(Map<String, Item> data) {
this.data = data;
}
@Override
public String toString() {
return "TemplateMessage [touser=" + touser + ", template_id="
+ template_id + ", url=" + url + ", topcolor=" + topcolor
+ ", data=" + data + "]";
}
}

View File

@ -0,0 +1,260 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.AutomatchMode;
import com.foxinmy.weixin4j.mp.type.AutoreplyMode;
/**
* 自动回复设置
*
* @className AutoReplySetting
* @author jy
* @date 2015年4月15日
* @since JDK 1.7
* @see
*/
public class AutoReplySetting implements Serializable {
private static final long serialVersionUID = 8164017927864497009L;
/**
* 关注后自动回复是否开启
*/
@JSONField(name = "is_add_friend_reply_open")
private boolean isAddFriendReplyOpen;
/**
* 消息自动回复是否开启
*/
@JSONField(name = "is_autoreply_open")
private boolean isAutoreplyOpen;
/**
* 关注后自动回复的信息
*
* @see com.foxinmy.weixin4j.mp.model.AutoReplySetting.Entry
*/
@JSONField(name = "add_friend_autoreply_info")
private Entry addFriendReply;
/**
* 默认自动回复的信息
*
* @see com.foxinmy.weixin4j.mp.model.AutoReplySetting.Entry
*/
@JSONField(name = "message_default_autoreply_info")
private Entry defaultReply;
/**
* 关键词自动回复的信息
*
* @see com.foxinmy.weixin4j.mp.model.AutoReplySetting.Rule
*/
private List<Rule> keywordReplyList;
public boolean isAddFriendReplyOpen() {
return isAddFriendReplyOpen;
}
public void setAddFriendReplyOpen(boolean isAddFriendReplyOpen) {
this.isAddFriendReplyOpen = isAddFriendReplyOpen;
}
public boolean isAutoreplyOpen() {
return isAutoreplyOpen;
}
public void setAutoreplyOpen(boolean isAutoreplyOpen) {
this.isAutoreplyOpen = isAutoreplyOpen;
}
public Entry getAddFriendReply() {
return addFriendReply;
}
public void setAddFriendReply(Entry addFriendReply) {
this.addFriendReply = addFriendReply;
}
public Entry getDefaultReply() {
return defaultReply;
}
public void setDefaultReply(Entry defaultReply) {
this.defaultReply = defaultReply;
}
public List<Rule> getKeywordReplyList() {
return keywordReplyList;
}
public void setKeywordReplyList(List<Rule> keywordReplyList) {
this.keywordReplyList = keywordReplyList;
}
@Override
public String toString() {
return "AutoReplySetting [isAddFriendReplyOpen=" + isAddFriendReplyOpen
+ ", isAutoreplyOpen=" + isAutoreplyOpen + ", addFriendReply="
+ addFriendReply + ", defaultReply=" + defaultReply
+ ", keywordReplyList=" + keywordReplyList + "]";
}
/**
* 关键字规则
*
* @className Rule
* @author jy
* @date 2015年4月15日
* @since JDK 1.7
* @see
*/
public static class Rule implements Serializable {
private static final long serialVersionUID = -7299903545861946025L;
/**
* 规则名称
*/
@JSONField(name = "rule_name")
private String ruleName;
/**
* 创建时间
*/
@JSONField(name = "create_time")
private Date createTime;
/**
* 回复模式
*
* @see com.foxinmy.weixin4j.mp.type.AutoreplyMode
*/
@JSONField(name = "reply_mode")
private AutoreplyMode replyMode;
/**
* 匹配的关键词列表
*
* @see com.foxinmy.weixin4j.mp.model.AutoReplySetting.Entry
*/
@JSONField(name = "keyword_list_info")
private List<Entry> keywordList;
/**
* 回复的信息列表
*
* @see com.foxinmy.weixin4j.mp.model.AutoReplySetting.Entry
*/
@JSONField(name = "reply_list_info")
private List<Entry> replyList;
public String getRuleName() {
return ruleName;
}
public void setRuleName(String ruleName) {
this.ruleName = ruleName;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public AutoreplyMode getReplyMode() {
return replyMode;
}
public void setReplyMode(AutoreplyMode replyMode) {
this.replyMode = replyMode;
}
public List<Entry> getKeywordList() {
return keywordList;
}
public void setKeywordList(List<Entry> keywordList) {
this.keywordList = keywordList;
}
public List<Entry> getReplyList() {
return replyList;
}
public void setReplyList(List<Entry> replyList) {
this.replyList = replyList;
}
@Override
public String toString() {
return "Rule [ruleName=" + ruleName + ", createTime="
+ createTime + ", replyMode=" + replyMode
+ ", keywordList=" + keywordList + ", replyList="
+ replyList + "]";
}
}
/**
* 数据项
*
* @className Entry
* @author jy
* @date 2015年4月15日
* @since JDK 1.7
* @see
*/
public static class Entry implements Serializable {
private static final long serialVersionUID = -187922224547974025L;
/**
* 自动回复的类型关注后自动回复和消息自动回复的类型仅支持文本text图片img语音voice视频video
* 关键词自动回复则还多了图文消息(news)
*/
private String type;
/**
* 对于文本类型content是文本内容对于图片语音视频类型content是mediaID,news是article
*
* @see com.foxinmy.weixin4j.tuple.MpArticle
*/
private Serializable content;
/**
* 匹配模式(仅但关键字列表)
*
* @see com.foxinmy.weixin4j.mp.type.AutomatchMode
*/
@JSONField(name = "match_mode")
private AutomatchMode matchMode;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Serializable getContent() {
return content;
}
public void setContent(Serializable content) {
this.content = content;
}
public AutomatchMode getMatchMode() {
return matchMode;
}
public void setMatchMode(AutomatchMode matchMode) {
this.matchMode = matchMode;
}
@Override
public String toString() {
return "Entry [type=" + type + ", content=" + content
+ ", matchMode=" + matchMode + "]";
}
}
}

View File

@ -0,0 +1,92 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.Date;
import com.foxinmy.weixin4j.mp.type.CustomRecordOperCode;
/**
* 客服聊天记录
*
* @className CustomRecord
* @author jy
* @date 2014年6月28日
* @since JDK 1.7
*/
public class CustomRecord implements Serializable {
private static final long serialVersionUID = -4024147769411601325L;
/**
* 客服账号
*/
private String worker;
/**
* 用户的标识
*/
private String openid;
/**
* 操作ID会话状态
*/
private CustomRecordOperCode opercode;
/**
* 操作时间
*/
private Date time;
/**
* 聊天记录
*/
private String text;
public String getWorker() {
return worker;
}
public void setWorker(String worker) {
this.worker = worker;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public CustomRecordOperCode getOpercode() {
return opercode;
}
public void setOpercode(int opercode) {
this.opercode = CustomRecordOperCode.getOper(opercode);
}
public Date getTime() {
return (Date) time.clone();
}
public void setTime(long time) {
this.time = new Date(time * 1000l);
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[CustomRecord worker=").append(worker);
sb.append(" ,openid=").append(openid);
sb.append(" ,opercode=").append(opercode);
sb.append(" ,time=").append(time);
sb.append(" ,text=").append(text);
sb.append("]");
return sb.toString();
}
}

View File

@ -0,0 +1,100 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.List;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 关注信息
*
* @author jy.hu
* @date 2014年4月4日
* @since JDK 1.7
*/
public class Following implements Serializable {
private static final long serialVersionUID = 1917454368271027134L;
/**
* 关注总数
*/
private int total;
/**
* 拉取的OPENID个数最大值为10000
*/
private int count;
/**
* 列表数据OPENID的列表
*/
@JSONField(name = "data")
private JSONObject dataJson;
/**
* 拉取列表的后一个用户的OPENID
*/
@JSONField(name = "next_openid")
private String nextOpenId;
/**
* 用户详情列表
*
* @see com.foxinmy.weixin4j.mp.model.User
*/
private List<User> userList;
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
public JSONObject getDataJson() {
return dataJson;
}
public void setDataJson(JSONObject dataJson) {
this.dataJson = dataJson;
}
public String getNextOpenId() {
return nextOpenId;
}
public void setNextOpenId(String nextOpenId) {
this.nextOpenId = nextOpenId;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[Following total=").append(total);
sb.append(", count=").append(count);
if (userList != null && !userList.isEmpty()) {
sb.append(", users={");
for (User u : userList) {
sb.append(u.toString());
}
sb.append("}");
}
sb.append(", nextOpenId=").append(nextOpenId).append("]");
return sb.toString();
}
}

View File

@ -0,0 +1,104 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
/**
* 分组
*
* @author jy.hu
* @date 2014年4月4日
* @since JDK 1.7
*/
public class Group implements Serializable {
private static final long serialVersionUID = 6979565973974005954L;
/**
* 分组id由微信分配
*/
private int id;
/**
* 分组名
*/
private String name;
/**
* 分组内用户数量
*/
private int count;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCount() {
return count;
}
public Group(int id, String name) {
this.id = id;
this.name = name;
}
public Group(String name) {
this.name = name;
}
public Group() {
}
/**
* 返回创建分组所需的json格式字符串
*
* @return {"group": {"name": "test"}}
*/
public String toCreateJson() {
return String.format("{\"group\":{\"name\":\"%s\"}}", name);
}
/**
* 返回修改分组所需的json格式字符串
*
* @return {"group": {"id": 107, "name": "test"}}
*/
public String toModifyJson() {
return String.format("{\"group\":{\"id\":%s,\"name\":\"%s\"}}", id,
name);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Group) {
return id == ((Group) obj).getId();
}
return super.equals(obj);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + count;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public String toString() {
return String.format("[Group id=%d ,name=%s ,count=%d]", id, name,
count);
}
}

View File

@ -0,0 +1,126 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.KfOnlineStatus;
/**
* 多客服账号信息
*
* @className KfAccount
* @author jy
* @date 2014年11月16日
* @since JDK 1.7
* @see <a href="http://dkf.qq.com/document-3_1.html">多客服账号信息</a>
*/
public class KfAccount implements Serializable {
private static final long serialVersionUID = -4565570894727129245L;
/**
* 客服账号@微信别名 微信别名如有修改旧账号返回旧的微信别名新增的账号返回新的微信别名
*/
@JSONField(name = "kf_account")
private String account;
/**
* 客服昵称
*/
@JSONField(name = "kf_nick")
private String nickName;
/**
* 客服工号
*/
@JSONField(name = "kf_id")
private String id;
/**
* 客服头像
*/
@JSONField(name = "kf_headimg")
private String headImg;
// 以下字段是调用在线客服状态返回的字段
/**
* 客服在线状态 1pc在线2手机在线 若pc和手机同时在线则为 1+2=3
*/
private KfOnlineStatus status;
/**
* 客服设置的最大自动接入数
*/
@JSONField(name = "auto_accept")
private int autoAccept;
/**
* 客服当前正在接待的会话数
*/
@JSONField(name = "accepted_case")
private int acceptedCase;
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getHeadImg() {
return headImg;
}
public void setHeadImg(String headImg) {
this.headImg = headImg;
}
public KfOnlineStatus getStatus() {
return status;
}
public void setStatus(int status) {
if (status == 1) {
this.status = KfOnlineStatus.PC;
} else if (status == 2) {
this.status = KfOnlineStatus.MOBILE;
} else {
this.status = KfOnlineStatus.BOTH;
}
}
public int getAutoAccept() {
return autoAccept;
}
public void setAutoAccept(int autoAccept) {
this.autoAccept = autoAccept;
}
public int getAcceptedCase() {
return acceptedCase;
}
public void setAcceptedCase(int acceptedCase) {
this.acceptedCase = acceptedCase;
}
@Override
public String toString() {
return "KfAccount [account=" + account + ", nickName=" + nickName
+ ", id=" + id + ", status=" + status + ", autoAccept="
+ autoAccept + ", acceptedCase=" + acceptedCase + "]";
}
}

View File

@ -0,0 +1,66 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 客服会话信息
*
* @className KfSession
* @author jy
* @date 2015年3月22日
* @since JDK 1.7
* @see
*/
public class KfSession implements Serializable {
private static final long serialVersionUID = 7236468333492555458L;
/**
* 客服账号
*/
@JSONField(name = "kf_account")
private String kfAccount;
/**
* 用户ID
*/
@JSONField(name = "openid")
private String userOpenId;
/**
* 创建时间
*/
@JSONField(name = "createtime")
private Date createTime;
public String getKfAccount() {
return kfAccount;
}
public void setKfAccount(String kfAccount) {
this.kfAccount = kfAccount;
}
public String getUserOpenId() {
return userOpenId;
}
public void setUserOpenId(String userOpenId) {
this.userOpenId = userOpenId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "KfSession [kfAccount=" + kfAccount + ", userOpenId="
+ userOpenId + ", createTime=" + createTime + "]";
}
}

View File

@ -0,0 +1,79 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 媒体素材总数
*
* @className MediaCounter
* @author jy
* @date 2015年3月22日
* @since JDK 1.7
* @see
*/
public class MediaCounter implements Serializable {
private static final long serialVersionUID = -1752502821323552783L;
/**
* 语音总数量
*/
@JSONField(name = "voice_count")
private long voiceCount;
/**
* 视频总数量
*/
@JSONField(name = "video_count")
private long videoCount;
/**
* 图片总数量
*/
@JSONField(name = "image_count")
private long imageCount;
/**
* 图文总数量
*/
@JSONField(name = "news_count")
private long newsCount;
public long getVoiceCount() {
return voiceCount;
}
public void setVoiceCount(long voiceCount) {
this.voiceCount = voiceCount;
}
public long getVideoCount() {
return videoCount;
}
public void setVideoCount(long videoCount) {
this.videoCount = videoCount;
}
public long getImageCount() {
return imageCount;
}
public void setImageCount(long imageCount) {
this.imageCount = imageCount;
}
public long getNewsCount() {
return newsCount;
}
public void setNewsCount(long newsCount) {
this.newsCount = newsCount;
}
@Override
public String toString() {
return "MediaCounter [voiceCount=" + voiceCount + ", videoCount="
+ videoCount + ", imageCount=" + imageCount + ", newsCount="
+ newsCount + "]";
}
}

View File

@ -0,0 +1,80 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.tuple.MpArticle;
/**
* 媒体素材信息
*
* @className MediaItem
* @author jy
* @date 2015年3月22日
* @since JDK 1.7
* @see
*/
public class MediaItem implements Serializable {
private static final long serialVersionUID = -2923028664954250134L;
/**
* 媒体素材ID
*/
@JSONField(name = "media_id")
private String mediaId;
/**
* 媒体素材名称
*/
private String name;
/**
* 媒体素材最后更新时间
*/
@JSONField(name = "update_time")
private Date updateTime;
/**
* 图文素材列表
*/
@JSONField(name = "news_item")
private List<MpArticle> articles;
public String getMediaId() {
return mediaId;
}
public void setMediaId(String mediaId) {
this.mediaId = mediaId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public List<MpArticle> getArticles() {
return articles;
}
public void setArticles(List<MpArticle> articles) {
this.articles = articles;
}
@Override
public String toString() {
return "MediaItem [mediaId=" + mediaId + ", name=" + name
+ ", updateTime=" + updateTime + ", articles=" + articles + "]";
}
}

View File

@ -0,0 +1,81 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.type.MediaType;
/**
* 媒体素材记录
*
* @className MediaRecord
* @author jy
* @date 2015年3月22日
* @since JDK 1.7
* @see
*/
public class MediaRecord implements Serializable {
private static final long serialVersionUID = 7017503153256241457L;
/**
* 该类型的素材的总数
*/
@JSONField(name = "total_count")
private int totalCount;
/**
* 本次调用获取的素材的数量
*/
@JSONField(name = "item_count")
private int itemCount;
/**
* 媒体类型
*/
@JSONField(serialize = false)
private MediaType mediaType;
/**
* 媒体信息
*/
@JSONField(name = "item")
private List<MediaItem> items;
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getItemCount() {
return itemCount;
}
public void setItemCount(int itemCount) {
this.itemCount = itemCount;
}
public MediaType getMediaType() {
return mediaType;
}
public void setMediaType(MediaType mediaType) {
this.mediaType = mediaType;
}
public List<MediaItem> getItems() {
return items;
}
public void setItems(List<MediaItem> items) {
this.items = items;
}
@Override
public String toString() {
return "MediaRecord [totalCount=" + totalCount + ", itemCount="
+ itemCount + ", mediaType=" + mediaType + ", items=" + items
+ "]";
}
}

View File

@ -0,0 +1,48 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.List;
import com.foxinmy.weixin4j.model.Button;
/**
* 自定义菜单配置
*
* @className MenuSetting
* @author jy
* @date 2015年4月14日
* @since JDK 1.7
* @see
*/
public class MenuSetting implements Serializable {
private static final long serialVersionUID = 2461505572495855830L;
/**
* 菜单是否开启
*/
private boolean isMenuOpen;
/**
* 菜单列表
*/
private List<Button> buttons;
public MenuSetting(boolean isMenuOpen, List<Button> buttons) {
this.isMenuOpen = isMenuOpen;
this.buttons = buttons;
}
public boolean isMenuOpen() {
return isMenuOpen;
}
public List<Button> getButtons() {
return buttons;
}
@Override
public String toString() {
return "MenuSetting [isMenuOpen=" + isMenuOpen + ", buttons=" + buttons
+ "]";
}
}

View File

@ -0,0 +1,64 @@
package com.foxinmy.weixin4j.mp.model;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.model.Token;
/**
* 用户授权token 一般通过授权页面获得
*
* @className OauthToken
* @author jy.hu
* @date 2014年4月6日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.model.AuthResult
* @see com.foxinmy.weixin4j.mp.model.AuthResult.AuthScope
*/
public class OauthToken extends Token {
private static final long serialVersionUID = 1L;
/**
* 用户的openi
*/
private String openid;
/**
* 刷新token时的凭证
*/
@JSONField(name = "refresh_token")
private String refreshToken;
private String scope;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
@Override
public String toString() {
return "OauthToken [openid=" + openid + ", refreshToken="
+ refreshToken + ", scope=" + scope + ", getAccessToken()="
+ getAccessToken() + ", getExpiresIn()=" + getExpiresIn()
+ ", getTime()=" + getTime() + "]";
}
}

View File

@ -0,0 +1,119 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import com.foxinmy.weixin4j.mp.type.QRType;
/**
* 二维码参数对象
* <p>
* 目前有2种类型的二维码,分别是临时二维码和永久二维码,前者有过期时间,最大为1800秒,但能够生成较多数量,后者无过期时间,数量较少(目前参数只支持1--
* 100000)
* </p>
*
* @className QRParameter
* @author jy.hu
* @date 2014年4月8日
* @since JDK 1.7
*
*/
public class QRParameter implements Serializable {
private static final long serialVersionUID = 6611187606558274253L;
/**
* 临时二维码的有效时间,以秒为单位,最大不超过604800即7天
*/
private int expireSeconds;
/**
* 二维码类型
*
* @see com.foxinmy.weixin4j.mp.type.QRType
*/
private QRType qrType;
/**
* 场景值I 根据qrType参数而定
*/
private String sceneValue;
private QRParameter() {
}
public int getExpireSeconds() {
return expireSeconds;
}
public QRType getQrType() {
return qrType;
}
public String getSceneValue() {
return sceneValue;
}
private String content;
public String getContent() {
return content;
}
/**
* 创建临时二维码
*
* @param expireSeconds
* 有效时间
* @param sceneValue
* 二维码的场景值
* @return 二维码参数
*/
public static QRParameter createTemporary(int expireSeconds, int sceneValue) {
QRParameter qr = new QRParameter();
qr.qrType = QRType.QR_SCENE;
qr.expireSeconds = expireSeconds;
qr.sceneValue = Integer.toString(sceneValue);
qr.content = String
.format("{\"expire_seconds\": %s, \"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_id\": %s}}}",
expireSeconds, QRType.QR_SCENE.name(), sceneValue);
return qr;
}
/**
* 创建永久二维码(场景值为int)
*
* @param sceneValue
* 场景值
* @return 二维码参数
*/
public static QRParameter createPermanenceInt(int sceneValue) {
QRParameter qr = new QRParameter();
qr.qrType = QRType.QR_LIMIT_SCENE;
qr.sceneValue = Integer.toString(sceneValue);
qr.content = String
.format("{\"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_id\": %s}}}",
QRType.QR_LIMIT_SCENE.name(), sceneValue);
return qr;
}
/**
* 创建永久二维码(场景值为string)
*
* @param sceneValue
* 场景值
* @return 二维码参数
*/
public static QRParameter createPermanenceStr(String sceneValue) {
QRParameter qr = new QRParameter();
qr.qrType = QRType.QR_LIMIT_STR_SCENE;
qr.sceneValue = sceneValue;
qr.content = String
.format("{\"action_name\": \"%s\", \"action_info\": {\"scene\": {\"scene_str\": \"%s\"}}}",
QRType.QR_LIMIT_STR_SCENE, sceneValue);
return qr;
}
@Override
public String toString() {
return "QRParameter [expireSeconds=" + expireSeconds + ", qrType="
+ qrType + ", sceneValue=" + sceneValue + "]";
}
}

View File

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

View File

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

View File

@ -0,0 +1,215 @@
package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.model.Gender;
import com.foxinmy.weixin4j.mp.type.FaceSize;
import com.foxinmy.weixin4j.mp.type.Lang;
/**
* 用户对象
* <p>
* 当用户与公众号有交互时,可通过openid获取信息
* </p>
*
* @author jy.hu
* @date 2014年4月8日
* @since JDK 1.7
*/
public class User implements Serializable {
private static final long serialVersionUID = 1638176217299286265L;
/**
* 用户的唯一标识
*/
private String openid;
/**
* 用户昵称
*/
private String nickname;
/**
* 用户的性别值为1时是男性值为2时是女性值为0时是未知
*/
@JSONField(name = "sex")
private Gender gender;
/**
* 用户个人资料填写的省份
*/
private String province;
/**
* 普通用户个人资料填写的城市
*/
private String city;
/**
* 国家如中国为CN
*/
private String country;
/**
* 用户头像最后一个数值代表正方形头像大小有0466496132数值可选0代表640*640正方形头像用户没有头像时该项为空
*/
private String headimgurl;
/**
* 用户特权信息json 数组如微信沃卡用户为chinaunicom
*/
private String privilege;
/**
* 用户是否订阅该公众号标识值为0时代表此用户没有关注该公众号拉取不到其余信息
*/
@JSONField(name = "subscribe")
private boolean isSubscribe;
/**
* 关注时间
*/
@JSONField(name = "subscribe_time")
private Date subscribeTime;
/**
* 使用语言
*/
private Lang language;
/**
* 只有在用户将公众号绑定到微信开放平台帐号后才会出现该字段
*/
private String unionid;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public Gender getGender() {
return gender;
}
public void setGender(int sex) {
if (sex == 1) {
this.gender = Gender.male;
} else if (sex == 2) {
this.gender = Gender.female;
} else {
this.gender = Gender.unknown;
}
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getHeadimgurl() {
return headimgurl;
}
public String getHeadimgurl(FaceSize size) {
if (StringUtils.isNotBlank(headimgurl)) {
StringBuilder sb = new StringBuilder(headimgurl);
return sb.replace(0, (headimgurl.length() - 1), size.getInt() + "")
.toString();
}
return "";
}
public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}
public String getPrivilege() {
return privilege;
}
public void setPrivilege(String privilege) {
this.privilege = privilege;
}
public Lang getLanguage() {
return language;
}
public void setLanguage(Lang language) {
this.language = language;
}
public boolean isSubscribe() {
return isSubscribe;
}
public void setSubscribe(boolean isSubscribe) {
this.isSubscribe = isSubscribe;
}
public Date getSubscribeTime() {
return (Date) subscribeTime.clone();
}
public void setSubscribeTime(long subscribeTime) {
this.subscribeTime = new Date(subscribeTime * 1000l);
}
public String getUnionid() {
return unionid;
}
public void setUnionid(String unionid) {
this.unionid = unionid;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof User) {
return openid.equals(((User) obj).getOpenid());
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[User openid=").append(openid);
sb.append(", nickname=").append(nickname);
sb.append(", gender=").append(gender);
sb.append(", province=").append(province);
sb.append(", city=").append(city);
sb.append(", country=").append(country);
sb.append(", headimgurl=").append(headimgurl);
sb.append(", privilege=").append(privilege);
sb.append(", language=").append(language);
sb.append(", subscribeTime=").append(subscribeTime);
sb.append(", unionid=").append(unionid);
sb.append(", isSubscribe=").append(isSubscribe).append("]");
return sb.toString();
}
}

View File

@ -0,0 +1,56 @@
package com.foxinmy.weixin4j.mp.payment;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* JSAPI支付回调时的POST信息
*
* @className JsPayNotify
* @author jy
* @date 2014年8月19日
* @since JDK 1.7
* @see
*/
public class JsPayNotify extends PayBaseInfo {
private static final long serialVersionUID = -4659030958445259803L;
/**
* 用户的openid
*/
@XStreamAlias("OpenId")
private String openid;
/**
* 是否关注公众号
*/
@XStreamAlias("IsSubscribe")
private int issubscribe;
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public int getIssubscribe() {
return issubscribe;
}
public void setIssubscribe(int issubscribe) {
this.issubscribe = issubscribe;
}
@JSONField(serialize = false, deserialize = false)
public boolean getFormatIssubscribe() {
return issubscribe == 1;
}
@Override
public String toString() {
return "openid=" + openid + ", issubscribe=" + getFormatIssubscribe()
+ ", " + super.toString();
}
}

View File

@ -0,0 +1,117 @@
package com.foxinmy.weixin4j.mp.payment;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.model.WeixinMpAccount;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 刷卡支付
*
* @className MicroPayPackage
* @author jy
* @date 2014年11月17日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class MicroPayPackage extends PayPackage {
private static final long serialVersionUID = 8944928173669656177L;
/**
* 微信分配的公众账号 必须
*/
private String appid;
/**
* 微信支付分配的商户号 必须
*/
@XStreamAlias("mch_id")
@JSONField(name = "mch_id")
private String mchId;
/**
* 微信支付分配的终端设备号 非必须
*/
@XStreamAlias("device_info")
@JSONField(name = "device_info")
private String deviceInfo;
/**
* 随机字符串,不长于 32 必须
*/
@XStreamAlias("nonce_str")
@JSONField(name = "nonce_str")
private String nonceStr;
/**
* 签名 <font color="red">调用者不必关注</font>
*/
private String sign;
/**
* 扫码支付授权码 ,设备读取用户微信中的条码或者二维码信息
*/
@XStreamAlias("auth_code")
@JSONField(name = "auth_code")
private String authCode;
public MicroPayPackage(WeixinMpAccount weixinAccount, String body,
String attach, String outTradeNo, double totalFee,
String spbillCreateIp, String authCode) {
this(weixinAccount.getId(), weixinAccount.getMchId(), weixinAccount
.getDeviceInfo(), RandomUtil.generateString(16), body, attach,
outTradeNo, totalFee, spbillCreateIp, null, null, null,
authCode);
}
public MicroPayPackage(String appid, String mchId, String deviceInfo,
String nonceStr, String body, String attach, String outTradeNo,
double totalFee, String spbillCreateIp, Date timeStart,
Date timeExpire, String goodsTag, String authCode) {
super(body, attach, outTradeNo, totalFee, spbillCreateIp, timeStart,
timeExpire, goodsTag, null);
this.appid = appid;
this.mchId = mchId;
this.deviceInfo = deviceInfo;
this.nonceStr = nonceStr;
this.authCode = authCode;
}
public String getAppid() {
return appid;
}
public String getMchId() {
return mchId;
}
public String getDeviceInfo() {
return deviceInfo;
}
public String getNonceStr() {
return nonceStr;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getAuthCode() {
return authCode;
}
public void setAuthCode(String authCode) {
this.authCode = authCode;
}
@Override
public String toString() {
return "MicroPayPackage [appid=" + appid + ", mchId=" + mchId
+ ", deviceInfo=" + deviceInfo + ", nonceStr=" + nonceStr
+ ", sign=" + sign + ", authCode=" + authCode + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,111 @@
package com.foxinmy.weixin4j.mp.payment;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.SignType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 基本信息
*
* @className PayBaseInfo
* @author jy
* @date 2014年11月5日
* @since JDK 1.7
* @see
*/
public class PayBaseInfo implements Serializable {
private static final long serialVersionUID = 1843024880782466990L;
/**
* 公众号ID
*/
@XStreamAlias("AppId")
private String appId;
/**
* 时间戳
*/
@XStreamAlias("TimeStamp")
private String timeStamp;
/**
* 随机字符串
*/
@XStreamAlias("NonceStr")
private String nonceStr;
/**
* 签名结果
*/
@XStreamAlias("AppSignature")
private String paySign;
/**
* 签名方式
*/
@XStreamAlias("SignMethod")
private String signType;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
}
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public String getPaySign() {
return paySign;
}
public void setPaySign(String paySign) {
this.paySign = paySign;
}
public String getSignType() {
return signType;
}
@JSONField(serialize = false, deserialize = false)
public SignType getFormatSignType() {
return SignType.valueOf(signType.toUpperCase());
}
public void setSignType(SignType signType) {
if (signType != null) {
this.signType = signType.name();
} else {
this.signType = null;
}
}
public PayBaseInfo() {
}
public PayBaseInfo(String appId, String timestamp, String noncestr) {
this.appId = appId;
this.timeStamp = timestamp;
this.nonceStr = noncestr;
}
@Override
public String toString() {
return "appId=" + appId + ", timeStamp=" + timeStamp + ", nonceStr="
+ nonceStr + ", paySign=" + paySign + ", signType=" + signType;
}
}

View File

@ -0,0 +1,225 @@
package com.foxinmy.weixin4j.mp.payment;
import java.io.Serializable;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 订单信息
*
* @className PayPackage
* @author jy
* @date 2014年12月18日
* @since JDK 1.7
* @see
*/
public class PayPackage implements Serializable {
private static final long serialVersionUID = 3450161267802545790L;
/**
* 商品描述 必须
*/
private String body;
/**
* 商品详情 非必须
*/
private String detail;
/**
* 附加数据,原样返回 非必须
*/
private String attach;
/**
* 商户系统内部的订单号 ,32 个字符内 可包含字母 ,确保 在商户系统唯一 必须
*/
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo;
/**
* 订单总金额,单位为分,不能带小数点 必须
*/
@XStreamAlias("total_fee")
@JSONField(name = "total_fee")
private String totalFee;
/**
* 订单生成的机器 IP 必须
*/
@XStreamAlias("spbill_create_ip")
@JSONField(name = "spbill_create_ip")
private String spbillCreateIp;
/**
* 订单生成时间,格式为 yyyyMMddHHmmss, 2009 12月25日9点10分10秒表示为 20091225091010时区
* GMT+8 beijing该时间取 自商户服务器 非必须
*/
@XStreamAlias("time_start")
@JSONField(name = "time_start")
private String timeStart;
/**
* 订单失效时间,格为 yyyyMMddHHmmss, 2009 12月27日9点10分10秒表示为 20091227091010时区
* GMT+8 beijing该时间取 自商户服务商品标记 非必须
*/
@XStreamAlias("time_expire")
@JSONField(name = "time_expire")
private String timeExpire;
/**
* 商品标记,该字段不能随便填,不使用请填空 非必须
*/
@XStreamAlias("goods_tag")
@JSONField(name = "goods_tag")
private String goodsTag;
/**
* 通知地址接收微信支付成功通知 必须
*/
@XStreamAlias("notify_url")
@JSONField(name = "notify_url")
private String notifyUrl;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getTotalFee() {
return totalFee;
}
/**
* <font color="red">单位为元,自动格式化为分</font>
*
* @param totalFee
* 订单总额 单位为元
*/
public void setTotalFee(double totalFee) {
this.totalFee = DateUtil.formaFee2Fen(totalFee);
}
public String getSpbillCreateIp() {
return spbillCreateIp;
}
public void setSpbillCreateIp(String spbillCreateIp) {
this.spbillCreateIp = spbillCreateIp;
}
public String getTimeStart() {
return timeStart;
}
public void setTimeStart(String timeStart) {
this.timeStart = timeStart;
}
public void setTimeExpire(String timeExpire) {
this.timeExpire = timeExpire;
}
public void setTimeStart(Date timeStart) {
this.timeStart = timeStart != null ? DateUtil
.fortmat2yyyyMMddHHmmss(timeStart) : null;
}
public String getTimeExpire() {
return timeExpire;
}
public void setTimeExpire(Date timeExpire) {
this.timeExpire = timeExpire != null ? DateUtil
.fortmat2yyyyMMddHHmmss(timeExpire) : null;
}
public String getGoodsTag() {
return goodsTag;
}
public void setGoodsTag(String goodsTag) {
this.goodsTag = goodsTag;
}
public String getNotifyUrl() {
return notifyUrl;
}
public void setNotifyUrl(String notifyUrl) {
this.notifyUrl = notifyUrl;
}
public PayPackage() {
}
/**
* 订单对象
*
* @param body
* 订单描述
* @param attach
* 附加数据
* @param outTradeNo
* 商户内部ID
* @param totalFee
* 订单总额 <font color="red">单位为元</font>
* @param spbillCreateIp
* 生成订单数据的机器IP
* @param timeStart
* 订单生成时间
* @param timeExpire
* 订单失效时间
* @param goodsTag
* 订单标记
* @param notifyUrl
* 回调地址
*/
public PayPackage(String body, String attach, String outTradeNo,
double totalFee, String spbillCreateIp, Date timeStart,
Date timeExpire, String goodsTag, String notifyUrl) {
this.body = body;
this.attach = attach;
this.outTradeNo = outTradeNo;
this.totalFee = DateUtil.formaFee2Fen(totalFee);
this.spbillCreateIp = spbillCreateIp;
this.timeStart = timeStart != null ? DateUtil
.fortmat2yyyyMMddHHmmss(timeStart) : null;
this.timeExpire = timeExpire != null ? DateUtil
.fortmat2yyyyMMddHHmmss(timeExpire) : null;
this.goodsTag = goodsTag;
this.notifyUrl = notifyUrl;
}
@Override
public String toString() {
return "PayPackage [body=" + body + ", detail=" + detail + ", attach="
+ attach + ", outTradeNo=" + outTradeNo + ", totalFee="
+ totalFee + ", spbillCreateIp=" + spbillCreateIp
+ ", timeStart=" + timeStart + ", timeExpire=" + timeExpire
+ ", goodsTag=" + goodsTag + ", notifyUrl=" + notifyUrl + "]";
}
}

View File

@ -0,0 +1,40 @@
package com.foxinmy.weixin4j.mp.payment;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
public class PayRequest extends PayBaseInfo {
private static final long serialVersionUID = -453746488398523883L;
/**
* 订单详情扩展 订单信息组成该字符串
*/
@XStreamAlias("Package")
@JSONField(name = "package")
private String packageInfo;
public PayRequest() {
super(null, DateUtil.timestamp2string(), RandomUtil.generateString(16));
}
public PayRequest(String appId, String packageInfo) {
super(appId, DateUtil.timestamp2string(), RandomUtil.generateString(16));
this.packageInfo = packageInfo;
}
public String getPackageInfo() {
return packageInfo;
}
public void setPackageInfo(String packageInfo) {
this.packageInfo = packageInfo;
}
@Override
public String toString() {
return "package" + packageInfo + ", " + super.toString();
}
}

View File

@ -0,0 +1,457 @@
package com.foxinmy.weixin4j.mp.payment;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.mp.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.v2.JsPayRequestV2;
import com.foxinmy.weixin4j.mp.payment.v2.NativePayResponseV2;
import com.foxinmy.weixin4j.mp.payment.v2.PayPackageV2;
import com.foxinmy.weixin4j.mp.payment.v3.PayPackageV3;
import com.foxinmy.weixin4j.mp.payment.v3.PayRequestV3;
import com.foxinmy.weixin4j.mp.payment.v3.PrePay;
import com.foxinmy.weixin4j.mp.type.SignType;
import com.foxinmy.weixin4j.mp.type.TradeType;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.MapUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.foxinmy.weixin4j.xml.XmlStream;
/**
* 支付工具类(JSAPI,NATIVE,MicroPay)
*
* @className PayUtil
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
public class PayUtil {
/**
* 生成JSAPI字符串
*
* @param payPackage
* 订单信息
* @param WeixinMpAccount
* 商户信息
* @since V2 & V3
* @return 支付json串
* @throws PayException
*/
public static String createPayJsRequestJson(PayPackage payPackage,
WeixinMpAccount weixinAccount) throws PayException {
if (payPackage instanceof PayPackageV2) {
return createPayJsRequestJsonV2((PayPackageV2) payPackage,
weixinAccount);
} else if (payPackage instanceof PayPackageV3) {
return createPayJsRequestJsonV3((PayPackageV3) payPackage,
weixinAccount);
} else {
throw new PayException("unknown pay");
}
}
/**
* 生成V2.x版本JSAPI支付字符串
*
* @param payPackage
* 订单信息
* @param weixinAccount
* 商户信息
* @return 支付json串
*/
public static String createPayJsRequestJsonV2(PayPackageV2 payPackage,
WeixinMpAccount weixinAccount) {
if (StringUtils.isBlank(payPackage.getPartner())) {
payPackage.setPartner(weixinAccount.getPartnerId());
}
JsPayRequestV2 jsPayRequest = new JsPayRequestV2(weixinAccount,
payPackage);
jsPayRequest.setPaySign(paysignSha(jsPayRequest,
weixinAccount.getPaySignKey()));
jsPayRequest.setSignType(SignType.SHA1);
return JSON.toJSONString(jsPayRequest);
}
/**
* 生成V2.x版本JSAPI支付字符串
*
* @param body
* 支付详情
* @param orderNo
* 订单号
* @param orderFee
* 订单总额 按实际金额传入即可() 构造函数会转换为分
* @param ip
* @param weixinAccount
* 商户信息
* @return 支付json串
*/
public static String createPayJsRequestJsonV2(String body, String orderNo,
double orderFee, String notify_url, String ip,
WeixinMpAccount weixinAccount) {
PayPackageV2 payPackage = new PayPackageV2(body, orderNo, orderFee,
notify_url, ip);
payPackage.setPartner(weixinAccount.getPartnerId());
return createPayJsRequestJsonV2(payPackage, weixinAccount);
}
/**
* sha签名(一般用于V2.x支付接口)
*
* @param obj
* 签名对象
* @return
*/
public static String paysignSha(Object obj) {
return DigestUtils
.sha1Hex(MapUtil.toJoinString(obj, false, true, null));
}
/**
* sha签名(一般用于V2.x支付接口)
*
* @param obj
* 签名对象
* @param paySignKey
* 支付API的密钥<font color="red">请注意排序放进去的是put("appKey",
* paySignKey)</font>
* @return
*/
public static String paysignSha(Object obj, String paySignKey) {
Map<String, String> extra = new HashMap<String, String>();
extra.put("appKey", paySignKey);
return DigestUtils.sha1Hex(MapUtil
.toJoinString(obj, false, true, extra));
}
/**
* md5签名(一般用于V3.x支付接口)
*
* @param obj
* 签名对象
* @param paySignKey
* 支付API的密钥
* @return
*/
public static String paysignMd5(Object obj, String paySignKey) {
StringBuilder sb = new StringBuilder();
// a--->string1
sb.append(MapUtil.toJoinString(obj, false, false, null));
// b--->
// string1 最后拼接上 key=paternerKey 得到 stringSignTemp 字符串,
// stringSignTemp 进行 md5 运算
// 再将得到的 字符串所有字符转换为大写 ,得到 sign signValue
sb.append("&key=").append(paySignKey);
return DigestUtils.md5Hex(sb.toString()).toUpperCase();
}
/**
* 生成V3.x版本JSAPI支付字符串
*
* @param openId
* 用户ID
* @param body
* 订单描述
* @param orderNo
* 订单号
* @param orderFee
* 订单总额 按实际金额传入即可() 构造函数会转换为分
* @param notifyUrl
* 支付通知地址
* @param ip
* ip地址
* @param weixinAccount
* 商户信息
* @return 支付json串
* @throws PayException
*/
public static String createPayJsRequestJsonV3(String openId, String body,
String orderNo, double orderFee, String notifyUrl, String ip,
WeixinMpAccount weixinAccount) throws PayException {
PayPackageV3 payPackage = new PayPackageV3(weixinAccount, openId, body,
orderNo, orderFee, ip, TradeType.JSAPI);
payPackage.setNotifyUrl(notifyUrl);
return createPayJsRequestJsonV3(payPackage, weixinAccount);
}
/**
* 生成V3.x版本JSAPI支付字符串
*
* @param payPackage
* 订单信息
* @param weixinAccount
* 商户信息
* @return 支付json串
* @throws PayException
*/
public static String createPayJsRequestJsonV3(PayPackageV3 payPackage,
WeixinMpAccount weixinAccount) throws PayException {
String paySignKey = weixinAccount.getPaySignKey();
payPackage.setSign(paysignMd5(payPackage, paySignKey));
PrePay prePay = createPrePay(payPackage, paySignKey);
PayRequestV3 jsPayRequest = new PayRequestV3(prePay);
jsPayRequest.setSignType(SignType.MD5);
jsPayRequest.setPaySign(paysignMd5(jsPayRequest, paySignKey));
return JSON.toJSONString(jsPayRequest);
}
/**
* 统一下单接口</br>
* 除被扫支付场景以外商户系统先调用该接口在微信支付服务后台生成预支付交易单返回正确的预支付交易回话标识后再按扫码JSAPI
* APP等不同场景生成交易串调起支付
*
* @param payPackage
* 包含订单信息的对象
* @param paySignKey
* <font color="red">如果sign为空 则拿paysignkey进行签名</font>
* @see com.foxinmy.weixin4j.mp.payment.v3.PayPackageV3
* @see com.foxinmy.weixin4j.mp.payment.v3.PrePay
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1">统一下单接口</a>
* @return 预支付对象
*/
public static PrePay createPrePay(PayPackageV3 payPackage, String paySignKey)
throws PayException {
if (StringUtils.isBlank(payPackage.getSign())) {
payPackage.setSign(paysignMd5(payPackage, paySignKey));
}
String payJsRequestXml = XmlStream.to(payPackage).replaceAll("__", "_");
HttpRequest request = new HttpRequest();
try {
Response response = request.post(Consts.UNIFIEDORDER,
payJsRequestXml);
PrePay prePay = response.getAsObject(new TypeReference<PrePay>() {
});
if (!prePay.getReturnCode().equalsIgnoreCase(Consts.SUCCESS)) {
throw new PayException(prePay.getReturnMsg(),
prePay.getReturnCode());
}
if (!prePay.getResultCode().equalsIgnoreCase(Consts.SUCCESS)) {
throw new PayException(prePay.getResultCode(),
prePay.getErrCodeDes());
}
return prePay;
} catch (WeixinException e) {
throw new PayException(e.getErrorCode(), e.getErrorMsg());
}
}
/**
* <p>
* 生成编辑地址请求
* </p>
*
* err_msg edit_address:ok获取编辑收货地址成功</br> edit_address:fail获取编辑收货地址失败</br>
* userName 收货人姓名</br> telNumber 收货人电话</br> addressPostalCode 邮编</br>
* proviceFirstStageName 国标收货地址第一级地址</br> addressCitySecondStageName
* 国标收货地址第二级地址</br> addressCountiesThirdStageName 国标收货地址第三级地址</br>
* addressDetailInfo 详细收货地址信息</br> nationalCode 收货地址国家码</br>
*
* @param appId
* 公众号的ID
* @param url
* 当前访问页的URL
* @param accessToken
* snsapi_base授权时产生的token
* @return
*/
public static String createAddressRequestJson(String appId, String url,
String accessToken) {
Map<String, String> map = new HashMap<String, String>();
map.put("appId", appId);
map.put("timeStamp", DateUtil.timestamp2string());
map.put("nonceStr", RandomUtil.generateString(16));
map.put("url", url);
map.put("accessToken", accessToken);
String sign = paysignSha(map);
map.remove("url");
map.remove("accessToken");
map.put("scope", "jsapi_address");
map.put("signType", SignType.SHA1.name().toLowerCase());
map.put("addrSign", sign);
return JSON.toJSONString(map);
}
/**
* 创建V2.x NativePay支付链接
*
* @param weixinAccount
* 商户信息
* @param productId
* 与订单ID等价
* @return 支付链接
*/
public static String createNativePayRequestURLV2(
WeixinMpAccount weixinAccount, String productId) {
Map<String, String> map = new HashMap<String, String>();
String timestamp = DateUtil.timestamp2string();
String noncestr = RandomUtil.generateString(16);
map.put("appid", weixinAccount.getId());
map.put("timestamp", timestamp);
map.put("noncestr", noncestr);
map.put("productid", productId);
map.put("appkey", weixinAccount.getPaySignKey());
String sign = paysignSha(map);
return String.format(Consts.NATIVEURLV2, sign, weixinAccount.getId(),
productId, timestamp, noncestr);
}
/**
* 创建V3.x NativePay支付(扫码支付)链接
*
* @param weixinAccount
* 支付配置信息
* @param productId
* 与订单ID等价
* @return 支付链接
* @see <a href="http://pay.weixin.qq.com/wiki/doc/api/native.php">扫码支付</a>
*/
public static String createNativePayRequestURLV3(
WeixinMpAccount weixinAccount, String productId) {
Map<String, String> map = new HashMap<String, String>();
String timestamp = DateUtil.timestamp2string();
String noncestr = RandomUtil.generateString(16);
map.put("appid", weixinAccount.getId());
map.put("mch_id", weixinAccount.getMchId());
map.put("time_stamp", timestamp);
map.put("nonce_str", noncestr);
map.put("product_id", productId);
String sign = paysignMd5(map, weixinAccount.getPaySignKey());
return String.format(Consts.NATIVEURLV3, sign, weixinAccount.getId(),
weixinAccount.getMchId(), productId, timestamp, noncestr);
}
/**
* 创建V2.x NATIVE回调时的响应字符串
*
* @param weixinAccount
* 商户信息
* @param payPackage
* 订单信息
* @return
*/
public static String createNativePayResponseV2(
WeixinMpAccount weixinAccount, PayPackageV2 payPackage) {
NativePayResponseV2 payRequest = new NativePayResponseV2(weixinAccount,
payPackage);
Map<String, String> map = new HashMap<String, String>();
String timestamp = DateUtil.timestamp2string();
String noncestr = RandomUtil.generateString(16);
map.put("appid", weixinAccount.getId());
map.put("appkey", weixinAccount.getPaySignKey());
map.put("timestamp", timestamp);
map.put("noncestr", noncestr);
map.put("package", payRequest.getPackageInfo());
map.put("retcode", payRequest.getRetCode());
map.put("reterrmsg", payRequest.getRetMsg());
payRequest.setPaySign(paysignSha(map));
return XmlStream.to(payRequest);
}
/**
* 提交被扫支付
*
* @param authCode
* 扫码支付授权码 ,设备读取用户微信中的条码或者二维码信息
* @param body
* 商品描述
* @param attach
* 附加数据
* @param orderNo
* 商户内部唯一订单号
* @param orderFee
* 商品总额 单位元
* @param ip
* 订单生成的机器 IP
* @param weixinAccount
* 商户信息
* @return 支付的订单信息
* @see {@link com.foxinmy.weixin4j.mp.payment.PayUtil#createMicroPay(MicroPayPackage, WeixinMpAccount)}
* @throws WeixinException
*/
public static com.foxinmy.weixin4j.mp.payment.v3.Order createMicroPay(
String authCode, String body, String attach, String orderNo,
double orderFee, String ip, WeixinMpAccount weixinAccount)
throws WeixinException {
MicroPayPackage payPackage = new MicroPayPackage(weixinAccount, body,
attach, orderNo, orderFee, ip, authCode);
return createMicroPay(payPackage, weixinAccount);
}
/**
* 提交被扫支付:收银员使用扫码设备读取微信用户刷卡授权码以后二维码或条码信息传送至商户收银台由商户收银台或者商户后台调用该接口发起支付.
*
* @param payPackage
* 订单信息
* @param weixinAccount
* 商户信息
* @return 支付的订单信息
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @see <a
* href="http://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10">提交被扫支付API</a>
*/
public static com.foxinmy.weixin4j.mp.payment.v3.Order createMicroPay(
MicroPayPackage payPackage, WeixinMpAccount weixinAccount)
throws WeixinException {
String sign = paysignMd5(payPackage, weixinAccount.getPaySignKey());
payPackage.setSign(sign);
String para = XmlStream.to(payPackage).replaceAll("__", "_");
HttpRequest request = new HttpRequest();
Response response = request.post(Consts.MICROPAYURL, para);
return response
.getAsObject(new TypeReference<com.foxinmy.weixin4j.mp.payment.v3.Order>() {
});
}
private static String JSAPIV2() {
WeixinMpAccount weixinAccount = JSON.parseObject(
ConfigUtil.getValue("account"), WeixinMpAccount.class);
return createPayJsRequestJsonV2("支付测试", "JSAPI01", 0.01d, "127.0.0.0",
"http://127.0.0.1/jsapi/notify", weixinAccount);
}
private static String NATIVEV2() {
WeixinMpAccount weixinAccount = JSON.parseObject(
ConfigUtil.getValue("account"), WeixinMpAccount.class);
return createNativePayRequestURLV2(weixinAccount, "P1");
}
private static String JSAPIV3() throws PayException {
WeixinMpAccount weixinAccount = JSON.parseObject(
ConfigUtil.getValue("account"), WeixinMpAccount.class);
return createPayJsRequestJsonV3("oyFLst1bqtuTcxK-ojF8hOGtLQao", "支付测试",
"JSAPI01", 0.01d, "http://127.0.0.1/jsapi/notify", "127.0.0.0",
weixinAccount);
}
private static String NATIVEV3() {
WeixinMpAccount weixinAccount = JSON.parseObject(
ConfigUtil.getValue("account"), WeixinMpAccount.class);
return createNativePayRequestURLV3(weixinAccount, "P1");
}
public static void main(String[] args) throws PayException {
// V2版本下的JS支付
System.out.println(JSAPIV2());
// V2版本下的原生支付
System.out.println(NATIVEV2());
// V3版本下的JS支付
System.out.println(JSAPIV3());
// V3版本下的原生支付
System.out.println(NATIVEV3());
}
}

View File

@ -0,0 +1,54 @@
支付模块【JSAPI】【NATIVE】【MICROPAY】
微信公众平台[V2版本支付](https://mp.weixin.qq.com/paymch/readtemplate?t=mp/business/course2_tmpl&lang=zh_CN)文档
微信公众平台[V3版本支付](https://mp.weixin.qq.com/paymch/readtemplate?t=mp/business/course3_tmpl&lang=zh_CN)文档
**在`2014年10月9号`之前申请并审核通过的支付接口应该属于`V2版本`支付,而之后申请的接口则为`V3版本`支付**
[PayUtil](./PayUtil.java)
-------------------------
* createPayJsRequestJson: 创建JSAPI支付串
* createPayJsRequestJsonV2: 创建V2版本的JSAPI支付串
* createNativePayRequestURLV2: 创建V2版本的扫码支付链接
* createPayJsRequestJsonV3: 创建V3版本(商户平台)的JSAPI支付串
* createNativePayRequestURLV3: 创建V3版本(商户平台)的扫码支付链接
* createPrePay: 调用V3版本(商户平台)的统一订单接口生成预订单数据
* createMicroPay: 创建刷卡支付(商户平台)请求
* createAddressRequestJson: 生成编辑收货地址请求串
[Pay3Api](./Pay3Api.java)
-------------------------
* orderQuery: 订单查询接口
* refund: 退款申请接口
* reverse: 冲正订单接口
* closeOrder: 关闭订单接口
* downloadbill: 下载对账单接口
* refundQuery: 退款查询接口
[Pay2Api](./Pay2Api.java)
-------------------------
* orderQuery: 订单查询接口
* refund: 退款申请接口
* downloadbill: 下载对账单接口
* refundQuery: 退款查询接口

View File

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

View File

@ -0,0 +1,135 @@
package com.foxinmy.weixin4j.mp.payment.conver;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.mp.payment.v3.Order;
import com.foxinmy.weixin4j.mp.payment.v3.RefundResult;
import com.foxinmy.weixin4j.xml.XmlStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* V3订单详情转换类
*
* @className OrderConverter
* @author jy
* @date 2015年3月24日
* @since JDK 1.7
* @see
*/
public class CouponConverter {
private final static XmlStream xStream = XmlStream.get();
private final static Mapper mapper;
private final static ReflectionProvider reflectionProvider;
private final static Pattern pattern = Pattern.compile("(_\\d)$");
private static Class<CouponInfo> COUPON_CLASS = CouponInfo.class;
private static Class<?> clazz;
static {
xStream.processAnnotations(new Class[] { COUPON_CLASS });
xStream.registerConverter(new $());
mapper = xStream.getMapper();
reflectionProvider = xStream.getReflectionProvider();
}
public static <T> T fromXML(String xml, Class<T> clazz) {
CouponConverter.clazz = clazz;
xStream.processAnnotations(clazz);
return xStream.fromXML(xml, clazz);
}
private static class $ implements Converter {
@Override
public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) {
return clazz.equals(Order.class)
|| clazz.equals(RefundResult.class);
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
new ReflectionConverter(mapper, reflectionProvider).marshal(source,
writer, context);
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Object object = null;
try {
object = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Matcher matcher = null;
Map<String, Map<String, String>> outMap = new HashMap<String, Map<String, String>>();
while (reader.hasMoreChildren()) {
reader.moveDown();
String nodeName = reader.getNodeName();
String fieldName = mapper.realMember(clazz, nodeName);
Field field = reflectionProvider.getFieldOrNull(clazz,
fieldName);
if (field != null) {
Object value = context.convertAnother(object,
field.getType());
reflectionProvider.writeField(object, fieldName, value,
field.getDeclaringClass());
} else if ((matcher = pattern.matcher(nodeName)).find()) {
String key = matcher.group();
Map<String, String> innerMap = null;
if ((innerMap = outMap.get(key)) == null) {
innerMap = new HashMap<String, String>();
outMap.put(key, innerMap);
}
innerMap.put(nodeName.replace(key, ""), reader.getValue());
}
reader.moveUp();
}
if (!outMap.isEmpty()) {
StringBuilder couponXml = new StringBuilder();
couponXml.append("<list>");
for (Iterator<Entry<String, Map<String, String>>> outIt = outMap
.entrySet().iterator(); outIt.hasNext();) {
couponXml.append("<")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
for (Iterator<Entry<String, String>> innerIt = outIt.next()
.getValue().entrySet().iterator(); innerIt
.hasNext();) {
Entry<String, String> entry = innerIt.next();
couponXml.append("<").append(entry.getKey())
.append(">");
couponXml.append(entry.getValue());
couponXml.append("</").append(entry.getKey())
.append(">");
}
couponXml.append("</")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
}
couponXml.append("</list>");
reflectionProvider.writeField(object, "couponList",
xStream.fromXML(couponXml.toString(), List.class),
List.class.getDeclaringClass());
}
return object;
}
}
}

View File

@ -0,0 +1,190 @@
package com.foxinmy.weixin4j.mp.payment.conver;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.xml.XmlStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* 退款查询接口调用结果转换类
*
* @className RefundConverter
* @author jy
* @date 2014年11月2日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundDetail
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail
*/
public class RefundConverter {
private final static XmlStream xStream = XmlStream.get();
private final static Mapper mapper;
private final static ReflectionProvider reflectionProvider;
private final static Pattern REFUND_PATTERN = Pattern.compile("_\\d{1,}$");
private final static Pattern COUPON_PATTERN = Pattern
.compile("_\\d{1,}_\\d{1,}$");
private static Class<?> clazz;
private final static Class<CouponInfo> COUPON_CLASS = CouponInfo.class;
private final static Class<com.foxinmy.weixin4j.mp.payment.v2.RefundRecord> REFUNDRECORD2 = com.foxinmy.weixin4j.mp.payment.v2.RefundRecord.class;
private final static Class<com.foxinmy.weixin4j.mp.payment.v3.RefundRecord> REFUNDRECORD3 = com.foxinmy.weixin4j.mp.payment.v3.RefundRecord.class;
static {
xStream.processAnnotations(new Class[] { REFUNDRECORD2, REFUNDRECORD3 });
xStream.aliasField("refund_state",
com.foxinmy.weixin4j.mp.payment.v2.RefundDetail.class,
"refundStatus");
xStream.registerConverter(new $());
mapper = xStream.getMapper();
reflectionProvider = xStream.getReflectionProvider();
}
public static <T> T fromXML(String xml, Class<T> clazz) {
RefundConverter.clazz = clazz;
xStream.processAnnotations(clazz);
return xStream.fromXML(xml, clazz);
}
private static class $ implements Converter {
@Override
public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) {
return clazz.equals(REFUNDRECORD2) || clazz.equals(REFUNDRECORD3);
}
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
MarshallingContext context) {
new ReflectionConverter(mapper, reflectionProvider).marshal(source,
writer, context);
}
@Override
public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) {
Object refund = null;
try {
refund = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Matcher matcher = null;
Map<String, Map<String, String>> refundMap = new HashMap<String, Map<String, String>>();
Map<String, Map<String, String>> couponMap = new HashMap<String, Map<String, String>>();
while (reader.hasMoreChildren()) {
reader.moveDown();
String nodeName = reader.getNodeName();
String fieldName = mapper.realMember(clazz, nodeName);
Field field = reflectionProvider.getFieldOrNull(clazz,
fieldName);
if (field != null) {
Object value = context.convertAnother(refund,
field.getType());
reflectionProvider.writeField(refund, fieldName, value,
field.getDeclaringClass());
} else if ((matcher = REFUND_PATTERN.matcher(nodeName)).find()) {
String key = matcher.group();
Map<String, String> innerMap = null;
if ((matcher = COUPON_PATTERN.matcher(nodeName)).find()) {
key = matcher.group();
if ((innerMap = couponMap.get(key)) == null) {
innerMap = new HashMap<String, String>();
couponMap.put(key, innerMap);
}
} else {
if ((innerMap = refundMap
.get(String.format("%s_", key))) == null) {
innerMap = new HashMap<String, String>();
refundMap.put(String.format("%s_", key), innerMap);
}
}
innerMap.put(nodeName.replaceFirst(key, ""),
reader.getValue());
}
reader.moveUp();
}
if (!refundMap.isEmpty()) {
StringBuilder detailXml = new StringBuilder();
detailXml.append("<list>");
String detailCanonicalName = clazz.getCanonicalName()
.replaceFirst("RefundRecord", "RefundDetail");
for (Iterator<Entry<String, Map<String, String>>> refundIT = refundMap
.entrySet().iterator(); refundIT.hasNext();) {
detailXml.append("<").append(detailCanonicalName)
.append(">");
Entry<String, Map<String, String>> refundEntry = refundIT
.next();
for (Iterator<Entry<String, String>> innerIt = refundEntry
.getValue().entrySet().iterator(); innerIt
.hasNext();) {
Entry<String, String> entry = innerIt.next();
detailXml.append("<").append(entry.getKey())
.append(">");
detailXml.append(entry.getValue());
detailXml.append("</").append(entry.getKey())
.append(">");
}
if (!couponMap.isEmpty()) {
detailXml.append("<couponList class=\"")
.append(ArrayList.class.getCanonicalName())
.append("\">");
Iterator<Entry<String, Map<String, String>>> couponIT = couponMap
.entrySet().iterator();
while (couponIT.hasNext()) {
Entry<String, Map<String, String>> couponEntry = couponIT
.next();
if (couponEntry.getKey().startsWith(
refundEntry.getKey())) {
detailXml
.append("<")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
for (Iterator<Entry<String, String>> innerIt = couponEntry
.getValue().entrySet().iterator(); innerIt
.hasNext();) {
Entry<String, String> entry = innerIt
.next();
detailXml.append("<")
.append(entry.getKey()).append(">");
detailXml.append(entry.getValue());
detailXml.append("</")
.append(entry.getKey()).append(">");
}
detailXml
.append("</")
.append(COUPON_CLASS.getCanonicalName())
.append(">");
couponIT.remove();
}
}
detailXml.append("</couponList>");
}
detailXml.append("</").append(detailCanonicalName)
.append(">");
}
detailXml.append("</list>");
reflectionProvider.writeField(refund, "details",
xStream.fromXML(detailXml.toString(), List.class),
List.class.getDeclaringClass());
}
return refund;
}
}
}

View File

@ -0,0 +1,371 @@
package com.foxinmy.weixin4j.mp.payment.coupon;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.type.CouponStatus;
import com.foxinmy.weixin4j.mp.type.CouponStockType;
import com.foxinmy.weixin4j.mp.type.CouponType;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 代金券详细
*
* @className CouponDetail
* @author jy
* @date 2015年3月27日
* @since JDK 1.7
* @see
*/
public class CouponDetail extends ApiResult {
private static final long serialVersionUID = -311265355895457070L;
/**
* 代金券批次Id
*/
@XStreamAlias("coupon_stock_id")
@JSONField(name = "coupon_stock_id")
private String couponStockId;
/**
* 批次类型1-批量型2-触发型
*/
@XStreamAlias("coupon_stock_type")
@JSONField(name = "coupon_stock_type")
private int couponStockType;
/**
* 代金券id
*/
@XStreamAlias("coupon_id")
@JSONField(name = "coupon_id")
private String couponId;
/**
* 代金券面值,单位是分
*/
@XStreamAlias("coupon_value")
@JSONField(name = "coupon_value")
private int couponValue;
/**
* 代金券使用最低限额,单位是分
*/
@XStreamAlias("coupon_mininum")
@JSONField(name = "coupon_mininum")
private int couponMininum;
/**
* 代金券名称
*/
@XStreamAlias("coupon_name")
@JSONField(name = "coupon_name")
private String couponName;
/**
* 代金券状态2-已激活4-已锁定8-已实扣
*/
@XStreamAlias("coupon_state")
@JSONField(name = "coupon_state")
private int couponStatus;
/**
* 代金券类型1-代金券无门槛2-代金券有门槛互斥3-代金券有门槛叠加
*/
@XStreamAlias("coupon_type")
@JSONField(name = "coupon_type")
private int couponType;
/**
* 代金券描述
*/
@XStreamAlias("coupon_desc")
@JSONField(name = "coupon_desc")
private String couponDesc;
/**
* 代金券实际使用金额
*/
@XStreamAlias("coupon_use_value")
@JSONField(name = "coupon_use_value")
private int couponUseValue;
/**
* 代金券剩余金额部分使用情况下可能会存在券剩余金额
*/
@XStreamAlias("coupon_remain_value")
@JSONField(name = "coupon_remain_value")
private int couponRemainValue;
/**
* 生效开始时间:格式为yyyyMMddhhmmss如2009年12月27日9点10分10秒表示为20091227091010
*/
@XStreamAlias("begin_time")
@JSONField(name = "begin_time")
private String beginTime;
/**
* 生效结束时间:格式为yyyyMMddhhmmss如2009年12月27日9点10分10秒表示为20091227091010
*/
@XStreamAlias("end_time")
@JSONField(name = "end_time")
private String endTime;
/**
* 发放时间:格式为yyyyMMddhhmmss如2009年12月27日9点10分10秒表示为20091227091010
*/
@XStreamAlias("send_time")
@JSONField(name = "send_time")
private String sendTime;
/**
* 使用时间:格式为yyyyMMddhhmmss如2009年12月27日9点10分10秒表示为20091227091010
*/
@XStreamAlias("use_time")
@JSONField(name = "use_time")
private String useTime;
/**
* 使用单号:代金券使用后关联的大单收单单号
*/
@XStreamAlias("trade_no")
@JSONField(name = "trade_no")
private String tradeNo;
/**
* 消耗方商户id:代金券使用后消耗方商户id
*/
@XStreamAlias("consumer_mch_id")
@JSONField(name = "consumer_mch_id")
private String consumerMchId;
/**
* 消耗方商户名称:代金券使用后消耗方商户名称
*/
@XStreamAlias("consumer_mch_name")
@JSONField(name = "consumer_mch_name")
private String consumerMchName;
/**
* 消耗方商户appid:代金券使用后消耗方商户appid
*/
@XStreamAlias("consumer_mch_appid")
@JSONField(name = "consumer_mch_appid")
private String consumerMchAppid;
/**
* 发放来源:代金券发放来源
*/
@XStreamAlias("send_source")
@JSONField(name = "send_source")
private String sendSource;
/**
* 是否允许部分使用:该代金券是否允许部分使用标识1-表示支持部分使用
*/
@XStreamAlias("is_partial_use")
@JSONField(name = "is_partial_use")
private int isPartialUse;
public String getCouponStockId() {
return couponStockId;
}
public int getCouponStockType() {
return couponStockType;
}
@JSONField(deserialize = false, serialize = false)
public CouponStockType getFormatCouponStockType() {
for (CouponStockType couponStockType : CouponStockType.values()) {
if (couponStockType.getVal() == this.couponStockType) {
return couponStockType;
}
}
return null;
}
public String getCouponId() {
return couponId;
}
public int getCouponValue() {
return couponValue;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponValue() {
return couponValue / 100d;
}
public int getCouponMininum() {
return couponMininum;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponMininum() {
return couponMininum / 100d;
}
public String getCouponName() {
return couponName;
}
public int getCouponStatus() {
return couponStatus;
}
@JSONField(deserialize = false, serialize = false)
public CouponStatus getFormatCouponStatus() {
for (CouponStatus couponStatus : CouponStatus.values()) {
if (couponStatus.getVal() == this.couponStatus) {
return couponStatus;
}
}
return null;
}
public int getCouponType() {
return couponType;
}
@JSONField(deserialize = false, serialize = false)
public CouponType getFormatCouponType() {
for (CouponType couponType : CouponType.values()) {
if (couponType.getVal() == this.couponType) {
return couponType;
}
}
return null;
}
public String getCouponDesc() {
return couponDesc;
}
public int getCouponUseValue() {
return couponUseValue;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponUseValue() {
return couponUseValue / 100d;
}
public int getCouponRemainValue() {
return couponRemainValue;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponRemainValue() {
return couponRemainValue / 100d;
}
public String getBeginTime() {
return beginTime;
}
@JSONField(deserialize = false, serialize = false)
public Date getFormatBeginTime() {
return DateUtil.parse2yyyyMMddHHmmss(beginTime);
}
public String getEndTime() {
return endTime;
}
@JSONField(deserialize = false, serialize = false)
public Date getFormatEndTime() {
return DateUtil.parse2yyyyMMddHHmmss(endTime);
}
public String getSendTime() {
return sendTime;
}
@JSONField(deserialize = false, serialize = false)
public Date getFormatSendTime() {
return DateUtil.parse2yyyyMMddHHmmss(sendTime);
}
public String getUseTime() {
return useTime;
}
@JSONField(deserialize = false, serialize = false)
public Date getFormatUseTime() {
return StringUtils.isNotBlank(useTime) ? DateUtil
.parse2yyyyMMddHHmmss(useTime) : null;
}
public String getTradeNo() {
return tradeNo;
}
public String getConsumerMchId() {
return consumerMchId;
}
public String getConsumerMchName() {
return consumerMchName;
}
public String getConsumerMchAppid() {
return consumerMchAppid;
}
public String getSendSource() {
return sendSource;
}
public int getIsPartialUse() {
return isPartialUse;
}
@JSONField(deserialize = false, serialize = false)
public boolean getFormatIsPartialUse() {
return isPartialUse == 1;
}
@Override
public String toString() {
return "CouponDetail [couponStockId=" + couponStockId
+ ", couponStockType=" + getFormatCouponStockType()
+ ", couponId=" + couponId + ", couponValue="
+ getFormatCouponValue() + ", couponMininum="
+ getFormatCouponMininum() + ", couponName=" + couponName
+ ", couponStatus=" + getCouponStatus() + ", couponType="
+ getFormatCouponType() + ", couponDesc=" + couponDesc
+ ", couponUseValue=" + getFormatCouponUseValue()
+ ", couponRemainValue=" + getFormatCouponRemainValue()
+ ", beginTime=" + getFormatBeginTime() + ", endTime="
+ getFormatEndTime() + ", sendTime=" + getFormatSendTime()
+ ", useTime=" + getFormatUseTime() + ", tradeNo=" + tradeNo
+ ", consumerMchId=" + consumerMchId + ", consumerMchName="
+ consumerMchName + ", consumerMchAppid=" + consumerMchAppid
+ ", sendSource=" + sendSource + ", isPartialUse="
+ getFormatIsPartialUse() + ", " + super.toString()
+ "]";
}
}

View File

@ -0,0 +1,71 @@
package com.foxinmy.weixin4j.mp.payment.coupon;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 代金券信息(订单,退款中体现)
*
* @className CouponInfo
* @author jy
* @date 2015年3月24日
* @since JDK 1.7
* @see
*/
public class CouponInfo implements Serializable {
private static final long serialVersionUID = -8744999305258786901L;
/**
* 代金券或立减优惠批次ID
*/
@XStreamAlias("coupon_batch_id")
@JSONField(name = "coupon_batch_id")
private String couponBatchId;
/**
* 代金券或立减优惠ID
*/
@XStreamAlias("coupon_id")
@JSONField(name = "coupon_id")
private String couponId;
/**
* 单个代金券或立减优惠支付金额
*/
@XStreamAlias("coupon_fee")
@JSONField(name = "coupon_fee")
private Integer couponFee;
public String getCouponBatchId() {
return couponBatchId;
}
public String getCouponId() {
return couponId;
}
public Integer getCouponFee() {
return couponFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCouponFee() {
return couponFee / 100d;
}
public void setCouponId(String couponId) {
this.couponId = couponId;
}
@Override
public String toString() {
return "couponBatchId=" + couponBatchId + ", couponId=" + couponId
+ ", couponFee=" + couponFee;
}
}

View File

@ -0,0 +1,117 @@
package com.foxinmy.weixin4j.mp.payment.coupon;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 代金券发放结果
*
* @className CouponResult
* @author jy
* @date 2015年3月25日
* @since JDK 1.7
* @see
*/
public class CouponResult extends ApiResult {
private static final long serialVersionUID = -1996967923720149124L;
/**
* 代金券批次id
*/
@XStreamAlias("coupon_stock_id")
@JSONField(name = "coupon_stock_id")
private String couponStockId;
/**
* 返回记录数
*/
@XStreamAlias("resp_count")
@JSONField(name = "resp_count")
private int responseCount;
/**
* 成功记录数
*/
@XStreamAlias("success_count")
@JSONField(name = "success_count")
private int successCount;
/**
* 失败记录数
*/
@XStreamAlias("failed_count")
@JSONField(name = "failed_count")
private int failedCount;
/**
* 用户在商户appid下的唯一标识
*/
@XStreamAlias("openid")
@JSONField(name = "openid")
private String openId;
/**
* 返回码 SUCCESS或者FAILED
*/
@XStreamAlias("ret_code")
@JSONField(name = "ret_code")
private String retCode;
/**
* 代金券id
*/
@XStreamAlias("coupon_id")
@JSONField(name = "coupon_id")
private String couponId;
/**
* 失败描述信息例如用户已达领用上限
*/
@XStreamAlias("ret_msg")
@JSONField(name = "ret_msg")
private String retMsg;
public String getCouponStockId() {
return couponStockId;
}
public int getResponseCount() {
return responseCount;
}
public int getSuccessCount() {
return successCount;
}
public int getFailedCount() {
return failedCount;
}
public String getOpenId() {
return openId;
}
public String getRetCode() {
return retCode;
}
public String getCouponId() {
return couponId;
}
public String getRetMsg() {
return retMsg;
}
@Override
public String toString() {
return "CouponResult [couponStockId=" + couponStockId
+ ", responseCount=" + responseCount + ", successCount="
+ successCount + ", failedCount=" + failedCount + ", openId="
+ openId + ", retCode=" + retCode + ", couponId=" + couponId
+ ", retMsg=" + retMsg + "]";
}
}

View File

@ -0,0 +1,296 @@
package com.foxinmy.weixin4j.mp.payment.coupon;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.type.CouponStockStatus;
import com.foxinmy.weixin4j.mp.type.CouponType;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 代金券信息
*
* @className CouponStock
* @author jy
* @date 2015年3月27日
* @since JDK 1.7
* @see
*/
public class CouponStock extends ApiResult {
private static final long serialVersionUID = -8627202879200080499L;
/**
* 代金券批次ID
*/
@XStreamAlias("coupon_stock_id")
@JSONField(name = "coupon_stock_id")
private String couponStockId;
/**
* 代金券名称
*/
@XStreamAlias("coupon_name")
@JSONField(name = "coupon_name")
private String couponName;
/**
* 代金券面额
*/
@XStreamAlias("coupon_value")
@JSONField(name = "coupon_value")
private int couponValue;
/**
* 代金券使用最低限额
*/
@XStreamAlias("coupon_mininumn")
@JSONField(name = "coupon_mininumn")
private Integer couponMininumn;
/**
* 代金券类型1-代金券无门槛2-代金券有门槛互斥3-代金券有门槛叠加
*/
@XStreamAlias("coupon_type")
@JSONField(name = "coupon_type")
private int couponType;
/**
* 批次状态: 1-未激活2-审批中4-已激活8-已作废16-中止发放
*/
@XStreamAlias("coupon_stock_status")
@JSONField(name = "coupon_stock_status")
private int couponStockStatus;
/**
* 代金券数量
*/
@XStreamAlias("coupon_total")
@JSONField(name = "coupon_total")
private int couponTotal;
/**
* 代金券每个人最多能领取的数量, 如果为0则表示没有限制
*/
@XStreamAlias("max_quota")
@JSONField(name = "max_quota")
private Integer maxQuota;
/**
* 代金券锁定数量
*/
@XStreamAlias("locked_num")
@JSONField(name = "locked_num")
private Integer lockedNum;
/**
* 代金券已使用数量
*/
@XStreamAlias("used_num")
@JSONField(name = "used_num")
private Integer usedNum;
/**
* 代金券已经发送的数量
*/
@XStreamAlias("is_send_num")
@JSONField(name = "is_send_num")
private Integer sendNum;
/**
* 生效开始时间 格式为yyyyMMddhhmmss如2009年12月27日9点10分10秒表示为20091227091010
*/
@XStreamAlias("begin_time")
@JSONField(name = "begin_time")
private String beginTime;
/**
* 生效结束时间 格式为yyyyMMddhhmmss如2009年12月27日9点10分10秒表示为20091227091010
*/
@XStreamAlias("end_time")
@JSONField(name = "end_time")
private String endTime;
/**
* 创建时间 格式为yyyyMMddhhmmss如2009年12月27日9点10分10秒表示为20091227091010
*/
@XStreamAlias("create_time")
@JSONField(name = "create_time")
private String createTime;
/**
* 代金券预算额度
*/
@XStreamAlias("coupon_budget")
@JSONField(name = "coupon_budget")
private Integer couponBudget;
public String getCouponStockId() {
return couponStockId;
}
public String getCouponName() {
return couponName;
}
public int getCouponValue() {
return couponValue;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponValue() {
return couponValue / 100d;
}
public Integer getCouponMininumn() {
return couponMininumn;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponMininumn() {
return couponMininumn != null ? couponMininumn.intValue() / 100d : 0d;
}
public int getCouponType() {
return couponType;
}
@JSONField(deserialize = false, serialize = false)
public CouponType getFormatCouponType() {
for (CouponType couponType : CouponType.values()) {
if (couponType.getVal() == this.couponType) {
return couponType;
}
}
return null;
}
public int getCouponStockStatus() {
return couponStockStatus;
}
@JSONField(deserialize = false, serialize = false)
public CouponStockStatus getFormatCouponStockStatus() {
for (CouponStockStatus couponStockStatus : CouponStockStatus.values()) {
if (couponStockStatus.getVal() == this.couponStockStatus) {
return couponStockStatus;
}
}
return null;
}
public int getCouponTotal() {
return couponTotal;
}
public Integer getMaxQuota() {
return maxQuota;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatMaxQuota() {
return maxQuota != null ? maxQuota.intValue() / 100d : 0d;
}
public Integer getLockedNum() {
return lockedNum;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatLockedNum() {
return lockedNum != null ? lockedNum.intValue() / 100d : 0d;
}
public Integer getUsedNum() {
return usedNum;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatUsedNum() {
return usedNum != null ? usedNum.intValue() / 100d : 0d;
}
public Integer getSendNum() {
return sendNum;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatSendNum() {
return sendNum != null ? sendNum.intValue() / 100d : 0d;
}
public String getBeginTime() {
return beginTime;
}
@JSONField(deserialize = false, serialize = false)
public Date getFormatBeginTime() {
return DateUtil.parse2yyyyMMddHHmmss(beginTime);
}
public String getEndTime() {
return endTime;
}
@JSONField(deserialize = false, serialize = false)
public Date getFormatEndTime() {
return DateUtil.parse2yyyyMMddHHmmss(endTime);
}
public String getCreateTime() {
return createTime;
}
@JSONField(deserialize = false, serialize = false)
public Date getFormatCreateTime() {
return DateUtil.parse2yyyyMMddHHmmss(createTime);
}
public Integer getCouponBudget() {
return couponBudget;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponBudget() {
return couponBudget != null ? couponBudget.intValue() / 100d : 0d;
}
@Override
public String toString() {
return "CouponDetail [couponStockId=" + couponStockId + ", couponName="
+ couponName + ", couponValue=" + getFormatCouponValue()
+ ", couponMininumn=" + getFormatCouponMininumn()
+ ", couponType=" + getFormatCouponType()
+ ", couponStockStatus=" + getFormatCouponStockStatus()
+ ", couponTotal=" + couponTotal + ", maxQuota="
+ getFormatMaxQuota() + ", lockedNum=" + getFormatLockedNum()
+ ", usedNum=" + getFormatUsedNum() + ", sendNum="
+ getFormatSendNum() + ", beginTime=" + beginTime
+ ", endTime=" + endTime + ", createTime=" + createTime
+ ", couponBudget=" + getFormatCouponBudget() + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,124 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import java.io.Serializable;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.SignType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 调用V2.x接口返回的公用字段
*
* @className ApiResult
* @author jy
* @date 2014年12月30日
* @since JDK 1.7
* @see
*/
public class ApiResult implements Serializable {
private static final long serialVersionUID = -2876899595643466203L;
/**
* 是查询结果状态码,0 表明成功,其他表明错误;
*/
@JSONField(name = "ret_code")
@XStreamAlias("retcode")
private int retCode;
/**
* 是查询结果出错信息;
*/
@JSONField(name = "ret_msg")
@XStreamAlias("retmsg")
private String retMsg;
/**
* 是返回信息中的编码方式;
*/
@JSONField(name = "input_charset")
@XStreamAlias("input_charset")
private String inputCharset;
/**
* 是财付通商户号,即前文的 partnerid;
*/
private String partner;
/**
* 多密钥支持的密钥序号,默认 1
*/
@XStreamAlias("sign_key_index")
@JSONField(name = "sign_key_index")
private Integer signKeyIndex;
/**
* 签名 <font color="red">调用者无需关注</font>
*/
private String sign;
/**
* 签名类型,取值:MD5RSA
*/
@JSONField(name = "sign_type")
@XStreamAlias("sign_type")
private SignType signType;
public int getRetCode() {
return retCode;
}
public void setRetCode(int retCode) {
this.retCode = retCode;
}
public String getRetMsg() {
return StringUtils.isNotBlank(retMsg) ? retMsg : null;
}
public void setRetMsg(String retMsg) {
this.retMsg = retMsg;
}
public String getInputCharset() {
return inputCharset;
}
public void setInputCharset(String inputCharset) {
this.inputCharset = inputCharset;
}
public String getPartner() {
return partner;
}
public void setPartner(String partner) {
this.partner = partner;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public SignType getSignType() {
return signType;
}
public void setSignType(SignType signType) {
this.signType = signType;
}
public Integer getSignKeyIndex() {
return signKeyIndex;
}
public void setSignKeyIndex(Integer signKeyIndex) {
this.signKeyIndex = signKeyIndex;
}
@Override
public String toString() {
return "retCode=" + retCode + ", retMsg=" + retMsg + ", inputCharset="
+ inputCharset + ", partner=" + partner + ", sign=" + sign
+ ", signType=" + signType + ", signKeyIndex=" + signKeyIndex;
}
}

View File

@ -0,0 +1,64 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import java.beans.Transient;
import org.apache.commons.codec.digest.DigestUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayRequest;
import com.foxinmy.weixin4j.util.MapUtil;
/**
* V2微信JS支付:get_brand_wcpay_request</br>
* <font color="red">所列参数均为非空字符串</font>
* <p>
* get_brand_wcpay_request:ok 支付成功<br>
* get_brand_wcpay_request:cancel 支付过程中用户取消<br>
* get_brand_wcpay_request:fail 支付失败
* </p>
*
* @className JsPayRequestV2
* @author jy
* @date 2014年8月17日
* @since JDK 1.7
* @see
*/
public class JsPayRequestV2 extends PayRequest {
private static final long serialVersionUID = -5972173459255255197L;
public JsPayRequestV2(WeixinMpAccount weixinAccount, PayPackageV2 payPackage) {
this.setAppId(weixinAccount.getId());
this.setPackageInfo(package2string(payPackage,
weixinAccount.getPartnerKey()));
}
@Transient
@JSONField(serialize = false)
private String package2string(PayPackageV2 payPackage, String partnerKey) {
StringBuilder sb = new StringBuilder();
// a.对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) ,
// 使用 URL 键值 对的格式( key1=value1&key2=value2...)拼接成字符串 string1
// 注意:值为空的参数不参与签名
sb.append(MapUtil.toJoinString(payPackage, false, false, null));
// b--->
// string1 最后拼接上 key=paternerKey 得到 stringSignTemp 字符串,
// stringSignTemp 进行 md5 运算
// 再将得到的 字符串所有字符转换为大写 ,得到 sign signValue
sb.append("&key=").append(partnerKey);
// c---> & d---->
String sign = DigestUtils.md5Hex(sb.toString()).toUpperCase();
sb.delete(0, sb.length());
// c.对传入参数中所有键值对的 value 进行 urlencode 转码后重新拼接成字符串 string2
sb.append(MapUtil.toJoinString(payPackage, true, false, null))
.append("&sign=").append(sign);
return sb.toString();
}
@Override
public String toString() {
return "JsPayRequestV2 [" + super.toString() + "]";
}
}

View File

@ -0,0 +1,38 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import com.foxinmy.weixin4j.mp.payment.JsPayNotify;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2 Native支付回调时POST的信息
*
* @className PayNativeNotifyV2
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
public class NativePayNotifyV2 extends JsPayNotify {
private static final long serialVersionUID = 1868431159301749988L;
/**
* 产品ID 可视为订单ID
*/
@XStreamAlias("ProductId")
private String productId;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
@Override
public String toString() {
return "NativePayNotifyV2 [productId=" + productId + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,58 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import com.foxinmy.weixin4j.mp.model.WeixinMpAccount;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2 Native支付时的回调响应
*
* @className NativePayResponseV2
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class NativePayResponseV2 extends JsPayRequestV2 {
private static final long serialVersionUID = 6119895998783333012L;
/**
* 返回码
*/
@XStreamAlias("RetCode")
private String retCode;
/**
* 返回消息
*/
@XStreamAlias("RetErrMsg")
private String retMsg;
public NativePayResponseV2(WeixinMpAccount weixinAccount,
PayPackageV2 payPackage) {
super(weixinAccount, payPackage);
this.retCode = "0";
this.retMsg = "OK";
}
public String getRetCode() {
return retCode;
}
public void setRetCode(String retCode) {
this.retCode = retCode;
}
public String getRetMsg() {
return retMsg;
}
public void setRetMsg(String retMsg) {
this.retMsg = retMsg;
}
@Override
public String toString() {
return "NativePayResponseV2 [retCode=" + retCode + ", retMsg=" + retMsg
+ ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,319 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.TradeState;
import com.foxinmy.weixin4j.util.DateUtil;
/**
* V2订单信息
*
* @className Order
* @author jy
* @date 2014年11月2日
* @since JDK 1.7
* @see
*/
public class Order extends ApiResult {
private static final long serialVersionUID = 4543552984506609920L;
/**
* 是订单状态,0 为成功,其他为失败;
*/
@JSONField(name = "trade_state")
private int tradeState;
/**
* 是交易模式,1 为即时到帐,其他保留;
*/
@JSONField(name = "trade_mode")
private int tradeMode;
/**
* 是银行类型;
*/
@JSONField(name = "bank_type")
private String bankType;
/**
* 是银行订单号;
*/
@JSONField(name = "bank_billno")
private String bankBillno;
/**
* 是总金额,单位为分;
*/
@JSONField(name = "total_fee")
private int totalFee;
/**
* 是币种,1 为人民币;
*/
@JSONField(name = "fee_type")
private int feeType;
/**
* 是财付通订单号;
*/
@JSONField(name = "transaction_id")
private String transactionId;
/**
* 是第三方订单号;
*/
@JSONField(name = "out_trade_no")
private String outTradeNo;
/**
* 表明是否分账,false 为无分账,true 为有分账;
*/
@JSONField(name = "is_split")
private boolean isSplit;
/**
* 表明是否退款,false 为无退款,ture 为退款;
*/
@JSONField(name = "is_refund")
private boolean isRefund;
/**
* attach 是商户数据包,即生成订单package 时商户填入的 attach;
*/
private String attach;
/**
* 支付完成时间;
*/
@JSONField(name = "time_end")
private String timeEnd;
/**
* 物流费用,单位为分;
*/
@JSONField(name = "transport_fee")
private int transportFee;
/**
* 物品费用,单位为分;
*/
@JSONField(name = "product_fee")
private int productFee;
/**
* 折扣价格,单位为分;
*/
private int discount;
/**
* 换算成人民币之后的总金额,单位为分,一般看 total_fee 即可
*/
@JSONField(name = "rmb_total_fee")
private Integer rmbTotalFee;
public int getTradeState() {
return tradeState;
}
@JSONField(serialize = false, deserialize = false)
public TradeState getFormatTradeState() {
if (tradeState == 0) {
return TradeState.SUCCESS;
}
return null;
}
public void setTradeState(int tradeState) {
this.tradeState = tradeState;
}
public int getTradeMode() {
return tradeMode;
}
public void setTradeMode(int tradeMode) {
this.tradeMode = tradeMode;
}
public String getBankType() {
return bankType;
}
public void setBankType(String bankType) {
this.bankType = bankType;
}
public String getBankBillno() {
return bankBillno;
}
public void setBankBillno(String bankBillno) {
this.bankBillno = bankBillno;
}
public int getTotalFee() {
return totalFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatTotalFee() {
return totalFee / 100d;
}
public void setTotalFee(int totalFee) {
this.totalFee = totalFee;
}
public int getFeeType() {
return feeType;
}
@JSONField(serialize = false, deserialize = false)
public CurrencyType getFormatFeeType() {
if (feeType == 1) {
return CurrencyType.CNY;
}
return null;
}
public void setFeeType(int feeType) {
this.feeType = feeType;
}
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public boolean isSplit() {
return isSplit;
}
public void setSplit(boolean isSplit) {
this.isSplit = isSplit;
}
public boolean isRefund() {
return isRefund;
}
public void setRefund(boolean isRefund) {
this.isRefund = isRefund;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getTimeEnd() {
return timeEnd;
}
@JSONField(serialize = false, deserialize = false)
public Date getFormatTimeEnd() {
return DateUtil.parse2yyyyMMddHHmmss(timeEnd);
}
public void setTimeEnd(String timeEnd) {
this.timeEnd = timeEnd;
}
public int getTransportFee() {
return transportFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatTransportFee() {
return transportFee / 100d;
}
public void setTransportFee(int transportFee) {
this.transportFee = transportFee;
}
public int getProductFee() {
return productFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatProductFee() {
return productFee / 100d;
}
public void setProductFee(int productFee) {
this.productFee = productFee;
}
public int getDiscount() {
return discount;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatDiscount() {
return discount / 100d;
}
public void setDiscount(int discount) {
this.discount = discount;
}
public Integer getRmbTotalFee() {
return rmbTotalFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatRmbTotalFee() {
return rmbTotalFee != null ? rmbTotalFee / 100d : 0d;
}
public void setRmbTotalFee(int rmbTotalFee) {
this.rmbTotalFee = rmbTotalFee;
}
@Override
public String toString() {
return "Order [tradeState=" + tradeState + ", tradeMode=" + tradeMode
+ ", bankType=" + bankType + ", bankBillno=" + bankBillno
+ ", totalFee=" + totalFee + ", feeType=" + feeType
+ ", transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", isSplit=" + isSplit + ", isRefund="
+ isRefund + ", attach=" + attach + ", timeEnd=" + timeEnd
+ ", transportFee=" + transportFee + ", productFee="
+ productFee + ", discount=" + discount + ", rmbTotalFee="
+ rmbTotalFee + ", tradeState=" + getFormatTradeState()
+ ", totalFee=" + getFormatTotalFee() + ", feeType="
+ getFormatFeeType() + ", timeEnd=" + getFormatTimeEnd()
+ ", transportFee=" + getFormatTransportFee() + ", productFee="
+ getFormatProductFee() + ", discount=" + getFormatDiscount()
+ ", rmbTotalFee=" + getFormatRmbTotalFee() + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,100 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import com.foxinmy.weixin4j.mp.payment.PayBaseInfo;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2维权POST的数据
*
* @className PayFeedback
* @author jy
* @date 2014年10月29日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class PayFeedback extends PayBaseInfo {
private static final long serialVersionUID = 7230049346213966310L;
/**
* 投诉单号
*/
@XStreamAlias("FeedBackId")
private String feedbackId;
/**
* 用户ID
*/
@XStreamAlias("OpenId")
private String openId;
/**
* 订单交易单号
*/
@XStreamAlias("TransId")
private String transId;
/**
* 投诉原因
*/
@XStreamAlias("Reason")
private String reason;
/**
* 用户希望解决方案
*/
@XStreamAlias("Solution")
private String solution;
/**
* 备注信息+电话
*/
@XStreamAlias("ExtInfo")
private String extInfo;
/**
* 用户上传的图片凭证,最多五张
*/
@XStreamAlias("PicInfo")
private String picInfo;
/**
* 通知类型 request 用户提交投诉 confirm 用户确认消除 投诉 reject 用户拒绝消除投诉
*/
@XStreamAlias("MsgType")
private String status;
public String getFeedbackId() {
return feedbackId;
}
public String getOpenId() {
return openId;
}
public String getTransId() {
return transId;
}
public String getReason() {
return reason;
}
public String getSolution() {
return solution;
}
public String getExtInfo() {
return extInfo;
}
public String getPicInfo() {
return picInfo;
}
public String getStatus() {
return status;
}
@Override
public String toString() {
return "PayFeedback [feedbackId=" + feedbackId + ", openId=" + openId
+ ", transId=" + transId + ", reason=" + reason + ", solution="
+ solution + ", extInfo=" + extInfo + ", picInfo=" + picInfo
+ ", status=" + status + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,146 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.PayPackage;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2支付的订单详情
*
* @className PayPackageV2
* @author jy
* @date 2014年8月17日
* @since JDK 1.7
* @see
*/
public class PayPackageV2 extends PayPackage {
private static final long serialVersionUID = 5557542103637795834L;
/**
* 银行通道类型 固定为"WX" 非空
*/
@XStreamAlias("bank_type")
@JSONField(name = "bank_type")
private String bankType;
/**
* 商户号 注册时分配的财付通商户号 非空
*/
private String partner;
/**
* 支付币种 默认值是"1" 非空
*/
@XStreamAlias("fee_type")
@JSONField(name = "fee_type")
private String feeType;
/**
* 物流费用 可为空 如果有值,必须保 transport_fee + product_fee=total_fee
*/
@XStreamAlias("transport_fee")
@JSONField(name = "transport_fee")
private String transportFee;
/**
* 商品费用 可为空 商品费用,单位为分如果有值,必须保 transport_fee +product_fee=total_fee;
*/
@XStreamAlias("product_fee")
@JSONField(name = "product_fee")
private String productFee;
/**
* 传入参数字符编码 取值范围:"GBK""UTF-8",默认:"GBK" 可为空
*/
@XStreamAlias("input_charset")
@JSONField(name = "input_charset")
private String inputCharset;
public PayPackageV2(String outTradeNo, double totalFee,
String spbillCreateIp) {
this(null, null, null, outTradeNo, totalFee, null, spbillCreateIp,
null, null, 0d, 0d, null);
}
public PayPackageV2(String body, String outTradeNo, double totalFee,
String notifyUrl, String spbillCreateIp) {
this(body, null, null, outTradeNo, totalFee, notifyUrl, spbillCreateIp,
null, null, 0d, 0d, null);
}
public PayPackageV2(String body, String partner, String outTradeNo,
double totalFee, String notifyUrl, String spbillCreateIp) {
this(body, null, partner, outTradeNo, totalFee, notifyUrl,
spbillCreateIp, null, null, 0d, 0d, null);
}
public PayPackageV2(String body, String attach, String partner,
String outTradeNo, double totalFee, String notifyUrl,
String spbillCreateIp, Date timeStart, Date timeExpire,
double transportFee, double productFee, String goodsTag) {
super(body, attach, outTradeNo, totalFee, spbillCreateIp, timeStart,
timeExpire, goodsTag, notifyUrl);
this.bankType = "WX";
this.feeType = "1";
this.inputCharset = "UTF-8";
this.transportFee = transportFee > 0d ? DateUtil
.formaFee2Fen(transportFee) : null;
this.productFee = productFee > 0 ? DateUtil.formaFee2Fen(productFee)
: null;
}
public String getBankType() {
return bankType;
}
public String getPartner() {
return partner;
}
public void setPartner(String partner) {
this.partner = partner;
}
public String getFeeType() {
return feeType;
}
public String getTransportFee() {
return transportFee;
}
/**
* <font color="red">单位为元,自动格式化为分</font>
*
* @param transportFee
* 物流费用 单位为元
*/
public void setTransportFee(double transportFee) {
this.transportFee = DateUtil.formaFee2Fen(transportFee);
}
public String getProductFee() {
return productFee;
}
/**
* <font color="red">单位为元,自动格式化为分</font>
*
* @param productFee
* 商品 单位为元
*/
public void setProductFee(double productFee) {
this.productFee = DateUtil.formaFee2Fen(productFee);
}
public String getInputCharset() {
return inputCharset;
}
@Override
public String toString() {
return "PayPackageV2 [bankType=" + bankType + ", partner=" + partner
+ ", feeType=" + feeType + ", transportFee=" + transportFee
+ ", productFee=" + productFee + ", inputCharset="
+ inputCharset + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,54 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import com.foxinmy.weixin4j.mp.payment.PayBaseInfo;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2告警通知
*
* @className PayWarn
* @author jy
* @date 2014年12月31日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class PayWarn extends PayBaseInfo {
private static final long serialVersionUID = 2334592957844332640L;
/**
* 错误代号 1001=发货超时
*/
@XStreamAlias("ErrorType")
private String errortype;
/**
* 错误描述
*/
@XStreamAlias("Description")
private String description;
/**
* 错误详情
*/
@XStreamAlias("AlarmContent")
private String alarmcontent;
public String getErrortype() {
return errortype;
}
public String getDescription() {
return description;
}
public String getAlarmcontent() {
return alarmcontent;
}
@Override
public String toString() {
return "PayWarn [errortype=" + errortype + ", description="
+ description + ", alarmcontent=" + alarmcontent + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,144 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.RefundChannel;
import com.foxinmy.weixin4j.mp.type.RefundStatus;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2退款详细
*
* @className RefundDetail
* @author jy
* @date 2014年11月6日
* @since JDK 1.7
* @see
*/
public class RefundDetail extends ApiResult {
private static final long serialVersionUID = -3687863914168618620L;
/**
* 商户退款单号
*/
@XStreamAlias("out_refund_no")
@JSONField(name = "out_refund_no")
private String outRefundNo;
/**
* 微信退款单号
*/
@XStreamAlias("refund_id")
@JSONField(name = "refund_id")
private String refundId;
/**
* 退款渠道 0:退到财付通1:退到银行;
*/
@XStreamAlias("refund_channel")
@JSONField(name = "refund_channel")
private int refundChannel;
/**
* 退款总金额,单位为分,可以做部分退款
*/
@XStreamAlias("refund_fee")
@JSONField(name = "refund_fee")
private int refundFee;
/**
* 退款状态
*/
@XStreamAlias("refund_status")
@JSONField(name = "refund_status")
private int refundStatus;
/**
* 转账退款接收退款的财付通帐号
*/
@XStreamAlias("recv_user_id")
@JSONField(name = "recv_user_id")
private String recvUserId;
/**
* 转账退款接收退款的姓名(需与接收退款的财付通帐号绑定的姓名一致)
*/
@XStreamAlias("reccv_user_name")
@JSONField(name = "reccv_user_name")
private String reccvUserName;
public String getOutRefundNo() {
return outRefundNo;
}
public String getRefundId() {
return refundId;
}
public int getRefundChannel() {
return refundChannel;
}
@JSONField(deserialize = false, serialize = false)
public RefundChannel getFormatRefundChannel() {
if (refundChannel == 0) {
return RefundChannel.TENPAY;
} else if (refundChannel == 1) {
return RefundChannel.BANK;
} else {
return null;
}
}
public int getRefundFee() {
return refundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatRefundFee() {
return refundFee / 100d;
}
public int getRefundStatus() {
return refundStatus;
}
@JSONField(deserialize = false, serialize = false)
public RefundStatus getFormatRefundStatus() {
String refundStatus_ = String.format(",%d,", refundStatus);
if (",4,10,".contains(refundStatus_)) {
return RefundStatus.SUCCESS;
} else if (",3,5,6,".contains(refundStatus_)) {
return RefundStatus.FAIL;
} else if (",8,9,11,".contains(refundStatus_)) {
return RefundStatus.PROCESSING;
} else if (",1,2,".contains(refundStatus_)) {
return RefundStatus.NOTSURE;
} else if (",7,".contains(refundStatus_)) {
return RefundStatus.CHANGE;
} else {
return null;
}
}
public String getRecvUserId() {
return StringUtils.isNotBlank(recvUserId) ? recvUserId : null;
}
public String getReccvUserName() {
return StringUtils.isNotBlank(reccvUserName) ? reccvUserName : null;
}
@Override
public String toString() {
return "outRefundNo=" + outRefundNo + ", refundId=" + refundId
+ ", refundChannel=" + refundChannel + ", refundFee="
+ refundFee + ", refundStatus=" + refundStatus
+ ", recvUserId=" + recvUserId + ", reccvUserName="
+ reccvUserName + ", refundChannel="
+ getFormatRefundChannel() + ", refundFee="
+ getFormatRefundFee() + ", refundStatus="
+ getFormatRefundStatus();
}
}

View File

@ -0,0 +1,72 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* V2退款记录
*
* @className RefundRecord
* @author jy
* @date 2014年11月1日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v2.RefundDetail
*/
@XStreamAlias("xml")
public class RefundRecord extends ApiResult {
private static final long serialVersionUID = -2971132874939642721L;
/**
* 微信订单号
*/
@XStreamAlias("transaction_id")
@JSONField(name = "transaction_id")
private String transactionId;
/**
* 商户订单号
*/
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo;
/**
* 退款笔数
*/
@XStreamAlias("refund_count")
@JSONField(name = "refund_count")
private int count;
/**
* 退款详情
*/
@XStreamOmitField
@JSONField(serialize = false, deserialize = false)
private List<RefundDetail> details;
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return StringUtils.isNotBlank(outTradeNo) ? outTradeNo : null;
}
public int getCount() {
return count;
}
public List<RefundDetail> getDetails() {
return details;
}
@Override
public String toString() {
return "RefundRecord [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", count=" + count + ", details=" + details
+ ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,46 @@
package com.foxinmy.weixin4j.mp.payment.v2;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2退款申请结果
*
* @className RefundResult
* @author jy
* @date 2014年11月6日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class RefundResult extends RefundDetail {
private static final long serialVersionUID = -3687863914168618620L;
/**
* 微信订单号
*/
@XStreamAlias("transaction_id")
@JSONField(name = "transaction_id")
private String transactionId;
/**
* 商户系统内部的订单号
*/
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo;
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
@Override
public String toString() {
return "RefundResult [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,137 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.http.XmlResult;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 调用V3.x接口返回的公用字段
*
* @className ApiResult
* @author jy
* @date 2014年10月21日
* @since JDK 1.7
* @see
*/
public class ApiResult extends XmlResult {
private static final long serialVersionUID = -8430005768959715444L;
/**
* 微信分配的公众账号 ID商户号 非空
*/
@XStreamAlias("appid")
@JSONField(name = "appid")
private String appId;
/**
* 微信支付分配的商户号 非空
*/
@XStreamAlias("mch_id")
@JSONField(name = "mch_id")
private String mchId;
/**
* 代理模式下分配的商户号 可能为空
*/
@XStreamAlias("sub_mch_id")
@JSONField(name = "sub_mch_id")
private String subMchId;
/**
* 随机字符串 非空
*/
@XStreamAlias("nonce_str")
@JSONField(name = "nonce_str")
private String nonceStr;
/**
* 签名 <font color="red">调用者无需关心</font>
*/
private String sign;
/**
* 微信支付分配的终端设备号 可能为空
*/
@XStreamAlias("device_info")
@JSONField(name = "device_info")
private String deviceInfo;
/**
* 是否需要继续调用接口 Y- 需要,N-不需要
*/
private String recall;
public ApiResult() {
}
public ApiResult(String returnCode, String returnMsg) {
super(returnCode, returnMsg);
}
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public String getMchId() {
return mchId;
}
public void setMchId(String mchId) {
this.mchId = mchId;
}
public String getSubMchId() {
return StringUtils.isNotBlank(subMchId) ? subMchId : null;
}
public void setSubMchId(String subMchId) {
this.subMchId = subMchId;
}
public String getNonceStr() {
return nonceStr;
}
public void setNonceStr(String nonceStr) {
this.nonceStr = nonceStr;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getDeviceInfo() {
return deviceInfo;
}
public void setDeviceInfo(String deviceInfo) {
this.deviceInfo = deviceInfo;
}
public String getRecall() {
return recall;
}
public String setRecall() {
return recall;
}
@JSONField(deserialize = false, serialize = false)
public boolean getFormatRecall() {
return recall != null && recall.equalsIgnoreCase("y");
}
@Override
public String toString() {
return "appId=" + appId + ", mchId=" + mchId + ", subMchId=" + subMchId
+ ", nonceStr=" + nonceStr + ", sign=" + sign + ", deviceInfo="
+ deviceInfo + ", recall=" + getFormatRecall() + ", "
+ super.toString();
}
}

View File

@ -0,0 +1,120 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.MPPaymentCheckNameType;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 企业付款
*
* @className MPPayment
* @author jy
* @date 2015年4月1日
* @since JDK 1.7
* @see
*/
public class MPPayment implements Serializable {
private static final long serialVersionUID = 3734639674346425312L;
/**
* 商户订单号
*/
@XStreamAlias("partner_trade_no")
@JSONField(name = "partner_trade_no")
private String outTradeNo;
/**
* 接收红包的用户的openid
*/
private String openid;
/**
* 校验用户姓名选项
*
* @see com.foxinmy.weixin4j.mp.type.MPPaymentCheckNameType
*/
@XStreamAlias("check_name")
@JSONField(name = "check_name")
private MPPaymentCheckNameType checkNameType;
/**
* 收款用户真实姓名 如果check_name设置为FORCE_CHECK或OPTION_CHECK则必填用户真实姓名 可选
*/
@XStreamAlias("re_user_name")
@JSONField(name = "re_user_name")
private String userName;
/**
* 企业付款描述信息
*/
private String desc;
/**
* 付款金额
*/
private String amount;
/**
* 调用接口的机器Ip地址
*/
@XStreamAlias("spbill_create_ip")
@JSONField(name = "spbill_create_ip")
private String clientIp;
/**
* 企业付款
* @param outTradeNo 商户的订单号
* @param openid 用户的openid
* @param checkNameType 校验用户姓名选项
* @param desc 描述
* @param amount 金额
* @param clientIp 调用接口IP
*/
public MPPayment(String outTradeNo, String openid,
MPPaymentCheckNameType checkNameType, String desc, double amount,
String clientIp) {
this.outTradeNo = outTradeNo;
this.openid = openid;
this.checkNameType = checkNameType;
this.desc = desc;
this.amount = DateUtil.formaFee2Fen(amount);
this.clientIp = clientIp;
}
public String getOutTradeNo() {
return outTradeNo;
}
public String getOpenid() {
return openid;
}
public MPPaymentCheckNameType getCheckNameType() {
return checkNameType;
}
public String getUserName() {
return userName;
}
public String getDesc() {
return desc;
}
public String getAmount() {
return amount;
}
public String getClientIp() {
return clientIp;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "MPPayment [outTradeNo=" + outTradeNo + ", openid=" + openid
+ ", checkNameType=" + checkNameType + ", userName=" + userName
+ ", desc=" + desc + ", amount=" + amount + ", clientIp="
+ clientIp + "]";
}
}

View File

@ -0,0 +1,56 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 企业付款结果
*
* @className MPPaymentResult
* @author jy
* @date 2015年4月1日
* @since JDK 1.7
* @see
*/
public class MPPaymentResult extends ApiResult {
private static final long serialVersionUID = 1110472826089211646L;
/**
* 微信订单订单号
*/
@JSONField(name = "payment_no")
@XStreamAlias("payment_no")
private String transactionId;
/**
* 商户订单号
*/
@JSONField(name = "partner_trade_no")
@XStreamAlias("partner_trade_no")
private String outTradeNo;
/**
* 支付时间
*/
@JSONField(name = "payment_time")
@XStreamAlias("payment_time")
private String paymentTime;
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
public String getPaymentTime() {
return paymentTime;
}
@Override
public String toString() {
return "MPPaymentResult [transactionId=" + transactionId
+ ", outTradeNo=" + outTradeNo + ", paymentTime=" + paymentTime
+ "]";
}
}

View File

@ -0,0 +1,38 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V3 Native支付回调时POST的信息
*
* @className PayNativeNotifyV3
* @author jy
* @date 2014年10月30日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class NativePayNotifyV3 extends ApiResult {
private static final long serialVersionUID = 4515471400239795492L;
/**
* 产品ID 可视为订单ID
*/
@XStreamAlias("product_id")
private String productId;
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
@Override
public String toString() {
return "NativePayNotifyV3 [productId=" + productId + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,79 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* V3 Native支付时的回调响应
*
* @className NativePayResponseV3
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class NativePayResponseV3 extends ApiResult {
private static final long serialVersionUID = 6119895998783333012L;
@XStreamOmitField
@JSONField(serialize = false)
private PrePay prePay;
private String prepay_id;
/**
* 一般作为校验失败时返回
*
* @param returnMsg
* 失败消息
* @param resultMsg
* 结果消息
* @throws PayException
*/
public NativePayResponseV3(String returnMsg, String resultMsg) {
super.setReturnMsg(returnMsg);
super.setReturnCode(Consts.FAIL);
super.setErrCodeDes(resultMsg);
super.setResultCode(Consts.FAIL);
}
/**
* 作为return_code SUCCESS 的时候返回
*
* @param payPackage
* 订单信息
* @throws PayException
*/
public NativePayResponseV3(PayPackageV3 payPackage, String paysignKey)
throws PayException {
super.setReturnCode(Consts.SUCCESS);
this.setResultCode(Consts.SUCCESS);
this.setMchId(payPackage.getMchId());
this.setAppId(payPackage.getAppid());
this.setNonceStr(RandomUtil.generateString(16));
this.prePay = PayUtil.createPrePay(payPackage, paysignKey);
this.prepay_id = prePay.getPrepayId();
}
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
@Override
public String toString() {
return "NativePayResponseV3 [prePay=" + prePay + ", prepay_id="
+ prepay_id + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,256 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.TradeState;
import com.foxinmy.weixin4j.mp.type.TradeType;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* V3订单信息
*
* @className Order
* @author jy
* @date 2014年11月2日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class Order extends ApiResult {
private static final long serialVersionUID = 5636828325595317079L;
/**
* 交易状态
*
* @see com.foxinmy.weixin4j.mp.type.TradeState
*/
@XStreamAlias("trade_state")
@JSONField(name = "trade_state")
private TradeState tradeState;
/**
* 用户的openid
*/
@XStreamAlias("openid")
@JSONField(name = "openid")
private String openId;
/**
* 用户是否关注公众账号,Y- 关注,N-未关注,仅在公众 账号类型支付有效
*/
@XStreamAlias("is_subscribe")
@JSONField(name = "is_subscribe")
private String isSubscribe;
/**
* 交易类型
*
* @see com.foxinmy.weixin4j.mp.type.TradeType
*/
@XStreamAlias("trade_type")
@JSONField(name = "trade_type")
private TradeType tradeType;
/**
* 银行类型
*/
@XStreamAlias("bank_type")
@JSONField(name = "bank_type")
private String bankType;
/**
* 订单总金额,单位为分
*/
@XStreamAlias("total_fee")
@JSONField(name = "total_fee")
private int totalFee;
/**
* 现金券支付金额<=订单总金 ,订单总金额-现金券金额 为现金支付金额
*/
@XStreamAlias("coupon_fee")
@JSONField(name = "coupon_fee")
private Integer couponFee;
/**
* 代金券或立减优惠使用数量
*/
@XStreamAlias("coupon_count")
@JSONField(name = "coupon_count")
private Integer couponCount;
/**
* 代金券信息
*/
@XStreamOmitField
@JSONField(serialize = false)
private List<CouponInfo> couponList;
/**
* 现金支付金额
*/
@XStreamAlias("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee;
/**
* 货币类型,符合 ISO 4217 标准的三位字母代码,默认人民币:CNY
*
* @see com.foxinmy.weixin4j.mp.type.CurrencyType
*/
@XStreamAlias("fee_type")
@JSONField(name = "fee_type")
private CurrencyType feeType;
/**
* 微信支付订单号
*/
@XStreamAlias("transaction_id")
@JSONField(name = "transaction_id")
private String transactionId;
/**
* 商户订单号
*/
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo;
/**
* 商家数据包
*/
private String attach;
/**
* 支付完成时间,格式为 yyyyMMddhhmmss
*/
@XStreamAlias("time_end")
@JSONField(name = "time_end")
private String timeEnd;
/**
* 交易状态描述
*/
@XStreamAlias("trade_state_desc")
@JSONField(name = "trade_state_desc")
private String tradeStateDesc;
public TradeState getTradeState() {
return tradeState;
}
public String getOpenId() {
return openId;
}
public String getIsSubscribe() {
return isSubscribe;
}
@JSONField(serialize = false, deserialize = false)
public boolean getFormatIsSubscribe() {
return isSubscribe != null && isSubscribe.equalsIgnoreCase("y");
}
public TradeType getTradeType() {
return tradeType;
}
public String getBankType() {
return bankType;
}
public int getTotalFee() {
return totalFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatTotalFee() {
return totalFee / 100d;
}
public Integer getCouponFee() {
return couponFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCouponFee() {
return couponFee != null ? couponFee / 100d : 0d;
}
public Integer getCouponCount() {
return couponCount;
}
@JSONField(serialize = false, deserialize = false)
public int getFormatCouponCount() {
return couponCount != null ? couponCount.intValue() : 0;
}
public int getCashFee() {
return cashFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCashFee() {
return cashFee / 100d;
}
public CurrencyType getFeeType() {
return feeType;
}
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
public String getAttach() {
return attach;
}
public String getTimeEnd() {
return timeEnd;
}
@JSONField(serialize = false, deserialize = false)
public Date getFormatTimeEnd() {
return DateUtil.parse2yyyyMMddHHmmss(timeEnd);
}
public String getTradeStateDesc() {
return tradeStateDesc;
}
public List<CouponInfo> getCouponList() {
return couponList;
}
public void setCouponList(List<CouponInfo> couponList) {
this.couponList = couponList;
}
@Override
public String toString() {
return "Order [tradeState=" + tradeState + ", openId=" + openId
+ ", isSubscribe=" + getFormatIsSubscribe() + ", tradeType="
+ tradeType + ", bankType=" + bankType + ", feeType=" + feeType
+ ", transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", attach=" + attach + ", timeEnd=" + timeEnd
+ ", totalFee=" + getFormatTotalFee() + ", couponFee="
+ getFormatCouponFee() + ", couponCount="
+ getFormatCouponCount() + ", couponList=" + couponList
+ ", cashFee=" + getFormatCashFee() + ", timeEnd="
+ getFormatTimeEnd() + ", tradeStateDesc=" + tradeStateDesc
+ ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,149 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.Date;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.PayPackage;
import com.foxinmy.weixin4j.mp.type.TradeType;
import com.foxinmy.weixin4j.util.RandomUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V3支付的订单详情
*
* @className PayPackageV3
* @author jy
* @date 2014年10月21日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class PayPackageV3 extends PayPackage {
private static final long serialVersionUID = 8944928173669656177L;
/**
* 微信分配的公众账号 必须
*/
private String appid;
/**
* 微信支付分配的商户号 必须
*/
@XStreamAlias("mch_id")
@JSONField(name = "mch_id")
private String mchId;
/**
* 微信支付分配的终端设备号 非必须
*/
@XStreamAlias("device_info")
@JSONField(name = "device_info")
private String deviceInfo;
/**
* 随机字符串,不长于 32 必须
*/
@XStreamAlias("nonce_str")
@JSONField(name = "nonce_str")
private String nonceStr;
/**
* 签名 <font color="red">调用者无需关心</font>
*/
private String sign;
/**
* 交易类型JSAPINATIVEAPP 必须
*/
@XStreamAlias("trade_type")
@JSONField(name = "trade_type")
private String tradeType;
/**
* 用户在商户 appid 下的唯一 标识, trade_type JSAPI ,此参数必传
*/
private String openid;
/**
* 只在 trade_type NATIVE 时需要填写 非必须
*/
@XStreamAlias("product_id")
@JSONField(name = "product_id")
private String productId;
public PayPackageV3(WeixinMpAccount weixinAccount, String openId,
String body, String outTradeNo, double totalFee,
String spbillCreateIp, TradeType tradeType) {
this(weixinAccount, openId, body, null, outTradeNo, totalFee, null,
spbillCreateIp, tradeType);
}
public PayPackageV3(WeixinMpAccount weixinAccount, String openId,
String body, String attach, String outTradeNo, double totalFee,
String notifyUrl, String spbillCreateIp, TradeType tradeType) {
this(weixinAccount.getId(), weixinAccount.getMchId(), weixinAccount
.getDeviceInfo(), RandomUtil.generateString(16), body, attach,
outTradeNo, totalFee, spbillCreateIp, null, null, null,
notifyUrl, tradeType, openId, null);
}
public PayPackageV3(String appid, String mchId, String deviceInfo,
String nonceStr, String body, String attach, String outTradeNo,
double totalFee, String spbillCreateIp, Date timeStart,
Date timeExpire, String goodsTag, String notifyUrl,
TradeType tradeType, String openid, String productId) {
super(body, attach, outTradeNo, totalFee, spbillCreateIp, timeStart,
timeExpire, goodsTag, notifyUrl);
this.appid = appid;
this.mchId = mchId;
this.deviceInfo = deviceInfo;
this.nonceStr = nonceStr;
this.tradeType = tradeType.name();
this.openid = openid;
this.productId = productId;
}
public String getAppid() {
return appid;
}
public String getMchId() {
return mchId;
}
public String getDeviceInfo() {
return deviceInfo;
}
public String getNonceStr() {
return nonceStr;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
public String getTradeType() {
return tradeType;
}
public String getOpenid() {
return openid;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
@Override
public String toString() {
return "PayPackageV3 [appid=" + appid + ", mchId=" + mchId
+ ", deviceInfo=" + deviceInfo + ", nonceStr=" + nonceStr
+ ", sign=" + sign + ", tradeType=" + tradeType + ", openid="
+ openid + ", productId=" + productId + ", " + super.toString()
+ "]";
}
}

View File

@ -0,0 +1,36 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.mp.payment.PayRequest;
/**
* V3 JS支付:get_brand_wcpay_request</br>
* <p>
* get_brand_wcpay_request:ok 支付成功<br>
* get_brand_wcpay_request:cancel 支付过程中用户取消<br>
* get_brand_wcpay_request:fail 支付失败
* </p>
* <p>
* NATIVE支付:PayRequest.TradeType=NATIVE
* </p>
*
* @className PayRequestV3
* @author jy
* @date 2014年8月17日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v3.PrePay
*/
public class PayRequestV3 extends PayRequest {
private static final long serialVersionUID = -5972173459255255197L;
public PayRequestV3(PrePay prePay) throws PayException {
this.setAppId(prePay.getAppId());
this.setPackageInfo("prepay_id=" + prePay.getPrepayId());
}
@Override
public String toString() {
return "JsPayRequestV3 [" + super.toString() + "]";
}
}

View File

@ -0,0 +1,75 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import com.foxinmy.weixin4j.mp.type.TradeType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V3预订单信息
*
* @className PrePay
* @author jy
* @date 2014年10月21日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class PrePay extends ApiResult {
private static final long serialVersionUID = -8430005768959715444L;
/**
* 调用接口提交的交易类型取值如下JSAPINATIVEAPP
*
* @see com.foxinmy.weixin4j.mp.type.TradeType
*/
@XStreamAlias("trade_type")
private TradeType tradeType;
/**
* 微信生成的预支付回话标识用于后续接口调用中使用该值有效期为2小时
*/
@XStreamAlias("prepay_id")
private String prepayId;
/**
* trade_type NATIVE 是有 返回,此参数可直接生成二 维码展示出来进行扫码支付 可能为空
*/
@XStreamAlias("code_url")
private String codeUrl;
public PrePay() {
}
public PrePay(String returnCode, String returnMsg) {
super(returnCode, returnMsg);
}
public TradeType getTradeType() {
return tradeType;
}
public void setTradeType(TradeType tradeType) {
this.tradeType = tradeType;
}
public String getPrepayId() {
return prepayId;
}
public void setPrepayId(String prepayId) {
this.prepayId = prepayId;
}
public String getCodeUrl() {
return codeUrl;
}
public void setCodeUrl(String codeUrl) {
this.codeUrl = codeUrl;
}
@Override
public String toString() {
return "PrePay [tradeType=" + tradeType + ", prepayId=" + prepayId
+ ", codeUrl=" + codeUrl + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,272 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.io.Serializable;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 红包
*
* @className Redpacket
* @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>
*/
public class Redpacket implements Serializable {
private static final long serialVersionUID = -7021352305575714281L;
/**
* 商户订单号每个订单号必须唯一 组成 mch_id+yyyymmdd+10位一天内不能重复的数字
*/
@XStreamAlias("mch_billno")
@JSONField(name = "mch_billno")
private String outTradeNo;
/**
* 提供方名称 必填
*/
@XStreamAlias("nick_name")
@JSONField(name = "nick_name")
private String nickName;
/**
* 红包发送者名称 必填
*/
@XStreamAlias("send_name")
@JSONField(name = "send_name")
private String sendName;
/**
* 接收红包的用户的openid
*/
@XStreamAlias("re_openid")
@JSONField(name = "re_openid")
private String openid;
/**
* 付款金额单位分
*/
@XStreamAlias("total_amount")
@JSONField(name = "total_amount")
private String totalAmount;
/**
* 最小红包金额单位分
*/
@XStreamAlias("min_value")
@JSONField(name = "min_value")
private String minValue;
/**
* 最大红包金额单位分 最小金额等于最大金额 min_value=max_value =total_amount
*/
@XStreamAlias("max_value")
@JSONField(name = "max_value")
private String maxValue;
/**
* 红包发放总人数
*/
@XStreamAlias("total_num")
@JSONField(name = "total_num")
private int totalNum;
/**
* 红包祝福语
*/
private String wishing;
/**
* ip地址
*/
@XStreamAlias("client_ip")
@JSONField(name = "client_ip")
private String clientIp;
/**
* 活动名称
*/
@XStreamAlias("act_name")
@JSONField(name = "act_name")
private String actName;
/**
* 备注
*/
private String remark;
/**
* 商户logo的url 非必填
*/
@XStreamAlias("logo_imgurl")
@JSONField(name = "logo_imgurl")
private String logoUrl;
/**
* 分享文案 非必填
*/
@XStreamAlias("share_content")
@JSONField(name = "share_content")
private String shareContent;
/**
* 分享链接 非必填
*/
@XStreamAlias("share_url")
@JSONField(name = "share_url")
private String shareUrl;
/**
* 分享的图片 非必填
*/
@XStreamAlias("share_imgurl")
@JSONField(name = "share_imgurl")
private String shareImageUrl;
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getSendName() {
return sendName;
}
public void setSendName(String sendName) {
this.sendName = sendName;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getTotalAmount() {
return totalAmount;
}
/**
* <font color="red">单位为元,自动格式化为分</font>
*
* @param totalAmount
* 付款金额 单位为元
*/
public void setTotalAmount(double totalAmount) {
this.totalAmount = DateUtil.formaFee2Fen(totalAmount);
}
public String getMinValue() {
return minValue;
}
/**
* <font color="red">单位为元,自动格式化为分</font>
*
* @param minValue
* 最小红包 单位为元
*/
public void setMinValue(double minValue) {
this.minValue = DateUtil.formaFee2Fen(minValue);
}
public String getMaxValue() {
return maxValue;
}
/**
* <font color="red">单位为元,自动格式化为分</font>
*
* @param minValue
* 最大红包 单位为元
*/
public void setMaxValue(double maxValue) {
this.maxValue = DateUtil.formaFee2Fen(maxValue);
}
public int getTotalNum() {
return totalNum;
}
public void setTotalNum(int totalNum) {
this.totalNum = totalNum;
}
public String getWishing() {
return wishing;
}
public void setWishing(String wishing) {
this.wishing = wishing;
}
public String getClientIp() {
return clientIp;
}
public void setClientIp(String clientIp) {
this.clientIp = clientIp;
}
public String getActName() {
return actName;
}
public void setActName(String actName) {
this.actName = actName;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getLogoUrl() {
return logoUrl;
}
public void setLogoUrl(String logoUrl) {
this.logoUrl = logoUrl;
}
public String getShareContent() {
return shareContent;
}
public void setShareContent(String shareContent) {
this.shareContent = shareContent;
}
public String getShareUrl() {
return shareUrl;
}
public void setShareUrl(String shareUrl) {
this.shareUrl = shareUrl;
}
public String getShareImageUrl() {
return shareImageUrl;
}
public void setShareImageUrl(String shareImageUrl) {
this.shareImageUrl = shareImageUrl;
}
@Override
public String toString() {
return "Redpacket [ nickName=" + nickName
+ ", sendName=" + sendName + ", openid=" + openid
+ ", totalAmount=" + totalAmount + ", minValue=" + minValue
+ ", maxValue=" + maxValue + ", totalNum=" + totalNum
+ ", wishing=" + wishing + ", clientIp=" + clientIp
+ ", actName=" + actName + ", remark=" + remark + ", logoUrl="
+ logoUrl + ", shareContent=" + shareContent + ", shareUrl="
+ shareUrl + ", shareImageUrl=" + shareImageUrl + "]";
}
}

View File

@ -0,0 +1,87 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.http.XmlResult;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 发送红包结果
*
* @className RedpacketSendResult
* @author jy
* @date 2015年4月1日
* @since JDK 1.7
* @see
*/
public class RedpacketSendResult extends XmlResult {
private static final long serialVersionUID = 5611847899634131711L;
/**
* 微信分配的公众账号
*/
@XStreamAlias("wxappid")
@JSONField(name = "wxappid")
private String appid;
/**
* 微信支付分配的商户号
*/
@XStreamAlias("mch_id")
@JSONField(name = "mch_id")
private String mchId;
/**
* 商户订单号每个订单号必须唯一 组成 mch_id+yyyymmdd+10位一天内不能重复的数字
*/
@XStreamAlias("mch_billno")
@JSONField(name = "mch_billno")
private String outTradeNo;
/**
* 接收红包的用户的openid
*/
@XStreamAlias("re_openid")
@JSONField(name = "re_openid")
private String openid;
/**
* 付款金额 单位为分
*/
@XStreamAlias("total_amount")
@JSONField(name = "total_amount")
private int totalAmount;
public String getAppid() {
return appid;
}
public String getMchId() {
return mchId;
}
public String getOutTradeNo() {
return outTradeNo;
}
public String getOpenid() {
return openid;
}
public int getTotalAmount() {
return totalAmount;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatTotalAmount() {
return totalAmount / 100d;
}
@Override
public String toString() {
return "RedpacketSendResult [appid=" + appid + ", mchId=" + mchId
+ ", outTradeNo=" + outTradeNo + ", openid=" + openid
+ ", totalAmount=" + totalAmount + ", " + super.toString()
+ "]";
}
}

View File

@ -0,0 +1,278 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.RefundChannel;
import com.foxinmy.weixin4j.mp.type.RefundStatus;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V3退款详细
*
* @className RefundDetail
* @author jy
* @date 2014年11月6日
* @since JDK 1.7
* @see
*/
public class RefundDetail extends ApiResult {
private static final long serialVersionUID = -3687863914168618620L;
/**
* 商户退款单号
*/
@XStreamAlias("out_refund_no")
@JSONField(name = "out_refund_no")
private String outRefundNo;
/**
* 微信退款单号
*/
@XStreamAlias("refund_id")
@JSONField(name = "refund_id")
private String refundId;
/**
* 退款渠道:ORIGINAL原路退款,默认 BALANCE退回到余额
*/
@XStreamAlias("refund_channel")
@JSONField(name = "refund_channel")
private String refundChannel;
/**
* 退款总金额,单位为分,可以做部分退款
*/
@XStreamAlias("refund_fee")
@JSONField(name = "refund_fee")
private int refundFee;
/**
* 退款货币种类
*
* @see com.foxinmy.weixin4j.mp.type.CurrencyType
*/
@XStreamAlias("refund_fee_type")
@JSONField(name = "refund_fee_type")
private CurrencyType refundFeeType;
/**
* 订单总金额
*/
@XStreamAlias("total_fee")
@JSONField(name = "total_fee")
private int totalFee;
/**
* 订单金额货币种类
*
* @see com.foxinmy.weixin4j.mp.type.CurrencyType
*/
@XStreamAlias("fee_type")
@JSONField(name = "fee_type")
private CurrencyType feeType;
/**
* 现金支付金额
*/
@XStreamAlias("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee;
/**
* 现金支付货币种类
*
* @see com.foxinmy.weixin4j.mp.type.CurrencyType
*/
@XStreamAlias("cash_fee_type")
@JSONField(name = "cash_fee_type")
private CurrencyType cashFeeType;
/**
* 现金退款金额
*/
@XStreamAlias("cash_refund_fee")
@JSONField(name = "cash_refund_fee")
private Integer cashRefundFee;
/**
* 现金退款货币类型
*
* @see com.foxinmy.weixin4j.mp.type.CurrencyType
*/
@XStreamAlias("cash_refund_fee_type")
@JSONField(name = "cash_refund_fee_type")
private CurrencyType cashRefundFeeType;
/**
* 退款状态
*/
@XStreamAlias("refund_status")
@JSONField(name = "refund_status")
private String refundStatus;
/**
* 现金券退款金额<=退款金额,退款金额-现金券退款金额为现金
*/
@XStreamAlias("coupon_refund_fee")
@JSONField(name = "coupon_refund_fee")
private Integer couponRefundFee;
/**
* 代金券或立减优惠使用数量 <font
* color="red">微信支付文档上写的coupon_count,而实际测试拿到的是coupon_refund_count,做个记号
* </font>
*/
@XStreamAlias("coupon_refund_count")
@JSONField(name = "coupon_refund_count")
private Integer couponRefundCount;
/**
* 代金券信息
*
* @see com.foxinmy.weixin4j.mp.payment.coupon.CouponInfo
*/
@JSONField(serialize = false)
private List<CouponInfo> couponList;
public String getOutRefundNo() {
return outRefundNo;
}
public String getRefundId() {
return refundId;
}
public String getRefundChannel() {
return refundChannel;
}
@JSONField(deserialize = false, serialize = false)
public RefundChannel getFormatRefundChannel() {
if (StringUtils.isNotBlank(refundChannel)) {
return RefundChannel.valueOf(refundChannel.toUpperCase());
}
return null;
}
public int getRefundFee() {
return refundFee;
}
public CurrencyType getFeeType() {
return feeType;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatRefundFee() {
return refundFee / 100d;
}
public String getRefundStatus() {
return refundStatus;
}
@JSONField(deserialize = false, serialize = false)
public RefundStatus getFormatRefundStatus() {
if (StringUtils.isNotBlank(refundStatus)) {
return RefundStatus.valueOf(refundStatus);
}
return null;
}
public Integer getCouponRefundFee() {
return couponRefundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponRefundFee() {
return couponRefundFee != null ? couponRefundFee.intValue() / 100d : 0d;
}
public CurrencyType getRefundFeeType() {
return refundFeeType;
}
public int getTotalFee() {
return totalFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatTotalFee() {
return totalFee / 100d;
}
public int getCashFee() {
return cashFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCashFee() {
return cashFee / 100d;
}
public CurrencyType getCashFeeType() {
return cashFeeType;
}
public Integer getCashRefundFee() {
return cashRefundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCashRefundFee() {
return cashRefundFee != null ? cashRefundFee.intValue() / 100d : 0d;
}
public CurrencyType getCashRefundFeeType() {
return cashRefundFeeType;
}
public Integer getCouponRefundCount() {
return couponRefundCount;
}
@JSONField(deserialize = false, serialize = false)
public int getFormatCouponRefundCount() {
return couponRefundCount != null ? couponRefundCount.intValue() : 0;
}
public List<CouponInfo> getCouponList() {
return couponList;
}
public void setCouponList(List<CouponInfo> couponList) {
this.couponList = couponList;
}
@Override
public String toString() {
return "RefundDetail [outRefundNo=" + outRefundNo + ", refundId="
+ refundId + ", refundChannel=" + refundChannel
+ ", refundFee=" + getFormatRefundFee() + ", refundFeeType="
+ refundFeeType + ", totalFee=" + getFormatTotalFee()
+ ", feeType=" + feeType + ", cashFee=" + getFormatCashFee()
+ ", cashFeeType=" + cashFeeType + ", cashRefundFee="
+ getFormatCashRefundFee() + ", cashRefundFeeType="
+ cashRefundFeeType + ", refundStatus=" + refundStatus
+ ", couponRefundFee=" + getFormatCouponRefundFee()
+ ", couponCount=" + getCouponRefundCount() + ", couponList="
+ couponList + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,183 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;
/**
* V3退款记录
*
* @className RefundRecord
* @author jy
* @date 2014年11月1日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail
*/
@XStreamAlias("xml")
public class RefundRecord extends ApiResult {
private static final long serialVersionUID = -2971132874939642721L;
/**
* 微信订单号
*/
@XStreamAlias("transaction_id")
@JSONField(name = "transaction_id")
private String transactionId;
/**
* 商户订单号
*/
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo;
/**
* 订单总金额
*/
@XStreamAlias("total_fee")
@JSONField(name = "total_fee")
private int totalFee;
/**
* 订单金额货币种类
*
* @see com.foxinmy.weixin4j.mp.type.CurrencyType
*/
@XStreamAlias("fee_type")
@JSONField(name = "fee_type")
private CurrencyType feeType;
/**
* 现金支付金额
*/
@XStreamAlias("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee;
/**
* 现金支付金额货币种类
*
* @see com.foxinmy.weixin4j.mp.type.CurrencyType
*/
@XStreamAlias("cash_fee_type")
@JSONField(name = "cash_fee_type")
private CurrencyType cashFeeType;
/**
* 退款总金额
*/
@XStreamAlias("refund_fee")
@JSONField(name = "refund_fee")
private int refundFee;
/**
* 代金券或立减优惠退款金额=订单金额-现金退款金额注意满立减金额不会退回
*/
@XStreamAlias("coupon_refund_fee")
@JSONField(name = "coupon_refund_fee")
private Integer couponRefundFee;
/**
* 退款笔数
*/
@XStreamAlias("refund_count")
@JSONField(name = "refund_count")
private int count;
/**
* 退款详情
*/
@XStreamOmitField
@JSONField(serialize = false, deserialize = false)
private List<RefundDetail> details;
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCashFee() {
return cashFee / 100d;
}
public int getCashFee() {
return cashFee;
}
public CurrencyType getFeeType() {
return feeType;
}
public CurrencyType getCashFeeType() {
return cashFeeType;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCouponRefundFee() {
return couponRefundFee != null ? couponRefundFee.intValue() / 100d : 0d;
}
public Integer getCouponRefundFee() {
return couponRefundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatTotalFee() {
return totalFee / 100d;
}
public int getTotalFee() {
return totalFee;
}
public int getCount() {
return count;
}
public List<RefundDetail> getDetails() {
return details;
}
public void setDetails(List<RefundDetail> details) {
this.details = details;
}
public int getRefundFee() {
return refundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatRefundFee() {
return refundFee / 100d;
}
@Override
public String toString() {
return "RefundRecord [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", totalFee=" + getFormatTotalFee()
+ ", feeType=" + feeType + ", cashFee=" + getFormatCashFee()
+ ", cashFeeType=" + cashFeeType + ", refundFee="
+ getFormatRefundFee() + ", couponRefundFee="
+ getFormatCouponRefundFee() + ", count=" + count
+ ", details=" + details + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,46 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V3退款申请结果
*
* @className RefundResult
* @author jy
* @date 2014年11月6日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class RefundResult extends RefundDetail {
private static final long serialVersionUID = -3687863914168618620L;
/**
* 微信订单号
*/
@XStreamAlias("transaction_id")
@JSONField(name = "transaction_id")
private String transactionId;
/**
* 商户系统内部的订单号
*/
@XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo;
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
@Override
public String toString() {
return "RefundResult [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", " + super.toString() + "]";
}
}

View File

@ -0,0 +1,3 @@
模拟微信公众平台登陆
(模拟登录|启用开发者模式|修改服务器配置|修改回调地址|启用服务器配置....more)

View File

@ -0,0 +1,666 @@
package com.foxinmy.weixin4j.mp.spider;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.util.IOUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
/**
* 模拟微信公众平台登陆
*
* <p>
* (模拟登录|启用开发者模式|修改服务器配置|修改回调地址|启用服务器配置....more)
* </p>
*
* @className WeixinExecutor
* @author jy
* @date 2014年8月15日
* @since JDK 1.7
* @see
*/
public class WeixinExecutor {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final static Map<String, String> accountMap = new HashMap<String, String>() {
private static final long serialVersionUID = 1L;
{
put("名称", "name");
put("头像", "avatar");
put("登录邮箱", "loginEmail");
put("原始ID", "originalId");
put("微信号", "weixinNo");
put("类型", "accountType");
put("认证情况", "weixinVerify");
put("主体信息", "bodyInfo");
put("介绍", "introduce");
put("所在地址", "address");
put("二维码", "qrcodeUrl");
}
};
private AbstractHttpClient client;
private HttpHost host;
private JSONObject weixin;
// 服务器响应地址
private String pushurl;
// oauth授权回调地址
private String backurl;
// 服务器校验token
private String token;
// 公众号用户名
private String uname;
// 公众号密码
private String pwd;
// 登录时验证码(如果有)
private String imgcode;
// 当要求输入验证码时,cookie需带上
private String sig;
public WeixinExecutor(String backurl, String pushurl, String token,
String uname, String pwd, String imgcode, String sig) {
this.backurl = backurl;
this.pushurl = pushurl;
this.token = token;
this.uname = uname;
this.pwd = pwd;
this.imgcode = StringUtils.isBlank(imgcode) ? "" : imgcode;
this.sig = sig;
weixin = new JSONObject();
weixin.put("host", "mp.weixin.qq.com");
weixin.put("base", "https://mp.weixin.qq.com");
weixin.put("auth", "https://mp.weixin.qq.com/cgi-bin/login?lang=zh_CN");
weixin.put(
"call",
"https://mp.weixin.qq.com/advanced/callbackprofile?t=ajax-response&token=%s&lang=zh_CN");
weixin.put("start",
"https://mp.weixin.qq.com/misc/skeyform?form=advancedswitchform");
weixin.put("back",
"https://mp.weixin.qq.com/merchant/myservice?action=set_oauth_domain&f=json");
weixin.put("verifycode",
"https://mp.weixin.qq.com/cgi-bin/verifycode?username=" + uname
+ "&r=%s");
weixin.put("bedeveloper",
"https://mp.weixin.qq.com/advanced/advanced?action=agreement");
List<BasicHeader> headers = new ArrayList<BasicHeader>();
headers.add(new BasicHeader("Origin", weixin.getString("base")));
headers.add(new BasicHeader("Connection", "keep-alive"));
headers.add(new BasicHeader(
"User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36"));
client = new DefaultHttpClient();
client.getParams().setParameter(ClientPNames.COOKIE_POLICY,
CookiePolicy.BROWSER_COMPATIBILITY);
client.getParams().setBooleanParameter(
"http.protocol.single-cookie-header", true);
client.getParams().setParameter(ClientPNames.DEFAULT_HEADERS, headers);
host = new HttpHost(weixin.getString("host"), -1, "https");
}
public JSONObject process() {
// 1.登陆微信公众号
step1_login();
int code = weixin.getIntValue("code");
if (code == 0) {
// 2.收集相关信息
step2_collect();
code = weixin.getIntValue("code");
// 3.填写服务器配置
// 3-1.未初始化账号
// 3-2.已配置账号
if (code == 0) {
step3_setting();
}
code = weixin.getIntValue("code");
// 4.修改网页授权地址
if (code == 0) {
step4_back();
}
// 5.创建底部菜单 (调用封装好的API)
// 5-1.订阅号
// 5-2.服务号
// 6.完成
}
return weixin;
}
/**
* step1:登录操作
*/
private void step1_login() {
HttpPost method = new HttpPost(weixin.getString("auth"));
try {
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
parameters.add(new BasicNameValuePair("username", uname));
parameters.add(new BasicNameValuePair("pwd", DigestUtils.md5Hex(pwd
.getBytes(Consts.UTF_8))));
parameters.add(new BasicNameValuePair("f", "json"));
parameters.add(new BasicNameValuePair("imgcode", imgcode));
if (!StringUtils.isBlank(imgcode)) {
method.addHeader("Cookie", "sig=" + sig);
}
method.setEntity(new UrlEncodedFormEntity(parameters, Consts.UTF_8));
method.addHeader("Referer", weixin.getString("base"));
HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine();
logger.info("step1_login--->status={},body=\n{}", line,
root.toString());
if (line.getStatusCode() == HttpStatus.SC_OK) {
JSONObject body = JSON.parseObject(root.body().text());
String msg = "";
int code = 0;
switch (body.getIntValue("ret")
+ body.getJSONObject("base_resp").getIntValue("ret")) {
case -1:
msg = "系统错误,请稍候再试。";
code = -1;
break;
case -2:
msg = "帐号或密码错误。";
code = 100;
break;
case -23:
msg = "您输入的帐号或者密码不正确,请重新输入。";
code = 101;
break;
case -21:
msg = "不存在该帐户。";
code = 102;
break;
case -7:
msg = "您目前处于访问受限状态。";
code = 103;
break;
case -8:
msg = "请输入图中的验证码";
code = 104;
break;
case -27:
msg = "您输入的验证码不正确,请重新输入";
code = 105;
break;
case -26:
msg = "该公众会议号已经过期,无法再登录使用。";
code = 106;
break;
case 0:
msg = "成功登录,正在跳转...";
break;
case -25:
msg = "海外帐号请在公众平台海外版登录,<a href=\"http://admin.wechat.com/\">点击登录</a>";
code = 107;
break;
default:
msg = "未知错误";
code = 108;
break;
}
if (code == 0) {
weixin.put(
"urlToken",
getQueryMap(body.getString("redirect_url")).get(
"token"));
weixin.put("indexUrl", String.format("%s%s",
weixin.getString("base"),
body.getString("redirect_url")));
weixin.put("step", "1");
} else {
if (code == 104 || code == 105) {
// 下载验证码
HttpGet get = new HttpGet(String.format(
weixin.getString("verifycode"),
System.currentTimeMillis()));
get.setHeaders(method.getAllHeaders());
response = client.execute(host, get);
StringBuffer base64 = new StringBuffer();
base64.append("data:")
.append(response.getFirstHeader("Content-Type")
.getValue()).append(";base64,");
base64.append(new String(
Base64.encodeBase64(IOUtil.toByteArray(response
.getEntity().getContent())),
Consts.UTF_8));
weixin.put("verifydata", base64.toString());
List<Cookie> cookieList = client.getCookieStore()
.getCookies();
for (Cookie cookie : cookieList) {
if (cookie.getName().equals("sig")) {
weixin.put("sig", cookie.getValue());
break;
}
}
}
weixin.put("code", code);
weixin.put("msg", msg);
}
} else {
weixin.put("code", "-3");
weixin.put("msg", "网络异常,请稍后重试!");
}
} catch (Exception e) {
weixin.put("code", "-2");
weixin.put("msg", "服务器繁忙,请稍后重试!");
weixin.put("exception", e.getMessage());
logger.error("step1_login catch error", e);
} finally {
if (weixin.getIntValue("code") != 0) {
client.getConnectionManager().shutdown();
}
}
}
/**
* step2:收集信息
*/
private void step2_collect() {
String url = weixin.getString("indexUrl");
HttpGet method = new HttpGet(url);
try {
method.addHeader("Referer", weixin.getString("base"));
HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine();
logger.info("step2_setting--->status={},body=\n{}", line,
root.toString());
if (line.getStatusCode() == HttpStatus.SC_OK) {
Element ele = root.getElementById("menuBar")
.getElementsByTag("dl").last();
url = ele.getElementsByTag("a").last().absUrl("href");
weixin.put("developerUrl", url);
method.addHeader("Referer", url);
url = ele.previousElementSibling().getElementsByTag("a")
.first().absUrl("href");
weixin.put("settingUrl", url);
method.setURI(URI.create(url));
response = client.execute(host, method);
entity = response.getEntity();
root = Jsoup.parse(entity.getContent(), Consts.UTF_8.name(),
weixin.getString("base"));
line = response.getStatusLine();
weixin.put("step", "2-1");
// 公众号配置页面
if (line.getStatusCode() == HttpStatus.SC_OK) {
Elements eles = root.getElementById("settingArea")
.getElementsByTag("li");
String key, value;
for (Element element : eles) {
key = element.getElementsByTag("h4").first().text();
ele = element.getElementsByClass("meta_content")
.first();
if (ele.children().isEmpty()) {
value = ele.text();
} else {
if (ele.child(0).tagName().equalsIgnoreCase("a")) {
value = ele.child(0).absUrl("href");
} else if (ele.child(0).tagName()
.equalsIgnoreCase("img")) {
value = ele.child(0).absUrl("src");
} else {
value = ele.text();
}
}
weixin.put(accountMap.get(key), value);
}
weixin.put("isVerify", weixin.getString("weixinVerify")
.contains("微信认证"));
weixin.put("isService", weixin.getString("accountType")
.contains("服务号"));
weixin.put("isSubscribe", weixin.getString("accountType")
.contains("订阅号"));
value = weixin.getString("qrcodeUrl");
method.setURI(URI.create(value));
response = client.execute(host, method);
weixin.put("qrcodeData", IOUtil.toByteArray(response
.getEntity().getContent()));
weixin.put("step", "2-2");
// 开发者页面
method.addHeader("Referer", url);
method.setURI(URI.create(weixin.getString("developerUrl")));
response = client.execute(host, method);
entity = response.getEntity();
root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(), weixin.getString("base"));
line = response.getStatusLine();
if (line.getStatusCode() == HttpStatus.SC_OK) {
// 还没有成为开发者 2014.10-06 jy.hu
// 触发成为开发者动作
ele = root.getElementById("js_toBeDeveloper");
if (ele != null && ele.hasText()) {
HttpPost post = new HttpPost(URI.create(weixin
.getString("bedeveloper")));
post.addHeader("Referer", url);
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
parameters.add(new BasicNameValuePair("token",
weixin.getString("urlToken")));
parameters.add(new BasicNameValuePair("f", "json"));
parameters.add(new BasicNameValuePair("ajax", "1"));
parameters.add(new BasicNameValuePair("lang",
"zh_CN"));
parameters.add(new BasicNameValuePair("random",
System.currentTimeMillis() + ""));
post.setEntity(new UrlEncodedFormEntity(parameters,
Consts.UTF_8));
response = client.execute(host, post);
entity = response.getEntity();
root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(),
weixin.getString("base"));
line = response.getStatusLine();
logger.info(
"step2_bedeveloper--->status={},body=\n{}",
line, root.toString());
if (line.getStatusCode() == HttpStatus.SC_OK) {
JSONObject body = JSON.parseObject(root.body()
.text());
if (body.getIntValue("ret") == 0) {
method.addHeader("Referer", url);
method.setURI(URI.create(weixin
.getString("developerUrl")));
response = client.execute(host, method);
entity = response.getEntity();
root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(),
weixin.getString("base"));
} else {
weixin.put("code", "-100");
weixin.put("msg", "成为开发者失败!");
return;
}
}
}
// 初始化状态
// 配置未启用状态
// 配置已启用状态
eles = root.getElementsByClass("developer_info_opr");
if (eles != null && eles.hasText()) {
weixin.put("developerModifyUrl", eles.first()
.children().first().absUrl("href"));
weixin.put("status",
eles.text().contains("启用") ? "READY"
: "RUNNING");
} else {
weixin.put("status", "INIT");
}
// appid&appsecret
if (weixin.getBooleanValue("isService")
|| (weixin.getBooleanValue("isSubscribe") && weixin
.getBooleanValue("isVerify"))) {
eles = root
.getElementsByClass("developer_info_item")
.first().children().last()
.getElementsByClass("frm_controls");
weixin.put("appId", eles.first().text());
weixin.put("appSecret",
eles.last().text().replace("重置", "").trim());
}
weixin.put("step", "2-3");
}
} else {
weixin.put("code", "-3");
weixin.put("msg", "网络异常,请稍后重试!");
}
} else {
weixin.put("code", "-3");
weixin.put("msg", "网络异常,请稍后重试!");
}
} catch (Exception e) {
weixin.put("code", "-2");
weixin.put("msg", "服务器繁忙,请稍后重试!");
weixin.put("exception", e.getMessage());
logger.error("step2_collect catch error", e);
} finally {
if (weixin.getIntValue("code") != 0) {
client.getConnectionManager().shutdown();
}
}
}
/**
* step3:填写配置
*/
private void step3_setting() {
HttpPost method = new HttpPost(String.format(weixin.getString("call"),
weixin.getString("urlToken")));
try {
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
parameters.add(new BasicNameValuePair("url", pushurl));
parameters.add(new BasicNameValuePair("callback_token", token));
// EncodingAESKey | 消息加解密方式(明文0,兼容1,安全2)
parameters.add(new BasicNameValuePair("encoding_aeskey", RandomUtil
.generateString(43)));
parameters
.add(new BasicNameValuePair("callback_encrypt_mode", "0"));
parameters.add(new BasicNameValuePair("operation_seq", RandomUtil
.generateStringByNumberChar(9)));
method.setEntity(new UrlEncodedFormEntity(parameters, Consts.UTF_8));
method.addHeader("Referer", weixin.getString("developerModifyUrl"));
HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine();
logger.info("step3_setting--->status={},body=\n{}", line,
root.toString());
if (line.getStatusCode() == HttpStatus.SC_OK) {
JSONObject body = JSON.parseObject(root.body().text());
String msg = "";
int code = 0;
switch (body.getIntValue("ret")
+ body.getJSONObject("base_resp").getIntValue("ret")) {
case -201:
msg = "无效的URL";
code = 200;
break;
case -202:
msg = "无效的Token";
code = 201;
break;
case -203:
msg = "操作频率太快,请休息一下。";
code = 202;
break;
case -204:
msg = "请先在设置页面完善当前帐号信息";
code = 203;
break;
case -205:
msg = "该URL可能存在安全风险请检查";
code = 207;
break;
case -301:
msg = "请求URL超时";
code = 204;
break;
case -302:
msg = "服务器没有正确响应Token验证请稍后重试";
code = 205;
break;
case -104:
msg = "参数错误,请重新填写。";
code = 206;
break;
case 0:
msg = "配置成功..";
break;
default:
msg = "未知错误";
code = 108;
break;
}
if (code == 0) {
// 触发启用按钮
if (!weixin.getString("status").equals("RUNNING")) {
parameters = new ArrayList<NameValuePair>();
parameters.add(new BasicNameValuePair("token", weixin
.getString("urlToken")));
parameters.add(new BasicNameValuePair("f", "json"));
parameters.add(new BasicNameValuePair("ajax", "1"));
parameters.add(new BasicNameValuePair("flag", "1"));
parameters.add(new BasicNameValuePair("type", "2"));
parameters.add(new BasicNameValuePair("lang", "zh_CN"));
parameters.add(new BasicNameValuePair("random", System
.currentTimeMillis() + ""));
method.setEntity(new UrlEncodedFormEntity(parameters,
Consts.UTF_8));
method.setURI(URI.create(weixin.getString("start")));
response = client.execute(host, method);
entity = response.getEntity();
root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(), weixin.getString("base"));
line = response.getStatusLine();
logger.info("step3_setting--->status={},body=\n{}",
line, root.toString());
if (line.getStatusCode() == HttpStatus.SC_OK) {
body = JSON.parseObject(root.body().text());
if (body.getIntValue("ret")
+ body.getJSONObject("base_resp")
.getIntValue("ret") != 0) {
weixin.put("code", 300);
weixin.put("msg", "启用开发者模式失败,请稍后再试!");
}
}
}
weixin.put("step", "3");
} else {
weixin.put("code", code);
weixin.put("msg", msg);
}
} else {
weixin.put("code", "-3");
weixin.put("msg", "网络异常,请稍后重试!");
}
} catch (Exception e) {
weixin.put("code", "-2");
weixin.put("msg", "服务器繁忙,请稍后重试!");
weixin.put("exception", e.getMessage());
logger.error("step3_setting catch error", e);
} finally {
if (weixin.getIntValue("code") != 0) {
client.getConnectionManager().shutdown();
}
}
}
/**
* step4:修改回调
*/
private void step4_back() {
try {
if (weixin.getBooleanValue("isVerify")) {
HttpPost method = new HttpPost(weixin.getString("back"));
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
parameters.add(new BasicNameValuePair("domain", backurl));
parameters.add(new BasicNameValuePair("token", weixin
.getString("urlToken")));
parameters.add(new BasicNameValuePair("f", "json"));
parameters.add(new BasicNameValuePair("ajax", "1"));
parameters.add(new BasicNameValuePair("flag", "1"));
parameters.add(new BasicNameValuePair("lang", "zh_CN"));
parameters.add(new BasicNameValuePair("random", System
.currentTimeMillis() + ""));
method.setEntity(new UrlEncodedFormEntity(parameters,
Consts.UTF_8));
method.addHeader("Referer", weixin.getString("developerUrl"));
HttpResponse response = client.execute(host, method);
HttpEntity entity = response.getEntity();
Document root = Jsoup.parse(entity.getContent(),
Consts.UTF_8.name(), weixin.getString("base"));
StatusLine line = response.getStatusLine();
logger.info("step4_back--->status={},body=\n{}", line,
root.toString());
if (line.getStatusCode() == HttpStatus.SC_OK) {
JSONObject body = JSON.parseObject(root.body().text());
if (body.getIntValue("ret")
+ body.getJSONObject("base_resp")
.getIntValue("ret") != 0) {
weixin.put("code", "400");
weixin.put("msg", "修改授权回调地址失败!");
}
weixin.put("step", "4");
}
} else {
logger.info("公众号尚未认证,放弃本次修改授权回调地址操作。{}", weixin);
}
} catch (Exception e) {
weixin.put("code", "-2");
weixin.put("msg", "服务器繁忙,请稍后重试!");
weixin.put("exception", e.getMessage());
logger.error("step4_back catch error", e);
} finally {
client.getConnectionManager().shutdown();
}
}
private Map<String, String> getQueryMap(String query) {
String[] params = query.split("&");
Map<String, String> map = new HashMap<String, String>();
for (String param : params) {
String name = param.split("=")[0];
String value = param.split("=")[1];
map.put(name, value);
}
return map;
}
}

View File

@ -0,0 +1,58 @@
package com.foxinmy.weixin4j.mp.token;
import com.alibaba.fastjson.JSONObject;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.token.TokenCreator;
import com.foxinmy.weixin4j.token.TokenHolder;
/**
* 微信公众平台JSTICKET创建者
*
* @className WeixinJSTicketCreator
* @author jy
* @date 2015年1月10日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95">JS
* TICKET</a>
*/
public class WeixinJSTicketCreator implements TokenCreator {
private final String appid;
private final TokenHolder weixinTokenHolder;
private final HttpRequest request;
/**
* jssdk
*
* @param appid
* appid
* @param weixinTokenHolder
* <font color="red">公众平台的access_token</font>
*/
public WeixinJSTicketCreator(String appid, TokenHolder weixinTokenHolder) {
this.appid = appid;
this.weixinTokenHolder = weixinTokenHolder;
this.request = new HttpRequest();
}
@Override
public String getCacheKey() {
return String.format("mp_jsticket_%s", appid);
}
@Override
public Token createToken() throws WeixinException {
Response response = request.get(String.format(Consts.MP_JS_TICKET_URL,
weixinTokenHolder.getToken().getAccessToken()));
JSONObject result = response.getAsJson();
Token token = new Token(result.getString("ticket"));
token.setExpiresIn(result.getIntValue("expires_in"));
token.setTime(System.currentTimeMillis());
return token;
}
}

View File

@ -0,0 +1,58 @@
package com.foxinmy.weixin4j.mp.token;
import com.alibaba.fastjson.TypeReference;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.HttpRequest;
import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.WeixinAccount;
import com.foxinmy.weixin4j.token.TokenCreator;
import com.foxinmy.weixin4j.util.ConfigUtil;
/**
* 微信公众平台TOKEN创建者
*
* @className WeixinTokenCreator
* @author jy
* @date 2015年1月10日
* @since JDK 1.7
* @see <a
* href="http://mp.weixin.qq.com/wiki/11/0e4b294685f817b95cbed85ba5e82b8f.html">微信公众平台获取token说明</a>
* @see com.foxinmy.weixin4j.model.Token
*/
public class WeixinTokenCreator implements TokenCreator {
private final HttpRequest request;
private final String appid;
private final String secret;
public WeixinTokenCreator() {
WeixinAccount weixinAccount = ConfigUtil.getWeixinAccount();
this.appid = weixinAccount.getId();
this.secret = weixinAccount.getSecret();
this.request = new HttpRequest();
}
public WeixinTokenCreator(String appid, String secret) {
this.appid = appid;
this.secret = secret;
this.request = new HttpRequest();
}
@Override
public String getCacheKey() {
return String.format("mp_token_%s", appid);
}
@Override
public Token createToken() throws WeixinException {
String tokenUrl = String.format(Consts.MP_ASSESS_TOKEN_URL, appid,
secret);
Response response = request.get(tokenUrl);
Token token = response.getAsObject(new TypeReference<Token>() {
});
token.setTime(System.currentTimeMillis());
return token;
}
}

View File

@ -0,0 +1,21 @@
package com.foxinmy.weixin4j.mp.type;
/**
* 自动匹配模式
*
* @className AutomatchMode
* @author jy
* @date 2015年4月15日
* @since JDK 1.7
* @see
*/
public enum AutomatchMode {
/**
* 代表消息中含有该关键词即可
*/
contain,
/**
* 表示消息内容必须和关键词严格相同
*/
equal
}

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