@@ -43,6 +43,11 @@ public class WxEntPayQueryRequest extends WxPayBaseRequest { | |||
this.partnerTradeNo = partnerTradeNo; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
//do nothing | |||
} | |||
@Override | |||
public String toString() { | |||
return ToStringUtils.toSimpleString(this); | |||
@@ -154,6 +154,11 @@ public class WxEntPayRequest extends WxPayBaseRequest { | |||
private String spbillCreateIp; | |||
@Override | |||
protected void checkConstraints() { | |||
} | |||
@Override | |||
public String getAppid() { | |||
return this.mchAppid; | |||
@@ -1,9 +1,14 @@ | |||
package com.github.binarywang.wxpay.bean.request; | |||
import com.github.binarywang.wxpay.config.WxPayConfig; | |||
import com.github.binarywang.wxpay.util.SignUtils; | |||
import com.thoughtworks.xstream.XStream; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.util.BeanUtils; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.math.BigDecimal; | |||
@@ -23,6 +28,22 @@ import java.math.BigDecimal; | |||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
*/ | |||
public abstract class WxPayBaseRequest { | |||
/** | |||
* 检查请求参数内容,包括必填参数以及特殊约束 | |||
*/ | |||
protected void checkFields() throws WxErrorException { | |||
//check required fields | |||
BeanUtils.checkRequiredFields(this); | |||
//check other parameters | |||
this.checkConstraints(); | |||
} | |||
/** | |||
* 检查约束情况 | |||
*/ | |||
protected abstract void checkConstraints(); | |||
/** | |||
* <pre> | |||
* 公众账号ID | |||
@@ -180,4 +201,41 @@ public abstract class WxPayBaseRequest { | |||
xstream.processAnnotations(this.getClass()); | |||
return xstream.toXML(this); | |||
} | |||
/** | |||
* <pre> | |||
* 检查参数,并设置签名 | |||
* 1、检查参数(注意:子类实现需要检查参数的而外功能时,请在调用父类的方法前进行相应判断) | |||
* 2、补充系统参数,如果未传入则从配置里读取 | |||
* 3、生成签名,并设置进去 | |||
* </pre> | |||
* @param config 支付配置对象,用于读取相应系统配置信息 | |||
*/ | |||
public void checkAndSign(WxPayConfig config) throws WxErrorException { | |||
this.checkFields(); | |||
if (StringUtils.isBlank(getAppid())) { | |||
this.setAppid(config.getAppId()); | |||
} | |||
if (StringUtils.isBlank(getMchId())) { | |||
this.setMchId(config.getMchId()); | |||
} | |||
if (StringUtils.isBlank(getSubAppId())) { | |||
this.setSubAppId(config.getSubAppId()); | |||
} | |||
if (StringUtils.isBlank(getSubMchId())) { | |||
this. setSubMchId(config.getSubMchId()); | |||
} | |||
if (StringUtils.isBlank(getNonceStr())) { | |||
this.setNonceStr(String.valueOf(System.currentTimeMillis())); | |||
} | |||
//设置签名字段的值 | |||
this.setSign(SignUtils.createSign(this, config.getMchKey())); | |||
} | |||
} |
@@ -2,6 +2,10 @@ package com.github.binarywang.wxpay.bean.request; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import me.chanjar.weixin.common.annotation.Required; | |||
import org.apache.commons.lang3.ArrayUtils; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.util.Arrays; | |||
/** | |||
* <pre> | |||
@@ -12,6 +16,8 @@ import me.chanjar.weixin.common.annotation.Required; | |||
*/ | |||
@XStreamAlias("xml") | |||
public class WxPayDownloadBillRequest extends WxPayBaseRequest { | |||
private static final String[] BILL_TYPE = new String[]{"ALL", "REFUND", "SUCCESS"}; | |||
/** | |||
* <pre> | |||
* 设备号 | |||
@@ -120,4 +126,16 @@ public class WxPayDownloadBillRequest extends WxPayBaseRequest { | |||
public void setTarType(String tarType) { | |||
this.tarType = tarType; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
if (StringUtils.isNotBlank(this.getTarType()) && !"GZIP".equals(this.getTarType())) { | |||
throw new IllegalArgumentException("tar_type值如果存在,只能为GZIP"); | |||
} | |||
if (!ArrayUtils.contains(BILL_TYPE, this.getBillType())) { | |||
throw new IllegalArgumentException(String.format("bill_tpye目前必须为%s其中之一,实际值:%s", | |||
Arrays.toString(BILL_TYPE), this.getBillType())); | |||
} | |||
} | |||
} |
@@ -261,6 +261,11 @@ public class WxPayMicropayRequest extends WxPayBaseRequest { | |||
this.authCode = authCode; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
//do nothing | |||
} | |||
public static final class Builder { | |||
private String signType; | |||
private String body; | |||
@@ -33,4 +33,8 @@ public class WxPayOrderCloseRequest extends WxPayBaseRequest { | |||
this.outTradeNo = outTradeNo; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
package com.github.binarywang.wxpay.bean.request; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import org.apache.commons.lang3.StringUtils; | |||
/** | |||
* <pre> | |||
@@ -61,4 +62,12 @@ public class WxPayOrderQueryRequest extends WxPayBaseRequest { | |||
public void setOutTradeNo(String outTradeNo) { | |||
this.outTradeNo = outTradeNo; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
if ((StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo)) || | |||
(StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo))) { | |||
throw new IllegalArgumentException("transaction_id 和 out_trade_no 不能同时存在或同时为空,必须二选一"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,163 @@ | |||
package com.github.binarywang.wxpay.bean.request; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import org.apache.commons.lang3.StringUtils; | |||
/** | |||
* <pre> | |||
* 撤销订单请求类 | |||
* Created by Binary Wang on 2017-3-23. | |||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
* </pre> | |||
*/ | |||
@XStreamAlias("xml") | |||
public class WxPayOrderReverseRequest extends WxPayBaseRequest { | |||
/** | |||
* <pre> | |||
* 微信订单号 | |||
* transaction_id | |||
* String(28) | |||
* 1217752501201400000000000000 | |||
* 微信的订单号,优先使用 | |||
* </pre> | |||
*/ | |||
@XStreamAlias("transaction_id") | |||
private String transactionId; | |||
/** | |||
* <pre> | |||
* 商户订单号 | |||
* out_trade_no | |||
* String(32) | |||
* 1217752501201400000000000000 | |||
* 商户系统内部的订单号 | |||
* transaction_id、out_trade_no二选一,如果同时存在优先级:transaction_id> out_trade_no | |||
* </pre> | |||
*/ | |||
@XStreamAlias("out_trade_no") | |||
private String outTradeNo; | |||
/** | |||
* <pre> | |||
* 签名类型 | |||
* sign_type | |||
* 否 | |||
* String(32) | |||
* HMAC-SHA256 | |||
* 签名类型,目前支持HMAC-SHA256和MD5,默认为MD5 | |||
**/ | |||
@XStreamAlias("sign_type") | |||
private String signType; | |||
private WxPayOrderReverseRequest(Builder builder) { | |||
setTransactionId(builder.transactionId); | |||
setAppid(builder.appid); | |||
setOutTradeNo(builder.outTradeNo); | |||
setMchId(builder.mchId); | |||
setSignType(builder.signType); | |||
setSubAppId(builder.subAppId); | |||
setSubMchId(builder.subMchId); | |||
setNonceStr(builder.nonceStr); | |||
setSign(builder.sign); | |||
} | |||
public static Builder newBuilder() { | |||
return new Builder(); | |||
} | |||
public String getTransactionId() { | |||
return this.transactionId; | |||
} | |||
public void setTransactionId(String transactionId) { | |||
this.transactionId = transactionId; | |||
} | |||
public String getOutTradeNo() { | |||
return this.outTradeNo; | |||
} | |||
public void setOutTradeNo(String outTradeNo) { | |||
this.outTradeNo = outTradeNo; | |||
} | |||
public String getSignType() { | |||
return this.signType; | |||
} | |||
public void setSignType(String signType) { | |||
this.signType = signType; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
if (StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo)) { | |||
throw new IllegalArgumentException("transaction_id 和 out_trade_no不能同时为空!"); | |||
} | |||
} | |||
public static final class Builder { | |||
private String transactionId; | |||
private String appid; | |||
private String outTradeNo; | |||
private String mchId; | |||
private String signType; | |||
private String subAppId; | |||
private String subMchId; | |||
private String nonceStr; | |||
private String sign; | |||
private Builder() { | |||
} | |||
public Builder transactionId(String transactionId) { | |||
this.transactionId = transactionId; | |||
return this; | |||
} | |||
public Builder appid(String appid) { | |||
this.appid = appid; | |||
return this; | |||
} | |||
public Builder outTradeNo(String outTradeNo) { | |||
this.outTradeNo = outTradeNo; | |||
return this; | |||
} | |||
public Builder mchId(String mchId) { | |||
this.mchId = mchId; | |||
return this; | |||
} | |||
public Builder signType(String signType) { | |||
this.signType = signType; | |||
return this; | |||
} | |||
public Builder subAppId(String subAppId) { | |||
this.subAppId = subAppId; | |||
return this; | |||
} | |||
public Builder subMchId(String subMchId) { | |||
this.subMchId = subMchId; | |||
return this; | |||
} | |||
public Builder nonceStr(String nonceStr) { | |||
this.nonceStr = nonceStr; | |||
return this; | |||
} | |||
public Builder sign(String sign) { | |||
this.sign = sign; | |||
return this; | |||
} | |||
public WxPayOrderReverseRequest build() { | |||
return new WxPayOrderReverseRequest(this); | |||
} | |||
} | |||
} |
@@ -54,4 +54,9 @@ public class WxPayRedpackQueryRequest extends WxPayBaseRequest { | |||
public void setMchBillNo(String mchBillNo) { | |||
this.mchBillNo = mchBillNo; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
} | |||
} |
@@ -1,6 +1,7 @@ | |||
package com.github.binarywang.wxpay.bean.request; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import org.apache.commons.lang3.StringUtils; | |||
/** | |||
* <pre> | |||
@@ -132,4 +133,15 @@ public class WxPayRefundQueryRequest extends WxPayBaseRequest { | |||
public void setRefundId(String refundId) { | |||
this.refundId = refundId; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
if ((StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo) | |||
&& StringUtils.isBlank(outRefundNo) && StringUtils.isBlank(refundId)) || | |||
(StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo) | |||
&& StringUtils.isNotBlank(outRefundNo) && StringUtils.isNotBlank(refundId))) { | |||
throw new IllegalArgumentException("transaction_id,out_trade_no,out_refund_no,refund_id 必须四选一"); | |||
} | |||
} | |||
} |
@@ -1,7 +1,13 @@ | |||
package com.github.binarywang.wxpay.bean.request; | |||
import com.github.binarywang.wxpay.config.WxPayConfig; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import me.chanjar.weixin.common.annotation.Required; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.apache.commons.lang3.ArrayUtils; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.util.Arrays; | |||
/** | |||
* <pre> | |||
@@ -13,13 +19,25 @@ import me.chanjar.weixin.common.annotation.Required; | |||
* <li>类型 | |||
* <li>示例值 | |||
* <li>描述 | |||
* Created by Binary Wang on 2016-10-08. | |||
* </pre> | |||
* | |||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
* Created by Binary Wang on 2016-10-08. | |||
*/ | |||
@XStreamAlias("xml") | |||
public class WxPayRefundRequest extends WxPayBaseRequest { | |||
private static final String[] REFUND_ACCOUNT = new String[]{"REFUND_SOURCE_RECHARGE_FUNDS", | |||
"REFUND_SOURCE_UNSETTLED_FUNDS"}; | |||
@Override | |||
public void checkAndSign(WxPayConfig config) throws WxErrorException { | |||
if (StringUtils.isBlank(this.getOpUserId())) { | |||
this.setOpUserId(config.getMchId()); | |||
} | |||
super.checkAndSign(config); | |||
} | |||
/** | |||
* <pre> | |||
* 设备号 | |||
@@ -240,6 +258,20 @@ public class WxPayRefundRequest extends WxPayBaseRequest { | |||
this.refundAccount = refundAccount; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
if (StringUtils.isNotBlank(this.getRefundAccount())) { | |||
if (!ArrayUtils.contains(REFUND_ACCOUNT, this.getRefundAccount())) { | |||
throw new IllegalArgumentException(String.format("refund_account目前必须为%s其中之一,实际值:%s", | |||
Arrays.toString(REFUND_ACCOUNT), this.getRefundAccount())); | |||
} | |||
} | |||
if (StringUtils.isBlank(this.getOutTradeNo()) && StringUtils.isBlank(this.getTransactionId())) { | |||
throw new IllegalArgumentException("transaction_id 和 out_trade_no 不能同时为空,必须提供一个"); | |||
} | |||
} | |||
public static final class Builder { | |||
private String deviceInfo; | |||
private String appid; | |||
@@ -270,4 +270,9 @@ public class WxPayReportRequest extends WxPayBaseRequest { | |||
public void setTime(String time) { | |||
this.time = time; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
//do nothing | |||
} | |||
} |
@@ -217,6 +217,11 @@ public class WxPaySendRedpackRequest extends WxPayBaseRequest { | |||
this.remark = remark; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
} | |||
@Override | |||
public String getAppid() { | |||
return this.wxAppid; | |||
@@ -1,7 +1,13 @@ | |||
package com.github.binarywang.wxpay.bean.request; | |||
import com.github.binarywang.wxpay.config.WxPayConfig; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import me.chanjar.weixin.common.annotation.Required; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.apache.commons.lang3.ArrayUtils; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.util.Arrays; | |||
/** | |||
* <pre> | |||
@@ -21,6 +27,7 @@ import me.chanjar.weixin.common.annotation.Required; | |||
*/ | |||
@XStreamAlias("xml") | |||
public class WxPayUnifiedOrderRequest extends WxPayBaseRequest { | |||
private static final String[] TRADE_TYPES = new String[]{"JSAPI", "NATIVE", "APP"}; | |||
/** | |||
* <pre> | |||
@@ -409,6 +416,35 @@ public class WxPayUnifiedOrderRequest extends WxPayBaseRequest { | |||
this.openid = openid; | |||
} | |||
@Override | |||
protected void checkConstraints() { | |||
if (!ArrayUtils.contains(TRADE_TYPES, this.getTradeType())) { | |||
throw new IllegalArgumentException(String.format("trade_type目前必须为%s其中之一,实际值:%s", | |||
Arrays.toString(TRADE_TYPES), this.getTradeType())); | |||
} | |||
if ("JSAPI".equals(this.getTradeType()) && this.getOpenid() == null) { | |||
throw new IllegalArgumentException("当 trade_type是'JSAPI'时未指定openid"); | |||
} | |||
if ("NATIVE".equals(this.getTradeType()) && this.getProductId() == null) { | |||
throw new IllegalArgumentException("当 trade_type是'NATIVE'时未指定product_id"); | |||
} | |||
} | |||
@Override | |||
public void checkAndSign(WxPayConfig config) throws WxErrorException { | |||
if (StringUtils.isBlank(this.getNotifyURL())) { | |||
this.setNotifyURL(config.getNotifyUrl()); | |||
} | |||
if (StringUtils.isBlank(this.getTradeType())) { | |||
this.setTradeType(config.getTradeType()); | |||
} | |||
super.checkAndSign(config); | |||
} | |||
public static class WxUnifiedOrderRequestBuilder { | |||
private String appid; | |||
private String mchId; | |||
@@ -1,9 +1,13 @@ | |||
package com.github.binarywang.wxpay.bean.result; | |||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; | |||
import com.github.binarywang.wxpay.util.SignUtils; | |||
import com.google.common.base.Joiner; | |||
import com.google.common.collect.Maps; | |||
import com.thoughtworks.xstream.XStream; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.util.ToStringUtils; | |||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||
import org.apache.commons.lang3.StringUtils; | |||
@@ -300,4 +304,44 @@ public abstract class WxPayBaseResult { | |||
return Integer.valueOf(result); | |||
} | |||
/** | |||
* 校验返回结果签名 | |||
*/ | |||
public void checkResult(WxPayServiceImpl wxPayService) throws WxErrorException { | |||
//校验返回结果签名 | |||
Map<String, String> map = toMap(); | |||
if (getSign() != null && !SignUtils.checkSign(map, wxPayService.getConfig().getMchKey())) { | |||
this.getLogger().debug("校验结果签名失败,参数:{}", map); | |||
throw new WxErrorException(WxError.newBuilder().setErrorCode(-1).setErrorMsg("参数格式校验错误!").build()); | |||
} | |||
//校验结果是否成功 | |||
if (!"SUCCESS".equalsIgnoreCase(getReturnCode()) | |||
|| !"SUCCESS".equalsIgnoreCase(getResultCode())) { | |||
StringBuilder errorMsg = new StringBuilder(); | |||
if (getReturnCode() != null) { | |||
errorMsg.append("返回代码:").append(getReturnCode()); | |||
} | |||
if (getReturnMsg() != null) { | |||
errorMsg.append(",返回信息:").append(getReturnMsg()); | |||
} | |||
if (getResultCode() != null) { | |||
errorMsg.append(",结果代码:").append(getResultCode()); | |||
} | |||
if (getErrCode() != null) { | |||
errorMsg.append(",错误代码:").append(getErrCode()); | |||
} | |||
if (getErrCodeDes() != null) { | |||
errorMsg.append(",错误详情:").append(getErrCodeDes()); | |||
} | |||
WxError error = WxError.newBuilder() | |||
.setErrorCode(-1) | |||
.setErrorMsg(errorMsg.toString()) | |||
.build(); | |||
this.getLogger().error("\n结果业务代码异常,返回結果:{},\n{}", map, error); | |||
throw new WxErrorException(error); | |||
} | |||
} | |||
} |
@@ -0,0 +1,34 @@ | |||
package com.github.binarywang.wxpay.bean.result; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
/** | |||
* <pre> | |||
* 撤销订单响应结果类 | |||
* Created by Binary Wang on 2017-3-23. | |||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
* </pre> | |||
*/ | |||
public class WxPayOrderReverseResult extends WxPayBaseResult { | |||
/** | |||
* <pre> | |||
* 是否重调 | |||
* recall | |||
* 是 | |||
* String(1) | |||
* Y | |||
* 是否需要继续调用撤销,Y-需要,N-不需要 | |||
* </pre> | |||
**/ | |||
@XStreamAlias("recall") | |||
private String isRecall; | |||
public String getIsRecall() { | |||
return this.isRecall; | |||
} | |||
public void setIsRecall(String isRecall) { | |||
this.isRecall = isRecall; | |||
} | |||
} |
@@ -116,84 +116,6 @@ public interface WxPayService { | |||
*/ | |||
WxPayOrderNotifyResult getOrderNotifyResult(String xmlData) throws WxErrorException; | |||
/** | |||
* 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3) | |||
* | |||
* @param xmlbean Bean需要标记有XML注解,默认使用配置中的PartnerKey进行签名 | |||
* @return 签名字符串 | |||
* @see #createSign(Map, String) | |||
* @since 2.5.0 | |||
*/ | |||
String createSign(Object xmlbean); | |||
/** | |||
* 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3) | |||
* | |||
* @param xmlbean Bean需要标记有XML注解 | |||
* @param signKey 签名Key | |||
* @return 签名字符串 | |||
* @see #createSign(Map, String) | |||
*/ | |||
String createSign(Object xmlbean, String signKey); | |||
/** | |||
* 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3) | |||
* | |||
* @param prams 参数信息,默认使用配置中的PartnerKey进行签名 | |||
* @return 签名字符串 | |||
* @see #createSign(Map, String) | |||
*/ | |||
String createSign(Map<String, String> prams); | |||
/** | |||
* 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3) | |||
* | |||
* @param prams 参数信息 | |||
* @param signKey 签名Key | |||
* @return 签名字符串 | |||
*/ | |||
String createSign(Map<String, String> prams, String signKey); | |||
/** | |||
* 校验签名是否正确,默认使用配置中的PartnerKey进行签名 | |||
* | |||
* @param xmlbean Bean需要标记有XML注解 | |||
* @return true - 签名校验成功,false - 签名校验失败 | |||
* @see #checkSign(Map, String) | |||
*/ | |||
boolean checkSign(Object xmlbean); | |||
/** | |||
* 校验签名是否正确 | |||
* | |||
* @param xmlbean Bean需要标记有XML注解 | |||
* @param signKey 校验的签名Key | |||
* @return true - 签名校验成功,false - 签名校验失败 | |||
* @see #checkSign(Map, String) | |||
*/ | |||
boolean checkSign(Object xmlbean, String signKey); | |||
/** | |||
* 校验签名是否正确,默认使用配置中的PartnerKey进行签名 | |||
* | |||
* @param prams 需要校验的参数Map | |||
* @return true - 签名校验成功,false - 签名校验失败 | |||
* @see #checkSign(Map, String) | |||
*/ | |||
boolean checkSign(Map<String, String> prams); | |||
/** | |||
* 校验签名是否正确 | |||
* | |||
* @param params 需要校验的参数Map | |||
* @param signKey 校验的签名Key | |||
* @return true - 签名校验成功,false - 签名校验失败 | |||
* @see #checkSign(Map, String) | |||
*/ | |||
boolean checkSign(Map<String, String> params, String signKey); | |||
/** | |||
* 发送微信红包给个人用户 | |||
* <pre> | |||
@@ -271,6 +193,7 @@ public interface WxPayService { | |||
* 其中XXXXX为商户需要填写的内容,商户将该链接生成二维码,如需要打印发布二维码,需要采用此格式。商户可调用第三方库生成二维码图片。 | |||
* 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4 | |||
* </pre> | |||
* | |||
* @param productId 产品Id | |||
* @return 生成的二维码URL连接 | |||
*/ | |||
@@ -328,6 +251,7 @@ public interface WxPayService { | |||
/** | |||
* <pre> | |||
* 提交刷卡支付 | |||
* 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1 | |||
* 应用场景: | |||
* 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,由商户收银台或者商户后台调用该接口发起支付。 | |||
* 提醒1:提交支付请求后微信会同步返回支付结果。当返回结果为“系统错误”时,商户系统等待5秒后调用【查询订单API】,查询支付实际交易结果;当返回结果为“USERPAYING”时,商户系统可设置间隔时间(建议10秒)重新查询支付结果,直到支付成功或超时(建议30秒); | |||
@@ -337,4 +261,18 @@ public interface WxPayService { | |||
* </pre> | |||
*/ | |||
WxPayMicropayResult micropay(WxPayMicropayRequest request) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 撤销订单API | |||
* 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_11&index=3 | |||
* 应用场景: | |||
* 支付交易返回失败或支付系统超时,调用该接口撤销交易。如果此订单用户支付失败,微信支付系统会将此订单关闭;如果用户支付成功,微信支付系统会将此订单资金退还给用户。 | |||
* 注意:7天以内的交易单可调用撤销,其他正常支付的单如需实现相同功能请调用申请退款API。提交支付交易后调用【查询订单API】,没有明确的支付结果再调用【撤销订单API】。 | |||
* 调用支付接口后请勿立即调用撤销订单API,建议支付后至少15s后再调用撤销订单接口。 | |||
* 接口链接 :https://api.mch.weixin.qq.com/secapi/pay/reverse | |||
* 是否需要证书:请求需要双向证书。 | |||
*</pre> | |||
*/ | |||
WxPayOrderReverseResult reverseOrder(WxPayOrderReverseRequest request) throws WxErrorException; | |||
} |
@@ -5,14 +5,12 @@ import com.github.binarywang.wxpay.bean.request.*; | |||
import com.github.binarywang.wxpay.bean.result.*; | |||
import com.github.binarywang.wxpay.config.WxPayConfig; | |||
import com.github.binarywang.wxpay.service.WxPayService; | |||
import com.github.binarywang.wxpay.util.SignUtils; | |||
import com.google.common.collect.Maps; | |||
import jodd.http.HttpRequest; | |||
import jodd.http.HttpResponse; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.util.BeanUtils; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.apache.commons.lang3.ArrayUtils; | |||
import org.apache.commons.lang3.CharEncoding; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.Consts; | |||
@@ -30,7 +28,8 @@ import org.slf4j.LoggerFactory; | |||
import javax.net.ssl.SSLContext; | |||
import java.io.File; | |||
import java.io.UnsupportedEncodingException; | |||
import java.util.*; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
/** | |||
* Created by Binary Wang on 2016/7/28. | |||
@@ -38,12 +37,7 @@ import java.util.*; | |||
* @author binarywang (https://github.com/binarywang) | |||
*/ | |||
public class WxPayServiceImpl implements WxPayService { | |||
private static final String PAY_BASE_URL = "https://api.mch.weixin.qq.com"; | |||
private static final String[] TRADE_TYPES = new String[]{"JSAPI", "NATIVE", "APP"}; | |||
private static final String[] REFUND_ACCOUNT = new String[]{"REFUND_SOURCE_RECHARGE_FUNDS", "REFUND_SOURCE_UNSETTLED_FUNDS"}; | |||
private static final String[] BILL_TYPE = new String[]{"ALL", "REFUND", "SUCCESS"}; | |||
private final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
private WxPayConfig config; | |||
@@ -62,126 +56,47 @@ public class WxPayServiceImpl implements WxPayService { | |||
if (this.getConfig().useSandboxForWxPay()) { | |||
return PAY_BASE_URL + "/sandboxnew"; | |||
} | |||
return PAY_BASE_URL; | |||
} | |||
@Override | |||
public WxPayRefundResult refund(WxPayRefundRequest request) throws WxErrorException { | |||
this.initRequest(request); | |||
if (StringUtils.isBlank(request.getOpUserId())) { | |||
request.setOpUserId(this.getConfig().getMchId()); | |||
} | |||
this.checkParameters(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/secapi/pay/refund"; | |||
String responseContent = this.postWithKey(url, request.toXML()); | |||
WxPayRefundResult result = WxPayRefundResult.fromXML(responseContent, WxPayRefundResult.class); | |||
this.checkResult(result); | |||
WxPayRefundResult result = WxPayBaseResult.fromXML(responseContent, WxPayRefundResult.class); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
@Override | |||
public WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, | |||
String outRefundNo, String refundId) | |||
public WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, String outRefundNo, String refundId) | |||
throws WxErrorException { | |||
if ((StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo) | |||
&& StringUtils.isBlank(outRefundNo) && StringUtils.isBlank(refundId)) || | |||
(StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo) | |||
&& StringUtils.isNotBlank(outRefundNo) && StringUtils.isNotBlank(refundId))) { | |||
throw new IllegalArgumentException("transaction_id , out_trade_no,out_refund_no, refund_id 必须四选一"); | |||
} | |||
WxPayRefundQueryRequest request = new WxPayRefundQueryRequest(); | |||
this.initRequest(request); | |||
request.setOutTradeNo(StringUtils.trimToNull(outTradeNo)); | |||
request.setTransactionId(StringUtils.trimToNull(transactionId)); | |||
request.setOutRefundNo(StringUtils.trimToNull(outRefundNo)); | |||
request.setRefundId(StringUtils.trimToNull(refundId)); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/pay/refundquery"; | |||
String responseContent = this.post(url, request.toXML()); | |||
WxPayRefundQueryResult result = WxPayRefundQueryResult.fromXML(responseContent, WxPayRefundQueryResult.class); | |||
WxPayRefundQueryResult result = WxPayBaseResult.fromXML(responseContent, WxPayRefundQueryResult.class); | |||
result.composeRefundRecords(); | |||
this.checkResult(result); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
private void checkResult(WxPayBaseResult result) throws WxErrorException { | |||
//校验返回结果签名 | |||
Map<String, String> map = result.toMap(); | |||
if (result.getSign() != null && !this.checkSign(map)) { | |||
log.debug("校验结果签名失败,参数:{}", map); | |||
throw new WxErrorException(WxError.newBuilder().setErrorCode(-1).setErrorMsg("参数格式校验错误!").build()); | |||
} | |||
//校验结果是否成功 | |||
if (!"SUCCESS".equalsIgnoreCase(result.getReturnCode()) | |||
|| !"SUCCESS".equalsIgnoreCase(result.getResultCode())) { | |||
StringBuilder errorMsg = new StringBuilder(); | |||
if (result.getReturnCode() != null) { | |||
errorMsg.append("返回代码:").append(result.getReturnCode()); | |||
} | |||
if (result.getReturnMsg() != null) { | |||
errorMsg.append(",返回信息:").append(result.getReturnMsg()); | |||
} | |||
if (result.getResultCode() != null) { | |||
errorMsg.append(",结果代码:").append(result.getResultCode()); | |||
} | |||
if (result.getErrCode() != null) { | |||
errorMsg.append(",错误代码:").append(result.getErrCode()); | |||
} | |||
if (result.getErrCodeDes() != null) { | |||
errorMsg.append(",错误详情:").append(result.getErrCodeDes()); | |||
} | |||
WxError error = WxError.newBuilder() | |||
.setErrorCode(-1) | |||
.setErrorMsg(errorMsg.toString()) | |||
.build(); | |||
log.error("\n结果业务代码异常,返回結果:{},\n{}", map, error); | |||
throw new WxErrorException(error); | |||
} | |||
} | |||
private void checkParameters(WxPayDownloadBillRequest request) throws WxErrorException { | |||
BeanUtils.checkRequiredFields(request); | |||
if (StringUtils.isNotBlank(request.getTarType()) && !"GZIP".equals(request.getTarType())) { | |||
throw new IllegalArgumentException("tar_type值如果存在,只能为GZIP"); | |||
} | |||
if (!ArrayUtils.contains(BILL_TYPE, request.getBillType())) { | |||
throw new IllegalArgumentException("bill_tpye目前必须为" + Arrays.toString(BILL_TYPE) | |||
+ "其中之一,实际值:" + request.getBillType()); | |||
} | |||
} | |||
private void checkParameters(WxPayRefundRequest request) throws WxErrorException { | |||
BeanUtils.checkRequiredFields(request); | |||
if (StringUtils.isNotBlank(request.getRefundAccount())) { | |||
if (!ArrayUtils.contains(REFUND_ACCOUNT, request.getRefundAccount())) { | |||
throw new IllegalArgumentException("refund_account目前必须为" + Arrays.toString(REFUND_ACCOUNT) | |||
+ "其中之一,实际值:" + request.getRefundAccount()); | |||
} | |||
} | |||
if (StringUtils.isBlank(request.getOutTradeNo()) && StringUtils.isBlank(request.getTransactionId())) { | |||
throw new IllegalArgumentException("transaction_id 和 out_trade_no 不能同时为空,必须提供一个"); | |||
} | |||
} | |||
@Override | |||
public WxPayOrderNotifyResult getOrderNotifyResult(String xmlData) throws WxErrorException { | |||
try { | |||
log.debug("微信支付回调参数详细:{}", xmlData); | |||
WxPayOrderNotifyResult result = WxPayOrderNotifyResult.fromXML(xmlData); | |||
log.debug("微信支付回调结果对象:{}", result); | |||
this.checkResult(result); | |||
result.checkResult(this); | |||
return result; | |||
} catch (WxErrorException e) { | |||
log.error(e.getMessage(), e); | |||
@@ -193,18 +108,17 @@ public class WxPayServiceImpl implements WxPayService { | |||
} | |||
@Override | |||
public WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request) | |||
throws WxErrorException { | |||
this.initRequest(request); | |||
request.setSign(this.createSign(request)); | |||
public WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request) throws WxErrorException { | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/mmpaymkttransfers/sendredpack"; | |||
if (request.getAmtType() != null) { | |||
//裂变红包 | |||
url = this.getPayBaseUrl() + "/mmpaymkttransfers/sendgroupredpack"; | |||
} | |||
String responseContent = this.postWithKey(url, request.toXML()); | |||
WxPaySendRedpackResult result = WxPaySendRedpackResult.fromXML(responseContent, WxPaySendRedpackResult.class); | |||
WxPaySendRedpackResult result = WxPayBaseResult.fromXML(responseContent, WxPaySendRedpackResult.class); | |||
//毋须校验,因为没有返回签名信息 | |||
// this.checkResult(result); | |||
return result; | |||
@@ -215,28 +129,21 @@ public class WxPayServiceImpl implements WxPayService { | |||
WxPayRedpackQueryRequest request = new WxPayRedpackQueryRequest(); | |||
request.setMchBillNo(mchBillNo); | |||
request.setBillType("MCHT"); | |||
initRequest(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/mmpaymkttransfers/gethbinfo"; | |||
String responseContent = this.postWithKey(url, request.toXML()); | |||
WxPayRedpackQueryResult result = WxPayRedpackQueryResult.fromXML(responseContent, WxPayRedpackQueryResult.class); | |||
this.checkResult(result); | |||
WxPayRedpackQueryResult result = WxPayBaseResult.fromXML(responseContent, WxPayRedpackQueryResult.class); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
@Override | |||
public WxPayOrderQueryResult queryOrder(String transactionId, String outTradeNo) throws WxErrorException { | |||
if ((StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo)) || | |||
(StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo))) { | |||
throw new IllegalArgumentException("transaction_id 和 out_trade_no 不能同时存在或同时为空,必须二选一"); | |||
} | |||
WxPayOrderQueryRequest request = new WxPayOrderQueryRequest(); | |||
request.setOutTradeNo(StringUtils.trimToNull(outTradeNo)); | |||
request.setTransactionId(StringUtils.trimToNull(transactionId)); | |||
initRequest(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/pay/orderquery"; | |||
String responseContent = this.post(url, request.toXML()); | |||
@@ -244,9 +151,9 @@ public class WxPayServiceImpl implements WxPayService { | |||
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无响应结果").build()); | |||
} | |||
WxPayOrderQueryResult result = WxPayOrderQueryResult.fromXML(responseContent, WxPayOrderQueryResult.class); | |||
WxPayOrderQueryResult result = WxPayBaseResult.fromXML(responseContent, WxPayOrderQueryResult.class); | |||
result.composeCoupons(); | |||
this.checkResult(result); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
@@ -258,77 +165,27 @@ public class WxPayServiceImpl implements WxPayService { | |||
WxPayOrderCloseRequest request = new WxPayOrderCloseRequest(); | |||
request.setOutTradeNo(StringUtils.trimToNull(outTradeNo)); | |||
this.initRequest(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/pay/closeorder"; | |||
String responseContent = this.post(url, request.toXML()); | |||
WxPayOrderCloseResult result = WxPayBaseResult.fromXML(responseContent, WxPayOrderCloseResult.class); | |||
this.checkResult(result); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
@Override | |||
public WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request) | |||
throws WxErrorException { | |||
this.initRequest(request); | |||
if (StringUtils.isBlank(request.getNotifyURL())) { | |||
request.setNotifyURL(getConfig().getNotifyUrl()); | |||
} | |||
if (StringUtils.isBlank(request.getTradeType())) { | |||
request.setTradeType(getConfig().getTradeType()); | |||
} | |||
this.checkParameters(request);//校验参数 | |||
request.setSign(this.createSign(request)); | |||
public WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request) throws WxErrorException { | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/pay/unifiedorder"; | |||
String responseContent = this.post(url, request.toXML()); | |||
WxPayUnifiedOrderResult result = WxPayUnifiedOrderResult.fromXML(responseContent, WxPayUnifiedOrderResult.class); | |||
this.checkResult(result); | |||
WxPayUnifiedOrderResult result = WxPayBaseResult.fromXML(responseContent, WxPayUnifiedOrderResult.class); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
private void initRequest(WxPayBaseRequest request) { | |||
if (StringUtils.isBlank(request.getAppid())) { | |||
request.setAppid(this.getConfig().getAppId()); | |||
} | |||
if (StringUtils.isBlank(request.getMchId())) { | |||
request.setMchId(this.getConfig().getMchId()); | |||
} | |||
if (StringUtils.isBlank(request.getSubAppId())) { | |||
request.setSubAppId(this.getConfig().getSubAppId()); | |||
} | |||
if (StringUtils.isBlank(request.getSubMchId())) { | |||
request.setSubMchId(this.getConfig().getSubMchId()); | |||
} | |||
if (StringUtils.isBlank(request.getNonceStr())) { | |||
request.setNonceStr(String.valueOf(System.currentTimeMillis())); | |||
} | |||
} | |||
private void checkParameters(WxPayUnifiedOrderRequest request) throws WxErrorException { | |||
BeanUtils.checkRequiredFields(request); | |||
if (!ArrayUtils.contains(TRADE_TYPES, request.getTradeType())) { | |||
throw new IllegalArgumentException("trade_type目前必须为" + Arrays.toString(TRADE_TYPES) + "其中之一,实际值:" + request.getTradeType()); | |||
} | |||
if ("JSAPI".equals(request.getTradeType()) && request.getOpenid() == null) { | |||
throw new IllegalArgumentException("当 trade_type是'JSAPI'时未指定openid"); | |||
} | |||
if ("NATIVE".equals(request.getTradeType()) && request.getProductId() == null) { | |||
throw new IllegalArgumentException("当 trade_type是'NATIVE'时未指定product_id"); | |||
} | |||
} | |||
@Override | |||
public Map<String, String> getPayInfo(WxPayUnifiedOrderRequest request) throws WxErrorException { | |||
WxPayUnifiedOrderResult unifiedOrderResult = this.unifiedOrder(request); | |||
@@ -349,21 +206,18 @@ public class WxPayServiceImpl implements WxPayService { | |||
if ("NATIVE".equals(request.getTradeType())) { | |||
payInfo.put("codeUrl", unifiedOrderResult.getCodeURL()); | |||
} | |||
payInfo.put("paySign", this.createSign(payInfo)); | |||
payInfo.put("paySign", SignUtils.createSign(payInfo, this.getConfig().getMchKey())); | |||
return payInfo; | |||
} | |||
@Override | |||
public WxEntPayResult entPay(WxEntPayRequest request) throws WxErrorException { | |||
this.initRequest(request); | |||
BeanUtils.checkRequiredFields(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/mmpaymkttransfers/promotion/transfers"; | |||
String responseContent = this.postWithKey(url, request.toXML()); | |||
WxEntPayResult result = WxEntPayResult.fromXML(responseContent, WxEntPayResult.class); | |||
this.checkResult(result); | |||
WxEntPayResult result = WxPayBaseResult.fromXML(responseContent, WxEntPayResult.class); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
@@ -371,25 +225,24 @@ public class WxPayServiceImpl implements WxPayService { | |||
public WxEntPayQueryResult queryEntPay(String partnerTradeNo) throws WxErrorException { | |||
WxEntPayQueryRequest request = new WxEntPayQueryRequest(); | |||
request.setPartnerTradeNo(partnerTradeNo); | |||
this.initRequest(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/mmpaymkttransfers/gettransferinfo"; | |||
String responseContent = this.postWithKey(url, request.toXML()); | |||
WxEntPayQueryResult result = WxEntPayQueryResult.fromXML(responseContent, WxEntPayQueryResult.class); | |||
this.checkResult(result); | |||
WxEntPayQueryResult result = WxPayBaseResult.fromXML(responseContent, WxEntPayQueryResult.class); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
@Override | |||
public byte[] createScanPayQrcodeMode1(String productId, File logoFile, Integer sideLength) { | |||
String content = createScanPayQrcodeMode1(productId); | |||
return createQrcode(content, logoFile, sideLength); | |||
String content = this.createScanPayQrcodeMode1(productId); | |||
return this.createQrcode(content, logoFile, sideLength); | |||
} | |||
@Override | |||
public String createScanPayQrcodeMode1(String productId){ | |||
//weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX | |||
public String createScanPayQrcodeMode1(String productId) { | |||
//weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX | |||
StringBuilder codeUrl = new StringBuilder("weixin://wxpay/bizpayurl?"); | |||
Map<String, String> params = Maps.newHashMap(); | |||
params.put("appid", this.getConfig().getAppId()); | |||
@@ -398,7 +251,7 @@ public class WxPayServiceImpl implements WxPayService { | |||
params.put("time_stamp", String.valueOf(System.currentTimeMillis() / 1000));//这里需要秒,10位数字 | |||
params.put("nonce_str", String.valueOf(System.currentTimeMillis())); | |||
String sign = this.createSign(params); | |||
String sign = SignUtils.createSign(params, this.getConfig().getMchKey()); | |||
params.put("sign", sign); | |||
@@ -407,72 +260,80 @@ public class WxPayServiceImpl implements WxPayService { | |||
} | |||
String content = codeUrl.toString().substring(0, codeUrl.length() - 1); | |||
log.debug("扫码支付模式一生成二维码的URL:{}",content); | |||
return content; | |||
log.debug("扫码支付模式一生成二维码的URL:{}", content); | |||
return content; | |||
} | |||
@Override | |||
public byte[] createScanPayQrcodeMode2(String codeUrl, File logoFile, Integer sideLength) { | |||
return createQrcode(codeUrl, logoFile, sideLength); | |||
return this.createQrcode(codeUrl, logoFile, sideLength); | |||
} | |||
private byte[] createQrcode(String content, File logoFile, Integer sideLength) { | |||
if (sideLength == null || sideLength < 1) { | |||
if (sideLength == null || sideLength < 1) { | |||
return QrcodeUtils.createQrcode(content, logoFile); | |||
} | |||
return QrcodeUtils.createQrcode(content, sideLength, logoFile); | |||
} | |||
public void report(WxPayReportRequest request) throws WxErrorException { | |||
BeanUtils.checkRequiredFields(request); | |||
this.initRequest(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/payitil/report"; | |||
String responseContent = this.post(url, request.toXML()); | |||
WxPayCommonResult result = WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class); | |||
this.checkResult(result); | |||
result.checkResult(this); | |||
} | |||
@Override | |||
public File downloadBill(String billDate, String billType, String tarType, String deviceInfo) throws WxErrorException { | |||
WxPayDownloadBillRequest request = new WxPayDownloadBillRequest(); | |||
this.initRequest(request); | |||
request.setBillType(billType); | |||
request.setBillDate(billDate); | |||
request.setTarType(tarType); | |||
request.setDeviceInfo(deviceInfo); | |||
this.checkParameters(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/pay/downloadbill"; | |||
//TODO 返回的内容可能是文件流,也有可能是xml,需要区分对待 | |||
String responseContent = this.post(url, request.toXML()); | |||
WxPayCommonResult result = WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class); | |||
this.checkResult(result); | |||
result.checkResult(this); | |||
//TODO 待实现,暂时无测试帐号,无法调试 | |||
return null; | |||
} | |||
@Override | |||
public WxPayMicropayResult micropay(WxPayMicropayRequest request) throws WxErrorException { | |||
this.initRequest(request); | |||
BeanUtils.checkRequiredFields(request); | |||
request.setSign(this.createSign(request)); | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/pay/micropay"; | |||
String responseContent = this.post(url, request.toXML()); | |||
WxPayMicropayResult result = WxPayBaseResult.fromXML(responseContent, WxPayMicropayResult.class); | |||
this.checkResult(result); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
@Override | |||
public WxPayOrderReverseResult reverseOrder(WxPayOrderReverseRequest request) throws WxErrorException { | |||
request.checkAndSign(this.getConfig()); | |||
String url = this.getPayBaseUrl() + "/secapi/pay/reverse"; | |||
String responseContent = this.postWithKey(url, request.toXML()); | |||
WxPayOrderReverseResult result = WxPayBaseResult.fromXML(responseContent, WxPayOrderReverseResult.class); | |||
result.checkResult(this); | |||
return result; | |||
} | |||
private String post(String url, String xmlParam) { | |||
String requestString = null; | |||
String requestString = xmlParam; | |||
try { | |||
requestString = new String(xmlParam.getBytes(CharEncoding.UTF_8), CharEncoding.ISO_8859_1); | |||
} catch (UnsupportedEncodingException e) { | |||
//实际上不会发生该异常 | |||
e.printStackTrace(); | |||
} | |||
@@ -520,62 +381,4 @@ public class WxPayServiceImpl implements WxPayService { | |||
} | |||
} | |||
@Override | |||
public String createSign(Object xmlBean) { | |||
return this.createSign(BeanUtils.xmlBean2Map(xmlBean), this.getConfig().getMchKey()); | |||
} | |||
@Override | |||
public String createSign(Object xmlBean, String signKey) { | |||
return this.createSign(BeanUtils.xmlBean2Map(xmlBean), signKey); | |||
} | |||
@Override | |||
public String createSign(Map<String, String> params) { | |||
return this.createSign(params, this.getConfig().getMchKey()); | |||
} | |||
@Override | |||
public String createSign(Map<String, String> params, String signKey) { | |||
if (this.getConfig().useSandboxForWxPay()) { | |||
//使用仿真测试环境 | |||
//TODO 目前测试发现,以下两行代码都会出问题,所以暂不建议使用仿真测试环境 | |||
signKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"; | |||
//return "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"; | |||
} | |||
SortedMap<String, String> sortedMap = new TreeMap<>(params); | |||
StringBuilder toSign = new StringBuilder(); | |||
for (String key : sortedMap.keySet()) { | |||
String value = params.get(key); | |||
if (StringUtils.isNotEmpty(value) && !"sign".equals(key) && !"key".equals(key)) { | |||
toSign.append(key + "=" + value + "&"); | |||
} | |||
} | |||
toSign.append("key=" + signKey); | |||
return DigestUtils.md5Hex(toSign.toString()).toUpperCase(); | |||
} | |||
@Override | |||
public boolean checkSign(Object xmlBean) { | |||
return this.checkSign(BeanUtils.xmlBean2Map(xmlBean), getConfig().getMchKey()); | |||
} | |||
@Override | |||
public boolean checkSign(Object xmlBean, String signKey) { | |||
return this.checkSign(BeanUtils.xmlBean2Map(xmlBean), signKey); | |||
} | |||
@Override | |||
public boolean checkSign(Map<String, String> params) { | |||
return this.checkSign(params, getConfig().getMchKey()); | |||
} | |||
@Override | |||
public boolean checkSign(Map<String, String> params, String signKey) { | |||
String sign = this.createSign(params, signKey); | |||
return sign.equals(params.get("sign")); | |||
} | |||
} |
@@ -0,0 +1,84 @@ | |||
package com.github.binarywang.wxpay.util; | |||
import me.chanjar.weixin.common.util.BeanUtils; | |||
import org.apache.commons.codec.digest.DigestUtils; | |||
import org.apache.commons.lang3.StringUtils; | |||
import java.util.Map; | |||
import java.util.SortedMap; | |||
import java.util.TreeMap; | |||
/** | |||
* <pre> | |||
* 签名相关工具类 | |||
* Created by Binary Wang on 2017-3-23. | |||
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
* </pre> | |||
*/ | |||
public class SignUtils { | |||
/** | |||
* 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3) | |||
* | |||
* @param xmlBean Bean需要标记有XML注解 | |||
* @param signKey 签名Key | |||
* @return 签名字符串 | |||
*/ | |||
public static String createSign(Object xmlBean, String signKey) { | |||
return createSign(BeanUtils.xmlBean2Map(xmlBean), signKey); | |||
} | |||
/** | |||
* 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3) | |||
* | |||
* @param params 参数信息 | |||
* @param signKey 签名Key | |||
* @return 签名字符串 | |||
*/ | |||
public static String createSign(Map<String, String> params, String signKey) { | |||
// if (this.getConfig().useSandboxForWxPay()) { | |||
// //使用仿真测试环境 | |||
// //TODO 目前测试发现,以下两行代码都会出问题,所以暂不建议使用仿真测试环境 | |||
// signKey = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"; | |||
// //return "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"; | |||
// } | |||
SortedMap<String, String> sortedMap = new TreeMap<>(params); | |||
StringBuilder toSign = new StringBuilder(); | |||
for (String key : sortedMap.keySet()) { | |||
String value = params.get(key); | |||
if (StringUtils.isNotEmpty(value) && !"sign".equals(key) && !"key".equals(key)) { | |||
toSign.append(key + "=" + value + "&"); | |||
} | |||
} | |||
toSign.append("key=" + signKey); | |||
return DigestUtils.md5Hex(toSign.toString()).toUpperCase(); | |||
} | |||
/** | |||
* 校验签名是否正确 | |||
* | |||
* @param xmlBean Bean需要标记有XML注解 | |||
* @param signKey 校验的签名Key | |||
* @return true - 签名校验成功,false - 签名校验失败 | |||
* @see #checkSign(Map, String) | |||
*/ | |||
public static boolean checkSign(Object xmlBean, String signKey) { | |||
return checkSign(BeanUtils.xmlBean2Map(xmlBean), signKey); | |||
} | |||
/** | |||
* 校验签名是否正确 | |||
* | |||
* @param params 需要校验的参数Map | |||
* @param signKey 校验的签名Key | |||
* @return true - 签名校验成功,false - 签名校验失败 | |||
* @see #checkSign(Map, String) | |||
*/ | |||
public static boolean checkSign(Map<String, String> params, String signKey) { | |||
String sign = createSign(params, signKey); | |||
return sign.equals(params.get("sign")); | |||
} | |||
} |