@@ -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; | |||
/** | |||
* <pre> | |||
* 企业付款业务是基于微信支付商户平台的资金管理能力,为了协助商户方便地实现企业向个人付款,针对部分有开发能力的商户,提供通过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 证书文件对象 | |||
* </pre> | |||
*/ | |||
WxEntPayResult entPay(WxEntPayRequest request, File keyFile) throws WxErrorException; | |||
} |
@@ -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<String> nullFields = Lists.newArrayList(); | |||
for (Entry<String, Reflect> 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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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)); | |||
} | |||
} |