From 2fd160a5df2d50ab130497a0c5f7e2d991863a6e Mon Sep 17 00:00:00 2001 From: xiangwbs Date: Mon, 26 Mar 2018 20:47:25 +0800 Subject: [PATCH] merge --- boot-service/pom.xml | 22 + .../entity/alipay/AlipayBarCodePayParam.java | 44 ++ .../entity/alipay/AlipayBarCodePayResult.java | 67 +++ .../entity/alipay/AlipayBaseResult.java | 29 + .../domain/entity/alipay/AlipayConstant.java | 39 ++ .../entity/alipay/AlipayQueryResult.java | 27 + .../entity/alipay/AlipayRefundParam.java | 53 ++ .../entity/alipay/AlipayRefundResult.java | 58 ++ .../entity/alipay/AlipayTradeStatusEnum.java | 30 + .../entity/wxpay/WxBarCodePayParam.java | 44 ++ .../entity/wxpay/WxBarCodePayResult.java | 72 +++ .../domain/entity/wxpay/WxBaseResult.java | 58 ++ .../domain/entity/wxpay/WxQueryResult.java | 26 + .../domain/entity/wxpay/WxRefundParam.java | 53 ++ .../domain/entity/wxpay/WxRefundResult.java | 74 +++ .../entity/wxpay/WxRefundStatusEnum.java | 31 + .../entity/wxpay/WxTradeStatusEnum.java | 34 ++ .../com/xwbing/exception/PayException.java | 22 + .../xwbing/service/other/AlipayService.java | 321 ++++++++++ .../xwbing/service/other/WxPayService.java | 554 ++++++++++++++++++ .../com/xwbing/util/payAlipay/AlipayCore.java | 94 +++ .../xwbing/util/payAlipay/AlipayNotify.java | 123 ++++ .../xwbing/util/payAlipay/AlipaySubmit.java | 218 +++++++ .../com/xwbing/util/payAlipay/Base64.java | 279 +++++++++ .../util/payAlipay/HttpProtocolHandler.java | 191 ++++++ .../xwbing/util/payAlipay/HttpRequest.java | 152 +++++ .../xwbing/util/payAlipay/HttpResponse.java | 72 +++ .../xwbing/util/payAlipay/HttpResultType.java | 27 + .../java/com/xwbing/util/payAlipay/MD5.java | 68 +++ .../java/com/xwbing/util/payAlipay/RSA.java | 119 ++++ .../xwbing/util/payWxpay/ClientCustomSSL.java | 98 ++++ .../java/com/xwbing/util/payWxpay/IPKit.java | 68 +++ .../xwbing/util/payWxpay/MD5Signature.java | 77 +++ .../com/xwbing/util/payWxpay/MD5Util.java | 54 ++ .../com/xwbing/util/payWxpay/Md5Encrypt.java | 69 +++ .../com/xwbing/util/payWxpay/RandomKit.java | 30 + .../java/com/xwbing/util/payWxpay/SHA1.java | 232 ++++++++ .../com/xwbing/util/payWxpay/StringUtil.java | 346 +++++++++++ .../com/xwbing/util/payWxpay/WxSignKit.java | 94 +++ .../com/xwbing/util/payWxpay/XmlUtil.java | 107 ++++ .../src/main/resources/pay.properties | 21 + 41 files changed, 4197 insertions(+) create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayParam.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBaseResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayConstant.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayQueryResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundParam.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayTradeStatusEnum.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayParam.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBaseResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxQueryResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundParam.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundResult.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundStatusEnum.java create mode 100644 boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxTradeStatusEnum.java create mode 100644 boot-service/src/main/java/com/xwbing/exception/PayException.java create mode 100644 boot-service/src/main/java/com/xwbing/service/other/AlipayService.java create mode 100644 boot-service/src/main/java/com/xwbing/service/other/WxPayService.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayCore.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayNotify.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/AlipaySubmit.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/Base64.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/HttpProtocolHandler.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/HttpRequest.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResponse.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResultType.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/MD5.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payAlipay/RSA.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/ClientCustomSSL.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/IPKit.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Signature.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Util.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/Md5Encrypt.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/RandomKit.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/SHA1.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/StringUtil.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/WxSignKit.java create mode 100644 boot-service/src/main/java/com/xwbing/util/payWxpay/XmlUtil.java create mode 100644 boot-service/src/main/resources/pay.properties diff --git a/boot-service/pom.xml b/boot-service/pom.xml index f2bccd7..7971e6e 100644 --- a/boot-service/pom.xml +++ b/boot-service/pom.xml @@ -105,5 +105,27 @@ javax.servlet-api provided + + + dom4j + dom4j + 1.6.1 + + + org.jdom + jdom + 2.0.2 + + + com.alipay.sdk + alipay + 1.0 + + + + commons-httpclient + commons-httpclient + 3.1 + \ No newline at end of file diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayParam.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayParam.java new file mode 100644 index 0000000..7ee395c --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayParam.java @@ -0,0 +1,44 @@ +package com.xwbing.domain.entity.alipay; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +/** + * 说明: 支付宝扫码支付参数 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:34 + * 作者: xiangwb + */ +@Data +public class AlipayBarCodePayParam { + /** + * 本系统订单号 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + /** + * 条形码 授权 + */ + @JSONField(name = "auth_code") + private String authCode; + /** + * 支付场景 条码支付或者是声波支付 + */ + private String scene; + /** + * 订单标题 商品名称 + */ + private String subject; + /** + * 总金额 + */ + @JSONField(name = "total_amount") + private float totalAmount; + + public AlipayBarCodePayParam(String outTradeNo, String authCode, String subject, float totalAmount) { + this.outTradeNo = outTradeNo; + this.authCode = authCode; + this.subject = subject; + this.totalAmount = totalAmount; + } +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayResult.java new file mode 100644 index 0000000..ea7f729 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBarCodePayResult.java @@ -0,0 +1,67 @@ +package com.xwbing.domain.entity.alipay; + +import com.alipay.api.domain.TradeFundBill; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.List; + +/** + * 说明: 条形码支付结果 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:36 + * 作者: xiangwb + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AlipayBarCodePayResult extends AlipayBaseResult { + public static final String HASPAYED = "ACQ.TRADE_HAS_SUCCESS"; + + /** + * 支付宝交易号 + */ + private String tradeNo; + /** + * 商户订单号 + */ + private String outTradeNo; + /** + * 买家支付宝账号 + */ + private String buyerLogonId; + /** + * 交易金额 + */ + private String totalAmount; + /** + * 实收金额 + */ + private String receiptAmount; + /** + * 交易支付时间 + */ + private Date gmtPayment; + /** + * 交易支付使用的资金渠道 + */ + private List fundBillList; + /** + * 买家在支付宝的用户id + */ + private String buyerUserId; + /** + * 本次交易支付所使用的单品券优惠的商品优惠信息 + */ + private String discountGoodsDetail; + + public AlipayBarCodePayResult(boolean isSuccess) { + this.setSuccess(isSuccess); + } + + @Override + public boolean isSuccess() { + //如果已支付过也认为是成功 + return super.getCode().equals("10000"); + } +} \ No newline at end of file diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBaseResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBaseResult.java new file mode 100644 index 0000000..1c97f1f --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayBaseResult.java @@ -0,0 +1,29 @@ +package com.xwbing.domain.entity.alipay; + +import lombok.Data; + +/** + * 说明: 支付宝接口基础类 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:35 + * 作者: xiangwb + */ +@Data +public class AlipayBaseResult { + /** + * 是否成功 + */ + private boolean isSuccess; + /** + * 错误信息 + */ + private String message; + /** + * 错误码 + */ + private String code; + /** + * 签名信息 + */ + private String sign; +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayConstant.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayConstant.java new file mode 100644 index 0000000..beb3619 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayConstant.java @@ -0,0 +1,39 @@ +package com.xwbing.domain.entity.alipay; + +/** + * 说明: 支付宝返回状态码 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:37 + * 作者: xiangwb + */ + +public class AlipayConstant { + /** + * 接口调用成功 + */ + public static String success_code = "10000"; + /** + * 服务不可用 + */ + public static String service_unavailable = "20000"; + /** + * 授权权限不足 + */ + public static String limited_authority = "20001"; + /** + * 缺少必选参数 + */ + public static String miss_argument = "40001"; + /** + * 非法的参数 + */ + public static String invalid_argument = "40002"; + /** + * 业务处理失败 + */ + public static String business_handling_failure = "40004"; + /** + * 权限不足 + */ + public static String insufficient_permissions = "40006"; +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayQueryResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayQueryResult.java new file mode 100644 index 0000000..186b443 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayQueryResult.java @@ -0,0 +1,27 @@ +package com.xwbing.domain.entity.alipay; + +/** + * 说明: 支付宝查询状态 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:38 + * 作者: xiangwb + */ +public class AlipayQueryResult extends AlipayBaseResult { + /** + * 交易支付状态 + */ + private String tradeStatus; + + public AlipayQueryResult(boolean success) { + this.setSuccess(success); + } + + public String getTradeStatus() { + return tradeStatus; + } + + public void setTradeStatus(String tradeStatus) { + this.tradeStatus = tradeStatus; + } +} + diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundParam.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundParam.java new file mode 100644 index 0000000..2f5a8ce --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundParam.java @@ -0,0 +1,53 @@ +package com.xwbing.domain.entity.alipay; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +/** + * 说明: 支付宝退款参数 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:38 + * 作者: xiangwb + */ +@Data +public class AlipayRefundParam { + /** + * 本系统订单号 订单号和支付宝交易号2选1 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + /** + * 支付宝交易号(推荐) 订单号和支付宝交易号2选1 + */ + @JSONField(name = "trade_no") + private String tradeNo; + /** + * 退款请求号 + */ + @JSONField(name = "out_request_no") + private String outRequestNo; + /** + * 退款金额 + */ + @JSONField(name = "refund_amount") + private float refundAmount; + /** + * 退款的原因说明 可选 + */ + @JSONField(name = "refund_reason") + private String refundReason; + + public AlipayRefundParam(String outRequestNo, String outTradeNo, float refundAmount, String refundReason) { + this.outRequestNo = outRequestNo; + this.outTradeNo = outTradeNo; + this.refundAmount = refundAmount; + this.refundReason = refundReason; + } + + public AlipayRefundParam(String outRequestNo, String tradeNo, String refundReason, float refundAmount) { + this.outRequestNo = outRequestNo; + this.tradeNo = tradeNo; + this.refundAmount = refundAmount; + this.refundReason = refundReason; + } +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundResult.java new file mode 100644 index 0000000..e25978d --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayRefundResult.java @@ -0,0 +1,58 @@ +package com.xwbing.domain.entity.alipay; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 说明: 支付宝退款结果 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:39 + * 作者: xiangwb + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class AlipayRefundResult extends AlipayBaseResult { + /** + * 支付宝交易号 + */ + @JSONField(name = "trade_no") + private String tradeNo; + /** + * 商户订单号 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + /** + * 用户的登录id + */ + @JSONField(name = "buyer_logon_id") + private String buyerLogonId; + /** + * 本次退款是否发生了资金变化 Y|N + */ + @JSONField(name = "fund_change") + private String fundChange; + /** + * 退款金额 + */ + @JSONField(name = "refund_fee") + private String refundFee; + /** + * 退款支付时间 + */ + @JSONField(name = "gmt_refund_pay") + private Date gmtRefundPay; + /** + * 买家在支付宝的用户id + */ + @JSONField(name = "buyer_user_id") + private String buyerUserId; + + public AlipayRefundResult(boolean isSuccess) { + this.setSuccess(isSuccess); + } +} + diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayTradeStatusEnum.java b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayTradeStatusEnum.java new file mode 100644 index 0000000..f8ce39c --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/alipay/AlipayTradeStatusEnum.java @@ -0,0 +1,30 @@ +package com.xwbing.domain.entity.alipay; + +/** + * 说明: 支付宝交易状态 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:39 + * 作者: xiangwb + */ +public enum AlipayTradeStatusEnum { + WAIT_BUYER_PAY("交易创建,等待买家付款", "WAIT_BUYER_PAY"), + TRADE_CLOSED("未付款交易超时关闭,或支付完成后全额退款", "TRADE_CLOSED"), + TRADE_SUCCESS("交易支付成功", "TRADE_SUCCESS"), + CLOSED("交易结束,不可退款", "TRADE_FINISHED"); + // 成员变量 + private String code; + private String name; + + private AlipayTradeStatusEnum(String name, String code) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayParam.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayParam.java new file mode 100644 index 0000000..e781de1 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayParam.java @@ -0,0 +1,44 @@ +package com.xwbing.domain.entity.wxpay; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +/** + * 说明: 微信扫码支付接口参数 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:41 + * 作者: xiangwb + */ +@Data +public class WxBarCodePayParam { + /** + * 商户订单号 + */ + private String outTradeNo; + /** + * 请求ip + */ + private String spblillCreateIp; + /** + * 授权码 扫码支付授权码,设备读取用户微信中的条码或者二维码信息 + */ + private String authCode; + /** + * 商品描述 + */ + private String body; + /** + * 订单金额 订单总金额,单位为分,只能为整数,详见支付金额 + */ + @JSONField(name = "total_fee") + private int totalFee; + + public WxBarCodePayParam(String outTradeNo, String spblillCreateIp, String authCode, String body, int totalFee) { + this.outTradeNo = outTradeNo; + this.spblillCreateIp = spblillCreateIp; + this.authCode = authCode; + this.body = body; + this.totalFee = totalFee; + } + +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayResult.java new file mode 100644 index 0000000..ad2cfff --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBarCodePayResult.java @@ -0,0 +1,72 @@ +package com.xwbing.domain.entity.wxpay; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 说明: 微信扫码支付接口结果 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:42 + * 作者: xiangwb + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WxBarCodePayResult extends WxBaseResult { + public static final String HASPAYED = "ORDERPAID"; + /** + * 公众账号ID + */ + private String appid; + /** + * 商户号 + */ + private String mchId; + /** + * 随机字符串 + */ + private String nonceStr; + /** + * 签名 + */ + private String sign; + /** + * 用户在商户appid 下的唯一标识 + */ + private String openId; + /** + * 用户是否关注公众账号,仅在公众账号类型支付有效,取值范围:Y或N;Y-关注;N-未关注 + */ + private String isSubscribe; + /** + * 支付类型为MICROPAY(即扫码支付) + */ + private String tradeType; + /** + * 付款银行 + */ + private String bankType; + /** + * 订单金额 + */ + private int totalFee; + /** + * 现金支付金额 + */ + private int cashFee; + /** + * 微信支付订单号 + */ + private String transactionId; + /** + * 商户订单号 + */ + private String outTradeNo; + /** + * 支付完成时间 + */ + private String timeEnd; + + public WxBarCodePayResult(boolean isSuccess) { + this.setSuccess(isSuccess); + } +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBaseResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBaseResult.java new file mode 100644 index 0000000..48cae96 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxBaseResult.java @@ -0,0 +1,58 @@ +package com.xwbing.domain.entity.wxpay; + +import org.apache.commons.lang3.StringUtils; + +/** + * 说明: 微信支付结果基础类 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:42 + * 作者: xiangwb + */ + +public class WxBaseResult { + /** + * 返回状态码 + */ + private String resultCode; + /** + * 返回信息 + */ + private String message; + /** + * 是否成功 + */ + private boolean isSuccess; + + public boolean isSuccess() { + if (StringUtils.isNotEmpty(resultCode)) { + return resultCode.equals("SUCCESS"); + } + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } + + public String getResultCode() { + return resultCode; + } + + public void setResultCode(String resultCode) { + this.resultCode = resultCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public String toString() { + return "BaseResult{" + "resultCode='" + resultCode + '\'' + ", message='" + message + '\'' + ", isSuccess=" + isSuccess + '}'; + } +} + diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxQueryResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxQueryResult.java new file mode 100644 index 0000000..d5399fb --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxQueryResult.java @@ -0,0 +1,26 @@ +package com.xwbing.domain.entity.wxpay; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 说明: 微信查询状态 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:43 + * 作者: xiangwb + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WxQueryResult extends WxBaseResult { + /** + * 交易支付状态 + */ + private String tradeStatus; + /** + * 退款状态 + */ + private String refundStatus; + public WxQueryResult(boolean success) { + this.setSuccess(success); + } +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundParam.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundParam.java new file mode 100644 index 0000000..aa80f3e --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundParam.java @@ -0,0 +1,53 @@ +package com.xwbing.domain.entity.wxpay; + +import lombok.Data; + +/** + * 说明: 微信退款参数 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:43 + * 作者: xiangwb + */ +@Data +public class WxRefundParam { + /** + * 微信订单号(推荐) 微信订单号和商户订单号2选1 + */ + private String transactionId; + /** + * 商户订单号 微信订单号和商户订单号2选1 + */ + private String outTradeNo; + /** + * 商户退款单号 + */ + private String outRefundNo; + /** + * 订单金额 + */ + private int totalFee; + /** + * 退款金额 + */ + private int refundFee; + /** + * 操作员账号 + */ + private String opUserId; + + public WxRefundParam(String outTradeNo, String outRefundNo, int totalFee, int refundFee, String opUserId) { + this.outTradeNo = outTradeNo; + this.outRefundNo = outRefundNo; + this.totalFee = totalFee; + this.refundFee = refundFee; + this.opUserId = opUserId; + } + public WxRefundParam(String transactionId, String outRefundNo, String opUserId, int totalFee, int refundFee) { + this.transactionId = transactionId; + this.outRefundNo = outRefundNo; + this.totalFee = totalFee; + this.refundFee = refundFee; + this.opUserId = opUserId; + } +} + diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundResult.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundResult.java new file mode 100644 index 0000000..a831832 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundResult.java @@ -0,0 +1,74 @@ +package com.xwbing.domain.entity.wxpay; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 说明: 退款返回结果 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:44 + * 作者: xiangwb + */ +@EqualsAndHashCode(callSuper = true) +@Data +public class WxRefundResult extends WxBaseResult { + /** + * 微信分配的公众账号ID + */ + private String appid; + /** + * 微信支付分配的商户号 + */ + @JSONField(name = "mch_id") + private String mchId; + /** + * 随机字符串 + */ + @JSONField(name = "nonce_str") + private String nonceStr; + /** + * 签名 + */ + @JSONField(name = "sign") + private String sign; + /** + * 微信订单号 + */ + @JSONField(name = "transaction_id") + private String transactionId; + /** + * 商户订单号 + */ + @JSONField(name = "out_trade_no") + private String outTradeNo; + /** + * 商户退款单号 + */ + @JSONField(name = "out_refund_no") + private String outRefundNo; + /** + * 微信退款单号 + */ + @JSONField(name = "refund_id") + private String refundId; + /** + * 退款金额 单位分 + */ + @JSONField(name = "refund_fee") + private int refundFee; + /** + * 订单总金额 单位分 + */ + @JSONField(name = "total_fee") + private int totalFee; + /** + * 现金支付金额 单位分 + */ + @JSONField(name = "cash_fee") + private int cashFee; + + public WxRefundResult(boolean isSuccess) { + this.setSuccess(isSuccess); + } +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundStatusEnum.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundStatusEnum.java new file mode 100644 index 0000000..468c432 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxRefundStatusEnum.java @@ -0,0 +1,31 @@ +package com.xwbing.domain.entity.wxpay; + +/** + * 说明: 微信退款状态 + * 项目名称: spring-demo + * 创建时间: 2017/5/12 10:05 + * 作者: xiangwb + */ + +public enum WxRefundStatusEnum { + SUCCESS("退款成功", "SUCCESS"), + REFUNDCLOSE("退款关闭", "REFUNDCLOSE"), + PROCESSING("退款处理中", "PROCESSING"), + CHANGE("退款失败", "CHANGE"); + // 成员变量 + private String code; + private String name; + + WxRefundStatusEnum(String name, String code) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } +} diff --git a/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxTradeStatusEnum.java b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxTradeStatusEnum.java new file mode 100644 index 0000000..91b13e3 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/domain/entity/wxpay/WxTradeStatusEnum.java @@ -0,0 +1,34 @@ +package com.xwbing.domain.entity.wxpay; + +/** + * 说明: 微信支付结果状态 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:44 + * 作者: xiangwb + */ + +public enum WxTradeStatusEnum { + SUCCESS("支付成功", "SUCCESS"), + REFUND("转入退款", "REFUND"), + NOTPAY("未支付", "NOTPAY"), + CLOSED("已关闭", "CLOSED"), + REVOKED("已撤销(刷卡支付)", "REVOKED"), + USERPAYING("用户支付中", "USERPAYING"), + PAYERROR("支付失败", "PAYERROR"); + // 成员变量 + private String code; + private String name; + + WxTradeStatusEnum(String name, String code) { + this.code = code; + this.name = name; + } + + public String getCode() { + return code; + } + + public String getName() { + return name; + } +} diff --git a/boot-service/src/main/java/com/xwbing/exception/PayException.java b/boot-service/src/main/java/com/xwbing/exception/PayException.java new file mode 100644 index 0000000..dfbf40c --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/exception/PayException.java @@ -0,0 +1,22 @@ +package com.xwbing.exception; + +/** + * 说明: 支付异常 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:31 + * 作者: xiangwb + */ + +public class PayException extends RuntimeException { + public PayException(Throwable cause) { + super(cause); + } + + public PayException(String message) { + super(message); + } + + public PayException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/boot-service/src/main/java/com/xwbing/service/other/AlipayService.java b/boot-service/src/main/java/com/xwbing/service/other/AlipayService.java new file mode 100644 index 0000000..0553792 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/service/other/AlipayService.java @@ -0,0 +1,321 @@ +package com.xwbing.service.other; + +import com.alibaba.fastjson.JSONObject; +import com.alipay.api.AlipayApiException; +import com.alipay.api.AlipayClient; +import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.request.AlipayTradePayRequest; +import com.alipay.api.request.AlipayTradeQueryRequest; +import com.alipay.api.request.AlipayTradeRefundRequest; +import com.alipay.api.response.AlipayTradePayResponse; +import com.alipay.api.response.AlipayTradeQueryResponse; +import com.alipay.api.response.AlipayTradeRefundResponse; +import com.xwbing.domain.entity.alipay.*; +import com.xwbing.exception.BusinessException; +import com.xwbing.exception.PayException; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +/** + * 说明: 支付宝支付接口实现 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:30 + * 作者: xiangwb + */ +@Service +@PropertySource("classpath:pay.properties") +public class AlipayService { + private final Logger logger = LoggerFactory.getLogger(AlipayService.class); + /** + * 支付宝分配给开发者的应用ID + */ + @Value("${alipay.appId}") + private static String appId; + /** + * 私钥 + */ + @Value("${alipay.rsaPrivateKey}") + private static String privateKey; + /** + * 公钥 + */ + @Value("${alipay.rsaPublicKey}") + private static String publicKey; + /** + * 请求url + */ + @Value("${alipay.requestUrl}") + private static String requestUrl; + + /** + * 条形码扫码付 + * + * @param param + * @return + */ + public AlipayBarCodePayResult barCodePay(AlipayBarCodePayParam param) throws PayException { + param.setScene("bar_code");//设置条码支付 + AlipayBarCodePayResult result = new AlipayBarCodePayResult(false); + String checkArgument = checkArgument(param); + if (StringUtils.isNotEmpty(checkArgument)) { + result.setMessage(checkArgument); + return result; + } + //获得初始化的AlipayClient + AlipayClient alipayClient = new DefaultAlipayClient(requestUrl, appId, privateKey, "json", "UTF-8", publicKey, "RSA2"); + //创建API对应的request类 + AlipayTradePayRequest request = new AlipayTradePayRequest(); + request.setBizContent(JSONObject.toJSONString(param)); + //通过alipayClient调用API,获得对应的response类 + AlipayTradePayResponse response = null; + try { + response = alipayClient.execute(request); + } catch (AlipayApiException e) { + logger.info(e.getMessage()); + result.setMessage(e.getMessage()); + return result; + } + if (response==null) { + logger.info("alipay response is null!"); + result.setMessage("alipay response is null!"); + return result; + } + result.setSuccess(response.isSuccess()); //源码StringUtils.isEmpty(this.subCode) + //根据response中的结果继续业务逻辑处理:subCode不为空,表示接口调用失败|code为10000,代表接口调用成功,subCode为空 + if (StringUtils.isNotEmpty(response.getSubCode())) { + result.setCode(response.getSubCode()); + result.setMessage(response.getSubMsg()); + logger.info(response.getSubMsg()); + return result; + } else { + result.setCode(response.getCode()); + result.setMessage(response.getMsg()); + } + result.setTradeNo(response.getTradeNo()); + result.setOutTradeNo(response.getOutTradeNo()); + result.setBuyerLogonId(response.getBuyerLogonId()); + result.setTotalAmount(response.getTotalAmount()); + result.setReceiptAmount(response.getReceiptAmount()); + result.setGmtPayment(response.getGmtPayment()); + result.setFundBillList(response.getFundBillList()); + result.setBuyerUserId(response.getBuyerUserId()); + result.setDiscountGoodsDetail(response.getDiscountGoodsDetail()); + logger.info("result = " + result); + return result; + } + + /** + * 退款 + * + * @param param + * @return + */ + public AlipayRefundResult refund(AlipayRefundParam param) throws PayException { + AlipayRefundResult result = new AlipayRefundResult(false); + AlipayClient alipayClient = new DefaultAlipayClient(requestUrl, appId, privateKey, "json", "UTF-8", publicKey, "RSA2"); + AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); + request.setBizContent(JSONObject.toJSONString(param)); + //通过alipayClient调用API,获得对应的response类 + AlipayTradeRefundResponse response = null; + try { + response = alipayClient.execute(request); + } catch (AlipayApiException e) { + logger.info(e.getMessage()); + result.setMessage(e.getMessage()); + return result; + } + if (response==null) { + logger.info("alipay response is null!"); + result.setMessage("alipay response is null!"); + return result; + } + result.setSuccess(response.isSuccess()); + //如果subCode存在,代表调用接接口失败 + if (StringUtils.isNotEmpty(response.getSubCode())) { + result.setCode(response.getSubCode()); + result.setMessage(response.getSubMsg()); + logger.info(response.getSubMsg()); + return result; + } else { + result.setCode(response.getCode()); + result.setMessage(response.getMsg()); + } + result.setTradeNo(response.getTradeNo()); + result.setOutTradeNo(response.getOutTradeNo()); + result.setBuyerLogonId(response.getBuyerLogonId()); + result.setFundChange(response.getFundChange()); + result.setRefundFee(response.getRefundFee()); + result.setGmtRefundPay(response.getGmtRefundPay()); + result.setBuyerUserId(response.getBuyerUserId()); + logger.info("result = " + result); + return result; + } + + /** + * 根据订单号 交易号查询 只需要一个即可 + * 如果isSuccess,根据tradeStatus,遍历AlipayTradeStatusEnum获取对应支付状态 + * + * @param outTradeNo 订单号 + * @param tradeNo 交易号(推荐) + * @return + */ + public AlipayQueryResult queryOrder(String outTradeNo, String tradeNo) throws PayException { + if (StringUtils.isEmpty(outTradeNo) && StringUtils.isEmpty(tradeNo)) { + throw new PayException("订单号和交易号不能同时为空!"); + } + AlipayQueryResult result = new AlipayQueryResult(false); + AlipayClient alipayClient = new DefaultAlipayClient(requestUrl, appId, privateKey, "json", "UTF-8", publicKey, "RSA2"); + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + JSONObject jsonObject = new JSONObject(); + if (StringUtils.isNotEmpty(outTradeNo)) { + jsonObject.put("out_trade_no", outTradeNo); + } + if (StringUtils.isNotEmpty(tradeNo)) { + jsonObject.put("trade_no", tradeNo); + } + request.setBizContent(jsonObject.toString()); + //通过alipayClient调用API,获得对应的response类 + AlipayTradeQueryResponse response = null; + try { + response = alipayClient.execute(request); + } catch (AlipayApiException e) { + logger.info(e.getMessage()); + result.setMessage(e.getMessage()); + return result; + } + if (response==null) { + logger.info("alipay response is null!"); + result.setMessage("alipay response is null!"); + return result; + } + result.setSuccess(response.isSuccess()); + //如果subCode存在,代表调用接接口失败 + if (StringUtils.isNotEmpty(response.getSubCode())) { + result.setCode(response.getSubCode()); + result.setMessage(response.getSubMsg()); + logger.info(response.getSubMsg()); + return result; + } else { + result.setCode(response.getCode()); + result.setMessage(response.getMsg()); + } + //返回支付状态,业务调用时,遍历AlipayTradeStatusEnum与tradeStatus做比较 + result.setTradeStatus(response.getTradeStatus()); + logger.info(response.getTradeStatus()); + return result; + } + + /** + * 退款查询 没有tradeStatus,isSuccess即为成功 + * 订单号和交易号2选1 + * + * @param outTradeNo 订单号 + * @param tradeNo 交易号(推荐) + * @param outRequestNo 退款请求号 + * @return + * @throws PayException + */ + public AlipayQueryResult queryRefund(String outTradeNo, String tradeNo, String outRequestNo) throws PayException { + if (StringUtils.isEmpty(outTradeNo) && StringUtils.isEmpty(tradeNo)) { + throw new PayException("订单号和交易号不能同时为空!"); + } + AlipayQueryResult result = new AlipayQueryResult(false); + AlipayClient alipayClient = new DefaultAlipayClient(requestUrl, appId, privateKey, "json", "UTF-8", publicKey, "RSA2"); + AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("out_request_no", outRequestNo); + if (StringUtils.isNotEmpty(outTradeNo)) { + jsonObject.put("out_trade_no", outTradeNo); + } + if (StringUtils.isNotEmpty(tradeNo)) { + jsonObject.put("trade_no", tradeNo); + } + request.setBizContent(jsonObject.toString()); + //通过alipayClient调用API,获得对应的response类 + AlipayTradeQueryResponse response = null; + try { + response = alipayClient.execute(request); + } catch (AlipayApiException e) { + logger.info(e.getMessage()); + result.setMessage(e.getMessage()); + return result; + } + if (response==null) { + logger.info("alipay response is null!"); + result.setMessage("alipay response is null!"); + return result; + } + result.setSuccess(response.isSuccess()); + //如果subCode存在,代表调用接接口失败。业务调用时只需判断result是否为success即可 + if (StringUtils.isNotEmpty(response.getSubCode())) { + result.setCode(response.getSubCode()); + result.setMessage(response.getSubMsg()); + logger.info(response.getSubMsg()); + } else { + result.setCode(response.getCode()); + result.setMessage(response.getMsg()); + logger.info(response.getMsg()); + } + return result; + } + + /** + * 入参校验 + * + * @return + */ + private String checkArgument(AlipayBarCodePayParam parma) { + String message; + if (StringUtils.isEmpty(parma.getOutTradeNo())) { + message = "订单号为空"; + } else if (StringUtils.isEmpty(parma.getAuthCode())) { + message = "授权码为空"; + } else if (StringUtils.isEmpty(parma.getSubject())) { + message = "产品名称为空"; + } else if (0 >= parma.getTotalAmount()) { + message = "金额必须大于0"; + } else { + message = StringUtils.EMPTY; + } + return message; + } + + public static void main(String[] args) { + //刷卡支付 + AlipayService alipayBuilder = new AlipayService(); + String orderNo = "201705180202"; +// String authCode = "286796667181427987"; +// AlipayBarCodePayParam codePayParam = new AlipayBarCodePayParam(orderNo, authCode, "test", 0.1f); +// AlipayBarCodePayResult codePayResult = alipayBuilder.barCodePay(codePayParam); +// System.out.println(codePayResult.isSuccess() + codePayResult.getMessage()); + + //查询订单 + String tradeNo = "2017051221001004630289850336"; + AlipayQueryResult queryResult = alipayBuilder.queryOrder(orderNo, tradeNo); + if (!queryResult.isSuccess()) { + throw new BusinessException(queryResult.getMessage()); + } + String tradeStatus = queryResult.getTradeStatus(); + for (AlipayTradeStatusEnum status : AlipayTradeStatusEnum.values()) { + if (Objects.equals(tradeStatus, status.getCode())) { + System.out.println(status.getName()); + break; + } + } + +// //退款操作 + AlipayRefundParam refundParam = new AlipayRefundParam("201505129999", orderNo, 0.05f, "test"); +// AlipayRefundResult refundResult = alipayBuilder.refund(refundParam); +// System.out.println(refundResult.getMessage()); + +// //查询退款 只要success,即为成功 +// AlipayQueryResult refund = alipayBuilder.queryRefund(orderNo, "", "201505129999"); +// System.out.println(refund.isSuccess()); + } +} diff --git a/boot-service/src/main/java/com/xwbing/service/other/WxPayService.java b/boot-service/src/main/java/com/xwbing/service/other/WxPayService.java new file mode 100644 index 0000000..1b1f6a1 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/service/other/WxPayService.java @@ -0,0 +1,554 @@ +package com.xwbing.service.other; + +import com.sun.xml.internal.ws.util.xml.XmlUtil; +import com.xwbing.domain.entity.wxpay.*; +import com.xwbing.exception.BusinessException; +import com.xwbing.exception.PayException; +import com.xwbing.util.payWxpay.ClientCustomSSL; +import com.xwbing.util.payWxpay.RandomKit; +import com.xwbing.util.payWxpay.WxSignKit; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.jdom2.JDOMException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * 说明: 微信支付接口实现 + * 项目名称: spring-demo + * 创建时间: 2017/5/10 17:30 + * 作者: xiangwb + */ +@Service +@PropertySource("classpath:pay.properties") +public class WxPayService { + private final Logger logger = LoggerFactory.getLogger(WxPayService.class); + /** + * 商户支付密钥 生成签名时用 + */ + @Value("${wx.apiKey}") + private static String apiKey; + /** + * 公众账号ID(企业号corpid) + */ + @Value("${wx.appId}") + private static String appId; + /** + * 商户号 + */ + @Value("${wx.mchId}") + private static String mchId; + /** + * 刷卡支付url + */ + @Value("${wx.barCodePayUrl}") + private static String barCodePayUrl; + /** + * 申请退款url + */ + @Value("${wx.refundUrl}") + private static String refundUrl; + /** + * 查询订单url + */ + @Value("${wx.orderQueryUrl}") + private static String orderQueryUrl; + /** + * 退款查询url + */ + @Value("${wx.refundQueryUrl}") + private static String refundQueryUrl; + + /** + * 条形码扫码付 + * @param param + * @return + * @throws PayException + */ + public WxBarCodePayResult barCodePay(WxBarCodePayParam param) throws PayException { + WxBarCodePayResult result = new WxBarCodePayResult(false); + HttpPost post = new HttpPost(barCodePayUrl); + //输入参数为转为strxml + String reqBody = buildBarCodeRequestBody(param); + post.setEntity(new StringEntity(reqBody, "UTF-8")); + CloseableHttpClient httpclient; + try { + //根据mchId读取微信证书,SSL创建安全连接 + httpclient = ClientCustomSSL.getCloseableHttpClient(mchId); + CloseableHttpResponse response = httpclient.execute(post); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity entity = response.getEntity(); + //返回结果为strxml + String content = EntityUtils.toString(entity, "UTF-8"); + logger.info(content + "===========++++"); + // 解析返回值 + Map returnMap; + returnMap = XmlUtil.doXMLParse(content); + //返回状态码SUCCESS/FAIL + result.setResultCode(returnMap.get("return_code")); + //返回信息 非空,为错误原因 + result.setMessage(returnMap.get("return_msg")); + if ("FAIL".equalsIgnoreCase(returnMap.get("return_code"))) { + logger.info("wx barCodePay failed!"); + result.setSuccess(false); + return result; + } + logger.info("outRefundNo=" + param.getOutTradeNo() + ",err_code=" + returnMap.get("err_code") + ",result_code=" + returnMap.get("result_code") + ",err_code_des=" + returnMap.get("err_code_des")); + //业务结果SUCCESS/FAIL + if ("SUCCESS".equals(returnMap.get("result_code"))) { + result.setSuccess(true); + result.setAppid(returnMap.get("appid")); + result.setMchId(returnMap.get("mch_id")); + result.setNonceStr(returnMap.get("nonce_str")); + result.setSign(returnMap.get("sign")); + result.setOpenId(returnMap.get("openid")); + result.setIsSubscribe(returnMap.get("is_subscribe")); + result.setTradeType(returnMap.get("trade_type")); + result.setBankType(returnMap.get("bank_type")); + result.setTotalFee(Integer.valueOf(returnMap.get("total_fee"))); + result.setCashFee(Integer.valueOf(returnMap.get("cash_fee"))); + result.setTransactionId(returnMap.get("transaction_id")); + result.setOutTradeNo(returnMap.get("out_trade_no")); + result.setTimeEnd(returnMap.get("time_end")); + } else { + result.setResultCode(returnMap.get("err_code")); + result.setMessage(returnMap.get("err_code_des")); + } + } else { + result.setMessage("error response"); + result.setSuccess(false); + } + } catch (ClientProtocolException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (IOException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (JDOMException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (Exception e) { + logger.error(e.getMessage()); + throw new PayException(e); + } + return result; + } + + /** + * 退款 + * @param param + * @return + * @throws PayException + */ + public WxRefundResult refund(WxRefundParam param) throws PayException { + WxRefundResult result = new WxRefundResult(false); + HttpPost post = new HttpPost(refundUrl); + //输入参数为转为strxml + String reqBody = buildRefundBarCodeRequestBody(param); + post.setEntity(new StringEntity(reqBody, "UTF-8")); + CloseableHttpClient httpclient; + try { + //根据mchId读取微信证书,SSL创建安全连接 + httpclient = ClientCustomSSL.getCloseableHttpClient(mchId); + CloseableHttpResponse response = httpclient.execute(post); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + //返回结果为strxml + HttpEntity entity = response.getEntity(); + String content = EntityUtils.toString(entity, "UTF-8"); + logger.info(content + "===========++++"); + // 解析返回值 + Map returnMap; + returnMap = XmlUtil.doXMLParse(content); + //返回状态码SUCCESS/FAIL + result.setResultCode(returnMap.get("return_code")); + //返回信息 非空,为错误原因 + result.setMessage(returnMap.get("return_msg")); + if ("FAIL".equalsIgnoreCase(returnMap.get("return_code"))) { + logger.info("wx barCodePay failed!"); + result.setSuccess(false); + return result; + } + logger.info("outRefundNo=" + param.getOutTradeNo() + ",err_code=" + returnMap.get("err_code") + ",result_code=" + returnMap.get("result_code") + ",err_code_des=" + returnMap.get("err_code_des")); + //业务结果SUCCESS/FAIL + if ("SUCCESS".equals(returnMap.get("result_code"))) { + result.setSuccess(true); + result.setAppid(returnMap.get("appid")); + result.setMchId(returnMap.get("mch_id")); + result.setNonceStr(returnMap.get("nonce_str")); + result.setSign(returnMap.get("sign")); + result.setTransactionId(returnMap.get("transaction_id")); + result.setOutTradeNo(returnMap.get("out_trade_no")); + result.setOutRefundNo(returnMap.get("out_refund_no")); + result.setRefundId(returnMap.get("refund_id")); + result.setRefundFee(Integer.valueOf(returnMap.get("refund_fee"))); + result.setTotalFee(Integer.valueOf(returnMap.get("total_fee"))); + result.setCashFee(Integer.valueOf(returnMap.get("cash_fee"))); + } else { + result.setResultCode(returnMap.get("err_code")); + result.setMessage(returnMap.get("err_code_des")); + } + } else { + result.setMessage("error response"); + result.setSuccess(false); + } + } catch (ClientProtocolException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (IOException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (JDOMException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (Exception e1) { + logger.error(e1.getMessage()); + throw new PayException(e1); + } + return result; + } + + /** + * 查询订单状态 + * 根据订单号 交易号查询 只需要一个即可 + * 如果isSuccess,根据tradeStatus,遍历WxTradeStatusEnum获取交易支付状态 + * + * @param outTradeNo 商户订单号 + * @param transactionId 微信的订单号(推荐) + * @return + */ + public WxQueryResult oderQuery(String outTradeNo, String transactionId) throws PayException { + WxQueryResult result = new WxQueryResult(false); + HttpPost post = new HttpPost(orderQueryUrl); + //输入参数为转为strxml + String reqBody = buildQueryRequestBody(outTradeNo, transactionId); + post.setEntity(new StringEntity(reqBody, "UTF-8")); + CloseableHttpClient httpclient; + try { + //根据mchId读取微信证书,SSL创建安全连接 + httpclient = ClientCustomSSL.getCloseableHttpClient(mchId); + CloseableHttpResponse response = httpclient.execute(post); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity entity = response.getEntity(); + //返回结果为strxml + String content = EntityUtils.toString(entity, "UTF-8"); + logger.info(content + "===========++++"); + // 解析返回值 + Map returnMap; + returnMap = XmlUtil.doXMLParse(content); + result.setResultCode(returnMap.get("return_code")); + result.setMessage(returnMap.get("return_msg")); + //此字段是通信标识,非交易标识 + if ("FAIL".equalsIgnoreCase(returnMap.get("return_code"))) { + logger.info("wx barCodePay failed!"); + result.setSuccess(false); + return result; + } + logger.info("outTradeNo=" + outTradeNo + ",transactionId=" + transactionId); + //业务结果 + if ("SUCCESS".equals(returnMap.get("result_code"))) { + result.setSuccess(true); + //交易状态 + result.setTradeStatus(returnMap.get("trade_state")); + return result; + } else { + result.setResultCode(returnMap.get("err_code")); + result.setMessage(returnMap.get("err_code_des")); + } + } else { + result.setMessage("error response"); + result.setSuccess(false); + } + } catch (ClientProtocolException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (IOException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (JDOMException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (Exception e1) { + logger.error(e1.getMessage()); + throw new PayException(e1); + } + return result; + } + + /** + * 查询退款 + * 参数四选一即可 + * 如果isSuccess,根据refundStatus,遍历WxRefundStatusEnum获取退款状态 + * + * @param outTradeNo 商户订单号 + * @param transactionId 微信的订单号 + * @param ouRefundNo 商户退款单号(推荐) + * @param refundid 微信退款单号(推荐) + * @return + * @throws PayException + */ + public WxQueryResult refundQuery(String outTradeNo, String transactionId, String ouRefundNo, String refundid) throws PayException { + WxQueryResult result = new WxQueryResult(false); + HttpPost post = new HttpPost(refundQueryUrl); + //输入参数为转为strxml + String reqBody = buildRefundQueryRequestBody(outTradeNo, transactionId, ouRefundNo, refundid); + post.setEntity(new StringEntity(reqBody, "UTF-8")); + CloseableHttpClient httpclient; + try { + //根据mchId读取微信证书,SSL创建安全连接 + httpclient = ClientCustomSSL.getCloseableHttpClient(mchId); + CloseableHttpResponse response = httpclient.execute(post); + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + HttpEntity entity = response.getEntity(); + //返回结果为strxml + String content = EntityUtils.toString(entity, "UTF-8"); + logger.info(content + "===========++++"); + // 解析返回值 + Map returnMap; + returnMap = XmlUtil.doXMLParse(content); + result.setResultCode(returnMap.get("return_code")); + result.setMessage(returnMap.get("return_msg")); + //此字段是通信标识,非交易标识 + if ("FAIL".equalsIgnoreCase(returnMap.get("return_code"))) { + logger.info("wx barCodePay failed!"); + result.setSuccess(false); + return result; + } + logger.info("outTradeNo=" + outTradeNo + ",transactionId=" + transactionId + "ouRefundNo=" + ouRefundNo + "refundId=" + refundid); + //业务结果 + if ("SUCCESS".equals(returnMap.get("result_code"))) { + result.setSuccess(true); + //第一笔退款状态 + result.setRefundStatus(returnMap.get("refund_status_0")); + return result; + } else { + result.setResultCode(returnMap.get("err_code")); + result.setMessage(returnMap.get("err_code_des")); + } + } else { + result.setMessage("error response"); + result.setSuccess(false); + } + } catch (ClientProtocolException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (IOException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (JDOMException e) { + logger.error(e.getMessage()); + throw new PayException(e); + } catch (Exception e) { + logger.error(e.getMessage()); + throw new PayException(e); + } + return result; + } + + /** + * 构建扫码参数 + * + * @param param 微信扫码支付接口参数实体 + * @return + */ + private String buildBarCodeRequestBody(WxBarCodePayParam param) { + Map params = new HashMap(); + params.put("appid", appId); + params.put("mch_id", mchId); + String nonce_str = RandomKit.buildRandom(32); + params.put("nonce_str", nonce_str); + params.put("body", param.getBody()); + params.put("out_trade_no", param.getOutTradeNo()); + //单位分 + params.put("total_fee", String.valueOf(param.getTotalFee())); + params.put("spbill_create_ip", param.getSpblillCreateIp()); + params.put("auth_code", param.getAuthCode()); + //签名放最后的 + String sign = WxSignKit.buildSign(params, apiKey); + params.put("sign", sign); + StringBuffer reqBody = new StringBuffer(); + reqBody.append(""); + for (String key : params.keySet()) { + String value = params.get(key); + reqBody.append("<").append(key).append(">").append(value).append(""); + } + reqBody.append(""); + logger.info(reqBody + "~~~------------"); + return reqBody.toString(); + } + + /** + * 构建退款参数 + * + * @param param 微信退款参数实体 + * @return + */ + private String buildRefundBarCodeRequestBody(WxRefundParam param) { + Map params = new HashMap(); + params.put("appid", appId); + params.put("mch_id", mchId); + String nonce_str = RandomKit.buildRandom(32); + params.put("nonce_str", nonce_str); + if (StringUtils.isEmpty(param.getTransactionId()) && StringUtils.isEmpty(param.getOutTradeNo())) { + throw new PayException("商户订单号和微信订单号不能同时为空!"); + } + if (StringUtils.isNotEmpty(param.getTransactionId())) { + params.put("transaction_id", param.getTransactionId()); + } + if (StringUtils.isNotEmpty(param.getOutTradeNo())) { + params.put("out_trade_no", param.getOutTradeNo()); + } + params.put("out_refund_no", param.getOutRefundNo()); + params.put("total_fee", String.valueOf(param.getTotalFee())); + params.put("refund_fee", String.valueOf(param.getRefundFee())); + params.put("op_user_id", param.getOpUserId()); + String sign = WxSignKit.buildSign(params, apiKey); + params.put("sign", sign); + //签名放最后的 + StringBuffer reqBody = new StringBuffer(); + reqBody.append(""); + for (String key : params.keySet()) { + String value = params.get(key); + reqBody.append("<").append(key).append(">").append(value).append(""); + } + reqBody.append(""); + logger.info(reqBody + "~~~------------"); + return reqBody.toString(); + } + + /** + * 查询封装参数 + * + * @param outTradeNo 商户订单号 + * @param transactionId 微信订单号 + * @return + */ + private String buildQueryRequestBody(String outTradeNo, String transactionId) { + Map params = new HashMap(); + params.put("appid", appId); + params.put("mch_id", mchId); + if (StringUtils.isEmpty(transactionId) && StringUtils.isEmpty(outTradeNo)) { + throw new PayException("商户订单号和微信订单号不能同时为空!"); + } + if (StringUtils.isNotEmpty(transactionId)) { + params.put("transaction_id", transactionId); + } + if (StringUtils.isNotEmpty(outTradeNo)) { + params.put("out_trade_no", outTradeNo); + } + String nonce_str = RandomKit.buildRandom(32); + params.put("nonce_str", nonce_str); + //签名放最后的 + String sign = WxSignKit.buildSign(params, apiKey); + params.put("sign", sign); + StringBuffer reqBody = new StringBuffer(); + reqBody.append(""); + for (String key : params.keySet()) { + String value = params.get(key); + reqBody.append("<").append(key).append(">").append(value).append(""); + } + reqBody.append(""); + logger.info(reqBody + "~~~------------"); + return reqBody.toString(); + } + + /** + * 查询封装参数 + * + * @param outTradeNo 商户订单号 + * @param transactionId 微信订单号 + * @param ouRefundNo 商户退款单号 + * @param refundid 微信退款单号 + * @return + */ + private String buildRefundQueryRequestBody(String outTradeNo, String transactionId, String ouRefundNo, String refundid) { + Map params = new HashMap(); + params.put("appid", appId); + params.put("mch_id", mchId); + if (StringUtils.isEmpty(transactionId) && StringUtils.isEmpty(outTradeNo) && StringUtils.isEmpty(ouRefundNo) && StringUtils.isEmpty(refundid)) { + throw new PayException("商户订单号,微信订单号,商户退款单号和微信退款单号不能同时为空!"); + } + if (StringUtils.isNotEmpty(transactionId)) { + params.put("transaction_id", transactionId); + } + if (StringUtils.isNotEmpty(outTradeNo)) { + params.put("out_trade_no", outTradeNo); + } + if (StringUtils.isNotEmpty(ouRefundNo)) { + params.put("out_refund_no", ouRefundNo); + } + if (StringUtils.isNotEmpty(refundid)) { + params.put("refund_id", refundid); + } + String nonce_str = RandomKit.buildRandom(32); + params.put("nonce_str", nonce_str); + //签名放最后的 + String sign = WxSignKit.buildSign(params, apiKey); + params.put("sign", sign); + StringBuffer reqBody = new StringBuffer(); + reqBody.append(""); + for (String key : params.keySet()) { + String value = params.get(key); + reqBody.append("<").append(key).append(">").append(value).append(""); + } + reqBody.append(""); + logger.info(reqBody + "~~~------------"); + return reqBody.toString(); + } + + public static void main(String[] args) { + WxPayService wxPayBuilder = new WxPayService(); + //刷卡支付 +// String authCode = "130203463134616871"; +// WxBarCodePayParam payParam = new WxBarCodePayParam("2017051200", "127.0.0.0", authCode, "test", 1); +// WxBarCodePayResult result = wxPayBuilder.barCodePay(payParam); +// System.out.println(result.isSuccess() + result.getMessage()); + + //查询订单 + String outTradeNo = "2017051200"; + String transactionId = "4001082001201705120512385115"; + WxQueryResult queryResult = wxPayBuilder.oderQuery(outTradeNo, transactionId); + if (!queryResult.isSuccess()) { + throw new BusinessException(queryResult.getMessage()); + } + String tradeStatus = queryResult.getTradeStatus(); + for (WxTradeStatusEnum status : WxTradeStatusEnum.values()) { + if (Objects.equals(tradeStatus, status.getCode())) { + System.out.println(status.getName()); + break; + } + } + + //退款操作 + WxRefundParam param = new WxRefundParam(transactionId, "2017051201", "xwbing", 1, 1); +// WxRefundResult refundResult = wxPayBuilder.refund(param); +// System.out.println(refundResult.isSuccess() + refundResult.getMessage()); + + //查询退款 + WxQueryResult refundQueryResult = wxPayBuilder.refundQuery(outTradeNo, transactionId, "", ""); + if (!refundQueryResult.isSuccess()) { + throw new BusinessException(refundQueryResult.getMessage()); + } + String refundStatus = refundQueryResult.getRefundStatus(); + for (WxRefundStatusEnum status : WxRefundStatusEnum.values()) { + if (Objects.equals(refundStatus, status.getCode())) { + System.out.println(status.getName()); + break; + } + } + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayCore.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayCore.java new file mode 100644 index 0000000..a215fa6 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayCore.java @@ -0,0 +1,94 @@ +package com.xwbing.util.payAlipay; + +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.httpclient.methods.multipart.FilePartSource; +import org.apache.commons.httpclient.methods.multipart.PartSource; + +import java.io.File; +import java.io.IOException; +import java.util.*; +/* * + *类名:AlipayFunction + *功能:支付宝接口公用函数类 + *详细:该类是请求、通知返回两个文件所调用的公用函数核心处理文件,不需要修改 + *版本:3.3 + *日期:2012-08-14 + *说明: + *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 + */ + +public class AlipayCore { + + /** + * 除去数组中的空值和签名参数 + * + * @param sArray 签名参数组 + * @return 去掉空值与签名参数后的新签名参数组 + */ + public static Map paraFilter(Map sArray) { + + Map result = new HashMap(); + + if (sArray == null || sArray.size() <= 0) { + return result; + } + + for (String key : sArray.keySet()) { + String value = sArray.get(key); + if (value == null || value.equals("") || key.equalsIgnoreCase("sign") + || key.equalsIgnoreCase("sign_type")) { + continue; + } + result.put(key, value); + } + + return result; + } + + /** + * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串 + * + * @param params 需要排序并参与字符拼接的参数组 + * @return 拼接后字符串 + */ + public static String createLinkString(Map params) { + + List keys = new ArrayList(params.keySet()); + Collections.sort(keys); + + String prestr = ""; + + for (int i = 0; i < keys.size(); i++) { + String key = keys.get(i); + String value = params.get(key); + + if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符 + prestr = prestr + key + "=" + value; + } else { + prestr = prestr + key + "=" + value + "&"; + } + } + + return prestr; + } + + + /** + * 生成文件摘要 + * + * @param strFilePath 文件路径 + * @param file_digest_type 摘要算法 + * @return 文件摘要结果 + */ + public static String getAbstract(String strFilePath, String file_digest_type) throws IOException { + PartSource file = new FilePartSource(new File(strFilePath)); + if (file_digest_type.equals("MD5")) { + return DigestUtils.md5Hex(file.createInputStream()); + } else if (file_digest_type.equals("SHA")) { + return DigestUtils.sha256Hex(file.createInputStream()); + } else { + return ""; + } + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayNotify.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayNotify.java new file mode 100644 index 0000000..ab4498a --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipayNotify.java @@ -0,0 +1,123 @@ +package com.xwbing.util.payAlipay; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + + +/* * + *类名:AlipayNotify + *功能:支付宝通知处理类 + *详细:处理支付宝各接口通知返回 + *版本:3.3 + *日期:2012-08-17 + *说明: + *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + *该代码仅供学习和研究支付宝接口使用,只是提供一个参考 + + *************************注意************************* + *调试通知返回时,可查看或改写log日志的写入TXT里的数据,来检查通知返回是否正常 + */ +public class AlipayNotify { + + /** + * 支付宝消息验证地址 + */ + private static final String HTTPS_VERIFY_URL = "https://mapi.payAlipay.com/gateway.do?service=notify_verify&"; + + /** + * 验证消息是否是支付宝发出的合法消息 + * @param params 通知返回来的参数数组 + * @return 验证结果 + */ + public static boolean verify(Map params) { + + //判断responsetTxt是否为true,isSign是否为true + //responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关 + //isSign不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关 + String responseTxt = "false"; + if(params.get("notify_id") != null) { + String notify_id = params.get("notify_id"); + responseTxt = verifyResponse(notify_id,params.get("partner")); + } + String sign = ""; + if(params.get("sign") != null) {sign = params.get("sign");} + boolean isSign = getSignVeryfy(params, sign); + + //写日志记录(若要调试,请取消下面两行注释) + //String sWord = "responseTxt=" + responseTxt + "\n isSign=" + isSign + "\n 返回回来的参数:" + AlipayCore.createLinkString(params); + //AlipayCore.logResult(sWord); + + if (isSign && responseTxt.equals("true")) { + return true; + } else { + return false; + } + } + + /** + * 根据反馈回来的信息,生成签名结果 + * @param Params 通知返回来的参数数组 + * @param sign 比对的签名结果 + * @return 生成的签名结果 + */ + private static boolean getSignVeryfy(Map Params, String sign) { + //过滤空值、sign与sign_type参数 + Map sParaNew = AlipayCore.paraFilter(Params); + //获取待签名字符串 + String preSignStr = AlipayCore.createLinkString(sParaNew); + //获得签名验证结果 + boolean isSign = false; + if(Params.get("sign_type").equals("MD5") ) { + isSign = MD5.verify(preSignStr, sign, Params.get("key"), "utf-8"); + }else{ + isSign = RSA.verify(preSignStr, sign, Params.get("key"), "utf-8"); + } + return isSign; + } + + /** + * 获取远程服务器ATN结果,验证返回URL + * @param notify_id 通知校验ID + * @return 服务器ATN结果 + * 验证结果集: + * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 + * true 返回正确信息 + * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟 + */ + private static String verifyResponse(String notify_id,String partner) { + //获取远程服务器ATN结果,验证是否是支付宝服务器发来的请求 + + String veryfy_url = HTTPS_VERIFY_URL + "partner=" + partner + "¬ify_id=" + notify_id; + + return checkUrl(veryfy_url); + } + + /** + * 获取远程服务器ATN结果 + * @param urlvalue 指定URL路径地址 + * @return 服务器ATN结果 + * 验证结果集: + * invalid命令参数不对 出现这个错误,请检测返回处理中partner和key是否为空 + * true 返回正确信息 + * false 请检查防火墙或者是服务器阻止端口问题以及验证时间是否超过一分钟 + */ + private static String checkUrl(String urlvalue) { + String inputLine = ""; + + try { + URL url = new URL(urlvalue); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection + .getInputStream())); + inputLine = in.readLine().toString(); + } catch (Exception e) { + e.printStackTrace(); + inputLine = ""; + } + + return inputLine; + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipaySubmit.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipaySubmit.java new file mode 100644 index 0000000..2d21317 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/AlipaySubmit.java @@ -0,0 +1,218 @@ +package com.xwbing.util.payAlipay; + +import org.apache.commons.httpclient.NameValuePair; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.Node; +import org.dom4j.io.SAXReader; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + + +/* * + *类名:AlipaySubmit + *功能:支付宝各接口请求提交类 + *详细:构造支付宝各接口表单HTML文本,获取远程HTTP数据 + *版本:3.3 + *日期:2012-08-13 + *说明: + *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 + */ + +public class AlipaySubmit { + + /** + * 支付宝提供给商户的服务接入网关URL(新) + */ + private static final String ALIPAY_GATEWAY_NEW = "https://mapi.payAlipay.com/gateway.do?"; + + /** + * 生成签名结果 + * @param sPara 要签名的数组 + * @return 签名结果字符串 + */ + public static String buildRequestMysign(Map sPara) { + String prestr = AlipayCore.createLinkString(sPara); //把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串 + String mysign = ""; + if(sPara.get("sign_type") !=null && sPara.get("sign_type").equals("RSA") ) { + mysign = RSA.sign(prestr, sPara.get("key"),sPara.get("_input_charset")); + }else{ + mysign = MD5.sign(prestr, sPara.get("key"),sPara.get("_input_charset")); + } + return mysign; + } + + /** + * 生成要请求给支付宝的参数数组 + * @param sParaTemp 请求前的参数数组 + * @return 要请求的参数数组 + */ + private static Map buildRequestPara(Map sParaTemp) { + //除去数组中的空值和签名参数 + Map sPara = AlipayCore.paraFilter(sParaTemp); + //生成签名结果 + String mysign = buildRequestMysign(sPara); + + //签名结果与签名方式加入请求提交参数组中 + sPara.put("sign", mysign); + sPara.put("sign_type", sParaTemp.get("sign_type")); + + return sPara; + } + + /** + * 建立请求,以表单HTML形式构造(默认) + * @param sParaTemp 请求参数数组 + * @param strMethod 提交方式。两个值可选:post、get + * @param strButtonName 确认按钮显示文字 + * @return 提交表单HTML文本 + */ + public static String buildRequest(Map sParaTemp, String strMethod, String strButtonName) { + //待请求参数数组 + Map sPara = buildRequestPara(sParaTemp); + List keys = new ArrayList(sPara.keySet()); + + StringBuffer sbHtml = new StringBuffer(); + + sbHtml.append("
"); + + for (int i = 0; i < keys.size(); i++) { + String name = (String) keys.get(i); + if("_input_charset".equals(name)) + continue; + String value = (String) sPara.get(name); + + sbHtml.append(""); + } + + //submit按钮控件请不要含有name属性 + sbHtml.append("
"); + sbHtml.append(""); + + return sbHtml.toString(); + } + + /** + * 建立请求,以表单HTML形式构造,带文件上传功能 + * @param sParaTemp 请求参数数组 + * @param strMethod 提交方式。两个值可选:post、get + * @param strButtonName 确认按钮显示文字 + * @param strParaFileName 文件上传的参数名 + * @return 提交表单HTML文本 + */ + public static String buildRequest(Map sParaTemp, String strMethod, String strButtonName, String strParaFileName) { + //待请求参数数组 + Map sPara = buildRequestPara(sParaTemp); + List keys = new ArrayList(sPara.keySet()); + + StringBuffer sbHtml = new StringBuffer(); + + sbHtml.append("
"); + + for (int i = 0; i < keys.size(); i++) { + String name = (String) keys.get(i); + String value = (String) sPara.get(name); + + sbHtml.append(""); + } + + sbHtml.append(""); + + //submit按钮控件请不要含有name属性 + sbHtml.append("
"); + + return sbHtml.toString(); + } + + /** + * 建立请求,以模拟远程HTTP的POST请求方式构造并获取支付宝的处理结果 + * 如果接口中没有上传文件参数,那么strParaFileName与strFilePath设置为空值 + * 如:buildRequest("", "",sParaTemp) + * @param strParaFileName 文件类型的参数名 + * @param strFilePath 文件路径 + * @param sParaTemp 请求参数数组 + * @return 支付宝处理结果 + * @throws Exception + */ + public static String buildRequest(String strParaFileName, String strFilePath,Map sParaTemp) throws Exception { + //待请求参数数组 + Map sPara = buildRequestPara(sParaTemp); + + HttpProtocolHandler httpProtocolHandler = HttpProtocolHandler.getInstance(); + + HttpRequest request = new HttpRequest(HttpResultType.BYTES); + //设置编码集 + request.setCharset("utf-8"); + + request.setParameters(generatNameValuePair(sPara)); + request.setUrl(ALIPAY_GATEWAY_NEW+"_input_charset=utf-8"); + + HttpResponse response = httpProtocolHandler.execute(request,strParaFileName,strFilePath); + if (response == null) { + return null; + } + + String strResult = response.getStringResult(); + + return strResult; + } + + /** + * MAP类型数组转换成NameValuePair类型 + * @param properties MAP类型数组 + * @return NameValuePair类型数组 + */ + private static NameValuePair[] generatNameValuePair(Map properties) { + NameValuePair[] nameValuePair = new NameValuePair[properties.size()]; + int i = 0; + for (Map.Entry entry : properties.entrySet()) { + nameValuePair[i++] = new NameValuePair(entry.getKey(), entry.getValue()); + } + + return nameValuePair; + } + + /** + * 用于防钓鱼,调用接口query_timestamp来获取时间戳的处理函数 + * 注意:远程解析XML出错,与服务器是否支持SSL等配置有关 + * @return 时间戳字符串 + * @throws IOException + * @throws DocumentException + * @throws MalformedURLException + */ + public static String query_timestamp(String partner) throws MalformedURLException, + DocumentException, IOException { + + //构造访问query_timestamp接口的URL串 + String strUrl = ALIPAY_GATEWAY_NEW + "service=query_timestamp&partner=" + partner + "&_input_charset=utf-8"; + StringBuffer result = new StringBuffer(); + + SAXReader reader = new SAXReader(); + Document doc = reader.read(new URL(strUrl).openStream()); + + List nodeList = doc.selectNodes("//alipay/*"); + + for (Node node : nodeList) { + // 截取部分不需要解析的信息 + if (node.getName().equals("is_success") && node.getText().equals("T")) { + // 判断是否有成功标示 + List nodeList1 = doc.selectNodes("//response/timestamp/*"); + for (Node node1 : nodeList1) { + result.append(node1.getText()); + } + } + } + + return result.toString(); + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/Base64.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/Base64.java new file mode 100644 index 0000000..8fb574d --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/Base64.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2010 The MobileSecurePay Project + * All right reserved. + * author: shiqun.shi@alipay.com + */ + +package com.xwbing.util.payAlipay; + +public final class Base64 { + + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static private final boolean fDebug = false; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static { + for (int i = 0; i < BASELENGTH; ++i) { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + + } + + private static boolean isWhiteSpace(char octect) { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) { + return (octect == PAD); + } + + private static boolean isData(char octect) { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) { + + if (binaryData == null) { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + if (fDebug) { + System.out.println("number of triplets = " + numberTriplets); + } + + for (int i = 0; i < numberTriplets; i++) { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + if (fDebug) { + System.out.println("b1= " + b1 + ", b2= " + b2 + ", b3= " + b3); + } + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + if (fDebug) { + System.out.println("val2 = " + val2); + System.out.println("k4 = " + (k << 4)); + System.out.println("vak = " + (val2 | (k << 4))); + } + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + if (fDebug) { + System.out.println("b1=" + b1); + System.out.println("b1<<2 = " + (b1 >> 2)); + } + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } else if (fewerThan24bits == SIXTEENBIT) { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) { + + if (encoded == null) { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) { + return null;//should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) + || !isData((d4 = base64Data[dataIndex++]))) { + return null; + }//if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) { + return null;//if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) {//Check if they are PAD characters + if (isPad(d3) && isPad(d4)) { + if ((b2 & 0xf) != 0)//last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } else if (!isPad(d3) && isPad(d4)) { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)//last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } else { + return null; + } + } else { //No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) { + if (data == null) { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) { + if (!isWhiteSpace(data[i])) { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpProtocolHandler.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpProtocolHandler.java new file mode 100644 index 0000000..146d016 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpProtocolHandler.java @@ -0,0 +1,191 @@ +package com.xwbing.util.payAlipay; + +import org.apache.commons.httpclient.*; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.httpclient.methods.multipart.*; +import org.apache.commons.httpclient.params.HttpMethodParams; +import org.apache.commons.httpclient.util.IdleConnectionTimeoutThread; + +import java.io.File; +import java.io.IOException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +/* * + *类名:HttpProtocolHandler + *功能:HttpClient方式访问 + *详细:获取远程HTTP数据 + *版本:3.3 + *日期:2012-08-17 + *说明: + *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 + */ + +public class HttpProtocolHandler { + + private static String DEFAULT_CHARSET = "GBK"; + + /** 连接超时时间,由bean factory设置,缺省为8秒钟 */ + private int defaultConnectionTimeout = 8000; + + /** 回应超时时间, 由bean factory设置,缺省为30秒钟 */ + private int defaultSoTimeout = 30000; + + /** 闲置连接超时时间, 由bean factory设置,缺省为60秒钟 */ + private int defaultIdleConnTimeout = 60000; + + private int defaultMaxConnPerHost = 30; + + private int defaultMaxTotalConn = 80; + + /** 默认等待HttpConnectionManager返回连接超时(只有在达到最大连接数时起作用):1秒*/ + private static final long defaultHttpConnectionManagerTimeout = 3 * 1000; + + /** + * HTTP连接管理器,该连接管理器必须是线程安全的. + */ + private HttpConnectionManager connectionManager; + + private static HttpProtocolHandler httpProtocolHandler = new HttpProtocolHandler(); + + /** + * 工厂方法 + * + * @return + */ + public static HttpProtocolHandler getInstance() { + return httpProtocolHandler; + } + + /** + * 私有的构造方法 + */ + private HttpProtocolHandler() { + // 创建一个线程安全的HTTP连接池 + connectionManager = new MultiThreadedHttpConnectionManager(); + connectionManager.getParams().setDefaultMaxConnectionsPerHost(defaultMaxConnPerHost); + connectionManager.getParams().setMaxTotalConnections(defaultMaxTotalConn); + + IdleConnectionTimeoutThread ict = new IdleConnectionTimeoutThread(); + ict.addConnectionManager(connectionManager); + ict.setConnectionTimeout(defaultIdleConnTimeout); + + ict.start(); + } + + /** + * 执行Http请求 + * + * @param request 请求数据 + * @param strParaFileName 文件类型的参数名 + * @param strFilePath 文件路径 + * @return + * @throws HttpException, IOException + */ + public HttpResponse execute(HttpRequest request, String strParaFileName, String strFilePath) throws HttpException, IOException { + HttpClient httpclient = new HttpClient(connectionManager); + + // 设置连接超时 + int connectionTimeout = defaultConnectionTimeout; + if (request.getConnectionTimeout() > 0) { + connectionTimeout = request.getConnectionTimeout(); + } + httpclient.getHttpConnectionManager().getParams().setConnectionTimeout(connectionTimeout); + + // 设置回应超时 + int soTimeout = defaultSoTimeout; + if (request.getTimeout() > 0) { + soTimeout = request.getTimeout(); + } + httpclient.getHttpConnectionManager().getParams().setSoTimeout(soTimeout); + + // 设置等待ConnectionManager释放connection的时间 + httpclient.getParams().setConnectionManagerTimeout(defaultHttpConnectionManagerTimeout); + + String charset = request.getCharset(); + charset = charset == null ? DEFAULT_CHARSET : charset; + HttpMethod method = null; + + //get模式且不带上传文件 + if (request.getMethod().equals(HttpRequest.METHOD_GET)) { + method = new GetMethod(request.getUrl()); + method.getParams().setCredentialCharset(charset); + + // parseNotifyConfig会保证使用GET方法时,request一定使用QueryString + method.setQueryString(request.getQueryString()); + } else if(strParaFileName.equals("") && strFilePath.equals("")) { + //post模式且不带上传文件 + method = new PostMethod(request.getUrl()); + ((PostMethod) method).addParameters(request.getParameters()); + method.addRequestHeader("Content-Type", "application/x-www-form-urlencoded; text/html; charset=" + charset); + } + else { + //post模式且带上传文件 + method = new PostMethod(request.getUrl()); + List parts = new ArrayList(); + for (int i = 0; i < request.getParameters().length; i++) { + parts.add(new StringPart(request.getParameters()[i].getName(), request.getParameters()[i].getValue(), charset)); + } + //增加文件参数,strParaFileName是参数名,使用本地文件 + parts.add(new FilePart(strParaFileName, new FilePartSource(new File(strFilePath)))); + + // 设置请求体 + ((PostMethod) method).setRequestEntity(new MultipartRequestEntity(parts.toArray(new Part[0]), new HttpMethodParams())); + } + + // 设置Http Header中的User-Agent属性 + method.addRequestHeader("User-Agent", "Mozilla/4.0"); + HttpResponse response = new HttpResponse(); + + try { + httpclient.executeMethod(method); + if (request.getResultType().equals(HttpResultType.STRING)) { + response.setStringResult(method.getResponseBodyAsString()); + } else if (request.getResultType().equals(HttpResultType.BYTES)) { + response.setByteResult(method.getResponseBody()); + } + response.setResponseHeaders(method.getResponseHeaders()); + } catch (UnknownHostException ex) { + + return null; + } catch (IOException ex) { + + return null; + } catch (Exception ex) { + + return null; + } finally { + method.releaseConnection(); + } + return response; + } + + /** + * 将NameValuePairs数组转变为字符串 + * + * @param nameValues + * @return + */ + protected String toString(NameValuePair[] nameValues) { + if (nameValues == null || nameValues.length == 0) { + return "null"; + } + + StringBuffer buffer = new StringBuffer(); + + for (int i = 0; i < nameValues.length; i++) { + NameValuePair nameValue = nameValues[i]; + + if (i == 0) { + buffer.append(nameValue.getName() + "=" + nameValue.getValue()); + } else { + buffer.append("&" + nameValue.getName() + "=" + nameValue.getValue()); + } + } + + return buffer.toString(); + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpRequest.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpRequest.java new file mode 100644 index 0000000..2ea12fe --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpRequest.java @@ -0,0 +1,152 @@ +package com.xwbing.util.payAlipay; + +import org.apache.commons.httpclient.NameValuePair; + +/* * + *类名:HttpRequest + *功能:Http请求对象的封装 + *详细:封装Http请求 + *版本:3.3 + *日期:2011-08-17 + *说明: + *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 + */ + +public class HttpRequest { + + /** HTTP GET method */ + public static final String METHOD_GET = "GET"; + + /** HTTP POST method */ + public static final String METHOD_POST = "POST"; + + /** + * 待请求的url + */ + private String url = null; + + /** + * 默认的请求方式 + */ + private String method = METHOD_POST; + + private int timeout = 0; + + private int connectionTimeout = 0; + + /** + * Post方式请求时组装好的参数值对 + */ + private NameValuePair[] parameters = null; + + /** + * Get方式请求时对应的参数 + */ + private String queryString = null; + + /** + * 默认的请求编码方式 + */ + private String charset = "GBK"; + + /** + * 请求发起方的ip地址 + */ + private String clientIp; + + /** + * 请求返回的方式 + */ + private HttpResultType resultType = HttpResultType.BYTES; + + public HttpRequest(HttpResultType resultType) { + super(); + this.resultType = resultType; + } + + /** + * @return Returns the clientIp. + */ + public String getClientIp() { + return clientIp; + } + + /** + * @param clientIp The clientIp to set. + */ + public void setClientIp(String clientIp) { + this.clientIp = clientIp; + } + + public NameValuePair[] getParameters() { + return parameters; + } + + public void setParameters(NameValuePair[] parameters) { + this.parameters = parameters; + } + + public String getQueryString() { + return queryString; + } + + public void setQueryString(String queryString) { + this.queryString = queryString; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public void setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + /** + * @return Returns the charset. + */ + public String getCharset() { + return charset; + } + + /** + * @param charset The charset to set. + */ + public void setCharset(String charset) { + this.charset = charset; + } + + public HttpResultType getResultType() { + return resultType; + } + + public void setResultType(HttpResultType resultType) { + this.resultType = resultType; + } + +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResponse.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResponse.java new file mode 100644 index 0000000..ffc09ba --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResponse.java @@ -0,0 +1,72 @@ +package com.xwbing.util.payAlipay; + +import org.apache.commons.httpclient.Header; + +import java.io.UnsupportedEncodingException; + + +/* * + *类名:HttpResponse + *功能:Http返回对象的封装 + *详细:封装Http返回信息 + *版本:3.3 + *日期:2011-08-17 + *说明: + *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 + */ + +public class HttpResponse { + + /** + * 返回中的Header信息 + */ + private Header[] responseHeaders; + + /** + * String类型的result + */ + private String stringResult; + + /** + * btye类型的result + */ + private byte[] byteResult; + + public Header[] getResponseHeaders() { + return responseHeaders; + } + + public void setResponseHeaders(Header[] responseHeaders) { + this.responseHeaders = responseHeaders; + } + + public byte[] getByteResult() { + if (byteResult != null) { + return byteResult; + } + if (stringResult != null) { + return stringResult.getBytes(); + } + return null; + } + + public void setByteResult(byte[] byteResult) { + this.byteResult = byteResult; + } + + public String getStringResult() throws UnsupportedEncodingException { + if (stringResult != null) { + return stringResult; + } + if (byteResult != null) { + return new String(byteResult, "utf-8"); + } + return null; + } + + public void setStringResult(String stringResult) { + this.stringResult = stringResult; + } + +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResultType.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResultType.java new file mode 100644 index 0000000..aff9195 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/HttpResultType.java @@ -0,0 +1,27 @@ +/* + * Alipay.com Inc. + * Copyright (c) 2004-2005 All Rights Reserved. + */ +package com.xwbing.util.payAlipay; + +/* * + *类名:HttpResultType + *功能:表示Http返回的结果字符方式 + *详细:表示Http返回的结果字符方式 + *版本:3.3 + *日期:2012-08-17 + *说明: + *以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + *该代码仅供学习和研究支付宝接口使用,只是提供一个参考。 + */ +public enum HttpResultType { + /** + * 字符串方式 + */ + STRING, + + /** + * 字节数组方式 + */ + BYTES +} diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/MD5.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/MD5.java new file mode 100644 index 0000000..772560d --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/MD5.java @@ -0,0 +1,68 @@ +package com.xwbing.util.payAlipay; + +import org.apache.commons.codec.digest.DigestUtils; + +import java.io.UnsupportedEncodingException; +import java.security.SignatureException; + +/** +* 功能:支付宝MD5签名处理核心文件,不需要修改 +* 版本:3.3 +* 修改日期:2012-08-17 +* 说明: +* 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 +* 该代码仅供学习和研究支付宝接口使用,只是提供一个 +* */ + +public class MD5 { + + /** + * 签名字符串 + * @param text 需要签名的字符串 + * @param key 密钥 + * @param input_charset 编码格式 + * @return 签名结果 + */ + public static String sign(String text, String key, String input_charset) { + text = text + key; + return DigestUtils.md5Hex(getContentBytes(text, input_charset)); + } + + /** + * 签名字符串 + * @param text 需要签名的字符串 + * @param sign 签名结果 + * @param key 密钥 + * @param input_charset 编码格式 + * @return 签名结果 + */ + public static boolean verify(String text, String sign, String key, String input_charset) { + text = text + key; + String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)); + if(mysign.equals(sign)) { + return true; + } + else { + return false; + } + } + + /** + * @param content + * @param charset + * @return + * @throws SignatureException + * @throws UnsupportedEncodingException + */ + private static byte[] getContentBytes(String content, String charset) { + if (charset == null || "".equals(charset)) { + return content.getBytes(); + } + try { + return content.getBytes(charset); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); + } + } + +} \ No newline at end of file diff --git a/boot-service/src/main/java/com/xwbing/util/payAlipay/RSA.java b/boot-service/src/main/java/com/xwbing/util/payAlipay/RSA.java new file mode 100644 index 0000000..1e19d75 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payAlipay/RSA.java @@ -0,0 +1,119 @@ + +package com.xwbing.util.payAlipay; + + +import javax.crypto.Cipher; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +public class RSA { + public static final String SIGN_ALGORITHMS = "SHA1WithRSA"; + + /** + * RSA签名 + * + * @param content 待签名数据 + * @param privateKey 商户私钥 + * @param input_charset 编码格式 + * @return 签名值 + */ + public static String sign(String content, String privateKey, String input_charset) { + try { + PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey)); + KeyFactory keyf = KeyFactory.getInstance("RSA"); + PrivateKey priKey = keyf.generatePrivate(priPKCS8); + java.security.Signature signature = java.security.Signature + .getInstance(SIGN_ALGORITHMS); + signature.initSign(priKey); + signature.update(content.getBytes(input_charset)); + byte[] signed = signature.sign(); + return Base64.encode(signed); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * RSA验签名检查 + * + * @param content 待签名数据 + * @param sign 签名值 + * @param ali_public_key 支付宝公钥 + * @param input_charset 编码格式 + * @return 布尔值 + */ + public static boolean verify(String content, String sign, String ali_public_key, String input_charset) { + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + byte[] encodedKey = Base64.decode(ali_public_key); + PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); + java.security.Signature signature = java.security.Signature + .getInstance(SIGN_ALGORITHMS); + signature.initVerify(pubKey); + signature.update(content.getBytes(input_charset)); + boolean bverify = signature.verify(Base64.decode(sign)); + return bverify; + + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * 解密 + * + * @param content 密文 + * @param private_key 商户私钥 + * @param input_charset 编码格式 + * @return 解密后的字符串 + */ + public static String decrypt(String content, String private_key, String input_charset) throws Exception { + PrivateKey prikey = getPrivateKey(private_key); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, prikey); + InputStream ins = new ByteArrayInputStream(Base64.decode(content)); + ByteArrayOutputStream writer = new ByteArrayOutputStream(); + //rsa解密的字节大小最多是128,将需要解密的内容,按128位拆开解密 + byte[] buf = new byte[128]; + int bufl; + while ((bufl = ins.read(buf)) != -1) { + byte[] block = null; + + if (buf.length == bufl) { + block = buf; + } else { + block = new byte[bufl]; + for (int i = 0; i < bufl; i++) { + block[i] = buf[i]; + } + } + writer.write(cipher.doFinal(block)); + } + return new String(writer.toByteArray(), input_charset); + } + + /** + * 得到私钥 + * + * @param key 密钥字符串(经过base64编码) + * @throws Exception + */ + public static PrivateKey getPrivateKey(String key) throws Exception { + byte[] keyBytes; + keyBytes = Base64.decode(key); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + return privateKey; + } + + +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/ClientCustomSSL.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/ClientCustomSSL.java new file mode 100644 index 0000000..ba8da15 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/ClientCustomSSL.java @@ -0,0 +1,98 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package com.xwbing.util.payWxpay; + +import com.xwbing.exception.BusinessException; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContexts; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import javax.net.ssl.SSLContext; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.*; + +/** + * This example demonstrates how to create secure connections with a custom SSL + * context. + */ +public class ClientCustomSSL { + + public static CloseableHttpClient getCloseableHttpClient(String mch_id) { + FileInputStream instream = null; + KeyStore keyStore = null; + try { + keyStore = KeyStore.getInstance("PKCS12"); + String path = ClientCustomSSL.class.getClassLoader().getResource("wx_apiclient_cert.p12") + .getPath(); + instream = new FileInputStream(new File(path)); + keyStore.load(instream, mch_id.toCharArray()); + } catch (Exception e) { + throw new BusinessException("读取微信证书出错!"); + } finally { + if (instream != null) { + try { + instream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + // Trust own CA and all self-signed certs + SSLContext sslcontext = null; + try { + sslcontext = SSLContexts.custom() + .loadKeyMaterial(keyStore, mch_id.toCharArray()) + .build(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (KeyManagementException e) { + e.printStackTrace(); + } catch (KeyStoreException e) { + e.printStackTrace(); + } catch (UnrecoverableKeyException e) { + e.printStackTrace(); + } + // Allow TLSv1 protocol only + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( + sslcontext, + new String[]{"TLSv1"}, + null, + SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + return HttpClients.custom() + .setSSLSocketFactory(sslsf) + .build(); + } + } + + public static void main(String[] args) { + String path = Thread.currentThread().getContextClassLoader().getResource("wx_apiclient_cert.p12") + .getPath(); + System.out.println(path); + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/IPKit.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/IPKit.java new file mode 100644 index 0000000..b5366de --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/IPKit.java @@ -0,0 +1,68 @@ +package com.xwbing.util.payWxpay; + + +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; + +/** + * Created by drore-wzm on 2015/11/23. + */ +public class IPKit { + public static String getIp2(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) { + // 多次反向代理后会有多个ip值,第一个ip才是真实ip + int index = ip.indexOf(","); + if (index != -1) { + return ip.substring(0, index); + } else { + return ip; + } + } + ip = request.getHeader("X-Real-IP"); + if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) { + return ip; + } + return request.getRemoteAddr(); + } + + public static String getIPAddress(HttpServletRequest request) { + + // 获取X-Forwarded-For + + String ip = request.getHeader("x-forwarded-for"); + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + + // 获取Proxy-Client-IP + + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + // WL-Proxy-Client-IP + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + // 获取的IP实际上是代理服务器的地址,并不是客户端的IP地址 + ip = request.getRemoteAddr(); + } + /* + * + * 如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值 + * + * X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130, + * 192.168.1.100 + * + * 用户真实IP为: 192.168.1.110 + */ + + if (ip.contains(",")) { + + ip = ip.split(",")[0]; + + } + return ip; + + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Signature.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Signature.java new file mode 100644 index 0000000..3ec698e --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Signature.java @@ -0,0 +1,77 @@ +/** + * + */ +package com.xwbing.util.payWxpay; + +import org.apache.commons.codec.digest.DigestUtils; + +import java.io.UnsupportedEncodingException; +import java.security.SignatureException; + + +public class MD5Signature { + + public static String sign(String content, String key) throws Exception { + + String tosign = (content == null ? "" : content) + key; + + System.out.println("signdata:" + tosign); + try { + return DigestUtils.md5Hex(getContentBytes(tosign, "utf-8")); + } catch (UnsupportedEncodingException e) { + throw new SignatureException("MD5签名[content = " + content + "; charset = utf-8" + "]发生异常!", e); + } + + } + + public static boolean verify(String content, String sign, String key) throws Exception { + String tosign = (content == null ? "" : content) + key; + + try { + String mySign = DigestUtils.md5Hex(getContentBytes(tosign, "utf-8")); + + return StringUtil.equals(mySign, sign) ? true : false; + } catch (UnsupportedEncodingException e) { + throw new SignatureException("MD5验证签名[content = " + content + "; charset =utf-8 " + "; signature = " + sign + "]发生异常!", e); + } + } + + public static boolean verify(String content, String sign, String key, String charset) throws Exception { + String tosign = (content == null ? "" : content) + key; + try { + String mySign = DigestUtils.md5Hex(getContentBytes(tosign, charset)); + + return StringUtil.equals(mySign, sign) ? true : false; + } catch (UnsupportedEncodingException e) { + throw new SignatureException("MD5验证签名[content = " + content + "; charset =" + charset + "; signature = " + sign + "]发生异常!", e); + } + } + + /** + * @param content + * @param charset + * @return + * @throws SignatureException + * @throws UnsupportedEncodingException + */ + protected static byte[] getContentBytes(String content, String charset) throws UnsupportedEncodingException { + if (StringUtil.isEmpty(charset)) { + return content.getBytes(); + } + + return content.getBytes(charset); + } + + public static void main(String[] args) { + try { + String str = MD5Signature.sign("123455", "666666666666666"); + System.out.println("qqq:" + str); + System.out.println(MD5Signature.verify("123455", "6edd55a3421c6324db29690dbc334666", "666666666666666")); + + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Util.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Util.java new file mode 100644 index 0000000..58bde04 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/MD5Util.java @@ -0,0 +1,54 @@ +package com.xwbing.util.payWxpay; + +import java.security.MessageDigest; + +/** + * MD5加密工具类 + * Created by drore-wzm on 2015/11/23. + */ +public class MD5Util { + private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; + + /*** + * 默认UTF-8 + * @param origin + * @return + */ + public static String MD5Encode(String origin) { + return MD5Encode(origin, "UTF-8"); + } + + public static String MD5Encode(String origin, String charsetname) { + String resultString = null; + try { + resultString = new String(origin); + MessageDigest md = MessageDigest.getInstance("MD5"); + if (charsetname == null || "".equals(charsetname)) + resultString = byteArrayToHexString(md.digest(resultString + .getBytes())); + else + resultString = byteArrayToHexString(md.digest(resultString + .getBytes(charsetname))); + } catch (Exception exception) { + } + return resultString; + } + + private static String byteToHexString(byte b) { + int n = b; + if (n < 0) + n += 256; + int d1 = n / 16; + int d2 = n % 16; + return hexDigits[d1] + hexDigits[d2]; + } + + private static String byteArrayToHexString(byte b[]) { + StringBuffer resultSb = new StringBuffer(); + for (int i = 0; i < b.length; i++) + resultSb.append(byteToHexString(b[i])); + + return resultSb.toString(); + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/Md5Encrypt.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/Md5Encrypt.java new file mode 100644 index 0000000..b00ea62 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/Md5Encrypt.java @@ -0,0 +1,69 @@ +package com.xwbing.util.payWxpay; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * 功能:支付宝MD5加密处理核心文件,不需要修改 版本:3.1 修改日期:2010-11-01 说明: + * 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 + * 该代码仅供学习和研究支付宝接口使用,只是提供一个 + * */ + +public class Md5Encrypt { + + + /** + * Used building output as Hex + */ + private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + /** + * 对字符串进行MD5加密 + * + * @param text + * 明文 + * + * @return 密文 + */ + public static String md5(String text) { + MessageDigest msgDigest = null; + + try { + msgDigest = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("System doesn't support MD5 algorithm."); + } + + try { + msgDigest.update(text.getBytes("utf-8")); // 注意改接口是按照指定编码形式签名 + + } catch (UnsupportedEncodingException e) { + + throw new IllegalStateException("System doesn't support your EncodingException."); + + } + + byte[] bytes = msgDigest.digest(); + + String md5Str = new String(encodeHex(bytes)); + + return md5Str; + } + + public static char[] encodeHex(byte[] data) { + + int l = data.length; + + char[] out = new char[l << 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; i < l; i++) { + out[j++] = DIGITS[(0xF0 & data[i]) >>> 4]; + out[j++] = DIGITS[0x0F & data[i]]; + } + + return out; + } + +} \ No newline at end of file diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/RandomKit.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/RandomKit.java new file mode 100644 index 0000000..fda3a9a --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/RandomKit.java @@ -0,0 +1,30 @@ +package com.xwbing.util.payWxpay; + +import java.util.Random; + +/** + * Created by drore-wzm on 2015/11/23. + */ +public class RandomKit { + static String[] la_arrs={"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","S","X","Y","Z","0","1","2","3","4","5","6","7","8","9"}; + + /**** + * 生成len个长度的随机字符 + * @param len + * @return + */ + public static synchronized String buildRandom(int len){ + if (len<=0) { + throw new RuntimeException("参数异常!!!"); + } + StringBuffer random=new StringBuffer(); + for (int i = 0; i < len; i++) { + random.append(la_arrs[new Random().nextInt(la_arrs.length)]); + } + return random.toString(); + } + public static void main(String[] args) { + System.out.println(buildRandom(32)); + } + +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/SHA1.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/SHA1.java new file mode 100644 index 0000000..f101ef5 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/SHA1.java @@ -0,0 +1,232 @@ +package com.xwbing.util.payWxpay; + +import java.security.MessageDigest; + +public class SHA1 { + private final int[] abcde = { + 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 + }; + // 摘要数据存储数组 + private int[] digestInt = new int[5]; + // 计算过程中的临时数据存储数组 + private int[] tmpData = new int[80]; + // 计算sha-1摘要 + private int process_input_bytes(byte[] bytedata) { + // 初试化常量 + System.arraycopy(abcde, 0, digestInt, 0, abcde.length); + // 格式化输入字节数组,补10及长度数据 + byte[] newbyte = byteArrayFormatData(bytedata); + // 获取数据摘要计算的数据单元个数 + int MCount = newbyte.length / 64; + // 循环对每个数据单元进行摘要计算 + for (int pos = 0; pos < MCount; pos++) { + // 将每个单元的数据转换成16个整型数据,并保存到tmpData的前16个数组元素中 + for (int j = 0; j < 16; j++) { + tmpData[j] = byteArrayToInt(newbyte, (pos * 64) + (j * 4)); + } + // 摘要计算函数 + encrypt(); + } + return 20; + } + // 格式化输入字节数组格式 + private byte[] byteArrayFormatData(byte[] bytedata) { + // 补0数量 + int zeros = 0; + // 补位后总位数 + int size = 0; + // 原始数据长度 + int n = bytedata.length; + // 模64后的剩余位数 + int m = n % 64; + // 计算添加0的个数以及添加10后的总长度 + if (m < 56) { + zeros = 55 - m; + size = n - m + 64; + } else if (m == 56) { + zeros = 63; + size = n + 8 + 64; + } else { + zeros = 63 - m + 56; + size = (n + 64) - m + 64; + } + // 补位后生成的新数组内容 + byte[] newbyte = new byte[size]; + // 复制数组的前面部分 + System.arraycopy(bytedata, 0, newbyte, 0, n); + // 获得数组Append数据元素的位置 + int l = n; + // 补1操作 + newbyte[l++] = (byte) 0x80; + // 补0操作 + for (int i = 0; i < zeros; i++) { + newbyte[l++] = (byte) 0x00; + } + // 计算数据长度,补数据长度位共8字节,长整型 + long N = (long) n * 8; + byte h8 = (byte) (N & 0xFF); + byte h7 = (byte) ((N >> 8) & 0xFF); + byte h6 = (byte) ((N >> 16) & 0xFF); + byte h5 = (byte) ((N >> 24) & 0xFF); + byte h4 = (byte) ((N >> 32) & 0xFF); + byte h3 = (byte) ((N >> 40) & 0xFF); + byte h2 = (byte) ((N >> 48) & 0xFF); + byte h1 = (byte) (N >> 56); + newbyte[l++] = h1; + newbyte[l++] = h2; + newbyte[l++] = h3; + newbyte[l++] = h4; + newbyte[l++] = h5; + newbyte[l++] = h6; + newbyte[l++] = h7; + newbyte[l++] = h8; + return newbyte; + } + private int f1(int x, int y, int z) { + return (x & y) | (~x & z); + } + private int f2(int x, int y, int z) { + return x ^ y ^ z; + } + private int f3(int x, int y, int z) { + return (x & y) | (x & z) | (y & z); + } + private int f4(int x, int y) { + return (x << y) | x >>> (32 - y); + } + // 单元摘要计算函数 + private void encrypt() { + for (int i = 16; i <= 79; i++) { + tmpData[i] = f4(tmpData[i - 3] ^ tmpData[i - 8] ^ tmpData[i - 14] ^ + tmpData[i - 16], 1); + } + int[] tmpabcde = new int[5]; + for (int i1 = 0; i1 < tmpabcde.length; i1++) { + tmpabcde[i1] = digestInt[i1]; + } + for (int j = 0; j <= 19; j++) { + int tmp = f4(tmpabcde[0], 5) + + f1(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + + tmpData[j] + 0x5a827999; + tmpabcde[4] = tmpabcde[3]; + tmpabcde[3] = tmpabcde[2]; + tmpabcde[2] = f4(tmpabcde[1], 30); + tmpabcde[1] = tmpabcde[0]; + tmpabcde[0] = tmp; + } + for (int k = 20; k <= 39; k++) { + int tmp = f4(tmpabcde[0], 5) + + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + + tmpData[k] + 0x6ed9eba1; + tmpabcde[4] = tmpabcde[3]; + tmpabcde[3] = tmpabcde[2]; + tmpabcde[2] = f4(tmpabcde[1], 30); + tmpabcde[1] = tmpabcde[0]; + tmpabcde[0] = tmp; + } + for (int l = 40; l <= 59; l++) { + int tmp = f4(tmpabcde[0], 5) + + f3(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + + tmpData[l] + 0x8f1bbcdc; + tmpabcde[4] = tmpabcde[3]; + tmpabcde[3] = tmpabcde[2]; + tmpabcde[2] = f4(tmpabcde[1], 30); + tmpabcde[1] = tmpabcde[0]; + tmpabcde[0] = tmp; + } + for (int m = 60; m <= 79; m++) { + int tmp = f4(tmpabcde[0], 5) + + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + + tmpData[m] + 0xca62c1d6; + tmpabcde[4] = tmpabcde[3]; + tmpabcde[3] = tmpabcde[2]; + tmpabcde[2] = f4(tmpabcde[1], 30); + tmpabcde[1] = tmpabcde[0]; + tmpabcde[0] = tmp; + } + for (int i2 = 0; i2 < tmpabcde.length; i2++) { + digestInt[i2] = digestInt[i2] + tmpabcde[i2]; + } + for (int n = 0; n < tmpData.length; n++) { + tmpData[n] = 0; + } + } + // 4字节数组转换为整数 + private int byteArrayToInt(byte[] bytedata, int i) { + return ((bytedata[i] & 0xff) << 24) | ((bytedata[i + 1] & 0xff) << 16) | + ((bytedata[i + 2] & 0xff) << 8) | (bytedata[i + 3] & 0xff); + } + // 整数转换为4字节数组 + private void intToByteArray(int intValue, byte[] byteData, int i) { + byteData[i] = (byte) (intValue >>> 24); + byteData[i + 1] = (byte) (intValue >>> 16); + byteData[i + 2] = (byte) (intValue >>> 8); + byteData[i + 3] = (byte) intValue; + } + // 将字节转换为十六进制字符串 + private static String byteToHexString(byte ib) { + char[] Digit = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', + 'D', 'E', 'F' + }; + char[] ob = new char[2]; + ob[0] = Digit[(ib >>> 4) & 0X0F]; + ob[1] = Digit[ib & 0X0F]; + String s = new String(ob); + return s; + } + // 将字节数组转换为十六进制字符串 + private static String byteArrayToHexString(byte[] bytearray) { + String strDigest = ""; + for (int i = 0; i < bytearray.length; i++) { + strDigest += byteToHexString(bytearray[i]); + } + return strDigest; + } + // 计算sha-1摘要,返回相应的字节数组 + public byte[] getDigestOfBytes(byte[] byteData) { + process_input_bytes(byteData); + byte[] digest = new byte[20]; + for (int i = 0; i < digestInt.length; i++) { + intToByteArray(digestInt[i], digest, i * 4); + } + return digest; + } + // 计算sha-1摘要,返回相应的十六进制字符串 + public String getDigestOfString(byte[] byteData) { + return byteArrayToHexString(getDigestOfBytes(byteData)); + } + public static void main(String[] args) { + String data = "123456"; + System.out.println(data); + String digest = new SHA1().getDigestOfString(data.getBytes()); + System.out.println(digest); + + // System.out.println( ToMD5.convertSHA1(data).toUpperCase()); + } + + private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private static String getFormattedText(byte[] bytes) { + int len = bytes.length; + StringBuilder buf = new StringBuilder(len * 2); + // 把密文转换成十六进制的字符串形式 + for (int j = 0; j < len; j++) { + buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); + buf.append(HEX_DIGITS[bytes[j] & 0x0f]); + } + return buf.toString(); + } + public static String encode(String str) { + if (str == null) { + return null; + } + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA1"); + messageDigest.update(str.getBytes()); + return getFormattedText(messageDigest.digest()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/StringUtil.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/StringUtil.java new file mode 100644 index 0000000..e5c38f3 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/StringUtil.java @@ -0,0 +1,346 @@ +/** + * Alipay.com Inc. + * Copyright (c) 2005-2006 All Rights Reserved. + */ +package com.xwbing.util.payWxpay; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 字符串处理的工具类 + * @author stone.zhangjl + * @version $Id: StringUtil.java, v 0.1 2008-8-21 上午10:47:41 stone.zhangjl Exp $ + */ +public class StringUtil { + + /** 空字符串。 */ + public static final String EMPTY_STRING = ""; + + /** + * 比较两个字符串(大小写敏感)。 + *
+     * StringUtil.equals(null, null)   = true
+     * StringUtil.equals(null, "abc")  = false
+     * StringUtil.equals("abc", null)  = false
+     * StringUtil.equals("abc", "abc") = true
+     * StringUtil.equals("abc", "ABC") = false
+     * 
+ * + * @param str1 要比较的字符串1 + * @param str2 要比较的字符串2 + * + * @return 如果两个字符串相同,或者都是null,则返回true + */ + public static boolean equals(String str1, String str2) { + if (str1 == null) { + return str2 == null; + } + return str1.equals(str2); + } + + /** + * 比较两个字符串(大小写不敏感)。 + *
+     * StringUtil.equalsIgnoreCase(null, null)   = true
+     * StringUtil.equalsIgnoreCase(null, "abc")  = false
+     * StringUtil.equalsIgnoreCase("abc", null)  = false
+     * StringUtil.equalsIgnoreCase("abc", "abc") = true
+     * StringUtil.equalsIgnoreCase("abc", "ABC") = true
+     * 
+ * + * @param str1 要比较的字符串1 + * @param str2 要比较的字符串2 + * + * @return 如果两个字符串相同,或者都是null,则返回true + */ + public static boolean equalsIgnoreCase(String str1, String str2) { + if (str1 == null) { + return str2 == null; + } + + return str1.equalsIgnoreCase(str2); + } + + /** + * 检查字符串是否是空白:null、空字符串""或只有空白字符。 + *
+     * StringUtil.isBlank(null)      = true
+     * StringUtil.isBlank("")        = true
+     * StringUtil.isBlank(" ")       = true
+     * StringUtil.isBlank("bob")     = false
+     * StringUtil.isBlank("  bob  ") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果为空白, 则返回true + */ + public static boolean isBlank(String str) { + int length; + + if ((str == null) || ((length = str.length()) == 0)) { + return true; + } + + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + + return true; + } + + /** + * 检查字符串是否不是空白:null、空字符串""或只有空白字符。 + *
+     * StringUtil.isBlank(null)      = false
+     * StringUtil.isBlank("")        = false
+     * StringUtil.isBlank(" ")       = false
+     * StringUtil.isBlank("bob")     = true
+     * StringUtil.isBlank("  bob  ") = true
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果为空白, 则返回true + */ + public static boolean isNotBlank(String str) { + int length; + + if ((str == null) || ((length = str.length()) == 0)) { + return false; + } + + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否为null或空字符串""。 + *
+     * StringUtil.isEmpty(null)      = true
+     * StringUtil.isEmpty("")        = true
+     * StringUtil.isEmpty(" ")       = false
+     * StringUtil.isEmpty("bob")     = false
+     * StringUtil.isEmpty("  bob  ") = false
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果为空, 则返回true + */ + public static boolean isEmpty(String str) { + return ((str == null) || (str.length() == 0)); + } + + /** + * 检查字符串是否不是null和空字符串""。 + *
+     * StringUtil.isEmpty(null)      = false
+     * StringUtil.isEmpty("")        = false
+     * StringUtil.isEmpty(" ")       = true
+     * StringUtil.isEmpty("bob")     = true
+     * StringUtil.isEmpty("  bob  ") = true
+     * 
+ * + * @param str 要检查的字符串 + * + * @return 如果不为空, 则返回true + */ + public static boolean isNotEmpty(String str) { + return ((str != null) && (str.length() > 0)); + } + + /** + * 在字符串中查找指定字符串,并返回第一个匹配的索引值。如果字符串为null或未找到,则返回-1。 + *
+     * StringUtil.indexOf(null, *)          = -1
+     * StringUtil.indexOf(*, null)          = -1
+     * StringUtil.indexOf("", "")           = 0
+     * StringUtil.indexOf("aabaabaa", "a")  = 0
+     * StringUtil.indexOf("aabaabaa", "b")  = 2
+     * StringUtil.indexOf("aabaabaa", "ab") = 1
+     * StringUtil.indexOf("aabaabaa", "")   = 0
+     * 
+ * + * @param str 要扫描的字符串 + * @param searchStr 要查找的字符串 + * + * @return 第一个匹配的索引值。如果字符串为null或未找到,则返回-1 + */ + public static int indexOf(String str, String searchStr) { + if ((str == null) || (searchStr == null)) { + return -1; + } + + return str.indexOf(searchStr); + } + + /** + * 在字符串中查找指定字符串,并返回第一个匹配的索引值。如果字符串为null或未找到,则返回-1。 + *
+     * StringUtil.indexOf(null, *, *)          = -1
+     * StringUtil.indexOf(*, null, *)          = -1
+     * StringUtil.indexOf("", "", 0)           = 0
+     * StringUtil.indexOf("aabaabaa", "a", 0)  = 0
+     * StringUtil.indexOf("aabaabaa", "b", 0)  = 2
+     * StringUtil.indexOf("aabaabaa", "ab", 0) = 1
+     * StringUtil.indexOf("aabaabaa", "b", 3)  = 5
+     * StringUtil.indexOf("aabaabaa", "b", 9)  = -1
+     * StringUtil.indexOf("aabaabaa", "b", -1) = 2
+     * StringUtil.indexOf("aabaabaa", "", 2)   = 2
+     * StringUtil.indexOf("abc", "", 9)        = 3
+     * 
+ * + * @param str 要扫描的字符串 + * @param searchStr 要查找的字符串 + * @param startPos 开始搜索的索引值,如果小于0,则看作0 + * + * @return 第一个匹配的索引值。如果字符串为null或未找到,则返回-1 + */ + public static int indexOf(String str, String searchStr, int startPos) { + if ((str == null) || (searchStr == null)) { + return -1; + } + + // JDK1.3及以下版本的bug:不能正确处理下面的情况 + if ((searchStr.length() == 0) && (startPos >= str.length())) { + return str.length(); + } + + return str.indexOf(searchStr, startPos); + } + + /** + * 取指定字符串的子串。 + * + *

+ * 负的索引代表从尾部开始计算。如果字符串为null,则返回null。 + *

+     * StringUtil.substring(null, *, *)    = null
+     * StringUtil.substring("", * ,  *)    = "";
+     * StringUtil.substring("abc", 0, 2)   = "ab"
+     * StringUtil.substring("abc", 2, 0)   = ""
+     * StringUtil.substring("abc", 2, 4)   = "c"
+     * StringUtil.substring("abc", 4, 6)   = ""
+     * StringUtil.substring("abc", 2, 2)   = ""
+     * StringUtil.substring("abc", -2, -1) = "b"
+     * StringUtil.substring("abc", -4, 2)  = "ab"
+     * 
+ *

+ * + * @param str 字符串 + * @param start 起始索引,如果为负数,表示从尾部计算 + * @param end 结束索引(不含),如果为负数,表示从尾部计算 + * + * @return 子串,如果原始串为null,则返回null + */ + public static String substring(String str, int start, int end) { + if (str == null) { + return null; + } + + if (end < 0) { + end = str.length() + end; + } + + if (start < 0) { + start = str.length() + start; + } + + if (end > str.length()) { + end = str.length(); + } + + if (start > end) { + return EMPTY_STRING; + } + + if (start < 0) { + start = 0; + } + + if (end < 0) { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 检查字符串中是否包含指定的字符串。如果字符串为null,将返回false。 + *
+     * StringUtil.contains(null, *)     = false
+     * StringUtil.contains(*, null)     = false
+     * StringUtil.contains("", "")      = true
+     * StringUtil.contains("abc", "")   = true
+     * StringUtil.contains("abc", "a")  = true
+     * StringUtil.contains("abc", "z")  = false
+     * 
+ * + * @param str 要扫描的字符串 + * @param searchStr 要查找的字符串 + * + * @return 如果找到,则返回true + */ + public static boolean contains(String str, String searchStr) { + if ((str == null) || (searchStr == null)) { + return false; + } + + return str.indexOf(searchStr) >= 0; + } + + /** + *

Checks if the String contains only unicode digits. + * A decimal point is not a unicode digit and returns false.

+ * + *

null will return false. + * An empty String ("") will return true.

+ * + *
+     * StringUtils.isNumeric(null)   = false
+     * StringUtils.isNumeric("")     = true
+     * StringUtils.isNumeric("  ")   = false
+     * StringUtils.isNumeric("123")  = true
+     * StringUtils.isNumeric("12 3") = false
+     * StringUtils.isNumeric("ab2c") = false
+     * StringUtils.isNumeric("12-3") = false
+     * StringUtils.isNumeric("12.3") = false
+     * 
+ * + * @param str the String to check, may be null + * @return true if only contains digits, and is non-null + */ + public static boolean isNumeric(String str) { + if (str == null) { + return false; + } + int sz = str.length(); + for (int i = 0; i < sz; i++) { + if (Character.isDigit(str.charAt(i)) == false) { + return false; + } + } + return true; + } + + //java去除字符串中的空格、回车、换行符、制表符 + public static String replaceBlank(String str) { + String dest = ""; + if (str!=null) { + Pattern p = Pattern.compile("\\s*|\t|\r|\n"); + Matcher m = p.matcher(str); + dest = m.replaceAll(""); + } + return dest; + } + +} diff --git a/boot-service/src/main/java/com/xwbing/util/payWxpay/WxSignKit.java b/boot-service/src/main/java/com/xwbing/util/payWxpay/WxSignKit.java new file mode 100644 index 0000000..f459646 --- /dev/null +++ b/boot-service/src/main/java/com/xwbing/util/payWxpay/WxSignKit.java @@ -0,0 +1,94 @@ +package com.xwbing.util.payWxpay; + +import java.util.*; +import java.util.logging.Logger; + +/** + * Created by drore-wzm on 2015/11/23. + */ +public class WxSignKit { + private static Logger logger = Logger.getLogger(WxSignKit.class + .getName()); + + /*** + * 构建jsapi的签名 + * @param params + * @return + */ + public static String buildJsApiSign(Map params){ + StringBuffer sb = new StringBuffer(); + Map result = paramsFilter(params); + String prestr = createLinkString(result); // 把数组所有元素,按照“参数参数值”的模式拼接成字符串 + String encodeKey= SHA1.encode(prestr); + return encodeKey; + } + + + /*** + * 构建签名参数 + * + * @param params + * 请求参数 + * @param key + * 秘钥 + * @return 签名sign + */ + public static String buildSign(Map params, String key) { + Map result = paramsFilter(params); + String prestr = createLinkString(result); // 把数组所有元素,按照“参数参数值”的模式拼接成字符串 + System.out.println(prestr+"==========="); + prestr = prestr +"&key="+ key; // 把接口密钥+拼接后的字符串直接连接起来 + System.out.println("==========="+prestr+"==========="); + String mysign = Md5Encrypt.md5(prestr); + if (mysign != null) { + mysign = mysign.toUpperCase(); + } + return mysign; + } + + /*** + * 除去数组中的空值和签名参数 + * + * @param sArray + * @return + */ + private static Map paramsFilter(Map sArray) { + Map result = new HashMap(); + if (sArray == null || sArray.size() <= 0) { + return result; + } + for (String key : sArray.keySet()) { + String value = sArray.get(key); + if (value == null || value.equals("") + || key.equalsIgnoreCase("sign")) { + continue; + } + result.put(key, value); + } + return result; + } + + /** + * 把数组所有元素排序,并按照“参数参数值”的模式拼接成字符串 + * + * @param params + * 需要排序并参与字符拼接的参数组 + * @return 拼接后字符串 + */ + private static String createLinkString(Map params) { + List keys = new ArrayList(params.keySet()); + Collections.sort(keys); + String prestr = ""; + StringBuffer sb=new StringBuffer(); + for (int i = 0; i < keys.size(); i++) { + String key = keys.get(i); + String value = params.get(key); + sb.append(key).append("=").append(value); + prestr = prestr + key +"="+ value+"&"; + if (i doXMLParse(String strxml) + throws JDOMException, IOException { + if (null == strxml || "".equals(strxml)) { + return null; + } + strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\""); + Map m = new HashMap(); + InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8")); + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(in); + Element root = doc.getRootElement(); + List list = root.getChildren(); + Iterator it = list.iterator(); + while (it.hasNext()) { + Element e = (Element) it.next(); + String k = e.getName(); + String v = ""; + List children = e.getChildren(); + if (children.isEmpty()) { + v = e.getTextNormalize(); + } else { + v = XmlUtil.getChildrenText(children); + } + m.put(k, v); + } + return m; + } + /** + * 获取子结点的xml + * @param children + * @return String + */ + public static String getChildrenText(List children) { + StringBuffer sb = new StringBuffer(); + if(!children.isEmpty()) { + Iterator it = children.iterator(); + while(it.hasNext()) { + Element e = (Element) it.next(); + String name = e.getName(); + String value = e.getTextNormalize(); + List list = e.getChildren(); + sb.append("<" + name + ">"); + if(!list.isEmpty()) { + sb.append(XmlUtil.getChildrenText(list)); + } + sb.append(value); + sb.append(""); + } + } + + return sb.toString(); + } + + /*** + * 告诉微信服务器,我收到信息了,不要在调用回调action了 + * @param return_code + * @param return_msg + * @return + */ + public static String setXML(String return_code, String return_msg){ + return ""; + } + + + public static void main(String[] args) { + String ff=""; + try { + Map sf= XmlUtil.doXMLParse(ff); + for (String s : sf.keySet()) { + System.out.println(s+",value:"+sf.get(s)); + } + } catch (JDOMException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + } +} diff --git a/boot-service/src/main/resources/pay.properties b/boot-service/src/main/resources/pay.properties new file mode 100644 index 0000000..f61ee01 --- /dev/null +++ b/boot-service/src/main/resources/pay.properties @@ -0,0 +1,21 @@ +###payConfig### + +###wx pay set start## +wx.apiKey=FJDKLAJxfsldkjf354fsalfjdlksfafd +wx.appId=wx024418cfeab498fc +wx.mchId=1245634802 +wx.barCodePayUrl=https://api.mch.weixin.qq.com/pay/micropay +wx.refundUrl=https://api.mch.weixin.qq.com/secapi/pay/refund +wx.orderQueryUrl=https://api.mch.weixin.qq.com/secapi/pay/orderquery +wx.refundQueryUrl=https://api.mch.weixin.qq.com/pay/refundquery +###wx pay set end### + + +###payAlipay pay set start## +alipay.appId=2017051807273744 +alipay.requestUrl=https://openapi.alipay.com/gateway.do +alipay.rsaPublicKey=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsa1COluDzLH7GhYCWZp4HclR9x2nJ51UNVx7nmDwjr4Gg4miAOiEHYjZ4ILOUavo2EPN0/pcGRhMa8U87xICWcHQr26Q8cYkZgIfb5DC7l2b8MROxUYzdk82Y+3c8mbZ/6I5LfJeztDWtnXLQUZihTqaI2hPeGL9WEeMxqxaXMge1ho1BF4LfylUlO0WOle9grWYv0Xqpz0zKz6kCiVj2gdvNpL6ULj/C8BHZtY+aHd0Pd1dOdFPft8c+uTs7ESpAwClvoS9Go2LXJGPZUNGd0FnzRSyjpbi905rOjDQbUlUdcSbcg/qiFHaH1+GF3yRD4RAIO8pgzTj8RLsrbj/MQIDAQAB +alipay.rsaPrivateKey=MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCxrUI6W4PMsfsaFgJZmngdyVH3HacnnVQ1XHueYPCOvgaDiaIA6IQdiNnggs5Rq+jYQ83T+lwZGExrxTzvEgJZwdCvbpDxxiRmAh9vkMLuXZvwxE7FRjN2TzZj7dzyZtn/ojkt8l7O0Na2dctBRmKFOpojaE94Yv1YR4zGrFpcyB7WGjUEXgt/KVSU7RY6V72CtZi/ReqnPTMrPqQKJWPaB282kvpQuP8LwEdm1j5od3Q93V050U9+3xz65OzsRKkDAKW+hL0ajYtckY9lQ0Z3QWfNFLKOluL3Tms6MNBtSVR1xJtyD+qIUdofX4YXfJEPhEAg7ymDNOPxEuytuP8xAgMBAAECggEAJ3k2nYB3Z6KjBsic/Qs0ZGm1rBEKceKiMri5PhaLWysch1ysgUcnEui0fwyQidSHhoSJ1e5pR6n+UHMhdnupygclNdR6nzxGGeOe2EpxBEQHwRXb6jgOdtpiWxEdZT00cXa4/Vw7c/cxvJeu8HGSscazN+qUvUpAvrwmqnzgFnx/9jnji+tpPPahBD9uqJuKEg4cfvuR7sOtkcV4OyTfV1ASpec4Fsu0SXUSugP7ApSpKRZqIIpYIT/G8jqTxZ73tf2tnj6f9PoxzpbgN7kocxAzELxRpZi5G+CTqtc1AX6DKh173BhNqnAlfW7rGbF2IfUEsqbtsOksSor3+BRlgQKBgQDvf0QoG1CqO7xTokHFO1MWjYsfFxDQ7PeJtMdr/Jp2dYNpUT9B4Yl09DcLSEwDwn15VR4/NYlpiJvlOIrpYDWI0/ZvkSPy6hQmOP6WYTH1cdUwxSetX+MNBsi5PHUUa9ObvXBYZWOVLAuHieSJDcmJuDiGFdJvo2X+0ul68piVgwKBgQC963sDPk9mLgxD/en/1cQu4HxdS4MCelBqogRD24TV1DWkLFPBeK8QMLCvwd5BSN2kTXdTEEyHPCwBbe3hyQ6cKHmM5SUujomXnzOgbNWshCU3A7eHHjQjyK+eqSWDTsK4kX7q1A4Pi5MftTEiBUI5EwVHsXLNtcVhHTC24bsuOwKBgBb6jv+KH2M1+AvONaFG3+H6Eq8Y93QUSGvd/IRP6EIVZFZmhTs8+INQKzfZ5/E0Z/WMRSqraFPH161Wj310za4jKDHsKxbTfrtsHIExMinqkhnr0LLB0Hcff4LGWjVrYxI+StkwYS2e3DxU7qh3ui6Mjl/PGDG967tXYS96t0khAoGAXEuY322jlmugaFZ4Jm1TRtivd4Nf6o9Yj0mnOvbuaiAT5XHpTieBJCQVaYSUpzNrCxLef053cm0qkZNDvKtB31/IGlGtrR91/GpQ45CgkpMxpre2tAJYol9AAaH/6f0XoiyK+kINhz50RD+A3iCiGyyEL2AsA45nmpdd+jJfDcsCgYEA3ZPASHIfbnflQkWzzvUbzKLFqZB/NulDJTK4QvHne2canOac2oyOivBXQBgio9sqTolQo5W+6SxH9YqpH8GjPcPMsEF9PVsn4o/Gm3hY68sf2gSgpVfQzLM6KYDJ6asO93ZmJ6z0fu6qwUGdACpcSlZzCy0QQ6+QhHVobR2Uma8= +###payAlipay end set start## + +