Prechádzať zdrojové kódy

微信支付,支付宝支付

lyhzzz 2 rokov pred
rodič
commit
c3fbe9b80c
57 zmenil súbory, kde vykonal 3986 pridanie a 1 odobranie
  1. 130 1
      pom.xml
  2. 40 0
      src/main/java/com/fdkankan/pay/PayApplication.java
  3. 34 0
      src/main/java/com/fdkankan/pay/common/ResultCode.java
  4. 64 0
      src/main/java/com/fdkankan/pay/common/ResultData.java
  5. 21 0
      src/main/java/com/fdkankan/pay/controller/AliConfigController.java
  6. 23 0
      src/main/java/com/fdkankan/pay/controller/BaseController.java
  7. 33 0
      src/main/java/com/fdkankan/pay/controller/OrderController.java
  8. 31 0
      src/main/java/com/fdkankan/pay/controller/PayController.java
  9. 30 0
      src/main/java/com/fdkankan/pay/controller/TestController.java
  10. 82 0
      src/main/java/com/fdkankan/pay/entity/AliConfig.java
  11. 66 0
      src/main/java/com/fdkankan/pay/entity/MqLog.java
  12. 91 0
      src/main/java/com/fdkankan/pay/entity/Order.java
  13. 54 0
      src/main/java/com/fdkankan/pay/entity/PayServe.java
  14. 69 0
      src/main/java/com/fdkankan/pay/entity/WxConfig.java
  15. 23 0
      src/main/java/com/fdkankan/pay/exception/BusinessException.java
  16. 49 0
      src/main/java/com/fdkankan/pay/exception/GlobalExceptionHandler.java
  17. 98 0
      src/main/java/com/fdkankan/pay/generate/AutoGenerate.java
  18. 18 0
      src/main/java/com/fdkankan/pay/mapper/IAliConfigMapper.java
  19. 18 0
      src/main/java/com/fdkankan/pay/mapper/IMqLogMapper.java
  20. 18 0
      src/main/java/com/fdkankan/pay/mapper/IOrderMapper.java
  21. 18 0
      src/main/java/com/fdkankan/pay/mapper/IPayServeMapper.java
  22. 18 0
      src/main/java/com/fdkankan/pay/mapper/IWxConfigMapper.java
  23. 62 0
      src/main/java/com/fdkankan/pay/mq/consumer/OrderDownConsumer.java
  24. 25 0
      src/main/java/com/fdkankan/pay/response/OpenPayResponse.java
  25. 17 0
      src/main/java/com/fdkankan/pay/service/IAliConfigService.java
  26. 17 0
      src/main/java/com/fdkankan/pay/service/IMqLogService.java
  27. 23 0
      src/main/java/com/fdkankan/pay/service/IOrderService.java
  28. 17 0
      src/main/java/com/fdkankan/pay/service/IPayServeService.java
  29. 17 0
      src/main/java/com/fdkankan/pay/service/IWxConfigService.java
  30. 27 0
      src/main/java/com/fdkankan/pay/service/impl/AliConfigServiceImpl.java
  31. 31 0
      src/main/java/com/fdkankan/pay/service/impl/MqLogServiceImpl.java
  32. 83 0
      src/main/java/com/fdkankan/pay/service/impl/OrderServiceImpl.java
  33. 66 0
      src/main/java/com/fdkankan/pay/service/impl/PayOrderService.java
  34. 27 0
      src/main/java/com/fdkankan/pay/service/impl/PayServeServiceImpl.java
  35. 27 0
      src/main/java/com/fdkankan/pay/service/impl/WxConfigServiceImpl.java
  36. 8 0
      src/main/java/com/fdkankan/pay/util/CacheUtil.java
  37. 41 0
      src/main/java/com/fdkankan/pay/util/OrderSnUtil.java
  38. 66 0
      src/main/java/com/fdkankan/pay/util/UrlUtils.java
  39. 27 0
      src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipayConfig.java
  40. 60 0
      src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipayGoodsDetail.java
  41. 371 0
      src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipayService.java
  42. 113 0
      src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipaymentEx.java
  43. 83 0
      src/main/java/com/fdkankan/pay/util/wx/MD5.java
  44. 172 0
      src/main/java/com/fdkankan/pay/util/wx/RSAUtil.java
  45. 667 0
      src/main/java/com/fdkankan/pay/util/wx/WXPay.java
  46. 45 0
      src/main/java/com/fdkankan/pay/util/wx/WXPayConstants.java
  47. 277 0
      src/main/java/com/fdkankan/pay/util/wx/WXPayUtil.java
  48. 248 0
      src/main/java/com/fdkankan/pay/util/wx/WechatPayService.java
  49. 22 0
      src/main/java/com/fdkankan/pay/util/wx/WxPayBean.java
  50. 32 0
      src/main/resources/bootstrap-test.yml
  51. 8 0
      src/main/resources/bootstrap.yml
  52. 254 0
      src/main/resources/logback-spring.xml
  53. 5 0
      src/main/resources/mapper/pay/AliConfigMapper.xml
  54. 5 0
      src/main/resources/mapper/pay/MqLogMapper.xml
  55. 5 0
      src/main/resources/mapper/pay/OrderMapper.xml
  56. 5 0
      src/main/resources/mapper/pay/PayServeMapper.xml
  57. 5 0
      src/main/resources/mapper/pay/WxConfigMapper.xml

+ 130 - 1
pom.xml

@@ -3,10 +3,139 @@
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <modelVersion>4.0.0</modelVersion>
-
     <groupId>com.4dkankan</groupId>
     <artifactId>4dkankan-ceneter-pay</artifactId>
     <version>1.0.0</version>
+    <packaging>jar</packaging>
+
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.12.RELEASE</version>
+        <relativePath/>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+            <version>2.2.7.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+            <version>2.2.7.RELEASE</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+            <version>1.2.83</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.25</version>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <scope>compile</scope>
+            <version>3.4.3.4</version>
+        </dependency>
+
+        <!--mybatis-plus代码生成器-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <scope>compile</scope>
+            <version>3.5.1</version>
+        </dependency>
+
+        <!--velocity模板-->
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+            <version>2.3</version>
+        </dependency>
+
+
+
+        <dependency>
+            <groupId>com.fdkankan</groupId>
+            <artifactId>4dkankan-utils-rabbitmq</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fdkankan</groupId>
+            <artifactId>4dkankan-utils-redis</artifactId>
+            <version>3.0.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
+
+        <!-- Alipay  -->
+        <dependency>
+            <groupId>com.alipay</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>20170324180803</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alipay</groupId>
+            <artifactId>alipay-trade-sdk</artifactId>
+            <version>20161215</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.paypal.sdk</groupId>
+            <artifactId>rest-api-sdk</artifactId>
+            <version>1.14.0</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
 
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <testFailureIgnore>true</testFailureIgnore>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>8</source>
+                    <target>8</target>
+                </configuration>
+            </plugin>
+        </plugins>
 
+    </build>
 </project>

+ 40 - 0
src/main/java/com/fdkankan/pay/PayApplication.java

@@ -0,0 +1,40 @@
+package com.fdkankan.pay;
+
+import com.fdkankan.pay.util.CacheUtil;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+@SpringBootApplication
+@EnableTransactionManagement//开启事务
+@EnableDiscoveryClient
+@EnableScheduling
+@ComponentScan(basePackages = {"com.fdkankan.*"})
+@MapperScan("com.fdkankan.**.mapper")
+public class PayApplication implements CommandLineRunner {
+
+    @Value("${queue.orderPay.order-down:order-down}")
+    private String orderDownQueue;
+    @Value("${queue.orderPay.down-payResult:down-payResult}")
+    private String orderPayResultQueue;
+    @Value("${main.url}")
+    private String mainUrl;
+
+    public static void main(String[] args) {
+        SpringApplication.run(PayApplication.class, args);
+    }
+
+    @Override
+    public void run(String... args) throws Exception {
+        CacheUtil.orderDownQueue = orderDownQueue;
+        CacheUtil.orderPayResultQueue = orderPayResultQueue;
+        CacheUtil.mainUrl = mainUrl;
+
+    }
+}

+ 34 - 0
src/main/java/com/fdkankan/pay/common/ResultCode.java

@@ -0,0 +1,34 @@
+package com.fdkankan.pay.common;
+
+
+public enum ResultCode {
+    SYSTEM_ERROR(500, "系统异常"),
+    DATA_TOO_LONG(10001, "长度超出限制!"),
+    WX_CONFIG_NOT(10002, "微信config缺失!"),
+    WX_ORDER_PAY_TYPE_ERROR(10003, "微信支付类型错误!"),
+    PARAM_ERROR(10004, "参数缺失!"),
+    ORDER_NOT_EXIST(10005, "订单不存在!"),
+    ALIPAY_ERROR(10006, "获取支付宝扫码链接失败!"),
+
+    ;
+
+    private Integer code;
+    private String message;
+
+   private ResultCode(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public Integer code() {
+        return this.code;
+    }
+
+    public String message() {
+        return this.message;
+    }
+
+    public String formatMessage(Object... args) {
+        return String.format(this.message, args);
+    }
+}

+ 64 - 0
src/main/java/com/fdkankan/pay/common/ResultData.java

@@ -0,0 +1,64 @@
+package com.fdkankan.pay.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Calendar;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ResultData<T> implements Serializable {
+    /**
+     * 状态码
+     */
+    private int code;
+    /**
+     * 响应信息
+     */
+    private String message;
+    /**
+     * 后端返回结果
+     */
+    private T data;
+    /**
+     * 后端返回结果
+     */
+    private Boolean success;
+    /**
+     * 响应时间戳
+     */
+    private long timestamp = Calendar.getInstance().getTimeInMillis();
+
+    public static ResultData ok() {
+        return ok(null);
+    }
+    public static ResultData ok(Object data) {
+        return ok("成功", data);
+    }
+    public static ResultData ok(String msg, Object data) {
+        return base(200, msg, data,true);
+    }
+
+    public static ResultData error(int code, String msg) {
+        return error(code, msg, null); }
+    public static ResultData error(int code, String msg, Object data) {
+        return base(code, msg, data,false);
+    }
+
+    public static ResultData error(ResultCode errorCode) {
+        return error(errorCode.code(), errorCode.message());
+    }
+
+    private static ResultData  base(int code, String msg, Object data,Boolean success) {
+        ResultData rd = new ResultData();
+        rd.setCode(code);
+        rd.setMessage(msg);
+        rd.setData(data);
+        rd.setSuccess(success);
+        return rd;
+    }
+
+}

+ 21 - 0
src/main/java/com/fdkankan/pay/controller/AliConfigController.java

@@ -0,0 +1,21 @@
+package com.fdkankan.pay.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ *  前端控制器
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-14
+ */
+@RestController
+@RequestMapping("/pay/aliConfig")
+public class AliConfigController {
+
+}
+

+ 23 - 0
src/main/java/com/fdkankan/pay/controller/BaseController.java

@@ -0,0 +1,23 @@
+package com.fdkankan.pay.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Component
+public class BaseController {
+    @Autowired
+    protected HttpServletRequest request;
+
+    @Autowired
+    protected HttpServletResponse response;
+
+    protected String getToken(){
+        return request.getHeader("token");
+    }
+
+
+
+}

+ 33 - 0
src/main/java/com/fdkankan/pay/controller/OrderController.java

@@ -0,0 +1,33 @@
+package com.fdkankan.pay.controller;
+
+import com.fdkankan.pay.common.ResultCode;
+import com.fdkankan.pay.common.ResultData;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.exception.BusinessException;
+import com.fdkankan.pay.service.IOrderService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/service/pay/order")
+public class OrderController {
+
+    @Autowired
+    IOrderService orderService;
+
+    @GetMapping("/info/{orderSn}")
+    public ResultData getByOrderSn(@PathVariable String orderSn){
+        Order order = orderService.getByOrderSn(orderSn);
+
+        return ResultData.ok(order);
+    }
+
+    @PostMapping("/orderDown")
+    public ResultData orderDown(@RequestBody Order param){
+        Order order = orderService.createOrder(param);
+        if(order == null){
+            throw new BusinessException(ResultCode.ORDER_NOT_EXIST);
+        }
+        return ResultData.ok(order);
+    }
+}

+ 31 - 0
src/main/java/com/fdkankan/pay/controller/PayController.java

@@ -0,0 +1,31 @@
+package com.fdkankan.pay.controller;
+
+import com.fdkankan.pay.common.ResultData;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.service.impl.PayOrderService;
+import com.fdkankan.pay.util.UrlUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/service/pay")
+public class PayController extends BaseController {
+
+    @Autowired
+    PayOrderService payOrderService;
+
+    @PostMapping(value = "/openPay")
+    public ResultData openPay(@RequestBody Order param) throws Exception {
+        return ResultData.ok(payOrderService.openPay(param, UrlUtils.getIpAddr(request)));
+    }
+
+
+    @GetMapping(value = "/callBack/{orderSn}")
+    public void callback(@PathVariable String orderSn) {
+        payOrderService.callBack(orderSn,request,response);
+    }
+
+
+
+
+}

+ 30 - 0
src/main/java/com/fdkankan/pay/controller/TestController.java

@@ -0,0 +1,30 @@
+package com.fdkankan.pay.controller;
+import java.math.BigDecimal;
+import java.util.Date;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fdkankan.pay.common.ResultData;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.util.CacheUtil;
+import com.fdkankan.rabbitmq.util.RabbitMqProducer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class TestController {
+
+    @Autowired
+    RabbitMqProducer rabbitMqProducer;
+
+    //下单
+    @GetMapping("/orderDown")
+    public ResultData resultData (){
+        Order order = new Order();
+        order.setServeName("test-pay");
+        order.setOrderType("test-pay");
+        order.setOrderMoney(new BigDecimal("0.01"));
+        rabbitMqProducer.sendByWorkQueue(CacheUtil.orderDownQueue,order);
+        return ResultData.ok();
+    }
+}

+ 82 - 0
src/main/java/com/fdkankan/pay/entity/AliConfig.java

@@ -0,0 +1,82 @@
+package com.fdkankan.pay.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-14
+ */
+@Getter
+@Setter
+@TableName("t_ali_config")
+public class AliConfig implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 支付宝配置表
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 支付宝gatewayUrl
+     */
+    @TableField("gateway_url")
+    private String gatewayUrl;
+
+    /**
+     * 商户应用id
+     */
+    @TableField("appid")
+    private String appid;
+
+    /**
+     * RSA私钥,用于对商户请求报文加签
+     */
+    @TableField("private_key")
+    private String privateKey;
+
+    /**
+     * 支付宝RSA公钥,用于验签支付宝应答
+     */
+    @TableField("public_key")
+    private String publicKey;
+
+    /**
+     * 回调地址
+     */
+    @TableField("call_back_url")
+    private String callBackUrl;
+
+    @TableField("return_url")
+    private String returnUrl;
+
+    @TableField("serve_id")
+    private Integer serveId;
+
+    @TableField("rec_status")
+    @TableLogic(value = "A",delval = "I")
+    private String recStatus;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+
+    @TableField("sign_type")
+    private String signType = "RSA2";
+}

+ 66 - 0
src/main/java/com/fdkankan/pay/entity/MqLog.java

@@ -0,0 +1,66 @@
+package com.fdkankan.pay.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+@Getter
+@Setter
+@TableName("t_mq_log")
+public class MqLog implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * mq记录
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("msg_id")
+    private String msgId;
+
+    /**
+     * mq消息 
+     */
+    @TableField("msg")
+    private String msg;
+
+    /**
+     * 0消费,1生产
+     */
+    @TableField("type")
+    private Integer type;
+    /**
+     * 0消费,1生产
+     */
+    @TableField("status")
+    private Integer status;
+
+    /**
+     * 队列名称
+     */
+    @TableField("queue")
+    private String queue;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+
+
+}

+ 91 - 0
src/main/java/com/fdkankan/pay/entity/Order.java

@@ -0,0 +1,91 @@
+package com.fdkankan.pay.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+@Getter
+@Setter
+@TableName("t_order")
+public class Order implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 订单表
+     */
+    @TableId(value = "order_id", type = IdType.AUTO)
+    private Integer orderId;
+
+    /**
+     * 服务名称
+     */
+    @TableField("serve_name")
+    private String serveName;
+    /**
+     * 服务名称
+     */
+    @TableField("serve_id")
+    private Integer serveId;
+
+    /**
+     * 订单编号
+     */
+    @TableField("order_sn")
+    private String orderSn;
+
+    /**
+     * 订单类型
+     */
+    @TableField("order_type")
+    private String orderType;
+
+    /**
+     * 订单金额
+     */
+    @TableField("order_money")
+    private BigDecimal orderMoney;
+
+    /**
+     * 0微信h5,1微信jsapi, 2支付宝,3paypal
+     */
+    @TableField("pay_type")
+    private Integer payType;
+
+    /**
+     * 支付状态,0未支付,1支付成功,2支付失败
+     */
+    @TableField("pay_status")
+    private Integer payStatus;
+
+    @TableField("open_id")
+    private String openId;
+
+    @TableField("trade_no")
+    private String tradeNo;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+
+    @TableField("pay_time")
+    private Date payTime;
+
+
+}

+ 54 - 0
src/main/java/com/fdkankan/pay/entity/PayServe.java

@@ -0,0 +1,54 @@
+package com.fdkankan.pay.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+@Getter
+@Setter
+@TableName("t_pay_serve")
+public class PayServe implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 接入服务
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 服务名称
+     */
+    @TableField("serve_name")
+    private String serveName;
+
+    @TableField("remark")
+    private String remark;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+
+    @TableField("rec_status")
+    @TableLogic(value = "A",delval = "I")
+    private String recStatus;
+
+
+}

+ 69 - 0
src/main/java/com/fdkankan/pay/entity/WxConfig.java

@@ -0,0 +1,69 @@
+package com.fdkankan.pay.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+/**
+ * <p>
+ * 
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+@Getter
+@Setter
+@ToString
+@TableName("t_wx_config")
+public class WxConfig implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 微信配置表
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("appid")
+    private String appid;
+
+    @TableField("secret")
+    private String secret;
+
+    @TableField("mch_id")
+    private String mchId;
+
+    @TableField("mch_key")
+    private String mchKey;
+
+    @TableField("call_back_url")
+    private String callBackUrl;
+
+    @TableField("remark")
+    private String remark;
+
+    @TableField("serve_id")
+    private Integer serveId;
+
+    @TableField("rec_status")
+    @TableLogic(value = "A",delval = "I")
+    private String recStatus;
+
+    @TableField("create_time")
+    private Date createTime;
+
+    @TableField("update_time")
+    private Date updateTime;
+
+
+
+}

+ 23 - 0
src/main/java/com/fdkankan/pay/exception/BusinessException.java

@@ -0,0 +1,23 @@
+package com.fdkankan.pay.exception;
+
+import com.fdkankan.pay.common.ResultCode;
+import lombok.Data;
+
+/**
+ * 自定义业务异常类
+ */
+@Data
+public class BusinessException extends RuntimeException {
+    private int code;
+    private String message;
+
+    public BusinessException(Integer code,String msg){
+        this.code = code;
+        this.message = msg;
+    }
+
+    public BusinessException(ResultCode errorCode) {
+        this.code = errorCode.code();
+        this.message = errorCode.message();
+    }
+}

+ 49 - 0
src/main/java/com/fdkankan/pay/exception/GlobalExceptionHandler.java

@@ -0,0 +1,49 @@
+package com.fdkankan.pay.exception;
+
+import com.fdkankan.pay.common.ResultCode;
+import com.fdkankan.pay.common.ResultData;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+/**
+ * 全局异常处理器
+ */
+@RestControllerAdvice
+@Slf4j
+public class GlobalExceptionHandler {
+
+    /**
+     * 处理未知异常
+     */
+    @ResponseBody
+    @ExceptionHandler(value = Exception.class)
+    public ResultData exceptionHandler(Exception e) throws Exception {
+        log.error("服务错误:", e);
+        return ResultData.error( 500, e.getCause().getMessage());
+    }
+
+    /**
+     * 处理业务异常
+     */
+    @ResponseBody
+    @ExceptionHandler(value = BusinessException.class)
+    public ResultData businessExceptionHandler(BusinessException e) {
+        log.error("业务异常code:{},message:{}", e.getCode(), e.getMessage());
+        return ResultData.error(e.getCode(), e.getMessage());
+    }
+    /**
+     * 处理业务异常
+     */
+    @ResponseBody
+    @ExceptionHandler(value = DataIntegrityViolationException.class)
+    public ResultData DataIntegrityViolationExceptionHandler(DataIntegrityViolationException e) {
+        log.error("mysql服务错误:", e);
+        if(e.getCause().getMessage().contains("Data too long")){
+            return ResultData.error(ResultCode.DATA_TOO_LONG);
+        }
+        return ResultData.error( 500, e.getCause().getMessage());
+    }
+}

+ 98 - 0
src/main/java/com/fdkankan/pay/generate/AutoGenerate.java

@@ -0,0 +1,98 @@
+package com.fdkankan.pay.generate;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.generator.FastAutoGenerator;
+import com.baomidou.mybatisplus.generator.config.OutputFile;
+import com.baomidou.mybatisplus.generator.config.rules.DateType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class AutoGenerate {
+
+
+    public static void main(String[] args) {
+
+        String path =System.getProperty("user.dir");
+
+        generate(path,"pay", getTables(new String[]{
+                "t_ali_config"
+        }));
+
+//        generate(path,"goods", getTables(new String[]{
+//                        "t_camera","t_camera_detail","t_camera_out","t_camera_space","t_camera_version",
+//                        "t_company","t_goods","t_goods_sku","t_cart","t_goods_spec",
+//                        "t_goods_spec_value","t_goods_spu_spec","t_sn_code"
+//        }));
+//
+//        generate(path,"order", getTables(new String[]{
+//                        "t_increment_order","t_invoice","t_order","t_order_item",
+//                        "t_pre_sale","t_space_sdk","t_trade_log","t_commerce_order","t_download_order","t_expansion_order"
+//        }));
+//        generate(path,"order", getTables(new String[]{
+//                        "t_virtual_order"
+//        }));
+//
+//        generate(path,"user", getTables(new String[]{
+//                        "t_user","t_user_increment","t_manager","t_province","t_increment_type","t_intercom_message","t_receiver_info"
+//        }));
+    }
+
+    public static List<String> getTables(String [] tableNames){
+        return new ArrayList<>(Arrays.asList(tableNames));
+    }
+
+
+    public static void  generate(String path,String moduleName,  List<String> tables){
+        FastAutoGenerator.create("jdbc:mysql://120.24.144.164:3306/4dkankan_v4_pay",
+                "root","4Dage@4Dage#@168")
+                .globalConfig(builder -> {
+                    builder.author("")               //作者
+                            .outputDir(path+"\\src\\main\\java")    //输出路径(写到java目录)
+                            //.enableSwagger()           //开启swagger
+                            .commentDate("yyyy-MM-dd")
+                            .dateType(DateType.ONLY_DATE)
+                            .fileOverride();            //开启覆盖之前生成的文件
+
+                })
+                .packageConfig(builder -> {
+                    builder.parent("com.fdkankan")
+                            .moduleName(moduleName)
+                            .entity("entity")
+                            .service("service")
+                            .serviceImpl("service.impl")
+                            .controller("controller")
+                            .mapper("mapper")
+                            .xml("test.mapper")
+                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml,path+"\\src\\main\\resources\\mapper\\"+moduleName));
+                })
+                .strategyConfig(builder -> {
+                    builder.addInclude(tables)
+                            .addTablePrefix("t_")
+
+                            .serviceBuilder()
+                            .formatServiceFileName("I%sService")
+                            .formatServiceImplFileName("%sServiceImpl")
+
+                            .entityBuilder()
+                            .enableLombok()
+                            .logicDeleteColumnName("rec_status")
+                            .enableTableFieldAnnotation()
+                            //.superClass(BaseEntity.class)
+
+                            .controllerBuilder()
+                            .formatFileName("%sController")
+                            .enableRestStyle()
+
+                            .mapperBuilder()
+                            .superClass(BaseMapper.class)
+                            .formatMapperFileName("I%sMapper")
+                            .enableMapperAnnotation()
+                            .formatXmlFileName("%sMapper");
+                })
+                // .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
+                .execute();
+    }
+}

+ 18 - 0
src/main/java/com/fdkankan/pay/mapper/IAliConfigMapper.java

@@ -0,0 +1,18 @@
+package com.fdkankan.pay.mapper;
+
+import com.fdkankan.pay.entity.AliConfig;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-14
+ */
+@Mapper
+public interface IAliConfigMapper extends BaseMapper<AliConfig> {
+
+}

+ 18 - 0
src/main/java/com/fdkankan/pay/mapper/IMqLogMapper.java

@@ -0,0 +1,18 @@
+package com.fdkankan.pay.mapper;
+
+import com.fdkankan.pay.entity.MqLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+@Mapper
+public interface IMqLogMapper extends BaseMapper<MqLog> {
+
+}

+ 18 - 0
src/main/java/com/fdkankan/pay/mapper/IOrderMapper.java

@@ -0,0 +1,18 @@
+package com.fdkankan.pay.mapper;
+
+import com.fdkankan.pay.entity.Order;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+@Mapper
+public interface IOrderMapper extends BaseMapper<Order> {
+
+}

+ 18 - 0
src/main/java/com/fdkankan/pay/mapper/IPayServeMapper.java

@@ -0,0 +1,18 @@
+package com.fdkankan.pay.mapper;
+
+import com.fdkankan.pay.entity.PayServe;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+@Mapper
+public interface IPayServeMapper extends BaseMapper<PayServe> {
+
+}

+ 18 - 0
src/main/java/com/fdkankan/pay/mapper/IWxConfigMapper.java

@@ -0,0 +1,18 @@
+package com.fdkankan.pay.mapper;
+
+import com.fdkankan.pay.entity.WxConfig;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ *  Mapper 接口
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+@Mapper
+public interface IWxConfigMapper extends BaseMapper<WxConfig> {
+
+}

+ 62 - 0
src/main/java/com/fdkankan/pay/mq/consumer/OrderDownConsumer.java

@@ -0,0 +1,62 @@
+package com.fdkankan.pay.mq.consumer;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fdkankan.pay.entity.MqLog;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.service.IMqLogService;
+import com.fdkankan.pay.service.IOrderService;
+import com.fdkankan.rabbitmq.util.RabbitMqProducer;
+import com.rabbitmq.client.Channel;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.amqp.core.ExchangeTypes;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.annotation.Exchange;
+import org.springframework.amqp.rabbit.annotation.Queue;
+import org.springframework.amqp.rabbit.annotation.QueueBinding;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import sun.rmi.runtime.Log;
+
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+@Component
+public class OrderDownConsumer {
+
+    @Value("${queue.orderPay.order-down:order-down}")
+    private String orderDownQueue;
+    @Value("${queue.orderPay.down-payResult:down-payResult}")
+    private String orderPayResultQueue;
+
+    @Autowired
+    IMqLogService mqLogService;
+    @Autowired
+    IOrderService orderService;
+    @Autowired
+    RabbitMqProducer rabbitMqProducer;
+
+
+    @RabbitListener(
+            queuesToDeclare = @Queue("${queue.orderPay.order-down:order-down}")
+    )
+    public void consumerQueue(Channel channel, Message message) throws Exception {
+        String messageId = message.getMessageProperties().getMessageId();
+        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
+        MqLog mqLog = mqLogService.saveLog(orderDownQueue, messageId, msg);
+
+        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
+
+        Order order = orderService.createOrderByMqMsg(msg);
+        if(StringUtils.isBlank(order.getOrderSn() )){
+            mqLog.setStatus(2);
+        }else {
+            mqLog.setStatus(1);
+            rabbitMqProducer.sendByWorkQueue(orderPayResultQueue, JSONObject.toJSONString(order));
+        }
+        mqLog.setUpdateTime(null);
+        mqLogService.updateById(mqLog);
+    }
+}

+ 25 - 0
src/main/java/com/fdkankan/pay/response/OpenPayResponse.java

@@ -0,0 +1,25 @@
+package com.fdkankan.pay.response;
+
+import lombok.Data;
+
+@Data
+public class OpenPayResponse {
+    private String orderSn;
+    private Integer payType;
+
+    private String h5Url;
+
+    private String nonceStr;
+    private String signType;
+    private String paySign;
+    private String mchId;
+    private String prepayId;
+    private String appid;
+    private String deviceInfo;
+    private String tradeType;
+    private String timeStamp;
+
+    private String qrCodeUrl;
+
+
+}

+ 17 - 0
src/main/java/com/fdkankan/pay/service/IAliConfigService.java

@@ -0,0 +1,17 @@
+package com.fdkankan.pay.service;
+
+import com.fdkankan.pay.entity.AliConfig;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-14
+ */
+public interface IAliConfigService extends IService<AliConfig> {
+
+    AliConfig getByServeId(Integer serveId);
+}

+ 17 - 0
src/main/java/com/fdkankan/pay/service/IMqLogService.java

@@ -0,0 +1,17 @@
+package com.fdkankan.pay.service;
+
+import com.fdkankan.pay.entity.MqLog;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+public interface IMqLogService extends IService<MqLog> {
+
+    MqLog saveLog(String orderDownQueue, String messageId, String msg);
+}

+ 23 - 0
src/main/java/com/fdkankan/pay/service/IOrderService.java

@@ -0,0 +1,23 @@
+package com.fdkankan.pay.service;
+
+import com.fdkankan.pay.entity.Order;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+public interface IOrderService extends IService<Order> {
+
+    Order createOrderByMqMsg(String msg);
+
+    Order getByOrderSn(String orderSn);
+
+    Order createOrder(Order order);
+
+    void payResult(Integer orderId, Boolean payFlag, String trade_no,String openId);
+}

+ 17 - 0
src/main/java/com/fdkankan/pay/service/IPayServeService.java

@@ -0,0 +1,17 @@
+package com.fdkankan.pay.service;
+
+import com.fdkankan.pay.entity.PayServe;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+public interface IPayServeService extends IService<PayServe> {
+
+    PayServe getByServeName(String serveName);
+}

+ 17 - 0
src/main/java/com/fdkankan/pay/service/IWxConfigService.java

@@ -0,0 +1,17 @@
+package com.fdkankan.pay.service;
+
+import com.fdkankan.pay.entity.WxConfig;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ *  服务类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+public interface IWxConfigService extends IService<WxConfig> {
+
+    WxConfig getByServeId(Integer serveId);
+}

+ 27 - 0
src/main/java/com/fdkankan/pay/service/impl/AliConfigServiceImpl.java

@@ -0,0 +1,27 @@
+package com.fdkankan.pay.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fdkankan.pay.entity.AliConfig;
+import com.fdkankan.pay.mapper.IAliConfigMapper;
+import com.fdkankan.pay.service.IAliConfigService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-14
+ */
+@Service
+public class AliConfigServiceImpl extends ServiceImpl<IAliConfigMapper, AliConfig> implements IAliConfigService {
+
+    @Override
+    public AliConfig getByServeId(Integer serveId) {
+        LambdaQueryWrapper<AliConfig> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(AliConfig::getServeId,serveId);
+        return this.getOne(wrapper);
+    }
+}

+ 31 - 0
src/main/java/com/fdkankan/pay/service/impl/MqLogServiceImpl.java

@@ -0,0 +1,31 @@
+package com.fdkankan.pay.service.impl;
+import java.util.Date;
+
+import com.fdkankan.pay.entity.MqLog;
+import com.fdkankan.pay.mapper.IMqLogMapper;
+import com.fdkankan.pay.service.IMqLogService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+@Service
+public class MqLogServiceImpl extends ServiceImpl<IMqLogMapper, MqLog> implements IMqLogService {
+
+    @Override
+    public MqLog saveLog(String orderDownQueue, String messageId, String msg) {
+        MqLog mqLog = new MqLog();
+        mqLog.setMsgId(messageId);
+        mqLog.setMsg(msg);
+        mqLog.setType(0);
+        mqLog.setQueue(orderDownQueue);
+        this.save(mqLog);
+        return mqLog;
+    }
+}

+ 83 - 0
src/main/java/com/fdkankan/pay/service/impl/OrderServiceImpl.java

@@ -0,0 +1,83 @@
+package com.fdkankan.pay.service.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.entity.PayServe;
+import com.fdkankan.pay.mapper.IOrderMapper;
+import com.fdkankan.pay.service.IOrderService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fdkankan.pay.service.IPayServeService;
+import com.fdkankan.pay.util.CacheUtil;
+import com.fdkankan.pay.util.OrderSnUtil;
+import com.fdkankan.rabbitmq.util.RabbitMqProducer;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-12
+ */
+@Service
+public class OrderServiceImpl extends ServiceImpl<IOrderMapper, Order> implements IOrderService {
+
+    @Autowired
+    OrderSnUtil orderSnUtil;
+    @Autowired
+    IPayServeService payServeService;
+    @Autowired
+    RabbitMqProducer rabbitMqProducer;
+
+    @Override
+    public Order createOrderByMqMsg(String msg) {
+        msg = StringEscapeUtils.unescapeJava(msg);
+        Order order = JSONObject.parseObject(msg, Order.class);
+        return this.createOrder(order);
+    }
+
+    @Override
+    public Order createOrder(Order order) {
+        if(StringUtils.isBlank(order.getServeName())){
+            return null;
+        }
+        PayServe payServe = payServeService.getByServeName(order.getServeName());
+        if(payServe == null ){
+            return null;
+        }
+        order.setOrderId(null);
+        order.setServeId(payServe.getId());
+        String orderSn = orderSnUtil.getOrderSn();
+        order.setOrderSn(orderSn);
+        this.save(order);
+        return order;
+    }
+
+    @Override
+    public Order getByOrderSn(String orderSn) {
+        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(Order::getOrderSn,orderSn);
+        return this.getOne(wrapper);
+    }
+
+    @Override
+    public void payResult(Integer orderId, Boolean payFlag, String trade_no,String openId) {
+        LambdaUpdateWrapper<Order> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.eq(Order::getOrderId,orderId);
+        wrapper.set(Order::getPayStatus,payFlag ? 1:2);
+        wrapper.set(Order::getTradeNo,trade_no);
+        wrapper.set(Order::getOpenId,openId);
+        wrapper.set(Order::getPayTime,new Date());
+        this.update(wrapper);
+        Order order = this.getById(orderId);
+        rabbitMqProducer.sendByWorkQueue(CacheUtil.orderPayResultQueue,order);
+    }
+}

+ 66 - 0
src/main/java/com/fdkankan/pay/service/impl/PayOrderService.java

@@ -0,0 +1,66 @@
+package com.fdkankan.pay.service.impl;
+
+import com.fdkankan.pay.common.ResultCode;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.exception.BusinessException;
+import com.fdkankan.pay.service.IOrderService;
+import com.fdkankan.pay.util.alipay.sdk.AlipayService;
+import com.fdkankan.pay.util.wx.WechatPayService;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@Service
+public class PayOrderService {
+
+    @Autowired
+    IOrderService orderService;
+    @Autowired
+    WechatPayService wechatPayService ;
+    @Autowired
+    AlipayService alipayService;
+
+
+    public Object openPay(Order param, String ipAddr) throws Exception {
+        if(StringUtils.isBlank(param.getOrderSn()) || param.getPayType() == null){
+            throw new BusinessException(ResultCode.PARAM_ERROR);
+        }
+        //jsApi微信支付,需要公众号openId
+        if(param.getPayType() == 1 && StringUtils.isBlank(param.getOpenId())){
+            throw new BusinessException(ResultCode.PARAM_ERROR);
+        }
+        Order order = orderService.getByOrderSn(param.getOrderSn());
+        if(order == null){
+            throw new BusinessException(ResultCode.ORDER_NOT_EXIST);
+        }
+        order.setPayType(param.getPayType());
+        //微信支付
+        if(param.getPayType() == 0 || param.getPayType() == 1 || param.getPayType() == 2){
+            order.setOpenId(param.getOpenId());
+            return wechatPayService.openPay(order,ipAddr);
+        }
+        //支付宝支付
+        if(param.getPayType() == 3 || param.getPayType() == 4 ){
+            return alipayService.openPay(order,ipAddr);
+        }
+        return null;
+    }
+
+    public void callBack(String orderSn, HttpServletRequest request, HttpServletResponse response) {
+        Order order = orderService.getByOrderSn(orderSn);
+        if(order == null){
+            throw new BusinessException(ResultCode.ORDER_NOT_EXIST);
+        }
+        //微信支付回调
+        if(order.getPayType() == 0 || order.getPayType() == 1 || order.getPayType() == 2){
+             wechatPayService.callBack(request,response,order);
+        }
+        //支付宝回调
+        if(order.getPayType() == 3 || order.getPayType() == 4 ){
+            alipayService.callBack(request,response,order);
+        }
+    }
+}

+ 27 - 0
src/main/java/com/fdkankan/pay/service/impl/PayServeServiceImpl.java

@@ -0,0 +1,27 @@
+package com.fdkankan.pay.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fdkankan.pay.entity.PayServe;
+import com.fdkankan.pay.mapper.IPayServeMapper;
+import com.fdkankan.pay.service.IPayServeService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+@Service
+public class PayServeServiceImpl extends ServiceImpl<IPayServeMapper, PayServe> implements IPayServeService {
+
+    @Override
+    public PayServe getByServeName(String serveName) {
+        LambdaQueryWrapper<PayServe> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(PayServe::getServeName,serveName);
+        return this.getOne(wrapper);
+    }
+}

+ 27 - 0
src/main/java/com/fdkankan/pay/service/impl/WxConfigServiceImpl.java

@@ -0,0 +1,27 @@
+package com.fdkankan.pay.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fdkankan.pay.entity.WxConfig;
+import com.fdkankan.pay.mapper.IWxConfigMapper;
+import com.fdkankan.pay.service.IWxConfigService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ *  服务实现类
+ * </p>
+ *
+ * @author 
+ * @since 2023-04-13
+ */
+@Service
+public class WxConfigServiceImpl extends ServiceImpl<IWxConfigMapper, WxConfig> implements IWxConfigService {
+
+    @Override
+    public WxConfig getByServeId(Integer serveId) {
+        LambdaQueryWrapper<WxConfig> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(WxConfig::getServeId,serveId);
+        return this.getOne(wrapper);
+    }
+}

+ 8 - 0
src/main/java/com/fdkankan/pay/util/CacheUtil.java

@@ -0,0 +1,8 @@
+package com.fdkankan.pay.util;
+
+public class CacheUtil {
+
+    public static String orderDownQueue;
+    public static String orderPayResultQueue;
+    public static String mainUrl;
+}

+ 41 - 0
src/main/java/com/fdkankan/pay/util/OrderSnUtil.java

@@ -0,0 +1,41 @@
+package com.fdkankan.pay.util;
+
+import com.fdkankan.redis.util.RedisUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+@Component
+@Slf4j
+public class OrderSnUtil {
+
+    public static String orderSnFmt = "yyyyMMddHHmmssSSS";
+
+    @Autowired
+    RedisUtil redisUtil;
+
+    public static String rediskey = "4dkankan:order-pay:orderSn:";
+
+    public String getOrderSn() {
+        String orderSn = null;
+        String lockKey = rediskey + "lock:";
+        try {
+            while (redisUtil.hasKey(lockKey)){
+                Thread.sleep(1L);
+            }
+            redisUtil.set(lockKey,"1");
+            Thread.sleep(1L);
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(orderSnFmt);
+            Date date = new Date();
+             orderSn = simpleDateFormat.format(date);
+            redisUtil.del(lockKey);
+        }catch (Exception e){
+            log.error("生产订单编号失败getOrderSn",e);
+        }
+        return orderSn;
+    }
+
+}

+ 66 - 0
src/main/java/com/fdkankan/pay/util/UrlUtils.java

@@ -0,0 +1,66 @@
+package com.fdkankan.pay.util;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class UrlUtils {
+
+    //沙箱链接
+//    public static final String TOKEN_URL = "https://api.sandbox.paypal.com/v1/oauth2/token";
+    public static final String TOKEN_URL = "https://api.paypal.com/v1/oauth2/token";
+    public static final String PAYMENT_DETAIL = "https://api.sandbox.paypal.com/v1/payments/payment/";
+
+    public static String getBaseURl(HttpServletRequest request) {
+        String scheme = request.getScheme();
+        String serverName = request.getServerName();
+        int serverPort = request.getServerPort();
+        String contextPath = request.getContextPath();
+        StringBuffer url =  new StringBuffer();
+        url.append(scheme).append("://").append(serverName);
+        if ((serverPort != 80) && (serverPort != 443)) {
+            url.append(":").append(serverPort);
+        }
+        url.append(contextPath);
+        if(url.toString().endsWith("/")){
+            url.append("/");
+        }
+        return url.toString();
+    }
+
+    /**
+     * 获取用户实际ip
+     * @param request
+     * @return
+     */
+    public static String getIpAddr(HttpServletRequest request){
+        String ipAddress = request.getHeader("x-forwarded-for");
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("Proxy-Client-IP");
+        }
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+            ipAddress = request.getRemoteAddr();
+            if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
+                //根据网卡取本机配置的IP
+                InetAddress inet=null;
+                try {
+                    inet = InetAddress.getLocalHost();
+                } catch (UnknownHostException e) {
+                    e.printStackTrace();
+                }
+                ipAddress= inet.getHostAddress();
+            }
+        }
+        //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
+            if(ipAddress.indexOf(",")>0){
+                ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
+            }
+        }
+        return ipAddress;
+    }
+
+}

+ 27 - 0
src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipayConfig.java

@@ -0,0 +1,27 @@
+package com.fdkankan.pay.util.alipay.sdk;
+
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.alipay.demo.trade.service.impl.AlipayTradeServiceImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+@Component
+public abstract class AlipayConfig {
+
+    /** 签名类型 */
+    public static String signType = "RSA2";
+    /** 格式 */
+    public static String formate = "json";
+    /** 编码 */
+    public static String charset = "UTF-8";
+    /** 最大查询次数 */
+    public static Integer maxQueryRetry = 5;
+    /** 查询间隔(毫秒) */
+    public static Long queryDuration = 5000L;
+    /** 最大撤销次数 */
+    public static Integer maxCancelRetry = 3;
+    /** 撤销间隔(毫秒) */
+    public static Long cancelDuration = 3000L;
+
+
+}

+ 60 - 0
src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipayGoodsDetail.java

@@ -0,0 +1,60 @@
+package com.fdkankan.pay.util.alipay.sdk;
+
+import java.io.Serializable;
+
+public class AlipayGoodsDetail implements Serializable {
+
+    private static final long serialVersionUID = -8875896136641533825L;
+
+    public AlipayGoodsDetail() {
+    }
+
+    public static AlipayGoodsDetail newInstance(String goodsId, String goodsName, long price, int quantity) {
+        AlipayGoodsDetail info = new AlipayGoodsDetail();
+        info.setGoodsId(goodsId);
+        info.setGoodsName(goodsName);
+        info.setPrice(price);
+        info.setQuantity(quantity);
+        return info;
+    }
+    // 必填,商品的编号,length=32
+    private String goodsId;
+    // 必填,商品的名称,length=256
+    private String goodsName;
+    // 必填,商品数量
+    private int quantity;
+    // 必填,商品单价,单位为元
+    private long price;
+
+    public String getGoodsId() {
+        return goodsId;
+    }
+
+    public void setGoodsId(String goodsId) {
+        this.goodsId = goodsId;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+
+    public int getQuantity() {
+        return quantity;
+    }
+
+    public void setQuantity(int quantity) {
+        this.quantity = quantity;
+    }
+
+    public long getPrice() {
+        return price;
+    }
+
+    public void setPrice(long price) {
+        this.price = price;
+    }
+}

+ 371 - 0
src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipayService.java

@@ -0,0 +1,371 @@
+package com.fdkankan.pay.util.alipay.sdk;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.AlipayClient;
+import com.alipay.api.AlipayResponse;
+import com.alipay.api.DefaultAlipayClient;
+import com.alipay.api.domain.AlipayTradeWapPayModel;
+import com.alipay.api.domain.TradeFundBill;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradeWapPayRequest;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
+import com.alipay.api.response.AlipayTradeQueryResponse;
+import com.alipay.demo.trade.model.GoodsDetail;
+import com.alipay.demo.trade.model.builder.AlipayTradePrecreateRequestBuilder;
+import com.alipay.demo.trade.model.builder.AlipayTradeQueryRequestBuilder;
+import com.alipay.demo.trade.model.result.AlipayF2FPrecreateResult;
+import com.alipay.demo.trade.model.result.AlipayF2FQueryResult;
+import com.alipay.demo.trade.service.AlipayTradeService;
+import com.alipay.demo.trade.service.impl.AlipayTradeServiceImpl;
+import com.alipay.demo.trade.utils.Utils;
+import com.fdkankan.pay.common.ResultCode;
+import com.fdkankan.pay.entity.AliConfig;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.entity.WxConfig;
+import com.fdkankan.pay.exception.BusinessException;
+import com.fdkankan.pay.response.OpenPayResponse;
+import com.fdkankan.pay.service.IAliConfigService;
+import com.fdkankan.pay.service.IOrderService;
+import com.fdkankan.pay.util.CacheUtil;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.imageio.ImageIO;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.*;
+
+@Log4j2
+@Service
+public class AlipayService {
+
+    @Autowired
+    IAliConfigService aliConfigService;
+    @Autowired
+    IOrderService orderService;
+
+    public Object openPay(Order param, String ipAddr) throws Exception {
+
+        String orderSn = param.getOrderSn();
+        BigDecimal amount = param.getOrderMoney().multiply(new BigDecimal(100));
+        String subject = param.getOrderType();
+        AliConfig aliConfig = aliConfigService.getByServeId(param.getServeId());
+        if(aliConfig == null){
+            throw new BusinessException(ResultCode.WX_CONFIG_NOT);
+        }
+        aliConfig.setCallBackUrl( CacheUtil.mainUrl + aliConfig.getCallBackUrl() + "/" + orderSn);
+        String body = subject;
+
+        List<AlipayGoodsDetail> goodsDetailList = new ArrayList<>();
+        AlipayGoodsDetail goodsDetail = AlipayGoodsDetail.newInstance(orderSn, subject, amount.intValue(), 1);
+        goodsDetailList.add(goodsDetail);
+        if(param.getPayType() == 3){
+            return this.aliPayScanPay(orderSn, subject, body, amount, goodsDetailList,aliConfig);
+        }
+        if(param.getPayType() == 4){
+            return this.aliPayH5Pay(orderSn, subject, body, amount,aliConfig);
+        }
+        orderService.updateById(param);
+        throw new BusinessException(ResultCode.WX_ORDER_PAY_TYPE_ERROR);
+
+    }
+
+
+    public OpenPayResponse aliPayScanPay(String orderSn, String subject, String body, BigDecimal totalFee, List<AlipayGoodsDetail> goodsDetailList, AliConfig aliConfig) throws Exception{
+        AlipaymentEx alipaymentEx = new AlipaymentEx();
+        alipaymentEx.setOutTradeNo(orderSn);
+        alipaymentEx.setSubject(subject);
+        alipaymentEx.setBody(body);
+        alipaymentEx.setTotalAmount(totalFee);
+        alipaymentEx.setStoreId("test_store_id");
+        alipaymentEx.setOperatorId("test_operator_id");
+        alipaymentEx.setTimeoutExpress("120m");
+        alipaymentEx.setGoodsDetailList(goodsDetailList);
+        Map<String, String> alipayMap = this.tradePrecreate(alipaymentEx,  aliConfig);
+        if ("0".equals(alipayMap.get("code"))){
+        }else{
+            throw new BusinessException(ResultCode.ALIPAY_ERROR);
+        }
+
+        OpenPayResponse openPayResponse = new OpenPayResponse();
+        openPayResponse.setQrCodeUrl(alipayMap.get("qr_code"));
+        openPayResponse.setOrderSn(orderSn);
+        openPayResponse.setPayType(2);
+        return openPayResponse;
+    }
+
+    public JSONObject aliPayH5Pay(String orderSn, String subject, String body, BigDecimal totalFee,AliConfig aliConfig) {
+        // 超时时间 可空
+        String timeoutExpress = "2m";
+        // 销售产品码 必填
+        String productCode = "QUICK_WAP_WAY";
+        /**********************/
+        // SDK 公共请求类,包含公共请求参数,以及封装了签名与验签,开发者无需关注签名与验签
+        //调用RSA签名方式,这个不用你管,只要你配置文件是正确的
+        AlipayClient client = new DefaultAlipayClient(aliConfig.getGatewayUrl(),
+                aliConfig.getAppid(), aliConfig.getPrivateKey(), AlipayConfig.formate,
+                AlipayConfig.charset, aliConfig.getPublicKey(),AlipayConfig.signType);
+        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();
+        // 封装请求支付信息
+        AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
+        //带!的都是你需要传递的参数 其他的是支付宝必须的
+        //!本地生成的订单编号
+        model.setOutTradeNo(orderSn);
+        //!商品名称
+        model.setSubject(subject);
+        //!商品价格
+        model.setTotalAmount(String.valueOf(totalFee));
+        //!商品介绍
+        model.setBody(body);
+        //超时时间
+        model.setTimeoutExpress(timeoutExpress);
+        //产品销售码
+        model.setProductCode(productCode);
+        //将参数传入到 BizModel中
+        alipayRequest.setBizModel(model);
+        //异步回调地址
+        alipayRequest.setNotifyUrl(aliConfig.getCallBackUrl());
+        //同步回调地址
+        alipayRequest.setReturnUrl(aliConfig.getReturnUrl());
+
+        String result = "";
+        try {
+            // 调用SDK生成表单
+            //get方式生成表单 主要解决在微信中对支付宝屏蔽问题,如果不是在微信中可post提交,将form的注释解开,result注释掉即可
+            result = client.pageExecute(alipayRequest, "get").getBody();
+            log.warn("支付宝调用SDK生成表单:" + result);
+            //form = client.pageExecute(alipay_request).getBody();
+//                System.out.println(form);
+        } catch (AlipayApiException e) {
+            e.printStackTrace();
+            log.error("生成表单失败!");
+        }
+        JSONObject j = new JSONObject();
+        j.put("form", result);
+        return j;
+    }
+
+
+    /**
+     * 当面付-扫码付
+     *
+     * 扫码支付,指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。
+     *
+     * 发起预下单请求,同步返回订单二维码
+     *
+     * 适用场景:商家获取二维码展示在屏幕上,然后用户去扫描屏幕上的二维码
+     * @return
+     * @throws AlipayApiException
+     */
+    public Map<String, String> tradePrecreate(AlipaymentEx alipaymentEx, AliConfig aliConfig) throws AlipayApiException {
+        if (StringUtils.isEmpty(alipaymentEx.getOutTradeNo())){
+            throw new AlipayApiException("outTradeNo should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getSubject())){
+            throw new AlipayApiException("subject should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getBody())){
+            throw new AlipayApiException("body should not be null");
+        }else if (alipaymentEx.getTotalAmount() == null){
+            throw new AlipayApiException("totalAmout should not be null");
+        }else if (StringUtils.isEmpty(alipaymentEx.getStoreId())){
+            throw new AlipayApiException("storeid should not be null");
+        }else if (alipaymentEx.getGoodsDetailList() == null || alipaymentEx.getGoodsDetailList().size() == 0){
+            throw new AlipayApiException("goodsDetailList should not be null");
+        }else if (StringUtils.isEmpty(aliConfig.getCallBackUrl())){
+            throw new AlipayApiException("notifyUrl should not be null");
+        }
+
+        List<AlipayGoodsDetail> alipayGoodsDetails = alipaymentEx.getGoodsDetailList();
+        List<GoodsDetail> goodsDetailList = new ArrayList<>();
+        for (AlipayGoodsDetail alipayGoodsDetail : alipayGoodsDetails){
+            GoodsDetail goods1 = GoodsDetail.newInstance(alipayGoodsDetail.getGoodsId(),
+                    alipayGoodsDetail.getGoodsName(), alipayGoodsDetail.getPrice(), alipayGoodsDetail.getQuantity());
+            goodsDetailList.add(goods1);
+        }
+
+        AlipayTradePrecreateRequestBuilder builder = new AlipayTradePrecreateRequestBuilder()
+                .setSubject(alipaymentEx.getSubject())
+                .setTotalAmount(alipaymentEx.getTotalAmount().toString())
+                .setOutTradeNo(alipaymentEx.getOutTradeNo())
+                .setUndiscountableAmount(alipaymentEx.getUndiscountableAmount() == null ? "" : alipaymentEx.getUndiscountableAmount().toString())
+                .setSellerId(alipaymentEx.getSellerId() == null ? "" : alipaymentEx.getSellerId())
+                .setBody(alipaymentEx.getBody())
+                .setOperatorId(alipaymentEx.getOperatorId() == null ? "" : alipaymentEx.getOperatorId())
+                .setStoreId(alipaymentEx.getStoreId())
+                .setTimeoutExpress(alipaymentEx.getTimeoutExpress())
+                //支付宝服务器主动通知商户服务器里指定的页面http路径,根据需要设置
+                .setNotifyUrl(aliConfig.getCallBackUrl())
+                .setGoodsDetailList(goodsDetailList);
+
+        Map<String, String> resp = new HashMap<>();
+
+        AlipayTradeServiceImpl alipayTradeService = new AlipayTradeServiceImpl.ClientBuilder()
+                .setGatewayUrl(aliConfig.getGatewayUrl())
+                .setAppid(aliConfig.getAppid())
+                .setPrivateKey(aliConfig.getPrivateKey())
+                .setAlipayPublicKey(aliConfig.getPublicKey())
+                .setSignType(aliConfig.getSignType())
+                .build();
+
+        AlipayF2FPrecreateResult result = alipayTradeService.tradePrecreate(builder);
+        AlipayTradePrecreateResponse res = result.getResponse();
+        switch (result.getTradeStatus()) {
+            case SUCCESS:
+                dumpResponse(res);
+                log.info("支付宝预下单成功。");
+                resp.put("code", "0");
+                resp.put("out_trade_no", res.getOutTradeNo());
+                resp.put("qr_code", res.getQrCode());
+                break;
+
+            case FAILED:
+                log.error("支付宝预下单失败,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-1");
+                break;
+
+            case UNKNOWN:
+                log.error("系统异常,预下单状态未知,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-2");
+                break;
+
+            default:
+                log.error("不支持的交易状态,交易返回异常,code:" + res.getCode() + ",msg:" + res.getMsg());
+                resp.put("code", "-3");
+                break;
+        }
+        return resp;
+    }
+
+    public String tradeQuery(String outTradeNo,AliConfig aliConfig) {
+        // (必填) 商户订单号,通过此商户订单号查询当面付的交易状态
+        String Type;
+        // 创建查询请求builder,设置请求参数
+        AlipayTradeQueryRequestBuilder builder = new AlipayTradeQueryRequestBuilder()
+                .setOutTradeNo(outTradeNo);
+        System.out.println("请求参数:" + builder);
+
+        AlipayTradeServiceImpl alipayTradeService = new AlipayTradeServiceImpl.ClientBuilder()
+                .setGatewayUrl(aliConfig.getGatewayUrl())
+                .setAppid(aliConfig.getAppid())
+                .setPrivateKey(aliConfig.getPrivateKey())
+                .setAlipayPublicKey(aliConfig.getPublicKey())
+                .setSignType(aliConfig.getSignType())
+                .build();
+
+
+        AlipayF2FQueryResult result = alipayTradeService.queryTradeResult(builder);
+        switch (result.getTradeStatus()) {
+            case SUCCESS:
+                log.info("查询返回该订单支付成功: )");
+                Type = "SUCCESS";
+                AlipayTradeQueryResponse response = result.getResponse();
+                dumpResponse(response);
+                log.info(response.getTradeStatus());
+                if (Utils.isListNotEmpty(response.getFundBillList())) {
+                    for (TradeFundBill bill : response.getFundBillList()) {
+                        log.info(bill.getFundChannel() + ":" + bill.getAmount());
+                    }
+                }
+                break;
+            case FAILED:
+                log.error("查询返回该订单支付失败或被关闭!!!");
+                Type = "FAILED";
+                break;
+            case UNKNOWN:
+                log.error("系统异常,订单支付状态未知!!!");
+                Type = "UNKNOWN";
+                break;
+            default:
+                log.error("不支持的交易状态,交易返回异常!!!");
+                Type = "default";
+                break;
+        }
+        return Type;
+    }
+
+    // 简单打印应答
+    private void dumpResponse(AlipayResponse response) {
+        if (response != null) {
+            log.info(String.format("code:%s, msg:%s", response.getCode(), response.getMsg()));
+            if (StringUtils.isNotEmpty(response.getSubCode())) {
+                log.info(String.format("subCode:%s, subMsg:%s", response.getSubCode(),
+                        response.getSubMsg()));
+            }
+            log.info("body:" + response.getBody());
+        }
+    }
+
+
+    public void callBack(HttpServletRequest request, HttpServletResponse response, Order order) {
+        log.info("ali-callBack--order:{}",order);
+        Boolean payFlag = false;
+        String trade_no = null;
+        String openId = null;
+        try {
+            AliConfig aliConfig = aliConfigService.getByServeId(order.getServeId());
+            if(aliConfig == null){
+                log.error("ali-callBack--wxConfig-notexist");
+                return;
+            }
+
+            //获取支付宝POST过来反馈信息
+            Map<String, String> params = new HashMap<String, String>();
+            Map<String, String[]> requestParams = request.getParameterMap();
+            for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
+                String name = (String) iter.next();
+                String[] values = (String[]) requestParams.get(name);
+                String valueStr = "";
+                for (int i = 0; i < values.length; i++) {
+                    valueStr = (i == values.length - 1) ? valueStr + values[i]
+                            : valueStr + values[i] + ",";
+                }
+                //乱码解决,这段代码在出现乱码时使用
+                //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
+                params.put(name, valueStr);
+            }
+            boolean signVerified = AlipaySignature.rsaCheckV1(params, aliConfig.getPublicKey(), AlipayConfig.charset, aliConfig.getSignType());
+            if (signVerified) {
+                log.info("支付宝回调签名认证成功");
+                //商户订单号
+                String out_trade_no = new String(request.getParameter("out_trade_no").getBytes("ISO-8859-1"), "UTF-8");
+                //支付宝交易号
+                trade_no = new String(request.getParameter("trade_no").getBytes("ISO-8859-1"), "UTF-8");
+                //交易状态
+                String trade_status = new String(request.getParameter("trade_status").getBytes("ISO-8859-1"), "UTF-8");
+                //交易body
+                String body = new String(request.getParameter("body").getBytes("ISO-8859-1"), "UTF-8");
+                log.info("支付宝回调返回body:" + body);
+                //付款金额
+//                String total_amount = new String(request.getParameter("total_amount").getBytes("ISO-8859-1"), "UTF-8");
+                openId = new String(request.getParameter("seller_id").getBytes("ISO-8859-1"),"UTF-8");
+                String orderSn = out_trade_no.split("_")[0];
+                if(body.split("_").length == 2){
+                    orderSn += "_" + body.split("_")[1];
+                }
+                if ("TRADE_FINISHED".equals(trade_status)){
+                    log.info("支付宝——交易结束了!" + out_trade_no);
+                }else if ("TRADE_SUCCESS".equals(trade_status)){
+                    //todo 支付宝支付成功
+                    payFlag = true;
+
+                }else {
+                    log.error("订单号:" + orderSn + "支付宝回调交易状态异常:" + trade_status);
+                }
+            }else{
+                log.info("支付宝回调签名认证失败,signVerified=false, paramsJson:{}", JSON.toJSONString(params));
+            }
+        } catch (Exception e) {
+            log.error("支付宝回调签名认证失败", e);
+        }finally {
+            if(order!= null){
+                orderService.payResult(order.getOrderId(),payFlag,trade_no,openId);
+            }
+        }
+    }
+}

+ 113 - 0
src/main/java/com/fdkankan/pay/util/alipay/sdk/AlipaymentEx.java

@@ -0,0 +1,113 @@
+package com.fdkankan.pay.util.alipay.sdk;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.List;
+
+public class AlipaymentEx implements Serializable {
+
+    private static final long serialVersionUID = 8082279987959002563L;
+    // (必填) 商户网站订单系统中唯一订单号,64个字符以内,只能包含字母、数字、下划线,
+    private String outTradeNo;
+    // (必填) 订单标题,粗略描述用户的支付目的。如“喜士多(浦东店)消费”
+    private String subject;
+    // 订单描述,可以对交易或商品进行一个详细地描述,比如填写"购买商品2件共15.00元"
+    private String body;
+    // (必填) 订单总金额,单位为元,不能超过1亿元
+    // 如果同时传入了【打折金额】,【不可打折金额】,【订单总金额】三者,则必须满足如下条件:【订单总金额】=【打折金额】+【不可打折金额】
+    private BigDecimal totalAmount;
+    // (可选) 订单不可打折金额,可以配合商家平台配置折扣活动,如果酒水不参与打折,则将对应金额填写至此字段
+    // 如果该值未传入,但传入了【订单总金额】,【打折金额】,则该值默认为【订单总金额】-【打折金额】
+    private BigDecimal undiscountableAmount;
+    // 卖家支付宝账号ID,用于支持一个签约账号下支持打款到不同的收款账号,(打款到sellerId对应的支付宝账号)
+    // 如果该字段为空,则默认为与支付宝签约的商户的PID,也就是appid对应的PID
+    private String sellerId;
+    // (必填) 商户门店编号,通过门店号和商家后台可以配置精准到门店的折扣信息,详询支付宝技术支持
+    private String storeId;
+    // 商户操作员编号,添加此参数可以为商户操作员做销售统计
+    private String operatorId;
+    // 支付超时,线下扫码交易定义为5分钟
+    private String timeoutExpress = "5m";
+    // 商品明细列表,需填写购买商品详细信息
+    private List<AlipayGoodsDetail> goodsDetailList;
+
+    public String getOutTradeNo() {
+        return outTradeNo;
+    }
+
+    public void setOutTradeNo(String outTradeNo) {
+        this.outTradeNo = outTradeNo;
+    }
+
+    public String getSubject() {
+        return subject;
+    }
+
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(String body) {
+        this.body = body;
+    }
+
+    public BigDecimal getTotalAmount() {
+        return totalAmount;
+    }
+
+    public void setTotalAmount(BigDecimal totalAmount) {
+        this.totalAmount = totalAmount;
+    }
+
+    public BigDecimal getUndiscountableAmount() {
+        return undiscountableAmount;
+    }
+
+    public void setUndiscountableAmount(BigDecimal undiscountableAmount) {
+        this.undiscountableAmount = undiscountableAmount;
+    }
+
+    public String getSellerId() {
+        return sellerId;
+    }
+
+    public void setSellerId(String sellerId) {
+        this.sellerId = sellerId;
+    }
+
+    public String getStoreId() {
+        return storeId;
+    }
+
+    public void setStoreId(String storeId) {
+        this.storeId = storeId;
+    }
+
+    public String getOperatorId() {
+        return operatorId;
+    }
+
+    public void setOperatorId(String operatorId) {
+        this.operatorId = operatorId;
+    }
+
+    public String getTimeoutExpress() {
+        return timeoutExpress;
+    }
+
+    public void setTimeoutExpress(String timeoutExpress) {
+        this.timeoutExpress = timeoutExpress;
+    }
+
+    public List<AlipayGoodsDetail> getGoodsDetailList() {
+        return goodsDetailList;
+    }
+
+    public void setGoodsDetailList(List<AlipayGoodsDetail> goodsDetailList) {
+        this.goodsDetailList = goodsDetailList;
+    }
+}

+ 83 - 0
src/main/java/com/fdkankan/pay/util/wx/MD5.java

@@ -0,0 +1,83 @@
+package com.fdkankan.pay.util.wx;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class MD5 {
+    private static String[] hexDigits = new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
+
+    public MD5() {
+    }
+
+    public static String md5Str(String str) {
+        return str == null ? "" : md5Str(str, 0);
+    }
+
+    public static String md5Str(String str, int offset) {
+        try {
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            byte[] b = str.getBytes("UTF8");
+            md5.update(b, offset, b.length);
+            return byteArrayToHexString(md5.digest());
+        } catch (NoSuchAlgorithmException var4) {
+            var4.printStackTrace();
+            return null;
+        } catch (UnsupportedEncodingException var5) {
+            var5.printStackTrace();
+            return null;
+        }
+    }
+
+    public static String byteArrayToHexString(byte[] b) {
+        String result = "";
+
+        for(int i = 0; i < b.length; ++i) {
+            result = result + byteToHexString(b[i]);
+        }
+
+        return result;
+    }
+
+    public static String byteToHexString(byte b) {
+        int n = b;
+        if (b < 0) {
+            n = 256 + b;
+        }
+
+        int d1 = n / 16;
+        int d2 = n % 16;
+        return hexDigits[d1] + hexDigits[d2];
+    }
+
+    public static void main(String[] args) {
+        System.out.println(byteToHexString((byte)-99));
+        String str = "eeeeeeeeeeeeeewrw213123122222222222222222222222213123213213213erwer";
+        String ened = md5Str(str);
+        System.out.println(ened.length());
+        System.out.println(ened);
+    }
+
+    public static final String getMessageDigest(byte[] buffer) {
+        char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+        try {
+            MessageDigest mdTemp = MessageDigest.getInstance("MD5");
+            mdTemp.update(buffer);
+            byte[] md = mdTemp.digest();
+            int j = md.length;
+            char[] str = new char[j * 2];
+            int k = 0;
+
+            for(int i = 0; i < j; ++i) {
+                byte byte0 = md[i];
+                str[k++] = hexDigits[byte0 >>> 4 & 15];
+                str[k++] = hexDigits[byte0 & 15];
+            }
+
+            return new String(str);
+        } catch (Exception var9) {
+            return null;
+        }
+    }
+}

+ 172 - 0
src/main/java/com/fdkankan/pay/util/wx/RSAUtil.java

@@ -0,0 +1,172 @@
+package com.fdkankan.pay.util.wx;
+
+import sun.misc.BASE64Decoder;
+import sun.misc.BASE64Encoder;
+
+import javax.crypto.Cipher;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.security.*;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+public class RSAUtil {
+    public static final String PRIVATE_KEY_PEM = "D:\\cert\\pkcs8.pem";
+    public static final String PUBLIC_KEY_PEM = "D:/rsa_public_key.pem";
+
+    public static final String KEY_SHA = "SHA";
+    public static final String KEY_MD5 = "MD5";
+    public static final String KEY_ALGORITHM = "RSA";
+    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
+
+
+
+
+
+
+    /**
+     * 用私钥对信息生成数字签名
+     * @param data       加密数据
+     * @param privateKey 私钥
+     * @return
+     * @throws Exception
+     */
+    public static String sign(byte[] data, PrivateKey privateKey) throws Exception {
+        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
+        signature.initSign(privateKey);
+        signature.update(data);
+        return encryptBASE64(signature.sign());
+    }
+
+    /**
+     * 校验数字签名
+     * @param data      加密数据
+     * @param publicKey 公钥
+     * @param sign      数字签名
+     * @return 校验成功返回true 失败返回false
+     * @throws Exception
+     */
+    public static boolean verify(byte[] data, PublicKey publicKey, String sign) throws Exception {
+        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
+        signature.initVerify(publicKey);
+        signature.update(data);
+        return signature.verify(decryptBASE64(sign));
+    }
+
+    /**
+     * 私钥解密
+     * @param data       密文
+     * @param privateKey 私钥
+     * @return
+     * @throws Exception
+     */
+    public static byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
+        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
+        cipher.init(Cipher.DECRYPT_MODE, privateKey);
+        return cipher.doFinal(data);
+    }
+
+    /**
+     * 用公钥解密
+     * @param data      密文
+     * @param publicKey 公钥
+     * @return
+     * @throws Exception
+     */
+    public static byte[] decryptByPublicKey(byte[] data, PublicKey publicKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
+        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
+        cipher.init(Cipher.DECRYPT_MODE, publicKey);
+        return cipher.doFinal(data);
+    }
+
+    /**
+     * 用公钥加密
+     * @param data      明文
+     * @param publicKey 公钥
+     * @return
+     * @throws Exception
+     */
+    public static byte[] encryptByPublicKey(byte[] data, PublicKey publicKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
+        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
+        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+        return cipher.doFinal(data);
+    }
+
+    /**
+     * 用私钥加密
+     * @param data       明文
+     * @param privateKey 私钥
+     * @return
+     * @throws Exception
+     */
+    public static byte[] encryptByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
+        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
+        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
+        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
+        return cipher.doFinal(data);
+    }
+
+    // 获取私匙
+    public static PrivateKey getPrivateKeyFromPem() throws Exception {
+        BufferedReader br = new BufferedReader(new FileReader(PRIVATE_KEY_PEM));
+        String s = br.readLine();
+        StringBuilder str = new StringBuilder();
+        s = br.readLine();
+        while (s.charAt(0) != '-') {
+            str.append(s).append("\r");
+            s = br.readLine();
+        }
+        BASE64Decoder base64decoder = new BASE64Decoder();
+        byte[] b = base64decoder.decodeBuffer(str.toString());
+
+        // 生成私匙
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(b);
+        PrivateKey privateKey = kf.generatePrivate(keySpec);
+        return privateKey;
+    }
+
+    // 获取公钥
+    public static PublicKey getPublicKeyFromPem() throws Exception {
+        BufferedReader br = new BufferedReader(new FileReader(PUBLIC_KEY_PEM));
+        String s = br.readLine();
+        String str = "";
+        s = br.readLine();
+        while (s.charAt(0) != '-') {
+            str += s + "\r";
+            s = br.readLine();
+        }
+        BASE64Decoder base64decoder = new BASE64Decoder();
+        byte[] b = base64decoder.decodeBuffer(str);
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(b);
+        PublicKey pubKey = kf.generatePublic(keySpec);
+        return pubKey;
+    }
+
+    public static byte[] decryptBASE64(String key) throws Exception {
+        return (new BASE64Decoder()).decodeBuffer(key);
+    }
+
+    public static String encryptBASE64(byte[] key) throws Exception {
+        return (new BASE64Encoder()).encodeBuffer(key);
+    }
+
+    public static byte[] encryptMD5(byte[] data) throws Exception {
+        MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
+        md5.update(data);
+        return md5.digest();
+    }
+
+    public static byte[] encryptSHA(byte[] data) throws Exception {
+        MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
+        sha.update(data);
+        return sha.digest();
+    }
+
+
+
+}

+ 667 - 0
src/main/java/com/fdkankan/pay/util/wx/WXPay.java

@@ -0,0 +1,667 @@
+package com.fdkankan.pay.util.wx;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Map;
+
+public class WXPay {
+
+    private static WXPayConstants.SignType signType = WXPayConstants.SignType.MD5;
+    private static Boolean useSandbox = false;
+    public static InputStream cerStream;
+    public static Integer httpConnectTimeoutMs = 8000;
+    public static Integer httpReadTimeoutMs = 10000;
+
+    /**
+     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
+     * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
+     *
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public static Map<String, String> fillRequestData(Map<String, String> reqData,
+                                                      String appId,String mchId,String mchKey) throws Exception {
+        reqData.put("appid", appId);
+        reqData.put("mch_id", mchId);
+        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
+        if (WXPayConstants.SignType.MD5.equals(signType)) {
+            reqData.put("sign_type", WXPayConstants.MD5);
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
+            reqData.put("sign_type", WXPayConstants.HMACSHA256);
+        }
+        reqData.put("sign", WXPayUtil.generateSignature(reqData, mchKey, signType));
+        return reqData;
+    }
+
+    /**
+     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public static boolean isResponseSignatureValid(Map<String, String> reqData,String mchKey) throws Exception {
+        // 返回数据的签名方式和请求中给定的签名方式是一致的
+        return WXPayUtil.isSignatureValid(reqData, mchKey, signType);
+    }
+
+    /**
+     * 判断支付结果通知中的sign是否有效
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public static boolean isPayResultNotifySignatureValid(Map<String, String> reqData,String mchKey) throws Exception {
+        String signTypeInData = reqData.get(WXPayConstants.FIELD_SIGN_TYPE);
+        WXPayConstants.SignType signType;
+        if (signTypeInData == null) {
+            signType = WXPayConstants.SignType.MD5;
+        }
+        else {
+            signTypeInData = signTypeInData.trim();
+            if (signTypeInData.length() == 0) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.MD5.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.MD5;
+            }
+            else if (WXPayConstants.HMACSHA256.equals(signTypeInData)) {
+                signType = WXPayConstants.SignType.HMACSHA256;
+            }
+            else {
+                throw new Exception(String.format("Unsupported sign_type: %s", signTypeInData));
+            }
+        }
+        return WXPayUtil.isSignatureValid(reqData, mchKey, signType);
+    }
+
+
+    /**
+     * 不需要证书的请求
+     * @param strUrl String
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public static String requestWithoutCert(String strUrl, Map<String, String> reqData,
+                                     int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String UTF8 = "UTF-8";
+        String reqBody = WXPayUtil.mapToXml(reqData);
+        URL httpUrl = new URL(strUrl);
+        HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
+        httpURLConnection.setDoOutput(true);
+        httpURLConnection.setRequestMethod("POST");
+        httpURLConnection.setConnectTimeout(connectTimeoutMs);
+        httpURLConnection.setReadTimeout(readTimeoutMs);
+        httpURLConnection.connect();
+        OutputStream outputStream = httpURLConnection.getOutputStream();
+        outputStream.write(reqBody.getBytes(UTF8));
+
+        // if (httpURLConnection.getResponseCode()!= 200) {
+        //     throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
+        // }
+
+        //获取内容
+        InputStream inputStream = httpURLConnection.getInputStream();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
+        final StringBuffer stringBuffer = new StringBuffer();
+        String line = null;
+        while ((line = bufferedReader.readLine()) != null) {
+            stringBuffer.append(line).append("\n");
+        }
+        String resp = stringBuffer.toString();
+        if (stringBuffer!=null) {
+            try {
+                bufferedReader.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (inputStream!=null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (outputStream!=null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        // if (httpURLConnection!=null) {
+        //     httpURLConnection.disconnect();
+        // }
+
+        return resp;
+    }
+
+
+    /**
+     * 需要证书的请求
+     * @param strUrl String
+     * @param reqData 向wxpay post的请求数据  Map
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public static String requestWithCert(String strUrl, Map<String, String> reqData,String mchId,
+                                  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String UTF8 = "UTF-8";
+        String reqBody = WXPayUtil.mapToXml(reqData);
+        URL httpUrl = new URL(strUrl);
+        char[] password = mchId.toCharArray();
+        InputStream certStream = cerStream;
+        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());
+        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
+
+        HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();
+
+        httpURLConnection.setDoOutput(true);
+        httpURLConnection.setRequestMethod("POST");
+        httpURLConnection.setConnectTimeout(connectTimeoutMs);
+        httpURLConnection.setReadTimeout(readTimeoutMs);
+        httpURLConnection.connect();
+        OutputStream outputStream = httpURLConnection.getOutputStream();
+        outputStream.write(reqBody.getBytes(UTF8));
+
+        // if (httpURLConnection.getResponseCode()!= 200) {
+        //     throw new Exception(String.format("HTTP response code is %d, not 200", httpURLConnection.getResponseCode()));
+        // }
+
+        //获取内容
+        InputStream inputStream = httpURLConnection.getInputStream();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, UTF8));
+        final StringBuffer stringBuffer = new StringBuffer();
+        String line = null;
+        while ((line = bufferedReader.readLine()) != null) {
+            stringBuffer.append(line);
+        }
+        String resp = stringBuffer.toString();
+        if (stringBuffer!=null) {
+            try {
+                bufferedReader.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (inputStream!=null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (outputStream!=null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        if (certStream!=null) {
+            try {
+                certStream.close();
+            } catch (IOException e) {
+                // e.printStackTrace();
+            }
+        }
+        // if (httpURLConnection!=null) {
+        //     httpURLConnection.disconnect();
+        // }
+
+        return resp;
+    }
+
+    /**
+     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
+     * @param xmlStr API返回的XML格式数据
+     * @return Map类型数据
+     * @throws Exception
+     */
+    public static Map<String, String> processResponseXml(String xmlStr,String mchKey) throws Exception {
+        String RETURN_CODE = "return_code";
+        String return_code;
+        Map<String, String> 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 (isResponseSignatureValid(respData,mchKey)) {
+               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));
+        }
+    }
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public static Map<String, String> microPay(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return microPay(reqData,appId,mchId,mchKey, httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public static Map<String, String> microPay(Map<String, String> reqData, String appId,String mchId,String mchKey,
+                                               int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_MICROPAY_URL;
+        }
+        else {
+            url = WXPayConstants.MICROPAY_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public static Map<String, String> unifiedOrder(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return unifiedOrder(reqData, appId,mchId,mchKey,httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public static Map<String, String> unifiedOrder(Map<String, String> reqData, String appId,String mchId,String mchKey,
+                                                   int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL;
+        }
+        else {
+            url = WXPayConstants.UNIFIEDORDER_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return orderQuery(reqData,appId,mchId,mchKey, httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据 int
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData, String appId,String mchId,String mchKey,
+                                          int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_ORDERQUERY_URL;
+        }
+        else {
+            url = WXPayConstants.ORDERQUERY_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return reverse(reqData,appId,mchId,mchKey, httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData, String appId,String mchId,String mchKey,
+                                       int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_REVERSE_URL;
+        }
+        else {
+            url = WXPayConstants.REVERSE_URL;
+        }
+        String respXml = requestWithCert(url, fillRequestData(reqData,appId,mchId,mchKey), mchId,connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return closeOrder(reqData,appId,mchId,mchKey, httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData, String appId,String mchId,String mchKey,
+                                          int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_CLOSEORDER_URL;
+        }
+        else {
+            url = WXPayConstants.CLOSEORDER_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return refund(reqData, appId,mchId,mchKey,httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData, String appId,String mchId,String mchKey,int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUND_URL;
+        }
+        else {
+            url = WXPayConstants.REFUND_URL;
+        }
+        String respXml = requestWithCert(url, fillRequestData(reqData,appId,mchId,mchKey),mchId, connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return refundQuery(reqData,appId,mchId,mchKey, httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData, String appId,String mchId,String mchKey,
+                                           int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUNDQUERY_URL;
+        }
+        else {
+            url = WXPayConstants.REFUNDQUERY_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return downloadBill(reqData, appId,mchId,mchKey,httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:对账单下载<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:无论是否成功都返回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<String, String> downloadBill(Map<String, String> reqData, String appId,String mchId,String mchKey, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL;
+        }
+        else {
+            url = WXPayConstants.DOWNLOADBILL_URL;
+        }
+        String respStr = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs).trim();
+        Map<String, String> ret;
+        // 出现错误,返回XML数据
+        if (respStr.indexOf("<") == 0) {
+            ret = WXPayUtil.xmlToMap(respStr);
+        }
+        else {
+            // 正常返回csv数据
+            ret = new HashMap<String, String>();
+            ret.put("return_code", WXPayConstants.SUCCESS);
+            ret.put("return_msg", "sdk");
+            ret.put("data", respStr);
+        }
+        return ret;
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return report(reqData, appId,mchId,mchKey,httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData, String appId,String mchId,String mchKey, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_REPORT_URL;
+        }
+        else {
+            url = WXPayConstants.REPORT_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return WXPayUtil.xmlToMap(respXml);
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return shortUrl(reqData, appId,mchId,mchKey,httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData, String appId,String mchId,String mchKey, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_SHORTURL_URL;
+        }
+        else {
+            url = WXPayConstants.SHORTURL_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, String appId,String mchId,String mchKey) throws Exception {
+        return authCodeToOpenid(reqData,appId,mchId,mchKey, httpConnectTimeoutMs, httpReadTimeoutMs);
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, String appId,String mchId,String mchKey, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (useSandbox) {
+            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL;
+        }
+        else {
+            url = WXPayConstants.AUTHCODETOOPENID_URL;
+        }
+        String respXml = requestWithoutCert(url, fillRequestData(reqData,appId,mchId,mchKey), connectTimeoutMs, readTimeoutMs);
+        return processResponseXml(respXml,mchKey);
+    }
+
+
+} // end class

+ 45 - 0
src/main/java/com/fdkankan/pay/util/wx/WXPayConstants.java

@@ -0,0 +1,45 @@
+package com.fdkankan.pay.util.wx;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+    public enum SignType {
+        MD5, HMACSHA256
+    }
+
+    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 MICROPAY_URL     = "https://api.mch.weixin.qq.com/pay/micropay";
+    public static final String UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
+    public static final String ORDERQUERY_URL   = "https://api.mch.weixin.qq.com/pay/orderquery";
+    public static final String REVERSE_URL      = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
+    public static final String CLOSEORDER_URL   = "https://api.mch.weixin.qq.com/pay/closeorder";
+    public static final String REFUND_URL       = "https://api.mch.weixin.qq.com/secapi/pay/refund";
+    public static final String REFUNDQUERY_URL  = "https://api.mch.weixin.qq.com/pay/refundquery";
+    public static final String DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
+    public static final String REPORT_URL       = "https://api.mch.weixin.qq.com/payitil/report";
+    public static final String SHORTURL_URL     = "https://api.mch.weixin.qq.com/tools/shorturl";
+    public static final String AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/tools/authcodetoopenid";
+
+    // sandbox
+    public static final String SANDBOX_MICROPAY_URL     = "https://api.mch.weixin.qq.com/sandboxnew/pay/micropay";
+    public static final String SANDBOX_UNIFIEDORDER_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder";
+    public static final String SANDBOX_ORDERQUERY_URL   = "https://api.mch.weixin.qq.com/sandboxnew/pay/orderquery";
+    public static final String SANDBOX_REVERSE_URL      = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/reverse";
+    public static final String SANDBOX_CLOSEORDER_URL   = "https://api.mch.weixin.qq.com/sandboxnew/pay/closeorder";
+    public static final String SANDBOX_REFUND_URL       = "https://api.mch.weixin.qq.com/sandboxnew/secapi/pay/refund";
+    public static final String SANDBOX_REFUNDQUERY_URL  = "https://api.mch.weixin.qq.com/sandboxnew/pay/refundquery";
+    public static final String SANDBOX_DOWNLOADBILL_URL = "https://api.mch.weixin.qq.com/sandboxnew/pay/downloadbill";
+    public static final String SANDBOX_REPORT_URL       = "https://api.mch.weixin.qq.com/sandboxnew/payitil/report";
+    public static final String SANDBOX_SHORTURL_URL     = "https://api.mch.weixin.qq.com/sandboxnew/tools/shorturl";
+    public static final String SANDBOX_AUTHCODETOOPENID_URL = "https://api.mch.weixin.qq.com/sandboxnew/tools/authcodetoopenid";
+
+}

+ 277 - 0
src/main/java/com/fdkankan/pay/util/wx/WXPayUtil.java

@@ -0,0 +1,277 @@
+package com.fdkankan.pay.util.wx;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+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.util.*;
+
+
+public class WXPayUtil {
+
+    /**
+     * XML格式字符串转换为Map
+     *
+     * @param strXML XML字符串
+     * @return XML数据转换后的Map
+     * @throws Exception
+     */
+    public static Map<String, String> xmlToMap(String strXML) throws Exception {
+        Map<String, String> data = new HashMap<String, String>();
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+
+        // 禁用XML外部实体注入
+        /**
+         * XXE
+         * XML 外部实体注入漏洞(XML External Entity Injection,简称 XXE),
+         * 是一种容易被忽视,但危害巨大的漏洞。它可以利用 XML 外部实体加载注入,
+         * 执行不可预控的代码,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
+         */
+
+        /**
+         * 原理:
+         * 通常,我们在使用微信支付时,商家会有一个通知的 URL 来接收异步支付结果。
+         * 然而,微信在 JAVA 版本的 SDK 存在一个 XXE 漏洞来处理这个结果。
+         * 由此攻击者可以向通知的 URL 中构建恶意的回调数据,以便根据需要窃取商家服务器上的任意信息。
+         * 一旦攻击者获得商家的关键安全密钥(md5-key 和merchant-Id),
+         * 那么他们可以通过发送伪造的信息来欺骗商家而无需付费购买任意商品。
+         */
+        documentBuilderFactory.setExpandEntityReferences(false);
+        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        DocumentBuilder documentBuilder= documentBuilderFactory.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) {
+
+        }
+        return data;
+    }
+
+    /**
+     * 将Map转换为XML格式的字符串
+     *
+     * @param data Map类型数据
+     * @return XML格式的字符串
+     * @throws Exception
+     */
+    public static String mapToXml(Map<String, String> data) throws Exception {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
+        org.w3c.dom.Document document = documentBuilder.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<String, String> data, String key) throws Exception {
+        return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名类型
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.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<String, String> 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<String, String> data, String key) throws Exception {
+        return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 判断签名是否正确,必须包含sign字段,否则返回false。
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名是否正确
+     * @throws Exception
+     */
+    public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.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<String, String> data, String key) throws Exception {
+        return generateSignature(data, key, WXPayConstants.SignType.MD5);
+    }
+
+    /**
+     * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
+     *
+     * @param data 待签名数据
+     * @param key API密钥
+     * @param signType 签名方式
+     * @return 签名
+     */
+    public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
+        Set<String> 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);
+        if (WXPayConstants.SignType.MD5.equals(signType)) {
+            return MD5(sb.toString()).toUpperCase();
+        }
+        else if (WXPayConstants.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() {
+        return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
+    }
+
+
+    /**
+     * 生成 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 {
+        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();
+    }
+
+}

+ 248 - 0
src/main/java/com/fdkankan/pay/util/wx/WechatPayService.java

@@ -0,0 +1,248 @@
+package com.fdkankan.pay.util.wx;
+
+import com.fdkankan.pay.common.ResultCode;
+import com.fdkankan.pay.entity.Order;
+import com.fdkankan.pay.entity.WxConfig;
+import com.fdkankan.pay.exception.BusinessException;
+import com.fdkankan.pay.response.OpenPayResponse;
+import com.fdkankan.pay.service.IOrderService;
+import com.fdkankan.pay.service.IWxConfigService;
+import com.fdkankan.pay.util.CacheUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedOutputStream;
+import java.math.BigDecimal;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+
+//微信支付
+@Service
+@Slf4j
+public class WechatPayService {
+
+    @Autowired
+    IWxConfigService wxConfigService;
+    @Autowired
+    IOrderService orderService;
+
+    public Object openPay(Order param, String ip) throws Exception {
+
+        String orderSn = param.getOrderSn();
+        BigDecimal amount = param.getOrderMoney().multiply(new BigDecimal(100));
+        String subject = param.getOrderType();
+        WxConfig wxConfig = wxConfigService.getByServeId(param.getServeId());
+        if(wxConfig == null){
+            throw new BusinessException(ResultCode.WX_CONFIG_NOT);
+        }
+        wxConfig.setCallBackUrl( CacheUtil.mainUrl + wxConfig.getCallBackUrl() +"/"+orderSn);
+        BigDecimal totalFee = amount.multiply(new BigDecimal(100));
+        String body = subject;
+        if(param.getPayType() == 0){
+            return this.wechatH5Pay(orderSn, subject, body, totalFee, ip,wxConfig);
+        }
+        if(param.getPayType() == 1){
+            return this.wechatPay(orderSn,subject, body, totalFee, ip, param.getOpenId(),wxConfig);
+        }
+        if(param.getPayType() == 2){
+            return this.wechatScanPay(orderSn,subject, body, totalFee, ip,wxConfig);
+        }
+        orderService.updateById(param);
+        throw new BusinessException(ResultCode.WX_ORDER_PAY_TYPE_ERROR);
+    }
+
+
+    public Object wechatH5Pay(String orderSn, String subject, String body, BigDecimal totalFee, String ipAddress,WxConfig wxConfig) throws Exception {
+        log.info("wx-h5-pay:orderSn:{},subject:{},body:{},totalFee:{},ip:{},wxConfig:{}",
+                orderSn,subject,body,totalFee,ipAddress,wxConfig);
+
+        Map<String, String> data = new HashMap<String, String>();
+        data.put("body", subject);
+        data.put("detail", body);
+        data.put("out_trade_no", orderSn );
+        data.put("device_info", "H5");
+        data.put("fee_type", "CNY");
+        data.put("total_fee", totalFee.stripTrailingZeros().toPlainString());
+        data.put("spbill_create_ip", ipAddress);
+        data.put("notify_url",wxConfig.getCallBackUrl());
+        data.put("trade_type", "MWEB");  // 此处指定为扫码支付
+        data.put("product_id", orderSn); //多个商品,使用订单号
+        data.put("nonce_str", MD5.getMessageDigest(String.valueOf(new Random().nextInt(10000)).getBytes()));
+
+        String mweb_url = "";
+        Map<String, String> resp = WXPay.unifiedOrder(data,wxConfig.getAppid(),wxConfig.getMchId(),wxConfig.getMchKey());
+        log.info("wx-h5-pay" + resp);
+        String return_code = resp.get("return_code");
+        String return_msg = resp.get("return_msg");
+        if ("SUCCESS".equals(return_code) && "OK".equals(return_msg)) {
+            mweb_url = resp.get("mweb_url");//调微信支付接口地址
+            mweb_url += "&redirect_url=" + URLEncoder.encode(wxConfig.getCallBackUrl()+"/"+ orderSn, "UTF-8");
+            log.warn("mweb_url=" + mweb_url);
+        } else {
+            log.error("微信统一支付接口获取预支付订单出错");
+        }
+
+        OpenPayResponse openPayResponse = new OpenPayResponse();
+        openPayResponse.setH5Url(mweb_url);
+        openPayResponse.setOrderSn(orderSn);
+        openPayResponse.setPayType(1);
+        return openPayResponse;
+    }
+
+
+    public Object wechatPay(String orderSn, String subject, String body, BigDecimal totalFee, String ipAddress, String openid,WxConfig wxConfig) throws Exception {
+        log.info("wx-jsapi-pay:orderSn:{},subject:{},body:{},totalFee:{},ip:{},openid:{},wxConfig:{}",
+                orderSn,subject,body,totalFee,ipAddress,openid,wxConfig);
+
+        Map<String, String> data = new HashMap<String, String>();
+        data.put("body", subject);
+        data.put("detail", body);
+        data.put("out_trade_no", orderSn );
+        data.put("device_info", "wechat");
+        data.put("fee_type", "CNY");
+        data.put("total_fee", totalFee.stripTrailingZeros().toPlainString());
+        data.put("spbill_create_ip", ipAddress);
+        data.put("notify_url",wxConfig.getCallBackUrl());
+        data.put("trade_type", "JSAPI");
+        data.put("product_id", orderSn);
+        data.put("openid", openid);
+
+        Map<String, String> resp = WXPay.unifiedOrder(data,wxConfig.getAppid(),wxConfig.getMchId(),wxConfig.getMchKey());
+        /**
+
+         * - wechatPay{nonce_str=BzpQLCRbx1HJyvpy, sign=39E64EEA1B86815028AF85D02A4BF431, return_msg=OK, mch_id=1505605401,
+         * prepay_id=wx1110324853730453c6af4a89e0f0830000,
+         * timeStamp=1681180368, device_info=wechat, paySign=77E4198B30ADFDD29C92ECCCC0DBFA03, appid=wxac3d59ea82d9b82a,
+         * trade_type=JSAPI, signType=MD5, result_code=SUCCESS, return_code=SUCCESS}
+         */
+        log.info("wx-jsapi-pay" + resp);
+
+        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
+        Map<String, String> sign = new HashMap<>();
+        sign.put("package", "prepay_id="+resp.get("prepay_id"));
+        sign.put("appId", wxConfig.getAppid());
+        sign.put("timeStamp",timeStamp);
+        sign.put("nonceStr", resp.get("nonce_str"));
+        sign.put("signType", "MD5");
+        String paySign = WXPayUtil.generateSignature(sign, wxConfig.getMchKey());
+
+        resp.put("signType", "MD5");
+        resp.put("paySign", paySign);
+        resp.put("timeStamp",timeStamp);
+
+
+        OpenPayResponse openPayResponse = new OpenPayResponse();
+        openPayResponse.setNonceStr(resp.get("nonce_str"));
+        openPayResponse.setSignType("MD5");
+        openPayResponse.setPaySign(paySign);
+        openPayResponse.setMchId(resp.get("mch_id"));
+        openPayResponse.setPrepayId(resp.get("prepay_id"));
+        openPayResponse.setAppid(resp.get("appid"));
+        openPayResponse.setTradeType(resp.get("trade_type"));
+        openPayResponse.setTimeStamp(timeStamp);
+        openPayResponse.setDeviceInfo(resp.get("device_info"));
+        openPayResponse.setOrderSn(orderSn);
+        openPayResponse.setPayType(1);
+        return openPayResponse;
+    }
+
+
+    public Object wechatScanPay(String orderSn, String subject, String body, BigDecimal totalFee, String ipAddress,WxConfig wxConfig) throws Exception {
+        log.info("wx-scene-pay:orderSn:{},subject:{},body:{},totalFee:{},ip:{},wxConfig:{}",
+                orderSn,subject,body,totalFee,ipAddress,wxConfig);
+        Map<String, String> data = new HashMap<String, String>();
+        data.put("body", body);
+        data.put("detail", subject);
+        data.put("out_trade_no", orderSn );
+        data.put("device_info", "WEB");
+        data.put("fee_type", "CNY");
+        data.put("total_fee", totalFee.stripTrailingZeros().toPlainString());
+        data.put("spbill_create_ip", ipAddress);
+        data.put("notify_url",wxConfig.getCallBackUrl());
+        data.put("trade_type", "NATIVE");  // 此处指定为扫码支付
+        data.put("product_id", orderSn); //多个商品,使用订单号
+
+        Map<String, String> resp = WXPay.unifiedOrder(data,wxConfig.getAppid(),wxConfig.getMchId(),wxConfig.getMchKey());
+        OpenPayResponse openPayResponse = new OpenPayResponse();
+        openPayResponse.setQrCodeUrl(resp.get("code_url"));
+        openPayResponse.setOrderSn(orderSn);
+        openPayResponse.setPayType(2);
+        return openPayResponse;
+    }
+
+
+    public void callBack(HttpServletRequest request, HttpServletResponse response,Order order) {
+        log.info("wx-callBack--order:{}",order);
+        Boolean payFlag = false;
+        String trade_no = null;
+        String openId = null;
+        try {
+            WxConfig wxConfig = wxConfigService.getByServeId(order.getServeId());
+            if(wxConfig == null){
+                log.error("wx-callBack--wxConfig-notexist");
+                return;
+            }
+            String inputLine = "";
+            String notifyXml = "";
+            while((inputLine = request.getReader().readLine()) != null){
+                notifyXml += inputLine;
+            }
+            //关闭流
+            request.getReader().close();
+            log.info("wx-callBack--msg:{}",notifyXml);
+            //解析成Map
+            Map<String,String> map = WXPayUtil.xmlToMap(notifyXml);
+
+            if (WXPayUtil.isSignatureValid(map,wxConfig.getMchKey(), WXPayConstants.SignType.MD5)
+                    || WXPayUtil.isSignatureValid(map, wxConfig.getMchKey(), WXPayConstants.SignType.HMACSHA256)) {
+                //判断 支付是否成功
+                if("SUCCESS".equals(map.get("result_code"))){
+                    //获得 返回的商户订单号
+                    String out_trade_no = map.get("out_trade_no");
+                    trade_no = map.get("transaction_id");
+                    openId = map.get("openid");
+                    log.info("微信回调返回商户订单号:out_trade_no:{},transaction_id:{},openid:{}" , out_trade_no,trade_no,openId);
+                    //修改订单状态
+                    //todo 支付成功
+                    if(!out_trade_no.equals(order.getOrderSn())){
+                        log.error("wx-call-back error :orderSn:{},out_trade_no:{}" ,order.getOrderSn(), out_trade_no);
+                        return;
+                    }
+                    payFlag = true;
+
+                    log.info("微信支付成功,订单号:"  + out_trade_no);
+                    //通知微信服务器已经支付成功
+                    notifyXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code>"
+                            + "<return_msg><![CDATA[OK]]></return_msg></xml> ";
+                }else{
+                    notifyXml = "<xml><return_code><![CDATA[FAIL]]></return_code>"
+                            + "<return_msg><![CDATA[报文为空]]></return_msg></xml> ";
+                }
+            } else{
+                log.error("签名验证错误");
+                notifyXml = "<xml><return_code><![CDATA[FAIL]]></return_code>"
+                        + "<return_msg><![CDATA[签名验证错误]]></return_msg></xml>";
+            }
+            BufferedOutputStream out = new BufferedOutputStream(
+                    response.getOutputStream());
+            out.write(notifyXml.getBytes());
+            out.flush();
+            out.close();
+
+        } catch (Exception e) {
+            log.error("微信支付回调数据异常, error:", e);
+        }finally {
+            if(order!= null){
+                orderService.payResult(order.getOrderId(),payFlag,trade_no,openId);
+            }
+        }
+    }
+
+
+}

+ 22 - 0
src/main/java/com/fdkankan/pay/util/wx/WxPayBean.java

@@ -0,0 +1,22 @@
+package com.fdkankan.pay.util.wx;
+
+import lombok.Data;
+
+@Data
+public class WxPayBean {
+
+    /*
+ "appId": "wx2421b1c4370ec43b",     //公众号ID,由商户传入
+  "timeStamp": "1395712654",     //时间戳,自1970年以来的秒数
+ "nonceStr": "e61463f8efa94090b1f366cccfbbb444",      //随机串
+ "package": "prepay_id=up_wx21201855730335ac86f8c43d1889123400",
+ "signType": "MD5",     //微信签名方式:
+ "paySign":
+ */
+    private String appId;
+    private String timeStamp;
+    private String nonceStr;
+    private String prepayId;
+    private String signType = "MD5";
+    private String paySign;
+}

+ 32 - 0
src/main/resources/bootstrap-test.yml

@@ -0,0 +1,32 @@
+spring:
+  application:
+    name: 4dkankan-center-pay
+  cloud:
+    nacos:
+      config:
+        server-addr: 120.24.144.164:8848
+        file-extension: yaml
+        namespace: 4dkankan-v4-test
+        extension-configs:
+          - data-id: 4dkankan-center-pay.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+        shared-configs:
+          - data-id: common-rabbitmq-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: common-redis-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+          - data-id: common-config.yaml
+            group: DEFAULT_GROUP
+            refresh: true
+
+      discovery:
+        server-addr: ${spring.cloud.nacos.config.server-addr}
+        namespace: ${spring.cloud.nacos.config.namespace}
+
+
+

+ 8 - 0
src/main/resources/bootstrap.yml

@@ -0,0 +1,8 @@
+spring:
+  profiles:
+    active: test
+logging:
+  config: classpath:logback-spring.xml
+mybatis-plus:
+  configuration:
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启sql日志

+ 254 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
+<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
+<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
+<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
+<configuration scan="true" scanPeriod="10 seconds">
+
+	<contextName>logback</contextName>
+	<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
+	<property name="log.path" value="${LOG_PATH}/orderPay/logs" />
+
+	<!-- 彩色日志 -->
+	<!-- 彩色日志依赖的渲染类 -->
+	<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
+	<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
+	<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
+	<!-- 彩色日志格式 -->
+	<property name="CONSOLE_LOG_PATTERN"
+			  value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
+
+	<!--输出到控制台 -->
+	<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+		<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息 -->
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>info</level>
+		</filter>
+		<encoder>
+			<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
+			<!-- 设置字符集 -->
+			<charset>UTF-8</charset>
+		</encoder>
+	</appender>
+	<!--输出到文件 -->
+
+	<!-- 时间滚动输出 level为 DEBUG 日志 -->
+	<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_debug.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<!-- 日志归档 -->
+			<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录debug级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>debug</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<!-- 时间滚动输出 level为 INFO 日志 -->
+	<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_info.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+			<charset>UTF-8</charset>
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<!-- 每天日志归档路径以及格式 -->
+			<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录info级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>info</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<!-- 时间滚动输出 level为 WARN 日志 -->
+	<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_warn.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录warn级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>warn</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+
+	<!-- 时间滚动输出 level为 ERROR 日志 -->
+	<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/log_error.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录ERROR级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<level>ERROR</level>
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+
+	<appender name="PROGRAM_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/program/log_program.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/program/log-program-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录ERROR级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+	<logger name="programLog" level="INFO" additivity="true">
+		<appender-ref ref="PROGRAM_FILE"/>
+	</logger>
+
+	<appender name="VISIT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<!-- 正在记录的日志文件的路径及文件名 -->
+		<file>${log.path}/visit/log_visit.log</file>
+		<!--日志文件输出格式 -->
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+		<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/visit/log-visit-%d{yyyy-MM-dd}.%i.log
+			</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数 -->
+			<maxHistory>15</maxHistory>
+		</rollingPolicy>
+		<!-- 此日志文件只记录ERROR级别的 -->
+		<filter class="ch.qos.logback.classic.filter.LevelFilter">
+			<onMatch>ACCEPT</onMatch>
+			<onMismatch>DENY</onMismatch>
+		</filter>
+	</appender>
+	<logger name="visitLog" level="INFO" additivity="true">
+		<appender-ref ref="VISIT_FILE"/>
+	</logger>
+
+
+	<!--  连接时长  -->
+	<appender name="timeLogger" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/timeLogger.log</file>
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>DEBUG</level>
+		</filter>
+		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/timeLogger/timeLogger-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+				<maxFileSize>100MB</maxFileSize>
+			</timeBasedFileNamingAndTriggeringPolicy>
+			<!--日志文件保留天数-->
+			<maxHistory>10</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
+			<charset>UTF-8</charset> <!-- 此处设置字符集 -->
+		</encoder>
+	</appender>
+	<logger name="timeLogger" additivity="false" level="DEBUG">
+		<appender-ref ref="timeLogger"/>
+	</logger>
+
+	<!-- <logger>用来设置某一个包或者具体的某一个类的日志打印级别、 以及指定<appender>。<logger>仅有一个name属性, 一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE,
+		DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。 如果未设置此属性,那么当前logger将会继承上级的级别。 addtivity:是否向上级logger传递打印信息。默认是true。 -->
+	<!--<logger name="org.springframework.web" level="info"/> -->
+	<!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/> -->
+	<!-- 使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作: 第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息 第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别: -->
+	<!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 不能设置为INHERITED或者同义词NULL。默认是DEBUG 可以包含零个或多个元素,标识这个appender将会添加到这个logger。 -->
+
+	<root level="info">
+		<appender-ref ref="CONSOLE" />
+		<appender-ref ref="DEBUG_FILE" />
+		<appender-ref ref="INFO_FILE" />
+		<appender-ref ref="WARN_FILE" />
+		<appender-ref ref="ERROR_FILE" />
+	</root>
+
+	<!--生产环境:输出到文件 -->
+	<!--<springProfile name="pro"> -->
+	<!--<root level="info"> -->
+	<!--<appender-ref ref="CONSOLE" /> -->
+	<!--<appender-ref ref="DEBUG_FILE" /> -->
+	<!--<appender-ref ref="INFO_FILE" /> -->
+	<!--<appender-ref ref="ERROR_FILE" /> -->
+	<!--<appender-ref ref="WARN_FILE" /> -->
+	<!--</root> -->
+	<!--</springProfile> -->
+
+</configuration>
+
+

+ 5 - 0
src/main/resources/mapper/pay/AliConfigMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fdkankan.pay.mapper.IAliConfigMapper">
+
+</mapper>

+ 5 - 0
src/main/resources/mapper/pay/MqLogMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fdkankan.pay.mapper.IMqLogMapper">
+
+</mapper>

+ 5 - 0
src/main/resources/mapper/pay/OrderMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fdkankan.pay.mapper.IOrderMapper">
+
+</mapper>

+ 5 - 0
src/main/resources/mapper/pay/PayServeMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fdkankan.pay.mapper.IPayServeMapper">
+
+</mapper>

+ 5 - 0
src/main/resources/mapper/pay/WxConfigMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fdkankan.pay.mapper.IWxConfigMapper">
+
+</mapper>