From ffeb3eef93214926f33be29f3b2a70b7cc2cb8c5 Mon Sep 17 00:00:00 2001 From: BinaryWang Date: Thu, 28 Jul 2016 13:10:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E4=BB=A3=E7=A0=81=E6=8B=89=E5=87=BA=E5=88=B0=E5=8D=95?= =?UTF-8?q?=E7=8B=AC=E7=B1=BB=E4=B8=AD=E7=BB=B4=E6=8A=A4=EF=BC=8C=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95=E6=9A=82=E6=97=B6=E6=9C=AA=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chanjar/weixin/mp/api/WxMpPayService.java | 138 +++++++ .../me/chanjar/weixin/mp/api/WxMpService.java | 155 +------- .../mp/api/impl/WxMpPayServiceImpl.java | 335 ++++++++++++++++ .../weixin/mp/api/impl/WxMpServiceImpl.java | 375 ++---------------- .../mp/api/impl/WxMpPayServiceImplTest.java | 60 +++ 5 files changed, 573 insertions(+), 490 deletions(-) create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java create mode 100644 weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java new file mode 100644 index 00000000..0db67cb7 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java @@ -0,0 +1,138 @@ +package me.chanjar.weixin.mp.api; + +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.mp.bean.result.*; + +import java.util.Map; + +/** + * 微信支付相关接口 + * Created by Binary Wang on 2016/7/28. + * @author binarywang (https://github.com/binarywang) + */ +public interface WxMpPayService { + + + /** + * 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) + * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" + * + * @param openId 支付人openId + * @param outTradeNo 商户端对应订单号 + * @param amt 金额(单位元) + * @param body 商品描述 + * @param tradeType 交易类型 JSAPI,NATIVE,APP,WAP + * @param ip 发起支付的客户端IP + * @param notifyUrl 通知地址 + * @deprecated Use me.chanjar.weixin.mp.api.WxMpService.getPrepayId(Map) instead + */ + @Deprecated + WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String notifyUrl); + + /** + * 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) + * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" + * + * @param parameters All required/optional parameters for weixin payment + */ + WxMpPrepayIdResult getPrepayId(Map parameters); + + /** + * 该接口调用“统一下单”接口,并拼装发起支付请求需要的参数 + * 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82 + * + * @param parameters the required or optional parameters + */ + Map getPayInfo(Map parameters) throws WxErrorException; + + /** + * 该接口调用“统一下单”接口,并拼装NATIVE发起支付请求需要的参数 + * 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82 + * tradeType 交易类型 NATIVE (其他交易类型JSAPI,APP,WAP) + * + * @param productId 商户商品ID + * @param outTradeNo 商户端对应订单号 + * @param amt 金额(单位元) + * @param body 商品描述 + * @param ip 发起支付的客户端IP + * @param notifyUrl 通知地址 + * @deprecated Use me.chanjar.weixin.mp.api.WxMpService.getPayInfo(Map) instead + */ + @Deprecated + Map getNativePayInfo(String productId, String outTradeNo, double amt, String body, String ip, String notifyUrl) throws WxErrorException; + + /** + * 该接口调用“统一下单”接口,并拼装JSAPI发起支付请求需要的参数 + * 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82 + * tradeType 交易类型 JSAPI(其他交易类型NATIVE,APP,WAP) + * + * @param openId 支付人openId + * @param outTradeNo 商户端对应订单号 + * @param amt 金额(单位元) + * @param body 商品描述 + * @param ip 发起支付的客户端IP + * @param notifyUrl 通知地址 + * @deprecated Use me.chanjar.weixin.mp.api.WxMpService.getPayInfo(Map) instead + */ + @Deprecated + Map getJsapiPayInfo(String openId, String outTradeNo, double amt, String body, String ip, String notifyUrl) throws WxErrorException; + + /** + * 该接口提供所有微信支付订单的查询,当支付通知处理异常戒丢失的情冴,商户可以通过该接口查询订单支付状态。 + * 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2 + * + */ + WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo); + + /** + * 读取支付结果通知 + * 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7 + * + */ + WxMpPayCallback getJSSDKCallbackData(String xmlData); + + /** + * 微信支付-申请退款 + * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4 + * + * @param parameters 需要传入的退款参数的Map。以下几项为参数的必须项:
+ *
  • transaction_id + *
  • out_trade_no (仅在上述transaction_id为空时是必须项) + *
  • out_refund_no + *
  • total_fee + *
  • refund_fee + * @return 退款操作结果 + */ + WxMpPayRefundResult refundPay(Map parameters) throws WxErrorException; + + /** + *
    +   * 计算Map键值对是否和签名相符,
    +   * 按照字段名的 ASCII 码从小到大排序(字典序)后,使用 URL 键值对的 格式(即 key1=value1&key2=value2...)拼接成字符串
    +   * 
    + * + */ + boolean checkJSSDKCallbackDataSignature(Map kvm, String signature); + + /** + * 发送微信红包给个人用户 + *

    + * 需要传入的必填参数如下: + * mch_billno//商户订单号 + * send_name//商户名称 + * re_openid//用户openid + * total_amount//红包总额 + * total_num//红包发放总人数 + * wishing//红包祝福语 + * client_ip//服务器Ip地址 + * act_name//活动名称 + * remark //备注 + * 文档详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5 + *

    + * 使用现金红包功能需要在xml配置文件中额外设置: + * 微信商户平台ID + * 商户平台设置的API密钥 + * + */ + WxRedpackResult sendRedpack(Map parameters) throws WxErrorException; +} 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 3811647b..d1366152 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 @@ -7,7 +7,6 @@ import me.chanjar.weixin.mp.bean.*; import me.chanjar.weixin.mp.bean.result.*; import java.text.SimpleDateFormat; -import java.util.Map; /** * 微信API的Service @@ -90,8 +89,6 @@ public interface WxMpService { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口 * * - * @param news - * @throws WxErrorException * @see #massGroupMessageSend(me.chanjar.weixin.mp.bean.WxMpMassGroupMessage) * @see #massOpenIdsMessageSend(me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage) */ @@ -134,7 +131,6 @@ public interface WxMpService { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口 * * - * @param long_url */ String shortUrl(String long_url) throws WxErrorException; @@ -144,9 +140,7 @@ public interface WxMpService { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=模板消息接口 * * - * @param templateMessage * @return msgid - * @throws WxErrorException */ String templateSend(WxMpTemplateMessage templateMessage) throws WxErrorException; @@ -164,8 +158,6 @@ public interface WxMpService { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息 * * - * @param scope - * @param state * @return url */ String oauth2buildAuthorizationUrl(String scope, String state); @@ -177,8 +169,6 @@ public interface WxMpService { * * * @param redirectURI 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode - * @param scope - * @param state * @return url */ String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state); @@ -203,7 +193,6 @@ public interface WxMpService { * 用oauth2获取用户信息, 当前面引导授权时的scope是snsapi_userinfo的时候才可以 * * - * @param oAuth2AccessToken * @param lang zh_CN, zh_TW, en */ WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException; @@ -213,7 +202,6 @@ public interface WxMpService { * 验证oauth2的access token是否有效 * * - * @param oAuth2AccessToken */ boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken); @@ -265,146 +253,13 @@ public interface WxMpService { */ void setMaxRetryTimes(int maxRetryTimes); - /** - * 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) - * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" - * - * @param openId 支付人openId - * @param outTradeNo 商户端对应订单号 - * @param amt 金额(单位元) - * @param body 商品描述 - * @param tradeType 交易类型 JSAPI,NATIVE,APP,WAP - * @param ip 发起支付的客户端IP - * @param notifyUrl 通知地址 - * @deprecated Use me.chanjar.weixin.mp.api.WxMpService.getPrepayId(Map) instead - */ - @Deprecated - WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String notifyUrl); - - /** - * 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) - * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" - * - * @param parameters All required/optional parameters for weixin payment - * @throws IllegalArgumentException - */ - WxMpPrepayIdResult getPrepayId(Map parameters); - - /** - * 该接口调用“统一下单”接口,并拼装发起支付请求需要的参数 - * 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82 - * - * @param parameters the required or optional parameters - */ - Map getPayInfo(Map parameters) throws WxErrorException; - - /** - * 该接口调用“统一下单”接口,并拼装NATIVE发起支付请求需要的参数 - * 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82 - * tradeType 交易类型 NATIVE (其他交易类型JSAPI,APP,WAP) - * - * @param productId 商户商品ID - * @param outTradeNo 商户端对应订单号 - * @param amt 金额(单位元) - * @param body 商品描述 - * @param ip 发起支付的客户端IP - * @param notifyUrl 通知地址 - * @deprecated Use me.chanjar.weixin.mp.api.WxMpService.getPayInfo(Map) instead - */ - @Deprecated - Map getNativePayInfo(String productId, String outTradeNo, double amt, String body, String ip, String notifyUrl) throws WxErrorException; - - /** - * 该接口调用“统一下单”接口,并拼装JSAPI发起支付请求需要的参数 - * 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82 - * tradeType 交易类型 JSAPI(其他交易类型NATIVE,APP,WAP) - * - * @param openId 支付人openId - * @param outTradeNo 商户端对应订单号 - * @param amt 金额(单位元) - * @param body 商品描述 - * @param ip 发起支付的客户端IP - * @param notifyUrl 通知地址 - * @deprecated Use me.chanjar.weixin.mp.api.WxMpService.getPayInfo(Map) instead - */ - @Deprecated - Map getJsapiPayInfo(String openId, String outTradeNo, double amt, String body, String ip, String notifyUrl) throws WxErrorException; - - /** - * 该接口提供所有微信支付订单的查询,当支付通知处理异常戒丢失的情冴,商户可以通过该接口查询订单支付状态。 - * 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2 - * - * @param transactionId - * @param outTradeNo - */ - WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo); - - /** - * 读取支付结果通知 - * 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7 - * - * @param xmlData - */ - WxMpPayCallback getJSSDKCallbackData(String xmlData); - - /** - * 微信支付-申请退款 - * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4 - * - * @param parameters 需要传入的退款参数的Map。以下几项为参数的必须项:
    - *

  • transaction_id - *
  • out_trade_no (仅在上述transaction_id为空时是必须项) - *
  • out_refund_no - *
  • total_fee - *
  • refund_fee - * @return 退款操作结果 - * @throws WxErrorException - */ - WxMpPayRefundResult refundPay(Map parameters) throws WxErrorException; - - /** - *
    -   * 计算Map键值对是否和签名相符,
    -   * 按照字段名的 ASCII 码从小到大排序(字典序)后,使用 URL 键值对的 格式(即 key1=value1&key2=value2...)拼接成字符串
    -   * 
    - * - * @param kvm - * @param signature - */ - boolean checkJSSDKCallbackDataSignature(Map kvm, String signature); - - /** - * 发送微信红包给个人用户 - *

    - * 需要传入的必填参数如下: - * mch_billno//商户订单号 - * send_name//商户名称 - * re_openid//用户openid - * total_amount//红包总额 - * total_num//红包发放总人数 - * wishing//红包祝福语 - * client_ip//服务器Ip地址 - * act_name//活动名称 - * remark //备注 - * 文档详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5 - *

    - * 使用现金红包功能需要在xml配置文件中额外设置: - * 微信商户平台ID - * 商户平台设置的API密钥 - * - * @param parameters - */ - WxRedpackResult sendRedpack(Map parameters) throws WxErrorException; - /** *

        * 预览接口
        * 详情请见:http://mp.weixin.qq.com/wiki/15/40b6865b893947b764e2de8e4a1fb55f.html#.E9.A2.84.E8.A7.88.E6.8E.A5.E5.8F.A3.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91
        * 
    * - * @param wxMpMassPreviewMessage * @return wxMpMassSendResult - * @throws WxErrorException */ WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception; @@ -415,9 +270,7 @@ public interface WxMpService { * 详情请见:http://mp.weixin.qq.com/wiki/5/6dde9eaa909f83354e0094dc3ad99e05.html#.E8.AE.BE.E7.BD.AE.E6.89.80.E5.B1.9E.E8.A1.8C.E4.B8.9A * * - * @param wxMpIndustry * @return JsonObject - * @throws WxErrorException */ String setIndustry(WxMpIndustry wxMpIndustry) throws WxErrorException; @@ -428,7 +281,6 @@ public interface WxMpService { * * * @return wxMpIndustry - * @throws WxErrorException */ WxMpIndustry getIndustry() throws WxErrorException; @@ -487,4 +339,11 @@ public interface WxMpService { * @return WxMpCardService */ WxMpCardService getCardService(); + + /** + * 返回微信支付相关接口的方法实现类,以方便调用个其各种接口 + * + * @return WxMpPayService + */ + WxMpPayService getPayService(); } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java new file mode 100644 index 00000000..81638b7a --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java @@ -0,0 +1,335 @@ +package me.chanjar.weixin.mp.api.impl; + +import com.thoughtworks.xstream.XStream; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.crypto.WxCryptUtil; +import me.chanjar.weixin.common.util.http.Utf8ResponseHandler; +import me.chanjar.weixin.common.util.xml.XStreamInitializer; +import me.chanjar.weixin.mp.api.WxMpPayService; +import me.chanjar.weixin.mp.bean.result.*; +import org.apache.http.Consts; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.helpers.MessageFormatter; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Created by Binary Wang on 2016/7/28. + * @author binarywang (https://github.com/binarywang) + */ +public class WxMpPayServiceImpl implements WxMpPayService { + + private final Logger log = LoggerFactory.getLogger(WxMpPayServiceImpl.class); + + private HttpHost httpProxy; + private WxMpServiceImpl wxMpService; + + public WxMpPayServiceImpl(WxMpServiceImpl wxMpService) { + this.wxMpService = wxMpService; + this.httpProxy = wxMpService.getHttpProxy(); + } + + @Override + public WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String callbackUrl) { + Map packageParams = new HashMap<>(); + packageParams.put("appid", this.wxMpService.getWxMpConfigStorage().getAppId()); + packageParams.put("mch_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + packageParams.put("body", body); + packageParams.put("out_trade_no", outTradeNo); + packageParams.put("total_fee", (int) (amt * 100) + ""); + packageParams.put("spbill_create_ip", ip); + packageParams.put("notify_url", callbackUrl); + packageParams.put("trade_type", tradeType); + packageParams.put("openid", openId); + + return getPrepayId(packageParams); + } + + @Override + public WxMpPrepayIdResult getPrepayId(final Map parameters) { + String nonce_str = System.currentTimeMillis() + ""; + + final SortedMap packageParams = new TreeMap<>(parameters); + packageParams.put("appid", this.wxMpService.getWxMpConfigStorage().getAppId()); + packageParams.put("mch_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + packageParams.put("nonce_str", nonce_str); + checkParameters(packageParams); + + String sign = WxCryptUtil.createSign(packageParams, this.wxMpService.getWxMpConfigStorage().getPartnerKey()); + packageParams.put("sign", sign); + + StringBuilder request = new StringBuilder(""); + for (Map.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/pay/unifiedorder"); + if (this.httpProxy != null) { + RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); + httpPost.setConfig(config); + } + + StringEntity entity = new StringEntity(request.toString(), Consts.UTF_8); + httpPost.setEntity(entity); + try(CloseableHttpResponse response = this.wxMpService.getHttpclient().execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + XStream xstream = XStreamInitializer.getInstance(); + xstream.alias("xml", WxMpPrepayIdResult.class); + return (WxMpPrepayIdResult) xstream.fromXML(responseContent); + } catch (IOException e) { + throw new RuntimeException("Failed to get prepay id due to IO exception.", e); + }finally { + httpPost.releaseConnection(); + } + } + + private final String[] REQUIRED_ORDER_PARAMETERS = new String[] { "appid", "mch_id", "body", "out_trade_no", + "total_fee", "spbill_create_ip", "notify_url","trade_type"}; + + private void checkParameters(Map parameters) { + for (String para : this.REQUIRED_ORDER_PARAMETERS) { + if (!parameters.containsKey(para)) + throw new IllegalArgumentException("Reqiured argument '" + para + "' is missing."); + } + if ("JSAPI".equals(parameters.get("trade_type")) && !parameters.containsKey("openid")) + throw new IllegalArgumentException("Reqiured argument 'openid' is missing when trade_type is 'JSAPI'."); + if ("NATIVE".equals(parameters.get("trade_type")) && !parameters.containsKey("product_id")) + throw new IllegalArgumentException("Reqiured argument 'product_id' is missing when trade_type is 'NATIVE'."); + } + + @Override + public Map getJsapiPayInfo(String openId,String outTradeNo, double amt, String body,String ip, String callbackUrl) throws WxErrorException { + Map packageParams = new HashMap<>(); + packageParams.put("appid", this.wxMpService.getWxMpConfigStorage().getAppId()); + packageParams.put("mch_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + packageParams.put("body", body); + packageParams.put("out_trade_no", outTradeNo); + packageParams.put("total_fee", (int) (amt * 100) + ""); + packageParams.put("spbill_create_ip", ip); + packageParams.put("notify_url", callbackUrl); + packageParams.put("trade_type", "JSAPI"); + packageParams.put("openid", openId); + + return getPayInfo(packageParams); + } + + @Override + public Map getNativePayInfo(String productId,String outTradeNo, double amt, String body,String ip, String callbackUrl) throws WxErrorException{ + Map packageParams = new HashMap<>(); + packageParams.put("appid", this.wxMpService.getWxMpConfigStorage().getAppId()); + packageParams.put("mch_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + packageParams.put("body", body); + packageParams.put("out_trade_no", outTradeNo); + packageParams.put("total_fee", (int) (amt * 100) + ""); + packageParams.put("spbill_create_ip", ip); + packageParams.put("notify_url", callbackUrl); + packageParams.put("trade_type", "NATIVE"); + packageParams.put("product_id", productId); + + return getPayInfo(packageParams); + } + + @Override + public Map getPayInfo(Map parameters) throws WxErrorException { + WxMpPrepayIdResult wxMpPrepayIdResult = getPrepayId(parameters); + + if (!"SUCCESS".equalsIgnoreCase(wxMpPrepayIdResult.getReturn_code()) + ||!"SUCCESS".equalsIgnoreCase(wxMpPrepayIdResult.getResult_code())) { + WxError error = new WxError(); + error.setErrorCode(-1); + error.setErrorMsg("return_code:" + wxMpPrepayIdResult.getReturn_code() + + ";return_msg:" + wxMpPrepayIdResult.getReturn_msg() + + ";result_code:" + wxMpPrepayIdResult.getResult_code() + + ";err_code" + wxMpPrepayIdResult.getErr_code() + + ";err_code_des" + wxMpPrepayIdResult.getErr_code_des()); + throw new WxErrorException(error); + } + + String prepayId = wxMpPrepayIdResult.getPrepay_id(); + if (prepayId == null || prepayId.equals("")) { + throw new RuntimeException(String.format("Failed to get prepay id due to error code '%s'(%s).", wxMpPrepayIdResult.getErr_code(), wxMpPrepayIdResult.getErr_code_des())); + } + + Map payInfo = new HashMap<>(); + payInfo.put("appId", this.wxMpService.getWxMpConfigStorage().getAppId()); + // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 + payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); + payInfo.put("nonceStr", System.currentTimeMillis() + ""); + payInfo.put("package", "prepay_id=" + prepayId); + payInfo.put("signType", "MD5"); + if("NATIVE".equals(parameters.get("trade_type"))){ + payInfo.put("codeUrl", wxMpPrepayIdResult.getCode_url()); + } + + String finalSign = WxCryptUtil.createSign(payInfo, this.wxMpService.getWxMpConfigStorage().getPartnerKey()); + payInfo.put("paySign", finalSign); + return payInfo; + } + + @Override + public WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo) { + String nonce_str = System.currentTimeMillis() + ""; + + SortedMap packageParams = new TreeMap<>(); + packageParams.put("appid", this.wxMpService.getWxMpConfigStorage().getAppId()); + packageParams.put("mch_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + if (transactionId != null && !"".equals(transactionId.trim())) + packageParams.put("transaction_id", transactionId); + else if (outTradeNo != null && !"".equals(outTradeNo.trim())) + packageParams.put("out_trade_no", outTradeNo); + else + throw new IllegalArgumentException("Either 'transactionId' or 'outTradeNo' must be given."); + packageParams.put("nonce_str", nonce_str); + packageParams.put("sign", WxCryptUtil.createSign(packageParams, this.wxMpService.getWxMpConfigStorage().getPartnerKey())); + + StringBuilder request = new StringBuilder(""); + for (Map.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/pay/orderquery"); + if (this.httpProxy != null) { + RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); + httpPost.setConfig(config); + } + + StringEntity entity = new StringEntity(request.toString(), Consts.UTF_8); + httpPost.setEntity(entity); + try(CloseableHttpResponse response = this.wxMpService.getHttpclient().execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + XStream xstream = XStreamInitializer.getInstance(); + xstream.alias("xml", WxMpPayResult.class); + return (WxMpPayResult) xstream.fromXML(responseContent); + } catch (IOException e) { + throw new RuntimeException("Failed to query order due to IO exception.", e); + } + } + + @Override + public WxMpPayCallback getJSSDKCallbackData(String xmlData) { + try { + XStream xstream = XStreamInitializer.getInstance(); + xstream.alias("xml", WxMpPayCallback.class); + return (WxMpPayCallback) xstream.fromXML(xmlData); + } catch (Exception e){ + e.printStackTrace(); + } + + return new WxMpPayCallback(); + } + + @Override + public WxMpPayRefundResult refundPay(Map parameters) throws WxErrorException { + SortedMap refundParams = new TreeMap<>(parameters); + refundParams.put("appid", this.wxMpService.getWxMpConfigStorage().getAppId()); + refundParams.put("mch_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + refundParams.put("nonce_str", System.currentTimeMillis() + ""); + refundParams.put("op_user_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + String sign = WxCryptUtil.createSign(refundParams, this.wxMpService.getWxMpConfigStorage().getPartnerKey()); + refundParams.put("sign", sign); + + StringBuilder request = new StringBuilder(""); + for (Map.Entry para : refundParams.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/secapi/pay/refund"); + if (this.httpProxy != null) { + RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); + httpPost.setConfig(config); + } + + StringEntity entity = new StringEntity(request.toString(), Consts.UTF_8); + httpPost.setEntity(entity); + try(CloseableHttpResponse response = this.wxMpService.getHttpclient().execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpPayRefundResult.class); + WxMpPayRefundResult wxMpPayRefundResult = (WxMpPayRefundResult) xstream.fromXML(responseContent); + + if (!"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getResultCode()) + ||!"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getReturnCode())) { + WxError error = new WxError(); + error.setErrorCode(-1); + error.setErrorMsg("return_code:" + wxMpPayRefundResult.getReturnCode() + + ";return_msg:" + wxMpPayRefundResult.getReturnMsg() + + ";result_code:" + wxMpPayRefundResult.getResultCode() + + ";err_code" + wxMpPayRefundResult.getErrCode() + + ";err_code_des" + wxMpPayRefundResult.getErrCodeDes()); + throw new WxErrorException(error); + } + + return wxMpPayRefundResult; + } catch (IOException e) { + this.log.error(MessageFormatter.format("The exception was happened when sending refund '{}'.", + request.toString()).getMessage(), e); + WxError error = new WxError(); + error.setErrorCode(-1); + error.setErrorMsg("incorrect response."); + throw new WxErrorException(error); + }finally { + httpPost.releaseConnection(); + } + } + + @Override + public boolean checkJSSDKCallbackDataSignature(Map kvm, String signature) { + return signature.equals(WxCryptUtil.createSign(kvm, this.wxMpService.getWxMpConfigStorage().getPartnerKey())); + } + + @Override + public WxRedpackResult sendRedpack(Map parameters) throws WxErrorException { + String nonce_str = System.currentTimeMillis() + ""; + + SortedMap packageParams = new TreeMap<>(parameters); + packageParams.put("wxappid", this.wxMpService.getWxMpConfigStorage().getAppId()); + packageParams.put("mch_id", this.wxMpService.getWxMpConfigStorage().getPartnerId()); + packageParams.put("nonce_str", nonce_str); + + String sign = WxCryptUtil.createSign(packageParams, this.wxMpService.getWxMpConfigStorage().getPartnerKey()); + packageParams.put("sign", sign); + + StringBuilder request = new StringBuilder(""); + for (Map.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 (this.httpProxy != null) { + RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); + httpPost.setConfig(config); + } + + StringEntity entity = new StringEntity(request.toString(), Consts.UTF_8); + httpPost.setEntity(entity); + try(CloseableHttpResponse response = this.wxMpService.getHttpclient().execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxRedpackResult.class); + return (WxRedpackResult) xstream.fromXML(responseContent); + } catch (IOException e) { + this.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); + }finally { + httpPost.releaseConnection(); + } + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java index 505d01fd..f0ca0d17 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java @@ -5,7 +5,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.internal.Streams; import com.google.gson.stream.JsonReader; -import com.thoughtworks.xstream.XStream; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.bean.WxJsapiSignature; import me.chanjar.weixin.common.bean.result.WxError; @@ -14,35 +13,23 @@ import me.chanjar.weixin.common.session.StandardSessionManager; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.RandomUtils; import me.chanjar.weixin.common.util.crypto.SHA1; -import me.chanjar.weixin.common.util.crypto.WxCryptUtil; import me.chanjar.weixin.common.util.http.*; -import me.chanjar.weixin.common.util.xml.XStreamInitializer; import me.chanjar.weixin.mp.api.*; import me.chanjar.weixin.mp.bean.*; import me.chanjar.weixin.mp.bean.result.*; -import org.apache.http.Consts; import org.apache.http.HttpHost; -import org.apache.http.client.ClientProtocolException; 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.BasicResponseHandler; import org.apache.http.impl.client.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.helpers.MessageFormatter; import java.io.IOException; import java.io.StringReader; import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.SortedMap; -import java.util.TreeMap; public class WxMpServiceImpl implements WxMpService { @@ -51,32 +38,38 @@ public class WxMpServiceImpl implements WxMpService { /** * 全局的是否正在刷新access token的锁 */ - protected final Object globalAccessTokenRefreshLock = new Object(); + private final Object globalAccessTokenRefreshLock = new Object(); /** * 全局的是否正在刷新jsapi_ticket的锁 */ - protected final Object globalJsapiTicketRefreshLock = new Object(); + private final Object globalJsapiTicketRefreshLock = new Object(); - protected WxMpConfigStorage wxMpConfigStorage; + private WxMpConfigStorage wxMpConfigStorage; - protected WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); + private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); - protected WxMpMaterialService materialService = new WxMpMaterialServiceImpl(this); + private WxMpMaterialService materialService = new WxMpMaterialServiceImpl(this); - protected WxMpMenuService menuService = new WxMpMenuServiceImpl(this); + private WxMpMenuService menuService = new WxMpMenuServiceImpl(this); - protected WxMpUserService userService = new WxMpUserServiceImpl(this); + private WxMpUserService userService = new WxMpUserServiceImpl(this); - protected WxMpGroupService groupService = new WxMpGroupServiceImpl(this); + private WxMpGroupService groupService = new WxMpGroupServiceImpl(this); - protected WxMpQrcodeService qrCodeService = new WxMpQrcodeServiceImpl(this); + private WxMpQrcodeService qrCodeService = new WxMpQrcodeServiceImpl(this); - protected WxMpCardService cardService = new WxMpCardServiceImpl(this); + private WxMpCardService cardService = new WxMpCardServiceImpl(this); - protected CloseableHttpClient httpClient; + private WxMpPayService payService = new WxMpPayServiceImpl(this); - protected HttpHost httpProxy; + private CloseableHttpClient httpClient; + + private HttpHost httpProxy; + + public HttpHost getHttpProxy() { + return this.httpProxy; + } private int retrySleepMillis = 1000; @@ -106,10 +99,9 @@ public class WxMpServiceImpl implements WxMpService { if (this.wxMpConfigStorage.isAccessTokenExpired()) { synchronized (this.globalAccessTokenRefreshLock) { if (this.wxMpConfigStorage.isAccessTokenExpired()) { - String url = new StringBuffer() - .append("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential") - .append("&appid=").append(this.wxMpConfigStorage.getAppId()) - .append("&secret=").append(this.wxMpConfigStorage.getSecret()).toString(); + String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + + "&appid=" + this.wxMpConfigStorage.getAppId() + + "&secret=" + this.wxMpConfigStorage.getSecret(); try { HttpGet httpGet = new HttpGet(url); if (this.httpProxy != null) { @@ -127,8 +119,6 @@ public class WxMpServiceImpl implements WxMpService { }finally { httpGet.releaseConnection(); } - } catch (ClientProtocolException e) { - throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } @@ -148,6 +138,7 @@ public class WxMpServiceImpl implements WxMpService { if (forceRefresh) { this.wxMpConfigStorage.expireJsapiTicket(); } + if (this.wxMpConfigStorage.isJsapiTicketExpired()) { synchronized (this.globalJsapiTicketRefreshLock) { if (this.wxMpConfigStorage.isJsapiTicketExpired()) { @@ -258,7 +249,7 @@ public class WxMpServiceImpl implements WxMpService { @Override public String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state) { - StringBuffer url = new StringBuffer(); + StringBuilder url = new StringBuilder(); url.append("https://open.weixin.qq.com/connect/oauth2/authorize?"); url.append("appid=").append(this.wxMpConfigStorage.getAppId()); url.append("&redirect_uri=").append(URIUtil.encodeURIComponent(redirectURI)); @@ -273,7 +264,7 @@ public class WxMpServiceImpl implements WxMpService { @Override public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException { - StringBuffer url = new StringBuffer(); + StringBuilder url = new StringBuilder(); url.append("https://api.weixin.qq.com/sns/oauth2/access_token?"); url.append("appid=").append(this.wxMpConfigStorage.getAppId()); url.append("&secret=").append(this.wxMpConfigStorage.getSecret()); @@ -284,8 +275,6 @@ public class WxMpServiceImpl implements WxMpService { RequestExecutor executor = new SimpleGetRequestExecutor(); String responseText = executor.execute(getHttpclient(), this.httpProxy, url.toString(), null); return WxMpOAuth2AccessToken.fromJson(responseText); - } catch (ClientProtocolException e) { - throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } @@ -293,7 +282,7 @@ public class WxMpServiceImpl implements WxMpService { @Override public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException { - StringBuffer url = new StringBuffer(); + StringBuilder url = new StringBuilder(); url.append("https://api.weixin.qq.com/sns/oauth2/refresh_token?"); url.append("appid=").append(this.wxMpConfigStorage.getAppId()); url.append("&grant_type=refresh_token"); @@ -303,8 +292,6 @@ public class WxMpServiceImpl implements WxMpService { RequestExecutor executor = new SimpleGetRequestExecutor(); String responseText = executor.execute(getHttpclient(), this.httpProxy, url.toString(), null); return WxMpOAuth2AccessToken.fromJson(responseText); - } catch (ClientProtocolException e) { - throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } @@ -312,7 +299,7 @@ public class WxMpServiceImpl implements WxMpService { @Override public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException { - StringBuffer url = new StringBuffer(); + StringBuilder url = new StringBuilder(); url.append("https://api.weixin.qq.com/sns/userinfo?"); url.append("access_token=").append(oAuth2AccessToken.getAccessToken()); url.append("&openid=").append(oAuth2AccessToken.getOpenId()); @@ -326,8 +313,6 @@ public class WxMpServiceImpl implements WxMpService { RequestExecutor executor = new SimpleGetRequestExecutor(); String responseText = executor.execute(getHttpclient(), this.httpProxy, url.toString(), null); return WxMpUser.fromJson(responseText); - } catch (ClientProtocolException e) { - throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } @@ -335,7 +320,7 @@ public class WxMpServiceImpl implements WxMpService { @Override public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken) { - StringBuffer url = new StringBuffer(); + StringBuilder url = new StringBuilder(); url.append("https://api.weixin.qq.com/sns/auth?"); url.append("access_token=").append(oAuth2AccessToken.getAccessToken()); url.append("&openid=").append(oAuth2AccessToken.getOpenId()); @@ -343,8 +328,6 @@ public class WxMpServiceImpl implements WxMpService { try { RequestExecutor executor = new SimpleGetRequestExecutor(); executor.execute(getHttpclient(), this.httpProxy, url.toString(), null); - } catch (ClientProtocolException e) { - throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (WxErrorException e) { @@ -434,14 +417,12 @@ public class WxMpServiceImpl implements WxMpService { throw new WxErrorException(error); } return null; - } catch (ClientProtocolException e) { - throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } - protected CloseableHttpClient getHttpclient() { + public CloseableHttpClient getHttpclient() { return this.httpClient; } @@ -486,301 +467,6 @@ public class WxMpServiceImpl implements WxMpService { this.maxRetryTimes = maxRetryTimes; } - @Override - public WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, double amt, String body, String tradeType, String ip, String callbackUrl) { - Map packageParams = new HashMap<>(); - packageParams.put("appid", this.wxMpConfigStorage.getAppId()); - packageParams.put("mch_id", this.wxMpConfigStorage.getPartnerId()); - packageParams.put("body", body); - packageParams.put("out_trade_no", outTradeNo); - packageParams.put("total_fee", (int) (amt * 100) + ""); - packageParams.put("spbill_create_ip", ip); - packageParams.put("notify_url", callbackUrl); - packageParams.put("trade_type", tradeType); - packageParams.put("openid", openId); - - return getPrepayId(packageParams); - } - - @Override - public WxMpPrepayIdResult getPrepayId(final Map parameters) { - String nonce_str = System.currentTimeMillis() + ""; - - final SortedMap packageParams = new TreeMap<>(parameters); - packageParams.put("appid", this.wxMpConfigStorage.getAppId()); - packageParams.put("mch_id", this.wxMpConfigStorage.getPartnerId()); - packageParams.put("nonce_str", nonce_str); - checkParameters(packageParams); - - String sign = WxCryptUtil.createSign(packageParams, this.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/pay/unifiedorder"); - if (this.httpProxy != null) { - RequestConfig config = RequestConfig.custom().setProxy(this.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.alias("xml", WxMpPrepayIdResult.class); - WxMpPrepayIdResult wxMpPrepayIdResult = (WxMpPrepayIdResult) xstream.fromXML(responseContent); - return wxMpPrepayIdResult; - } catch (IOException e) { - throw new RuntimeException("Failed to get prepay id due to IO exception.", e); - }finally { - httpPost.releaseConnection(); - } - } - - final String[] REQUIRED_ORDER_PARAMETERS = new String[] { "appid", "mch_id", "body", "out_trade_no", "total_fee", "spbill_create_ip", "notify_url", - "trade_type", }; - - private void checkParameters(Map parameters) { - for (String para : this.REQUIRED_ORDER_PARAMETERS) { - if (!parameters.containsKey(para)) - throw new IllegalArgumentException("Reqiured argument '" + para + "' is missing."); - } - if ("JSAPI".equals(parameters.get("trade_type")) && !parameters.containsKey("openid")) - throw new IllegalArgumentException("Reqiured argument 'openid' is missing when trade_type is 'JSAPI'."); - if ("NATIVE".equals(parameters.get("trade_type")) && !parameters.containsKey("product_id")) - throw new IllegalArgumentException("Reqiured argument 'product_id' is missing when trade_type is 'NATIVE'."); - } - - @Override - public Map getJsapiPayInfo(String openId,String outTradeNo, double amt, String body,String ip, String callbackUrl) throws WxErrorException{ - Map packageParams = new HashMap<>(); - packageParams.put("appid", this.wxMpConfigStorage.getAppId()); - packageParams.put("mch_id", this.wxMpConfigStorage.getPartnerId()); - packageParams.put("body", body); - packageParams.put("out_trade_no", outTradeNo); - packageParams.put("total_fee", (int) (amt * 100) + ""); - packageParams.put("spbill_create_ip", ip); - packageParams.put("notify_url", callbackUrl); - packageParams.put("trade_type", "JSAPI"); - packageParams.put("openid", openId); - - return getPayInfo(packageParams); - } - - @Override - public Map getNativePayInfo(String productId,String outTradeNo, double amt, String body,String ip, String callbackUrl) throws WxErrorException{ - Map packageParams = new HashMap<>(); - packageParams.put("appid", this.wxMpConfigStorage.getAppId()); - packageParams.put("mch_id", this.wxMpConfigStorage.getPartnerId()); - packageParams.put("body", body); - packageParams.put("out_trade_no", outTradeNo); - packageParams.put("total_fee", (int) (amt * 100) + ""); - packageParams.put("spbill_create_ip", ip); - packageParams.put("notify_url", callbackUrl); - packageParams.put("trade_type", "NATIVE"); - packageParams.put("product_id", productId); - - return getPayInfo(packageParams); - } - - @Override - public Map getPayInfo(Map parameters) throws WxErrorException { - WxMpPrepayIdResult wxMpPrepayIdResult = getPrepayId(parameters); - - if (!"SUCCESS".equalsIgnoreCase(wxMpPrepayIdResult.getReturn_code()) - ||!"SUCCESS".equalsIgnoreCase(wxMpPrepayIdResult.getResult_code())) { - WxError error = new WxError(); - error.setErrorCode(-1); - error.setErrorMsg("return_code:" + wxMpPrepayIdResult.getReturn_code() + - ";return_msg:" + wxMpPrepayIdResult.getReturn_msg() + - ";result_code:" + wxMpPrepayIdResult.getResult_code() + - ";err_code" + wxMpPrepayIdResult.getErr_code() + - ";err_code_des" + wxMpPrepayIdResult.getErr_code_des()); - throw new WxErrorException(error); - } - - String prepayId = wxMpPrepayIdResult.getPrepay_id(); - if (prepayId == null || prepayId.equals("")) { - throw new RuntimeException(String.format("Failed to get prepay id due to error code '%s'(%s).", wxMpPrepayIdResult.getErr_code(), wxMpPrepayIdResult.getErr_code_des())); - } - - Map payInfo = new HashMap<>(); - payInfo.put("appId", this.wxMpConfigStorage.getAppId()); - // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 - payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); - payInfo.put("nonceStr", System.currentTimeMillis() + ""); - payInfo.put("package", "prepay_id=" + prepayId); - payInfo.put("signType", "MD5"); - if("NATIVE".equals(parameters.get("trade_type"))){ - payInfo.put("codeUrl", wxMpPrepayIdResult.getCode_url()); - } - - String finalSign = WxCryptUtil.createSign(payInfo, this.wxMpConfigStorage.getPartnerKey()); - payInfo.put("paySign", finalSign); - return payInfo; - } - - @Override - public WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo) { - String nonce_str = System.currentTimeMillis() + ""; - - SortedMap packageParams = new TreeMap<>(); - packageParams.put("appid", this.wxMpConfigStorage.getAppId()); - packageParams.put("mch_id", this.wxMpConfigStorage.getPartnerId()); - if (transactionId != null && !"".equals(transactionId.trim())) - packageParams.put("transaction_id", transactionId); - else if (outTradeNo != null && !"".equals(outTradeNo.trim())) - packageParams.put("out_trade_no", outTradeNo); - else - throw new IllegalArgumentException("Either 'transactionId' or 'outTradeNo' must be given."); - packageParams.put("nonce_str", nonce_str); - packageParams.put("sign", WxCryptUtil.createSign(packageParams, this.wxMpConfigStorage.getPartnerKey())); - - 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/pay/orderquery"); - if (this.httpProxy != null) { - RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); - httpPost.setConfig(config); - } - - StringEntity entity = new StringEntity(request.toString(), Consts.UTF_8); - httpPost.setEntity(entity); - try(CloseableHttpResponse response = this.httpClient.execute(httpPost)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - XStream xstream = XStreamInitializer.getInstance(); - xstream.alias("xml", WxMpPayResult.class); - WxMpPayResult wxMpPayResult = (WxMpPayResult) xstream.fromXML(responseContent); - return wxMpPayResult; - } catch (IOException e) { - throw new RuntimeException("Failed to query order due to IO exception.", e); - } - } - - @Override - public WxMpPayCallback getJSSDKCallbackData(String xmlData) { - try { - XStream xstream = XStreamInitializer.getInstance(); - xstream.alias("xml", WxMpPayCallback.class); - WxMpPayCallback wxMpCallback = (WxMpPayCallback) xstream.fromXML(xmlData); - return wxMpCallback; - } catch (Exception e){ - e.printStackTrace(); - } - return new WxMpPayCallback(); - } - - @Override - public WxMpPayRefundResult refundPay(Map parameters) throws WxErrorException { - SortedMap refundParams = new TreeMap<>(parameters); - refundParams.put("appid", this.wxMpConfigStorage.getAppId()); - refundParams.put("mch_id", this.wxMpConfigStorage.getPartnerId()); - refundParams.put("nonce_str", System.currentTimeMillis() + ""); - refundParams.put("op_user_id", this.wxMpConfigStorage.getPartnerId()); - String sign = WxCryptUtil.createSign(refundParams, this.wxMpConfigStorage.getPartnerKey()); - refundParams.put("sign", sign); - - StringBuilder request = new StringBuilder(""); - for (Entry para : refundParams.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/secapi/pay/refund"); - if (this.httpProxy != null) { - RequestConfig config = RequestConfig.custom().setProxy(this.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(WxMpPayRefundResult.class); - WxMpPayRefundResult wxMpPayRefundResult = (WxMpPayRefundResult) xstream.fromXML(responseContent); - - if (!"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getResultCode()) - ||!"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getReturnCode())) { - WxError error = new WxError(); - error.setErrorCode(-1); - error.setErrorMsg("return_code:" + wxMpPayRefundResult.getReturnCode() + - ";return_msg:" + wxMpPayRefundResult.getReturnMsg() + - ";result_code:" + wxMpPayRefundResult.getResultCode() + - ";err_code" + wxMpPayRefundResult.getErrCode() + - ";err_code_des" + wxMpPayRefundResult.getErrCodeDes()); - throw new WxErrorException(error); - } - - return wxMpPayRefundResult; - } catch (IOException e) { - this.log.error(MessageFormatter.format("The exception was happened when sending refund '{}'.", request.toString()).getMessage(), e); - WxError error = new WxError(); - error.setErrorCode(-1); - error.setErrorMsg("incorrect response."); - throw new WxErrorException(error); - }finally { - httpPost.releaseConnection(); - } - } - - @Override - public boolean checkJSSDKCallbackDataSignature(Map kvm, String signature) { - return signature.equals(WxCryptUtil.createSign(kvm, this.wxMpConfigStorage.getPartnerKey())); - } - - @Override - public WxRedpackResult sendRedpack(Map parameters) throws WxErrorException { - String nonce_str = System.currentTimeMillis() + ""; - - SortedMap packageParams = new TreeMap<>(parameters); - packageParams.put("wxappid", this.wxMpConfigStorage.getAppId()); - packageParams.put("mch_id", this.wxMpConfigStorage.getPartnerId()); - packageParams.put("nonce_str", nonce_str); - - String sign = WxCryptUtil.createSign(packageParams, this.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 (this.httpProxy != null) { - RequestConfig config = RequestConfig.custom().setProxy(this.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) { - this.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); - }finally { - httpPost.releaseConnection(); - } - } - @Override public WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception { String url = "https://api.weixin.qq.com/cgi-bin/message/mass/preview"; @@ -840,4 +526,9 @@ public class WxMpServiceImpl implements WxMpService { return this.cardService; } + @Override + public WxMpPayService getPayService() { + return this.payService; + } + } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java new file mode 100644 index 00000000..79a5a631 --- /dev/null +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImplTest.java @@ -0,0 +1,60 @@ +package me.chanjar.weixin.mp.api.impl; + +import org.testng.annotations.Test; + +/** + * Created by Binary Wang on 2016/7/28. + * @author binarywang (https://github.com/binarywang) + */ +public class WxMpPayServiceImplTest { + @Test + public void testGetPrepayId() throws Exception { + + } + + @Test + public void testGetPrepayId1() throws Exception { + + } + + @Test + public void testGetJsapiPayInfo() throws Exception { + + } + + @Test + public void testGetNativePayInfo() throws Exception { + + } + + @Test + public void testGetPayInfo() throws Exception { + + } + + @Test + public void testGetJSSDKPayResult() throws Exception { + + } + + @Test + public void testGetJSSDKCallbackData() throws Exception { + + } + + @Test + public void testRefundPay() throws Exception { + + } + + @Test + public void testCheckJSSDKCallbackDataSignature() throws Exception { + + } + + @Test + public void testSendRedpack() throws Exception { + + } + +} \ No newline at end of file