diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/CashApi.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/CashApi.java index 9082d846..5a09f443 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/CashApi.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/api/CashApi.java @@ -1,8 +1,15 @@ package com.foxinmy.weixin4j.api; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; @@ -49,11 +56,19 @@ public class CashApi extends MchApi { * * @param redpacket * 红包信息 - * @see #sendRedpack(Redpacket,String) + * @param openId + * 接受收红包的用户的openid 必填 + * @see #sendRedpacks(Redpacket, String...) */ - public RedpacketSendResult sendRedpack(Redpacket redpacket) + public RedpacketSendResult sendRedpack(Redpacket redpacket, String openId) throws WeixinException { - return sendRedpack(redpacket, null); + try { + return sendRedpacks(redpacket, openId).get(0).get(); + } catch (InterruptedException e) { + throw new WeixinException("send redpack error", e); + } catch (ExecutionException e) { + throw new WeixinException("send redpack error", e); + } } /** @@ -61,8 +76,8 @@ public class CashApi extends MchApi { * * @param redpacket * 红包信息 - * @param appId - * 应用ID 可为空 主要是针对企业号支付时传入的agentid + * @param openIds + * 接受收红包的用户的openid 必填 * @return 发放结果 * @see com.foxinmy.weixin4j.payment.mch.Redpacket * @see com.foxinmy.weixin4j.payment.mch.RedpacketSendResult @@ -74,23 +89,44 @@ public class CashApi extends MchApi { * 发放裂变红包接口 * @throws WeixinException */ - public RedpacketSendResult sendRedpack(Redpacket redpacket, String appId) - throws WeixinException { + public List> sendRedpacks(Redpacket redpacket, + String... openIds) { + String appId = redpacket.getAppId(); super.declareMerchant(redpacket); - JSONObject obj = (JSONObject) JSON.toJSON(redpacket); + final JSONObject obj = (JSONObject) JSON.toJSON(redpacket); if (StringUtil.isNotBlank(appId)) { obj.put("appid", appId); } obj.put("wxappid", obj.remove("appid")); - obj.put("sign", weixinSignature.sign(obj)); - String param = XmlStream.map2xml(obj); - WeixinResponse response = getWeixinSSLExecutor() - .post(redpacket.getTotalNum() > 1 ? getRequestUri("groupredpack_send_uri") - : getRequestUri("redpack_send_uri"), param); - String text = response.getAsString() - .replaceFirst("", "") - .replaceFirst("", ""); - return XmlStream.fromXML(text, RedpacketSendResult.class); + final String redpack_uri = redpacket.getTotalNum() > 1 ? getRequestUri("groupredpack_send_uri") + : getRequestUri("redpack_send_uri"); + int sendLength = openIds.length; + ExecutorService sendExecutor = Executors.newFixedThreadPool(Math.max(1, + sendLength / 10)); // 十分之一? + List> callSendList = new ArrayList>( + sendLength); + for (final String openId : openIds) { + Future futureSend = sendExecutor + .submit(new Callable() { + @Override + public RedpacketSendResult call() throws Exception { + obj.put("re_openid", openId); + obj.put("sign", weixinSignature.sign(obj)); + String param = XmlStream.map2xml(obj); + WeixinResponse response = getWeixinSSLExecutor() + .post(redpack_uri, param); + String text = response.getAsString() + .replaceFirst("", "") + .replaceFirst("", ""); + return XmlStream.fromXML(text, + RedpacketSendResult.class); + } + }); + callSendList.add(futureSend); + } + // 关闭启动线程,不再接受新的任务 + sendExecutor.shutdown(); + return callSendList; } /** diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/WeixinPayProxy.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/WeixinPayProxy.java index 596a6369..bec415f2 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/WeixinPayProxy.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/WeixinPayProxy.java @@ -3,6 +3,8 @@ package com.foxinmy.weixin4j.payment; import java.io.IOException; import java.io.OutputStream; import java.util.Date; +import java.util.List; +import java.util.concurrent.Future; import com.alibaba.fastjson.JSON; import com.foxinmy.weixin4j.api.CashApi; @@ -679,11 +681,15 @@ public class WeixinPayProxy { * * @param redpacket * 红包信息 - * @see #sendRedpack(Redpacket,String) + * @param openId + * 接受收红包的用户的openid 必填 + * @see com.foxinmy.weixin4j.api.CashApi + * @throws WeixinException + * @see #sendRedpacks(Redpacket, String...) */ - public RedpacketSendResult sendRedpack(Redpacket redpacket) + public RedpacketSendResult sendRedpack(Redpacket redpacket, String openId) throws WeixinException { - return cashApi.sendRedpack(redpacket); + return cashApi.sendRedpack(redpacket, openId); } /** @@ -691,8 +697,8 @@ public class WeixinPayProxy { * * @param redpacket * 红包信息 - * @param appId - * 应用ID 可为空 主要是针对企业号支付时传入的agentid + * @param openIds + * 接受收红包的用户的openid 必填 * @return 发放结果 * @see com.foxinmy.weixin4j.api.CashApi * @see com.foxinmy.weixin4j.payment.mch.Redpacket @@ -705,9 +711,9 @@ public class WeixinPayProxy { * 发放裂变红包接口 * @throws WeixinException */ - public RedpacketSendResult sendRedpack(Redpacket redpacket, String appId) - throws WeixinException { - return cashApi.sendRedpack(redpacket, appId); + public List> sendRedpacks(Redpacket redpacket, + String... openIds) { + return cashApi.sendRedpacks(redpacket, openIds); } /** diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/mch/Redpacket.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/mch/Redpacket.java index e20aa563..47339592 100644 --- a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/mch/Redpacket.java +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/mch/Redpacket.java @@ -6,6 +6,7 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import com.alibaba.fastjson.annotation.JSONField; +import com.foxinmy.weixin4j.type.mch.RedpacketSceneType; import com.foxinmy.weixin4j.util.DateUtil; /** @@ -38,12 +39,6 @@ public class Redpacket extends MerchantResult { @XmlElement(name = "send_name") @JSONField(name = "send_name") private String sendName; - /** - * 接收红包的用户的openid - */ - @XmlElement(name = "re_openid") - @JSONField(name = "re_openid") - private String openId; /** * 付款金额,单位分 */ @@ -96,6 +91,18 @@ public class Redpacket extends MerchantResult { @XmlElement(name = "consume_mch_id") @JSONField(name = "consume_mch_id") private String consumeMchId; + /** + * 发放红包使用场景,红包金额大于200时必传 + */ + @XmlElement(name = "scene_id") + @JSONField(name = "scene_id") + private RedpacketSceneType sceneType; + /** + * 活动信息 + */ + @XmlElement(name = "risk_info") + @JSONField(name = "risk_info") + private String risk; protected Redpacket() { // jaxb required @@ -108,8 +115,6 @@ public class Redpacket extends MerchantResult { * 商户侧一天内不可重复的订单号 接口根据商户订单号支持重入 如出现超时可再调用 必填 * @param sendName * 红包发送者名称 必填 - * @param openId - * 接受收红包的用户的openid 必填 * @param totalAmount * 付款金额 单位为元,自动格式化为分 必填 * @param totalNum @@ -123,12 +128,11 @@ public class Redpacket extends MerchantResult { * @param remark * 备注 必填 */ - public Redpacket(String outTradeNo, String sendName, String openId, - double totalAmount, int totalNum, String wishing, String clientIp, - String actName, String remark) { + public Redpacket(String outTradeNo, String sendName, double totalAmount, + int totalNum, String wishing, String clientIp, String actName, + String remark) { this.outTradeNo = outTradeNo; this.sendName = sendName; - this.openId = openId; this.totalNum = totalNum; this.wishing = wishing; this.clientIp = clientIp; @@ -146,14 +150,10 @@ public class Redpacket extends MerchantResult { return sendName; } - public String getOpenId() { - return openId; - } - public int getTotalAmount() { return totalAmount; } - + /** * 调用接口获取单位为分,get方法转换为元方便使用 * @@ -204,14 +204,34 @@ public class Redpacket extends MerchantResult { this.consumeMchId = consumeMchId; } + public RedpacketSceneType getSceneType() { + return sceneType; + } + + public void setSceneType(RedpacketSceneType sceneType) { + this.sceneType = sceneType; + } + + public String getRisk() { + return risk; + } + + public void setRisk(String risk) { + this.risk = risk; + } + + public void setRisk(RedpacketRisk risk) { + this.risk = risk.toContent(); + } + @Override public String toString() { - return "Redpacket [msgAppId=" + msgAppId + ", consumeMchId=" - + consumeMchId + ", outTradeNo=" + outTradeNo + ", sendName=" - + sendName + ", openId=" + openId + ", totalAmount=" - + totalAmount + ", totalNum=" + totalNum + ", amtType=" - + amtType + ", wishing=" + wishing + ", clientIp=" + clientIp - + ", actName=" + actName + ", remark=" + remark + ", " - + super.toString() + "]"; + return "Redpacket [outTradeNo=" + outTradeNo + ", sendName=" + sendName + + ", totalAmount=" + totalAmount + ", totalNum=" + totalNum + + ", amtType=" + amtType + ", wishing=" + wishing + + ", clientIp=" + clientIp + ", actName=" + actName + + ", remark=" + remark + ", msgAppId=" + msgAppId + + ", consumeMchId=" + consumeMchId + ", sceneType=" + sceneType + + ", risk=" + risk + ", " + super.toString() + "]"; } } diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/mch/RedpacketRisk.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/mch/RedpacketRisk.java new file mode 100644 index 00000000..809d4738 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/payment/mch/RedpacketRisk.java @@ -0,0 +1,85 @@ +package com.foxinmy.weixin4j.payment.mch; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +import com.foxinmy.weixin4j.util.Consts; +import com.foxinmy.weixin4j.util.DateUtil; +import com.foxinmy.weixin4j.util.MapUtil; + +/** + * 发送红包的活动信息 + * + * @className RedpacketRisk + * @author jinyu(foxinmy@gmail.com) + * @date 2017年1月4日 + * @since JDK 1.6 + * @see + */ +public class RedpacketRisk { + private Map risk; + + public RedpacketRisk() { + this.risk = new HashMap(); + } + + /** + * 用户操作的时间戳 + * + * @return + */ + public RedpacketRisk postTimestamp() { + risk.put("posttime", DateUtil.timestamp2string()); + return this; + } + + /** + * 业务系统账号的手机号,国家代码-手机号。不需要+号 + * + * @param mobile + * @return + */ + public RedpacketRisk mobile(String mobile) { + risk.put("mobile", mobile); + return this; + } + + /** + * 用户操作的客户端版本 + * + * @param clientVersion + * @return + */ + public RedpacketRisk clientVersion(String clientVersion) { + risk.put("clientversion", clientVersion); + return this; + } + + /** + * mac 地址或者设备唯一标识 + * + * @param deviceid + * @return + */ + public RedpacketRisk deviceid(String deviceid) { + risk.put("deviceid", deviceid); + return this; + } + + public Map getRisk() { + return risk; + } + + public String toContent() { + if (risk.isEmpty()) + return null; + try { + return URLEncoder.encode(MapUtil.toJoinString(risk, false, false), + Consts.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + return null; + } + } +} diff --git a/weixin4j-base/src/main/java/com/foxinmy/weixin4j/type/mch/RedpacketSceneType.java b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/type/mch/RedpacketSceneType.java new file mode 100644 index 00000000..0da68f68 --- /dev/null +++ b/weixin4j-base/src/main/java/com/foxinmy/weixin4j/type/mch/RedpacketSceneType.java @@ -0,0 +1,44 @@ +package com.foxinmy.weixin4j.type.mch; + +/** + * 发放红包使用场景 + * + * @className RedpacketSceneType + * @author jinyu(foxinmy@gmail.com) + * @date 2017年1月4日 + * @since JDK 1.6 + */ +public enum RedpacketSceneType { + /** + * 商品促销 + */ + PRODUCT_1, + /** + * 抽奖 + */ + PRODUCT_2, + /** + * 虚拟物品兑奖 + */ + PRODUCT_3, + /** + * 企业内部福利 + */ + PRODUCT_4, + /** + * 渠道分润 + */ + PRODUCT_5, + /** + * 保险回馈 + */ + PRODUCT_6, + /** + * 彩票派奖 + */ + PRODUCT_7, + /** + * 税务刮奖 + */ + PRODUCT_8 +} diff --git a/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/CashTest.java b/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/CashTest.java index c7c04683..85e3366e 100644 --- a/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/CashTest.java +++ b/weixin4j-base/src/test/java/com/foxinmy/weixin4j/base/test/CashTest.java @@ -26,10 +26,10 @@ public class CashTest extends PayTest { @Test public void sendRedpacket() throws WeixinException { - Redpacket redpacket = new Redpacket("HB001", "无忧钱庄", - "oyFLst1bqtuTcxK-ojF8hOGtLQao", 1d, 1, "红包测试", "127.0.0.1", - "快来领取红包吧!", "来就送钱"); - RedpacketSendResult result = PAY.sendRedpack(redpacket); + Redpacket redpacket = new Redpacket("HB001", "无忧钱庄", 1d, 1, "红包测试", + "127.0.0.1", "快来领取红包吧!", "来就送钱"); + RedpacketSendResult result = PAY.sendRedpack(redpacket, + "oyFLst1bqtuTcxK-ojF8hOGtLQao"); Assert.assertEquals(Consts.SUCCESS, result.getReturnCode()); Assert.assertEquals(Consts.SUCCESS, result.getResultCode()); System.err.println(result); diff --git a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CardApi.java b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CardApi.java index 4439ab14..79af30b5 100644 --- a/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CardApi.java +++ b/weixin4j-mp/src/main/java/com/foxinmy/weixin4j/mp/api/CardApi.java @@ -168,13 +168,19 @@ public class CardApi extends MpApi { * 1.同时支持“openid”、“username”两种字段设置白名单,总数上限为10个。 * 2.设置测试白名单接口为全量设置,即测试名单发生变化时需调用该接口重新传入所有测试人员的ID. * 3.白名单用户领取该卡券时将无视卡券失效状态,请开发者注意。 - * @param openIds the open ids - * @param userNames the user names + * + * @param openIds + * the open ids + * @param userNames + * the user names * @author fengyapeng * @since 2016 -12-20 11:22:57 - * @see 设置测试白名单 + * @see 设置测试白名单 */ - public void setTestWhiteList(List openIds, List userNames) throws WeixinException { + public ApiResult setTestWhiteList(List openIds, + List userNames) throws WeixinException { JSONObject requestObj = new JSONObject(); if (openIds != null && openIds.size() > 0) { requestObj.put("openid", openIds); @@ -185,27 +191,34 @@ public class CardApi extends MpApi { String card_set_test_whitelist_uri = getRequestUri("card_set_test_whitelist_uri"); Token token = tokenManager.getCache(); WeixinResponse response = weixinExecutor.post( - String.format(card_set_test_whitelist_uri, token.getAccessToken()), - requestObj.toJSONString()); + String.format(card_set_test_whitelist_uri, + token.getAccessToken()), requestObj.toJSONString()); + return response.getAsResult(); } /** * 查看获取卡券的审核状态 - * @see 查看卡券详情 + * + * @see 查看卡券详情 * * @author fengyapeng * @since 2016 -12-20 11:48:23 */ public CardStatus queryCardStatus(String cardId) throws WeixinException { JSONObject requestObj = new JSONObject(); - requestObj.put("card_id",cardId); + requestObj.put("card_id", cardId); String card_get_uri = getRequestUri("card_get_uri"); Token token = tokenManager.getCache(); - WeixinResponse response = weixinExecutor.post(String.format(card_get_uri, token.getAccessToken()),requestObj.toJSONString()); + WeixinResponse response = weixinExecutor.post( + String.format(card_get_uri, token.getAccessToken()), + requestObj.toJSONString()); JSONObject responseAsJson = response.getAsJson(); JSONObject card = responseAsJson.getJSONObject("card"); String cardType = card.getString("card_type"); - JSONObject baseInfo = card.getJSONObject(cardType.toLowerCase()).getJSONObject("base_info"); + JSONObject baseInfo = card.getJSONObject(cardType.toLowerCase()) + .getJSONObject("base_info"); String status = baseInfo.getString("status"); return CardStatus.valueOf(status); } @@ -213,15 +226,19 @@ public class CardApi extends MpApi { /** * 支持更新所有卡券类型的部分通用字段及特殊卡券(会员卡、飞机票、电影票、会议门票)中特定字段的信息。 * - * @param cardId the card id - * @param card the card + * @param cardId + * the card id + * @param card + * the card * @return 是否提交审核,false为修改后不会重新提审,true为修改字段后重新提审,该卡券的状态变为审核中 - * @throws WeixinException the weixin exception + * @throws WeixinException + * the weixin exception * @author fengyapeng * @see * @since 2016 -12-21 15:29:10 */ - public Boolean updateCardCoupon(String cardId, CardCoupon card) throws WeixinException { + public Boolean updateCardCoupon(String cardId, CardCoupon card) + throws WeixinException { JSONObject request = new JSONObject(); request.put("card_id", cardId); CardType cardType = card.getCardType(); @@ -229,84 +246,88 @@ public class CardApi extends MpApi { request.put(cardType.name().toLowerCase(), card); String card_update_uri = getRequestUri("card_update_uri"); Token token = tokenManager.getCache(); - WeixinResponse response = weixinExecutor.post(String.format(card_update_uri,token.getAccessToken()),JSON.toJSONString(request)); - JSONObject jsonObject= response.getAsJson(); - return jsonObject.getBoolean("send_check"); + WeixinResponse response = weixinExecutor.post( + String.format(card_update_uri, token.getAccessToken()), + JSON.toJSONString(request)); + JSONObject jsonObject = response.getAsJson(); + return jsonObject.getBoolean("send_check"); } - - - - /** - * 激活方式说明 - * 接口激活通常需要开发者开发用户填写资料的网页。通常有两种激活流程: - * 1. 用户必须在填写资料后才能领卡,领卡后开发者调用激活接口为用户激活会员卡; - * 2. 是用户可以先领取会员卡,点击激活会员卡跳转至开发者设置的资料填写页面,填写完成后开发者调用激活接口为用户激活会员卡。 + * 激活方式说明 接口激活通常需要开发者开发用户填写资料的网页。通常有两种激活流程: 1. + * 用户必须在填写资料后才能领卡,领卡后开发者调用激活接口为用户激活会员卡; 2. + * 是用户可以先领取会员卡,点击激活会员卡跳转至开发者设置的资料填写页面,填写完成后开发者调用激活接口为用户激活会员卡。 * - * @see 接口激活 + * @see 接口激活 */ - public ApiResult activateMemberCard(MemberInitInfo memberInitInfo) throws WeixinException { + public ApiResult activateMemberCard(MemberInitInfo memberInitInfo) + throws WeixinException { String card_member_card_activate_uri = getRequestUri("card_member_card_activate_uri"); Token token = tokenManager.getCache(); - WeixinResponse response = weixinExecutor - .post(String.format(card_member_card_activate_uri, token.getAccessToken()), JSON.toJSONString(memberInitInfo)); + WeixinResponse response = weixinExecutor.post( + String.format(card_member_card_activate_uri, + token.getAccessToken()), + JSON.toJSONString(memberInitInfo)); return response.getAsResult(); } /** - * 设置开卡字段接口 - * 开发者在创建时填入wx_activate字段后, - * 需要调用该接口设置用户激活时需要填写的选项,否则一键开卡设置不生效。 + * 设置开卡字段接口 开发者在创建时填入wx_activate字段后, 需要调用该接口设置用户激活时需要填写的选项,否则一键开卡设置不生效。 * - * @see 一键激活 + * @see 一键激活 */ - public ApiResult setActivateUserForm(MemberUserForm memberUserForm) throws WeixinException { + public ApiResult setActivateUserForm(MemberUserForm memberUserForm) + throws WeixinException { String user_form_uri = getRequestUri("card_member_card_activate_user_form_uri"); Token token = tokenManager.getCache(); - WeixinResponse response = weixinExecutor - .post(String.format(user_form_uri, token.getAccessToken()), JSON.toJSONString(memberUserForm)); + WeixinResponse response = weixinExecutor.post( + String.format(user_form_uri, token.getAccessToken()), + JSON.toJSONString(memberUserForm)); return response.getAsResult(); } /** * 拉取会员信息接口。 * - * @param cardId the card id - * @param code the code + * @param cardId + * the card id + * @param code + * the code * @author fengyapeng * @since 2016 -12-21 11:28:45 */ - public MemberUserInfo getMemberUserInfo(String cardId, String code) throws WeixinException { + public MemberUserInfo getMemberUserInfo(String cardId, String code) + throws WeixinException { String user_info_uri = getRequestUri("card_member_card_user_info_uri"); Token token = tokenManager.getCache(); JSONObject jsonObject = new JSONObject(); jsonObject.put("card_id", cardId); jsonObject.put("code", code); - WeixinResponse response = weixinExecutor.post(String.format(user_info_uri, token.getAccessToken()), JSON.toJSONString(jsonObject)); + WeixinResponse response = weixinExecutor.post( + String.format(user_info_uri, token.getAccessToken()), + JSON.toJSONString(jsonObject)); return response.getAsObject(new TypeReference() { }); } /** - * 更新会员 - * result_bonus 当前用户积分总额 - * result_balance 当前用户预存总金额 - * openid 用户openid + * 更新会员 result_bonus 当前用户积分总额 result_balance 当前用户预存总金额 openid 用户openid + * * @param updateInfo * @return * @throws WeixinException */ - public JSONObject updateMemberUserInfo(MemberUpdateInfo updateInfo) throws WeixinException { + public JSONObject updateMemberUserInfo(MemberUpdateInfo updateInfo) + throws WeixinException { String card_member_card_update_user_uri = getRequestUri("card_member_card_update_user_uri"); Token token = tokenManager.getCache(); - WeixinResponse response = weixinExecutor - .post(String.format(card_member_card_update_user_uri, token.getAccessToken()), JSON.toJSONString(updateInfo)); + WeixinResponse response = weixinExecutor.post( + String.format(card_member_card_update_user_uri, + token.getAccessToken()), JSON.toJSONString(updateInfo)); return response.getAsJson(); } - - - - - }