@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.api; | |||
import java.io.File; | |||
import javax.net.ssl.SSLContext; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
/** | |||
@@ -74,4 +76,5 @@ public interface WxMpConfigStorage { | |||
public File getTmpDirFile(); | |||
public SSLContext getSSLContext(); | |||
} |
@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.api; | |||
import java.io.File; | |||
import javax.net.ssl.SSLContext; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
/** | |||
@@ -35,6 +37,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
*/ | |||
protected volatile File tmpDirFile; | |||
protected volatile SSLContext sslContext; | |||
public String getAccessToken() { | |||
return this.accessToken; | |||
} | |||
@@ -219,4 +223,13 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
this.tmpDirFile = tmpDirFile; | |||
} | |||
@Override | |||
public SSLContext getSSLContext() { | |||
return sslContext; | |||
} | |||
public void setSSLContext(SSLContext context) { | |||
sslContext = context; | |||
} | |||
} |
@@ -748,4 +748,12 @@ public interface WxMpService { | |||
* @return | |||
*/ | |||
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.WxMpUserList; | |||
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.MaterialNewsInfoRequestExecutor; | |||
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.HttpGet; | |||
import org.apache.http.client.methods.HttpPost; | |||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||
import org.apache.http.entity.StringEntity; | |||
import org.apache.http.impl.client.BasicCredentialsProvider; | |||
import org.apache.http.impl.client.BasicResponseHandler; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.apache.http.impl.client.HttpClientBuilder; | |||
import org.apache.http.impl.client.HttpClients; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.slf4j.helpers.MessageFormatter; | |||
import com.google.gson.JsonArray; | |||
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_password = wxMpConfigStorage.getHttp_proxy_password(); | |||
final HttpClientBuilder builder = HttpClients.custom(); | |||
if (StringUtils.isNotBlank(http_proxy_host)) { | |||
// 使用代理服务器 | |||
if (StringUtils.isNotBlank(http_proxy_username)) { | |||
@@ -751,18 +756,22 @@ public class WxMpServiceImpl implements WxMpService { | |||
credsProvider.setCredentials( | |||
new AuthScope(http_proxy_host, http_proxy_port), | |||
new UsernamePasswordCredentials(http_proxy_username, http_proxy_password)); | |||
httpClient = HttpClients | |||
.custom() | |||
.setDefaultCredentialsProvider(credsProvider) | |||
.build(); | |||
builder | |||
.setDefaultCredentialsProvider(credsProvider); | |||
} else { | |||
// 无需用户认证的代理服务器 | |||
httpClient = HttpClients.createDefault(); | |||
} | |||
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 | |||
@@ -941,4 +950,45 @@ public class WxMpServiceImpl implements WxMpService { | |||
public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature) { | |||
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()); | |||
} | |||
} |