diff --git a/README.md b/README.md index 994a95d8..f17c8713 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,18 @@ netty的代码没有放到maven中心仓库,也没什么意义,因为最终需 + **weixin4j-qy**: 新增获取微信服务器IP接口 + **weixin4j-qy**: 调整回调模式下的首次验证的签名方式 + +* 2015-01-04 + + + **weixin4j-base**: 新增获取classpath目录下的资源路径的方法 + + + **weixin4j-mp**: 支付模块拆分为V2跟V3,新增WeixinPayProxy类 + + + **weixin4j-mp**: 退款相关类拆分V2跟V3 + + + **weixin4j-mp**: 新增接口上报接口 + + + **weixin4j-qy**: 新增批量删除员工接口 接下来 ------ diff --git a/pom.xml b/pom.xml index 01f3994e..23461acd 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - + 4.0.0 com.foxinmy weixin4j @@ -209,6 +210,9 @@ **/*.xml **/*.properties **/*.pem + **/*.p12 + **/*.pfx + **/*.pem diff --git a/weixin4j-base/README.md b/weixin4j-base/README.md index 2537c56a..6eae5023 100644 --- a/weixin4j-base/README.md +++ b/weixin4j-base/README.md @@ -37,4 +37,8 @@ weixin4j-base * 2014-11-24 - + 将Action跟Mapping基础类并入到项目 \ No newline at end of file + + 将Action跟Mapping基础类并入到项目 + +* 2015-01-04 + + + ConfigUtil类新增获取classpath目录下的资源路径的方法 \ No newline at end of file diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/BaseApi.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/BaseApi.java index 90a969a4..15eeb642 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/BaseApi.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/BaseApi.java @@ -13,6 +13,7 @@ import com.thoughtworks.xstream.mapper.DefaultMapper; /** * API基础 + * * @className BaseApi * @author jy.hu * @date 2014年9月26日 @@ -37,7 +38,9 @@ public abstract class BaseApi { protected Map xml2map(String xml) { return mapXstream.fromXML(xml, Map.class); } + protected abstract ResourceBundle getWeixinBundle(); + protected String getRequestUri(String key) { String url = getWeixinBundle().getString(key); Pattern p = Pattern.compile("(\\{[^\\}]*\\})"); diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpRequest.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpRequest.java index 744b5814..4195c36c 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpRequest.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/HttpRequest.java @@ -188,7 +188,6 @@ public class HttpRequest { response.setStatusText(statusLine.getReasonPhrase()); response.setStream(new ByteArrayInputStream(data)); response.setText(new String(data, Consts.UTF_8)); - EntityUtils.consume(httpEntity); Header contentType = httpResponse .getFirstHeader(HttpHeaders.CONTENT_TYPE); diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/Response.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/Response.java index 84a28a94..cd3ae6d5 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/Response.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/Response.java @@ -168,10 +168,9 @@ public class Response { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("[Response text=").append(text); - sb.append(", statusCode=").append(statusCode); - sb.append(", statusText=").append(statusText).append("]"); - return sb.toString(); + return "Response [text=" + text + ", statusCode=" + statusCode + + ", statusText=" + statusText + ", stream=" + stream + + ", isJsonResult=" + isJsonResult + ", isXmlResult=" + + isXmlResult + "]"; } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SSLHttpRequest.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SSLHttpRequest.java index 947a1ba9..a4e4c40c 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SSLHttpRequest.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/SSLHttpRequest.java @@ -1,8 +1,5 @@ package com.foxinmy.weixin4j.http; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; @@ -23,10 +20,6 @@ import org.apache.http.conn.ssl.SSLSocketFactory; */ public class SSLHttpRequest extends HttpRequest { - public SSLHttpRequest(String password, File file) throws IOException { - this(password, new FileInputStream(file)); - } - public SSLHttpRequest(String password, InputStream inputStream) { super(); try { diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/XmlResult.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/XmlResult.java index 878718ef..60d09ee5 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/XmlResult.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/http/XmlResult.java @@ -2,7 +2,9 @@ package com.foxinmy.weixin4j.http; 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; /** @@ -19,16 +21,29 @@ public class XmlResult implements Serializable { private static final long serialVersionUID = -6185313616955051150L; @XStreamAlias("return_code") + @JSONField(name = "return_code") private String returnCode;// 此字段是通信标识,非交易 标识,交易是否成功需要查 看 result_code 来判断 非空 @XStreamAlias("return_msg") + @JSONField(name = "return_msg") private String returnMsg;// 返回信息,如非 空,为错误原因 可能为空 @XStreamAlias("result_code") + @JSONField(name = "result_code") private String resultCode;// 业务结果SUCCESS/FAIL 非空 @XStreamAlias("err_code") + @JSONField(name = "err_code") private String errCode;// 错误代码 可为空 @XStreamAlias("err_code_des") + @JSONField(name = "err_code_des") private String errCodeDes;// 结果信息描述 可为空 + public XmlResult() { + } + + public XmlResult(String returnCode, String returnMsg) { + this.returnCode = returnCode; + this.returnMsg = returnMsg; + } + public String getReturnCode() { return returnCode; } @@ -38,7 +53,7 @@ public class XmlResult implements Serializable { } public String getReturnMsg() { - return returnMsg; + return StringUtils.isNotBlank(returnMsg) ? returnMsg : null; } public void setReturnMsg(String returnMsg) { @@ -62,27 +77,13 @@ public class XmlResult implements Serializable { } public String getErrCodeDes() { - return errCodeDes; + return StringUtils.isNotBlank(errCodeDes) ? errCodeDes : null; } public void setErrCodeDes(String 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 public String toString() { return "returnCode=" + returnCode + ", returnMsg=" + returnMsg diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java index 19563cc4..3e8ddc15 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/model/Consts.java @@ -1,4 +1,7 @@ package com.foxinmy.weixin4j.model; + +import java.nio.charset.Charset; + /** * 常量类 * @className Consts @@ -8,6 +11,8 @@ package com.foxinmy.weixin4j.model; * @see */ 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 FAIL = "FAIL"; public static final String SunX509 = "SunX509"; diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ConfigUtil.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ConfigUtil.java index 7a1781bf..eb932aee 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ConfigUtil.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/util/ConfigUtil.java @@ -19,16 +19,21 @@ import com.foxinmy.weixin4j.model.WeixinQyAccount; * @see */ public class ConfigUtil { + private final static String CLASSPATH_PREFIX = "classpath:"; + private final static String CLASSPATH_VALUE; private final static ResourceBundle weixinBundle; static { weixinBundle = ResourceBundle.getBundle("weixin"); Set keySet = weixinBundle.keySet(); File file = null; + CLASSPATH_VALUE = Thread.currentThread().getContextClassLoader() + .getResource("").getPath(); for (String key : keySet) { if (!key.endsWith("_path")) { continue; } - file = new File(getValue(key)); + file = new File(getValue(key).replaceFirst(CLASSPATH_PREFIX, + CLASSPATH_VALUE)); if (!file.exists() && !file.mkdirs()) { System.err.append(String.format("%s create fail.%n", file.getAbsolutePath())); @@ -36,10 +41,27 @@ public class ConfigUtil { } } + /** + * 获取weixin.properties文件中的key值 + * + * @param key + * @return + */ public static String getValue(String 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 getWeixinAccount(Class clazz) { String text = getValue("account"); return JSON.parseObject(text, clazz); diff --git a/weixin4j-mp/README.md b/weixin4j-mp/README.md index 72373214..051522d0 100644 --- a/weixin4j-mp/README.md +++ b/weixin4j-mp/README.md @@ -65,6 +65,7 @@ weixin4j-mp media_path=/tmp/weixin/media bill_path=/tmp/weixin/bill 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`两个包引入到自己的工程. @@ -150,4 +151,12 @@ weixin4j-mp + **weixin4j-mp-api**: 新增群发消息预览、状态查询接口 - + **weixin4j-mp-api**: 新增多客服添加账号、更新账号、上传头像、删除账号接口 \ No newline at end of file + + **weixin4j-mp-api**: 新增多客服添加账号、更新账号、上传头像、删除账号接口 + +* 2015-01-04 + + + **weixin4j-mp-api**: 支付模块拆分为V2跟V3,新增WeixinPayProxy类 + + + **weixin4j-mp-api**: 退款相关类拆分为V2跟V3 + + + **weixin4j-mp-api**: 新增接口上报接口 \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-api/README.md b/weixin4j-mp/weixin4j-mp-api/README.md index 7c881498..9cad6a7f 100644 --- a/weixin4j-mp/weixin4j-mp-api/README.md +++ b/weixin4j-mp/weixin4j-mp-api/README.md @@ -60,6 +60,7 @@ weixin.properties说明 media_path=/tmp/weixin/media bill_path=/tmp/weixin/bill ca_file=/tmp/weixin/xxxxx.p12 | xxxx.pfx + #classpath路径下:ca_file=classpath:xxxxx.p12 2.实例化一个`WeixinProxy`对象,调用API,需要强调的是如果只传入appid,appsecret两个参数将无法调用支付相关接口 @@ -131,4 +132,12 @@ weixin.properties说明 + 新增群发消息预览、状态查询接口 - + 新增多客服添加账号、更新账号、上传头像、删除账号接口 \ No newline at end of file + + 新增多客服添加账号、更新账号、上传头像、删除账号接口 + +* 2015-01-04 + + + 支付模块拆分为V2跟V3,新增WeixinPayProxy类 + + + 退款相关类拆分为V2跟V3 + + + 新增接口上报接口 \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/WeixinPayProxy.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/WeixinPayProxy.java new file mode 100644 index 00000000..5bde1cc4 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/WeixinPayProxy.java @@ -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_id、out_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_id、out_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申请退款(请求需要双向证书)
+ *

+ * 交易时间超过 1 年的订单无法提交退款;
支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no。一笔退款失 + * 败后重新提交,要采用原来的 out_refund_no。总退款金额不能超过用户实际支付金额。
+ *

+ * + * @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退款查询
退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态 + * + * @param idQuery + * 单号 refund_id、out_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申请退款(请求需要双向证书)
+ *

+ * 交易时间超过 1 年的订单无法提交退款;
支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no。一笔退款失 + * 败后重新提交,要采用原来的 out_refund_no。总退款金额不能超过用户实际支付金额。
+ *

+ * + * @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退款查询
退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态 + * + * @param idQuery + * 单号 refund_id、out_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); + } + + /** + * 下载对账单
+ * 1.微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type 为 + * REVOKED;
+ * 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;
+ * 3.对账单中涉及金额的字段单位为“元”。
+ * + * @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); + } + + /** + * 冲正订单(需要证书)
当支付返回失败,或收银系统超时需要取消交易,可以调用该接口
接口逻辑:支 + * 付失败的关单,支付成功的撤销支付
7天以内的单可撤销,其他正常支付的单 + * 如需实现相同功能请调用退款接口
调用扣款接口后请勿立即调用撤销,需要等待5秒以上。先调用查单接口,如果没有确切的返回,再调用撤销
+ * + * @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_id、out_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); + } + + /** + * 关闭订单
当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完 + * 成支付请按正常支付处理。如果出现银行掉单,调用关单成功后,微信后台会主动发起退款。 + * + * @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); + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/WeixinProxy.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/WeixinProxy.java index 5a624159..eb3412b3 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/WeixinProxy.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/WeixinProxy.java @@ -2,13 +2,11 @@ package com.foxinmy.weixin4j.mp; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.util.Date; import java.util.List; import com.foxinmy.weixin4j.exception.WeixinException; import com.foxinmy.weixin4j.http.JsonResult; -import com.foxinmy.weixin4j.http.XmlResult; import com.foxinmy.weixin4j.model.Button; import com.foxinmy.weixin4j.model.WeixinMpAccount; 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.MenuApi; 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.TmplApi; 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.SemResult; 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.Lang; import com.foxinmy.weixin4j.msg.model.Base; @@ -70,7 +61,6 @@ public class WeixinProxy { private final QrApi qrApi; private final TmplApi tmplApi; private final HelperApi helperApi; - private final PayApi payApi; /** * 默认采用文件存放Token信息 @@ -116,7 +106,6 @@ public class WeixinProxy { this.qrApi = new QrApi(tokenHolder); this.tmplApi = new TmplApi(tokenHolder); this.helperApi = new HelperApi(tokenHolder); - this.payApi = new PayApi(tokenHolder); } /** @@ -502,8 +491,7 @@ public class WeixinProxy { * href="http://mp.weixin.qq.com/wiki/15/5380a4e6f02f2ffdc7981a8ed7a40753.html#.E5.88.A0.E9.99.A4.E7.BE.A4.E5.8F.91.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91">删除群发 * @see com.foxinmy.weixin4j.mp.api.MassApi * @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 { return massApi.deleteMassNews(msgid); @@ -756,7 +744,8 @@ public class WeixinProxy { /** * 自定义菜单 * - * @param btnList 菜单列表 + * @param btnList + * 菜单列表 * @throws WeixinException * @see 创建自定义菜单 @@ -798,7 +787,8 @@ public class WeixinProxy { /** * 生成带参数的二维码 * - * @param parameter 二维码参数 + * @param parameter + * 二维码参数 * @return byte数据包 * @throws WeixinException * @see com.foxinmy.weixin4j.mp.api.QrApi @@ -900,7 +890,8 @@ public class WeixinProxy { /** * 长链接转短链接 * - * @param url 待转换的链接 + * @param url + * 待转换的链接 * @return 短链接 * @throws WeixinException * @see 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); - } - - /** - * 下载对账单
- * 1.微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type 为 - * REVOKED;
- * 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;
- * 3.对账单中涉及金额的字段单位为“元”。
- * - * @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请求需要双向证书)
- *

- * 交易时间超过 1 年的订单无法提交退款;
支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no。一笔退款失 - * 败后重新提交,要采用原来的 out_refund_no。总退款金额不能超过用户实际支付金额。
- *

- * - * @param ca - * 证书文件V2版本时无需传入 - * @param idQuery - * ) 商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级: - * transaction_id> out_trade_no - * @param outRefundNo - * 商户系统内部的退款单号,商 户系统内部唯一,同一退款单号多次请求只退一笔 - * @param totalFee - * 订单总金额,单位为元 - * @param refundFee - * 退款总金额,单位为元,可以做部分退款 - * @param opUserId - * 操作员帐号, 默认为商户号 - * @param opUserPasswd - * V3版本留空,V2版本需传入值 - * - * @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); - } - - /** - * 不同的退款接口选择
V3支付则采用properties中配置的ca文件
- * V2支付则需要传入opUserPasswd参数
- * - * @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); - } - - /** - * 退款查询
退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态 - * - * @param idQuery - * 单号 refund_id、out_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); - } - - /** - * 关闭订单
当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完 - * 成支付请按正常支付处理。如果出现银行掉单,调用关单成功后,微信后台会主动发起退款。 - * - * @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 getcallbackip() throws WeixinException { return helperApi.getcallbackip(); } - - /** - * 冲正订单(需要证书)
当支付返回失败,或收银系统超时需要取消交易,可以调用该接口
接口逻辑:支 - * 付失败的关单,支付成功的撤销支付
7天以内的单可撤销,其他正常支付的单 - * 如需实现相同功能请调用退款接口
调用扣款接口后请勿立即调用撤销,需要等待5秒以上。先调用查单接口,如果没有确切的返回,再调用撤销
- * - * @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_id、out_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); - } } diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay2Api.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay2Api.java new file mode 100644 index 00000000..9c06d918 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay2Api.java @@ -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; + } + + /** + * 申请退款(需要证书)
+ *

+ * 交易时间超过 1 年的订单无法提交退款;
支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no。一笔退款失 + * 败后重新提交,要采用原来的 out_refund_no。总退款金额不能超过用户实际支付金额。
+ *

+ * + * @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 mopara) throws WeixinException { + String refund_uri = getRequestUri("refund_v2_uri"); + Response response = null; + InputStream ca = null; + try { + ca = new FileInputStream(caFile); + Map map = new HashMap(); + 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() { + }); + } + + /** + * 退款申请 + * + * @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 mopara = new HashMap(); + 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 mopara = new HashMap(); + 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); + } + + /** + * 冲正订单(需要证书)
V2暂不支持 + * + * @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"); + } + + /** + * 关闭订单
当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完 + * 成支付请按正常支付处理。如果出现银行掉单,调用关单成功后,微信后台会主动发起退款。 + * + * @param outTradeNo + * 商户系统内部的订单号 + * @return 处理结果 + * @since V2 + * @throws WeixinException + */ + public ApiResult closeOrder(String outTradeNo) throws WeixinException { + throw new WeixinException("V2 unsupport closeOrder api"); + } + + /** + * 下载对账单
+ * 1.微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type 为 + * REVOKED;
+ * 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;
+ * 3.对账单中涉及金额的字段单位为“元”。
+ * + * @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 map = new LinkedHashMap(); + 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 bills = new LinkedList(); + while ((line = reader.readLine()) != null) { + bills.add(line.replaceAll("`", "").split(",")); + } + + List headers = Arrays.asList(bills.remove(0)); + List totalDatas = Arrays + .asList(bills.remove(bills.size() - 1)); + List 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; + } + + /** + * 退款查询
退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态 + * + * @param idQuery + * 单号 refund_id、out_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 map = new HashMap(); + 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); + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java new file mode 100644 index 00000000..fea847ab --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/Pay3Api.java @@ -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_id、out_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 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() { + }); + } + + /** + * 申请退款(请求需要双向证书)
+ *

+ * 交易时间超过 1 年的订单无法提交退款;
支持部分退款,部分退需要设置相同的订单号和不同的 out_refund_no。一笔退款失 + * 败后重新提交,要采用原来的 out_refund_no。总退款金额不能超过用户实际支付金额。
+ *

+ * + * @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 mopara) throws WeixinException { + String refund_uri = getRequestUri("refund_v3_uri"); + Response response = null; + InputStream ca = null; + try { + ca = new FileInputStream(caFile); + + Map 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() { + }); + } + + /** + * 退款申请 + * + * @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); + } + + /** + * 冲正订单(需要证书)
当支付返回失败,或收银系统超时需要取消交易,可以调用该接口
接口逻辑:支 + * 付失败的关单,支付成功的撤销支付
7天以内的单可撤销,其他正常支付的单 + * 如需实现相同功能请调用退款接口
调用扣款接口后请勿立即调用撤销,需要等待5秒以上。先调用查单接口,如果没有确切的返回,再调用撤销
+ * + * @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 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() { + }); + } 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 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"); + } + + /** + * 关闭订单
当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完 + * 成支付请按正常支付处理。如果出现银行掉单,调用关单成功后,微信后台会主动发起退款。 + * + * @param outTradeNo + * 商户系统内部的订单号 + * @return 处理结果 + * @since V3 + * @throws WeixinException + */ + public ApiResult closeOrder(String outTradeNo) throws WeixinException { + Map 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() { + }); + } + + /** + * 下载对账单
+ * 1.微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账 单中,跟原支付单订单号一致,bill_type 为 + * REVOKED;
+ * 2.微信在次日 9 点启动生成前一天的对账单,建议商户 9 点半后再获取;
+ * 3.对账单中涉及金额的字段单位为“元”。
+ * + * @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 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 bills = new LinkedList(); + while ((line = reader.readLine()) != null) { + bills.add(line.replaceAll("`", "").split(",")); + } + + List headers = Arrays.asList(bills.remove(0)); + List totalDatas = Arrays + .asList(bills.remove(bills.size() - 1)); + List 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; + } + + /** + * 退款查询
退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态 + * + * @param idQuery + * 单号 refund_id、out_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 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 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) 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 baseMap(IdQuery idQuery) { + Map map = new HashMap(); + 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; + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/PayApi.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/PayApi.java index fb6b8d0d..de207017 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/PayApi.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/PayApi.java @@ -1,64 +1,23 @@ 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.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.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.JsonResult; 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.WeixinMpAccount; -import com.foxinmy.weixin4j.mp.payment.ApiResult; import com.foxinmy.weixin4j.mp.payment.PayUtil; -import com.foxinmy.weixin4j.mp.payment.Refund; -import com.foxinmy.weixin4j.mp.payment.RefundConverter; -import com.foxinmy.weixin4j.mp.payment.RefundResult; -import com.foxinmy.weixin4j.mp.payment.v2.Order; +import com.foxinmy.weixin4j.mp.payment.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.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; -import com.foxinmy.weixin4j.util.RandomUtil; /** * 支付API @@ -67,11 +26,12 @@ import com.foxinmy.weixin4j.util.RandomUtil; * @author jy * @date 2014年10月28日 * @since JDK 1.7 - * @see + * @see com.foxinmy.weixin4j.mp.api.Pay2Api + * @see com.foxinmy.weixin4j.mp.api.Pay3Api */ -public class PayApi extends MpApi { - private final TokenHolder tokenHolder; - private final WeixinMpAccount weixinAccount; +public abstract class PayApi extends MpApi { + protected final TokenHolder tokenHolder; + protected final WeixinMpAccount weixinAccount; public PayApi(TokenHolder tokenHolder) { this.tokenHolder = tokenHolder; @@ -92,7 +52,6 @@ public class PayApi extends MpApi { * @param statusMsg * status为失败时携带的信息 * @return 发货处理结果 - * @since V2 & V3 * @throws WeixinException */ public JsonResult deliverNotify(String openId, String transid, @@ -116,63 +75,9 @@ public class PayApi extends MpApi { Response response = request.post( String.format(delivernotify_uri, token.getAccessToken()), JSON.toJSONString(map)); - 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>() { - })); - return order; - } - /** * 维权处理 * @@ -181,7 +86,6 @@ public class PayApi extends MpApi { * @param feedbackId * 维权单号 * @return 维权处理结果 - * @since V2 & V3 * @throws WeixinException */ public JsonResult updateFeedback(String openId, String feedbackId) @@ -194,27 +98,17 @@ public class PayApi extends MpApi { } /** - * V3订单查询 + * 订单查询 * * @param idQuery * 商户系统内部的订单号, transaction_id、out_trade_no 二 选一,如果同时存在优先级: * transaction_id> out_trade_no * @return 订单信息 + * @see com.foxinmy.weixin4j.mp.payment.v2.Order * @see com.foxinmy.weixin4j.mp.payment.v3.Order * @throws WeixinException */ - public com.foxinmy.weixin4j.mp.payment.v3.Order orderQueryV3(IdQuery idQuery) - throws WeixinException { - Map 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() { - }); - } + public abstract Object orderQuery(IdQuery idQuery) throws WeixinException; /** * 申请退款(请求需要双向证书)
@@ -223,8 +117,8 @@ public class PayApi extends MpApi { * 败后重新提交,要采用原来的 out_refund_no。总退款金额不能超过用户实际支付金额。
*

* - * @param ca - * 证书文件 + * @param caFile + * 证书文件(V2版本后缀为*.pfx,V3版本后缀为*.p12) * @param idQuery * 商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级: * transaction_id> out_trade_no @@ -236,236 +130,45 @@ public class PayApi extends MpApi { * 退款总金额,单位为元,可以做部分退款 * @param opUserId * 操作员帐号, 默认为商户号 - * @param opUserPasswd - * V3版本留空,V2版本需传入值 + * @param mopara + * 更多参数 如V2版本的opUserPasswd * * @return 退款申请结果 - * @see com.foxinmy.weixin4j.mp.payment.RefundResult - * @since V2 & V3 + * @see com.foxinmy.weixin4j.mp.payment.v2.RefundResult + * @see com.foxinmy.weixin4j.mp.payment.v3.RefundResult * @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 opUserId, String opUserPasswd) throws WeixinException{ - int version = weixinAccount.getVersion(); - String refund_uri = getRequestUri(String.format("refund_v%d_uri", - version)); - Response response = null; - if (version == 2) { - Map map = new HashMap(); - 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 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() { - }); - } + String opUserId, Map mopara) throws WeixinException; /** - * 退款申请:默认采用properties中配置的ca文件
V2支付则需要传入opUserPasswd参数 + * 退款查询
退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态 * - * @see {@link com.foxinmy.weixin4j.mp.api.PayApi#refund(InputStream, IdQuery, String, double, double, String, String)} + * @param idQuery + * 单号 refund_id、out_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, - 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); - } + public abstract Object refundQuery(IdQuery idQuery) throws WeixinException; /** - * 冲正订单(需要证书)
当支付返回失败,或收银系统超时需要取消交易,可以调用该接口
接口逻辑:支 - * 付失败的关单,支付成功的撤销支付
7天以内的单可撤销,其他正常支付的单 - * 如需实现相同功能请调用退款接口
调用扣款接口后请勿立即调用撤销,需要等待5秒以上。先调用查单接口,如果没有确切的返回,再调用撤销
+ * 冲正订单(需要证书) * - * @param ca - * 证书文件 + * @param caFile + * 证书文件 (V2版本后缀为*.pfx,V3版本后缀为*.p12) * @param idQuery * 商户系统内部的订单号, transaction_id 、 out_trade_no 二选一,如果同时存在优先级: * transaction_id> out_trade_no * @return 撤销结果 * @throws WeixinException */ - public ApiResult reverse(InputStream ca, IdQuery idQuery) - throws WeixinException { - SSLHttpRequest request = new SSLHttpRequest(weixinAccount.getMchId(), - ca); - String reverse_uri = getRequestUri("reverse_uri"); - Map 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() { - }); - } - - /** - * 冲正撤销:默认采用properties中配置的ca文件 - * - * @param idQuery - * transaction_id、out_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 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"); - } - - /** - * 关闭订单
当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完 - * 成支付请按正常支付处理。如果出现银行掉单,调用关单成功后,微信后台会主动发起退款。 - * - * @param outTradeNo - * 商户系统内部的订单号 - * @return 处理结果 - * @since V3 - * @throws WeixinException - */ - public XmlResult closeOrder(String outTradeNo) throws WeixinException { - Map 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(); - } + public abstract ApiResult reverse(File caFile, IdQuery idQuery) + throws WeixinException; /** * 下载对账单
@@ -480,154 +183,30 @@ public class PayApi extends MpApi { * 下载对账单的类型 ALL,返回当日所有订单信息, 默认值 SUCCESS,返回当日成功支付的订单 * REFUND,返回当日退款订单 * @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 map = new LinkedHashMap(); - 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 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 bills = new LinkedList(); - while ((line = reader.readLine()) != null) { - bills.add(line.replaceAll("`", "").split(",")); - } - - List headers = Arrays.asList(bills.remove(0)); - List totalDatas = Arrays - .asList(bills.remove(bills.size() - 1)); - List 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; - } - - /** - * 退款查询
退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款 3 个工作日后重新查询退款状态 - * - * @param idQuery - * 单号 refund_id、out_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 */ - public Refund refundQuery(IdQuery idQuery) throws WeixinException { - int version = weixinAccount.getVersion(); - String refundquery_uri = getRequestUri(String.format( - "refundquery_v%d_uri", version)); - Response response = null; - if (version == 2) { - Map map = new HashMap(); - 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 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()); - } + public abstract File downloadbill(Date billDate, BillType billType) + throws WeixinException; /** - * V3接口请求基本数据 + * 关闭订单
当订单支付失败,调用关单接口后用新订单号重新发起支付,如果关单失败,返回已完 + * 成支付请按正常支付处理。如果出现银行掉单,调用关单成功后,微信后台会主动发起退款。 * - * @return + * @param outTradeNo + * 商户系统内部的订单号 + * @return 处理结果 + * @throws WeixinException */ - private Map baseMapV3(IdQuery idQuery) { - Map map = new HashMap(); - 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; - } + public abstract ApiResult closeOrder(String outTradeNo) + throws WeixinException; + + /** + * native支付URL转短链接 + * + * @param url + * 具有native标识的支付URL + * @return 转换后的短链接 + * @throws WeixinException + */ + public abstract String getShorturl(String url) throws WeixinException; } diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties index ae297722..068e62a3 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/api/weixin.properties @@ -121,6 +121,8 @@ refund_v3_uri={mch_base_url}/secapi/pay/refund reverse_uri={mch_base_url}/secapi/pay/reverse # \u88ab\u626b\u652f\u4ed8 micropay_uri={mch_base_url}/pay/micropay +# \u63a5\u53e3\u4e0a\u62a5 +pay_report_uri={mch_base_url}/payitil/report # \u7edf\u4e00\u8ba2\u5355\u751f\u6210 diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/CustomRecord.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/CustomRecord.java index e8f0d226..5c08b040 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/CustomRecord.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/CustomRecord.java @@ -51,7 +51,7 @@ public class CustomRecord implements Serializable { } public void setTime(long time) { - this.time = new Date(time * 1000); + this.time = new Date(time * 1000l); } public String getText() { diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/User.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/User.java index 32909be9..23c77d51 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/User.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/model/User.java @@ -1,9 +1,11 @@ package com.foxinmy.weixin4j.mp.model; import java.io.Serializable; +import java.util.Date; import org.apache.commons.lang3.StringUtils; +import com.alibaba.fastjson.annotation.JSONField; import com.foxinmy.weixin4j.model.Gender; import com.foxinmy.weixin4j.mp.type.FaceSize; import com.foxinmy.weixin4j.mp.type.Lang; @@ -24,22 +26,20 @@ public class User implements Serializable { private String openid; // 用户的唯一标识 private String nickname; // 用户昵称 - private int sex; // 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 + @JSONField(name = "sex") + private Gender gender; // 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 private String province; // 用户个人资料填写的省份 private String city; // 普通用户个人资料填写的城市 private String country; // 国家,如中国为CN private String headimgurl; // 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 private String privilege; // 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom) - private int subscribe; // 是否关注 - private long subscribe_time; // 关注时间 + @JSONField(name = "subscribe") + private boolean isSubscribe; // 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。 + @JSONField(name = "subscribe_time") + private Date subscribeTime; // 关注时间 private Lang language; // 使用语言 private String unionid; // 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段 - public User() { - this.sex = 0; - this.language = Lang.zh_CN; - } - public String getOpenid() { return openid; } @@ -56,22 +56,18 @@ public class User implements Serializable { this.nickname = nickname; } - public int getSex() { - return sex; - } - public Gender getGender() { - if (sex == 1) { - return Gender.male; - } else if (sex == 2) { - return Gender.female; - } else { - return Gender.unknown; - } + return gender; } - public void setSex(int sex) { - this.sex = sex; + public void setGender(int sex) { + if (sex == 1) { + this.gender = Gender.male; + } else if (sex == 2) { + this.gender = Gender.female; + } else { + this.gender = Gender.unknown; + } } public String getProvince() { @@ -123,14 +119,6 @@ public class User implements Serializable { this.privilege = privilege; } - public int getSubscribe() { - return subscribe; - } - - public void setSubscribe(int subscribe) { - this.subscribe = subscribe; - } - public Lang getLanguage() { return language; } @@ -139,12 +127,20 @@ public class User implements Serializable { this.language = language; } - public long getSubscribe_time() { - return subscribe_time; + public boolean isSubscribe() { + return isSubscribe; } - public void setSubscribe_time(long subscribe_time) { - this.subscribe_time = subscribe_time; + public void setSubscribe(boolean isSubscribe) { + this.isSubscribe = isSubscribe; + } + + public Date getSubscribeTime() { + return (Date) subscribeTime.clone(); + } + + public void setSubscribeTime(long subscribeTime) { + this.subscribeTime = new Date(subscribeTime * 1000l); } public String getUnionid() { @@ -163,46 +159,21 @@ public class User implements Serializable { 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 public String toString() { StringBuilder sb = new StringBuilder(); sb.append("[User openid=").append(openid); sb.append(", nickname=").append(nickname); - sb.append(", sex=").append(sex); + sb.append(", gender=").append(gender); sb.append(", province=").append(province); sb.append(", city=").append(city); sb.append(", country=").append(country); sb.append(", headimgurl=").append(headimgurl); sb.append(", privilege=").append(privilege); sb.append(", language=").append(language); - sb.append(", subscribe_time=").append(subscribe_time); + sb.append(", subscribeTime=").append(subscribeTime); sb.append(", unionid=").append(unionid); - sb.append(", subscribe=").append(subscribe).append("]"); + sb.append(", isSubscribe=").append(isSubscribe).append("]"); return sb.toString(); } } diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayUtil.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayUtil.java index c8adece2..cfe12868 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayUtil.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/PayUtil.java @@ -45,6 +45,7 @@ public class PayUtil { * 订单信息 * @param WeixinMpAccount * 商户信息 + * @since V2 & V3 * @return 支付json串 * @throws PayException */ @@ -56,8 +57,9 @@ public class PayUtil { } else if (payPackage instanceof PayPackageV3) { return createPayJsRequestJsonV3((PayPackageV3) payPackage, weixinAccount); + } else { + throw new PayException("unknown pay"); } - throw new PayException("unknown pay"); } /** diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/Refund.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/Refund.java deleted file mode 100644 index 9f784a63..00000000 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/Refund.java +++ /dev/null @@ -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 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 getDetails() { - return details; - } - - public void setDetails(List details) { - this.details = details; - } - - @Override - public String toString() { - return "Refund [transactionId=" + transactionId + ", orderNo=" - + orderNo + ", subMchId=" + subMchId + ", count=" + count - + ", partner=" + partner + ", details=" + details - + ", " + super.toString() + "]"; - } -} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundConverter.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundConverter.java index ea1c5faf..85e41127 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundConverter.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundConverter.java @@ -26,34 +26,39 @@ import com.thoughtworks.xstream.mapper.Mapper; * @author jy * @date 2014年11月2日 * @since JDK 1.7 - * @see com.foxinmy.weixin4j.mp.payment.Refund - * @see com.foxinmy.weixin4j.mp.payment.RefundDetail + * @see com.foxinmy.weixin4j.mp.payment.v2.RefundRecord + * @see com.foxinmy.weixin4j.mp.payment.v2.RefundDetail + * @see com.foxinmy.weixin4j.mp.payment.v3.RefundRecord + * @see com.foxinmy.weixin4j.mp.payment.v3.RefundDetail */ public class RefundConverter { private final static XmlStream xStream = XmlStream.get(); private final static Mapper mapper; private final static ReflectionProvider reflectionProvider; private final static Pattern pattern = Pattern.compile("(_\\d)$"); + private static Class clazz; + private final static Class REFUNDRECORD2 = com.foxinmy.weixin4j.mp.payment.v2.RefundRecord.class; + private final static Class REFUNDRECORD3 = com.foxinmy.weixin4j.mp.payment.v3.RefundRecord.class; + static { - xStream.processAnnotations(Refund.class); - xStream.processAnnotations(RefundDetail.class); + xStream.processAnnotations(new Class[] { REFUNDRECORD2, REFUNDRECORD3, + 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 $()); mapper = xStream.getMapper(); reflectionProvider = xStream.getReflectionProvider(); } - public static String toXML(Refund refund) { - return xStream.toXML(refund); - } - - public static Refund fromXML(String xml) { - return xStream.fromXML(xml, Refund.class); + public static T fromXML(String xml, Class clazz) { + RefundConverter.clazz = clazz; + return xStream.fromXML(xml, clazz); } private static class $ implements Converter { @Override public boolean canConvert(@SuppressWarnings("rawtypes") Class clazz) { - return clazz.equals(Refund.class); + return clazz.equals(REFUNDRECORD2) || clazz.equals(REFUNDRECORD3); } @Override @@ -63,18 +68,24 @@ public class RefundConverter { writer, context); } - @SuppressWarnings("unchecked") @Override public Object unmarshal(HierarchicalStreamReader reader, 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; Map> outMap = new HashMap>(); while (reader.hasMoreChildren()) { reader.moveDown(); String nodeName = reader.getNodeName(); - String fieldName = mapper.realMember(Refund.class, nodeName); - Field field = reflectionProvider.getFieldOrNull(Refund.class, + String fieldName = mapper.realMember(clazz, nodeName); + Field field = reflectionProvider.getFieldOrNull(clazz, fieldName); if (field != null) { Object value = context.convertAnother(refund, @@ -94,7 +105,8 @@ public class RefundConverter { } StringBuilder detailXml = new StringBuilder(); detailXml.append(""); - String detailCanonicalName = RefundDetail.class.getCanonicalName(); + String detailCanonicalName = clazz.getCanonicalName().replaceFirst( + "RefundRecord", "RefundDetail"); for (Iterator>> outIt = outMap .entrySet().iterator(); outIt.hasNext();) { detailXml.append("<").append(detailCanonicalName).append(">"); @@ -108,7 +120,9 @@ public class RefundConverter { detailXml.append(""); } detailXml.append(""); - refund.setDetails(xStream.fromXML(detailXml.toString(), List.class)); + reflectionProvider.writeField(refund, "details", + xStream.fromXML(detailXml.toString(), List.class), + List.class.getDeclaringClass()); return refund; } } diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundDetail.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundDetail.java deleted file mode 100644 index 0c09a2e2..00000000 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundDetail.java +++ /dev/null @@ -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;// 签名类型,取值:MD5、RSA,默认: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; - } - - /** - * 调用接口获取单位为分,get方法转换为元方便使用 - * - * @return 元单位 - */ - public double getRefundFee() { - return refundFee / 100d; - } - - /** - * 调用接口获取单位为分,get方法转换为元方便使用 - * - * @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 + "]"; - } -} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundResult.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundResult.java deleted file mode 100644 index 28a0b88b..00000000 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/RefundResult.java +++ /dev/null @@ -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;// 签名类型,取值:MD5、RSA,默认: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; - } - - /** - * 调用接口获取单位为分,get方法转换为元方便使用 - * - * @return 元单位 - */ - public double getRefundFee() { - return refundFee / 100d; - } - - /** - * 调用接口获取单位为分,get方法转换为元方便使用 - * - * @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() + "]"; - } -} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/ApiResult.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/ApiResult.java new file mode 100644 index 00000000..200cfb9a --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/ApiResult.java @@ -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; // 签名类型,取值:MD5、RSA + + 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; + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/JsPayRequestV2.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/JsPayRequestV2.java index 874d2060..19aa5049 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/JsPayRequestV2.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/JsPayRequestV2.java @@ -10,7 +10,7 @@ import com.foxinmy.weixin4j.mp.payment.PayRequest; import com.foxinmy.weixin4j.util.MapUtil; /** - * 微信JS支付:get_brand_wcpay_request
+ * V2微信JS支付:get_brand_wcpay_request
* 所列参数均为非空字符串 *

* get_brand_wcpay_request:ok 支付成功
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayNotifyV2.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayNotifyV2.java index c85bd833..08178056 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayNotifyV2.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayNotifyV2.java @@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.mp.payment.JsPayNotify; import com.thoughtworks.xstream.annotations.XStreamAlias; /** - * Native支付回调时POST的信息 + * V2 Native支付回调时POST的信息 * * @className PayNativeNotifyV2 * @author jy diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayResponseV2.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayResponseV2.java index 4c1dc57b..93381ef3 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayResponseV2.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/NativePayResponseV2.java @@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.model.WeixinMpAccount; import com.thoughtworks.xstream.annotations.XStreamAlias; /** - * Native支付时的回调响应 + * V2 Native支付时的回调响应 * * @className NativePayResponseV2 * @author jy diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/Order.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/Order.java index c4f5de27..ffef89a8 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/Order.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/Order.java @@ -1,14 +1,14 @@ package com.foxinmy.weixin4j.mp.payment.v2; import java.util.Date; -import java.util.Map; 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; /** - * 订单信息 + * V2订单信息 * * @className Order * @author jy @@ -16,27 +16,16 @@ import com.foxinmy.weixin4j.util.DateUtil; * @since JDK 1.7 * @see */ -public class Order extends JsonResult { +public class Order extends ApiResult { 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 为成功,其他为失败; @JSONField(name = "trade_state") private int tradeState; // 是交易模式,1 为即时到帐,其他保留; @JSONField(name = "trade_mode") private int tradeMode; - // 是财付通商户号,即前文的 partnerid; - private String partner; // 是银行类型; @JSONField(name = "bank_type") private String bankType; @@ -48,7 +37,7 @@ public class Order extends JsonResult { private int totalFee; // 是币种,1 为人民币; @JSONField(name = "fee_type") - private String feeType; + private int feeType; // 是财付通订单号; @JSONField(name = "transaction_id") private String transactionId; @@ -76,39 +65,20 @@ public class Order extends JsonResult { private int discount; // 换算成人民币之后的总金额,单位为分,一般看 total_fee 即可。 @JSONField(name = "rmb_total_fee") - private int rmbTotalFee; - - @JSONField(serialize = false) - private Map 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; - } + private Integer rmbTotalFee; public int getTradeState() { return tradeState; } + @JSONField(serialize = false, deserialize = false) + public TradeState getFormatTradeState() { + if (tradeState == 0) { + return TradeState.SUCCESS; + } + return null; + } + public void setTradeState(int tradeState) { this.tradeState = tradeState; } @@ -121,14 +91,6 @@ public class Order extends JsonResult { this.tradeMode = tradeMode; } - public String getPartner() { - return partner; - } - - public void setPartner(String partner) { - this.partner = partner; - } - public String getBankType() { return bankType; } @@ -145,12 +107,17 @@ public class Order extends JsonResult { this.bankBillno = bankBillno; } + public int getTotalFee() { + return totalFee; + } + /** * 调用接口获取单位为分,get方法转换为元方便使用 * * @return 元单位 */ - public double getTotalFee() { + @JSONField(serialize = false, deserialize = false) + public double getFormatTotalFee() { return totalFee / 100d; } @@ -158,11 +125,19 @@ public class Order extends JsonResult { this.totalFee = totalFee; } - public String getFeeType() { + public int getFeeType() { 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; } @@ -206,7 +181,12 @@ public class Order extends JsonResult { this.attach = attach; } - public Date getTimeEnd() { + public String getTimeEnd() { + return timeEnd; + } + + @JSONField(serialize = false, deserialize = false) + public Date getFormatTimeEnd() { return DateUtil.parse2yyyyMMddHHmmss(timeEnd); } @@ -214,12 +194,17 @@ public class Order extends JsonResult { this.timeEnd = timeEnd; } + public int getTransportFee() { + return transportFee; + } + /** * 调用接口获取单位为分,get方法转换为元方便使用 * * @return 元单位 */ - public double getTransportFee() { + @JSONField(serialize = false, deserialize = false) + public double getFormatTransportFee() { return transportFee / 100d; } @@ -227,12 +212,17 @@ public class Order extends JsonResult { this.transportFee = transportFee; } + public int getProductFee() { + return productFee; + } + /** * 调用接口获取单位为分,get方法转换为元方便使用 * * @return 元单位 */ - public double getProductFee() { + @JSONField(serialize = false, deserialize = false) + public double getFormatProductFee() { return productFee / 100d; } @@ -240,12 +230,17 @@ public class Order extends JsonResult { this.productFee = productFee; } + public int getDiscount() { + return discount; + } + /** * 调用接口获取单位为分,get方法转换为元方便使用 * * @return 元单位 */ - public double getDiscount() { + @JSONField(serialize = false, deserialize = false) + public double getFormatDiscount() { return discount / 100d; } @@ -253,39 +248,42 @@ public class Order extends JsonResult { this.discount = discount; } + public Integer getRmbTotalFee() { + return rmbTotalFee; + } + /** * 调用接口获取单位为分,get方法转换为元方便使用 * * @return 元单位 */ - public double getRmbTotalFee() { - return rmbTotalFee / 100d; + @JSONField(serialize = false, deserialize = false) + public double getFormatRmbTotalFee() { + return rmbTotalFee != null ? rmbTotalFee / 100d : 0d; } public void setRmbTotalFee(int rmbTotalFee) { this.rmbTotalFee = rmbTotalFee; } - public Map getMapData() { - return mapData; - } - - public void setMapData(Map mapData) { - this.mapData = mapData; - } - @Override public String toString() { - return "Order [retCode=" + retCode + ", retMsg=" + retMsg - + ", inputCharset=" + inputCharset + ", tradeState=" - + tradeState + ", tradeMode=" + tradeMode + ", partner=" - + partner + ", bankType=" + bankType + ", bankBillno=" - + bankBillno + ", totalFee=" + totalFee + ", feeType=" - + feeType + ", transactionId=" + transactionId - + ", outTradeNo=" + outTradeNo + ", isSplit=" + isSplit - + ", isRefund=" + isRefund + ", attach=" + attach - + ", timeEnd=" + timeEnd + ", transportFee=" + transportFee - + ", productFee=" + productFee + ", discount=" + discount - + ", rmbTotalFee=" + rmbTotalFee + ", mapData=" + mapData + "]"; + return "Order [tradeState=" + tradeState + ", tradeMode=" + tradeMode + + ", bankType=" + bankType + ", bankBillno=" + bankBillno + + ", totalFee=" + totalFee + ", feeType=" + feeType + + ", transactionId=" + transactionId + ", outTradeNo=" + + outTradeNo + ", isSplit=" + isSplit + ", isRefund=" + + isRefund + ", attach=" + attach + ", timeEnd=" + timeEnd + + ", transportFee=" + transportFee + ", productFee=" + + productFee + ", discount=" + discount + ", rmbTotalFee=" + + rmbTotalFee + ", getFormatTradeState()=" + + getFormatTradeState() + ", getFormatTotalFee()=" + + getFormatTotalFee() + ", getFormatFeeType()=" + + getFormatFeeType() + ", getFormatTimeEnd()=" + + getFormatTimeEnd() + ", getFormatTransportFee()=" + + getFormatTransportFee() + ", getFormatProductFee()=" + + getFormatProductFee() + ", getFormatDiscount()=" + + getFormatDiscount() + ", getFormatRmbTotalFee()=" + + getFormatRmbTotalFee() + ", " + super.toString() + "]"; } } diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayFeedback.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayFeedback.java index c3710d07..77a549e3 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayFeedback.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayFeedback.java @@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.mp.payment.PayBaseInfo; import com.thoughtworks.xstream.annotations.XStreamAlias; /** - * 维权POST的数据 + * V2维权POST的数据 * * @className PayFeedback * @author jy diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayWarn.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayWarn.java index cf81130a..bc64636d 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayWarn.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/PayWarn.java @@ -3,6 +3,14 @@ package com.foxinmy.weixin4j.mp.payment.v2; import com.foxinmy.weixin4j.mp.payment.PayBaseInfo; import com.thoughtworks.xstream.annotations.XStreamAlias; +/** + * V2告警通知 + * @className PayWarn + * @author jy + * @date 2014年12月31日 + * @since JDK 1.7 + * @see + */ @XStreamAlias("xml") public class PayWarn extends PayBaseInfo { diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundDetail.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundDetail.java new file mode 100644 index 00000000..94c4d002 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundDetail.java @@ -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; + } + + /** + * 调用接口获取单位为分,get方法转换为元方便使用 + * + * @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(); + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundRecord.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundRecord.java new file mode 100644 index 00000000..1073b80f --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundRecord.java @@ -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 details; // 退款详情 + + public String getTransactionId() { + return transactionId; + } + + public String getOutTradeNo() { + return StringUtils.isNotBlank(outTradeNo) ? outTradeNo : null; + } + + public int getCount() { + return count; + } + + public List getDetails() { + return details; + } + + @Override + public String toString() { + return "RefundRecord [transactionId=" + transactionId + ", outTradeNo=" + + outTradeNo + ", count=" + count + ", details=" + details + + ", " + super.toString() + "]"; + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundResult.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundResult.java new file mode 100644 index 00000000..f1206c83 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v2/RefundResult.java @@ -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() + "]"; + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/ApiResult.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/ApiResult.java similarity index 80% rename from weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/ApiResult.java rename to weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/ApiResult.java index e795ea4e..d8c06538 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/ApiResult.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/ApiResult.java @@ -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.thoughtworks.xstream.annotations.XStreamAlias; @@ -17,15 +20,20 @@ public class ApiResult extends XmlResult { private static final long serialVersionUID = -8430005768959715444L; @XStreamAlias("appid") + @JSONField(name = "appid") private String appId;// 微信分配的公众账号 ID商户号 非空 @XStreamAlias("mch_id") + @JSONField(name = "mch_id") private String mchId;// 微信支付分配的商户号 非空 @XStreamAlias("sub_mch_id") + @JSONField(name = "sub_mch_id") private String subMchId; // 未知 可能为空 @XStreamAlias("nonce_str") + @JSONField(name = "nonce_str") private String nonceStr;// 随机字符串 非空 private String sign;// 签名 非空 @XStreamAlias("device_info") + @JSONField(name = "device_info") private String deviceInfo;// 微信支付分配的终端设备号 可能为空 private String recall;// 是否需要继续调用接口 Y- 需要,N-不需要 @@ -54,7 +62,7 @@ public class ApiResult extends XmlResult { } public String getSubMchId() { - return subMchId; + return StringUtils.isNotBlank(subMchId) ? subMchId : null; } public void setSubMchId(String subMchId) { @@ -89,8 +97,9 @@ public class ApiResult extends XmlResult { return recall; } - public void setRecall(String recall) { - this.recall = recall; + @JSONField(deserialize = false, serialize = false) + public boolean getFormatRecall() { + return recall != null && recall.equalsIgnoreCase("y"); } @Override diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayNotifyV3.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayNotifyV3.java index b8b89159..a0203d48 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayNotifyV3.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayNotifyV3.java @@ -1,10 +1,9 @@ package com.foxinmy.weixin4j.mp.payment.v3; -import com.foxinmy.weixin4j.mp.payment.ApiResult; import com.thoughtworks.xstream.annotations.XStreamAlias; /** - * Native支付回调时POST的信息 + * V3 Native支付回调时POST的信息 * * @className PayNativeNotifyV3 * @author jy diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayResponseV3.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayResponseV3.java index d797d5d9..b991d56d 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayResponseV3.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/NativePayResponseV3.java @@ -3,14 +3,13 @@ package com.foxinmy.weixin4j.mp.payment.v3; import com.alibaba.fastjson.annotation.JSONField; import com.foxinmy.weixin4j.exception.PayException; import com.foxinmy.weixin4j.model.Consts; -import com.foxinmy.weixin4j.mp.payment.ApiResult; import com.foxinmy.weixin4j.mp.payment.PayUtil; import com.foxinmy.weixin4j.util.RandomUtil; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamOmitField; /** - * Native支付时的回调响应 + * V3 Native支付时的回调响应 * * @className NativePayResponseV3 * @author jy diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/Order.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/Order.java index b20c96dc..58e972c0 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/Order.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/Order.java @@ -2,7 +2,9 @@ package com.foxinmy.weixin4j.mp.payment.v3; 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.TradeState; import com.foxinmy.weixin4j.mp.type.TradeType; @@ -10,7 +12,7 @@ import com.foxinmy.weixin4j.util.DateUtil; import com.thoughtworks.xstream.annotations.XStreamAlias; /** - * 订单信息 + * V3订单信息 * * @className Order * @author jy @@ -26,39 +28,52 @@ public class Order extends ApiResult { // USERPAYING--用户支付中 NOPAY--未支付(输入密码或 确认支付超时) PAYERROR--支付失败(其他 原因,如银行返回失败) // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回 @XStreamAlias("trade_state") + @JSONField(name = "trade_state") private TradeState tradeState; // 用户标识ID @XStreamAlias("openid") + @JSONField(name = "openid") private String openId; // 用户是否关注公众账号,Y- 关注,N-未关注,仅在公众 账号类型支付有效 @XStreamAlias("is_subscribe") + @JSONField(name = "is_subscribe") private String isSubscribe; // 交易类型 @XStreamAlias("trade_type") + @JSONField(name = "trade_type") private TradeType tradeType; // 银行类型 @XStreamAlias("bank_type") + @JSONField(name = "bank_type") private String bankType; // 订单总金额,单位为分 @XStreamAlias("total_fee") + @JSONField(name = "total_fee") private int totalFee; // 现金券支付金额<=订单总金 额,订单总金额-现金券金额 为现金支付金额 @XStreamAlias("coupon_fee") - private int couponFee; + @JSONField(name = "coupon_fee") + private Integer couponFee; + @XStreamAlias("cash_fee") + @JSONField(name = "cash_fee") + private int cashFee; // 货币类型,符合 ISO 4217 标准的三位字母代码,默认人民币:CNY @XStreamAlias("fee_type") + @JSONField(name = "fee_type") private CurrencyType feeType; // 微信支付订单号 @XStreamAlias("transaction_id") + @JSONField(name = "transaction_id") private String transactionId; // 商户订单号 - @XStreamAlias("out_rade_no") + @XStreamAlias("out_trade_no") + @JSONField(name = "out_trade_no") private String outTradeNo; // 商家数据包 - @XStreamAlias("attach") private String attach; // 支付完成时间,格式为 yyyyMMddhhmmss @XStreamAlias("time_end") + @JSONField(name = "time_end") private String timeEnd; public TradeState getTradeState() { @@ -81,13 +96,8 @@ public class Order extends ApiResult { return bankType; } - /** - * 调用接口获取单位为分,get方法转换为元方便使用 - * - * @return 元单位 - */ - public double getTotalFee() { - return totalFee / 100d; + public int getTotalFee() { + return totalFee; } /** @@ -95,8 +105,37 @@ public class Order extends ApiResult { * * @return 元单位 */ - public double getCouponFee() { - return couponFee / 100d; + @JSONField(serialize = false, deserialize = false) + public double getFormatTotalFee() { + return totalFee / 100d; + } + + public Integer getCouponFee() { + return couponFee; + } + + /** + * 调用接口获取单位为分,get方法转换为元方便使用 + * + * @return 元单位 + */ + @JSONField(serialize = false, deserialize = false) + public double getFormatCouponFee() { + return couponFee != null ? couponFee / 100d : 0d; + } + + public int getCashFee() { + return cashFee; + } + + /** + * 调用接口获取单位为分,get方法转换为元方便使用 + * + * @return 元单位 + */ + @JSONField(serialize = false, deserialize = false) + public double getFormatCashFee() { + return cashFee / 100d; } public CurrencyType getFeeType() { @@ -112,10 +151,15 @@ public class Order extends ApiResult { } 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); } @@ -124,9 +168,13 @@ public class Order extends ApiResult { return "Order [tradeState=" + tradeState + ", openId=" + openId + ", isSubscribe=" + isSubscribe + ", tradeType=" + tradeType + ", bankType=" + bankType + ", totalFee=" + totalFee - + ", couponFee=" + couponFee + ", feeType=" + feeType - + ", transactionId=" + transactionId + ", outTradeNo=" - + outTradeNo + ", attach=" + attach + ", timeEnd=" + timeEnd - + ", " + super.toString() + "]"; + + ", couponFee=" + couponFee + ", cashFee=" + cashFee + + ", feeType=" + feeType + ", transactionId=" + transactionId + + ", outTradeNo=" + outTradeNo + ", attach=" + attach + + ", timeEnd=" + timeEnd + ", getFormatTotalFee()=" + + getFormatTotalFee() + ", getFormatCouponFee()=" + + getFormatCouponFee() + ", getFormatCashFee()=" + + getFormatCashFee() + ", getFormatTimeEnd()=" + + getFormatTimeEnd() + ", " + super.toString() + "]"; } } diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PayRequestV3.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PayRequestV3.java index de21e353..cfa8f499 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PayRequestV3.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PayRequestV3.java @@ -4,7 +4,7 @@ import com.foxinmy.weixin4j.exception.PayException; import com.foxinmy.weixin4j.mp.payment.PayRequest; /** - * JS支付:get_brand_wcpay_request
+ * V3 JS支付:get_brand_wcpay_request
*

* get_brand_wcpay_request:ok 支付成功
* get_brand_wcpay_request:cancel 支付过程中用户取消
diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PrePay.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PrePay.java index 9f00b640..2e4121e6 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PrePay.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/PrePay.java @@ -1,11 +1,10 @@ package com.foxinmy.weixin4j.mp.payment.v3; -import com.foxinmy.weixin4j.mp.payment.ApiResult; import com.foxinmy.weixin4j.mp.type.TradeType; import com.thoughtworks.xstream.annotations.XStreamAlias; /** - * 预订单信息 + * V3预订单信息 * * @className PrePay * @author jy diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundDetail.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundDetail.java new file mode 100644 index 00000000..b6c08c25 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundDetail.java @@ -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; + } + + /** + * 调用接口获取单位为分,get方法转换为元方便使用 + * + * @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; + } + + /** + * 调用接口获取单位为分,get方法转换为元方便使用 + * + * @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(); + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundRecord.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundRecord.java new file mode 100644 index 00000000..d0a225c2 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundRecord.java @@ -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 details; // 退款详情 + + public String getTransactionId() { + return transactionId; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + /** + * 调用接口获取单位为分,get方法转换为元方便使用 + * + * @return 元单位 + */ + @JSONField(serialize = false, deserialize = false) + public double getFormatCashFee() { + return cashFee / 100d; + } + + public int getCashFee() { + return cashFee; + } + + public int getCount() { + return count; + } + + public List getDetails() { + return details; + } + + public void setDetails(List details) { + this.details = details; + } + + @Override + public String toString() { + return "RefundRecord [transactionId=" + transactionId + ", outTradeNo=" + + outTradeNo + ", cashFee=" + cashFee + ", count=" + count + + ", details=" + details + ", " + super.toString() + "]"; + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundResult.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundResult.java new file mode 100644 index 00000000..c3de1e84 --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/payment/v3/RefundResult.java @@ -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() + "]"; + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/IdQuery.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/IdQuery.java index 0aaca163..a349f176 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/IdQuery.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/IdQuery.java @@ -37,4 +37,10 @@ public class IdQuery implements Serializable { this.id = id; this.type = idType; } + + @Override + public String toString() { + return String.format("%s=%s", type.getName(), id); + } + } diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/RefundStatus.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/RefundStatus.java index 9c996b99..e3b5c2d1 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/RefundStatus.java +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/RefundStatus.java @@ -10,7 +10,7 @@ package com.foxinmy.weixin4j.mp.type; * @see */ public enum RefundStatus { - SUCCES, // 退款成功 + SUCCESS, // 退款成功 FAIL, // 退款失败 PROCESSING, // 退款处理中 NOTSURE, // 未确定,需要商户 原退款单号重新发起 diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/RefundType.java b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/RefundType.java new file mode 100644 index 00000000..1faceb0d --- /dev/null +++ b/weixin4j-mp/weixin4j-mp-api/src/main/java/com/foxinmy/weixin4j/mp/type/RefundType.java @@ -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; + } +} diff --git a/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties b/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties index bd75d8c7..e4de9fe9 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties +++ b/weixin4j-mp/weixin4j-mp-api/src/main/resources/weixin.properties @@ -17,5 +17,7 @@ qr_path=/tmp/weixin/qr media_path=/tmp/weixin/media # \u5bf9\u8d26\u5355\u4fdd\u5b58\u8def\u5f84 bill_path=/tmp/weixin/bill -# ca\u8bc1\u4e66\u5b58\u653e\u7684\u5b8c\u6574\u8def\u5f84 -ca_file=/tmp/weixin/xxxxx.p12 | xxxxx.pfx \ No newline at end of file +# 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 +# classpath\u8def\u5f84\u4e0b\u53ef\u4ee5\u8fd9\u4e48\u5199 +ca_file=classpath:xxxxx.pfx \ No newline at end of file diff --git a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/UserTest.java b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/UserTest.java index 7f6f2a72..2b375dee 100644 --- a/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/UserTest.java +++ b/weixin4j-mp/weixin4j-mp-api/src/test/java/com/foxinmy/weixin4j/mp/test/UserTest.java @@ -32,7 +32,7 @@ public class UserTest extends TokenTest { User user = userApi.getUser("owGBft_vbBbOaQOmpEUE4xDLeRSU"); Assert.assertNotNull(user); System.out.println(user); - following(); + // following(); } @Test diff --git a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/PayAction.java b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/PayAction.java index 888dabf1..58592289 100644 --- a/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/PayAction.java +++ b/weixin4j-mp/weixin4j-mp-server/src/main/java/com/foxinmy/weixin4j/mp/action/PayAction.java @@ -50,8 +50,8 @@ public class PayAction { JSONObject obj = new JSONObject(); WeixinMpAccount weixinAccount = ConfigUtil.getWeixinMpAccount(); // V3 支付 - PayPackage payPackage = new PayPackageV3(weixinAccount, "用户openid", "商品描述", - "系统内部订单号", 1d, "IP地址", TradeType.JSAPI); + PayPackage payPackage = new PayPackageV3(weixinAccount, "用户openid", + "商品描述", "系统内部订单号", 1d, "IP地址", TradeType.JSAPI); // V2 支付 payPackage = new PayPackageV2("商品描述", weixinAccount.getPartnerId(), "系统内部订单号", 1d, "回调地址", "IP地址"); @@ -163,7 +163,7 @@ public class PayAction { if (!sign.equals(valid_sign)) { return XmlStream.to(new XmlResult(Consts.FAIL, "签名错误")); } - return XmlStream.to(new XmlResult()); + return XmlStream.to(new XmlResult(Consts.SUCCESS, "")); } /** diff --git a/weixin4j-qy/README.md b/weixin4j-qy/README.md index 0a853053..77ae30b0 100644 --- a/weixin4j-qy/README.md +++ b/weixin4j-qy/README.md @@ -91,10 +91,14 @@ weixin4j-qy * 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**: 新增批量删除员工接口 diff --git a/weixin4j-qy/weixin4j-qy-api/README.md b/weixin4j-qy/weixin4j-qy-api/README.md index 2e1eba04..915be955 100644 --- a/weixin4j-qy/weixin4j-qy-api/README.md +++ b/weixin4j-qy/weixin4j-qy-api/README.md @@ -63,3 +63,7 @@ weixin.properties说明 + 增加`批量获取用户详情`的接口 + 新增`获取微信服务器IP`接口 + +* 2015-01-04 + + + 新增批量删除员工接口 diff --git a/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/WeixinProxy.java b/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/WeixinProxy.java index 548a220b..674029e6 100644 --- a/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/WeixinProxy.java +++ b/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/WeixinProxy.java @@ -231,6 +231,23 @@ public class WeixinProxy { return userApi.deleteUser(userid); } + /** + * 批量删除成员 + * + * @param userIds + * 成员列表 + * @see
批量删除成员说明 userIds) + throws WeixinException { + return userApi.batchDeleteUser(userIds); + } + /** * 创建标签(创建的标签属于管理组;默认为未加锁状态) * diff --git a/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/UserApi.java b/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/UserApi.java index 1b114a4a..e7548869 100644 --- a/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/UserApi.java +++ b/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/UserApi.java @@ -200,6 +200,28 @@ public class UserApi extends QyApi { return response.getAsJsonResult(); } + /** + * 批量删除成员 + * + * @param userIds + * 成员列表 + * @see 批量删除成员说明 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对应员工的管理权限) * diff --git a/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/weixin.properties b/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/weixin.properties index f6a5def3..8df836c3 100644 --- a/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/weixin.properties +++ b/weixin4j-qy/weixin4j-qy-api/src/main/java/com/foxinmy/weixin4j/qy/api/weixin.properties @@ -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 # \u5220\u9664\u6210\u5458 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 user_authsucc_uri={api_base_url}/user/authsucc?access_token=%s&userid=%s # \u521b\u5efa\u6807\u7b7e