| @@ -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()); | |||||
| } | |||||
| } | |||||