@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.api; | |||||
import java.io.File; | import java.io.File; | ||||
import javax.net.ssl.SSLContext; | |||||
import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
/** | /** | ||||
@@ -74,4 +76,5 @@ public interface WxMpConfigStorage { | |||||
public File getTmpDirFile(); | public File getTmpDirFile(); | ||||
public SSLContext getSSLContext(); | |||||
} | } |
@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.api; | |||||
import java.io.File; | import java.io.File; | ||||
import javax.net.ssl.SSLContext; | |||||
import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
/** | /** | ||||
@@ -35,6 +37,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
*/ | */ | ||||
protected volatile File tmpDirFile; | protected volatile File tmpDirFile; | ||||
protected volatile SSLContext sslContext; | |||||
public String getAccessToken() { | public String getAccessToken() { | ||||
return this.accessToken; | return this.accessToken; | ||||
} | } | ||||
@@ -219,4 +223,13 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
this.tmpDirFile = tmpDirFile; | this.tmpDirFile = tmpDirFile; | ||||
} | } | ||||
@Override | |||||
public SSLContext getSSLContext() { | |||||
return sslContext; | |||||
} | |||||
public void setSSLContext(SSLContext context) { | |||||
sslContext = context; | |||||
} | |||||
} | } |
@@ -748,4 +748,12 @@ public interface WxMpService { | |||||
* @return | * @return | ||||
*/ | */ | ||||
public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature); | public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature); | ||||
/** | |||||
* 发送微信红包给个人用户 | |||||
* @param parameters | |||||
* @return | |||||
* @throws WxErrorException | |||||
*/ | |||||
public WxRedpackResult sendRedpack(Map<String, String> parameters) throws WxErrorException; | |||||
} | } |
@@ -66,6 +66,7 @@ import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||||
import me.chanjar.weixin.mp.bean.result.WxMpUserCumulate; | import me.chanjar.weixin.mp.bean.result.WxMpUserCumulate; | ||||
import me.chanjar.weixin.mp.bean.result.WxMpUserList; | import me.chanjar.weixin.mp.bean.result.WxMpUserList; | ||||
import me.chanjar.weixin.mp.bean.result.WxMpUserSummary; | import me.chanjar.weixin.mp.bean.result.WxMpUserSummary; | ||||
import me.chanjar.weixin.mp.bean.result.WxRedpackResult; | |||||
import me.chanjar.weixin.mp.util.http.MaterialDeleteRequestExecutor; | import me.chanjar.weixin.mp.util.http.MaterialDeleteRequestExecutor; | ||||
import me.chanjar.weixin.mp.util.http.MaterialNewsInfoRequestExecutor; | import me.chanjar.weixin.mp.util.http.MaterialNewsInfoRequestExecutor; | ||||
import me.chanjar.weixin.mp.util.http.MaterialUploadRequestExecutor; | import me.chanjar.weixin.mp.util.http.MaterialUploadRequestExecutor; | ||||
@@ -84,13 +85,16 @@ import org.apache.http.client.config.RequestConfig; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | import org.apache.http.client.methods.CloseableHttpResponse; | ||||
import org.apache.http.client.methods.HttpGet; | import org.apache.http.client.methods.HttpGet; | ||||
import org.apache.http.client.methods.HttpPost; | import org.apache.http.client.methods.HttpPost; | ||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||||
import org.apache.http.entity.StringEntity; | import org.apache.http.entity.StringEntity; | ||||
import org.apache.http.impl.client.BasicCredentialsProvider; | import org.apache.http.impl.client.BasicCredentialsProvider; | ||||
import org.apache.http.impl.client.BasicResponseHandler; | import org.apache.http.impl.client.BasicResponseHandler; | ||||
import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
import org.apache.http.impl.client.HttpClientBuilder; | |||||
import org.apache.http.impl.client.HttpClients; | import org.apache.http.impl.client.HttpClients; | ||||
import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
import org.slf4j.helpers.MessageFormatter; | |||||
import com.google.gson.JsonArray; | import com.google.gson.JsonArray; | ||||
import com.google.gson.JsonElement; | import com.google.gson.JsonElement; | ||||
@@ -743,6 +747,7 @@ public class WxMpServiceImpl implements WxMpService { | |||||
String http_proxy_username = wxMpConfigStorage.getHttp_proxy_username(); | String http_proxy_username = wxMpConfigStorage.getHttp_proxy_username(); | ||||
String http_proxy_password = wxMpConfigStorage.getHttp_proxy_password(); | String http_proxy_password = wxMpConfigStorage.getHttp_proxy_password(); | ||||
final HttpClientBuilder builder = HttpClients.custom(); | |||||
if (StringUtils.isNotBlank(http_proxy_host)) { | if (StringUtils.isNotBlank(http_proxy_host)) { | ||||
// 使用代理服务器 | // 使用代理服务器 | ||||
if (StringUtils.isNotBlank(http_proxy_username)) { | if (StringUtils.isNotBlank(http_proxy_username)) { | ||||
@@ -751,18 +756,22 @@ public class WxMpServiceImpl implements WxMpService { | |||||
credsProvider.setCredentials( | credsProvider.setCredentials( | ||||
new AuthScope(http_proxy_host, http_proxy_port), | new AuthScope(http_proxy_host, http_proxy_port), | ||||
new UsernamePasswordCredentials(http_proxy_username, http_proxy_password)); | new UsernamePasswordCredentials(http_proxy_username, http_proxy_password)); | ||||
httpClient = HttpClients | |||||
.custom() | |||||
.setDefaultCredentialsProvider(credsProvider) | |||||
.build(); | |||||
builder | |||||
.setDefaultCredentialsProvider(credsProvider); | |||||
} else { | } else { | ||||
// 无需用户认证的代理服务器 | // 无需用户认证的代理服务器 | ||||
httpClient = HttpClients.createDefault(); | |||||
} | } | ||||
httpProxy = new HttpHost(http_proxy_host, http_proxy_port); | httpProxy = new HttpHost(http_proxy_host, http_proxy_port); | ||||
} else { | |||||
httpClient = HttpClients.createDefault(); | |||||
} | } | ||||
if (wxConfigProvider.getSSLContext() != null){ | |||||
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( | |||||
wxConfigProvider.getSSLContext(), | |||||
new String[] { "TLSv1" }, | |||||
null, | |||||
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); | |||||
builder.setSSLSocketFactory(sslsf); | |||||
} | |||||
httpClient = builder.build(); | |||||
} | } | ||||
@Override | @Override | ||||
@@ -941,4 +950,45 @@ public class WxMpServiceImpl implements WxMpService { | |||||
public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature) { | public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature) { | ||||
return signature.equals(WxCryptUtil.createSign(kvm, wxMpConfigStorage.getPartnerKey())); | return signature.equals(WxCryptUtil.createSign(kvm, wxMpConfigStorage.getPartnerKey())); | ||||
} | } | ||||
@Override | |||||
public WxRedpackResult sendRedpack(Map<String, String> parameters) throws WxErrorException { | |||||
String nonce_str = System.currentTimeMillis() + ""; | |||||
SortedMap<String, String> packageParams = new TreeMap<String, String>(parameters); | |||||
packageParams.put("wxappid", wxMpConfigStorage.getAppId()); | |||||
packageParams.put("mch_id", wxMpConfigStorage.getPartnerId()); | |||||
packageParams.put("nonce_str", nonce_str); | |||||
String sign = WxCryptUtil.createSign(packageParams, wxMpConfigStorage.getPartnerKey()); | |||||
packageParams.put("sign", sign); | |||||
StringBuilder request = new StringBuilder("<xml>"); | |||||
for (Entry<String, String> para : packageParams.entrySet()) { | |||||
request.append(String.format("<%s>%s</%s>", para.getKey(), para.getValue(), para.getKey())); | |||||
} | |||||
request.append("</xml>"); | |||||
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"); | |||||
if (httpProxy != null) { | |||||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
httpPost.setConfig(config); | |||||
} | |||||
StringEntity entity = new StringEntity(request.toString(), Consts.UTF_8); | |||||
httpPost.setEntity(entity); | |||||
try { | |||||
CloseableHttpResponse response = getHttpclient().execute(httpPost); | |||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxRedpackResult.class); | |||||
WxRedpackResult wxMpRedpackResult = (WxRedpackResult) xstream.fromXML(responseContent); | |||||
return wxMpRedpackResult; | |||||
} catch (IOException e) { | |||||
log.error(MessageFormatter.format("The exception was happened when sending redpack '{}'.", request.toString()).getMessage(), e); | |||||
WxError error = new WxError(); | |||||
error.setErrorCode(-1); | |||||
throw new WxErrorException(error); | |||||
} | |||||
} | |||||
} | } |
@@ -0,0 +1,118 @@ | |||||
package me.chanjar.weixin.mp.bean.result; | |||||
import java.io.Serializable; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
/** | |||||
* 向微信用户个人发现金红包返回结果 | |||||
* https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5 | |||||
* @author kane | |||||
* | |||||
*/ | |||||
@XStreamAlias("xml") | |||||
public class WxRedpackResult implements Serializable { | |||||
/** | |||||
* | |||||
*/ | |||||
private static final long serialVersionUID = -4837415036337132073L; | |||||
@XStreamAlias("return_code") | |||||
String returnCode; | |||||
@XStreamAlias("return_msg") | |||||
String returnMsg; | |||||
@XStreamAlias("sign") | |||||
String sign; | |||||
@XStreamAlias("result_code") | |||||
String resultCode; | |||||
@XStreamAlias("err_code") | |||||
String errCode; | |||||
@XStreamAlias("err_code_des") | |||||
String errCodeDes; | |||||
@XStreamAlias("mch_billno") | |||||
String mchBillno; | |||||
@XStreamAlias("mch_id") | |||||
String mchId; | |||||
@XStreamAlias("wxappid") | |||||
String wxappid; | |||||
@XStreamAlias("re_openid") | |||||
String reOpenid; | |||||
@XStreamAlias("total_amount") | |||||
int totalAmount; | |||||
@XStreamAlias("send_time") | |||||
String sendTime; | |||||
@XStreamAlias("send_listid") | |||||
String sendListid; | |||||
public String getErrCode() { | |||||
return errCode; | |||||
} | |||||
public String getErrCodeDes() { | |||||
return errCodeDes; | |||||
} | |||||
public String getReturnCode() { | |||||
return returnCode; | |||||
} | |||||
public String getReturnMsg() { | |||||
return returnMsg; | |||||
} | |||||
public String getSign() { | |||||
return sign; | |||||
} | |||||
public String getResultCode() { | |||||
return resultCode; | |||||
} | |||||
public String getMchBillno() { | |||||
return mchBillno; | |||||
} | |||||
public String getMchId() { | |||||
return mchId; | |||||
} | |||||
public String getWxappid() { | |||||
return wxappid; | |||||
} | |||||
public String getReOpenid() { | |||||
return reOpenid; | |||||
} | |||||
public int getTotalAmount() { | |||||
return totalAmount; | |||||
} | |||||
public String getSendTime() { | |||||
return sendTime; | |||||
} | |||||
public String getSendListid() { | |||||
return sendListid; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "WxRedpackResult{" + | |||||
"returnCode=" + returnCode + | |||||
", returnMsg=" + returnMsg + | |||||
", sign=" + sign + | |||||
", errCode=" + errCode + | |||||
", errCodeDes=" + errCodeDes + | |||||
", mchBillno=" + mchBillno + | |||||
", mchId=" + mchId + | |||||
", wxappid=" + wxappid + | |||||
", reOpenid=" + reOpenid + | |||||
", totalAmount=" + totalAmount + | |||||
", sendTime=" + sendTime + | |||||
", sendListid=" + sendListid + | |||||
'}'; | |||||
} | |||||
} |
@@ -0,0 +1,63 @@ | |||||
package me.chanjar.weixin.mp.bean; | |||||
import static org.junit.Assert.assertEquals; | |||||
import org.junit.Before; | |||||
import org.junit.Test; | |||||
import com.thoughtworks.xstream.XStream; | |||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||||
import me.chanjar.weixin.mp.bean.result.WxRedpackResult; | |||||
public class WxRedpackResultTest { | |||||
private XStream xstream; | |||||
@Before | |||||
public void setup() { | |||||
xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxRedpackResult.class); | |||||
} | |||||
@Test public void loadSuccessResult() { | |||||
final String successSample = "<xml>\n" + | |||||
"<return_code><![CDATA[SUCCESS]]></return_code>\n" + | |||||
"<return_msg><![CDATA[发放成功.]]></return_msg>\n" + | |||||
"<result_code><![CDATA[SUCCESS]]></result_code>\n" + | |||||
"<err_code><![CDATA[0]]></err_code>\n" + | |||||
"<err_code_des><![CDATA[发放成功.]]></err_code_des>\n" + | |||||
"<mch_billno><![CDATA[0010010404201411170000046545]]></mch_billno>\n" + | |||||
"<mch_id>10010404</mch_id>\n" + | |||||
"<wxappid><![CDATA[wx6fa7e3bab7e15415]]></wxappid>\n" + | |||||
"<re_openid><![CDATA[onqOjjmM1tad-3ROpncN-yUfa6uI]]></re_openid>\n" + | |||||
"<total_amount>1</total_amount>\n" + | |||||
"<send_listid>100000000020150520314766074200</send_listid>\n" + | |||||
"<send_time>20150520102602</send_time>\n" + | |||||
"</xml>"; | |||||
WxRedpackResult wxMpRedpackResult = (WxRedpackResult) xstream.fromXML(successSample); | |||||
assertEquals("SUCCESS", wxMpRedpackResult.getReturnCode()); | |||||
assertEquals("SUCCESS", wxMpRedpackResult.getResultCode()); | |||||
assertEquals("20150520102602", wxMpRedpackResult.getSendTime()); | |||||
} | |||||
@Test public void loadFailureResult() { | |||||
final String failureSample = "<xml>\n" + | |||||
"<return_code><![CDATA[FAIL]]></return_code>\n" + | |||||
"<return_msg><![CDATA[系统繁忙,请稍后再试.]]></return_msg>\n" + | |||||
"<result_code><![CDATA[FAIL]]></result_code>\n" + | |||||
"<err_code><![CDATA[268458547]]></err_code>\n" + | |||||
"<err_code_des><![CDATA[系统繁忙,请稍后再试.]]></err_code_des>\n" + | |||||
"<mch_billno><![CDATA[0010010404201411170000046542]]></mch_billno>\n" + | |||||
"<mch_id>10010404</mch_id>\n" + | |||||
"<wxappid><![CDATA[wx6fa7e3bab7e15415]]></wxappid>\n" + | |||||
"<re_openid><![CDATA[onqOjjmM1tad-3ROpncN-yUfa6uI]]></re_openid>\n" + | |||||
"<total_amount>1</total_amount>\n" + | |||||
"</xml>"; | |||||
WxRedpackResult wxMpRedpackResult = (WxRedpackResult) xstream.fromXML(failureSample); | |||||
assertEquals("FAIL", wxMpRedpackResult.getReturnCode()); | |||||
assertEquals("FAIL", wxMpRedpackResult.getResultCode()); | |||||
assertEquals("onqOjjmM1tad-3ROpncN-yUfa6uI", wxMpRedpackResult.getReOpenid()); | |||||
assertEquals(1, wxMpRedpackResult.getTotalAmount()); | |||||
} | |||||
} |