From 30f22896b1a5e1a96e5734d34e2c9dc5b248db67 Mon Sep 17 00:00:00 2001 From: Kane Zhu Date: Tue, 17 Nov 2015 16:16:03 +0800 Subject: [PATCH] Support sending redpack. --- .../weixin/mp/api/WxMpConfigStorage.java | 3 + .../mp/api/WxMpInMemoryConfigStorage.java | 13 ++ .../me/chanjar/weixin/mp/api/WxMpService.java | 8 ++ .../weixin/mp/api/WxMpServiceImpl.java | 64 ++++++++-- .../mp/bean/result/WxRedpackResult.java | 118 ++++++++++++++++++ .../weixin/mp/bean/WxRedpackResultTest.java | 63 ++++++++++ 6 files changed, 262 insertions(+), 7 deletions(-) create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxRedpackResult.java create mode 100644 weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxRedpackResultTest.java diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java index 3ee43659..09d90d64 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java @@ -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(); } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java index 98247599..e0c7aaa3 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java @@ -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; + } + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index b9c8d2fd..6dd96f58 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java @@ -748,4 +748,12 @@ public interface WxMpService { * @return */ public boolean checkJSSDKCallbackDataSignature(Map kvm, String signature); + + /** + * 发送微信红包给个人用户 + * @param parameters + * @return + * @throws WxErrorException + */ + public WxRedpackResult sendRedpack(Map parameters) throws WxErrorException; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java index 38de36f4..9d523981 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java @@ -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 kvm, String signature) { return signature.equals(WxCryptUtil.createSign(kvm, wxMpConfigStorage.getPartnerKey())); } + + @Override + public WxRedpackResult sendRedpack(Map parameters) throws WxErrorException { + String nonce_str = System.currentTimeMillis() + ""; + + SortedMap packageParams = new TreeMap(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(""); + for (Entry para : packageParams.entrySet()) { + request.append(String.format("<%s>%s", para.getKey(), para.getValue(), para.getKey())); + } + request.append(""); + + 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); + } + } } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxRedpackResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxRedpackResult.java new file mode 100644 index 00000000..37121247 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxRedpackResult.java @@ -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 + + '}'; + } +} diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxRedpackResultTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxRedpackResultTest.java new file mode 100644 index 00000000..f7913aef --- /dev/null +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxRedpackResultTest.java @@ -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 = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "10010404\n" + + "\n" + + "\n" + + "1\n" + + "100000000020150520314766074200\n" + + "20150520102602\n" + + ""; + 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 = "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "10010404\n" + + "\n" + + "\n" + + "1\n" + + ""; + WxRedpackResult wxMpRedpackResult = (WxRedpackResult) xstream.fromXML(failureSample); + assertEquals("FAIL", wxMpRedpackResult.getReturnCode()); + assertEquals("FAIL", wxMpRedpackResult.getResultCode()); + assertEquals("onqOjjmM1tad-3ROpncN-yUfa6uI", wxMpRedpackResult.getReOpenid()); + assertEquals(1, wxMpRedpackResult.getTotalAmount()); + } +}