diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java
index c6480e6c..cf5bb8c3 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java
@@ -1,8 +1,11 @@
package me.chanjar.weixin.mp.api;
+import java.io.File;
import java.util.Map;
import me.chanjar.weixin.common.exception.WxErrorException;
+import me.chanjar.weixin.mp.bean.pay.WxEntPayRequest;
+import me.chanjar.weixin.mp.bean.pay.WxEntPayResult;
import me.chanjar.weixin.mp.bean.pay.WxMpPayCallback;
import me.chanjar.weixin.mp.bean.pay.WxMpPayRefundResult;
import me.chanjar.weixin.mp.bean.pay.WxMpPayResult;
@@ -85,4 +88,17 @@ public interface WxMpPayService {
*/
WxRedpackResult sendRedpack(WxSendRedpackRequest request) throws WxErrorException;
+ /**
+ *
+ * 企业付款业务是基于微信支付商户平台的资金管理能力,为了协助商户方便地实现企业向个人付款,针对部分有开发能力的商户,提供通过API完成企业付款的功能。
+ * 比如目前的保险行业向客户退保、给付、理赔。
+ * 企业付款将使用商户的可用余额,需确保可用余额充足。查看可用余额、充值、提现请登录商户平台“资金管理”https://pay.weixin.qq.com/进行操作。
+ * 注意:与商户微信支付收款资金并非同一账户,需要单独充值。
+ * 文档详见:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
+ * 接口链接:https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers
+ * @param keyFile 证书文件对象
+ *
+ */
+ WxEntPayResult entPay(WxEntPayRequest request, File keyFile) throws WxErrorException;
+
}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java
index 965e5df1..79e2c312 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java
@@ -1,6 +1,9 @@
package me.chanjar.weixin.mp.api.impl;
+import java.io.File;
+import java.io.FileInputStream;
import java.lang.reflect.Field;
+import java.security.KeyStore;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -8,8 +11,19 @@ import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
+import javax.net.ssl.SSLContext;
+
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContexts;
+import org.apache.http.util.EntityUtils;
import org.joor.Reflect;
import com.google.common.collect.Lists;
@@ -23,6 +37,8 @@ import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
import me.chanjar.weixin.mp.api.WxMpPayService;
import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.pay.WxEntPayRequest;
+import me.chanjar.weixin.mp.bean.pay.WxEntPayResult;
import me.chanjar.weixin.mp.bean.pay.WxMpPayCallback;
import me.chanjar.weixin.mp.bean.pay.WxMpPayRefundResult;
import me.chanjar.weixin.mp.bean.pay.WxMpPayResult;
@@ -263,6 +279,23 @@ public class WxMpPayServiceImpl implements WxMpPayService {
private void checkParameters(WxUnifiedOrderRequest request) {
+ checkNotNullParams(request);
+
+ if (!TRADE_TYPES.contains(request.getTradeType())) {
+ throw new IllegalArgumentException("trade_type目前必须为" + TRADE_TYPES + "其中之一");
+
+ }
+
+ 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");
+ }
+ }
+
+ private void checkNotNullParams(Object request) {
List nullFields = Lists.newArrayList();
for (Entry entry : Reflect.on(request).fields()
.entrySet()) {
@@ -281,20 +314,6 @@ public class WxMpPayServiceImpl implements WxMpPayService {
if (!nullFields.isEmpty()) {
throw new IllegalArgumentException("必填字段[" + nullFields + "]必须提供值");
}
-
- if (!TRADE_TYPES.contains(request.getTradeType())) {
- throw new IllegalArgumentException("trade_type目前必须为" + TRADE_TYPES + "其中之一");
-
- }
-
- 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
@@ -332,4 +351,50 @@ public class WxMpPayServiceImpl implements WxMpPayService {
return payInfo;
}
+ @Override
+ public WxEntPayResult entPay(WxEntPayRequest request, File keyFile) throws WxErrorException {
+ checkNotNullParams(request);
+
+ XStream xstream = XStreamInitializer.getInstance();
+ xstream.processAnnotations(WxEntPayRequest.class);
+ xstream.processAnnotations(WxEntPayResult.class);
+
+ request.setMchAppid(this.wxMpService.getWxMpConfigStorage().getAppId());
+ request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId());
+ request.setNonceStr(System.currentTimeMillis() + "");
+
+ String sign = this.createSign(xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey());
+ request.setSign(sign);
+
+ String url = PAY_BASE_URL + "/mmpaymkttransfers/promotion/transfers";
+
+ try (FileInputStream instream = new FileInputStream(keyFile)) {
+ String mchId = request.getMchId();
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ keyStore.load(instream, mchId.toCharArray());
+
+ SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
+ SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
+ new DefaultHostnameVerifier());
+
+ try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build()) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new StringEntity(new String(xstream.toXML(request).getBytes("UTF-8"), "ISO-8859-1")));
+
+ try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
+ String responseContent = EntityUtils.toString(response.getEntity());
+ WxEntPayResult result = (WxEntPayResult) xstream.fromXML(responseContent);
+ if ("FAIL".equals(result.getResultCode())) {
+ throw new WxErrorException(
+ WxError.newBuilder().setErrorMsg(result.getErrCode() + ":" + result.getErrCodeDes()).build());
+ }
+
+ return result;
+ }
+ }
+ } catch (Exception e) {
+ throw new WxErrorException(WxError.newBuilder().setErrorMsg(e.getMessage()).build(), e);
+ }
+ }
+
}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/pay/WxEntPayRequest.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/pay/WxEntPayRequest.java
new file mode 100644
index 00000000..9c7253d2
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/pay/WxEntPayRequest.java
@@ -0,0 +1,253 @@
+package me.chanjar.weixin.mp.bean.pay;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ * 企业付款请求对象
+ * 注释中各行含义如下:
+ * 字段名、 变量名、 是否必填、 示例值、 类型、 描述
+ * Created by Binary Wang on 2016/10/02.
+ * @author binarywang (https://github.com/binarywang)
+ */
+@XStreamAlias("xml")
+public class WxEntPayRequest {
+ /**
+ * 公众账号appid
+ * mch_appid
+ * 是
+ * wx8888888888888888
+ * String
+ * 微信分配的公众账号ID(企业号corpid即为此appId)
+ */
+ @XStreamAlias("mch_appid")
+ private String mchAppid;
+
+ /**
+ * 商户号
+ * mchid
+ * 是
+ * 1900000109
+ * String(32)
+ * 微信支付分配的商户号
+ */
+ @XStreamAlias("mchid")
+ private String mchId;
+
+ /**
+ * 设备号
+ * device_info
+ * 否
+ * 13467007045764
+ * String(32)
+ *微信支付分配的终端设备号
+ */
+ @XStreamAlias("device_info")
+ private String deviceInfo;
+
+ /**
+ * 随机字符串
+ * nonce_str
+ *是
+ *5K8264ILTKCH16CQ2502SI8ZNMTM67VS
+ *String(32)
+ *随机字符串,不长于32位
+ */
+ @XStreamAlias("nonce_str")
+ private String nonceStr;
+
+ /**
+ * 签名
+ * sign
+ * 是
+ * C380BEC2BFD727A4B6845133519F3AD6
+ * String(32)
+ *签名,详见签名算法
+ */
+ @XStreamAlias("sign")
+ private String sign;
+
+ /**
+ * 商户订单号
+ * partner_trade_no
+ * 是
+ * 10000098201411111234567890
+ * String
+ * 商户订单号,
+ */
+ @XStreamAlias("partner_trade_no")
+ private String partnerTradeNo;
+
+ /**
+ * 需保持唯一性 用户openid
+ * openid
+ * 是
+ * oxTWIuGaIt6gTKsQRLau2M0yL16E
+ * String
+ * 商户appid下,某用户的openid
+ */
+ @XStreamAlias("openid")
+ private String openid;
+
+ /**
+ * 校验用户姓名选项
+ * check_name
+ * 是
+ * OPTION_CHECK
+ * String
+ * NO_CHECK:不校验真实姓名
+ * FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账)
+ * OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功)
+ */
+ @XStreamAlias("check_name")
+ private String checkName;
+
+ /**
+ * 收款用户姓名
+ * re_user_name
+ * 可选
+ * 马花花
+ * String
+ * 收款用户真实姓名。
+ * 如果check_name设置为FORCE_CHECK或OPTION_CHECK, 则必填用户真实姓名
+ */
+ @XStreamAlias("re_user_name")
+ private String reUserName;
+
+ /**
+ * 金额
+ * amount
+ * 是
+ * 10099
+ * int
+ * 企业付款金额, 单位为分
+ */
+ @XStreamAlias("amount")
+ private Integer amount;
+
+ /**
+ * 企业付款描述信息
+ * desc
+ * 是
+ * 理赔
+ * String
+ * 企业付款操作说明信息。必填。
+ */
+ @XStreamAlias("desc")
+ private String description;
+
+ /**
+ * Ip地址
+ * spbill_create_ip
+ * 是
+ * 192.168.0.1
+ * String(32)
+ * 调用接口的机器Ip地址
+ */
+ @XStreamAlias("spbill_create_ip")
+ private String spbillCreateIp;
+
+ public String getMchAppid() {
+ return mchAppid;
+ }
+
+ public void setMchAppid(String mchAppid) {
+ this.mchAppid = mchAppid;
+ }
+
+ public String getMchId() {
+ return mchId;
+ }
+
+ public void setMchId(String mchId) {
+ this.mchId = mchId;
+ }
+
+ public String getDeviceInfo() {
+ return deviceInfo;
+ }
+
+ public void setDeviceInfo(String deviceInfo) {
+ this.deviceInfo = deviceInfo;
+ }
+
+ public String getNonceStr() {
+ return nonceStr;
+ }
+
+ public void setNonceStr(String nonceStr) {
+ this.nonceStr = nonceStr;
+ }
+
+ public String getSign() {
+ return sign;
+ }
+
+ public void setSign(String sign) {
+ this.sign = sign;
+ }
+
+ public String getPartnerTradeNo() {
+ return partnerTradeNo;
+ }
+
+ public void setPartnerTradeNo(String partnerTradeNo) {
+ this.partnerTradeNo = partnerTradeNo;
+ }
+
+ public String getOpenid() {
+ return openid;
+ }
+
+ public void setOpenid(String openid) {
+ this.openid = openid;
+ }
+
+ public String getCheckName() {
+ return checkName;
+ }
+
+ public void setCheckName(String checkName) {
+ this.checkName = checkName;
+ }
+
+ public String getReUserName() {
+ return reUserName;
+ }
+
+ public void setReUserName(String reUserName) {
+ this.reUserName = reUserName;
+ }
+
+ public Integer getAmount() {
+ return amount;
+ }
+
+ public void setAmount(Integer amount) {
+ this.amount = amount;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getSpbillCreateIp() {
+ return spbillCreateIp;
+ }
+
+ public void setSpbillCreateIp(String spbillCreateIp) {
+ this.spbillCreateIp = spbillCreateIp;
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+ }
+
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/pay/WxEntPayResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/pay/WxEntPayResult.java
new file mode 100644
index 00000000..b2dbb8f1
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/pay/WxEntPayResult.java
@@ -0,0 +1,190 @@
+package me.chanjar.weixin.mp.bean.pay;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import com.thoughtworks.xstream.annotations.XStreamAlias;
+
+/**
+ * 企业付款返回结果
+ * Created by Binary Wang on 2016/10/02.
+ * @author binarywang (https://github.com/binarywang)
+ */
+@XStreamAlias("xml")
+public class WxEntPayResult {
+
+ /**
+ * 返回状态码
+ */
+ @XStreamAlias("return_code")
+ private String returnCode;
+
+ /**
+ * 返回信息
+ */
+ @XStreamAlias("return_msg")
+ private String returnMsg;
+
+ //############以下字段在return_code为SUCCESS的时候有返回
+
+ /**
+ * 商户appid
+ */
+ @XStreamAlias("mch_appid")
+ private String mchAppid;
+
+ /**
+ * 商户号
+ */
+ @XStreamAlias("mchid")
+ private String mchId;
+
+ /**
+ * 设备号
+ */
+ @XStreamAlias("device_info")
+ private String deviceInfo;
+
+ /**
+ * 随机字符串
+ */
+ @XStreamAlias("nonce_str")
+ private String nonceStr;
+
+ /**
+ * 业务结果
+ */
+ @XStreamAlias("result_code")
+ private String resultCode;
+ /**
+ * 错误代码
+ */
+ @XStreamAlias("err_code")
+ private String errCode;
+
+ /**
+ * 错误代码描述
+ */
+ @XStreamAlias("err_code_des")
+ private String errCodeDes;
+
+ //############以下字段在return_code 和result_code都为SUCCESS的时候有返回##############
+ /**
+ * 商户订单号
+ */
+ @XStreamAlias("partner_trade_no")
+ private String partnerTradeNo;
+
+ /**
+ * 微信订单号
+ */
+ @XStreamAlias("payment_no")
+ private String paymentNo;
+
+ /**
+ * 微信支付成功时间
+ */
+ @XStreamAlias("payment_time")
+ private String paymentTime;
+
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+ }
+
+ public String getReturnCode() {
+ return returnCode;
+ }
+
+ public void setReturnCode(String returnCode) {
+ this.returnCode = returnCode;
+ }
+
+ public String getReturnMsg() {
+ return returnMsg;
+ }
+
+ public void setReturnMsg(String returnMsg) {
+ this.returnMsg = returnMsg;
+ }
+
+ public String getMchAppid() {
+ return mchAppid;
+ }
+
+ public void setMchAppid(String mchAppid) {
+ this.mchAppid = mchAppid;
+ }
+
+ public String getMchId() {
+ return mchId;
+ }
+
+ public void setMchId(String mchId) {
+ this.mchId = mchId;
+ }
+
+ public String getDeviceInfo() {
+ return deviceInfo;
+ }
+
+ public void setDeviceInfo(String deviceInfo) {
+ this.deviceInfo = deviceInfo;
+ }
+
+ public String getNonceStr() {
+ return nonceStr;
+ }
+
+ public void setNonceStr(String nonceStr) {
+ this.nonceStr = nonceStr;
+ }
+
+ public String getResultCode() {
+ return resultCode;
+ }
+
+ public void setResultCode(String resultCode) {
+ this.resultCode = resultCode;
+ }
+
+ public String getErrCode() {
+ return errCode;
+ }
+
+ public void setErrCode(String errCode) {
+ this.errCode = errCode;
+ }
+
+ public String getErrCodeDes() {
+ return errCodeDes;
+ }
+
+ public void setErrCodeDes(String errCodeDes) {
+ this.errCodeDes = errCodeDes;
+ }
+
+ public String getPartnerTradeNo() {
+ return partnerTradeNo;
+ }
+
+ public void setPartnerTradeNo(String partnerTradeNo) {
+ this.partnerTradeNo = partnerTradeNo;
+ }
+
+ public String getPaymentNo() {
+ return paymentNo;
+ }
+
+ public void setPaymentNo(String paymentNo) {
+ this.paymentNo = paymentNo;
+ }
+
+ public String getPaymentTime() {
+ return paymentTime;
+ }
+
+ public void setPaymentTime(String paymentTime) {
+ this.paymentTime = paymentTime;
+ }
+}
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java
index 092be38a..6199efeb 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java
@@ -1,5 +1,7 @@
package me.chanjar.weixin.mp.api.impl;
+import java.io.File;
+
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
@@ -8,6 +10,7 @@ import com.google.inject.Inject;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.mp.api.ApiTestModule;
import me.chanjar.weixin.mp.api.WxXmlMpInMemoryConfigStorage;
+import me.chanjar.weixin.mp.bean.pay.WxEntPayRequest;
import me.chanjar.weixin.mp.bean.pay.WxRedpackResult;
import me.chanjar.weixin.mp.bean.pay.WxSendRedpackRequest;
import me.chanjar.weixin.mp.bean.pay.WxUnifiedOrderRequest;
@@ -74,4 +77,16 @@ public class WxMpPayServiceImplTest {
.tradeType("JSAPI").openid("122").outTradeNo("111111").build());
System.err.println(result);
}
+
+ /**
+ * Test method for {@link me.chanjar.weixin.mp.api.impl.WxMpPayServiceImpl#entPay(WxEntPayRequest, File)}.
+ * @throws WxErrorException
+ */
+ @Test
+ public final void testEntPay() throws WxErrorException {
+ File keyFile = new File("E:\\dlt.p12");
+ WxEntPayRequest request = new WxEntPayRequest();
+ System.err.println(this.wxService.getPayService().entPay(request, keyFile));
+ }
+
}