支付模块拆分为V2跟V3

This commit is contained in:
jy.hu 2015-01-04 22:32:39 +08:00
parent 75dc019b1f
commit 5b40a3728c
55 changed files with 2396 additions and 1266 deletions

View File

@ -208,6 +208,18 @@ netty的代码没有放到maven中心仓库,也没什么意义,因为最终需
+ **weixin4j-qy**: 调整回调模式下的首次验证的签名方式 + **weixin4j-qy**: 调整回调模式下的首次验证的签名方式
* 2015-01-04
+ **weixin4j-base**: 新增获取classpath目录下的资源路径的方法
+ **weixin4j-mp**: 支付模块拆分为V2跟V3,新增WeixinPayProxy类
+ **weixin4j-mp**: 退款相关类拆分V2跟V3
+ **weixin4j-mp**: 新增接口上报接口
+ **weixin4j-qy**: 新增批量删除员工接口
接下来 接下来
------ ------
* 企业号第三方应用 * 企业号第三方应用

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.foxinmy</groupId> <groupId>com.foxinmy</groupId>
<artifactId>weixin4j</artifactId> <artifactId>weixin4j</artifactId>
@ -209,6 +210,9 @@
<include>**/*.xml</include> <include>**/*.xml</include>
<include>**/*.properties</include> <include>**/*.properties</include>
<include>**/*.pem</include> <include>**/*.pem</include>
<include>**/*.p12</include>
<include>**/*.pfx</include>
<include>**/*.pem</include>
</includes> </includes>
</resource> </resource>
<resource> <resource>

View File

@ -38,3 +38,7 @@ weixin4j-base
* 2014-11-24 * 2014-11-24
+ 将Action跟Mapping基础类并入到项目 + 将Action跟Mapping基础类并入到项目
* 2015-01-04
+ ConfigUtil类新增获取classpath目录下的资源路径的方法

View File

@ -13,6 +13,7 @@ import com.thoughtworks.xstream.mapper.DefaultMapper;
/** /**
* API基础 * API基础
*
* @className BaseApi * @className BaseApi
* @author jy.hu * @author jy.hu
* @date 2014年9月26日 * @date 2014年9月26日
@ -37,7 +38,9 @@ public abstract class BaseApi {
protected Map<String, String> xml2map(String xml) { protected Map<String, String> xml2map(String xml) {
return mapXstream.fromXML(xml, Map.class); return mapXstream.fromXML(xml, Map.class);
} }
protected abstract ResourceBundle getWeixinBundle(); protected abstract ResourceBundle getWeixinBundle();
protected String getRequestUri(String key) { protected String getRequestUri(String key) {
String url = getWeixinBundle().getString(key); String url = getWeixinBundle().getString(key);
Pattern p = Pattern.compile("(\\{[^\\}]*\\})"); Pattern p = Pattern.compile("(\\{[^\\}]*\\})");

View File

@ -188,7 +188,6 @@ public class HttpRequest {
response.setStatusText(statusLine.getReasonPhrase()); response.setStatusText(statusLine.getReasonPhrase());
response.setStream(new ByteArrayInputStream(data)); response.setStream(new ByteArrayInputStream(data));
response.setText(new String(data, Consts.UTF_8)); response.setText(new String(data, Consts.UTF_8));
EntityUtils.consume(httpEntity); EntityUtils.consume(httpEntity);
Header contentType = httpResponse Header contentType = httpResponse
.getFirstHeader(HttpHeaders.CONTENT_TYPE); .getFirstHeader(HttpHeaders.CONTENT_TYPE);

View File

@ -168,10 +168,9 @@ public class Response {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); return "Response [text=" + text + ", statusCode=" + statusCode
sb.append("[Response text=").append(text); + ", statusText=" + statusText + ", stream=" + stream
sb.append(", statusCode=").append(statusCode); + ", isJsonResult=" + isJsonResult + ", isXmlResult="
sb.append(", statusText=").append(statusText).append("]"); + isXmlResult + "]";
return sb.toString();
} }
} }

View File

@ -1,8 +1,5 @@
package com.foxinmy.weixin4j.http; package com.foxinmy.weixin4j.http;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
@ -23,10 +20,6 @@ import org.apache.http.conn.ssl.SSLSocketFactory;
*/ */
public class SSLHttpRequest extends HttpRequest { public class SSLHttpRequest extends HttpRequest {
public SSLHttpRequest(String password, File file) throws IOException {
this(password, new FileInputStream(file));
}
public SSLHttpRequest(String password, InputStream inputStream) { public SSLHttpRequest(String password, InputStream inputStream) {
super(); super();
try { try {

View File

@ -2,7 +2,9 @@ package com.foxinmy.weixin4j.http;
import java.io.Serializable; import java.io.Serializable;
import com.foxinmy.weixin4j.model.Consts; import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
@ -19,16 +21,29 @@ public class XmlResult implements Serializable {
private static final long serialVersionUID = -6185313616955051150L; private static final long serialVersionUID = -6185313616955051150L;
@XStreamAlias("return_code") @XStreamAlias("return_code")
@JSONField(name = "return_code")
private String returnCode;// 此字段是通信标识,非交易 标识,交易是否成功需要查 result_code 来判断 非空 private String returnCode;// 此字段是通信标识,非交易 标识,交易是否成功需要查 result_code 来判断 非空
@XStreamAlias("return_msg") @XStreamAlias("return_msg")
@JSONField(name = "return_msg")
private String returnMsg;// 返回信息,如非 ,为错误原因 可能为空 private String returnMsg;// 返回信息,如非 ,为错误原因 可能为空
@XStreamAlias("result_code") @XStreamAlias("result_code")
@JSONField(name = "result_code")
private String resultCode;// 业务结果SUCCESS/FAIL 非空 private String resultCode;// 业务结果SUCCESS/FAIL 非空
@XStreamAlias("err_code") @XStreamAlias("err_code")
@JSONField(name = "err_code")
private String errCode;// 错误代码 可为空 private String errCode;// 错误代码 可为空
@XStreamAlias("err_code_des") @XStreamAlias("err_code_des")
@JSONField(name = "err_code_des")
private String errCodeDes;// 结果信息描述 可为空 private String errCodeDes;// 结果信息描述 可为空
public XmlResult() {
}
public XmlResult(String returnCode, String returnMsg) {
this.returnCode = returnCode;
this.returnMsg = returnMsg;
}
public String getReturnCode() { public String getReturnCode() {
return returnCode; return returnCode;
} }
@ -38,7 +53,7 @@ public class XmlResult implements Serializable {
} }
public String getReturnMsg() { public String getReturnMsg() {
return returnMsg; return StringUtils.isNotBlank(returnMsg) ? returnMsg : null;
} }
public void setReturnMsg(String returnMsg) { public void setReturnMsg(String returnMsg) {
@ -62,27 +77,13 @@ public class XmlResult implements Serializable {
} }
public String getErrCodeDes() { public String getErrCodeDes() {
return errCodeDes; return StringUtils.isNotBlank(errCodeDes) ? errCodeDes : null;
} }
public void setErrCodeDes(String errCodeDes) { public void setErrCodeDes(String errCodeDes) {
this.errCodeDes = errCodeDes; this.errCodeDes = errCodeDes;
} }
public XmlResult() {
this(Consts.SUCCESS.toLowerCase(), "");
}
public XmlResult(String returnCode, String returnMsg) {
this.returnCode = returnCode;
this.returnMsg = returnMsg;
if (returnCode.equalsIgnoreCase(Consts.SUCCESS)) {
this.resultCode = Consts.SUCCESS.toLowerCase();
this.errCode = Consts.SUCCESS.toLowerCase();
this.errCodeDes = "";
}
}
@Override @Override
public String toString() { public String toString() {
return "returnCode=" + returnCode + ", returnMsg=" + returnMsg return "returnCode=" + returnCode + ", returnMsg=" + returnMsg

View File

@ -1,4 +1,7 @@
package com.foxinmy.weixin4j.model; package com.foxinmy.weixin4j.model;
import java.nio.charset.Charset;
/** /**
* 常量类 * 常量类
* @className Consts * @className Consts
@ -8,6 +11,8 @@ package com.foxinmy.weixin4j.model;
* @see * @see
*/ */
public final class Consts { public final class Consts {
public static final Charset UTF_8 = Charset.forName("UTF-8");
public static final Charset GBK = Charset.forName("GBK");
public static final String SUCCESS = "SUCCESS"; public static final String SUCCESS = "SUCCESS";
public static final String FAIL = "FAIL"; public static final String FAIL = "FAIL";
public static final String SunX509 = "SunX509"; public static final String SunX509 = "SunX509";

View File

@ -19,16 +19,21 @@ import com.foxinmy.weixin4j.model.WeixinQyAccount;
* @see * @see
*/ */
public class ConfigUtil { public class ConfigUtil {
private final static String CLASSPATH_PREFIX = "classpath:";
private final static String CLASSPATH_VALUE;
private final static ResourceBundle weixinBundle; private final static ResourceBundle weixinBundle;
static { static {
weixinBundle = ResourceBundle.getBundle("weixin"); weixinBundle = ResourceBundle.getBundle("weixin");
Set<String> keySet = weixinBundle.keySet(); Set<String> keySet = weixinBundle.keySet();
File file = null; File file = null;
CLASSPATH_VALUE = Thread.currentThread().getContextClassLoader()
.getResource("").getPath();
for (String key : keySet) { for (String key : keySet) {
if (!key.endsWith("_path")) { if (!key.endsWith("_path")) {
continue; continue;
} }
file = new File(getValue(key)); file = new File(getValue(key).replaceFirst(CLASSPATH_PREFIX,
CLASSPATH_VALUE));
if (!file.exists() && !file.mkdirs()) { if (!file.exists() && !file.mkdirs()) {
System.err.append(String.format("%s create fail.%n", System.err.append(String.format("%s create fail.%n",
file.getAbsolutePath())); file.getAbsolutePath()));
@ -36,10 +41,27 @@ public class ConfigUtil {
} }
} }
/**
* 获取weixin.properties文件中的key值
*
* @param key
* @return
*/
public static String getValue(String key) { public static String getValue(String key) {
return weixinBundle.getString(key); return weixinBundle.getString(key);
} }
/**
* 判断属性是否存在[classpath:]如果存在则拼接项目路径后返回 一般用于文件的绝对路径获取
*
* @param key
* @return
*/
public static String getClassPathValue(String key) {
return new File(getValue(key).replaceFirst(CLASSPATH_PREFIX,
CLASSPATH_VALUE)).getPath();
}
public static <T extends WeixinAccount> T getWeixinAccount(Class<T> clazz) { public static <T extends WeixinAccount> T getWeixinAccount(Class<T> clazz) {
String text = getValue("account"); String text = getValue("account");
return JSON.parseObject(text, clazz); return JSON.parseObject(text, clazz);

View File

@ -65,6 +65,7 @@ weixin4j-mp
media_path=/tmp/weixin/media media_path=/tmp/weixin/media
bill_path=/tmp/weixin/bill bill_path=/tmp/weixin/bill
ca_file=/tmp/weixin/xxxxx.p12 | xxxx.pfx ca_file=/tmp/weixin/xxxxx.p12 | xxxx.pfx
#classpath路径下:ca_file=classpath:xxxxx.p12
3.在项目根目录下执行`mvn package`命令后得到jar包,将`weixin4j-mp-full`包或者`weixin4j-base``weixin4j-mp-api`两个包引入到自己的工程. 3.在项目根目录下执行`mvn package`命令后得到jar包,将`weixin4j-mp-full`包或者`weixin4j-base``weixin4j-mp-api`两个包引入到自己的工程.
@ -151,3 +152,11 @@ weixin4j-mp
+ **weixin4j-mp-api**: 新增群发消息预览、状态查询接口 + **weixin4j-mp-api**: 新增群发消息预览、状态查询接口
+ **weixin4j-mp-api**: 新增多客服添加账号、更新账号、上传头像、删除账号接口 + **weixin4j-mp-api**: 新增多客服添加账号、更新账号、上传头像、删除账号接口
* 2015-01-04
+ **weixin4j-mp-api**: 支付模块拆分为V2跟V3,新增WeixinPayProxy类
+ **weixin4j-mp-api**: 退款相关类拆分为V2跟V3
+ **weixin4j-mp-api**: 新增接口上报接口

View File

@ -60,6 +60,7 @@ weixin.properties说明
media_path=/tmp/weixin/media media_path=/tmp/weixin/media
bill_path=/tmp/weixin/bill bill_path=/tmp/weixin/bill
ca_file=/tmp/weixin/xxxxx.p12 | xxxx.pfx ca_file=/tmp/weixin/xxxxx.p12 | xxxx.pfx
#classpath路径下:ca_file=classpath:xxxxx.p12
2.实例化一个`WeixinProxy`对象,调用API,需要强调的是如果只传入appid,appsecret两个参数将无法调用支付相关接口 2.实例化一个`WeixinProxy`对象,调用API,需要强调的是如果只传入appid,appsecret两个参数将无法调用支付相关接口
@ -132,3 +133,11 @@ weixin.properties说明
+ 新增群发消息预览、状态查询接口 + 新增群发消息预览、状态查询接口
+ 新增多客服添加账号、更新账号、上传头像、删除账号接口 + 新增多客服添加账号、更新账号、上传头像、删除账号接口
* 2015-01-04
+ 支付模块拆分为V2跟V3,新增WeixinPayProxy类
+ 退款相关类拆分为V2跟V3
+ 新增接口上报接口

View File

@ -0,0 +1,449 @@
package com.foxinmy.weixin4j.mp;
import java.io.File;
import java.util.Date;
import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.api.Pay2Api;
import com.foxinmy.weixin4j.mp.api.Pay3Api;
import com.foxinmy.weixin4j.mp.api.PayApi;
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.IdType;
import com.foxinmy.weixin4j.mp.type.RefundType;
import com.foxinmy.weixin4j.token.FileTokenHolder;
import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.type.AccountType;
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
*/
public class WeixinPayProxy {
private final PayApi payApi;
private final Pay2Api pay2Api;
private final Pay3Api pay3Api;
/**
* 默认采用文件存放Token信息
*/
public WeixinPayProxy() {
this(new FileTokenHolder(AccountType.MP));
}
/**
* WeixinAccount对象
*
* @param weixinAccount
* 微信账户
*/
public WeixinPayProxy(WeixinMpAccount weixinAccount) {
this(new FileTokenHolder(weixinAccount));
}
/**
* TokenHolder对象
*
* @param tokenHolder
*/
public WeixinPayProxy(TokenHolder tokenHolder) {
this.pay2Api = new Pay2Api(tokenHolder);
this.pay3Api = new Pay3Api(tokenHolder);
int version = ((WeixinMpAccount) tokenHolder.getAccount()).getVersion();
if (version == 2) {
this.payApi = this.pay2Api;
} else if (version == 3) {
this.payApi = this.pay3Api;
} else {
this.payApi = this.pay3Api;
}
}
/**
* 发货通知
*
* @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订单查询
*
* @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
* @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 style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* </p>
*
* @param caFile
* 证书文件(后缀为*.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 com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @since V3
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v3.RefundResult refundV3(
File caFile, IdQuery idQuery, String outRefundNo, double totalFee,
double refundFee, String opUserId) throws WeixinException {
return pay3Api.refund(caFile, idQuery, outRefundNo, totalFee,
refundFee, opUserId);
}
/**
* V3退款申请采用properties中配置的ca文件
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinPayProxy#refundV3(File, IdQuery, String, double, double, 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, opUserId);
}
/**
* V3退款查询</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.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord
* @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
* @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);
}
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 执行结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
* @since V3
* @throws WeixinException
*/
public ApiResult closeOrder(String outTradeNo) throws WeixinException {
return payApi.closeOrder(outTradeNo);
}
/**
* native支付URL转短链接
*
* @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
* @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
* @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);
}
}

View File

@ -2,13 +2,11 @@ package com.foxinmy.weixin4j.mp;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult; import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.Button; import com.foxinmy.weixin4j.model.Button;
import com.foxinmy.weixin4j.model.WeixinMpAccount; import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.api.CustomApi; import com.foxinmy.weixin4j.mp.api.CustomApi;
@ -18,7 +16,6 @@ import com.foxinmy.weixin4j.mp.api.MassApi;
import com.foxinmy.weixin4j.mp.api.MediaApi; import com.foxinmy.weixin4j.mp.api.MediaApi;
import com.foxinmy.weixin4j.mp.api.MenuApi; import com.foxinmy.weixin4j.mp.api.MenuApi;
import com.foxinmy.weixin4j.mp.api.NotifyApi; import com.foxinmy.weixin4j.mp.api.NotifyApi;
import com.foxinmy.weixin4j.mp.api.PayApi;
import com.foxinmy.weixin4j.mp.api.QrApi; import com.foxinmy.weixin4j.mp.api.QrApi;
import com.foxinmy.weixin4j.mp.api.TmplApi; import com.foxinmy.weixin4j.mp.api.TmplApi;
import com.foxinmy.weixin4j.mp.api.UserApi; import com.foxinmy.weixin4j.mp.api.UserApi;
@ -33,12 +30,6 @@ import com.foxinmy.weixin4j.mp.model.QRParameter;
import com.foxinmy.weixin4j.mp.model.SemQuery; import com.foxinmy.weixin4j.mp.model.SemQuery;
import com.foxinmy.weixin4j.mp.model.SemResult; import com.foxinmy.weixin4j.mp.model.SemResult;
import com.foxinmy.weixin4j.mp.model.User; import com.foxinmy.weixin4j.mp.model.User;
import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.foxinmy.weixin4j.mp.payment.Refund;
import com.foxinmy.weixin4j.mp.payment.RefundResult;
import com.foxinmy.weixin4j.mp.payment.v2.Order;
import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.IndustryType; import com.foxinmy.weixin4j.mp.type.IndustryType;
import com.foxinmy.weixin4j.mp.type.Lang; import com.foxinmy.weixin4j.mp.type.Lang;
import com.foxinmy.weixin4j.msg.model.Base; import com.foxinmy.weixin4j.msg.model.Base;
@ -70,7 +61,6 @@ public class WeixinProxy {
private final QrApi qrApi; private final QrApi qrApi;
private final TmplApi tmplApi; private final TmplApi tmplApi;
private final HelperApi helperApi; private final HelperApi helperApi;
private final PayApi payApi;
/** /**
* 默认采用文件存放Token信息 * 默认采用文件存放Token信息
@ -116,7 +106,6 @@ public class WeixinProxy {
this.qrApi = new QrApi(tokenHolder); this.qrApi = new QrApi(tokenHolder);
this.tmplApi = new TmplApi(tokenHolder); this.tmplApi = new TmplApi(tokenHolder);
this.helperApi = new HelperApi(tokenHolder); this.helperApi = new HelperApi(tokenHolder);
this.payApi = new PayApi(tokenHolder);
} }
/** /**
@ -503,7 +492,6 @@ public class WeixinProxy {
* @see com.foxinmy.weixin4j.mp.api.MassApi * @see com.foxinmy.weixin4j.mp.api.MassApi
* @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#massByGroupId(Base, int)} * @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#massByGroupId(Base, int)}
* @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#massByOpenIds(Base, String...) * @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#massByOpenIds(Base, String...)
*/ */
public JsonResult deleteMassNews(String msgid) throws WeixinException { public JsonResult deleteMassNews(String msgid) throws WeixinException {
return massApi.deleteMassNews(msgid); return massApi.deleteMassNews(msgid);
@ -756,7 +744,8 @@ public class WeixinProxy {
/** /**
* 自定义菜单 * 自定义菜单
* *
* @param btnList 菜单列表 * @param btnList
* 菜单列表
* @throws WeixinException * @throws WeixinException
* @see <a * @see <a
* href="http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html">创建自定义菜单</a> * href="http://mp.weixin.qq.com/wiki/13/43de8269be54a0a6f64413e4dfa94f39.html">创建自定义菜单</a>
@ -798,7 +787,8 @@ public class WeixinProxy {
/** /**
* 生成带参数的二维码 * 生成带参数的二维码
* *
* @param parameter 二维码参数 * @param parameter
* 二维码参数
* @return byte数据包 * @return byte数据包
* @throws WeixinException * @throws WeixinException
* @see com.foxinmy.weixin4j.mp.api.QrApi * @see com.foxinmy.weixin4j.mp.api.QrApi
@ -900,7 +890,8 @@ public class WeixinProxy {
/** /**
* 长链接转短链接 * 长链接转短链接
* *
* @param url 待转换的链接 * @param url
* 待转换的链接
* @return 短链接 * @return 短链接
* @throws WeixinException * @throws WeixinException
* @see <a * @see <a
@ -911,195 +902,6 @@ public class WeixinProxy {
return helperApi.getShorturl(url); return helperApi.getShorturl(url);
} }
/**
* 发货通知
*
* @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 outTradeNo
* 订单号
* @return 订单信息
* @throws WeixinException
* @see com.foxinmy.weixin4j.mp.api.PayApi
*/
public Order orderQueryV2(WeixinMpAccount weixinAccount, String outTradeNo)
throws WeixinException {
return payApi.orderQueryV2(outTradeNo);
}
/**
* 维权处理
*
* @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);
}
/**
* V3订单查询
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @throws WeixinException
*/
public com.foxinmy.weixin4j.mp.payment.v3.Order orderQueryV3(IdQuery idQuery)
throws WeixinException {
return payApi.orderQueryV3(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
* @throws WeixinException
* @throws IOException
*/
public File downloadbill(Date billDate, BillType billType)
throws WeixinException, IOException {
return payApi.downloadbill(billDate, billType);
}
/**
* 申请退款(V3请求需要双向证书)</br>
* <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* </p>
*
* @param ca
* 证书文件<font color="red">V2版本时无需传入</font>
* @param idQuery
* ) 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @param outRefundNo
* 商户系统内部的退款单号, 户系统内部唯一,同一退款单号多次请求只退一笔
* @param totalFee
* 订单总金额,单位为元
* @param refundFee
* 退款总金额,单位为元,可以做部分退款
* @param opUserId
* 操作员帐号, 默认为商户号
* @param opUserPasswd
* <font color="red">V3版本留空,V2版本需传入值</font>
*
* @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @see com.foxinmy.weixin4j.mp.payment.RefundResult
* @since V2 & V3
* @throws WeixinException
* @throws IOException
*/
public RefundResult refund(InputStream ca, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee,
String opUserId, String opUserPasswd) throws WeixinException,
IOException {
return payApi.refund(ca, idQuery, outRefundNo, totalFee, refundFee,
opUserId, opUserPasswd);
}
/**
* 不同的退款接口选择</br> V3支付则采用properties中配置的ca文件</br>
* V2支付则需要传入opUserPasswd参数</br>
*
* @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#refund(InputStream, IdQuery, String, double, double, String,String)}
*/
public RefundResult refund(IdQuery idQuery, String outRefundNo,
double totalFee, double refundFee, String opUserId,
String opUserPasswd) throws WeixinException, IOException {
return payApi.refund(idQuery, outRefundNo, totalFee, refundFee,
opUserId, opUserPasswd);
}
/**
* 退款查询</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.api.PayApi
* @see com.foxinmy.weixin4j.mp.payment.Refund
* @since V2 & V3
* @throws WeixinException
*/
public Refund refundQuery(IdQuery idQuery) throws WeixinException {
return payApi.refundQuery(idQuery);
}
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 执行结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @since V3
* @throws WeixinException
*/
public XmlResult closeOrder(String outTradeNo) throws WeixinException {
return payApi.closeOrder(outTradeNo);
}
/**
* native支付URL转短链接
*
* @param url
* 具有native标识的支付URL
* @return 转换后的短链接
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @throws WeixinException
*/
public String getPayShorturl(String url) throws WeixinException {
return payApi.getShorturl(url);
}
/** /**
* 语义理解 * 语义理解
* *
@ -1129,39 +931,4 @@ public class WeixinProxy {
public List<String> getcallbackip() throws WeixinException { public List<String> getcallbackip() throws WeixinException {
return helperApi.getcallbackip(); return helperApi.getcallbackip();
} }
/**
* 冲正订单(需要证书)</br> 当支付返回失败,或收银系统超时需要取消交易,可以调用该接口</br> 接口逻辑:
* 付失败的关单,支付成功的撤销支付</br> <font color="red">7天以内的单可撤销,其他正常支付的单
* 如需实现相同功能请调用退款接口</font></br> <font
* color="red">调用扣款接口后请勿立即调用撤销,需要等待5秒以上先调用查单接口,如果没有确切的返回,再调用撤销</font></br>
*
* @param ca
* 证书文件
* @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 撤销结果
* @see com.foxinmy.weixin4j.mp.api.PayApi
* @throws WeixinException
*/
public ApiResult reverse(InputStream ca, IdQuery idQuery)
throws WeixinException {
return payApi.reverse(ca, idQuery);
}
/**
* 冲正撤销:默认采用properties中配置的ca文件
*
* @param idQuery
* transaction_idout_trade_no 二选一
* @return 撤销结果
* @see {@link com.foxinmy.weixin4j.mp.WeixinProxy#reverse(InputStream, IdQuery)}
* @throws WeixinException
* @throws IOException
*/
public ApiResult reverse(IdQuery idQuery) throws WeixinException,
IOException {
return payApi.reverse(idQuery);
}
} }

View File

@ -0,0 +1,465 @@
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.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.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 PayApi
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
public class Pay2Api extends PayApi {
private final HelperApi helperApi;
public Pay2Api(TokenHolder tokenHolder) {
super(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.toLowerCase());
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,430 @@
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.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.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.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 PayApi
* @author jy
* @date 2014年10月28日
* @since JDK 1.7
* @see
*/
public class Pay3Api extends PayApi {
public Pay3Api(TokenHolder tokenHolder) {
super(tokenHolder);
}
/**
* 订单查询
*
* @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no
* @return 订单信息
* @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @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 response.getAsObject(new TypeReference<Order>() {
});
}
/**
* 申请退款(请求需要双向证书)</br>
* <p style="color:red">
* 交易时间超过 1 年的订单无法提交退款; </br> 支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no一笔退款失
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* </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
* @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 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
* 操作员帐号, 默认为商户号
* @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,
String opUserId) throws WeixinException {
return refund(caFile, idQuery, outRefundNo, totalFee, refundFee,
opUserId, null);
}
/**
* 冲正订单(需要证书)</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转短链接
*
* @param url
* 具有native标识的支付URL
* @return 转换后的短链接
* @throws WeixinException
*/
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");
}
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 处理结果
* @since V3
* @throws WeixinException
*/
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
* @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;
}
/**
* 退款查询</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.v3.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail
* @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
*/
@SuppressWarnings("unchecked")
public XmlResult interfaceReport(String interfaceUrl, int executeTime,
String outTradeNo, String ip, Date time, XmlResult returnXml)
throws WeixinException {
String pay_report_uri = getRequestUri("pay_report_uri");
Map<String, String> map = baseMap(null);
map.put("interface_url", interfaceUrl);
map.put("execute_time_", Integer.toString(executeTime));
map.put("out_trade_no", outTradeNo);
map.put("user_ip", ip);
map.put("time", DateUtil.fortmat2yyyyMMddHHmmss(time));
map.putAll((Map<String, String>) JSON.toJSON(returnXml));
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
Response response = request.post(pay_report_uri, param);
return response.getAsXmlResult();
}
/**
* V3接口请求基本数据
*
* @return
*/
private Map<String, String> baseMap(IdQuery idQuery) {
Map<String, String> map = new HashMap<String, String>();
map.put("appid", weixinAccount.getId());
map.put("mch_id", weixinAccount.getMchId());
map.put("nonce_str", RandomUtil.generateString(16));
if (StringUtils.isNotBlank(weixinAccount.getDeviceInfo())) {
map.put("device_info", weixinAccount.getDeviceInfo());
}
if (idQuery != null) {
map.put(idQuery.getType().getName(), idQuery.getId());
}
return map;
}
}

View File

@ -1,64 +1,23 @@
package com.foxinmy.weixin4j.mp.api; package com.foxinmy.weixin4j.mp.api;
import java.io.BufferedReader;
import java.io.File; 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.nio.charset.Charset;
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.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import com.alibaba.fastjson.JSON; 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.exception.WeixinException;
import com.foxinmy.weixin4j.http.JsonResult; import com.foxinmy.weixin4j.http.JsonResult;
import com.foxinmy.weixin4j.http.Response; import com.foxinmy.weixin4j.http.Response;
import com.foxinmy.weixin4j.http.SSLHttpRequest;
import com.foxinmy.weixin4j.http.XmlResult;
import com.foxinmy.weixin4j.model.Token; import com.foxinmy.weixin4j.model.Token;
import com.foxinmy.weixin4j.model.WeixinMpAccount; import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.foxinmy.weixin4j.mp.payment.PayUtil; import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.mp.payment.Refund; import com.foxinmy.weixin4j.mp.payment.v3.ApiResult;
import com.foxinmy.weixin4j.mp.payment.RefundConverter;
import com.foxinmy.weixin4j.mp.payment.RefundResult;
import com.foxinmy.weixin4j.mp.payment.v2.Order;
import com.foxinmy.weixin4j.mp.type.BillType; import com.foxinmy.weixin4j.mp.type.BillType;
import com.foxinmy.weixin4j.mp.type.IdQuery; import com.foxinmy.weixin4j.mp.type.IdQuery;
import com.foxinmy.weixin4j.mp.type.IdType;
import com.foxinmy.weixin4j.mp.type.SignType; import com.foxinmy.weixin4j.mp.type.SignType;
import com.foxinmy.weixin4j.mp.util.ExcelUtil;
import com.foxinmy.weixin4j.token.TokenHolder; import com.foxinmy.weixin4j.token.TokenHolder;
import com.foxinmy.weixin4j.util.ConfigUtil;
import com.foxinmy.weixin4j.util.DateUtil; import com.foxinmy.weixin4j.util.DateUtil;
import com.foxinmy.weixin4j.util.MapUtil;
import com.foxinmy.weixin4j.util.RandomUtil;
/** /**
* 支付API * 支付API
@ -67,11 +26,12 @@ import com.foxinmy.weixin4j.util.RandomUtil;
* @author jy * @author jy
* @date 2014年10月28日 * @date 2014年10月28日
* @since JDK 1.7 * @since JDK 1.7
* @see * @see com.foxinmy.weixin4j.mp.api.Pay2Api
* @see com.foxinmy.weixin4j.mp.api.Pay3Api
*/ */
public class PayApi extends MpApi { public abstract class PayApi extends MpApi {
private final TokenHolder tokenHolder; protected final TokenHolder tokenHolder;
private final WeixinMpAccount weixinAccount; protected final WeixinMpAccount weixinAccount;
public PayApi(TokenHolder tokenHolder) { public PayApi(TokenHolder tokenHolder) {
this.tokenHolder = tokenHolder; this.tokenHolder = tokenHolder;
@ -92,7 +52,6 @@ public class PayApi extends MpApi {
* @param statusMsg * @param statusMsg
* status为失败时携带的信息 * status为失败时携带的信息
* @return 发货处理结果 * @return 发货处理结果
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public JsonResult deliverNotify(String openId, String transid, public JsonResult deliverNotify(String openId, String transid,
@ -116,63 +75,9 @@ public class PayApi extends MpApi {
Response response = request.post( Response response = request.post(
String.format(delivernotify_uri, token.getAccessToken()), String.format(delivernotify_uri, token.getAccessToken()),
JSON.toJSONString(map)); JSON.toJSONString(map));
return response.getAsJsonResult(); return response.getAsJsonResult();
} }
/**
* V2订单查询
*
* @param orderNo
* 订单号
* @return 订单信息
* @see com.foxinmy.weixin4j.mp.payment.v2.Order
* @throws WeixinException
*/
public Order orderQueryV2(String orderNo) throws WeixinException {
String orderquery_uri = getRequestUri("orderquery_uri");
Token token = tokenHolder.getToken();
StringBuilder sb = new StringBuilder();
sb.append("out_trade_no=").append(orderNo);
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());
}
order.setMapData(JSON.parseObject(order_info,
new TypeReference<Map<String, String>>() {
}));
return order;
}
/** /**
* 维权处理 * 维权处理
* *
@ -181,7 +86,6 @@ public class PayApi extends MpApi {
* @param feedbackId * @param feedbackId
* 维权单号 * 维权单号
* @return 维权处理结果 * @return 维权处理结果
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public JsonResult updateFeedback(String openId, String feedbackId) public JsonResult updateFeedback(String openId, String feedbackId)
@ -194,27 +98,17 @@ public class PayApi extends MpApi {
} }
/** /**
* V3订单查询 * 订单查询
* *
* @param idQuery * @param idQuery
* 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级: * 商户系统内部的订单号, transaction_idout_trade_no 选一,如果同时存在优先级:
* transaction_id> out_trade_no * transaction_id> out_trade_no
* @return 订单信息 * @return 订单信息
* @see com.foxinmy.weixin4j.mp.payment.v2.Order
* @see com.foxinmy.weixin4j.mp.payment.v3.Order * @see com.foxinmy.weixin4j.mp.payment.v3.Order
* @throws WeixinException * @throws WeixinException
*/ */
public com.foxinmy.weixin4j.mp.payment.v3.Order orderQueryV3(IdQuery idQuery) public abstract Object orderQuery(IdQuery idQuery) throws WeixinException;
throws WeixinException {
Map<String, String> map = baseMapV3(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 response
.getAsObject(new TypeReference<com.foxinmy.weixin4j.mp.payment.v3.Order>() {
});
}
/** /**
* 申请退款(请求需要双向证书)</br> * 申请退款(请求需要双向证书)</br>
@ -223,8 +117,8 @@ public class PayApi extends MpApi {
* 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br> * 败后重新提交,要采用原来的 out_refund_no总退款金额不能超过用户实际支付金额</br>
* </p> * </p>
* *
* @param ca * @param caFile
* 证书文件 * 证书文件(V2版本后缀为*.pfx,V3版本后缀为*.p12)
* @param idQuery * @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级: * 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no * transaction_id> out_trade_no
@ -236,236 +130,45 @@ public class PayApi extends MpApi {
* 退款总金额,单位为元,可以做部分退款 * 退款总金额,单位为元,可以做部分退款
* @param opUserId * @param opUserId
* 操作员帐号, 默认为商户号 * 操作员帐号, 默认为商户号
* @param opUserPasswd * @param mopara
* <font color="red">V3版本留空,V2版本需传入值</font> * 更多参数 如V2版本的opUserPasswd
* *
* @return 退款申请结果 * @return 退款申请结果
* @see com.foxinmy.weixin4j.mp.payment.RefundResult * @see com.foxinmy.weixin4j.mp.payment.v2.RefundResult
* @since V2 & V3 * @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult
* @throws WeixinException * @throws WeixinException
* @throws IOException
*/ */
public RefundResult refund(InputStream ca, IdQuery idQuery, protected abstract Object refund(File caFile, IdQuery idQuery,
String outRefundNo, double totalFee, double refundFee, String outRefundNo, double totalFee, double refundFee,
String opUserId, String opUserPasswd) throws WeixinException{ String opUserId, Map<String, String> mopara) throws WeixinException;
int version = weixinAccount.getVersion();
String refund_uri = getRequestUri(String.format("refund_v%d_uri",
version));
Response response = null;
if (version == 2) {
Map<String, String> map = new HashMap<String, String>();
map.put("input_charset", Consts.UTF_8.name());
// 版本号
// 填写为 1.0 ,操作员密码为明文
// 填写为 1.1 ,操作员密码为 MD5(密码)
map.put("service_version", "1.1");
map.put("partner", weixinAccount.getPartnerId());
map.put("out_refund_no", outRefundNo);
map.put("total_fee", DateUtil.formaFee2Fen(totalFee));
map.put("refund_fee", DateUtil.formaFee2Fen(refundFee));
map.put(idQuery.getType().getName(), idQuery.getId());
if (StringUtils.isBlank(opUserId)) {
opUserId = weixinAccount.getPartnerId();
}
map.put("op_user_id", opUserId);
map.put("op_user_passwd", DigestUtils.md5Hex(opUserPasswd));
// 以下几个字段可能用不到 记录下来
// [接收人帐号]
// recv_user_id
// 转账退款接收退款的财付通帐号
// 一般无需填写,只有退银行失败,资金转入商 户号现金账号时(即状态为转入代发,查询返 回的 refund_status 7
// 11),填写原退款 单号并填写此字段,资金才会退到指定财付通 账号其他情况此字段忽略
// ---------------------------------------------------------------------------------
// [接收人姓名]
// reccv_user_name
// 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
// ---------------------------------------------------------------------------------
// [通过商户订单号退款]
// use_spbill_no_flag
// 若通过接口 (https://www.tenpay.com/cgi-bin/v1.0/pay _gate.cgi)
// 支付的商户订单号来退款,则取值 1;而通过本文档支付接口的,则无需传值
// ---------------------------------------------------------------------------------
// [退款类型]
// refund_type
// 为空或者填 1:商户号余额退款;2:现金帐号 退款;3:优先商户号退款,若商户号余额不足, 再做现金帐号退款使用 2 3
// ,需联系财 付通开通此功能
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPartnerKey());
map.put("sign", sign.toLowerCase());
SSLContext ctx = null;
try {
File file = new File(ConfigUtil.getValue("ca_file"));
String jksPwd = "";
File jksFile = new File(String.format("%s/tenpay_cacert.jks",
file.getParent()));
KeyStore ks = null;
// create jks ca
if (!jksFile.exists()) {
CertificateFactory cf = CertificateFactory
.getInstance(com.foxinmy.weixin4j.model.Consts.X509);
java.security.cert.Certificate cert = cf
.generateCertificate(PayUtil.class
.getResourceAsStream("cacert.pem"));
ks = KeyStore
.getInstance(com.foxinmy.weixin4j.model.Consts.JKS);
ks.load(null, null);
ks.setCertificateEntry("tenpay", cert);
ks.store(new FileOutputStream(jksFile),
jksPwd.toCharArray());
}
// load jks ca
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(com.foxinmy.weixin4j.model.Consts.SunX509);
ks = KeyStore
.getInstance(com.foxinmy.weixin4j.model.Consts.JKS);
ks.load(new FileInputStream(jksFile), jksPwd.toCharArray());
tmf.init(ks);
// load pfx ca
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(com.foxinmy.weixin4j.model.Consts.SunX509);
ks = KeyStore
.getInstance(com.foxinmy.weixin4j.model.Consts.PKCS12);
ks.load(ca, weixinAccount.getPartnerId().toCharArray());
kmf.init(ks, weixinAccount.getPartnerId().toCharArray());
ctx = SSLContext
.getInstance(com.foxinmy.weixin4j.model.Consts.TLS);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
new SecureRandom());
} catch (Exception e) {
throw new WeixinException(e.getMessage());
}
SSLHttpRequest request = new SSLHttpRequest(ctx);
response = request.get(refund_uri, map);
} else if (version == 3) {
Map<String, String> map = baseMapV3(idQuery);
map.put("out_refund_no", outRefundNo);
map.put("total_fee", DateUtil.formaFee2Fen(totalFee));
map.put("refund_fee", DateUtil.formaFee2Fen(refundFee));
if (StringUtils.isBlank(opUserId)) {
opUserId = weixinAccount.getMchId();
}
map.put("op_user_id", opUserId);
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
SSLHttpRequest request = new SSLHttpRequest(
weixinAccount.getMchId(), ca);
response = request.post(refund_uri, param);
} else {
throw new WeixinException(String.format("unknown version:%d",
version));
}
return response.getAsObject(new TypeReference<RefundResult>() {
});
}
/** /**
* 退款申请:默认采用properties中配置的ca文件</br> <font * 退款查询</br> 退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态
* color="red">V2支付则需要传入opUserPasswd参数</font>
* *
* @see {@link com.foxinmy.weixin4j.mp.api.PayApi#refund(InputStream, IdQuery, String, double, double, String, String)} * @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 RefundResult refund(IdQuery idQuery, String outRefundNo, public abstract Object refundQuery(IdQuery idQuery) throws WeixinException;
double totalFee, double refundFee, String opUserId,
String opUserPasswd) throws WeixinException, IOException {
File ca = new File(ConfigUtil.getValue("ca_file"));
return refund(new FileInputStream(ca), idQuery, outRefundNo, totalFee,
refundFee, opUserId, opUserPasswd);
}
/** /**
* 冲正订单(需要证书)</br> 当支付返回失败,或收银系统超时需要取消交易,可以调用该接口</br> 接口逻辑: * 冲正订单(需要证书)
* 付失败的关单,支付成功的撤销支付</br> <font color="red">7天以内的单可撤销,其他正常支付的单
* 如需实现相同功能请调用退款接口</font></br> <font
* color="red">调用扣款接口后请勿立即调用撤销,需要等待5秒以上先调用查单接口,如果没有确切的返回,再调用撤销</font></br>
* *
* @param ca * @param caFile
* 证书文件 * 证书文件 (V2版本后缀为*.pfx,V3版本后缀为*.p12)
* @param idQuery * @param idQuery
* 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级: * 商户系统内部的订单号, transaction_id out_trade_no 二选一,如果同时存在优先级:
* transaction_id> out_trade_no * transaction_id> out_trade_no
* @return 撤销结果 * @return 撤销结果
* @throws WeixinException * @throws WeixinException
*/ */
public ApiResult reverse(InputStream ca, IdQuery idQuery) public abstract ApiResult reverse(File caFile, IdQuery idQuery)
throws WeixinException { throws WeixinException;
SSLHttpRequest request = new SSLHttpRequest(weixinAccount.getMchId(),
ca);
String reverse_uri = getRequestUri("reverse_uri");
Map<String, String> map = baseMapV3(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>() {
});
}
/**
* 冲正撤销:默认采用properties中配置的ca文件
*
* @param idQuery
* transaction_idout_trade_no 二选一
* @return 撤销结果
* @see {@link com.foxinmy.weixin4j.mp.api.PayApi#reverse(InputStream, IdQuery)}
* @throws WeixinException
* @throws IOException
*/
public ApiResult reverse(IdQuery idQuery) throws WeixinException,
IOException {
File ca = new File(ConfigUtil.getValue("ca_file"));
return reverse(new FileInputStream(ca), idQuery);
}
/**
* native支付URL转短链接
*
* @param url
* 具有native标识的支付URL
* @return 转换后的短链接
* @throws WeixinException
*/
public String getShorturl(String url) throws WeixinException {
Map<String, String> map = baseMapV3(null);
map.put("long_url", url);
String sign = PayUtil.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
try {
map.put("long_url", URLEncoder.encode(url, Consts.UTF_8.name()));
} catch (UnsupportedEncodingException ignore) {
;
}
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");
}
/**
* 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
*
* @param outTradeNo
* 商户系统内部的订单号
* @return 处理结果
* @since V3
* @throws WeixinException
*/
public XmlResult closeOrder(String outTradeNo) throws WeixinException {
Map<String, String> map = baseMapV3(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.getAsXmlResult();
}
/** /**
* 下载对账单<br> * 下载对账单<br>
@ -480,154 +183,30 @@ public class PayApi extends MpApi {
* 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单 * 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单 * REFUND,返回当日退款订单
* @return excel表格 * @return excel表格
* @since V2 & V3
* @throws WeixinException
* @throws IOException
*/
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 _billDate = DateUtil.fortmat2yyyyMMdd(billDate);
String bill_path = ConfigUtil.getValue("bill_path");
String fileName = String.format("%s_%s_%s.xls", _billDate, billType
.name().toLowerCase(), weixinAccount.getId());
File file = new File(String.format("%s/%s", bill_path, fileName));
if (file.exists()) {
return file;
}
int version = weixinAccount.getVersion();
String downloadbill_uri = getRequestUri(String.format(
"downloadbill_v%d_uri", version));
Response response = null;
Charset charset = Consts.UTF_8;
if (version == 2) {
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 = request.get(downloadbill_uri, map);
charset = Charset.forName("GBK");
} else if (version == 3) {
Map<String, String> map = baseMapV3(null);
map.put("bill_date", _billDate);
map.put("bill_type", billType.name());
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
response = request.post(downloadbill_uri, param);
} else {
throw new WeixinException(String.format("unknown version:%d",
version));
}
BufferedReader reader = null;
OutputStream os = null;
try {
reader = new BufferedReader(new InputStreamReader(
response.getStream(), charset));
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(_billDate + "对账单");
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.Refund
* @since V2 & V3
* @throws WeixinException * @throws WeixinException
*/ */
public Refund refundQuery(IdQuery idQuery) throws WeixinException { public abstract File downloadbill(Date billDate, BillType billType)
int version = weixinAccount.getVersion(); throws WeixinException;
String refundquery_uri = getRequestUri(String.format(
"refundquery_v%d_uri", version));
Response response = null;
if (version == 2) {
Map<String, String> map = new HashMap<String, String>();
map.put("input_charset", Consts.UTF_8.name());
map.put("partner", weixinAccount.getPartnerId());
map.put(idQuery.getType().getName(), idQuery.getId());
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPartnerKey());
map.put("sign", sign.toLowerCase());
response = request.get(refundquery_uri, map);
} else if (version == 3) {
Map<String, String> map = baseMapV3(idQuery);
String sign = PayUtil
.paysignMd5(map, weixinAccount.getPaySignKey());
map.put("sign", sign);
String param = map2xml(map);
response = request.post(refundquery_uri, param);
} else {
throw new WeixinException(String.format("unknown version:%d",
version));
}
return RefundConverter.fromXML(response.getAsString());
}
/** /**
* V3接口请求基本数据 * 关闭订单</br> 当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完
* 成支付请按正常支付处理如果出现银行掉单,调用关单成功后,微信后台会主动发起退款
* *
* @return * @param outTradeNo
* 商户系统内部的订单号
* @return 处理结果
* @throws WeixinException
*/ */
private Map<String, String> baseMapV3(IdQuery idQuery) { public abstract ApiResult closeOrder(String outTradeNo)
Map<String, String> map = new HashMap<String, String>(); throws WeixinException;
map.put("appid", weixinAccount.getId());
map.put("mch_id", weixinAccount.getMchId()); /**
map.put("nonce_str", RandomUtil.generateString(16)); * native支付URL转短链接
if (StringUtils.isNotBlank(weixinAccount.getDeviceInfo())) { *
map.put("device_info", weixinAccount.getDeviceInfo()); * @param url
} * 具有native标识的支付URL
if (idQuery != null) { * @return 转换后的短链接
map.put(idQuery.getType().getName(), idQuery.getId()); * @throws WeixinException
} */
return map; public abstract String getShorturl(String url) throws WeixinException;
}
} }

View File

@ -121,6 +121,8 @@ refund_v3_uri={mch_base_url}/secapi/pay/refund
reverse_uri={mch_base_url}/secapi/pay/reverse reverse_uri={mch_base_url}/secapi/pay/reverse
# \u88ab\u626b\u652f\u4ed8 # \u88ab\u626b\u652f\u4ed8
micropay_uri={mch_base_url}/pay/micropay 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 # \u7edf\u4e00\u8ba2\u5355\u751f\u6210

View File

@ -51,7 +51,7 @@ public class CustomRecord implements Serializable {
} }
public void setTime(long time) { public void setTime(long time) {
this.time = new Date(time * 1000); this.time = new Date(time * 1000l);
} }
public String getText() { public String getText() {

View File

@ -1,9 +1,11 @@
package com.foxinmy.weixin4j.mp.model; package com.foxinmy.weixin4j.mp.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.model.Gender; import com.foxinmy.weixin4j.model.Gender;
import com.foxinmy.weixin4j.mp.type.FaceSize; import com.foxinmy.weixin4j.mp.type.FaceSize;
import com.foxinmy.weixin4j.mp.type.Lang; import com.foxinmy.weixin4j.mp.type.Lang;
@ -24,22 +26,20 @@ public class User implements Serializable {
private String openid; // 用户的唯一标识 private String openid; // 用户的唯一标识
private String nickname; // 用户昵称 private String nickname; // 用户昵称
private int sex; // 用户的性别值为1时是男性值为2时是女性值为0时是未知 @JSONField(name = "sex")
private Gender gender; // 用户的性别值为1时是男性值为2时是女性值为0时是未知
private String province; // 用户个人资料填写的省份 private String province; // 用户个人资料填写的省份
private String city; // 普通用户个人资料填写的城市 private String city; // 普通用户个人资料填写的城市
private String country; // 国家如中国为CN private String country; // 国家如中国为CN
private String headimgurl; // 用户头像最后一个数值代表正方形头像大小有0466496132数值可选0代表640*640正方形头像用户没有头像时该项为空 private String headimgurl; // 用户头像最后一个数值代表正方形头像大小有0466496132数值可选0代表640*640正方形头像用户没有头像时该项为空
private String privilege; // 用户特权信息json 数组如微信沃卡用户为chinaunicom private String privilege; // 用户特权信息json 数组如微信沃卡用户为chinaunicom
private int subscribe; // 是否关注 @JSONField(name = "subscribe")
private long subscribe_time; // 关注时间 private boolean isSubscribe; // 用户是否订阅该公众号标识值为0时代表此用户没有关注该公众号拉取不到其余信息
@JSONField(name = "subscribe_time")
private Date subscribeTime; // 关注时间
private Lang language; // 使用语言 private Lang language; // 使用语言
private String unionid; // 只有在用户将公众号绑定到微信开放平台帐号后才会出现该字段 private String unionid; // 只有在用户将公众号绑定到微信开放平台帐号后才会出现该字段
public User() {
this.sex = 0;
this.language = Lang.zh_CN;
}
public String getOpenid() { public String getOpenid() {
return openid; return openid;
} }
@ -56,22 +56,18 @@ public class User implements Serializable {
this.nickname = nickname; this.nickname = nickname;
} }
public int getSex() {
return sex;
}
public Gender getGender() { public Gender getGender() {
if (sex == 1) { return gender;
return Gender.male;
} else if (sex == 2) {
return Gender.female;
} else {
return Gender.unknown;
}
} }
public void setSex(int sex) { public void setGender(int sex) {
this.sex = sex; if (sex == 1) {
this.gender = Gender.male;
} else if (sex == 2) {
this.gender = Gender.female;
} else {
this.gender = Gender.unknown;
}
} }
public String getProvince() { public String getProvince() {
@ -123,14 +119,6 @@ public class User implements Serializable {
this.privilege = privilege; this.privilege = privilege;
} }
public int getSubscribe() {
return subscribe;
}
public void setSubscribe(int subscribe) {
this.subscribe = subscribe;
}
public Lang getLanguage() { public Lang getLanguage() {
return language; return language;
} }
@ -139,12 +127,20 @@ public class User implements Serializable {
this.language = language; this.language = language;
} }
public long getSubscribe_time() { public boolean isSubscribe() {
return subscribe_time; return isSubscribe;
} }
public void setSubscribe_time(long subscribe_time) { public void setSubscribe(boolean isSubscribe) {
this.subscribe_time = subscribe_time; this.isSubscribe = isSubscribe;
}
public Date getSubscribeTime() {
return (Date) subscribeTime.clone();
}
public void setSubscribeTime(long subscribeTime) {
this.subscribeTime = new Date(subscribeTime * 1000l);
} }
public String getUnionid() { public String getUnionid() {
@ -163,46 +159,21 @@ public class User implements Serializable {
return false; return false;
} }
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((city == null) ? 0 : city.hashCode());
result = prime * result + ((country == null) ? 0 : country.hashCode());
result = prime * result
+ ((headimgurl == null) ? 0 : headimgurl.hashCode());
result = prime * result
+ ((language == null) ? 0 : language.hashCode());
result = prime * result
+ ((nickname == null) ? 0 : nickname.hashCode());
result = prime * result + ((openid == null) ? 0 : openid.hashCode());
result = prime * result
+ ((privilege == null) ? 0 : privilege.hashCode());
result = prime * result
+ ((province == null) ? 0 : province.hashCode());
result = prime * result + sex;
result = prime * result + subscribe;
result = prime * result
+ (int) (subscribe_time ^ (subscribe_time >>> 32));
result = prime * result + ((unionid == null) ? 0 : unionid.hashCode());
return result;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("[User openid=").append(openid); sb.append("[User openid=").append(openid);
sb.append(", nickname=").append(nickname); sb.append(", nickname=").append(nickname);
sb.append(", sex=").append(sex); sb.append(", gender=").append(gender);
sb.append(", province=").append(province); sb.append(", province=").append(province);
sb.append(", city=").append(city); sb.append(", city=").append(city);
sb.append(", country=").append(country); sb.append(", country=").append(country);
sb.append(", headimgurl=").append(headimgurl); sb.append(", headimgurl=").append(headimgurl);
sb.append(", privilege=").append(privilege); sb.append(", privilege=").append(privilege);
sb.append(", language=").append(language); sb.append(", language=").append(language);
sb.append(", subscribe_time=").append(subscribe_time); sb.append(", subscribeTime=").append(subscribeTime);
sb.append(", unionid=").append(unionid); sb.append(", unionid=").append(unionid);
sb.append(", subscribe=").append(subscribe).append("]"); sb.append(", isSubscribe=").append(isSubscribe).append("]");
return sb.toString(); return sb.toString();
} }
} }

View File

@ -45,6 +45,7 @@ public class PayUtil {
* 订单信息 * 订单信息
* @param WeixinMpAccount * @param WeixinMpAccount
* 商户信息 * 商户信息
* @since V2 & V3
* @return 支付json串 * @return 支付json串
* @throws PayException * @throws PayException
*/ */
@ -56,8 +57,9 @@ public class PayUtil {
} else if (payPackage instanceof PayPackageV3) { } else if (payPackage instanceof PayPackageV3) {
return createPayJsRequestJsonV3((PayPackageV3) payPackage, return createPayJsRequestJsonV3((PayPackageV3) payPackage,
weixinAccount); weixinAccount);
} else {
throw new PayException("unknown pay");
} }
throw new PayException("unknown pay");
} }
/** /**

View File

@ -1,67 +0,0 @@
package com.foxinmy.weixin4j.mp.payment;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 退款记录
*
* @className Refund
* @author jy
* @date 2014年11月1日
* @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.RefundDetail
*/
@XStreamAlias("xml")
public class Refund extends ApiResult {
private static final long serialVersionUID = -2971132874939642721L;
@XStreamAlias("transaction_id")
private String transactionId;// 微信订单号
@XStreamAlias("out_trade_no")
private String orderNo;// 商户订单号
@XStreamAlias("sub_mch_id")
private String subMchId; //
@XStreamAlias("refund_count")
private int count;// 退款笔数
private String partner; // 商户号V2
private List<RefundDetail> details;
public String getTransactionId() {
return transactionId;
}
public String getOrderNo() {
return orderNo;
}
public String getSubMchId() {
return subMchId;
}
public int getCount() {
return count;
}
public String getPartner() {
return partner;
}
public List<RefundDetail> getDetails() {
return details;
}
public void setDetails(List<RefundDetail> details) {
this.details = details;
}
@Override
public String toString() {
return "Refund [transactionId=" + transactionId + ", orderNo="
+ orderNo + ", subMchId=" + subMchId + ", count=" + count
+ ", partner=" + partner + ", details=" + details
+ ", " + super.toString() + "]";
}
}

View File

@ -26,34 +26,39 @@ import com.thoughtworks.xstream.mapper.Mapper;
* @author jy * @author jy
* @date 2014年11月2日 * @date 2014年11月2日
* @since JDK 1.7 * @since JDK 1.7
* @see com.foxinmy.weixin4j.mp.payment.Refund * @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord
* @see com.foxinmy.weixin4j.mp.payment.RefundDetail * @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 { public class RefundConverter {
private final static XmlStream xStream = XmlStream.get(); private final static XmlStream xStream = XmlStream.get();
private final static Mapper mapper; private final static Mapper mapper;
private final static ReflectionProvider reflectionProvider; private final static ReflectionProvider reflectionProvider;
private final static Pattern pattern = Pattern.compile("(_\\d)$"); private final static Pattern pattern = Pattern.compile("(_\\d)$");
private static Class<?> clazz;
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 { static {
xStream.processAnnotations(Refund.class); xStream.processAnnotations(new Class[] { REFUNDRECORD2, REFUNDRECORD3,
xStream.processAnnotations(RefundDetail.class); com.foxinmy.weixin4j.mp.payment.v2.RefundDetail.class,
com.foxinmy.weixin4j.mp.payment.v3.RefundDetail.class });
xStream.aliasField("refund_state", com.foxinmy.weixin4j.mp.payment.v2.RefundDetail.class, "refundStatus");
xStream.registerConverter(new $()); xStream.registerConverter(new $());
mapper = xStream.getMapper(); mapper = xStream.getMapper();
reflectionProvider = xStream.getReflectionProvider(); reflectionProvider = xStream.getReflectionProvider();
} }
public static String toXML(Refund refund) { public static <T> T fromXML(String xml, Class<T> clazz) {
return xStream.toXML(refund); RefundConverter.clazz = clazz;
} return xStream.fromXML(xml, clazz);
public static Refund fromXML(String xml) {
return xStream.fromXML(xml, Refund.class);
} }
private static class $ implements Converter { private static class $ implements Converter {
@Override @Override
public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) { public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) {
return clazz.equals(Refund.class); return clazz.equals(REFUNDRECORD2) || clazz.equals(REFUNDRECORD3);
} }
@Override @Override
@ -63,18 +68,24 @@ public class RefundConverter {
writer, context); writer, context);
} }
@SuppressWarnings("unchecked")
@Override @Override
public Object unmarshal(HierarchicalStreamReader reader, public Object unmarshal(HierarchicalStreamReader reader,
UnmarshallingContext context) { UnmarshallingContext context) {
Refund refund = new Refund(); Object refund = null;
try {
refund = clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Matcher matcher = null; Matcher matcher = null;
Map<String, Map<String, String>> outMap = new HashMap<String, Map<String, String>>(); Map<String, Map<String, String>> outMap = new HashMap<String, Map<String, String>>();
while (reader.hasMoreChildren()) { while (reader.hasMoreChildren()) {
reader.moveDown(); reader.moveDown();
String nodeName = reader.getNodeName(); String nodeName = reader.getNodeName();
String fieldName = mapper.realMember(Refund.class, nodeName); String fieldName = mapper.realMember(clazz, nodeName);
Field field = reflectionProvider.getFieldOrNull(Refund.class, Field field = reflectionProvider.getFieldOrNull(clazz,
fieldName); fieldName);
if (field != null) { if (field != null) {
Object value = context.convertAnother(refund, Object value = context.convertAnother(refund,
@ -94,7 +105,8 @@ public class RefundConverter {
} }
StringBuilder detailXml = new StringBuilder(); StringBuilder detailXml = new StringBuilder();
detailXml.append("<list>"); detailXml.append("<list>");
String detailCanonicalName = RefundDetail.class.getCanonicalName(); String detailCanonicalName = clazz.getCanonicalName().replaceFirst(
"RefundRecord", "RefundDetail");
for (Iterator<Entry<String, Map<String, String>>> outIt = outMap for (Iterator<Entry<String, Map<String, String>>> outIt = outMap
.entrySet().iterator(); outIt.hasNext();) { .entrySet().iterator(); outIt.hasNext();) {
detailXml.append("<").append(detailCanonicalName).append(">"); detailXml.append("<").append(detailCanonicalName).append(">");
@ -108,7 +120,9 @@ public class RefundConverter {
detailXml.append("</").append(detailCanonicalName).append(">"); detailXml.append("</").append(detailCanonicalName).append(">");
} }
detailXml.append("</list>"); detailXml.append("</list>");
refund.setDetails(xStream.fromXML(detailXml.toString(), List.class)); reflectionProvider.writeField(refund, "details",
xStream.fromXML(detailXml.toString(), List.class),
List.class.getDeclaringClass());
return refund; return refund;
} }
} }

View File

@ -1,127 +0,0 @@
package com.foxinmy.weixin4j.mp.payment;
import java.io.Serializable;
import org.apache.commons.lang3.StringUtils;
import com.foxinmy.weixin4j.mp.type.RefundChannel;
import com.foxinmy.weixin4j.mp.type.RefundStatus;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 退款详细
*
* @className RefundDetail
* @author jy
* @date 2014年11月2日
* @since JDK 1.7
* @see
*/
public class RefundDetail implements Serializable {
private static final long serialVersionUID = 2828640496307351988L;
@XStreamAlias("out_refund_no")
private String outRefundNo; // 商户退款单号
@XStreamAlias("refund_id")
private String refundId; // 微信退款单号
@XStreamAlias("refund_channel")
private String refundChannel; // 退款渠道 ORIGINAL原路退款 BALANCE退回到余额
@XStreamAlias("refund_fee")
private int refundFee; // 退款总金额,单位为分,可以做部分退款
@XStreamAlias("coupon_refund_fee")
private int couponRefundFee; // 现金券退款金额<=退款金额,退款金额-现金券退款金额为现金
@XStreamAlias("refund_status")
private String refundStatus; // 退款状态
@XStreamAlias("recv_user_id")
private String recvUserId;// 转账退款接收退款的财付通帐号
@XStreamAlias("reccv_user_name")
private String reccvUserName;// 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
@XStreamAlias("sign_key_index")
private String signKeyIndex;// 多密钥支持的密钥序号,默认 1
@XStreamAlias("sign_type")
private String signType;// 签名类型,取值:MD5RSA,默认:MD5
public String getOutRefundNo() {
return outRefundNo;
}
public String getRefundId() {
return refundId;
}
public String getRefundChannel() {
if (StringUtils.isBlank(refundChannel)) {
return RefundChannel.BALANCE.name();
}
// V2
if (refundChannel.equals("0")) {
return RefundChannel.TENPAY.name();
} else if (refundChannel.equals("1")) {
return RefundChannel.BALANCE.name();
}
return refundChannel;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
public double getRefundFee() {
return refundFee / 100d;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
public double getCouponRefundFee() {
return couponRefundFee / 100d;
}
public RefundStatus getRefundStatus() {
// V2
if ("4,10,".contains(refundStatus + ",")) {
return RefundStatus.SUCCES;
} else if ("3,5,6,".contains(refundStatus + ",")) {
return RefundStatus.FAIL;
} else if ("8,9,10,".contains(refundStatus + ",")) {
return RefundStatus.PROCESSING;
} else if ("1,2,".contains(refundStatus + ",")) {
return RefundStatus.NOTSURE;
} else if ("7,".contains(refundStatus + ",")) {
return RefundStatus.CHANGE;
} else {
return RefundStatus.valueOf(refundStatus);
}
}
public String getRecvUserId() {
return recvUserId;
}
public String getReccvUserName() {
return reccvUserName;
}
public String getSignKeyIndex() {
return signKeyIndex;
}
public String getSignType() {
return signType;
}
@Override
public String toString() {
return "RefundDetail [outRefundNo=" + outRefundNo + ", refundId="
+ refundId + ", refundChannel=" + refundChannel
+ ", refundFee=" + refundFee + ", couponRefundFee="
+ couponRefundFee + ", refundStatus=" + refundStatus
+ ", recvUserId=" + recvUserId + ", reccvUserName="
+ reccvUserName + ", signKeyIndex=" + signKeyIndex
+ ", signType=" + signType + "]";
}
}

View File

@ -1,118 +0,0 @@
package com.foxinmy.weixin4j.mp.payment;
import org.apache.commons.lang3.StringUtils;
import com.foxinmy.weixin4j.mp.type.RefundChannel;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* 退款申请结果
*
* @className RefundResult
* @author jy
* @date 2014年11月6日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml")
public class RefundResult extends ApiResult {
private static final long serialVersionUID = -3687863914168618620L;
// 微信订单号
@XStreamAlias("transaction_id")
private String transactionId;
// 商户系统内部的订单号
@XStreamAlias("out_trade_no")
private String outTradeNo;
// 商户退款单号
@XStreamAlias("out_refund_no")
private String outRefundNo;
// 微信退款单号
@XStreamAlias("refund_id")
private String refundId;
// 退款渠道 ORIGINAL原路退款,默认 BALANCE退回到余额
@XStreamAlias("refund_channel")
private String refundChannel;
// 退款总金额,单位为元,可以做部分退款
@XStreamAlias("refund_fee")
private int refundFee;
// 现金券退款金额<=退款金 ,退款金额-现金券退款金 额为现金
@XStreamAlias("coupon_refund_fee")
private int couponRefundFee;
@XStreamAlias("recv_user_id")
private String recvUserId;// 转账退款接收退款的财付通帐号
@XStreamAlias("reccv_user_name")
private String reccvUserName;// 转账退款接收退款的姓名(需与接收退款的财 付通帐号绑定的姓名一致)
@XStreamAlias("sign_key_index")
private String signKeyIndex;// 多密钥支持的密钥序号,默认 1
@XStreamAlias("sign_type")
private String signType;// 签名类型,取值:MD5RSA,默认:MD5
public String getTransactionId() {
return transactionId;
}
public String getOutTradeNo() {
return outTradeNo;
}
public String getOutRefundNo() {
return outRefundNo;
}
public String getRefundId() {
return refundId;
}
public String getRefundChannel() {
if (StringUtils.isBlank(refundChannel)) {
return RefundChannel.BALANCE.name();
}
// V2
if (refundChannel.equals("0")) {
return RefundChannel.TENPAY.name();
} else if (refundChannel.equals("1")) {
return RefundChannel.BALANCE.name();
}
return refundChannel;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
public double getRefundFee() {
return refundFee / 100d;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
public double getCouponRefundFee() {
return couponRefundFee / 100d;
}
public String getSignKeyIndex() {
return signKeyIndex;
}
public String getSignType() {
return signType;
}
@Override
public String toString() {
return "RefundResult [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", outRefundNo=" + outRefundNo + ", refundId="
+ refundId + ", refundChannel=" + refundChannel
+ ", refundFee=" + refundFee + ", couponRefundFee="
+ couponRefundFee + ", recvUserId=" + recvUserId
+ ", reccvUserName=" + reccvUserName + ", signKeyIndex="
+ signKeyIndex + ", signType=" + signType + ", "
+ super.toString() + "]";
}
}

View File

@ -0,0 +1,107 @@
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;
@XStreamAlias("sign_key_index")
@JSONField(name = "sign_key_index")
private Integer signKeyIndex; // 多密钥支持的密钥序号,默认 1
private String sign;// 签名
@JSONField(name = "sign_type")
@XStreamAlias("sign_type")
private SignType signType; // 签名类型,取值:MD5RSA
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

@ -10,7 +10,7 @@ import com.foxinmy.weixin4j.mp.payment.PayRequest;
import com.foxinmy.weixin4j.util.MapUtil; import com.foxinmy.weixin4j.util.MapUtil;
/** /**
* 微信JS支付:get_brand_wcpay_request</br> * V2微信JS支付:get_brand_wcpay_request</br>
* <font color="red">所列参数均为非空字符串</font> * <font color="red">所列参数均为非空字符串</font>
* <p> * <p>
* get_brand_wcpay_request:ok 支付成功<br> * get_brand_wcpay_request:ok 支付成功<br>

View File

@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.mp.payment.JsPayNotify;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
* Native支付回调时POST的信息 * V2 Native支付回调时POST的信息
* *
* @className PayNativeNotifyV2 * @className PayNativeNotifyV2
* @author jy * @author jy

View File

@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.model.WeixinMpAccount;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
* Native支付时的回调响应 * V2 Native支付时的回调响应
* *
* @className NativePayResponseV2 * @className NativePayResponseV2
* @author jy * @author jy

View File

@ -1,14 +1,14 @@
package com.foxinmy.weixin4j.mp.payment.v2; package com.foxinmy.weixin4j.mp.payment.v2;
import java.util.Date; import java.util.Date;
import java.util.Map;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.http.JsonResult; import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.TradeState;
import com.foxinmy.weixin4j.util.DateUtil; import com.foxinmy.weixin4j.util.DateUtil;
/** /**
* 订单信息 * V2订单信息
* *
* @className Order * @className Order
* @author jy * @author jy
@ -16,27 +16,16 @@ import com.foxinmy.weixin4j.util.DateUtil;
* @since JDK 1.7 * @since JDK 1.7
* @see * @see
*/ */
public class Order extends JsonResult { public class Order extends ApiResult {
private static final long serialVersionUID = 4543552984506609920L; private static final long serialVersionUID = 4543552984506609920L;
// 是查询结果状态码,0 表明成功,其他表明错误;
@JSONField(name = "ret_code")
private int retCode;
// 是查询结果出错信息;
@JSONField(name = "ret_msg")
private String retMsg;
// 是返回信息中的编码方式;
@JSONField(name = "input_charset")
private String inputCharset;
// 是订单状态,0 为成功,其他为失败; // 是订单状态,0 为成功,其他为失败;
@JSONField(name = "trade_state") @JSONField(name = "trade_state")
private int tradeState; private int tradeState;
// 是交易模式,1 为即时到帐,其他保留; // 是交易模式,1 为即时到帐,其他保留;
@JSONField(name = "trade_mode") @JSONField(name = "trade_mode")
private int tradeMode; private int tradeMode;
// 是财付通商户号,即前文的 partnerid;
private String partner;
// 是银行类型; // 是银行类型;
@JSONField(name = "bank_type") @JSONField(name = "bank_type")
private String bankType; private String bankType;
@ -48,7 +37,7 @@ public class Order extends JsonResult {
private int totalFee; private int totalFee;
// 是币种,1 为人民币; // 是币种,1 为人民币;
@JSONField(name = "fee_type") @JSONField(name = "fee_type")
private String feeType; private int feeType;
// 是财付通订单号; // 是财付通订单号;
@JSONField(name = "transaction_id") @JSONField(name = "transaction_id")
private String transactionId; private String transactionId;
@ -76,39 +65,20 @@ public class Order extends JsonResult {
private int discount; private int discount;
// 换算成人民币之后的总金额,单位为分,一般看 total_fee 即可 // 换算成人民币之后的总金额,单位为分,一般看 total_fee 即可
@JSONField(name = "rmb_total_fee") @JSONField(name = "rmb_total_fee")
private int rmbTotalFee; private Integer rmbTotalFee;
@JSONField(serialize = false)
private Map<String, String> mapData;
public int getRetCode() {
return retCode;
}
public void setRetCode(int retCode) {
this.retCode = retCode;
}
public String getRetMsg() {
return retMsg;
}
public void setRetMsg(String retMsg) {
this.retMsg = retMsg;
}
public String getInputCharset() {
return inputCharset;
}
public void setInputCharset(String inputCharset) {
this.inputCharset = inputCharset;
}
public int getTradeState() { public int getTradeState() {
return tradeState; return tradeState;
} }
@JSONField(serialize = false, deserialize = false)
public TradeState getFormatTradeState() {
if (tradeState == 0) {
return TradeState.SUCCESS;
}
return null;
}
public void setTradeState(int tradeState) { public void setTradeState(int tradeState) {
this.tradeState = tradeState; this.tradeState = tradeState;
} }
@ -121,14 +91,6 @@ public class Order extends JsonResult {
this.tradeMode = tradeMode; this.tradeMode = tradeMode;
} }
public String getPartner() {
return partner;
}
public void setPartner(String partner) {
this.partner = partner;
}
public String getBankType() { public String getBankType() {
return bankType; return bankType;
} }
@ -145,12 +107,17 @@ public class Order extends JsonResult {
this.bankBillno = bankBillno; this.bankBillno = bankBillno;
} }
public int getTotalFee() {
return totalFee;
}
/** /**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font> * <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
* *
* @return 元单位 * @return 元单位
*/ */
public double getTotalFee() { @JSONField(serialize = false, deserialize = false)
public double getFormatTotalFee() {
return totalFee / 100d; return totalFee / 100d;
} }
@ -158,11 +125,19 @@ public class Order extends JsonResult {
this.totalFee = totalFee; this.totalFee = totalFee;
} }
public String getFeeType() { public int getFeeType() {
return feeType; return feeType;
} }
public void setFeeType(String 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; this.feeType = feeType;
} }
@ -206,7 +181,12 @@ public class Order extends JsonResult {
this.attach = attach; this.attach = attach;
} }
public Date getTimeEnd() { public String getTimeEnd() {
return timeEnd;
}
@JSONField(serialize = false, deserialize = false)
public Date getFormatTimeEnd() {
return DateUtil.parse2yyyyMMddHHmmss(timeEnd); return DateUtil.parse2yyyyMMddHHmmss(timeEnd);
} }
@ -214,12 +194,17 @@ public class Order extends JsonResult {
this.timeEnd = timeEnd; this.timeEnd = timeEnd;
} }
public int getTransportFee() {
return transportFee;
}
/** /**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font> * <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
* *
* @return 元单位 * @return 元单位
*/ */
public double getTransportFee() { @JSONField(serialize = false, deserialize = false)
public double getFormatTransportFee() {
return transportFee / 100d; return transportFee / 100d;
} }
@ -227,12 +212,17 @@ public class Order extends JsonResult {
this.transportFee = transportFee; this.transportFee = transportFee;
} }
public int getProductFee() {
return productFee;
}
/** /**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font> * <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
* *
* @return 元单位 * @return 元单位
*/ */
public double getProductFee() { @JSONField(serialize = false, deserialize = false)
public double getFormatProductFee() {
return productFee / 100d; return productFee / 100d;
} }
@ -240,12 +230,17 @@ public class Order extends JsonResult {
this.productFee = productFee; this.productFee = productFee;
} }
public int getDiscount() {
return discount;
}
/** /**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font> * <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
* *
* @return 元单位 * @return 元单位
*/ */
public double getDiscount() { @JSONField(serialize = false, deserialize = false)
public double getFormatDiscount() {
return discount / 100d; return discount / 100d;
} }
@ -253,39 +248,42 @@ public class Order extends JsonResult {
this.discount = discount; this.discount = discount;
} }
public Integer getRmbTotalFee() {
return rmbTotalFee;
}
/** /**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font> * <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
* *
* @return 元单位 * @return 元单位
*/ */
public double getRmbTotalFee() { @JSONField(serialize = false, deserialize = false)
return rmbTotalFee / 100d; public double getFormatRmbTotalFee() {
return rmbTotalFee != null ? rmbTotalFee / 100d : 0d;
} }
public void setRmbTotalFee(int rmbTotalFee) { public void setRmbTotalFee(int rmbTotalFee) {
this.rmbTotalFee = rmbTotalFee; this.rmbTotalFee = rmbTotalFee;
} }
public Map<String, String> getMapData() {
return mapData;
}
public void setMapData(Map<String, String> mapData) {
this.mapData = mapData;
}
@Override @Override
public String toString() { public String toString() {
return "Order [retCode=" + retCode + ", retMsg=" + retMsg return "Order [tradeState=" + tradeState + ", tradeMode=" + tradeMode
+ ", inputCharset=" + inputCharset + ", tradeState=" + ", bankType=" + bankType + ", bankBillno=" + bankBillno
+ tradeState + ", tradeMode=" + tradeMode + ", partner=" + ", totalFee=" + totalFee + ", feeType=" + feeType
+ partner + ", bankType=" + bankType + ", bankBillno=" + ", transactionId=" + transactionId + ", outTradeNo="
+ bankBillno + ", totalFee=" + totalFee + ", feeType=" + outTradeNo + ", isSplit=" + isSplit + ", isRefund="
+ feeType + ", transactionId=" + transactionId + isRefund + ", attach=" + attach + ", timeEnd=" + timeEnd
+ ", outTradeNo=" + outTradeNo + ", isSplit=" + isSplit + ", transportFee=" + transportFee + ", productFee="
+ ", isRefund=" + isRefund + ", attach=" + attach + productFee + ", discount=" + discount + ", rmbTotalFee="
+ ", timeEnd=" + timeEnd + ", transportFee=" + transportFee + rmbTotalFee + ", getFormatTradeState()="
+ ", productFee=" + productFee + ", discount=" + discount + getFormatTradeState() + ", getFormatTotalFee()="
+ ", rmbTotalFee=" + rmbTotalFee + ", mapData=" + mapData + "]"; + getFormatTotalFee() + ", getFormatFeeType()="
+ getFormatFeeType() + ", getFormatTimeEnd()="
+ getFormatTimeEnd() + ", getFormatTransportFee()="
+ getFormatTransportFee() + ", getFormatProductFee()="
+ getFormatProductFee() + ", getFormatDiscount()="
+ getFormatDiscount() + ", getFormatRmbTotalFee()="
+ getFormatRmbTotalFee() + ", " + super.toString() + "]";
} }
} }

View File

@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.mp.payment.PayBaseInfo;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
* 维权POST的数据 * V2维权POST的数据
* *
* @className PayFeedback * @className PayFeedback
* @author jy * @author jy

View File

@ -3,6 +3,14 @@ package com.foxinmy.weixin4j.mp.payment.v2;
import com.foxinmy.weixin4j.mp.payment.PayBaseInfo; import com.foxinmy.weixin4j.mp.payment.PayBaseInfo;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* V2告警通知
* @className PayWarn
* @author jy
* @date 2014年12月31日
* @since JDK 1.7
* @see
*/
@XStreamAlias("xml") @XStreamAlias("xml")
public class PayWarn extends PayBaseInfo { public class PayWarn extends PayBaseInfo {

View File

@ -0,0 +1,123 @@
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; // 微信退款单号
@XStreamAlias("refund_channel")
@JSONField(name = "refund_channel")
private int refundChannel; // 退款渠道 0:退到财付通1:退到银行;
@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 + ", getFormatRefundChannel()="
+ getFormatRefundChannel() + ", getFormatRefundFee()="
+ getFormatRefundFee() + ", getFormatRefundStatus()="
+ getFormatRefundStatus();
}
}

View File

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

@ -1,5 +1,8 @@
package com.foxinmy.weixin4j.mp.payment; 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.foxinmy.weixin4j.http.XmlResult;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
@ -17,15 +20,20 @@ public class ApiResult extends XmlResult {
private static final long serialVersionUID = -8430005768959715444L; private static final long serialVersionUID = -8430005768959715444L;
@XStreamAlias("appid") @XStreamAlias("appid")
@JSONField(name = "appid")
private String appId;// 微信分配的公众账号 ID商户号 非空 private String appId;// 微信分配的公众账号 ID商户号 非空
@XStreamAlias("mch_id") @XStreamAlias("mch_id")
@JSONField(name = "mch_id")
private String mchId;// 微信支付分配的商户号 非空 private String mchId;// 微信支付分配的商户号 非空
@XStreamAlias("sub_mch_id") @XStreamAlias("sub_mch_id")
@JSONField(name = "sub_mch_id")
private String subMchId; // 未知 可能为空 private String subMchId; // 未知 可能为空
@XStreamAlias("nonce_str") @XStreamAlias("nonce_str")
@JSONField(name = "nonce_str")
private String nonceStr;// 随机字符串 非空 private String nonceStr;// 随机字符串 非空
private String sign;// 签名 非空 private String sign;// 签名 非空
@XStreamAlias("device_info") @XStreamAlias("device_info")
@JSONField(name = "device_info")
private String deviceInfo;// 微信支付分配的终端设备号 可能为空 private String deviceInfo;// 微信支付分配的终端设备号 可能为空
private String recall;// 是否需要继续调用接口 Y- 需要,N-不需要 private String recall;// 是否需要继续调用接口 Y- 需要,N-不需要
@ -54,7 +62,7 @@ public class ApiResult extends XmlResult {
} }
public String getSubMchId() { public String getSubMchId() {
return subMchId; return StringUtils.isNotBlank(subMchId) ? subMchId : null;
} }
public void setSubMchId(String subMchId) { public void setSubMchId(String subMchId) {
@ -89,8 +97,9 @@ public class ApiResult extends XmlResult {
return recall; return recall;
} }
public void setRecall(String recall) { @JSONField(deserialize = false, serialize = false)
this.recall = recall; public boolean getFormatRecall() {
return recall != null && recall.equalsIgnoreCase("y");
} }
@Override @Override

View File

@ -1,10 +1,9 @@
package com.foxinmy.weixin4j.mp.payment.v3; package com.foxinmy.weixin4j.mp.payment.v3;
import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
* Native支付回调时POST的信息 * V3 Native支付回调时POST的信息
* *
* @className PayNativeNotifyV3 * @className PayNativeNotifyV3
* @author jy * @author jy

View File

@ -3,14 +3,13 @@ package com.foxinmy.weixin4j.mp.payment.v3;
import com.alibaba.fastjson.annotation.JSONField; import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.exception.PayException; import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.model.Consts; import com.foxinmy.weixin4j.model.Consts;
import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.foxinmy.weixin4j.mp.payment.PayUtil; import com.foxinmy.weixin4j.mp.payment.PayUtil;
import com.foxinmy.weixin4j.util.RandomUtil; import com.foxinmy.weixin4j.util.RandomUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField; import com.thoughtworks.xstream.annotations.XStreamOmitField;
/** /**
* Native支付时的回调响应 * V3 Native支付时的回调响应
* *
* @className NativePayResponseV3 * @className NativePayResponseV3
* @author jy * @author jy

View File

@ -2,7 +2,9 @@ package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.Date; import java.util.Date;
import com.foxinmy.weixin4j.mp.payment.ApiResult; import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.annotation.JSONField;
import com.foxinmy.weixin4j.mp.type.CurrencyType; import com.foxinmy.weixin4j.mp.type.CurrencyType;
import com.foxinmy.weixin4j.mp.type.TradeState; import com.foxinmy.weixin4j.mp.type.TradeState;
import com.foxinmy.weixin4j.mp.type.TradeType; import com.foxinmy.weixin4j.mp.type.TradeType;
@ -10,7 +12,7 @@ import com.foxinmy.weixin4j.util.DateUtil;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
* 订单信息 * V3订单信息
* *
* @className Order * @className Order
* @author jy * @author jy
@ -26,39 +28,52 @@ public class Order extends ApiResult {
// USERPAYING--用户支付中 NOPAY--未支付(输入密码或 确认支付超时) PAYERROR--支付失败(其他 原因,如银行返回失败) // USERPAYING--用户支付中 NOPAY--未支付(输入密码或 确认支付超时) PAYERROR--支付失败(其他 原因,如银行返回失败)
// 以下字段在 return_code result_code 都为 SUCCESS 的时候有返回 // 以下字段在 return_code result_code 都为 SUCCESS 的时候有返回
@XStreamAlias("trade_state") @XStreamAlias("trade_state")
@JSONField(name = "trade_state")
private TradeState tradeState; private TradeState tradeState;
// 用户标识ID // 用户标识ID
@XStreamAlias("openid") @XStreamAlias("openid")
@JSONField(name = "openid")
private String openId; private String openId;
// 用户是否关注公众账号,Y- 关注,N-未关注,仅在公众 账号类型支付有效 // 用户是否关注公众账号,Y- 关注,N-未关注,仅在公众 账号类型支付有效
@XStreamAlias("is_subscribe") @XStreamAlias("is_subscribe")
@JSONField(name = "is_subscribe")
private String isSubscribe; private String isSubscribe;
// 交易类型 // 交易类型
@XStreamAlias("trade_type") @XStreamAlias("trade_type")
@JSONField(name = "trade_type")
private TradeType tradeType; private TradeType tradeType;
// 银行类型 // 银行类型
@XStreamAlias("bank_type") @XStreamAlias("bank_type")
@JSONField(name = "bank_type")
private String bankType; private String bankType;
// 订单总金额,单位为分 // 订单总金额,单位为分
@XStreamAlias("total_fee") @XStreamAlias("total_fee")
@JSONField(name = "total_fee")
private int totalFee; private int totalFee;
// 现金券支付金额<=订单总金 ,订单总金额-现金券金额 为现金支付金额 // 现金券支付金额<=订单总金 ,订单总金额-现金券金额 为现金支付金额
@XStreamAlias("coupon_fee") @XStreamAlias("coupon_fee")
private int couponFee; @JSONField(name = "coupon_fee")
private Integer couponFee;
@XStreamAlias("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee;
// 货币类型,符合 ISO 4217 标准的三位字母代码,默认人民币:CNY // 货币类型,符合 ISO 4217 标准的三位字母代码,默认人民币:CNY
@XStreamAlias("fee_type") @XStreamAlias("fee_type")
@JSONField(name = "fee_type")
private CurrencyType feeType; private CurrencyType feeType;
// 微信支付订单号 // 微信支付订单号
@XStreamAlias("transaction_id") @XStreamAlias("transaction_id")
@JSONField(name = "transaction_id")
private String transactionId; private String transactionId;
// 商户订单号 // 商户订单号
@XStreamAlias("out_rade_no") @XStreamAlias("out_trade_no")
@JSONField(name = "out_trade_no")
private String outTradeNo; private String outTradeNo;
// 商家数据包 // 商家数据包
@XStreamAlias("attach")
private String attach; private String attach;
// 支付完成时间,格式为 yyyyMMddhhmmss // 支付完成时间,格式为 yyyyMMddhhmmss
@XStreamAlias("time_end") @XStreamAlias("time_end")
@JSONField(name = "time_end")
private String timeEnd; private String timeEnd;
public TradeState getTradeState() { public TradeState getTradeState() {
@ -81,13 +96,8 @@ public class Order extends ApiResult {
return bankType; return bankType;
} }
/** public int getTotalFee() {
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font> return totalFee;
*
* @return 元单位
*/
public double getTotalFee() {
return totalFee / 100d;
} }
/** /**
@ -95,8 +105,37 @@ public class Order extends ApiResult {
* *
* @return 元单位 * @return 元单位
*/ */
public double getCouponFee() { @JSONField(serialize = false, deserialize = false)
return couponFee / 100d; 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 int getCashFee() {
return cashFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(serialize = false, deserialize = false)
public double getFormatCashFee() {
return cashFee / 100d;
} }
public CurrencyType getFeeType() { public CurrencyType getFeeType() {
@ -112,10 +151,15 @@ public class Order extends ApiResult {
} }
public String getAttach() { public String getAttach() {
return attach; return StringUtils.isBlank(attach) ? null : attach;
} }
public Date getTimeEnd() { public String getTimeEnd() {
return timeEnd;
}
@JSONField(serialize = false, deserialize = false)
public Date getFormatTimeEnd() {
return DateUtil.parse2yyyyMMddHHmmss(timeEnd); return DateUtil.parse2yyyyMMddHHmmss(timeEnd);
} }
@ -124,9 +168,13 @@ public class Order extends ApiResult {
return "Order [tradeState=" + tradeState + ", openId=" + openId return "Order [tradeState=" + tradeState + ", openId=" + openId
+ ", isSubscribe=" + isSubscribe + ", tradeType=" + tradeType + ", isSubscribe=" + isSubscribe + ", tradeType=" + tradeType
+ ", bankType=" + bankType + ", totalFee=" + totalFee + ", bankType=" + bankType + ", totalFee=" + totalFee
+ ", couponFee=" + couponFee + ", feeType=" + feeType + ", couponFee=" + couponFee + ", cashFee=" + cashFee
+ ", transactionId=" + transactionId + ", outTradeNo=" + ", feeType=" + feeType + ", transactionId=" + transactionId
+ outTradeNo + ", attach=" + attach + ", timeEnd=" + timeEnd + ", outTradeNo=" + outTradeNo + ", attach=" + attach
+ ", " + super.toString() + "]"; + ", timeEnd=" + timeEnd + ", getFormatTotalFee()="
+ getFormatTotalFee() + ", getFormatCouponFee()="
+ getFormatCouponFee() + ", getFormatCashFee()="
+ getFormatCashFee() + ", getFormatTimeEnd()="
+ getFormatTimeEnd() + ", " + super.toString() + "]";
} }
} }

View File

@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.exception.PayException;
import com.foxinmy.weixin4j.mp.payment.PayRequest; import com.foxinmy.weixin4j.mp.payment.PayRequest;
/** /**
* JS支付:get_brand_wcpay_request</br> * V3 JS支付:get_brand_wcpay_request</br>
* <p> * <p>
* get_brand_wcpay_request:ok 支付成功<br> * get_brand_wcpay_request:ok 支付成功<br>
* get_brand_wcpay_request:cancel 支付过程中用户取消<br> * get_brand_wcpay_request:cancel 支付过程中用户取消<br>

View File

@ -1,11 +1,10 @@
package com.foxinmy.weixin4j.mp.payment.v3; package com.foxinmy.weixin4j.mp.payment.v3;
import com.foxinmy.weixin4j.mp.payment.ApiResult;
import com.foxinmy.weixin4j.mp.type.TradeType; import com.foxinmy.weixin4j.mp.type.TradeType;
import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAlias;
/** /**
* 预订单信息 * V3预订单信息
* *
* @className PrePay * @className PrePay
* @author jy * @author jy

View File

@ -0,0 +1,112 @@
package com.foxinmy.weixin4j.mp.payment.v3;
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;
/**
* 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; // 微信退款单号
@XStreamAlias("refund_channel")
@JSONField(name = "refund_channel")
private String refundChannel;// 退款渠道:ORIGINAL原路退款,默认 BALANCE退回到余额
@XStreamAlias("refund_fee")
@JSONField(name = "refund_fee")
private int refundFee; // 退款总金额,单位为分,可以做部分退款
@XStreamAlias("refund_status")
@JSONField(name = "refund_status")
private String refundStatus; // 退款状态
@XStreamAlias("coupon_refund_fee")
@JSONField(name = "coupon_refund_fee")
private int couponRefundFee; // 现金券退款金额<=退款金额,退款金额-现金券退款金额为现金
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;
}
/**
* <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 int getCouponRefundFee() {
return couponRefundFee;
}
/**
* <font color="red">调用接口获取单位为分,get方法转换为元方便使用</font>
*
* @return 元单位
*/
@JSONField(deserialize = false, serialize = false)
public double getFormatCouponRefundFee() {
return couponRefundFee / 100d;
}
@Override
public String toString() {
return "RefundDetail [outRefundNo=" + outRefundNo + ", refundId="
+ refundId + ", refundChannel=" + refundChannel
+ ", refundFee=" + refundFee + ", refundStatus=" + refundStatus
+ ", couponRefundFee=" + couponRefundFee
+ ", getFormatRefundChannel()=" + getFormatRefundChannel()
+ ", getFormatRefundStatus()=" + getFormatRefundStatus()
+ ", getFormatCouponRefundFee()=" + getFormatCouponRefundFee();
}
}

View File

@ -0,0 +1,79 @@
package com.foxinmy.weixin4j.mp.payment.v3;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
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("cash_fee")
@JSONField(name = "cash_fee")
private int cashFee;
@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 int getCount() {
return count;
}
public List<RefundDetail> getDetails() {
return details;
}
public void setDetails(List<RefundDetail> details) {
this.details = details;
}
@Override
public String toString() {
return "RefundRecord [transactionId=" + transactionId + ", outTradeNo="
+ outTradeNo + ", cashFee=" + cashFee + ", count=" + count
+ ", details=" + details + ", " + super.toString() + "]";
}
}

View File

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

@ -37,4 +37,10 @@ public class IdQuery implements Serializable {
this.id = id; this.id = id;
this.type = idType; this.type = idType;
} }
@Override
public String toString() {
return String.format("%s=%s", type.getName(), id);
}
} }

View File

@ -10,7 +10,7 @@ package com.foxinmy.weixin4j.mp.type;
* @see * @see
*/ */
public enum RefundStatus { public enum RefundStatus {
SUCCES, // 退款成功 SUCCESS, // 退款成功
FAIL, // 退款失败 FAIL, // 退款失败
PROCESSING, // 退款处理中 PROCESSING, // 退款处理中
NOTSURE, // 未确定,需要商户 原退款单号重新发起 NOTSURE, // 未确定,需要商户 原退款单号重新发起

View File

@ -0,0 +1,25 @@
package com.foxinmy.weixin4j.mp.type;
/**
* 退款类型
*
* @className RefundType
* @author jy
* @date 2014年12月31日
* @since JDK 1.7
* @see
*/
public enum RefundType {
BALANCE(1), // 1:商户号余额退款;
CASH(2), // 2:现金帐号 退款;
BOTH(3);// 3:优先商户号退款,若商户号余额不足, 再做现金帐号退款
// 使用 2 3 ,需联系财 付通开通此功能
private int val;
RefundType(int val) {
this.val = val;
}
public int getVal() {
return val;
}
}

View File

@ -17,5 +17,7 @@ qr_path=/tmp/weixin/qr
media_path=/tmp/weixin/media media_path=/tmp/weixin/media
# \u5bf9\u8d26\u5355\u4fdd\u5b58\u8def\u5f84 # \u5bf9\u8d26\u5355\u4fdd\u5b58\u8def\u5f84
bill_path=/tmp/weixin/bill bill_path=/tmp/weixin/bill
# ca\u8bc1\u4e66\u5b58\u653e\u7684\u5b8c\u6574\u8def\u5f84 # ca\u8bc1\u4e66\u5b58\u653e\u7684\u5b8c\u6574\u8def\u5f84 (V2\u7248\u672c\u540e\u7f00\u4e3a*.pfx,V3\u7248\u672c\u540e\u7f00\u4e3a*.p12)
ca_file=/tmp/weixin/xxxxx.p12 | xxxxx.pfx ca_file=/tmp/weixin/xxxxx.p12
# classpath\u8def\u5f84\u4e0b\u53ef\u4ee5\u8fd9\u4e48\u5199
ca_file=classpath:xxxxx.pfx

View File

@ -32,7 +32,7 @@ public class UserTest extends TokenTest {
User user = userApi.getUser("owGBft_vbBbOaQOmpEUE4xDLeRSU"); User user = userApi.getUser("owGBft_vbBbOaQOmpEUE4xDLeRSU");
Assert.assertNotNull(user); Assert.assertNotNull(user);
System.out.println(user); System.out.println(user);
following(); // following();
} }
@Test @Test

View File

@ -50,8 +50,8 @@ public class PayAction {
JSONObject obj = new JSONObject(); JSONObject obj = new JSONObject();
WeixinMpAccount weixinAccount = ConfigUtil.getWeixinMpAccount(); WeixinMpAccount weixinAccount = ConfigUtil.getWeixinMpAccount();
// V3 支付 // V3 支付
PayPackage payPackage = new PayPackageV3(weixinAccount, "用户openid", "商品描述", PayPackage payPackage = new PayPackageV3(weixinAccount, "用户openid",
"系统内部订单号", 1d, "IP地址", TradeType.JSAPI); "商品描述", "系统内部订单号", 1d, "IP地址", TradeType.JSAPI);
// V2 支付 // V2 支付
payPackage = new PayPackageV2("商品描述", weixinAccount.getPartnerId(), payPackage = new PayPackageV2("商品描述", weixinAccount.getPartnerId(),
"系统内部订单号", 1d, "回调地址", "IP地址"); "系统内部订单号", 1d, "回调地址", "IP地址");
@ -163,7 +163,7 @@ public class PayAction {
if (!sign.equals(valid_sign)) { if (!sign.equals(valid_sign)) {
return XmlStream.to(new XmlResult(Consts.FAIL, "签名错误")); return XmlStream.to(new XmlResult(Consts.FAIL, "签名错误"));
} }
return XmlStream.to(new XmlResult()); return XmlStream.to(new XmlResult(Consts.SUCCESS, ""));
} }
/** /**

View File

@ -91,10 +91,14 @@ weixin4j-qy
* 2014-12-28 * 2014-12-28
+ **weixin4j-qy**: 增加用户进入应用的callback事件 + **weixin4j-qy-api**: 增加用户进入应用的callback事件
+ **weixin4j-qy**: 增加批量获取用户详情的接口 + **weixin4j-qy-api**: 增加批量获取用户详情的接口
+ **weixin4j-qy**: 新增获取微信服务器IP接口 + **weixin4j-qy-api**: 新增获取微信服务器IP接口
+ **weixin4j-qy**: 调整回调模式下的首次验证的签名方式 + **weixin4j-qy-server**: 调整回调模式下的首次验证的签名方式
* 2015-01-04
+ **weixin4j-qy-api**: 新增批量删除员工接口

View File

@ -63,3 +63,7 @@ weixin.properties说明
+ 增加`批量获取用户详情`的接口 + 增加`批量获取用户详情`的接口
+ 新增`获取微信服务器IP`接口 + 新增`获取微信服务器IP`接口
* 2015-01-04
+ 新增批量删除员工接口

View File

@ -231,6 +231,23 @@ public class WeixinProxy {
return userApi.deleteUser(userid); return userApi.deleteUser(userid);
} }
/**
* 批量删除成员
*
* @param userIds
* 成员列表
* @see <a href=
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AE%A1%E7%90%86%E6%88%90%E5%91%98#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98"
* >批量删除成员说明</a
* @see com.foxinmy.weixin4j.qy.api.UserApi
* @return 处理结果
* @throws WeixinException
*/
public JsonResult batchDeleteUser(List<String> userIds)
throws WeixinException {
return userApi.batchDeleteUser(userIds);
}
/** /**
* 创建标签(创建的标签属于管理组;默认为未加锁状态) * 创建标签(创建的标签属于管理组;默认为未加锁状态)
* *

View File

@ -200,6 +200,28 @@ public class UserApi extends QyApi {
return response.getAsJsonResult(); return response.getAsJsonResult();
} }
/**
* 批量删除成员
*
* @param userIds
* 成员列表
* @see <a href=
* "http://qydev.weixin.qq.com/wiki/index.php?title=%E7%AE%A1%E7%90%86%E6%88%90%E5%91%98#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98"
* >批量删除成员说明</a
* @return 处理结果
* @throws WeixinException
*/
public JsonResult batchDeleteUser(List<String> userIds)
throws WeixinException {
JSONObject obj = new JSONObject();
obj.put("useridlist", userIds);
String user_delete_uri = getRequestUri("user_batchdelete_uri");
Token token = tokenHolder.getToken();
Response response = request.post(String.format(user_delete_uri,
token.getAccessToken(), obj.toJSONString()));
return response.getAsJsonResult();
}
/** /**
* 开启二次验证成功时调用(管理员须拥有userid对应员工的管理权限) * 开启二次验证成功时调用(管理员须拥有userid对应员工的管理权限)
* *

View File

@ -27,6 +27,8 @@ user_slist_uri={api_base_url}/user/simplelist?access_token=%s&department_id=%d&f
user_list_uri={api_base_url}/user/list?access_token=%s&department_id=%d&fetch_child=%d&status=%d user_list_uri={api_base_url}/user/list?access_token=%s&department_id=%d&fetch_child=%d&status=%d
# \u5220\u9664\u6210\u5458 # \u5220\u9664\u6210\u5458
user_delete_uri={api_base_url}/user/delete?access_token=%s&userid=%s user_delete_uri={api_base_url}/user/delete?access_token=%s&userid=%s
# \u6279\u91cf\u5220\u9664\u6210\u5458
user_batchdelete_uri={api_base_url}/user/batchdelete?access_token=%s
# \u6210\u5458\u4e8c\u6b21\u9a8c\u8bc1\u6210\u529f\u65f6\u8c03\u7528 # \u6210\u5458\u4e8c\u6b21\u9a8c\u8bc1\u6210\u529f\u65f6\u8c03\u7528
user_authsucc_uri={api_base_url}/user/authsucc?access_token=%s&userid=%s user_authsucc_uri={api_base_url}/user/authsucc?access_token=%s&userid=%s
# \u521b\u5efa\u6807\u7b7e # \u521b\u5efa\u6807\u7b7e