diff --git a/payment-base-app/payment-base-app.iml b/payment-base-app/payment-base-app.iml
new file mode 100644
index 0000000..e3fda62
--- /dev/null
+++ b/payment-base-app/payment-base-app.iml
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/payment-base-app/pom.xml b/payment-base-app/pom.xml
index a1bbbe9..8ea2679 100644
--- a/payment-base-app/pom.xml
+++ b/payment-base-app/pom.xml
@@ -214,6 +214,8 @@
jar-with-dependencies
+ rbs-pay
+ ../target
diff --git a/payment-base-app/src/main/java/com/base/springcloud/aspect/aspect/WebMvcConfiguration.java b/payment-base-app/src/main/java/com/base/springcloud/aspect/aspect/WebMvcConfiguration.java
new file mode 100644
index 0000000..182c36c
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/aspect/aspect/WebMvcConfiguration.java
@@ -0,0 +1,32 @@
+package com.base.springcloud.aspect.aspect;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * Spring Boot 2.0 解决跨域问题
+ *
+ * @Author qinfeng
+ *
+ */
+@Configuration
+public class WebMvcConfiguration implements WebMvcConfigurer {
+
+
+ @Override
+ public void addCorsMappings(CorsRegistry registry){
+ // 设置允许跨域的路由
+ registry.addMapping("/**")
+ // 设置允许跨域请求的域名------------修改此行
+ .allowedOrigins("*")
+ // 是否允许证书(cookies)
+ .allowCredentials(true)
+ // 设置允许的方法
+ .allowedMethods("*")
+ // 跨域允许时间
+ .maxAge(3600);
+ }
+
+
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/base/MchConfigEntity.java b/payment-base-app/src/main/java/com/base/springcloud/base/MchConfigEntity.java
new file mode 100644
index 0000000..630f835
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/base/MchConfigEntity.java
@@ -0,0 +1,22 @@
+package com.base.springcloud.base;
+
+import lombok.Data;
+
+@Data
+public class MchConfigEntity {
+
+ // 商户编号
+ private String mchId;
+
+ // 商户私钥
+ private String mchPrivateKey;
+
+ // 微信平台证书
+ private String certificate;
+
+ // 证书序列号
+ private String serialNo;
+
+ // 公众号Id
+ private String appId;
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/bo/CodePayBO.java b/payment-base-app/src/main/java/com/base/springcloud/bo/CodePayBO.java
new file mode 100644
index 0000000..a9d8bc3
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/bo/CodePayBO.java
@@ -0,0 +1,32 @@
+package com.base.springcloud.bo;
+
+import com.base.springcloud.base.MchConfigEntity;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class CodePayBO extends MchConfigEntity implements Serializable {
+
+ // 商品描述
+ private String body;
+
+ // 附加数据
+ private String attach;
+
+ // 订单号
+ private String orderNo;
+
+ // 订单金额
+ private BigDecimal amount;
+
+ // 微信付款码
+ private String authCode;
+
+ // 设备号
+ private String deviceInfo;
+
+ // 终端IP
+ private String spbillCreateIp;
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/bo/OrderBO.java b/payment-base-app/src/main/java/com/base/springcloud/bo/OrderBO.java
index 7b15216..e43578e 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/bo/OrderBO.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/bo/OrderBO.java
@@ -1,5 +1,6 @@
package com.base.springcloud.bo;
+import com.base.springcloud.base.MchConfigEntity;
import com.base.springcloud.entity.Order;
import com.base.springcloud.entity.SaleBill;
import lombok.Data;
@@ -7,13 +8,7 @@ import lombok.Data;
import java.io.Serializable;
@Data
-public class OrderBO implements Serializable {
-
- // 商户编号
- private String mchId;
-
- // 商户私钥
- private String mchPrivateKey;
+public class OrderBO extends MchConfigEntity implements Serializable {
// 销售单据号
private String billNo;
diff --git a/payment-base-app/src/main/java/com/base/springcloud/bo/RefundBO.java b/payment-base-app/src/main/java/com/base/springcloud/bo/RefundBO.java
new file mode 100644
index 0000000..3bdefa1
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/bo/RefundBO.java
@@ -0,0 +1,32 @@
+package com.base.springcloud.bo;
+
+import com.base.springcloud.base.MchConfigEntity;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+public class RefundBO extends MchConfigEntity implements Serializable {
+
+ // 销售退款单据号
+ private String billRefundNo;
+
+ // 原交易订单号即支付订单号
+ private String origOrderNo;
+
+ // 原订单金额
+ private BigDecimal orderAmount;
+
+ // 退款订单号
+ private String refundNo;
+
+ // 退款金额
+ private BigDecimal refundAmount;
+
+ // 退款通知地址
+ private String notifyUrl;
+
+ // 退款原因
+ private String reason;
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/constant/PaymentConstant.java b/payment-base-app/src/main/java/com/base/springcloud/constant/PaymentConstant.java
index b756fb1..00f8b30 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/constant/PaymentConstant.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/constant/PaymentConstant.java
@@ -21,6 +21,9 @@ public interface PaymentConstant {
String CP = "CP"; // 云闪付
String QRCCP = "QRCCP";
+ String HB = "HB"; // 和包
+ String QRCHB = "QRCHB";
+
String NATIVE = "Native"; // Native支付
String H5 = "H5"; // H5支付
String JSAPI = "Jsapi"; // JSAPI支付
diff --git a/payment-base-app/src/main/java/com/base/springcloud/controller/PaymentController.java b/payment-base-app/src/main/java/com/base/springcloud/controller/PaymentController.java
index a03ddea..29ccbfc 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/controller/PaymentController.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/controller/PaymentController.java
@@ -33,7 +33,8 @@ public class PaymentController {
@ApiOperation(value = "native下单", notes = "微信native下单")
@GetMapping("/nativePlaceOrder")
- public ReportResult nativePlaceOrder(@RequestParam String salesBill,@RequestParam String mchId) {
+ public ReportResult nativePlaceOrder(@RequestParam String salesBill) {
+ String mchId = "888027018000004";
OrderBO orderBO = new OrderBO();
orderBO.setBillNo(salesBill);
orderBO.setMchId(mchId);
diff --git a/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayRefundVo.java b/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayRefundVo.java
index 068d10e..d59723f 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayRefundVo.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayRefundVo.java
@@ -3,6 +3,7 @@ package com.base.springcloud.entity;
import lombok.Data;
import java.io.Serializable;
+import java.math.BigDecimal;
/**
* 微信退款预下单-主体信息
@@ -36,10 +37,10 @@ public class WxpayRefundVo implements Serializable {
public static class WxRefundAmountVo {
// 退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额。
// 示例值:888
- private long refund;
+ private BigDecimal refund;
// 原支付交易的订单总金额,币种的最小单位,只能为整数。
// 示例值:888
- private long total;
+ private BigDecimal total;
// CNY:人民币,境内商户号仅支持人民币。
// 示例值:CNY
private String currency = "CNY";
@@ -48,7 +49,7 @@ public class WxpayRefundVo implements Serializable {
super();
}
- public WxRefundAmountVo(long refund, long total) {
+ public WxRefundAmountVo(BigDecimal refund, BigDecimal total) {
super();
this.refund = refund;
this.total = total;
diff --git a/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayTradeVo.java b/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayTradeVo.java
index e57629f..751f797 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayTradeVo.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/entity/WxpayTradeVo.java
@@ -3,6 +3,7 @@ package com.base.springcloud.entity;
import lombok.Data;
import java.io.Serializable;
+import java.math.BigDecimal;
/**
* 微信支付预下单-主体信息
@@ -63,7 +64,7 @@ public class WxpayTradeVo implements Serializable {
public static class WxTradeAmountVo {
// 订单总金额,单位为分。
// 示例值:100
- private long total;
+ private BigDecimal total;
// CNY:人民币,境内商户号仅支持人民币。
// 示例值:CNY
private String currency = "CNY";
@@ -71,7 +72,7 @@ public class WxpayTradeVo implements Serializable {
public WxTradeAmountVo() {
super();
}
- public WxTradeAmountVo(long total) {
+ public WxTradeAmountVo(BigDecimal total) {
super();
this.total = total;
}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/service/WecPaymentService.java b/payment-base-app/src/main/java/com/base/springcloud/service/WecPaymentService.java
index 881ddcf..e564616 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/service/WecPaymentService.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/service/WecPaymentService.java
@@ -1,6 +1,8 @@
package com.base.springcloud.service;
+import com.base.springcloud.bo.CodePayBO;
import com.base.springcloud.bo.OrderBO;
+import com.base.springcloud.bo.RefundBO;
import com.base.springcloud.entity.Order;
public interface WecPaymentService {
@@ -12,6 +14,23 @@ public interface WecPaymentService {
*/
String nativePay(OrderBO orderBO);
+ /**
+ * 微信Native支付
+ * @param orderBO
+ */
+ String wecNativePay(OrderBO orderBO);
+
+ /**
+ * 微信APIv3退款
+ * @param refundBO
+ */
+ void wecRefund(RefundBO refundBO);
+
+ /**
+ * 微信付款码支付
+ */
+ void codePay(CodePayBO codePayBO);
+
/**
* 查询微信订单结果
* @param orderBO
diff --git a/payment-base-app/src/main/java/com/base/springcloud/service/imp/MerchantServiceImp.java b/payment-base-app/src/main/java/com/base/springcloud/service/imp/MerchantServiceImp.java
index a0edac2..4e999b2 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/service/imp/MerchantServiceImp.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/service/imp/MerchantServiceImp.java
@@ -2,20 +2,15 @@ package com.base.springcloud.service.imp;
import com.base.springcloud.bo.MerchantConfigBO;
import com.base.springcloud.bo.OrderBO;
-import com.base.springcloud.constant.PaymentConstant;
import com.base.springcloud.dao.MerchantConfigMapper;
import com.base.springcloud.entity.MerchantConfig;
-import com.base.springcloud.entity.WxpayTradeVo;
import com.base.springcloud.service.MerchantService;
+import com.base.springcloud.util.DateUtil;
import com.base.springcloud.util.FtpUtil;
-import com.base.springcloud.util.WxpayUtils;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
+import com.base.springcloud.wxsdk.MyConfig;
+import com.base.springcloud.wxsdk.WXPay;
+import com.base.springcloud.wxsdk.WXPayUtil;
import org.apache.commons.lang.StringUtils;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -23,10 +18,7 @@ import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
+import java.util.*;
import java.util.stream.Collectors;
@Service
@@ -192,22 +184,29 @@ public class MerchantServiceImp implements MerchantService {
return null;
}
- /*public static void main(String[] args) {
- WxpayTradeVo wxpayTradeVo = new WxpayTradeVo();
- wxpayTradeVo.setAppid("wxa91984b310053c3d");
- wxpayTradeVo.setMchid("1610494969");
- wxpayTradeVo.setDescription("Image形象店-深圳腾大-QQ公仔");
- wxpayTradeVo.setOut_trade_no("20220301095644");
- wxpayTradeVo.setNotify_url("https://www.weixin.qq.com/wxpay/pay.php");
- WxpayTradeVo.WxTradeAmountVo wxTradeAmountVo = new WxpayTradeVo.WxTradeAmountVo();
- wxTradeAmountVo.setTotal(1l);
- wxTradeAmountVo.setCurrency("CNY");
- wxpayTradeVo.setAmount(wxTradeAmountVo);
- ObjectNode objectNode = WxpayUtils.getWxpay(wxpayTradeVo,
- PaymentConstant.NATIVE,
- "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVVy4CUrRTPJZ49vuK9Tr5Rurd3dE3eiz7lsnWPAYgZtdWsC6TORUaP0Px/ZMtaJYNURMpVKC5koXZSPTNr9ROHa6B6IX4lTMERzHgiVzGIqLlE/twBYXbSuh8yXy3k59adFDYZZNN+2THWdst2Ao7pD+GXlDby8gfF67ax9CbWq5yyx6MYMKHwOVe3H/ON2pcmZR6I809Lwa4zfmUyOez1U5pwNiwv2cyorM5UXzVMsvK5jfIKDaskNVIV/+ax15tf+OQ/6t9iaWzRAV1zeeJd/TuVVGB/QU5slg5xyIwaqKzJM1H3n64er5QNRKpU31Ru0dHzJTW7ljtILWCQZubAgMBAAECggEAVga6QixSJACqzVO44vsQ1be8Y8H7WvHOJ79c1UQOY2vab91ddwFS3qPDCAsCVQ4CIfmqCAzv6PY2lEwqeLHooncGp17Cjcekc7pvSODXyGr7Wup/1K/Zw9EKy5dleHpZOUn3qDAbArjl/NAOKgJhIqoizcxW18YzqXWDDBQ+Xr6kqmjWXWZ3Kj6ghZNoSkdoynTQ7Ti0C2lq0UeXg611ea6Zs3fgjOgYEBMSSI7v4zE/6RN7jFGlZe5Z1NgO15rl8r3YdDnXTCIpEiTV+yHsDkluqJquB0E1Xrr1AXl9AGMuq6lYEB4houJUPtGgQ6bLs93MrnyzgnGTLbLSnXDNwQKBgQDD6Z+0xZ8lApXZylI2N4zuSrCsi6wB6Qp/64jr+C7GAdmC7w9WRoIlIF7Q2s9p5ulGyxD2hAbrajY5CsjV3S0uIDAaULoG3dw5vxYFEdvcKgxphf8j0gcajUnPGzCxF5vm29B9D+o1mrylr04dtWHlP0KpKwQ6HiTX7if+DcXMhwKBgQDDJOFpoCcVdZ0clfpvJPZpgRTu3I4HFTs27elCQ98g1UWav/VY7729YLAKU27d6lG+iqH1X5lVu2lr/du8wdJoMbpBPD34V+V6ye8gV9lCG7wSCgsNnPqM/HoyBSe6+JUgwG+vCRh1KMhNBmqEMCqPRAtbW3wfDdCShfTBHAjxTQKBgCjYrNOj4Gp2/4dJB5sGOfv01qEi/dnoHKhPE10fgaEHAbKq5Se3y6qx9RLWjCgoXLrF2+OJ42dx3ZxHA1iTpLfZPi4+ZorH9wKTNKQZ9R0SJ0RZC7kMMS74BkuH/77sjTS03+MzTkGe4XYp9r968c46QkvDDD6Ryly66tutQ3BBAoGBAJiE/MDncWSuVit/dQrlYso3HztJmqAikfNuoz8tePlad1pp6cKnLi8ToMORTjeX6mf4RnlXTKrbHZxP/yxaeK8DHsXZ5b1yRWNg7MrknmOy9Z1LyDPNm24R7xelO9ZAb533idLOUxVO2K6K8zl5w+nRjubKbAqBa6D5ncBIvxyJAoGAP9cSun39cFuzjdTaWojmbQMM8k5pyvFWr2UdY50GFrFCdajkAxqoTLkFEUP5fx8InIU/X9aSbVE6Va5fdOKiifbx5KaSKFrH/xZS6XykAbIkWDWqOPMovHoMWj3lGO96wDInry9YZNy7JA8AuZsLSBUA7Xb33FqZ2WU5ePeFOAA=",
- "MIID9jCCAt6gAwIBAgIUHPQVmPutv7O6ub4lOfSWhLWhflowDQYJKoZIhvcNAQELBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsTFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3QgQ0EwHhcNMjEwNjA5MDMzMjQ5WhcNMjYwNjA4MDMzMjQ5WjCBhzETMBEGA1UEAwwKMTYxMDQ5NDk2OTEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTMwMQYDVQQLDCrotLXlt57pgJrnqIvlhYTlvJ/lt6XnqIvmnLrmorDmnInpmZDlhazlj7gxCzAJBgNVBAYMAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJVXLgJStFM8lnj2+4r1OvlG6t3d0Td6LPuWydY8BiBm11awLpM5FRo/Q/H9ky1olg1REylUoLmShdlI9M2v1E4droHohfiVMwRHMeCJXMYiouUT+3AFhdtK6HzJfLeTn1p0UNhlk037ZMdZ2y3YCjukP4ZeUNvLyB8XrtrH0JtarnLLHoxgwofA5V7cf843alyZlHojzT0vBrjN+ZTI57PVTmnA2LC/ZzKiszlRfNUyy8rmN8goNqyQ1UhX/5rHXm1/45D/q32JpbNEBXXN54l39O5VUYH9BTmyWDnHIjBqorMkzUfefrh6vlA1EqlTfVG7R0fMlNbuWO0gtYJBm5sCAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMGUGA1UdHwReMFwwWqBYoFaGVGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMvaXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4RUJEMjANBgkqhkiG9w0BAQsFAAOCAQEACoG5Uf3X33UlDY6VWxgvN6Kg1W9ZRwcwcxSPuURv7OVO1o1JH0c1lPRqY3H8A+TeVI4/5wt5GPRRkSOtecPhMikgvhUjxJZnM0b30sxEv7hoqgEqNcsu2Kc60W6hGzVW0Fw8qimjOmoREBAaOOpYNr/zIrZZiW4vJd//ESNXvpTlwwK1xE9VW3VJGHW7mMZ+Whr3S13esH7rds5mLE6OQvrL6B2weEcVlzP1oEZAbaZW5bgz2gQBR5WheU8QGySnU/e38nzLsdwY0r7ztaWQ39z7fsOeY2gupB8cZ251pWZ9oYvUu4u0VFflrbJiMdLLEZDej02MYtgO6o9xb8BNmw==",
- "1CF41598FBADBFB3BAB9BE2539F49684B5A17E5A");
- System.out.println(objectNode);
- }*/
+ public static void main(String[] args) throws Exception {
+
+ MyConfig config = new MyConfig();
+ WXPay wxPay = new WXPay(config);
+
+ Map data = new HashMap();
+ //公众账号ID (微信分配的公众账号ID(企业号corpid即为此appId))
+ data.put("appid", "wxa91984b310053c3d");
+ //商户号 (微信支付分配的商户号)
+ data.put("mch_id", "1610494969");
+ data.put("device_info", "sb0001");
+ //商品描述 (商品简单描述,该字段须严格按照规范传递) 店名-销售商品类目 例 小张南山店-超市
+ data.put("body", "Image形象店-深圳腾大-QQ公仔");
+ //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
+ data.put("out_trade_no", DateUtil.getDateString(25));
+ //订单总金额,单位为分,只能为整数
+ data.put("total_fee", "1");
+ data.put("auth_code", "135080797699902868");
+ data.put("spbill_create_ip", "8.8.8.8");
+ //付款码 (扫码支付付款码,设备读取用户微信中的条码或者二维码信息(注:用户付款码条形码规则:18位纯数字,以10、11、12、13、14、15开头))
+ data.put("sign", WXPayUtil.generateSignature(data,"yDXt3Jczcs2dSKvwUxGkYYfHzCe8SXtY"));
+ Map responseMap = wxPay.microPay(data);
+ //判断是否成功
+ System.out.println("微信返回响应:" + responseMap.toString());
+ }
}
\ No newline at end of file
diff --git a/payment-base-app/src/main/java/com/base/springcloud/service/imp/OrderServiceImp.java b/payment-base-app/src/main/java/com/base/springcloud/service/imp/OrderServiceImp.java
index 7eef2c4..70a9cdd 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/service/imp/OrderServiceImp.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/service/imp/OrderServiceImp.java
@@ -36,7 +36,8 @@ public class OrderServiceImp implements OrderService {
order.setTimeStamp(new Date());
order.setMchId(orderBO.getMchId());
order.setOrderState(PaymentConstant.WAIT);
- order.setOrderAmount(DecimalUtil.divide(orderBO.getSaleBill().getTotalAmount(),new BigDecimal(100)));
+ BigDecimal amount = orderBO.getSaleBill().getTotalAmount();
+ order.setOrderAmount(DecimalUtil.divide(Optional.ofNullable(amount).orElse(new BigDecimal(0.01)),new BigDecimal(100)));
orderMapper.insert(order);
orderBO.setOrder(order);
}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/service/imp/WecPaymentServiceImp.java b/payment-base-app/src/main/java/com/base/springcloud/service/imp/WecPaymentServiceImp.java
index da1e9e1..6e7797e 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/service/imp/WecPaymentServiceImp.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/service/imp/WecPaymentServiceImp.java
@@ -1,11 +1,19 @@
package com.base.springcloud.service.imp;
+import com.base.springcloud.bo.CodePayBO;
import com.base.springcloud.bo.OrderBO;
+import com.base.springcloud.bo.RefundBO;
import com.base.springcloud.constant.PaymentConstant;
import com.base.springcloud.entity.Order;
+import com.base.springcloud.entity.WxpayRefundVo;
+import com.base.springcloud.entity.WxpayRefundVo.WxRefundAmountVo;
+import com.base.springcloud.entity.WxpayTradeVo;
import com.base.springcloud.service.WecPaymentService;
import com.base.springcloud.util.DateUtil;
import com.base.springcloud.util.DecimalUtil;
+import com.base.springcloud.wxsdk.WxpayUtils;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.hisun.iposm.HiCASignUtil2;
import com.hisun.iposm.HiiposmUtil;
import org.apache.commons.lang.StringUtils;
@@ -15,6 +23,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Map;
+import java.util.Optional;
@Service
public class WecPaymentServiceImp implements WecPaymentService {
@@ -41,7 +52,7 @@ public class WecPaymentServiceImp implements WecPaymentService {
String storeBrand = "RBS-MD";
String cityBranchNm = "RBS-PP";
String createDate = DateUtil.getYearMonthDay2();
-
+ String amount = DecimalUtil.multiple(orderBO.getSaleBill().getTotalAmount(),100).setScale(0, BigDecimal.ROUND_DOWN).toPlainString();
// 签名数据
Order order = orderBO.getOrder();
@@ -54,9 +65,12 @@ public class WecPaymentServiceImp implements WecPaymentService {
signDate.append(PaymentConstant.SIGN_TYPE);
signDate.append(type);
signDate.append(version);
- signDate.append(DecimalUtil.stripTrailingZeros(orderBO.getSaleBill().getTotalAmount()));
+ signDate.append(amount);
signDate.append(createDate);
switch (order.getOrderType()) {
+ case PaymentConstant.HB:
+ signDate.append(PaymentConstant.QRCHB);
+ break;
case PaymentConstant.WEC:
signDate.append(PaymentConstant.QRCWC);
break;
@@ -83,10 +97,13 @@ public class WecPaymentServiceImp implements WecPaymentService {
requestParam.append("&signType=").append(PaymentConstant.SIGN_TYPE);
requestParam.append("&type=").append(type);
requestParam.append("&version=").append(version);
- requestParam.append("&amount=").append(DecimalUtil.stripTrailingZeros(orderBO.getSaleBill().getTotalAmount()));
+ requestParam.append("&amount=").append(amount);
requestParam.append("&orderDate=").append(createDate);
requestParam.append("&payWay=");
switch (order.getOrderType()) {
+ case PaymentConstant.HB:
+ requestParam.append(PaymentConstant.QRCHB);
+ break;
case PaymentConstant.WEC:
requestParam.append(PaymentConstant.QRCWC);
break;
@@ -112,6 +129,92 @@ public class WecPaymentServiceImp implements WecPaymentService {
return StringUtils.isNotBlank(res) ? util.getValue(res, "sessionId") : "";
}
+ /**
+ * 微信Native支付
+ * @param orderBO
+ */
+ @Override
+ public String wecNativePay(OrderBO orderBO) {
+ Order order = Optional.ofNullable(orderBO).map(o->orderBO.getOrder()).orElse(null);
+ if(order == null) {
+ // TODO 抛出业务异常
+ }
+ WxpayTradeVo wxpayTradeVo = new WxpayTradeVo();
+ wxpayTradeVo.setAppid(orderBO.getAppId());
+ wxpayTradeVo.setMchid(orderBO.getMchId());
+ wxpayTradeVo.setDescription(order.getGoodName());
+ wxpayTradeVo.setOut_trade_no(order.getOrderNo());
+ // FIXME 待定为商户系统||RBS系统回调接收
+ wxpayTradeVo.setNotify_url("https://www.weixin.qq.com/wxpay/pay.php");
+ WxpayTradeVo.WxTradeAmountVo wxTradeAmountVo = new WxpayTradeVo.WxTradeAmountVo();
+ wxTradeAmountVo.setTotal(order.getOrderAmount());
+ wxpayTradeVo.setAmount(wxTradeAmountVo);
+ ObjectNode objectNode = WxpayUtils.getWxpay(
+ wxpayTradeVo,
+ PaymentConstant.NATIVE,
+ orderBO.getMchPrivateKey(),
+ orderBO.getCertificate(),
+ orderBO.getSerialNo());
+ logger.info("Native支付订单 :{} 接收响应数据 :{}",order.getOrderNo(),objectNode.toString());
+ JsonNode jsonNode = objectNode.get("code_url");
+ return jsonNode.toString();
+ }
+
+ /**
+ * 微信APIv3退款
+ * @param refundBO
+ */
+ @Override
+ public void wecRefund(RefundBO refundBO) {
+ WxpayRefundVo wxpayRefundVo = new WxpayRefundVo();
+ wxpayRefundVo.setTransaction_id(refundBO.getOrigOrderNo());
+ wxpayRefundVo.setReason(refundBO.getReason());
+ WxRefundAmountVo wxRefundAmountVo = new WxRefundAmountVo();
+ wxRefundAmountVo.setTotal(refundBO.getOrderAmount());
+ wxRefundAmountVo.setRefund(refundBO.getRefundAmount());
+ wxpayRefundVo.setNotify_url(refundBO.getNotifyUrl());
+ wxpayRefundVo.setOut_refund_no(refundBO.getRefundNo());
+ wxpayRefundVo.setAmount(wxRefundAmountVo);
+ String refundRsp = WxpayUtils.refund(wxpayRefundVo,refundBO.getMchId(),refundBO.getMchPrivateKey(),refundBO.getCertificate(),refundBO.getSerialNo());
+ }
+
+ /**
+ * 微信付款码支付
+ * @param codePayBO
+ */
+ @Override
+ public void codePay(CodePayBO codePayBO) {
+ }
+
+ /*public static void main(String[] args) {
+ CodePayBO codePayBO = new CodePayBO();
+ codePayBO.setAppId("wxa91984b310053c3d");
+ codePayBO.setMchId("1610494969");
+ codePayBO.setDeviceInfo("sb0001");
+ codePayBO.setSpbillCreateIp("8.8.8.8");
+ codePayBO.setBody("Image形象店-深圳腾大-QQ公仔");
+ codePayBO.setOrderNo(DateUtil.getDateString(25));
+ codePayBO.setAmount(new BigDecimal(1));
+ codePayBO.setMchPrivateKey("MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCVVy4CUrRTPJZ49vuK9Tr5Rurd3dE3eiz7lsnWPAYgZtdWsC6TORUaP0Px/ZMtaJYNURMpVKC5koXZSPTNr9ROHa6B6IX4lTMERzHgiVzGIqLlE/twBYXbSuh8yXy3k59adFDYZZNN+2THWdst2Ao7pD+GXlDby8gfF67ax9CbWq5yyx6MYMKHwOVe3H/ON2pcmZR6I809Lwa4zfmUyOez1U5pwNiwv2cyorM5UXzVMsvK5jfIKDaskNVIV/+ax15tf+OQ/6t9iaWzRAV1zeeJd/TuVVGB/QU5slg5xyIwaqKzJM1H3n64er5QNRKpU31Ru0dHzJTW7ljtILWCQZubAgMBAAECggEAVga6QixSJACqzVO44vsQ1be8Y8H7WvHOJ79c1UQOY2vab91ddwFS3qPDCAsCVQ4CIfmqCAzv6PY2lEwqeLHooncGp17Cjcekc7pvSODXyGr7Wup/1K/Zw9EKy5dleHpZOUn3qDAbArjl/NAOKgJhIqoizcxW18YzqXWDDBQ+Xr6kqmjWXWZ3Kj6ghZNoSkdoynTQ7Ti0C2lq0UeXg611ea6Zs3fgjOgYEBMSSI7v4zE/6RN7jFGlZe5Z1NgO15rl8r3YdDnXTCIpEiTV+yHsDkluqJquB0E1Xrr1AXl9AGMuq6lYEB4houJUPtGgQ6bLs93MrnyzgnGTLbLSnXDNwQKBgQDD6Z+0xZ8lApXZylI2N4zuSrCsi6wB6Qp/64jr+C7GAdmC7w9WRoIlIF7Q2s9p5ulGyxD2hAbrajY5CsjV3S0uIDAaULoG3dw5vxYFEdvcKgxphf8j0gcajUnPGzCxF5vm29B9D+o1mrylr04dtWHlP0KpKwQ6HiTX7if+DcXMhwKBgQDDJOFpoCcVdZ0clfpvJPZpgRTu3I4HFTs27elCQ98g1UWav/VY7729YLAKU27d6lG+iqH1X5lVu2lr/du8wdJoMbpBPD34V+V6ye8gV9lCG7wSCgsNnPqM/HoyBSe6+JUgwG+vCRh1KMhNBmqEMCqPRAtbW3wfDdCShfTBHAjxTQKBgCjYrNOj4Gp2/4dJB5sGOfv01qEi/dnoHKhPE10fgaEHAbKq5Se3y6qx9RLWjCgoXLrF2+OJ42dx3ZxHA1iTpLfZPi4+ZorH9wKTNKQZ9R0SJ0RZC7kMMS74BkuH/77sjTS03+MzTkGe4XYp9r968c46QkvDDD6Ryly66tutQ3BBAoGBAJiE/MDncWSuVit/dQrlYso3HztJmqAikfNuoz8tePlad1pp6cKnLi8ToMORTjeX6mf4RnlXTKrbHZxP/yxaeK8DHsXZ5b1yRWNg7MrknmOy9Z1LyDPNm24R7xelO9ZAb533idLOUxVO2K6K8zl5w+nRjubKbAqBa6D5ncBIvxyJAoGAP9cSun39cFuzjdTaWojmbQMM8k5pyvFWr2UdY50GFrFCdajkAxqoTLkFEUP5fx8InIU/X9aSbVE6Va5fdOKiifbx5KaSKFrH/xZS6XykAbIkWDWqOPMovHoMWj3lGO96wDInry9YZNy7JA8AuZsLSBUA7Xb33FqZ2WU5ePeFOAA=");
+ codePayBO.setAuthCode("135276524069149568");
+
+ Map params = MicroPayModel
+ .builder()
+ .appid(codePayBO.getAppId())
+ .mch_id(codePayBO.getMchId())
+ .device_info(codePayBO.getDeviceInfo())
+ .nonce_str(WxPayKit.generateStr())
+ .body(codePayBO.getBody())
+ //.attach(codePayBO.getAttach())
+ .out_trade_no(codePayBO.getOrderNo())
+ .total_fee(codePayBO.getAmount().toPlainString())
+ .spbill_create_ip(codePayBO.getSpbillCreateIp())
+ .auth_code(codePayBO.getAuthCode())
+ .build()
+ .createSign(codePayBO.getMchPrivateKey(), SignType.HMACSHA256);
+ System.out.println(WxPayApi.microPay(false,params));
+ }
+*/
/**
* 订单状态查询
*
@@ -167,5 +270,4 @@ public class WecPaymentServiceImp implements WecPaymentService {
}
return false;
}
-
}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/util/DecimalUtil.java b/payment-base-app/src/main/java/com/base/springcloud/util/DecimalUtil.java
index d10bbbf..22b44da 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/util/DecimalUtil.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/util/DecimalUtil.java
@@ -1,9 +1,12 @@
package com.base.springcloud.util;
import java.math.BigDecimal;
+import java.text.DecimalFormat;
public class DecimalUtil {
+ private DecimalFormat df = new DecimalFormat("#.00");
+
/**
* 加法计算(result = x + y)
*
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/IWXPayDomain.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/IWXPayDomain.java
new file mode 100644
index 0000000..da5ae42
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/IWXPayDomain.java
@@ -0,0 +1,42 @@
+package com.base.springcloud.wxsdk;
+
+/**
+ * 域名管理,实现主备域名自动切换
+ */
+public abstract interface IWXPayDomain {
+ /**
+ * 上报域名网络状况
+ * @param domain 域名。 比如:api.mch.weixin.qq.com
+ * @param elapsedTimeMillis 耗时
+ * @param ex 网络请求中出现的异常。
+ * null表示没有异常
+ * ConnectTimeoutException,表示建立网络连接异常
+ * UnknownHostException, 表示dns解析异常
+ */
+ abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);
+
+ /**
+ * 获取域名
+ * @param config 配置
+ * @return 域名
+ */
+ abstract DomainInfo getDomain(final WXPayConfig config);
+
+ static class DomainInfo{
+ public String domain; //域名
+ public boolean primaryDomain; //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
+ public DomainInfo(String domain, boolean primaryDomain) {
+ this.domain = domain;
+ this.primaryDomain = primaryDomain;
+ }
+
+ @Override
+ public String toString() {
+ return "DomainInfo{" +
+ "domain='" + domain + '\'' +
+ ", primaryDomain=" + primaryDomain +
+ '}';
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/MyConfig.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/MyConfig.java
new file mode 100644
index 0000000..1286ae3
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/MyConfig.java
@@ -0,0 +1,87 @@
+package com.base.springcloud.wxsdk;
+
+import sun.misc.BASE64Decoder;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MyConfig extends WXPayConfig {
+
+ private String appid = "wxa91984b310053c3d";
+ private String mchid = "1610494969";
+ private String key = "yDXt3Jczcs2dSKvwUxGkYYfHzCe8SXtY";
+
+ /**
+ * 加载证书 这里证书需要到微信商户平台进行下载
+ */
+ private byte[] certData;
+
+ public MyConfig() throws Exception {
+ this.certData = new BASE64Decoder().decodeBuffer("MIID9jCCAt6gAwIBAgIUHPQVmPutv7O6ub4lOfSWhLWhflowDQYJKoZIhvcNAQELBQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsTFFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3QgQ0EwHhcNMjEwNjA5MDMzMjQ5WhcNMjYwNjA4MDMzMjQ5WjCBhzETMBEGA1UEAwwKMTYxMDQ5NDk2OTEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTMwMQYDVQQLDCrotLXlt57pgJrnqIvlhYTlvJ/lt6XnqIvmnLrmorDmnInpmZDlhazlj7gxCzAJBgNVBAYMAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJVXLgJStFM8lnj2+4r1OvlG6t3d0Td6LPuWydY8BiBm11awLpM5FRo/Q/H9ky1olg1REylUoLmShdlI9M2v1E4droHohfiVMwRHMeCJXMYiouUT+3AFhdtK6HzJfLeTn1p0UNhlk037ZMdZ2y3YCjukP4ZeUNvLyB8XrtrH0JtarnLLHoxgwofA5V7cf843alyZlHojzT0vBrjN+ZTI57PVTmnA2LC/ZzKiszlRfNUyy8rmN8goNqyQ1UhX/5rHXm1/45D/q32JpbNEBXXN54l39O5VUYH9BTmyWDnHIjBqorMkzUfefrh6vlA1EqlTfVG7R0fMlNbuWO0gtYJBm5sCAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMGUGA1UdHwReMFwwWqBYoFaGVGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMvaXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4RUJEMjANBgkqhkiG9w0BAQsFAAOCAQEACoG5Uf3X33UlDY6VWxgvN6Kg1W9ZRwcwcxSPuURv7OVO1o1JH0c1lPRqY3H8A+TeVI4/5wt5GPRRkSOtecPhMikgvhUjxJZnM0b30sxEv7hoqgEqNcsu2Kc60W6hGzVW0Fw8qimjOmoREBAaOOpYNr/zIrZZiW4vJd//ESNXvpTlwwK1xE9VW3VJGHW7mMZ+Whr3S13esH7rds5mLE6OQvrL6B2weEcVlzP1oEZAbaZW5bgz2gQBR5WheU8QGySnU/e38nzLsdwY0r7ztaWQ39z7fsOeY2gupB8cZ251pWZ9oYvUu4u0VFflrbJiMdLLEZDej02MYtgO6o9xb8BNmw==");
+ }
+
+ @Override
+ String getAppID() {
+ return this.appid;
+ }
+
+ @Override
+ String getMchID() {
+ return this.mchid;
+ }
+
+ @Override
+ String getKey() {
+ return this.key;
+ }
+
+ void setKey(String key){this.key = key;}
+
+ @Override
+ InputStream getCertStream() {
+ return new ByteArrayInputStream(this.certData);
+ }
+
+ @Override
+ public IWXPayDomain getWXPayDomain() { // 这个方法需要这样实现, 否则无法正常初始化WXPay
+ IWXPayDomain iwxPayDomain = new IWXPayDomain() {
+ @Override
+ public void report(String domain, long elapsedTimeMillis, Exception ex) {
+
+ }
+
+ @Override
+ public DomainInfo getDomain(WXPayConfig config) {
+ return new DomainInfo(WXPayConstants.DOMAIN_API, true);
+ }
+ };
+ return iwxPayDomain;
+ }
+
+ /**
+ * 获取沙箱密钥
+ * @return
+ */
+ public String getSandboxSignKey() {
+ try {
+ WXPay wxPay = new WXPay(this);
+ Map params = new HashMap();
+ params.put("mch_id", this.getMchID());
+ params.put("nonce_str", WXPayUtil.generateNonceStr());
+ params.put("sign", WXPayUtil.generateSignature(params, this.getKey()));
+ String strXML = wxPay.requestWithoutCert("https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey",
+ params, this.getHttpConnectTimeoutMs(), this.getHttpReadTimeoutMs());
+ System.out.println(strXML);
+ Map result = WXPayUtil.xmlToMap(strXML);
+ if ("SUCCESS".equals(result.get("return_code"))) {
+ return result.get("sandbox_signkey");
+ }
+ return null;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPay.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPay.java
new file mode 100644
index 0000000..2e8df72
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPay.java
@@ -0,0 +1,689 @@
+package com.base.springcloud.wxsdk;
+
+import com.base.springcloud.wxsdk.WXPayConstants.SignType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class WXPay {
+
+ private WXPayConfig config;
+ private SignType signType;
+ private boolean autoReport;
+ private boolean useSandbox;
+ private String notifyUrl;
+ private WXPayRequest wxPayRequest;
+
+ public WXPay(final WXPayConfig config) throws Exception {
+ this(config, null, true, false);
+ }
+
+ public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception {
+ this(config, null, autoReport, false);
+ }
+
+
+ public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{
+ this(config, null, autoReport, useSandbox);
+ }
+
+ public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception {
+ this(config, notifyUrl, true, false);
+ }
+
+ public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception {
+ this(config, notifyUrl, autoReport, false);
+ }
+
+ public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
+ this.config = config;
+ this.notifyUrl = notifyUrl;
+ this.autoReport = autoReport;
+ this.useSandbox = useSandbox;
+ if (useSandbox) {
+ this.signType = SignType.MD5; // 沙箱环境
+ }
+ else {
+ this.signType = SignType.HMACSHA256;
+ }
+ this.wxPayRequest = new WXPayRequest(config);
+ }
+
+ private void checkWXPayConfig() throws Exception {
+ if (this.config == null) {
+ throw new Exception("config is null");
+ }
+ if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) {
+ throw new Exception("appid in config is empty");
+ }
+ if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) {
+ throw new Exception("appid in config is empty");
+ }
+ if (this.config.getCertStream() == null) {
+ throw new Exception("cert stream in config is empty");
+ }
+ if (this.config.getWXPayDomain() == null){
+ throw new Exception("config.getWXPayDomain() is null");
+ }
+
+ if (this.config.getHttpConnectTimeoutMs() < 10) {
+ throw new Exception("http connect timeout is too small");
+ }
+ if (this.config.getHttpReadTimeoutMs() < 10) {
+ throw new Exception("http read timeout is too small");
+ }
+
+ }
+
+ /**
+ * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign
+ * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
+ *
+ * @param reqData
+ * @return
+ * @throws Exception
+ */
+ public Map fillRequestData(Map reqData) throws Exception {
+ reqData.put("appid", config.getAppID());
+ reqData.put("mch_id", config.getMchID());
+ reqData.put("nonce_str", WXPayUtil.generateNonceStr());
+ if (SignType.MD5.equals(this.signType)) {
+ reqData.put("sign_type", WXPayConstants.MD5);
+ }
+ else if (SignType.HMACSHA256.equals(this.signType)) {
+ reqData.put("sign_type", WXPayConstants.HMACSHA256);
+ }
+ reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
+ return reqData;
+ }
+
+ /**
+ * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
+ *
+ * @param reqData 向wxpay post的请求数据
+ * @return 签名是否有效
+ * @throws Exception
+ */
+ public boolean isResponseSignatureValid(Map reqData) throws Exception {
+ // 返回数据的签名方式和请求中给定的签名方式是一致的
+ return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
+ }
+
+ /**
+ * 判断支付结果通知中的sign是否有效
+ *
+ * @param reqData 向wxpay post的请求数据
+ * @return 签名是否有效
+ * @throws Exception
+ */
+ public boolean isPayResultNotifySignatureValid(Map reqData) throws Exception {
+ String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
+ SignType signType;
+ if (signTypeInData == null) {
+ signType = SignType.MD5;
+ }
+ else {
+ signTypeInData = signTypeInData.trim();
+ if (signTypeInData.length() == 0) {
+ signType = SignType.MD5;
+ }
+ else if (WXPayConstants.MD5.equals(signTypeInData)) {
+ signType = SignType.MD5;
+ }
+ else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
+ signType = SignType.HMACSHA256;
+ }
+ else {
+ throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
+ }
+ }
+ return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), signType);
+ }
+
+
+ /**
+ * 不需要证书的请求
+ * @param urlSuffix String
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 超时时间,单位是毫秒
+ * @param readTimeoutMs 超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public String requestWithoutCert(String urlSuffix, Map reqData,
+ int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String msgUUID = reqData.get("nonce_str");
+ String reqBody = WXPayUtil.mapToXml(reqData);
+ System.out.println("请求串:" + reqBody);
+ String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
+ return resp;
+ }
+
+
+ /**
+ * 需要证书的请求
+ * @param urlSuffix String
+ * @param reqData 向wxpay post的请求数据 Map
+ * @param connectTimeoutMs 超时时间,单位是毫秒
+ * @param readTimeoutMs 超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public String requestWithCert(String urlSuffix, Map reqData,
+ int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String msgUUID= reqData.get("nonce_str");
+ String reqBody = WXPayUtil.mapToXml(reqData);
+
+ String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
+ return resp;
+ }
+
+ /**
+ * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
+ * @param xmlStr API返回的XML格式数据
+ * @return Map类型数据
+ * @throws Exception
+ */
+ public Map processResponseXml(String xmlStr) throws Exception {
+ String RETURN_CODE = "return_code";
+ String return_code;
+ Map respData = WXPayUtil.xmlToMap(xmlStr);
+ if (respData.containsKey(RETURN_CODE)) {
+ return_code = respData.get(RETURN_CODE);
+ }
+ else {
+ throw new Exception(String.format("No `return_code` in XML: %s", xmlStr));
+ }
+
+ if (return_code.equals(WXPayConstants.FAIL)) {
+ return respData;
+ }
+ else if (return_code.equals(WXPayConstants.SUCCESS)) {
+ if (this.isResponseSignatureValid(respData)) {
+ return respData;
+ }
+ else {
+ throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
+ }
+ }
+ else {
+ throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
+ }
+ }
+
+ /**
+ * 作用:提交刷卡支付
+ * 场景:刷卡支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map microPay(Map reqData) throws Exception {
+ return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:提交刷卡支付
+ * 场景:刷卡支付
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map microPay(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.MICROPAY_URL_SUFFIX;
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+ /**
+ * 提交刷卡支付,针对软POS,尽可能做成功
+ * 内置重试机制,最多60s
+ * @param reqData
+ * @return
+ * @throws Exception
+ */
+ public Map microPayWithPos(Map reqData) throws Exception {
+ return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());
+ }
+
+ /**
+ * 提交刷卡支付,针对软POS,尽可能做成功
+ * 内置重试机制,最多60s
+ * @param reqData
+ * @param connectTimeoutMs
+ * @return
+ * @throws Exception
+ */
+ public Map microPayWithPos(Map reqData, int connectTimeoutMs) throws Exception {
+ int remainingTimeMs = 60*1000;
+ long startTimestampMs = 0;
+ Map lastResult = null;
+ Exception lastException = null;
+
+ while (true) {
+ startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+ int readTimeoutMs = remainingTimeMs - connectTimeoutMs;
+ if (readTimeoutMs > 1000) {
+ try {
+ lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);
+ String returnCode = lastResult.get("return_code");
+ if (returnCode.equals("SUCCESS")) {
+ String resultCode = lastResult.get("result_code");
+ String errCode = lastResult.get("err_code");
+ if (resultCode.equals("SUCCESS")) {
+ break;
+ }
+ else {
+ // 看错误码,若支付结果未知,则重试提交刷卡支付
+ if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {
+ remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs);
+ if (remainingTimeMs <= 100) {
+ break;
+ }
+ else {
+ WXPayUtil.getLogger().info("microPayWithPos: try micropay again");
+ if (remainingTimeMs > 5*1000) {
+ Thread.sleep(5*1000);
+ }
+ else {
+ Thread.sleep(1*1000);
+ }
+ continue;
+ }
+ }
+ else {
+ break;
+ }
+ }
+ }
+ else {
+ break;
+ }
+ }
+ catch (Exception ex) {
+ lastResult = null;
+ lastException = ex;
+ }
+ }
+ else {
+ break;
+ }
+ }
+
+ if (lastResult == null) {
+ throw lastException;
+ }
+ else {
+ return lastResult;
+ }
+ }
+
+
+
+ /**
+ * 作用:统一下单
+ * 场景:公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map unifiedOrder(Map reqData) throws Exception {
+ return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:统一下单
+ * 场景:公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map unifiedOrder(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
+ }
+ if(this.notifyUrl != null) {
+ reqData.put("notify_url", this.notifyUrl);
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+ /**
+ * 作用:查询订单
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map orderQuery(Map reqData) throws Exception {
+ return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:查询订单
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据 int
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map orderQuery(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.ORDERQUERY_URL_SUFFIX;
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+ /**
+ * 作用:撤销订单
+ * 场景:刷卡支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map reverse(Map reqData) throws Exception {
+ return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:撤销订单
+ * 场景:刷卡支付
+ * 其他:需要证书
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map reverse(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.REVERSE_URL_SUFFIX;
+ }
+ String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+ /**
+ * 作用:关闭订单
+ * 场景:公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map closeOrder(Map reqData) throws Exception {
+ return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:关闭订单
+ * 场景:公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map closeOrder(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.CLOSEORDER_URL_SUFFIX;
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+ /**
+ * 作用:申请退款
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map refund(Map reqData) throws Exception {
+ return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:申请退款
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * 其他:需要证书
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map refund(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.REFUND_URL_SUFFIX;
+ }
+ String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+ /**
+ * 作用:退款查询
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map refundQuery(Map reqData) throws Exception {
+ return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:退款查询
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map refundQuery(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.REFUNDQUERY_URL_SUFFIX;
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+ /**
+ * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map downloadBill(Map reqData) throws Exception {
+ return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:对账单下载
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * 其他:无论是否成功都返回Map。若成功,返回的Map中含有return_code、return_msg、data,
+ * 其中return_code为`SUCCESS`,data为对账单数据。
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return 经过封装的API返回数据
+ * @throws Exception
+ */
+ public Map downloadBill(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX;
+ }
+ String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
+ Map ret;
+ // 出现错误,返回XML数据
+ if (respStr.indexOf("<") == 0) {
+ ret = WXPayUtil.xmlToMap(respStr);
+ }
+ else {
+ // 正常返回csv数据
+ ret = new HashMap();
+ ret.put("return_code", WXPayConstants.SUCCESS);
+ ret.put("return_msg", "ok");
+ ret.put("data", respStr);
+ }
+ return ret;
+ }
+
+
+ /**
+ * 作用:交易保障
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map report(Map reqData) throws Exception {
+ return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:交易保障
+ * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map report(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.REPORT_URL_SUFFIX;
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return WXPayUtil.xmlToMap(respXml);
+ }
+
+
+ /**
+ * 作用:转换短链接
+ * 场景:刷卡支付、扫码支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map shortUrl(Map reqData) throws Exception {
+ return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:转换短链接
+ * 场景:刷卡支付、扫码支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map shortUrl(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.SHORTURL_URL_SUFFIX;
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+ /**
+ * 作用:授权码查询OPENID接口
+ * 场景:刷卡支付
+ * @param reqData 向wxpay post的请求数据
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map authCodeToOpenid(Map reqData) throws Exception {
+ return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+ }
+
+
+ /**
+ * 作用:授权码查询OPENID接口
+ * 场景:刷卡支付
+ * @param reqData 向wxpay post的请求数据
+ * @param connectTimeoutMs 连接超时时间,单位是毫秒
+ * @param readTimeoutMs 读超时时间,单位是毫秒
+ * @return API返回数据
+ * @throws Exception
+ */
+ public Map authCodeToOpenid(Map reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+ String url;
+ if (this.useSandbox) {
+ url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX;
+ }
+ else {
+ url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX;
+ }
+ String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+ return this.processResponseXml(respXml);
+ }
+
+
+} // end class
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayConfig.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayConfig.java
new file mode 100644
index 0000000..47e3427
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayConfig.java
@@ -0,0 +1,103 @@
+package com.base.springcloud.wxsdk;
+
+import java.io.InputStream;
+
+public abstract class WXPayConfig {
+
+
+
+ /**
+ * 获取 App ID
+ *
+ * @return App ID
+ */
+ abstract String getAppID();
+
+
+ /**
+ * 获取 Mch ID
+ *
+ * @return Mch ID
+ */
+ abstract String getMchID();
+
+
+ /**
+ * 获取 API 密钥
+ *
+ * @return API密钥
+ */
+ abstract String getKey();
+
+
+ /**
+ * 获取商户证书内容
+ *
+ * @return 商户证书内容
+ */
+ abstract InputStream getCertStream();
+
+ /**
+ * HTTP(S) 连接超时时间,单位毫秒
+ *
+ * @return
+ */
+ public int getHttpConnectTimeoutMs() {
+ return 6*1000;
+ }
+
+ /**
+ * HTTP(S) 读数据超时时间,单位毫秒
+ *
+ * @return
+ */
+ public int getHttpReadTimeoutMs() {
+ return 8*1000;
+ }
+
+ /**
+ * 获取WXPayDomain, 用于多域名容灾自动切换
+ * @return
+ */
+ abstract IWXPayDomain getWXPayDomain();
+
+ /**
+ * 是否自动上报。
+ * 若要关闭自动上报,子类中实现该函数返回 false 即可。
+ *
+ * @return
+ */
+ public boolean shouldAutoReport() {
+ return true;
+ }
+
+ /**
+ * 进行健康上报的线程的数量
+ *
+ * @return
+ */
+ public int getReportWorkerNum() {
+ return 6;
+ }
+
+
+ /**
+ * 健康上报缓存消息的最大数量。会有线程去独立上报
+ * 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
+ *
+ * @return
+ */
+ public int getReportQueueMaxSize() {
+ return 10000;
+ }
+
+ /**
+ * 批量上报,一次最多上报多个数据
+ *
+ * @return
+ */
+ public int getReportBatchSize() {
+ return 10;
+ }
+
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayConstants.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayConstants.java
new file mode 100644
index 0000000..e725363
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayConstants.java
@@ -0,0 +1,59 @@
+package com.base.springcloud.wxsdk;
+
+import org.apache.http.client.HttpClient;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+ public enum SignType {
+ MD5, HMACSHA256
+ }
+
+ public static final String DOMAIN_API = "api.mch.weixin.qq.com";
+ public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
+ public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
+ public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
+
+
+ public static final String FAIL = "FAIL";
+ public static final String SUCCESS = "SUCCESS";
+ public static final String HMACSHA256 = "HMAC-SHA256";
+ public static final String MD5 = "MD5";
+
+ public static final String FIELD_SIGN = "sign";
+ public static final String FIELD_SIGN_TYPE = "sign_type";
+
+ public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
+ public static final String USER_AGENT = WXPAYSDK_VERSION +
+ " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
+ ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
+
+ public static final String MICROPAY_URL_SUFFIX = "/pay/micropay";
+ public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
+ public static final String ORDERQUERY_URL_SUFFIX = "/pay/orderquery";
+ public static final String REVERSE_URL_SUFFIX = "/secapi/pay/reverse";
+ public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder";
+ public static final String REFUND_URL_SUFFIX = "/secapi/pay/refund";
+ public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery";
+ public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
+ public static final String REPORT_URL_SUFFIX = "/payitil/report";
+ public static final String SHORTURL_URL_SUFFIX = "/tools/shorturl";
+ public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
+
+ // sandbox
+ public static final String SANDBOX_MICROPAY_URL_SUFFIX = "/sandboxnew/pay/micropay";
+ public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
+ public static final String SANDBOX_ORDERQUERY_URL_SUFFIX = "/sandboxnew/pay/orderquery";
+ public static final String SANDBOX_REVERSE_URL_SUFFIX = "/sandboxnew/secapi/pay/reverse";
+ public static final String SANDBOX_CLOSEORDER_URL_SUFFIX = "/sandboxnew/pay/closeorder";
+ public static final String SANDBOX_REFUND_URL_SUFFIX = "/sandboxnew/secapi/pay/refund";
+ public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX = "/sandboxnew/pay/refundquery";
+ public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
+ public static final String SANDBOX_REPORT_URL_SUFFIX = "/sandboxnew/payitil/report";
+ public static final String SANDBOX_SHORTURL_URL_SUFFIX = "/sandboxnew/tools/shorturl";
+ public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
+
+}
+
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayReport.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayReport.java
new file mode 100644
index 0000000..13067d0
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayReport.java
@@ -0,0 +1,265 @@
+package com.base.springcloud.wxsdk;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * 交易保障
+ */
+public class WXPayReport {
+
+ public static class ReportInfo {
+
+ /**
+ * 布尔变量使用int。0为false, 1为true。
+ */
+
+ // 基本信息
+ private String version = "v1";
+ private String sdk = WXPayConstants.WXPAYSDK_VERSION;
+ private String uuid; // 交易的标识
+ private long timestamp; // 上报时的时间戳,单位秒
+ private long elapsedTimeMillis; // 耗时,单位 毫秒
+
+ // 针对主域名
+ private String firstDomain; // 第1次请求的域名
+ private boolean primaryDomain; //是否主域名
+ private int firstConnectTimeoutMillis; // 第1次请求设置的连接超时时间,单位 毫秒
+ private int firstReadTimeoutMillis; // 第1次请求设置的读写超时时间,单位 毫秒
+ private int firstHasDnsError; // 第1次请求是否出现dns问题
+ private int firstHasConnectTimeout; // 第1次请求是否出现连接超时
+ private int firstHasReadTimeout; // 第1次请求是否出现连接超时
+
+ public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
+ this.uuid = uuid;
+ this.timestamp = timestamp;
+ this.elapsedTimeMillis = elapsedTimeMillis;
+ this.firstDomain = firstDomain;
+ this.primaryDomain = primaryDomain;
+ this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;
+ this.firstReadTimeoutMillis = firstReadTimeoutMillis;
+ this.firstHasDnsError = firstHasDnsError?1:0;
+ this.firstHasConnectTimeout = firstHasConnectTimeout?1:0;
+ this.firstHasReadTimeout = firstHasReadTimeout?1:0;
+ }
+
+ @Override
+ public String toString() {
+ return "ReportInfo{" +
+ "version='" + version + '\'' +
+ ", sdk='" + sdk + '\'' +
+ ", uuid='" + uuid + '\'' +
+ ", timestamp=" + timestamp +
+ ", elapsedTimeMillis=" + elapsedTimeMillis +
+ ", firstDomain='" + firstDomain + '\'' +
+ ", primaryDomain=" + primaryDomain +
+ ", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis +
+ ", firstReadTimeoutMillis=" + firstReadTimeoutMillis +
+ ", firstHasDnsError=" + firstHasDnsError +
+ ", firstHasConnectTimeout=" + firstHasConnectTimeout +
+ ", firstHasReadTimeout=" + firstHasReadTimeout +
+ '}';
+ }
+
+ /**
+ * 转换成 csv 格式
+ *
+ * @return
+ */
+ public String toLineString(String key) {
+ String separator = ",";
+ Object[] objects = new Object[] {
+ version, sdk, uuid, timestamp, elapsedTimeMillis,
+ firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
+ firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout
+ };
+ StringBuffer sb = new StringBuffer();
+ for(Object obj: objects) {
+ sb.append(obj).append(separator);
+ }
+ try {
+ String sign = WXPayUtil.HMACSHA256(sb.toString(), key);
+ sb.append(sign);
+ return sb.toString();
+ }
+ catch (Exception ex) {
+ return null;
+ }
+
+ }
+
+ }
+
+ private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default";
+ // private static final String REPORT_URL = "http://127.0.0.1:5000/test";
+
+
+ private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000;
+ private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000;
+
+ private LinkedBlockingQueue reportMsgQueue = null;
+ private WXPayConfig config;
+ private ExecutorService executorService;
+
+ private volatile static WXPayReport INSTANCE;
+
+ private WXPayReport(final WXPayConfig config) {
+ this.config = config;
+ reportMsgQueue = new LinkedBlockingQueue(config.getReportQueueMaxSize());
+
+ // 添加处理线程
+ executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() {
+ public Thread newThread(Runnable r) {
+ Thread t = Executors.defaultThreadFactory().newThread(r);
+ t.setDaemon(true);
+ return t;
+ }
+ });
+
+ if (config.shouldAutoReport()) {
+ WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());
+ for (int i = 0; i < config.getReportWorkerNum(); ++i) {
+ executorService.execute(new Runnable() {
+ public void run() {
+ while (true) {
+ // 先用 take 获取数据
+ try {
+ StringBuffer sb = new StringBuffer();
+ String firstMsg = reportMsgQueue.take();
+ WXPayUtil.getLogger().info("get first report msg: {}", firstMsg);
+ String msg = null;
+ sb.append(firstMsg); //会阻塞至有消息
+ int remainNum = config.getReportBatchSize() - 1;
+ for (int j=0; jcreate()
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", SSLConnectionSocketFactory.getSocketFactory())
+ .build(),
+ null,
+ null,
+ null
+ );
+ HttpClient httpClient = HttpClientBuilder.create()
+ .setConnectionManager(connManager)
+ .build();
+
+ HttpPost httpPost = new HttpPost(REPORT_URL);
+
+ RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+ httpPost.setConfig(requestConfig);
+
+ StringEntity postEntity = new StringEntity(data, "UTF-8");
+ httpPost.addHeader("Content-Type", "text/xml");
+ httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT);
+ httpPost.setEntity(postEntity);
+
+ HttpResponse httpResponse = httpClient.execute(httpPost);
+ HttpEntity httpEntity = httpResponse.getEntity();
+ return EntityUtils.toString(httpEntity, "UTF-8");
+ }
+
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayRequest.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayRequest.java
new file mode 100644
index 0000000..cad3463
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayRequest.java
@@ -0,0 +1,257 @@
+package com.base.springcloud.wxsdk;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import static com.base.springcloud.wxsdk.WXPayConstants.USER_AGENT;
+
+public class WXPayRequest {
+ private WXPayConfig config;
+ public WXPayRequest(WXPayConfig config) throws Exception{
+
+ this.config = config;
+ }
+
+ /**
+ * 请求,只请求一次,不做重试
+ * @param domain
+ * @param urlSuffix
+ * @param uuid
+ * @param data
+ * @param connectTimeoutMs
+ * @param readTimeoutMs
+ * @param useCert 是否使用证书,针对退款、撤销等操作
+ * @return
+ * @throws Exception
+ */
+ private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
+ BasicHttpClientConnectionManager connManager;
+ if (useCert) {
+ // 证书
+ char[] password = config.getMchID().toCharArray();
+ InputStream certStream = config.getCertStream();
+ KeyStore ks = KeyStore.getInstance("PKCS12");
+ ks.load(certStream, password);
+
+ // 实例化密钥库 & 初始化密钥工厂
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, password);
+
+ // 创建 SSLContext
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+
+ SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
+ sslContext,
+ new String[]{"TLSv1"},
+ null,
+ new DefaultHostnameVerifier());
+
+ connManager = new BasicHttpClientConnectionManager(
+ RegistryBuilder.create()
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", sslConnectionSocketFactory)
+ .build(),
+ null,
+ null,
+ null
+ );
+ }
+ else {
+ connManager = new BasicHttpClientConnectionManager(
+ RegistryBuilder.create()
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", SSLConnectionSocketFactory.getSocketFactory())
+ .build(),
+ null,
+ null,
+ null
+ );
+ }
+
+ HttpClient httpClient = HttpClientBuilder.create()
+ .setConnectionManager(connManager)
+ .build();
+
+ String url = "https://" + domain + urlSuffix;
+ HttpPost httpPost = new HttpPost(url);
+
+ RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+ httpPost.setConfig(requestConfig);
+
+ StringEntity postEntity = new StringEntity(data, "UTF-8");
+ httpPost.addHeader("Content-Type", "text/xml");
+ httpPost.addHeader("User-Agent", USER_AGENT + " " + config.getMchID());
+ httpPost.setEntity(postEntity);
+
+ HttpResponse httpResponse = httpClient.execute(httpPost);
+ HttpEntity httpEntity = httpResponse.getEntity();
+ return EntityUtils.toString(httpEntity, "UTF-8");
+
+ }
+
+
+ private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
+ Exception exception = null;
+ long elapsedTimeMillis = 0;
+ long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+ boolean firstHasDnsErr = false;
+ boolean firstHasConnectTimeout = false;
+ boolean firstHasReadTimeout = false;
+ IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
+ if(domainInfo == null){
+ throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
+ }
+ try {
+ String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
+ elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+ config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
+ WXPayReport.getInstance(config).report(
+ uuid,
+ elapsedTimeMillis,
+ domainInfo.domain,
+ domainInfo.primaryDomain,
+ connectTimeoutMs,
+ readTimeoutMs,
+ firstHasDnsErr,
+ firstHasConnectTimeout,
+ firstHasReadTimeout);
+ return result;
+ }
+ catch (UnknownHostException ex) { // dns 解析错误,或域名不存在
+ exception = ex;
+ firstHasDnsErr = true;
+ elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+ WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
+ WXPayReport.getInstance(config).report(
+ uuid,
+ elapsedTimeMillis,
+ domainInfo.domain,
+ domainInfo.primaryDomain,
+ connectTimeoutMs,
+ readTimeoutMs,
+ firstHasDnsErr,
+ firstHasConnectTimeout,
+ firstHasReadTimeout
+ );
+ }
+ catch (ConnectTimeoutException ex) {
+ exception = ex;
+ firstHasConnectTimeout = true;
+ elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+ WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
+ WXPayReport.getInstance(config).report(
+ uuid,
+ elapsedTimeMillis,
+ domainInfo.domain,
+ domainInfo.primaryDomain,
+ connectTimeoutMs,
+ readTimeoutMs,
+ firstHasDnsErr,
+ firstHasConnectTimeout,
+ firstHasReadTimeout
+ );
+ }
+ catch (SocketTimeoutException ex) {
+ exception = ex;
+ firstHasReadTimeout = true;
+ elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+ WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
+ WXPayReport.getInstance(config).report(
+ uuid,
+ elapsedTimeMillis,
+ domainInfo.domain,
+ domainInfo.primaryDomain,
+ connectTimeoutMs,
+ readTimeoutMs,
+ firstHasDnsErr,
+ firstHasConnectTimeout,
+ firstHasReadTimeout);
+ }
+ catch (Exception ex) {
+ exception = ex;
+ elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+ WXPayReport.getInstance(config).report(
+ uuid,
+ elapsedTimeMillis,
+ domainInfo.domain,
+ domainInfo.primaryDomain,
+ connectTimeoutMs,
+ readTimeoutMs,
+ firstHasDnsErr,
+ firstHasConnectTimeout,
+ firstHasReadTimeout);
+ }
+ config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
+ throw exception;
+ }
+
+
+ /**
+ * 可重试的,非双向认证的请求
+ * @param urlSuffix
+ * @param uuid
+ * @param data
+ * @return
+ */
+ public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+ return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport);
+ }
+
+ /**
+ * 可重试的,非双向认证的请求
+ * @param urlSuffix
+ * @param uuid
+ * @param data
+ * @param connectTimeoutMs
+ * @param readTimeoutMs
+ * @return
+ */
+ public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
+ return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
+ }
+
+ /**
+ * 可重试的,双向认证的请求
+ * @param urlSuffix
+ * @param uuid
+ * @param data
+ * @return
+ */
+ public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+ return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport);
+ }
+
+ /**
+ * 可重试的,双向认证的请求
+ * @param urlSuffix
+ * @param uuid
+ * @param data
+ * @param connectTimeoutMs
+ * @param readTimeoutMs
+ * @return
+ */
+ public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
+ return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
+ }
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayUtil.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayUtil.java
new file mode 100644
index 0000000..e43d756
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayUtil.java
@@ -0,0 +1,298 @@
+package com.base.springcloud.wxsdk;
+
+import com.base.springcloud.wxsdk.WXPayConstants.SignType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.*;
+
+
+public class WXPayUtil {
+
+ private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ private static final Random RANDOM = new SecureRandom();
+
+ /**
+ * XML格式字符串转换为Map
+ *
+ * @param strXML XML字符串
+ * @return XML数据转换后的Map
+ * @throws Exception
+ */
+ public static Map xmlToMap(String strXML) throws Exception {
+ try {
+ Map data = new HashMap();
+ DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
+ InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
+ org.w3c.dom.Document doc = documentBuilder.parse(stream);
+ doc.getDocumentElement().normalize();
+ NodeList nodeList = doc.getDocumentElement().getChildNodes();
+ for (int idx = 0; idx < nodeList.getLength(); ++idx) {
+ Node node = nodeList.item(idx);
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ org.w3c.dom.Element element = (org.w3c.dom.Element) node;
+ data.put(element.getNodeName(), element.getTextContent());
+ }
+ }
+ try {
+ stream.close();
+ } catch (Exception ex) {
+ // do nothing
+ }
+ return data;
+ } catch (Exception ex) {
+ WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
+ throw ex;
+ }
+
+ }
+
+ /**
+ * 将Map转换为XML格式的字符串
+ *
+ * @param data Map类型数据
+ * @return XML格式的字符串
+ * @throws Exception
+ */
+ public static String mapToXml(Map data) throws Exception {
+ org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
+ org.w3c.dom.Element root = document.createElement("xml");
+ document.appendChild(root);
+ for (String key: data.keySet()) {
+ String value = data.get(key);
+ if (value == null) {
+ value = "";
+ }
+ value = value.trim();
+ org.w3c.dom.Element filed = document.createElement(key);
+ filed.appendChild(document.createTextNode(value));
+ root.appendChild(filed);
+ }
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ DOMSource source = new DOMSource(document);
+ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ StringWriter writer = new StringWriter();
+ StreamResult result = new StreamResult(writer);
+ transformer.transform(source, result);
+ String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
+ try {
+ writer.close();
+ }
+ catch (Exception ex) {
+ }
+ return output;
+ }
+
+
+ /**
+ * 生成带有 sign 的 XML 格式字符串
+ *
+ * @param data Map类型数据
+ * @param key API密钥
+ * @return 含有sign字段的XML
+ */
+ public static String generateSignedXml(final Map data, String key) throws Exception {
+ return generateSignedXml(data, key, SignType.MD5);
+ }
+
+ /**
+ * 生成带有 sign 的 XML 格式字符串
+ *
+ * @param data Map类型数据
+ * @param key API密钥
+ * @param signType 签名类型
+ * @return 含有sign字段的XML
+ */
+ public static String generateSignedXml(final Map data, String key, SignType signType) throws Exception {
+ String sign = generateSignature(data, key, signType);
+ data.put(WXPayConstants.FIELD_SIGN, sign);
+ return mapToXml(data);
+ }
+
+
+ /**
+ * 判断签名是否正确
+ *
+ * @param xmlStr XML格式数据
+ * @param key API密钥
+ * @return 签名是否正确
+ * @throws Exception
+ */
+ public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
+ Map data = xmlToMap(xmlStr);
+ if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+ return false;
+ }
+ String sign = data.get(WXPayConstants.FIELD_SIGN);
+ return generateSignature(data, key).equals(sign);
+ }
+
+ /**
+ * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
+ *
+ * @param data Map类型数据
+ * @param key API密钥
+ * @return 签名是否正确
+ * @throws Exception
+ */
+ public static boolean isSignatureValid(Map data, String key) throws Exception {
+ return isSignatureValid(data, key, SignType.MD5);
+ }
+
+ /**
+ * 判断签名是否正确,必须包含sign字段,否则返回false。
+ *
+ * @param data Map类型数据
+ * @param key API密钥
+ * @param signType 签名方式
+ * @return 签名是否正确
+ * @throws Exception
+ */
+ public static boolean isSignatureValid(Map data, String key, SignType signType) throws Exception {
+ if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
+ return false;
+ }
+ String sign = data.get(WXPayConstants.FIELD_SIGN);
+ return generateSignature(data, key, signType).equals(sign);
+ }
+
+ /**
+ * 生成签名
+ *
+ * @param data 待签名数据
+ * @param key API密钥
+ * @return 签名
+ */
+ public static String generateSignature(final Map data, String key) throws Exception {
+ return generateSignature(data, key, SignType.MD5);
+ }
+
+ /**
+ * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
+ *
+ * @param data 待签名数据
+ * @param key API密钥
+ * @param signType 签名方式
+ * @return 签名
+ */
+ public static String generateSignature(final Map data, String key, SignType signType) throws Exception {
+ Set keySet = data.keySet();
+ String[] keyArray = keySet.toArray(new String[keySet.size()]);
+ Arrays.sort(keyArray);
+ StringBuilder sb = new StringBuilder();
+ for (String k : keyArray) {
+ if (k.equals(WXPayConstants.FIELD_SIGN)) {
+ continue;
+ }
+ if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
+ sb.append(k).append("=").append(data.get(k).trim()).append("&");
+ }
+ sb.append("key=").append(key);
+ //String signDate = sb.toString().substring(0,sb.toString().length() -1);
+ //System.out.println("签名串:" + signDate);
+ if (SignType.MD5.equals(signType)) {
+ return MD5(sb.toString()).toUpperCase();
+ }
+ else if (SignType.HMACSHA256.equals(signType)) {
+ return HMACSHA256(sb.toString(), key);
+ }
+ else {
+ throw new Exception(String.format("Invalid sign_type: %s", signType));
+ }
+ }
+
+
+ /**
+ * 获取随机字符串 Nonce Str
+ *
+ * @return String 随机字符串
+ */
+ public static String generateNonceStr() {
+ char[] nonceChars = new char[32];
+ for (int index = 0; index < nonceChars.length; ++index) {
+ nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
+ }
+ return new String(nonceChars);
+ }
+
+
+ /**
+ * 生成 MD5
+ *
+ * @param data 待处理数据
+ * @return MD5结果
+ */
+ public static String MD5(String data) throws Exception {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] array = md.digest(data.getBytes("UTF-8"));
+ StringBuilder sb = new StringBuilder();
+ for (byte item : array) {
+ sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+ }
+ return sb.toString().toUpperCase();
+ }
+
+ /**
+ * 生成 HMACSHA256
+ * @param data 待处理数据
+ * @param key 密钥
+ * @return 加密结果
+ * @throws Exception
+ */
+ public static String HMACSHA256(String data, String key) throws Exception {
+ System.out.println("签名串:"+data);
+ Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+ SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
+ sha256_HMAC.init(secret_key);
+ byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
+ StringBuilder sb = new StringBuilder();
+ for (byte item : array) {
+ sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+ }
+ return sb.toString().toUpperCase();
+ }
+
+ /**
+ * 日志
+ * @return
+ */
+ public static Logger getLogger() {
+ Logger logger = LoggerFactory.getLogger("wxpay java sdk");
+ return logger;
+ }
+
+ /**
+ * 获取当前时间戳,单位秒
+ * @return
+ */
+ public static long getCurrentTimestamp() {
+ return System.currentTimeMillis()/1000;
+ }
+
+ /**
+ * 获取当前时间戳,单位毫秒
+ * @return
+ */
+ public static long getCurrentTimestampMs() {
+ return System.currentTimeMillis();
+ }
+
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayXmlUtil.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayXmlUtil.java
new file mode 100644
index 0000000..9b3e8dd
--- /dev/null
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WXPayXmlUtil.java
@@ -0,0 +1,30 @@
+package com.base.springcloud.wxsdk;
+
+import org.w3c.dom.Document;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * 2018/7/3
+ */
+public final class WXPayXmlUtil {
+ public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+ documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+ documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+ documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ documentBuilderFactory.setXIncludeAware(false);
+ documentBuilderFactory.setExpandEntityReferences(false);
+
+ return documentBuilderFactory.newDocumentBuilder();
+ }
+
+ public static Document newDocument() throws ParserConfigurationException {
+ return newDocumentBuilder().newDocument();
+ }
+}
diff --git a/payment-base-app/src/main/java/com/base/springcloud/util/WxpayUtils.java b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WxpayUtils.java
similarity index 97%
rename from payment-base-app/src/main/java/com/base/springcloud/util/WxpayUtils.java
rename to payment-base-app/src/main/java/com/base/springcloud/wxsdk/WxpayUtils.java
index 8d476af..9281578 100644
--- a/payment-base-app/src/main/java/com/base/springcloud/util/WxpayUtils.java
+++ b/payment-base-app/src/main/java/com/base/springcloud/wxsdk/WxpayUtils.java
@@ -1,10 +1,11 @@
-package com.base.springcloud.util;
+package com.base.springcloud.wxsdk;
import com.alibaba.fastjson.JSON;
import com.base.springcloud.constant.PaymentConstant;
import com.base.springcloud.entity.CallbackBodyVo;
import com.base.springcloud.entity.WxpayRefundVo;
import com.base.springcloud.entity.WxpayTradeVo;
+import com.base.springcloud.util.StringHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
@@ -100,10 +101,11 @@ public class WxpayUtils {
* @param serialNo
* @return
*/
- public static void refund(WxpayRefundVo tradeVo, String mchid, String privateKey, String certificate, String serialNo) {
+ public static String refund(WxpayRefundVo tradeVo, String mchid, String privateKey, String certificate, String serialNo) {
// ...
String responseBody = response(JSON.toJSONString(tradeVo), REFUND_URL, mchid, privateKey, certificate, serialNo);
log.info("======>>> wxrefund response body: ", responseBody);
+ return responseBody;
}
/**
@@ -174,7 +176,7 @@ public class WxpayUtils {
X509Certificate cert = null;
try {
cert = (X509Certificate) certificateFactory.generateCertificate(base);
- }catch (CertificateException e){
+ } catch (CertificateException e) {
throw e;
}
assert cert != null;