From 5d33f4766e36d655b0fd430374a757805e8db2a5 Mon Sep 17 00:00:00 2001
From: zhaoxiaohao <279049017@qq.com>
Date: Mon, 21 Sep 2020 10:25:14 +0800
Subject: [PATCH] opration 添加支付的功能

---
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayConfig.java             |   23 
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConfig.java              |  104 ++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/weixin/WxController.java         |  142 +++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayUtil.java                |  296 ++++++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyConfig.java                 |   73 +
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/mapper/SaasClientPayMapper.java                |   34 
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/SaasClientPayController.java     |  114 ++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WeiXinOfficPayProperties.java |   51 +
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPay.java                    |  689 ++++++++++++++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/alipay/AlipayController.java     |  181 +++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayReport.java              |  265 +++++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/IWXPayDomain.java             |   42 
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayRequest.java             |  258 +++++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/resources/mapper/SaasClientPayMapper.xml                                       |   88 +
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConstants.java           |   59 +
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayProperties.java         |   22 
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-api/src/main/java/com/kidgrow/oprationcenter/model/SaasClientPay.java                       |  150 +++
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/pom.xml                                                                                 |    6 
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/impl/SaasClientPayServiceImpl.java     |   49 +
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyIWXPayDomain.java           |   14 
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayXmlUtil.java             |   30 
 kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/ISaasClientPayService.java             |   34 
 22 files changed, 2,724 insertions(+), 0 deletions(-)

diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-api/src/main/java/com/kidgrow/oprationcenter/model/SaasClientPay.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-api/src/main/java/com/kidgrow/oprationcenter/model/SaasClientPay.java
new file mode 100644
index 0000000..bf59e61
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-api/src/main/java/com/kidgrow/oprationcenter/model/SaasClientPay.java
@@ -0,0 +1,150 @@
+package com.kidgrow.oprationcenter.model;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.NotEmpty;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br>
+ *
+ * @version 1.0
+ * @Description: <br>
+ * @Project: 用户中心<br>
+ * @CreateDate: Created in 2020-09-17 17:24:49 <br>
+ * @Author: <a href="4345453@kidgrow.com">liuke</a>
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("saas_client_pay")
+public class SaasClientPay {
+    private static final long serialVersionUID = 1L;
+
+    private String id;
+    /**
+     * 诊断医生所在科室
+     */
+    private String createHospitalDepartment;
+    /**
+     * 科室的唯一标识
+     */
+    @NotEmpty(message = "科室的唯一标识不能为空")
+    private String createHospitalDepartid;
+    /**
+     * 创建医院的唯一标识
+     */
+    @NotEmpty(message = "创建医院的唯一标识不能为空")
+    private Long createHospitalId;
+    /**
+     * 创建医院名称
+     */
+    private String createHospitalName;
+    /**
+     * 金额 (单位为分)
+     */
+    @NotEmpty(message = "金额 (单位为分)不能为空")
+    private Integer payPrice;
+    /**
+     * 内部订单号
+     */
+    @NotEmpty(message = "内部订单号不能为空")
+    private String outTradeNo;
+    /**
+     * 支付完成时间
+     */
+    private Date payTime;
+    /**
+     * 支付状态0订单创建1订单提交到网关2订单支付成功-1订单支付失败
+     */
+    private Integer payStatus;
+    /**
+     * 支付方式,暂时只用微信和支付宝0为微信1为支付宝
+     */
+    private Integer payMethod;
+    /**
+     * 关联诊断记录ID
+     */
+    @NotEmpty(message = "关联诊断记录ID不能为空")
+    private String diaId;
+    /**
+     * 支付宝交易号  微信支付订单号
+     */
+    private String tradeNo;
+    /**
+     * 微信生成的预支付会话标识
+     */
+    private String prepayId;
+    /**
+     * 支付给喜高的费用
+     */
+    private String payKidgrow;
+    /**
+     * 支付给客户的费用
+     */
+    private String payCustom;
+    /**
+     * 支付状态
+     */
+    private Boolean type;
+
+    @JsonSerialize(
+            using = ToStringSerializer.class
+    )
+    @TableId(
+            type = IdType.ASSIGN_ID
+    )
+    @DateTimeFormat(
+            pattern = "yyyy-MM-dd HH:mm:ss"
+    )
+    @JsonFormat(
+            timezone = "GMT+8",
+            pattern = "yyyy-MM-dd HH:mm:ss"
+    )
+    @TableField(
+            fill = FieldFill.INSERT
+    )
+    private Date createTime;
+    @DateTimeFormat(
+            pattern = "yyyy-MM-dd HH:mm:ss"
+    )
+    @JsonFormat(
+            timezone = "GMT+8",
+            pattern = "yyyy-MM-dd HH:mm:ss"
+    )
+    @TableField(
+            fill = FieldFill.INSERT_UPDATE
+    )
+    private Date updateTime;
+    @JsonSerialize(
+            using = ToStringSerializer.class
+    )
+    @TableField(
+            fill = FieldFill.INSERT
+    )
+    private long createUserId;
+    @TableField(
+            fill = FieldFill.INSERT
+    )
+    private String createUserName;
+    @JsonSerialize(
+            using = ToStringSerializer.class
+    )
+    @TableField(
+            fill = FieldFill.INSERT_UPDATE
+    )
+    private long updateUserId;
+    @TableField(
+            fill = FieldFill.INSERT_UPDATE
+    )
+    private String updateUserName;
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/pom.xml b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/pom.xml
index 9c25797..f07a555 100644
--- a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/pom.xml
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/pom.xml
@@ -30,5 +30,11 @@
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-bus-amqp</artifactId>
         </dependency>
+        <!--        支付宝-->
+        <dependency>
+            <groupId>com.alipay.sdk</groupId>
+            <artifactId>alipay-sdk-java</artifactId>
+            <version>4.10.124.ALL</version>
+        </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/mapper/SaasClientPayMapper.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/mapper/SaasClientPayMapper.java
new file mode 100644
index 0000000..6c774a2
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/mapper/SaasClientPayMapper.java
@@ -0,0 +1,34 @@
+package com.kidgrow.oprationcenter.mapper;
+
+import com.kidgrow.db.mapper.SuperMapper;
+import com.kidgrow.oprationcenter.model.SaasClientPay;
+import org.apache.ibatis.annotations.Param;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br>
+ * @Description: <br>
+ * @Project: 用户中心<br>
+ * @CreateDate: Created in 2020-09-17 16:21:03 <br>
+ * @Author: <a href="4345453@kidgrow.com">liuke</a>
+ * @version 1.0
+ */
+public interface SaasClientPayMapper extends SuperMapper<SaasClientPay> {
+    /**
+     * 分页查询列表
+     * @param page
+     * @param params
+     * @return
+     */
+    List<SaasClientPay> findList(Page<SaasClientPay> page, @Param("p") Map<String, Object> params);
+
+    /**
+     * 根据SaasClientPay对象当做查询条件进行查询
+     * @param
+     * @return SaasClientPay对象
+     */
+    SaasClientPay findByObject(@Param("p") SaasClientPay saasClientPay);
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/ISaasClientPayService.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/ISaasClientPayService.java
new file mode 100644
index 0000000..35b7fbe
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/ISaasClientPayService.java
@@ -0,0 +1,34 @@
+package com.kidgrow.oprationcenter.service;
+
+
+import com.kidgrow.common.model.PageResult;
+import com.kidgrow.common.service.ISuperService;
+import com.kidgrow.oprationcenter.model.SaasClientPay;
+
+import java.util.Map;
+
+/**
+ * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br>
+ * @Description: <br>
+ * @Project: 用户中心<br>
+ * @CreateDate: Created in 2020-09-17 16:21:03 <br>
+ * @Author: <a href="4345453@kidgrow.com">liuke</a>
+ * @version 1.0
+ */
+public interface ISaasClientPayService extends ISuperService<SaasClientPay> {
+    /**
+     * 列表
+     * @param params
+     * @return
+     */
+    PageResult<SaasClientPay> findList(Map<String, Object> params);
+
+
+    /**
+    * 根据SaasClientPay对象当做查询条件进行查询
+    * @param saasClientPay
+    * @return SaasClientPay对象
+    */
+    SaasClientPay findByObject(SaasClientPay saasClientPay);
+}
+
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/impl/SaasClientPayServiceImpl.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/impl/SaasClientPayServiceImpl.java
new file mode 100644
index 0000000..c66c4ad
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/java/com/kidgrow/oprationcenter/service/impl/SaasClientPayServiceImpl.java
@@ -0,0 +1,49 @@
+package com.kidgrow.oprationcenter.service.impl;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.kidgrow.common.model.PageResult;
+import com.kidgrow.common.service.impl.SuperServiceImpl;
+import com.kidgrow.oprationcenter.mapper.SaasClientPayMapper;
+import com.kidgrow.oprationcenter.model.SaasClientPay;
+import com.kidgrow.oprationcenter.service.ISaasClientPayService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.MapUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br>
+ * @Description: <br>
+ * @Project: 用户中心<br>
+ * @CreateDate: Created in 2020-09-17 16:21:03 <br>
+ * @Author: <a href="4345453@kidgrow.com">liuke</a>
+ * @version 1.0
+ */
+@Slf4j
+@Service
+public class SaasClientPayServiceImpl extends SuperServiceImpl<SaasClientPayMapper, SaasClientPay> implements ISaasClientPayService {
+    /**
+     * 列表
+     * @param params
+     * @return
+     */
+    @Override
+    public PageResult<SaasClientPay> findList(Map<String, Object> params){
+        Page<SaasClientPay> page = new Page<>(MapUtils.getInteger(params, "page"), MapUtils.getInteger(params, "limit"));
+        List<SaasClientPay> list  =  baseMapper.findList(page, params);
+        return PageResult.<SaasClientPay>builder().data(list).code(0).count(page.getTotal()).build();
+    }
+
+    /**
+   * 根据SaasClientPay对象当做查询条件进行查询
+   * @param saasClientPay
+   * @return SaasClientPay
+   */
+    @Override
+    public SaasClientPay findByObject(SaasClientPay saasClientPay){
+        return baseMapper.findByObject(saasClientPay);
+    }
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/resources/mapper/SaasClientPayMapper.xml b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/resources/mapper/SaasClientPayMapper.xml
new file mode 100644
index 0000000..5fcfbed
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-biz/src/main/resources/mapper/SaasClientPayMapper.xml
@@ -0,0 +1,88 @@
+<?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.kidgrow.oprationcenter.mapper.SaasClientPayMapper">
+    <!--定义查询列-->
+    <sql id="Column_List">
+	   *
+	</sql>
+
+    <!--sql查询片段-->
+    <sql id="where">
+        <where>
+            <!--查询条件自行添加-->
+            is_del=0
+                                    <if test="p.id != null and p.id !=''">
+                and  id =  #{p.id}
+            </if>
+                                                <if test="p.createHospitalDepartment != null and p.createHospitalDepartment !=''">
+                and  create_hospital_department =  #{p.createHospitalDepartment}
+            </if>
+                                                <if test="p.createHospitalDepartid != null and p.createHospitalDepartid !=''">
+                and  create_hospital_departid =  #{p.createHospitalDepartid}
+            </if>
+                                                <if test="p.createHospitalId != null and p.createHospitalId !=''">
+                and  create_hospital_id =  #{p.createHospitalId}
+            </if>
+                                                <if test="p.createHospitalName != null and p.createHospitalName !=''">
+                and  create_hospital_name =  #{p.createHospitalName}
+            </if>
+                                                <if test="p.payPrice != null and p.payPrice !=''">
+                and  pay_price =  #{p.payPrice}
+            </if>
+                                                <if test="p.outTradeNo != null and p.outTradeNo !=''">
+                and  out_trade_no =  #{p.outTradeNo}
+            </if>
+                                                <if test="p.payTime != null and p.payTime !=''">
+                and  pay_time =  #{p.payTime}
+            </if>
+                                                <if test="p.payStatus != null and p.payStatus !=''">
+                and  pay_status =  #{p.payStatus}
+            </if>
+                                                <if test="p.payMethod != null and p.payMethod !=''">
+                and  pay_method =  #{p.payMethod}
+            </if>
+                                                <if test="p.diaId != null and p.diaId !=''">
+                and  dia_id =  #{p.diaId}
+            </if>
+                                                <if test="p.tradeNo != null and p.tradeNo !=''">
+                and  trade_no =  #{p.tradeNo}
+            </if>
+                                                <if test="p.prepayId != null and p.prepayId !=''">
+                and  prepay_id =  #{p.prepayId}
+            </if>
+                                                <if test="p.createUserId != null and p.createUserId !=''">
+                and  create_user_id =  #{p.createUserId}
+            </if>
+                                                <if test="p.createUserName != null and p.createUserName !=''">
+                and  create_user_name =  #{p.createUserName}
+            </if>
+                                                <if test="p.updateUserId != null and p.updateUserId !=''">
+                and  update_user_id =  #{p.updateUserId}
+            </if>
+                                                <if test="p.updateUserName != null and p.updateUserName !=''">
+                and  update_user_name =  #{p.updateUserName}
+            </if>
+                                                <if test="p.updateTime != null and p.updateTime !=''">
+                and  update_time =  #{p.updateTime}
+            </if>
+                                </where>
+    </sql>
+
+    <!--定义根据-SaasClientPay当作查询条件返回对象-->
+    <select id="findByObject"  resultType="com.kidgrow.oprationcenter.model.SaasClientPay">
+        select <include refid="Column_List"/>
+        from saas_client_pay
+        <include refid="where"/>
+        order by id desc
+        limit 1
+    </select>
+
+    <!--定义根据-SaasClientPay当作查询条件返回对象集合-->
+    <select id="findList" resultType="com.kidgrow.oprationcenter.model.SaasClientPay">
+        select <include refid="Column_List"/>
+        from saas_client_pay
+        <include refid="where"/>
+        order by id desc
+    </select>
+</mapper>
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayConfig.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayConfig.java
new file mode 100644
index 0000000..330f9b5
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayConfig.java
@@ -0,0 +1,23 @@
+package com.kidgrow.oprationcenter.config.alipay;
+
+import com.alipay.api.AlipayClient;
+import com.alipay.api.DefaultAlipayClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import javax.annotation.Resource;
+
+@Configuration
+public class AlipayConfig {
+ 
+    @Resource
+    private AlipayProperties alipayProperties;
+ 
+    @Bean
+    public AlipayClient initAlipayClient(){
+        return new DefaultAlipayClient(alipayProperties.getGatewayUrl(),
+                alipayProperties.getAppId(),alipayProperties.getAppPrivateKey(),
+                alipayProperties.getFormat(),alipayProperties.getCharset(),
+                alipayProperties.getAlipayPublicKey(),alipayProperties.getSignType());
+    }
+}
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayProperties.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayProperties.java
new file mode 100644
index 0000000..31fb6aa
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/alipay/AlipayProperties.java
@@ -0,0 +1,22 @@
+package com.kidgrow.oprationcenter.config.alipay;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties("pay.alipay")
+public class AlipayProperties {
+ 
+    private String appId;
+    private String gatewayUrl;
+    private String format;
+    private String charset;
+    private String signType;
+    private String appPrivateKey;
+    private String alipayPublicKey;
+ 
+    private String returnUrl;
+    private String notifyUrl;
+}
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/IWXPayDomain.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/IWXPayDomain.java
new file mode 100644
index 0000000..0e0a07b
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/IWXPayDomain.java
@@ -0,0 +1,42 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+/**
+ * 域名管理,实现主备域名自动切换
+ */
+public abstract interface IWXPayDomain {
+    /**
+     * 上报域名网络状况
+     * @param domain 域名。 比如:api.mch.weixin.qq.com
+     * @param elapsedTimeMillis 耗时
+     * @param ex 网络请求中出现的异常。
+     *           null表示没有异常
+     *           ConnectTimeoutException,表示建立网络连接异常
+     *           UnknownHostException, 表示dns解析异常
+     */
+    abstract void report(final String domain, long elapsedTimeMillis, final Exception ex);
+
+    /**
+     * 获取域名
+     * @param config 配置
+     * @return 域名
+     */
+    abstract DomainInfo getDomain(final WXPayConfig config);
+
+    static class DomainInfo{
+        public String domain;       //域名
+        public boolean primaryDomain;     //该域名是否为主域名。例如:api.mch.weixin.qq.com为主域名
+        public DomainInfo(String domain, boolean primaryDomain) {
+            this.domain = domain;
+            this.primaryDomain = primaryDomain;
+        }
+
+        @Override
+        public String toString() {
+            return "DomainInfo{" +
+                    "domain='" + domain + '\'' +
+                    ", primaryDomain=" + primaryDomain +
+                    '}';
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyConfig.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyConfig.java
new file mode 100644
index 0000000..ab08742
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyConfig.java
@@ -0,0 +1,73 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+public class MyConfig extends WXPayConfig{
+
+    public MyConfig(WeiXinOfficPayProperties weiXinOfficPayProperties) {
+        this.weiXinOfficPayProperties = weiXinOfficPayProperties;
+    }
+
+    private WeiXinOfficPayProperties weiXinOfficPayProperties;
+
+    private byte[] certData;
+
+    public MyConfig() throws Exception {
+        String certPath = "D:\\develop\\chengxu\\houtai\\3cc.txt";
+        File file = new File(certPath);
+        InputStream certStream = new FileInputStream(file);
+        this.certData = new byte[(int) file.length()];
+        certStream.read(this.certData);
+        certStream.close();
+    }
+    //wx84c77dcda51c612c
+    @Override
+    public String getAppID() {
+        return weiXinOfficPayProperties.getAppID();
+//        return "wx84c77dcda51c612c";
+    }
+    @Override
+    //"1386873502"
+    public String getMchID() {
+        return weiXinOfficPayProperties.getMchID();
+//        return "1386873502";
+    }
+    @Override
+    //"GSFcX6WdgRTAS6154EW14WE3SGBSER49"
+    public String getKey() {
+        return weiXinOfficPayProperties.getKey();
+//        return "GSFcX6WdgRTAS6154EW14WE3SGBSER49";
+    }
+    @Override
+    public InputStream getCertStream() {
+        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
+        return certBis;
+    }
+    @Override
+    public int getHttpConnectTimeoutMs() {
+        return 8000;
+    }
+    @Override
+    public int getHttpReadTimeoutMs() {
+        return 10000;
+    }
+
+    @Override
+    IWXPayDomain getWXPayDomain() throws Exception {
+        MyIWXPayDomain myIWXPayDomain=new MyIWXPayDomain();
+        MyConfig myConfig = new MyConfig();
+        System.out.println(myConfig.getDomain());
+        myIWXPayDomain.getDomain(myConfig);
+        return myIWXPayDomain;
+    }
+
+    @Override
+    public String getDomain() {
+        return "api.mch.weixin.qq.com";
+    }
+
+    public String getPayNotifyUrl() {return weiXinOfficPayProperties.getPayNotifyUrl();}
+}
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyIWXPayDomain.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyIWXPayDomain.java
new file mode 100644
index 0000000..f32387d
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/MyIWXPayDomain.java
@@ -0,0 +1,14 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+public class MyIWXPayDomain implements IWXPayDomain {
+    @Override
+    public void report(String domain, long elapsedTimeMillis, Exception ex) {
+
+    }
+
+    @Override
+    public DomainInfo getDomain(WXPayConfig config) {
+        IWXPayDomain.DomainInfo domainInfo=new DomainInfo(config.getDomain(),true);
+        return domainInfo;
+    }
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPay.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPay.java
new file mode 100644
index 0000000..f8d17b6
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPay.java
@@ -0,0 +1,689 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+//import com.github.wxpay.sdk.WXPayConstants.SignType;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class WXPay {
+
+    private WXPayConfig config;
+    private WXPayConstants.SignType signType;
+    private boolean autoReport;
+    private boolean useSandbox;
+    private String notifyUrl;
+    private WXPayRequest wxPayRequest;
+
+    public WXPay(final WXPayConfig config) throws Exception {
+        this(config, null, true, false);
+    }
+
+    public WXPay(final WXPayConfig config, final boolean autoReport) throws Exception {
+        this(config, null, autoReport, false);
+    }
+
+
+    public WXPay(final WXPayConfig config, final boolean autoReport, final boolean useSandbox) throws Exception{
+        this(config, null, autoReport, useSandbox);
+    }
+
+    public WXPay(final WXPayConfig config, final String notifyUrl) throws Exception {
+        this(config, notifyUrl, true, false);
+    }
+
+    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport) throws Exception {
+        this(config, notifyUrl, autoReport, false);
+    }
+
+    public WXPay(final WXPayConfig config, final String notifyUrl, final boolean autoReport, final boolean useSandbox) throws Exception {
+        this.config = config;
+        this.notifyUrl = notifyUrl;
+        this.autoReport = autoReport;
+        this.useSandbox = useSandbox;
+        if (useSandbox) {
+            this.signType = WXPayConstants.SignType.MD5; // 沙箱环境
+        }
+        else {
+            this.signType = WXPayConstants.SignType.HMACSHA256;
+        }
+        this.wxPayRequest = new WXPayRequest(config);
+    }
+
+    private void checkWXPayConfig() throws Exception {
+        if (this.config == null) {
+            throw new Exception("config is null");
+        }
+        if (this.config.getAppID() == null || this.config.getAppID().trim().length() == 0) {
+            throw new Exception("appid in config is empty");
+        }
+        if (this.config.getMchID() == null || this.config.getMchID().trim().length() == 0) {
+            throw new Exception("appid in config is empty");
+        }
+//        if (this.config.getCertStream() == null) {
+//            throw new Exception("cert stream in config is empty");
+//        }
+        if (this.config.getWXPayDomain() == null){
+            throw new Exception("config.getWXPayDomain() is null");
+        }
+
+        if (this.config.getHttpConnectTimeoutMs() < 10) {
+            throw new Exception("http connect timeout is too small");
+        }
+        if (this.config.getHttpReadTimeoutMs() < 10) {
+            throw new Exception("http read timeout is too small");
+        }
+
+    }
+
+    /**
+     * 向 Map 中添加 appid、mch_id、nonce_str、sign_type、sign <br>
+     * 该函数适用于商户适用于统一下单等接口,不适用于红包、代金券接口
+     *
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> fillRequestData(Map<String, String> reqData) throws Exception {
+        reqData.put("appid", config.getAppID());
+        reqData.put("mch_id", config.getMchID());
+        reqData.put("nonce_str", WXPayUtil.generateNonceStr());
+        if (WXPayConstants.SignType.MD5.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.MD5);
+        }
+        else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {
+            reqData.put("sign_type", WXPayConstants.HMACSHA256);
+        }
+        reqData.put("sign", WXPayUtil.generateSignature(reqData, config.getKey(), this.signType));
+        return reqData;
+    }
+
+    /**
+     * 判断xml数据的sign是否有效,必须包含sign字段,否则返回false。
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isResponseSignatureValid(Map<String, String> reqData) throws Exception {
+        // 返回数据的签名方式和请求中给定的签名方式是一致的
+        return WXPayUtil.isSignatureValid(reqData, this.config.getKey(), this.signType);
+    }
+
+    /**
+     * 判断支付结果通知中的sign是否有效
+     *
+     * @param reqData 向wxpay post的请求数据
+     * @return 签名是否有效
+     * @throws Exception
+     */
+    public boolean isPayResultNotifySignatureValid(Map<String, String> reqData) 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, this.config.getKey(), signType);
+    }
+
+
+    /**
+     * 不需要证书的请求
+     * @param urlSuffix String
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithoutCert(String urlSuffix, Map<String, String> reqData,
+                                     int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String msgUUID = reqData.get("nonce_str");
+        String reqBody = WXPayUtil.mapToXml(reqData);
+
+        String resp = this.wxPayRequest.requestWithoutCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, autoReport);
+        return resp;
+    }
+
+
+    /**
+     * 需要证书的请求
+     * @param urlSuffix String
+     * @param reqData 向wxpay post的请求数据  Map
+     * @param connectTimeoutMs 超时时间,单位是毫秒
+     * @param readTimeoutMs 超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public String requestWithCert(String urlSuffix, Map<String, String> reqData,
+                                  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String msgUUID= reqData.get("nonce_str");
+        String reqBody = WXPayUtil.mapToXml(reqData);
+
+        String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
+        return resp;
+    }
+
+    /**
+     * 处理 HTTPS API返回数据,转换成Map对象。return_code为SUCCESS时,验证签名。
+     * @param xmlStr API返回的XML格式数据
+     * @return Map类型数据
+     * @throws Exception
+     */
+    public Map<String, String> processResponseXml(String xmlStr) 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 (this.isResponseSignatureValid(respData)) {
+               return respData;
+           }
+           else {
+               throw new Exception(String.format("Invalid sign value in XML: %s", xmlStr));
+           }
+        }
+        else {
+            throw new Exception(String.format("return_code value %s is invalid in XML: %s", return_code, xmlStr));
+        }
+    }
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData) throws Exception {
+        return this.microPay(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:提交刷卡支付<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> microPay(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_MICROPAY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.MICROPAY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+    /**
+     * 提交刷卡支付,针对软POS,尽可能做成功
+     * 内置重试机制,最多60s
+     * @param reqData
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> microPayWithPos(Map<String, String> reqData) throws Exception {
+        return this.microPayWithPos(reqData, this.config.getHttpConnectTimeoutMs());
+    }
+
+    /**
+     * 提交刷卡支付,针对软POS,尽可能做成功
+     * 内置重试机制,最多60s
+     * @param reqData
+     * @param connectTimeoutMs
+     * @return
+     * @throws Exception
+     */
+    public Map<String, String> microPayWithPos(Map<String, String> reqData, int connectTimeoutMs) throws Exception {
+        int remainingTimeMs = 60*1000;
+        long startTimestampMs = 0;
+        Map<String, String> lastResult = null;
+        Exception lastException = null;
+
+        while (true) {
+            startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+            int readTimeoutMs = remainingTimeMs - connectTimeoutMs;
+            if (readTimeoutMs > 1000) {
+                try {
+                    lastResult = this.microPay(reqData, connectTimeoutMs, readTimeoutMs);
+                    String returnCode = lastResult.get("return_code");
+                    if (returnCode.equals("SUCCESS")) {
+                        String resultCode = lastResult.get("result_code");
+                        String errCode = lastResult.get("err_code");
+                        if (resultCode.equals("SUCCESS")) {
+                            break;
+                        }
+                        else {
+                            // 看错误码,若支付结果未知,则重试提交刷卡支付
+                            if (errCode.equals("SYSTEMERROR") || errCode.equals("BANKERROR") || errCode.equals("USERPAYING")) {
+                                remainingTimeMs = remainingTimeMs - (int)(WXPayUtil.getCurrentTimestampMs() - startTimestampMs);
+                                if (remainingTimeMs <= 100) {
+                                    break;
+                                }
+                                else {
+                                    WXPayUtil.getLogger().info("microPayWithPos: try micropay again");
+                                    if (remainingTimeMs > 5*1000) {
+                                        Thread.sleep(5*1000);
+                                    }
+                                    else {
+                                        Thread.sleep(1*1000);
+                                    }
+                                    continue;
+                                }
+                            }
+                            else {
+                                break;
+                            }
+                        }
+                    }
+                    else {
+                        break;
+                    }
+                }
+                catch (Exception ex) {
+                    lastResult = null;
+                    lastException = ex;
+                }
+            }
+            else {
+                break;
+            }
+        }
+
+        if (lastResult == null) {
+            throw lastException;
+        }
+        else {
+            return lastResult;
+        }
+    }
+
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData) throws Exception {
+        return this.unifiedOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:统一下单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> unifiedOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_UNIFIEDORDER_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.UNIFIEDORDER_URL_SUFFIX;
+        }
+        if(this.notifyUrl != null) {
+            reqData.put("notify_url", this.notifyUrl);
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData) throws Exception {
+        return this.orderQuery(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:查询订单<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据 int
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> orderQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_ORDERQUERY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.ORDERQUERY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData) throws Exception {
+        return this.reverse(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:撤销订单<br>
+     * 场景:刷卡支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> reverse(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REVERSE_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REVERSE_URL_SUFFIX;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData) throws Exception {
+        return this.closeOrder(reqData, config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:关闭订单<br>
+     * 场景:公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> closeOrder(Map<String, String> reqData,  int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_CLOSEORDER_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.CLOSEORDER_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData) throws Exception {
+        return this.refund(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:申请退款<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付<br>
+     * 其他:需要证书
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUND_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REFUND_URL_SUFFIX;
+        }
+        String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData) throws Exception {
+        return this.refundQuery(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:退款查询<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> refundQuery(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REFUNDQUERY_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REFUNDQUERY_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:对账单下载(成功时返回对账单数据,失败时返回XML格式数据)<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> downloadBill(Map<String, String> reqData) throws Exception {
+        return this.downloadBill(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:对账单下载<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, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_DOWNLOADBILL_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.DOWNLOADBILL_URL_SUFFIX;
+        }
+        String respStr = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs).trim();
+        Map<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", "ok");
+            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) throws Exception {
+        return this.report(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:交易保障<br>
+     * 场景:刷卡支付、公共号支付、扫码支付、APP支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> report(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_REPORT_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.REPORT_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return WXPayUtil.xmlToMap(respXml);
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData) throws Exception {
+        return this.shortUrl(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:转换短链接<br>
+     * 场景:刷卡支付、扫码支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> shortUrl(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_SHORTURL_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.SHORTURL_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData) throws Exception {
+        return this.authCodeToOpenid(reqData, this.config.getHttpConnectTimeoutMs(), this.config.getHttpReadTimeoutMs());
+    }
+
+
+    /**
+     * 作用:授权码查询OPENID接口<br>
+     * 场景:刷卡支付
+     * @param reqData 向wxpay post的请求数据
+     * @param connectTimeoutMs 连接超时时间,单位是毫秒
+     * @param readTimeoutMs 读超时时间,单位是毫秒
+     * @return API返回数据
+     * @throws Exception
+     */
+    public Map<String, String> authCodeToOpenid(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
+        String url;
+        if (this.useSandbox) {
+            url = WXPayConstants.SANDBOX_AUTHCODETOOPENID_URL_SUFFIX;
+        }
+        else {
+            url = WXPayConstants.AUTHCODETOOPENID_URL_SUFFIX;
+        }
+        String respXml = this.requestWithoutCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
+        return this.processResponseXml(respXml);
+    }
+
+
+} // end class
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConfig.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConfig.java
new file mode 100644
index 0000000..00b4678
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConfig.java
@@ -0,0 +1,104 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+import java.io.InputStream;
+
+public abstract class WXPayConfig {
+
+
+
+    /**
+     * 获取 App ID
+     *
+     * @return App ID
+     */
+    abstract String getAppID();
+
+
+    /**
+     * 获取 Mch ID
+     *
+     * @return Mch ID
+     */
+    abstract String getMchID();
+
+
+    /**
+     * 获取 API 密钥
+     *
+     * @return API密钥
+     */
+    abstract String getKey();
+
+
+    /**
+     * 获取商户证书内容
+     *
+     * @return 商户证书内容
+     */
+    abstract InputStream getCertStream();
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpConnectTimeoutMs() {
+        return 6*1000;
+    }
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     *
+     * @return
+     */
+    public int getHttpReadTimeoutMs() {
+        return 8*1000;
+    }
+
+    /**
+     * 获取WXPayDomain, 用于多域名容灾自动切换
+     * @return
+     */
+    abstract IWXPayDomain getWXPayDomain()throws Exception ;
+
+    /**
+     * 是否自动上报。
+     * 若要关闭自动上报,子类中实现该函数返回 false 即可。
+     *
+     * @return
+     */
+    public boolean shouldAutoReport() {
+        return true;
+    }
+
+    /**
+     * 进行健康上报的线程的数量
+     *
+     * @return
+     */
+    public int getReportWorkerNum() {
+        return 6;
+    }
+
+
+    /**
+     * 健康上报缓存消息的最大数量。会有线程去独立上报
+     * 粗略计算:加入一条消息200B,10000消息占用空间 2000 KB,约为2MB,可以接受
+     *
+     * @return
+     */
+    public int getReportQueueMaxSize() {
+        return 10000;
+    }
+
+    /**
+     * 批量上报,一次最多上报多个数据
+     *
+     * @return
+     */
+    public int getReportBatchSize() {
+        return 10;
+    }
+
+    public abstract String getDomain();
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConstants.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConstants.java
new file mode 100644
index 0000000..71a5905
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayConstants.java
@@ -0,0 +1,59 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+import org.apache.http.client.HttpClient;
+
+/**
+ * 常量
+ */
+public class WXPayConstants {
+
+    public enum SignType {
+        MD5, HMACSHA256
+    }
+
+    public static final String DOMAIN_API = "api.mch.weixin.qq.com";
+    public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com";
+    public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com";
+    public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com";
+
+
+    public static final String FAIL     = "FAIL";
+    public static final String SUCCESS  = "SUCCESS";
+    public static final String HMACSHA256 = "HMAC-SHA256";
+    public static final String MD5 = "MD5";
+
+    public static final String FIELD_SIGN = "sign";
+    public static final String FIELD_SIGN_TYPE = "sign_type";
+
+    public static final String WXPAYSDK_VERSION = "WXPaySDK/3.0.9";
+    public static final String USER_AGENT = WXPAYSDK_VERSION +
+            " (" + System.getProperty("os.arch") + " " + System.getProperty("os.name") + " " + System.getProperty("os.version") +
+            ") Java/" + System.getProperty("java.version") + " HttpClient/" + HttpClient.class.getPackage().getImplementationVersion();
+
+    public static final String MICROPAY_URL_SUFFIX     = "/pay/micropay";
+    public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder";
+    public static final String ORDERQUERY_URL_SUFFIX   = "/pay/orderquery";
+    public static final String REVERSE_URL_SUFFIX      = "/secapi/pay/reverse";
+    public static final String CLOSEORDER_URL_SUFFIX   = "/pay/closeorder";
+    public static final String REFUND_URL_SUFFIX       = "/secapi/pay/refund";
+    public static final String REFUNDQUERY_URL_SUFFIX  = "/pay/refundquery";
+    public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill";
+    public static final String REPORT_URL_SUFFIX       = "/payitil/report";
+    public static final String SHORTURL_URL_SUFFIX     = "/tools/shorturl";
+    public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid";
+
+    // sandbox
+    public static final String SANDBOX_MICROPAY_URL_SUFFIX     = "/sandboxnew/pay/micropay";
+    public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
+    public static final String SANDBOX_ORDERQUERY_URL_SUFFIX   = "/sandboxnew/pay/orderquery";
+    public static final String SANDBOX_REVERSE_URL_SUFFIX      = "/sandboxnew/secapi/pay/reverse";
+    public static final String SANDBOX_CLOSEORDER_URL_SUFFIX   = "/sandboxnew/pay/closeorder";
+    public static final String SANDBOX_REFUND_URL_SUFFIX       = "/sandboxnew/secapi/pay/refund";
+    public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX  = "/sandboxnew/pay/refundquery";
+    public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
+    public static final String SANDBOX_REPORT_URL_SUFFIX       = "/sandboxnew/payitil/report";
+    public static final String SANDBOX_SHORTURL_URL_SUFFIX     = "/sandboxnew/tools/shorturl";
+    public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
+
+}
+
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayReport.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayReport.java
new file mode 100644
index 0000000..965d759
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayReport.java
@@ -0,0 +1,265 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * 交易保障
+ */
+public class WXPayReport {
+
+    public static class ReportInfo {
+
+        /**
+         * 布尔变量使用int。0为false, 1为true。
+         */
+
+        // 基本信息
+        private String version = "v1";
+        private String sdk = WXPayConstants.WXPAYSDK_VERSION;
+        private String uuid;  // 交易的标识
+        private long timestamp;   // 上报时的时间戳,单位秒
+        private long elapsedTimeMillis; // 耗时,单位 毫秒
+
+        // 针对主域名
+        private String firstDomain;  // 第1次请求的域名
+        private boolean primaryDomain; //是否主域名
+        private int firstConnectTimeoutMillis;  // 第1次请求设置的连接超时时间,单位 毫秒
+        private int firstReadTimeoutMillis;  // 第1次请求设置的读写超时时间,单位 毫秒
+        private int firstHasDnsError;  // 第1次请求是否出现dns问题
+        private int firstHasConnectTimeout; // 第1次请求是否出现连接超时
+        private int firstHasReadTimeout; // 第1次请求是否出现连接超时
+
+        public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis, boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
+            this.uuid = uuid;
+            this.timestamp = timestamp;
+            this.elapsedTimeMillis = elapsedTimeMillis;
+            this.firstDomain = firstDomain;
+            this.primaryDomain = primaryDomain;
+            this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;
+            this.firstReadTimeoutMillis = firstReadTimeoutMillis;
+            this.firstHasDnsError = firstHasDnsError?1:0;
+            this.firstHasConnectTimeout = firstHasConnectTimeout?1:0;
+            this.firstHasReadTimeout = firstHasReadTimeout?1:0;
+         }
+
+        @Override
+        public String toString() {
+            return "ReportInfo{" +
+                    "version='" + version + '\'' +
+                    ", sdk='" + sdk + '\'' +
+                    ", uuid='" + uuid + '\'' +
+                    ", timestamp=" + timestamp +
+                    ", elapsedTimeMillis=" + elapsedTimeMillis +
+                    ", firstDomain='" + firstDomain + '\'' +
+                    ", primaryDomain=" + primaryDomain +
+                    ", firstConnectTimeoutMillis=" + firstConnectTimeoutMillis +
+                    ", firstReadTimeoutMillis=" + firstReadTimeoutMillis +
+                    ", firstHasDnsError=" + firstHasDnsError +
+                    ", firstHasConnectTimeout=" + firstHasConnectTimeout +
+                    ", firstHasReadTimeout=" + firstHasReadTimeout +
+                    '}';
+        }
+
+        /**
+         * 转换成 csv 格式
+         *
+         * @return
+         */
+        public String toLineString(String key) {
+            String separator = ",";
+            Object[] objects = new Object[] {
+                version, sdk, uuid, timestamp, elapsedTimeMillis,
+                    firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
+                    firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout
+            };
+            StringBuffer sb = new StringBuffer();
+            for(Object obj: objects) {
+                sb.append(obj).append(separator);
+            }
+            try {
+                String sign = WXPayUtil.HMACSHA256(sb.toString(), key);
+                sb.append(sign);
+                return sb.toString();
+            }
+            catch (Exception ex) {
+                return null;
+            }
+
+        }
+
+    }
+
+    private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default";
+    // private static final String REPORT_URL = "http://127.0.0.1:5000/test";
+
+
+    private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6*1000;
+    private static final int DEFAULT_READ_TIMEOUT_MS = 8*1000;
+
+    private LinkedBlockingQueue<String> reportMsgQueue = null;
+    private WXPayConfig config;
+    private ExecutorService executorService;
+
+    private volatile static WXPayReport INSTANCE;
+
+    private WXPayReport(final WXPayConfig config) {
+        this.config = config;
+        reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize());
+
+        // 添加处理线程
+        executorService = Executors.newFixedThreadPool(config.getReportWorkerNum(), new ThreadFactory() {
+            public Thread newThread(Runnable r) {
+                Thread t = Executors.defaultThreadFactory().newThread(r);
+                t.setDaemon(true);
+                return t;
+            }
+        });
+
+        if (config.shouldAutoReport()) {
+            WXPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());
+            for (int i = 0; i < config.getReportWorkerNum(); ++i) {
+                executorService.execute(new Runnable() {
+                    public void run() {
+                        while (true) {
+                            // 先用 take 获取数据
+                            try {
+                                StringBuffer sb = new StringBuffer();
+                                String firstMsg = reportMsgQueue.take();
+                                WXPayUtil.getLogger().info("get first report msg: {}", firstMsg);
+                                String msg = null;
+                                sb.append(firstMsg); //会阻塞至有消息
+                                int remainNum = config.getReportBatchSize() - 1;
+                                for (int j=0; j<remainNum; ++j) {
+                                    WXPayUtil.getLogger().info("try get remain report msg");
+                                    // msg = reportMsgQueue.poll();  // 不阻塞了
+                                    msg = reportMsgQueue.take();
+                                    WXPayUtil.getLogger().info("get remain report msg: {}", msg);
+                                    if (msg == null) {
+                                        break;
+                                    }
+                                    else {
+                                        sb.append("\n");
+                                        sb.append(msg);
+                                    }
+                                }
+                                // 上报
+                                WXPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+                            }
+                            catch (Exception ex) {
+                                WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
+                            }
+                        }
+                    }
+                });
+            }
+        }
+
+    }
+
+    /**
+     * 单例,双重校验,请在 JDK 1.5及更高版本中使用
+     *
+     * @param config
+     * @return
+     */
+    public static WXPayReport getInstance(WXPayConfig config) {
+        if (INSTANCE == null) {
+            synchronized (WXPayReport.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new WXPayReport(config);
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    public void report(String uuid, long elapsedTimeMillis,
+                       String firstDomain, boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,
+                       boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout) {
+        long currentTimestamp = WXPayUtil.getCurrentTimestamp();
+        ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis,
+                firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
+                firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout);
+        String data = reportInfo.toLineString(config.getKey());
+        WXPayUtil.getLogger().info("report {}", data);
+        if (data != null) {
+            reportMsgQueue.offer(data);
+        }
+    }
+
+
+    @Deprecated
+    private void reportSync(final String data) throws Exception {
+        httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+    }
+
+    @Deprecated
+    private void reportAsync(final String data) throws Exception {
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    httpRequest(data, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_READ_TIMEOUT_MS);
+                }
+                catch (Exception ex) {
+                    WXPayUtil.getLogger().warn("report fail. reason: {}", ex.getMessage());
+                }
+            }
+        }).start();
+    }
+
+    /**
+     * http 请求
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     * @throws Exception
+     */
+    private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs) throws Exception{
+        BasicHttpClientConnectionManager connManager;
+        connManager = new BasicHttpClientConnectionManager(
+                RegistryBuilder.<ConnectionSocketFactory>create()
+                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                        .register("https", SSLConnectionSocketFactory.getSocketFactory())
+                        .build(),
+                null,
+                null,
+                null
+        );
+        HttpClient httpClient = HttpClientBuilder.create()
+                .setConnectionManager(connManager)
+                .build();
+
+        HttpPost httpPost = new HttpPost(REPORT_URL);
+
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+        httpPost.setConfig(requestConfig);
+
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
+        httpPost.addHeader("Content-Type", "text/xml");
+        httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT);
+        httpPost.setEntity(postEntity);
+
+        HttpResponse httpResponse = httpClient.execute(httpPost);
+        HttpEntity httpEntity = httpResponse.getEntity();
+        return EntityUtils.toString(httpEntity, "UTF-8");
+    }
+
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayRequest.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayRequest.java
new file mode 100644
index 0000000..f59ff8a
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayRequest.java
@@ -0,0 +1,258 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+
+//import static com.github.wxpay.sdk.WXPayConstants.USER_AGENT;
+
+public class WXPayRequest {
+    private WXPayConfig config;
+    public WXPayRequest(WXPayConfig config) throws Exception{
+
+        this.config = config;
+    }
+
+    /**
+     * 请求,只请求一次,不做重试
+     * @param domain
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @param useCert 是否使用证书,针对退款、撤销等操作
+     * @return
+     * @throws Exception
+     */
+    private String requestOnce(final String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
+        BasicHttpClientConnectionManager connManager;
+        if (useCert) {
+            // 证书
+            char[] password = config.getMchID().toCharArray();
+            InputStream certStream = config.getCertStream();
+            KeyStore ks = KeyStore.getInstance("PKCS12");
+            ks.load(certStream, password);
+
+            // 实例化密钥库 & 初始化密钥工厂
+            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(ks, password);
+
+            // 创建 SSLContext
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+
+            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
+                    sslContext,
+                    new String[]{"TLSv1"},
+                    null,
+                    new DefaultHostnameVerifier());
+
+            connManager = new BasicHttpClientConnectionManager(
+                    RegistryBuilder.<ConnectionSocketFactory>create()
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                            .register("https", sslConnectionSocketFactory)
+                            .build(),
+                    null,
+                    null,
+                    null
+            );
+        }
+        else {
+            connManager = new BasicHttpClientConnectionManager(
+                    RegistryBuilder.<ConnectionSocketFactory>create()
+                            .register("http", PlainConnectionSocketFactory.getSocketFactory())
+                            .register("https", SSLConnectionSocketFactory.getSocketFactory())
+                            .build(),
+                    null,
+                    null,
+                    null
+            );
+        }
+
+        HttpClient httpClient = HttpClientBuilder.create()
+                .setConnectionManager(connManager)
+                .build();
+
+        String url = "https://" + domain + urlSuffix;
+        HttpPost httpPost = new HttpPost(url);
+
+        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
+        httpPost.setConfig(requestConfig);
+
+        StringEntity postEntity = new StringEntity(data, "UTF-8");
+        httpPost.addHeader("Content-Type", "text/xml");
+        httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + config.getMchID());
+        httpPost.setEntity(postEntity);
+
+        HttpResponse httpResponse = httpClient.execute(httpPost);
+        HttpEntity httpEntity = httpResponse.getEntity();
+        return EntityUtils.toString(httpEntity, "UTF-8");
+
+    }
+
+
+    private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
+        Exception exception = null;
+        long elapsedTimeMillis = 0;
+        long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
+        boolean firstHasDnsErr = false;
+        boolean firstHasConnectTimeout = false;
+        boolean firstHasReadTimeout = false;
+        IWXPayDomain.DomainInfo domainInfo = config.getWXPayDomain().getDomain(config);
+        if(domainInfo == null){
+            throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
+        }
+        try {
+            String result = requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, null);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+            return result;
+        }
+        catch (UnknownHostException ex) {  // dns 解析错误,或域名不存在
+            exception = ex;
+            firstHasDnsErr = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout
+            );
+        }
+        catch (ConnectTimeoutException ex) {
+            exception = ex;
+            firstHasConnectTimeout = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout
+            );
+        }
+        catch (SocketTimeoutException ex) {
+            exception = ex;
+            firstHasReadTimeout = true;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+        }
+        catch (Exception ex) {
+            exception = ex;
+            elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs()-startTimestampMs;
+            WXPayReport.getInstance(config).report(
+                    uuid,
+                    elapsedTimeMillis,
+                    domainInfo.domain,
+                    domainInfo.primaryDomain,
+                    connectTimeoutMs,
+                    readTimeoutMs,
+                    firstHasDnsErr,
+                    firstHasConnectTimeout,
+                    firstHasReadTimeout);
+        }
+        config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, exception);
+        throw exception;
+    }
+
+
+    /**
+     * 可重试的,非双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @return
+     */
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), false, autoReport);
+    }
+
+    /**
+     * 可重试的,非双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     */
+    public String requestWithoutCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs,  boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, false, autoReport);
+    }
+
+    /**
+     * 可重试的,双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @return
+     */
+    public String requestWithCert(String urlSuffix, String uuid, String data, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs(), true, autoReport);
+    }
+
+    /**
+     * 可重试的,双向认证的请求
+     * @param urlSuffix
+     * @param uuid
+     * @param data
+     * @param connectTimeoutMs
+     * @param readTimeoutMs
+     * @return
+     */
+    public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
+        return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
+    }
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayUtil.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayUtil.java
new file mode 100644
index 0000000..eb8dc9f
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayUtil.java
@@ -0,0 +1,296 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+//import com.github.wxpay.sdk.WXPayConstants.SignType;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.*;
+
+
+public class WXPayUtil {
+
+    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+    private static final Random RANDOM = new SecureRandom();
+
+    /**
+     * XML格式字符串转换为Map
+     *
+     * @param strXML XML字符串
+     * @return XML数据转换后的Map
+     * @throws Exception
+     */
+    public static Map<String, String> xmlToMap(String strXML) throws Exception {
+        try {
+            Map<String, String> data = new HashMap<String, String>();
+            DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
+            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
+            org.w3c.dom.Document doc = documentBuilder.parse(stream);
+            doc.getDocumentElement().normalize();
+            NodeList nodeList = doc.getDocumentElement().getChildNodes();
+            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
+                Node node = nodeList.item(idx);
+                if (node.getNodeType() == Node.ELEMENT_NODE) {
+                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
+                    data.put(element.getNodeName(), element.getTextContent());
+                }
+            }
+            try {
+                stream.close();
+            } catch (Exception ex) {
+                // do nothing
+            }
+            return data;
+        } catch (Exception ex) {
+            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
+            throw ex;
+        }
+
+    }
+
+    /**
+     * 将Map转换为XML格式的字符串
+     *
+     * @param data Map类型数据
+     * @return XML格式的字符串
+     * @throws Exception
+     */
+    public static String mapToXml(Map<String, String> data) throws Exception {
+        org.w3c.dom.Document document = WXPayXmlUtil.newDocument();
+        org.w3c.dom.Element root = document.createElement("xml");
+        document.appendChild(root);
+        for (String key: data.keySet()) {
+            String value = data.get(key);
+            if (value == null) {
+                value = "";
+            }
+            value = value.trim();
+            org.w3c.dom.Element filed = document.createElement(key);
+            filed.appendChild(document.createTextNode(value));
+            root.appendChild(filed);
+        }
+        TransformerFactory tf = TransformerFactory.newInstance();
+        Transformer transformer = tf.newTransformer();
+        DOMSource source = new DOMSource(document);
+        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+        StringWriter writer = new StringWriter();
+        StreamResult result = new StreamResult(writer);
+        transformer.transform(source, result);
+        String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
+        try {
+            writer.close();
+        }
+        catch (Exception ex) {
+        }
+        return output;
+    }
+
+
+    /**
+     * 生成带有 sign 的 XML 格式字符串
+     *
+     * @param data Map类型数据
+     * @param key API密钥
+     * @return 含有sign字段的XML
+     */
+    public static String generateSignedXml(final Map<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() {
+        char[] nonceChars = new char[32];
+        for (int index = 0; index < nonceChars.length; ++index) {
+            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
+        }
+        return new String(nonceChars);
+    }
+
+
+    /**
+     * 生成 MD5
+     *
+     * @param data 待处理数据
+     * @return MD5结果
+     */
+    public static String MD5(String data) throws Exception {
+        MessageDigest md = MessageDigest.getInstance("MD5");
+        byte[] array = md.digest(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 生成 HMACSHA256
+     * @param data 待处理数据
+     * @param key 密钥
+     * @return 加密结果
+     * @throws Exception
+     */
+    public static String HMACSHA256(String data, String key) throws Exception {
+        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
+        sha256_HMAC.init(secret_key);
+        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
+        StringBuilder sb = new StringBuilder();
+        for (byte item : array) {
+            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
+        }
+        return sb.toString().toUpperCase();
+    }
+
+    /**
+     * 日志
+     * @return
+     */
+    public static Logger getLogger() {
+        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
+        return logger;
+    }
+
+    /**
+     * 获取当前时间戳,单位秒
+     * @return
+     */
+    public static long getCurrentTimestamp() {
+        return System.currentTimeMillis()/1000;
+    }
+
+    /**
+     * 获取当前时间戳,单位毫秒
+     * @return
+     */
+    public static long getCurrentTimestampMs() {
+        return System.currentTimeMillis();
+    }
+
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayXmlUtil.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayXmlUtil.java
new file mode 100644
index 0000000..b4a6a7f
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WXPayXmlUtil.java
@@ -0,0 +1,30 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+import org.w3c.dom.Document;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ * 2018/7/3
+ */
+public final class WXPayXmlUtil {
+    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
+        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        documentBuilderFactory.setXIncludeAware(false);
+        documentBuilderFactory.setExpandEntityReferences(false);
+
+        return documentBuilderFactory.newDocumentBuilder();
+    }
+
+    public static Document newDocument() throws ParserConfigurationException {
+        return newDocumentBuilder().newDocument();
+    }
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WeiXinOfficPayProperties.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WeiXinOfficPayProperties.java
new file mode 100644
index 0000000..1dbded0
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/config/weixin/WeiXinOfficPayProperties.java
@@ -0,0 +1,51 @@
+package com.kidgrow.oprationcenter.config.weixin;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties("pay.wxpay")
+@Data
+public class WeiXinOfficPayProperties  {
+    /**
+     * appID
+     */
+    private String appID;
+
+    /**
+     * 商户号
+     */
+    private String mchID;
+
+    /**
+     * API 密钥
+     */
+    private String key;
+
+    /**
+     * API证书绝对路径 (本项目放在了 resources/cert/wxpay/apiclient_cert.p12")
+     */
+    private String certPath;
+
+    /**
+     * HTTP(S) 连接超时时间,单位毫秒
+     */
+    private int httpConnectTimeoutMs = 8000;
+
+    /**
+     * HTTP(S) 读数据超时时间,单位毫秒
+     */
+    private int httpReadTimeoutMs = 10000;
+
+    /**
+     * 微信支付异步通知地址
+     */
+    private String payNotifyUrl;
+
+    /**
+     * 微信退款异步通知地址
+     */
+    private String refundNotifyUrl;
+
+}
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/SaasClientPayController.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/SaasClientPayController.java
new file mode 100644
index 0000000..68a50d2
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/SaasClientPayController.java
@@ -0,0 +1,114 @@
+package com.kidgrow.oprationcenter.controller;
+
+import com.kidgrow.common.controller.BaseController;
+import com.kidgrow.common.model.PageResult;
+import com.kidgrow.common.model.ResultBody;
+import com.kidgrow.oprationcenter.model.SaasClientPay;
+import com.kidgrow.oprationcenter.service.ISaasClientPayService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * 石家庄喜高科技有限责任公司 版权所有 © Copyright 2020<br>
+ * @Description: 
+ * @Project: 用户中心
+ * @CreateDate: Created in 2020-09-17 16:21:03 <br>
+ * @Author: <a href="4345453@kidgrow.com">liuke</a>
+ * @version: 1.0
+ */
+@Slf4j
+@RestController
+@RequestMapping("/saasclientpay")
+@Api(tags = "")
+public class SaasClientPayController extends BaseController {
+    @Autowired
+    private ISaasClientPayService saasClientPayService;
+
+    /**
+     * 列表
+     */
+    @ApiOperation(value = "查询列表")
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "page", value = "分页起始位置", required = true, dataType = "Integer"),
+            @ApiImplicitParam(name = "limit", value = "分页结束位置", required = true, dataType = "Integer")
+    })
+    @GetMapping
+    public ResultBody<PageResult> list(@RequestParam Map<String, Object> params) {
+        if(params.size()==0){
+            params.put("page",1);
+            params.put("limit",10);
+        }
+        return ResultBody.ok().data(saasClientPayService.findList(params));
+    }
+
+    /**
+     * 查询
+     */
+    @ApiOperation(value = "查询")
+    @GetMapping("/{id}")
+    public ResultBody findById(@PathVariable Long id) {
+        SaasClientPay model = saasClientPayService.getById(id);
+        return ResultBody.ok().data(model).msg("查询成功");
+    }
+
+    /**
+     * 根据SaasClientPay当做查询条件进行查询
+     */
+    @ApiOperation(value = "根据SaasClientPay当做查询条件进行查询")
+    @PostMapping("/query")
+    public ResultBody findByObject(@RequestBody SaasClientPay saasClientPay) {
+        SaasClientPay model = saasClientPayService.findByObject(saasClientPay);
+        return ResultBody.ok().data(model).msg("查询成功");
+    }
+
+    /**
+     * 新增or更新
+     */
+    @ApiOperation(value = "保存")
+    @PostMapping
+    public ResultBody save(@Valid @RequestBody SaasClientPay saasClientPay, BindingResult bindingResult) {
+        List<String> errMsg= new ArrayList<>();
+        if (bindingResult.hasErrors()) {
+            for (ObjectError error : bindingResult.getAllErrors()) {
+                errMsg.add(error.getDefaultMessage());
+            }
+            return ResultBody.failed().msg(errMsg.toString());
+        } else {
+            boolean v= saasClientPayService.saveOrUpdate(saasClientPay);
+            if(v) {
+                return ResultBody.ok().data(saasClientPay).msg("保存成功");
+            }
+            else {
+                return ResultBody.failed().msg("保存失败");
+            }
+        }
+    }
+
+    /**
+     * 删除
+     */
+    @ApiOperation(value = "删除")
+    @DeleteMapping("/{id}")
+    public ResultBody delete(@PathVariable Long id) {
+        boolean v= saasClientPayService.removeById(id);
+        if(v) {
+            return ResultBody.ok().msg("删除成功");
+        }
+        else {
+            return ResultBody.failed().msg("删除失败");
+        }
+    }
+}
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/alipay/AlipayController.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/alipay/AlipayController.java
new file mode 100644
index 0000000..eab0b5c
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/alipay/AlipayController.java
@@ -0,0 +1,181 @@
+package com.kidgrow.oprationcenter.controller.alipay;
+
+import com.alipay.api.AlipayClient;
+import com.alipay.api.domain.AlipayTradeCancelModel;
+import com.alipay.api.domain.AlipayTradePrecreateModel;
+import com.alipay.api.internal.util.AlipaySignature;
+import com.alipay.api.request.AlipayTradeCancelRequest;
+import com.alipay.api.request.AlipayTradePrecreateRequest;
+import com.alipay.api.response.AlipayTradeCancelResponse;
+import com.alipay.api.response.AlipayTradePrecreateResponse;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.kidgrow.common.model.ResultBody;
+import com.kidgrow.common.utils.DateUtils;
+import com.kidgrow.common.utils.QRCodeUtil;
+import com.kidgrow.common.utils.StringUtils;
+import com.kidgrow.oprationcenter.config.alipay.AlipayProperties;
+import com.kidgrow.oprationcenter.model.SaasClientPay;
+import com.kidgrow.oprationcenter.service.ISaasClientPayService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.Instant;
+import java.util.*;
+
+@Slf4j
+@RestController
+@RequestMapping("alipay")
+@Api(tags = "支付宝的支付controller")
+public class AlipayController  {
+ 
+    @Resource
+    private AlipayClient alipayClient;
+    @Resource
+    private AlipayProperties alipayProperties;
+    @Autowired
+    private ISaasClientPayService saasClientPayService;
+
+    @ApiOperation(value = "调用预支付的接口,生成二维码")
+    @PostMapping("/precreate")
+    public ResultBody precreate(HttpServletResponse response) throws Exception{  //商户预创建支付订单,生成二维码
+
+        String outTradeNo = UUID.randomUUID().toString().replaceAll("_", "");
+        QueryWrapper queryWrapper = new QueryWrapper();
+        queryWrapper.eq("dia_id","1");
+        queryWrapper.eq("pay_status",0);
+        AlipayTradePrecreateModel model=new AlipayTradePrecreateModel();
+        model.setProductCode("FACE_TO_FACE_PAYMENT"); //销售产品码
+        List<SaasClientPay> list = saasClientPayService.list(queryWrapper);
+        if (!list.isEmpty()) {
+            SaasClientPay saasClientPay = list.get(0);
+            model.setOutTradeNo(outTradeNo);                   //商户订单号
+            model.setSubject("海贼王");   //订单标题
+            model.setTotalAmount(Double.valueOf(saasClientPay.getPayPrice())/100+"");  //订单总金额
+        }else {
+            model.setOutTradeNo(outTradeNo);                   //商户订单号
+            model.setSubject("海贼王");   //订单标题
+            model.setTotalAmount("0.01");  //订单总金额
+            SaasClientPay saasClientPay = this.fengData(model);
+            boolean b = saasClientPayService.saveOrUpdate(saasClientPay);
+        }
+        AlipayTradePrecreateRequest request=new AlipayTradePrecreateRequest();
+        request.setBizModel(model);
+        //兼容pkcs1 编码;
+        java.security.Security.addProvider(
+                new org.bouncycastle.jce.provider.BouncyCastleProvider()
+        );
+        AlipayTradePrecreateResponse alipayTradePrecreateResponse=alipayClient.execute(request);
+        if (StringUtils.isBlank(alipayTradePrecreateResponse.getQrCode())) {
+            return ResultBody.failed().data("生成二维码失败");
+        }
+        String content=alipayTradePrecreateResponse.getQrCode();
+        String base64 = QRCodeUtil.creatRrCode(content, 200, 200, 0);
+        System.out.println(base64);
+        return ResultBody.ok().data(base64);
+    }
+    //数据封装
+    public SaasClientPay fengData(AlipayTradePrecreateModel model){
+        SaasClientPay saasClientPay = new SaasClientPay();
+        saasClientPay.setCreateHospitalDepartment("");
+        saasClientPay.setCreateHospitalDepartid("");
+        saasClientPay.setCreateHospitalId(Long.valueOf(1L));
+        saasClientPay.setPayPrice(1);
+        saasClientPay.setOutTradeNo(model.getOutTradeNo());
+        saasClientPay.setPayStatus(0);
+        saasClientPay.setPayMethod(1);
+        saasClientPay.setDiaId("1");
+        saasClientPay.setId( UUID.randomUUID().toString().replaceAll("_", ""));
+        saasClientPay.setCreateTime(new Date());
+        saasClientPay.setCreateUserId(Long.valueOf(1L));
+        saasClientPay.setCreateUserName("");
+        return saasClientPay;
+    }
+    @ApiOperation(value = "取消订单,支付超时、支付结果未知是可撤销,超过24小时不可撤销-------暂时没有配置")
+    @PostMapping("/cancel")
+    public ResultBody cancel() throws Exception{  //取消订单,支付超时、支付结果未知是可撤销,超过24小时不可撤销
+        AlipayTradeCancelModel model=new AlipayTradeCancelModel();
+        model.setOutTradeNo("300");
+ 
+        AlipayTradeCancelRequest request=new AlipayTradeCancelRequest();
+        request.setBizModel(model);
+ 
+        AlipayTradeCancelResponse response=alipayClient.execute(request);
+        return ResultBody.ok().data(response.getBody());
+    }
+
+    @ApiOperation(value = "trade_success状态下异步通知接口,端口异常的接口")
+    @PostMapping("/notify")
+    public ResultBody notify(HttpServletRequest request) throws Exception{   //trade_success状态下异步通知接口
+        if (check(request.getParameterMap())){
+            System.out.println(request.getParameter("trade_status"));
+            Map<String, String> requestMap = this.getRequestMap(request.getParameterMap());
+            log.error("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
+            log.error(request.toString());
+            log.error("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
+            System.out.println("异步通知 "+ Instant.now());
+        }else {
+            System.out.println("验签失败");
+        }
+        return ResultBody.ok().data("订单失败");
+    }
+    @ApiOperation(value = "订单支付成功后同步返回地址")
+    @PostMapping("/return")
+    public ResultBody returnUrl(HttpServletRequest request, Map<String,Object> map) throws Exception{  //订单支付成功后同步返回地址
+        if (check(request.getParameterMap())){
+            Map<String, String> requestMap = this.getRequestMap(request.getParameterMap());
+            log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww");
+            log.error(request.toString());
+            log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww");
+            log.error(map.toString());
+            log.error("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
+            String trade_status = requestMap.get("trade_status");
+            if(StringUtils.isNotBlank(trade_status)){
+                //支付成功
+                if(trade_status.equals("TRADE_SUCCESS")){
+                    //更新状态 out_trade_no
+                    QueryWrapper queryWrapper = new QueryWrapper();
+                    queryWrapper.eq("out_trade_no",requestMap.get("out_trade_no"));
+                    queryWrapper.eq("pay_status",0);
+                    List<SaasClientPay> list = saasClientPayService.list(queryWrapper);
+                    if(!list.isEmpty()){
+                        SaasClientPay saasClientPay = list.get(0);
+                        saasClientPay.setPayTime(DateUtils.parseDate(requestMap.get("gmt_payment")));
+                        saasClientPay.setPayStatus(2);
+                        saasClientPay.setTradeNo(requestMap.get("trade_no"));
+                        boolean b = saasClientPayService.saveOrUpdate(saasClientPay);
+                    }
+                }
+            }
+            return ResultBody.ok().data("success");
+        }else {
+            return ResultBody.failed().data("false");
+        }
+    }
+ private Map<String,String> getRequestMap(Map<String,String[]> requestParams){
+     Map<String,String> params = new HashMap<>();
+     for (String name : requestParams.keySet()) {
+         String[] values = requestParams.get(name);
+         String valueStr = "";
+         for (int i = 0; i < values.length; i++) {
+             valueStr = (i == values.length - 1) ? valueStr + values[i]
+                     : valueStr + values[i] + ",";
+         }
+         params.put(name, valueStr);
+         System.out.println(name+" ==> "+valueStr);
+     }
+     return params;
+ }
+    private boolean check(Map<String,String[]> requestParams) throws Exception{  //对return、notify参数进行验签
+        Map<String, String> requestMap = this.getRequestMap(requestParams);
+        return AlipaySignature.rsaCheckV1(requestMap, alipayProperties.getAlipayPublicKey(),
+                alipayProperties.getCharset(), alipayProperties.getSignType()); //调用SDK验证签名
+    }
+}
\ No newline at end of file
diff --git a/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/weixin/WxController.java b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/weixin/WxController.java
new file mode 100644
index 0000000..3a2ed62
--- /dev/null
+++ b/kidgrow-business/kidgrow-opration-center/kidgrow-opration-center-server/src/main/java/com/kidgrow/oprationcenter/controller/weixin/WxController.java
@@ -0,0 +1,142 @@
+package com.kidgrow.oprationcenter.controller.weixin;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.kidgrow.common.model.ResultBody;
+import com.kidgrow.common.utils.DateUtils;
+import com.kidgrow.common.utils.QRCodeUtil;
+import com.kidgrow.common.utils.StringUtils;
+import com.kidgrow.oprationcenter.config.weixin.MyConfig;
+import com.kidgrow.oprationcenter.config.weixin.WXPay;
+import com.kidgrow.oprationcenter.config.weixin.WXPayUtil;
+import com.kidgrow.oprationcenter.config.weixin.WeiXinOfficPayProperties;
+import com.kidgrow.oprationcenter.model.SaasClientPay;
+import com.kidgrow.oprationcenter.service.ISaasClientPayService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.MapUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+
+
+@Slf4j
+@RestController
+@RequestMapping("/wxController")
+@Api(tags = "复诊相关操作")
+public class WxController  {
+    /**
+     * @param map
+     * @return
+     */
+    @Autowired
+    ISaasClientPayService iSaasClientPayService;
+    @Resource
+    WeiXinOfficPayProperties weiXinOfficPayProperties;
+
+    @ApiOperation(value = "保存")
+    @PostMapping("pay")
+    public ResultBody save(@RequestBody Map<String,Object> map) throws Exception {
+        Long hospitalId = MapUtils.getLong(map, "hospitalId");
+        Long doctorId = MapUtils.getLong(map, "doctorId");
+        Long diaId = MapUtils.getLong(map, "diaId");
+        if (hospitalId!=null) {
+            return ResultBody.failed().data("请输入医院id");
+        }
+        if (doctorId!=null) {
+            return ResultBody.failed().data("请输入医生id");
+        }
+        if (diaId!=null) {
+            return ResultBody.failed().data("请输入诊断记录ID");
+        }
+        Map<String,String> jsonObject=new HashMap<>();
+        Map<String,Object> mapto=new HashMap<>();
+        jsonObject.put("body","腾讯");//商品描述
+        jsonObject.put("out_trade_no","20150806123434");//商户订单号
+        jsonObject.put("total_fee","1");//标价金额
+        jsonObject.put("spbill_create_ip","192.168.2.240");//终端IP
+//        jsonObject.put("notify_url",myConfig.getPayNotifyUrl());//通知地址
+        jsonObject.put("notify_url","http://open.zuul.kidgrow.cloud/api-record/wxController/callback");//通知地址
+        jsonObject.put("trade_type","NATIVE");//交易类型
+        MyConfig wxPayConfig= new MyConfig(weiXinOfficPayProperties);
+
+        WXPay wxPay=new WXPay(wxPayConfig);
+        Map<String, String> stringStringMap = wxPay.fillRequestData(jsonObject);
+        Map<String, String> result = wxPay.unifiedOrder(stringStringMap);
+        String resultStr = result.get("code_url");
+        if(result.get("code_url")==null|| StringUtils.isBlank(resultStr)){
+            return ResultBody.failed().data("生成二维码失败");
+        }
+        SaasClientPay saasClientPay=new SaasClientPay();
+        saasClientPay.setId(UUID.randomUUID().toString().replaceAll("_", ""));
+        saasClientPay.setCreateHospitalDepartment("");
+        saasClientPay.setCreateHospitalDepartid("");
+        saasClientPay.setCreateHospitalId(Long.valueOf(1L));
+        saasClientPay.setPayPrice(1);
+        saasClientPay.setCreateHospitalName("");
+        saasClientPay.setOutTradeNo(jsonObject.get("out_trade_no"));
+        saasClientPay.setPayTime(new Date());
+        saasClientPay.setPayStatus(0);
+        saasClientPay.setPayMethod(0);
+        saasClientPay.setDiaId("1");
+        boolean save = iSaasClientPayService.saveOrUpdate(saasClientPay);
+        String s = QRCodeUtil.creatRrCode(resultStr, 200, 200, 0);
+        return ResultBody.ok().data(s);
+    }
+    @RequestMapping("/callback")
+    public void OrderCallBack(HttpServletRequest request, HttpServletResponse response,Map<String,Object> map) throws Exception {
+        Map<String, String> requestMap = this.getRequestMap(request.getParameterMap());
+        log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww");
+        log.error(requestMap.toString());
+        log.error("wwwwwwwwwwwwwwwwwwwwwwwwwwwwwww");
+        log.error(map.toString());
+        log.error("eeeeeeeeeeeeeeeeeeeeee");
+        if(WXPayUtil.isSignatureValid(requestMap, "GSFcX6WdgRTAS6154EW14WE3SGBSER49")){
+            //支付成功
+            if(requestMap.get("result_code").equals("SUCCESS")){
+                //更新状态 out_trade_no
+                QueryWrapper queryWrapper = new QueryWrapper();
+                queryWrapper.eq("out_trade_no",requestMap.get("out_trade_no"));
+                queryWrapper.eq("pay_status",0);
+                List<SaasClientPay> list = iSaasClientPayService.list(queryWrapper);
+                if(!list.isEmpty()){
+                    SaasClientPay saasClientPay = list.get(0);
+                    saasClientPay.setPayTime(DateUtils.parseDate(requestMap.get("time_end")));
+                    saasClientPay.setPayStatus(2);
+                    saasClientPay.setTradeNo(requestMap.get("transaction_id"));
+                    boolean b = iSaasClientPayService.saveOrUpdate(saasClientPay);
+                }
+            }
+        }else {
+            System.out.println("验签失败");
+        }
+    }
+    private Map<String,String> getRequestMap(Map<String,String[]> requestParams){
+        Map<String,String> params = new HashMap<>();
+        for (String name : requestParams.keySet()) {
+            String[] values = requestParams.get(name);
+            String valueStr = "";
+            for (int i = 0; i < values.length; i++) {
+                valueStr = (i == values.length - 1) ? valueStr + values[i]
+                        : valueStr + values[i] + ",";
+            }
+            params.put(name, valueStr);
+            System.out.println(name+" ==> "+valueStr);
+        }
+        return params;
+    }
+    public static void main(String[] args) throws Exception {
+        WxController weixinController=new WxController();
+        Map<String,Object> map=null;
+        ResultBody save = weixinController.save(map);
+        String data = (String)save.getData();
+    }
+
+}

--
Gitblit v1.8.0