dcs hai 2 meses
pai
achega
38ead73c24
Modificáronse 23 ficheiros con 1870 adicións e 2 borrados
  1. 109 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/config/WxPayConfig.java
  2. 206 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/controller/WxPayController.java
  3. 17 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/dao/PaymentInfoDao.java
  4. 20 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/dao/RefundInfoDao.java
  5. 29 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/PayType.java
  6. 27 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/wxpay/WxNotifyType.java
  7. 37 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/wxpay/WxRefundStatus.java
  8. 39 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/wxpay/WxTradeState.java
  9. 137 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/model/PaymentInfo.java
  10. 163 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/model/RefundInfo.java
  11. 66 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/service/PaymentInfoService.java
  12. 109 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/service/RefundInfoService.java
  13. 478 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/service/WxPayService.java
  14. 40 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/AesUtil.java
  15. 168 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/HttpClientUtils.java
  16. 39 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/HttpUtils.java
  17. 58 0
      virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/QrRcodeGenUtil.java
  18. 5 1
      virgo.api/src/main/resources/application.properties
  19. 1 1
      virgo.api/src/main/resources/mapper/OrderInfoMapper.xml
  20. 49 0
      virgo.api/src/main/resources/mapper/PaymentInfoMapper.xml
  21. 61 0
      virgo.api/src/main/resources/mapper/RefundInfoMapper.xml
  22. 9 0
      virgo.api/src/main/resources/pub_key.pem
  23. 3 0
      virgo.core/src/main/java/com/bosshand/virgo/core/config/ShiroConfig.java

+ 109 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/config/WxPayConfig.java

@@ -0,0 +1,109 @@
+package com.bosshand.virgo.api.workark.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Configuration;
+
+
+@Configuration
+public class WxPayConfig {
+
+    /**
+     * 商户号
+     */
+    @Value("${wxpay.mch-id}")
+    private String mchId;
+
+    /**
+     * 商户API证书序列号
+     */
+    @Value("${wxpay.mch-serial-no}")
+    private String mchSerialNo;
+
+    /**
+     * 商户私钥文件
+     */
+    @Value("${wxpay.private-key-path}")
+    private String privateKeyPath;
+
+    /**
+     * APIv3密钥
+     */
+    @Value("${wxpay.api-v3-key}")
+    private String apiV3Key;
+
+    /**
+     * APPID
+     */
+    @Value("${wxpay.appid}")
+    private String appid;
+
+    /**
+     * 微信服务器地址
+     */
+    @Value("${wxpay.domain}")
+    private String domain;
+
+    /**
+     * 接收结果通知地址
+     */
+    @Value("${wxpay.notify-domain}")
+    private String notifyDomain;
+
+    /**
+     * APIv2密钥
+     */
+    @Value("${wxpay.partnerKey}")
+    private String partnerKey;
+
+    /**
+     * 公钥ID
+     */
+    @Value("${wxpay.publicKeyId}")
+    private String publicKeyId;
+
+    /**
+     * 商户公钥文件
+     */
+    @Value("${wxpay.public-key-path}")
+    private String publicKeyPath;
+
+    public String getMchId() {
+        return mchId;
+    }
+
+    public String getMchSerialNo() {
+        return mchSerialNo;
+    }
+
+    public String getPrivateKeyPath() {
+        return privateKeyPath;
+    }
+
+    public String getApiV3Key() {
+        return apiV3Key;
+    }
+
+    public String getAppid() {
+        return appid;
+    }
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public String getNotifyDomain() {
+        return notifyDomain;
+    }
+
+    public String getPartnerKey() {
+        return partnerKey;
+    }
+
+    public String getPublicKeyId() {
+        return publicKeyId;
+    }
+
+    public String getPublicKeyPath() {
+        return publicKeyPath;
+    }
+}

+ 206 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/controller/WxPayController.java

@@ -0,0 +1,206 @@
+package com.bosshand.virgo.api.workark.controller;
+
+
+import com.bosshand.virgo.api.workark.service.WxPayService;
+import com.bosshand.virgo.api.workark.util.HttpUtils;
+import com.bosshand.virgo.core.response.Response;
+import com.google.gson.Gson;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@RestController
+@RequestMapping({"workarkWxPay"})
+@Api(tags = {"微信支付"})
+public class WxPayController {
+
+    private final static Log log = LogFactory.getLog(WxPayService.class);
+
+    @Autowired
+    WxPayService wxPayService;
+
+    /**
+     * Native下单
+     * @param orderNo
+     * @return
+     * @throws Exception
+     */
+    @ApiOperation("提交订单,生成支付二维码")
+    @PostMapping("/native/{orderNo}")
+    public Response nativePay(@PathVariable String orderNo) throws Exception {
+
+        log.info("发起支付请求 v3");
+        //返回支付二维码连接和订单号
+        Map<String, Object> map = wxPayService.nativePay(orderNo);
+        return Response.ok(map);
+    }
+
+    /**
+     * 支付通知
+     * 微信支付通过支付通知接口将用户支付成功消息通知给商户
+     */
+    @ApiOperation("支付通知")
+    @PostMapping("/native/notify")
+    public String nativeNotify(HttpServletRequest request, HttpServletResponse response){
+
+        Gson gson = new Gson();
+        Map<String, String> map = new HashMap<>();//应答对象
+
+        try {
+
+            //处理通知参数
+            String body = HttpUtils.readData(request);
+            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
+
+            log.info(bodyMap.toString());
+
+            String requestId = (String)bodyMap.get("id");
+            log.info("支付通知的id ===>"+requestId);
+
+            //签名的验证
+
+            if(false){
+
+                log.error("通知验签失败");
+                //失败应答
+                response.setStatus(500);
+                map.put("code", "ERROR");
+                map.put("message", "通知验签失败");
+                return gson.toJson(map);
+            }
+            log.info("通知验签成功");
+
+            //处理订单
+            //wxPayService.processOrder(bodyMap);
+
+            //应答超时
+            //模拟接收微信端的重复通知
+            TimeUnit.SECONDS.sleep(3);
+
+            //成功应答
+            response.setStatus(200);
+            map.put("code", "SUCCESS");
+            map.put("message", "成功");
+            return gson.toJson(map);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            //失败应答
+            response.setStatus(500);
+            map.put("code", "ERROR");
+            map.put("message", "失败");
+            return gson.toJson(map);
+        }
+
+    }
+
+    /**
+     * 用户取消订单
+     * @param orderNo
+     * @return
+     * @throws Exception
+     */
+    @ApiOperation("用户取消订单")
+    @PostMapping("/cancel/{orderNo}")
+    public Response cancel(@PathVariable String orderNo) throws Exception {
+        log.info("取消订单");
+        wxPayService.cancelOrder(orderNo);
+        return Response.ok("订单已取消");
+    }
+
+    /**
+     * 查询订单
+     * @param orderNo
+     * @return
+     * @throws Exception
+     */
+    @ApiOperation("查询订单:测试订单状态用")
+    @GetMapping("/query/{orderNo}")
+    public Response queryOrder(@PathVariable String orderNo) throws Exception {
+        log.info("查询订单");
+        String result = wxPayService.queryOrder(orderNo);
+        return Response.ok(result);
+
+    }
+
+    @ApiOperation("申请退款")
+    @PostMapping("/refunds/{orderNo}/{reason}")
+    public Response refunds(@PathVariable String orderNo, @PathVariable String reason) throws Exception {
+        log.info("申请退款");
+        wxPayService.refund(orderNo, reason);
+        return Response.ok();
+    }
+
+    /**
+     * 查询退款
+     * @param refundNo
+     * @return
+     * @throws Exception
+     */
+    @ApiOperation("查询退款:测试用")
+    @GetMapping("/query-refund/{refundNo}")
+    public Response queryRefund(@PathVariable String refundNo) throws Exception {
+        log.info("查询退款");
+        String result = wxPayService.queryRefund(refundNo);
+        return Response.ok(result);
+    }
+
+    /**
+     * 退款结果通知
+     * 退款状态改变后,微信会把相关退款结果发送给商户。
+     */
+    @ApiOperation("退款结果通知")
+    @PostMapping("/refunds/notify")
+    public String refundsNotify(HttpServletRequest request, HttpServletResponse response){
+        log.info("退款通知执行");
+        Gson gson = new Gson();
+        Map<String, String> map = new HashMap<>();//应答对象
+
+        try {
+            //处理通知参数
+            String body = HttpUtils.readData(request);
+            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
+            String requestId = (String)bodyMap.get("id");
+            log.info("支付通知的id ===>"+ requestId);
+
+            //签名的验证
+            if(false){
+
+                log.error("通知验签失败");
+                //失败应答
+                response.setStatus(500);
+                map.put("code", "ERROR");
+                map.put("message", "通知验签失败");
+                return gson.toJson(map);
+            }
+            log.info("通知验签成功");
+
+            //处理退款单
+            wxPayService.processRefund(bodyMap);
+
+            //成功应答
+            response.setStatus(200);
+            map.put("code", "SUCCESS");
+            map.put("message", "成功");
+            return gson.toJson(map);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            //失败应答
+            response.setStatus(500);
+            map.put("code", "ERROR");
+            map.put("message", "失败");
+            return gson.toJson(map);
+        }
+    }
+
+}

+ 17 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/dao/PaymentInfoDao.java

@@ -0,0 +1,17 @@
+package com.bosshand.virgo.api.workark.dao;
+
+import com.bosshand.virgo.api.workark.model.PaymentInfo;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface PaymentInfoDao {
+
+    void save(PaymentInfo paymentInfo);
+
+    void update(PaymentInfo paymentInfo);
+
+    List<PaymentInfo> getList(PaymentInfo paymentInfo);
+
+}

+ 20 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/dao/RefundInfoDao.java

@@ -0,0 +1,20 @@
+package com.bosshand.virgo.api.workark.dao;
+
+import com.bosshand.virgo.api.workark.model.RefundInfo;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface RefundInfoDao {
+
+    void save(RefundInfo refundInfo);
+
+    void update(RefundInfo refundInfo);
+
+    List<RefundInfo> getList(RefundInfo refundInfo);
+
+    RefundInfo getRefundNo(String refundNo);
+
+    List<RefundInfo> selectList(RefundInfo refundInfo);
+}

+ 29 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/PayType.java

@@ -0,0 +1,29 @@
+package com.bosshand.virgo.api.workark.enums;
+
+public enum PayType {
+    /**
+     * 微信
+     */
+    WXPAY("微信"),
+
+
+    /**
+     * 支付宝
+     */
+    ALIPAY("支付宝");
+
+    /**
+     * 类型
+     */
+    private final String type;
+
+    PayType(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+}
+
+

+ 27 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/wxpay/WxNotifyType.java

@@ -0,0 +1,27 @@
+package com.bosshand.virgo.api.workark.enums.wxpay;
+
+public enum WxNotifyType {
+
+	/**
+	 * 支付通知
+	 */
+	NATIVE_NOTIFY("/api/workarkWxPay/native/notify"),
+
+	/**
+	 * 退款结果通知
+	 */
+	REFUND_NOTIFY("/api/workarkWxPay/refunds/notify");
+
+	/**
+	 * 类型
+	 */
+	private final String type;
+
+	WxNotifyType(String type) {
+		this.type = type;
+	}
+
+	public String getType() {
+		return type;
+	}
+}

+ 37 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/wxpay/WxRefundStatus.java

@@ -0,0 +1,37 @@
+package com.bosshand.virgo.api.workark.enums.wxpay;
+
+public enum WxRefundStatus {
+
+    /**
+     * 退款成功
+     */
+    SUCCESS("SUCCESS"),
+
+    /**
+     * 退款关闭
+     */
+    CLOSED("CLOSED"),
+
+    /**
+     * 退款处理中
+     */
+    PROCESSING("PROCESSING"),
+
+    /**
+     * 退款异常
+     */
+    ABNORMAL("ABNORMAL");
+
+    /**
+     * 类型
+     */
+    private final String type;
+
+    WxRefundStatus(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+}

+ 39 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/enums/wxpay/WxTradeState.java

@@ -0,0 +1,39 @@
+package com.bosshand.virgo.api.workark.enums.wxpay;
+
+public enum WxTradeState {
+
+    /**
+     * 支付成功
+     */
+    SUCCESS("SUCCESS"),
+
+    /**
+     * 未支付
+     */
+    NOTPAY("NOTPAY"),
+
+    /**
+     * 已关闭
+     */
+    CLOSED("CLOSED"),
+
+    /**
+     * 转入退款
+     */
+    REFUND("REFUND");
+
+    /**
+     * 类型
+     */
+    private final String type;
+
+    WxTradeState(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+
+}

+ 137 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/model/PaymentInfo.java

@@ -0,0 +1,137 @@
+package com.bosshand.virgo.api.workark.model;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 付款信息
+ */
+public class PaymentInfo {
+
+    private Long id;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    /**
+     * 商品订单编号
+     */
+    private String orderNo;
+
+    /**
+     * 支付系统交易编号
+     */
+    private String transactionId;
+
+    /**
+     * 支付类型
+     */
+    private String paymentType;
+
+    /**
+     * 交易类型
+     */
+    private String tradeType;
+
+    /**
+     * 交易状态
+     */
+    private String tradeState;
+
+    /**
+     * 支付金额(元)
+     */
+    private BigDecimal payerTotal;
+
+    /**
+     * 通知参数
+     */
+    private String content;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getOrderNo() {
+        return orderNo;
+    }
+
+    public void setOrderNo(String orderNo) {
+        this.orderNo = orderNo;
+    }
+
+    public String getTransactionId() {
+        return transactionId;
+    }
+
+    public void setTransactionId(String transactionId) {
+        this.transactionId = transactionId;
+    }
+
+    public String getPaymentType() {
+        return paymentType;
+    }
+
+    public void setPaymentType(String paymentType) {
+        this.paymentType = paymentType;
+    }
+
+    public String getTradeType() {
+        return tradeType;
+    }
+
+    public void setTradeType(String tradeType) {
+        this.tradeType = tradeType;
+    }
+
+    public String getTradeState() {
+        return tradeState;
+    }
+
+    public void setTradeState(String tradeState) {
+        this.tradeState = tradeState;
+    }
+
+    public BigDecimal getPayerTotal() {
+        return payerTotal;
+    }
+
+    public void setPayerTotal(BigDecimal payerTotal) {
+        this.payerTotal = payerTotal;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+}

+ 163 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/model/RefundInfo.java

@@ -0,0 +1,163 @@
+package com.bosshand.virgo.api.workark.model;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 退款信息
+ */
+public class RefundInfo {
+
+    private Long id;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+    /**
+     * 商品订单编号
+     */
+    private String orderNo;
+
+    /**
+     * 退款单编号
+     */
+    private String refundNo;
+
+    /**
+     * 支付系统退款单号
+     */
+    private String refundId;
+
+    /**
+     * 原订单金额(元)
+     */
+    private BigDecimal totalFee;
+
+    /**
+     * 退款金额(元)
+     */
+    private BigDecimal refund;
+
+    /**
+     * 退款原因
+     */
+    private String reason;
+
+    /**
+     * 退款单状态
+     */
+    private String refundStatus;
+
+    /**
+     * 申请退款返回参数
+     */
+    private String contentReturn;
+
+    /**
+     * 退款结果通知参数
+     */
+    private String contentNotify;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getOrderNo() {
+        return orderNo;
+    }
+
+    public void setOrderNo(String orderNo) {
+        this.orderNo = orderNo;
+    }
+
+    public String getRefundNo() {
+        return refundNo;
+    }
+
+    public void setRefundNo(String refundNo) {
+        this.refundNo = refundNo;
+    }
+
+    public String getRefundId() {
+        return refundId;
+    }
+
+    public void setRefundId(String refundId) {
+        this.refundId = refundId;
+    }
+
+    public BigDecimal getTotalFee() {
+        return totalFee;
+    }
+
+    public void setTotalFee(BigDecimal totalFee) {
+        this.totalFee = totalFee;
+    }
+
+    public BigDecimal getRefund() {
+        return refund;
+    }
+
+    public void setRefund(BigDecimal refund) {
+        this.refund = refund;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public void setReason(String reason) {
+        this.reason = reason;
+    }
+
+    public String getRefundStatus() {
+        return refundStatus;
+    }
+
+    public void setRefundStatus(String refundStatus) {
+        this.refundStatus = refundStatus;
+    }
+
+    public String getContentReturn() {
+        return contentReturn;
+    }
+
+    public void setContentReturn(String contentReturn) {
+        this.contentReturn = contentReturn;
+    }
+
+    public String getContentNotify() {
+        return contentNotify;
+    }
+
+    public void setContentNotify(String contentNotify) {
+        this.contentNotify = contentNotify;
+    }
+}

+ 66 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/service/PaymentInfoService.java

@@ -0,0 +1,66 @@
+package com.bosshand.virgo.api.workark.service;
+
+import com.bosshand.virgo.api.workark.dao.PaymentInfoDao;
+import com.bosshand.virgo.api.workark.enums.PayType;
+import com.bosshand.virgo.api.workark.model.PaymentInfo;
+import com.google.gson.Gson;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class PaymentInfoService {
+
+    @Autowired
+    PaymentInfoDao paymentInfoDao;
+
+    private final static Log log = LogFactory.getLog(PaymentInfoService.class);
+
+    /**
+     * 记录支付日志
+     * @param plainText
+     */
+    public void createPaymentInfo(String plainText){
+
+        log.info("记录支付日志");
+
+        Gson gson = new Gson();
+        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
+
+        //订单号
+        String orderNo = (String)plainTextMap.get("outTradeNo");
+        //业务编号
+        String transactionId = (String)plainTextMap.get("transactionId");
+        //支付类型
+        String tradeType = (String)plainTextMap.get("tradeType");
+        //交易状态
+        String tradeState = (String)plainTextMap.get("tradeState");
+        //用户实际支付金额
+        Map<String, Object> amount = (Map)plainTextMap.get("amount");
+        Integer payerTotal = ((Double) amount.get("payerTotal")).intValue();
+
+        // 分 转换 元
+        BigDecimal multiplier = new BigDecimal("100");
+        BigDecimal payerTotalValue = new BigDecimal(payerTotal);
+        // 使用divide方法除以100
+        BigDecimal totalValue = payerTotalValue.divide(multiplier);
+
+        PaymentInfo paymentInfo = new PaymentInfo();
+        paymentInfo.setOrderNo(orderNo);
+        paymentInfo.setPaymentType(PayType.WXPAY.getType());
+        paymentInfo.setTransactionId(transactionId);
+        paymentInfo.setTradeType(tradeType);
+        paymentInfo.setTradeState(tradeState);
+        paymentInfo.setPayerTotal(totalValue);
+        paymentInfo.setContent(plainText);
+
+        paymentInfoDao.save(paymentInfo);
+
+    }
+
+}

+ 109 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/service/RefundInfoService.java

@@ -0,0 +1,109 @@
+package com.bosshand.virgo.api.workark.service;
+
+import com.bosshand.virgo.api.workark.dao.OrderInfoDao;
+import com.bosshand.virgo.api.workark.dao.RefundInfoDao;
+import com.bosshand.virgo.api.workark.enums.wxpay.WxRefundStatus;
+import com.bosshand.virgo.api.workark.model.OrderInfo;
+import com.bosshand.virgo.api.workark.model.RefundInfo;
+import com.bosshand.virgo.api.workark.util.OrderNoUtils;
+import com.google.gson.Gson;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class RefundInfoService {
+
+    @Autowired
+    OrderInfoDao orderInfoDao;
+
+    @Autowired
+    RefundInfoDao refundInfoDao;
+
+    /**
+     * 根据订单号创建退款订单
+     * @param orderNo
+     * @return
+     */
+    public RefundInfo createRefundByOrderNo(String orderNo, String reason) {
+
+        //根据订单号获取订单信息
+        OrderInfo orderInfo = orderInfoDao.getOrderNo(orderNo);
+
+        //根据订单号生成退款订单
+        RefundInfo refundInfo = new RefundInfo();
+        refundInfo.setOrderNo(orderNo);//订单编号
+        refundInfo.setRefundNo(OrderNoUtils.getRefundNo());//退款单编号
+        refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(元)
+        refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(元)
+        refundInfo.setReason(reason);//退款原因
+
+        //保存退款订单
+        refundInfoDao.save(refundInfo);
+
+        return refundInfo;
+    }
+
+    /**
+     * 记录退款记录
+     * @param content
+     */
+    public void updateRefund(String content) {
+
+        //将json字符串转换成Map
+        Gson gson = new Gson();
+        Map<String, String> resultMap = gson.fromJson(content, HashMap.class);
+
+        //根据退款单编号修改退款单
+        RefundInfo refundInfo = refundInfoDao.getRefundNo(resultMap.get("out_refund_no"));
+
+        //设置要修改的字段
+        refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号
+
+        //查询退款和申请退款中的返回参数
+        if(resultMap.get("status") != null){
+            refundInfo.setRefundStatus(resultMap.get("status"));//退款状态
+            refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
+        }
+        //退款回调中的回调参数
+        if(resultMap.get("refund_status") != null){
+            refundInfo.setRefundStatus(resultMap.get("refund_status"));//退款状态
+            refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段
+        }
+
+        //更新退款单
+        refundInfoDao.update(refundInfo);
+    }
+
+    /**
+     * 找出申请退款超过minutes分钟并且未成功的退款单
+     * @param minutes
+     * @return
+     */
+    public List<RefundInfo> getNoRefundOrderByDuration(int minutes) {
+
+        //minutes分钟之前的时间
+        Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
+
+        ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
+        Date date = Date.from(zonedDateTime.toInstant());
+
+
+        RefundInfo refundInfo = new  RefundInfo();
+        refundInfo.setRefundStatus(WxRefundStatus.PROCESSING.getType());
+        refundInfo.setCreateTime(date);
+
+        List<RefundInfo> refundInfoList = refundInfoDao.selectList(refundInfo);
+        return refundInfoList;
+    }
+
+
+}

+ 478 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/service/WxPayService.java

@@ -0,0 +1,478 @@
+package com.bosshand.virgo.api.workark.service;
+
+import com.alibaba.fastjson.JSONObject;
+import com.bosshand.virgo.api.workark.config.WxPayConfig;
+import com.bosshand.virgo.api.workark.enums.OrderStatus;
+import com.bosshand.virgo.api.workark.enums.wxpay.WxNotifyType;
+import com.bosshand.virgo.api.workark.enums.wxpay.WxRefundStatus;
+import com.bosshand.virgo.api.workark.enums.wxpay.WxTradeState;
+import com.bosshand.virgo.api.workark.model.OrderInfo;
+import com.bosshand.virgo.api.workark.model.RefundInfo;
+import com.bosshand.virgo.api.workark.util.AesUtil;
+import com.bosshand.virgo.api.workark.util.QrRcodeGenUtil;
+import com.google.gson.Gson;
+import com.wechat.pay.java.core.Config;
+import com.wechat.pay.java.core.RSAPublicKeyConfig;
+import com.wechat.pay.java.service.payments.model.Transaction;
+import com.wechat.pay.java.service.payments.nativepay.NativePayService;
+import com.wechat.pay.java.service.payments.nativepay.model.*;
+import com.wechat.pay.java.service.refund.RefundService;
+import com.wechat.pay.java.service.refund.model.AmountReq;
+import com.wechat.pay.java.service.refund.model.CreateRequest;
+import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
+import com.wechat.pay.java.service.refund.model.Refund;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Service
+public class WxPayService {
+
+    @Resource
+    private WxPayConfig wxPayConfig;
+
+    @Autowired
+    private OrderInfoService orderInfoService;
+
+    @Autowired
+    private PaymentInfoService paymentInfoService;
+
+    @Autowired
+    private RefundInfoService refundInfoService;
+
+    private final ReentrantLock lock = new ReentrantLock();
+
+    private final static Log log = LogFactory.getLog(WxPayService.class);
+
+    public String getFileContent(String filePath) {
+        try {
+            ClassPathResource resource = new ClassPathResource(filePath);
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
+                StringBuilder content = new StringBuilder();
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    content.append(line).append("\n");
+                }
+                return content.toString();
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Error reading file: " + filePath, e);
+        }
+    }
+
+    private Config getConfig(){
+        // 初始化商户配置
+        Config config =
+                new RSAPublicKeyConfig.Builder()
+                        .merchantId(wxPayConfig.getMchId())
+                        .privateKey(getFileContent(wxPayConfig.getPrivateKeyPath()))
+                        .publicKey(getFileContent(wxPayConfig.getPublicKeyPath()))
+                        .publicKeyId(wxPayConfig.getPublicKeyId())
+                        .merchantSerialNumber(wxPayConfig.getMchSerialNo())
+                        .apiV3Key(wxPayConfig.getApiV3Key())
+                        .build();
+        return config;
+    }
+
+    /**
+     * 创建订单,调用Native支付接口
+     * @param orderNo
+     * @return code_url 和 订单号
+     * @throws Exception
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public Map<String, Object> nativePay(String orderNo) throws Exception {
+
+        log.info("获取订单");
+
+        // 生成订单
+        OrderInfo orderInfo = orderInfoService.getOrderNo(orderNo);
+        String codeUrl = orderInfo.getCodeUrl();
+        if (orderInfo != null && !StringUtils.isEmpty(codeUrl)) {
+            log.info("订单已存在,二维码已保存");
+            //返回二维码
+            Map<String, Object> map = new HashMap<>();
+            map.put("codeUrl", codeUrl);
+            map.put("orderNo", orderInfo.getOrderNo());
+            map.put("base64", QrRcodeGenUtil.jumpToQRcodeGen(codeUrl));
+            return map;
+        }
+
+        log.info("调用统一下单API");
+
+        // 初始化服务
+        NativePayService service = new NativePayService.Builder().config(getConfig()).build();
+
+        PrepayRequest request = new PrepayRequest();
+        request.setAppid(wxPayConfig.getAppid());
+        request.setMchid(wxPayConfig.getMchId());
+        request.setDescription(orderInfo.getTitle());
+        request.setNotifyUrl(wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
+        request.setOutTradeNo(orderInfo.getOrderNo());
+
+        // 元 转换 分
+        BigDecimal multiplier = new BigDecimal("100");
+        // 使用multiply方法乘以100
+        BigDecimal result = orderInfo.getTotalFee().multiply(multiplier);
+
+        Amount amount = new Amount();
+        amount.setTotal(result.intValue());
+        amount.setCurrency("CNY");
+        request.setAmount(amount);
+
+        // 调用下单方法,得到应答
+        PrepayResponse response = service.prepay(request);
+
+        // 使用微信扫描 code_url 对应的二维码
+        codeUrl = response.getCodeUrl();
+
+        //保存二维码
+        orderInfoService.saveCodeUrl(orderInfo.getOrderNo(), codeUrl);
+
+        //返回二维码
+        Map<String, Object> map = new HashMap<>();
+        map.put("codeUrl", codeUrl);
+        map.put("orderNo", orderInfo.getOrderNo());
+        map.put("base64", QrRcodeGenUtil.jumpToQRcodeGen(codeUrl));
+        return map;
+
+    }
+
+    /**
+     * 关单接口的调用
+     * @param orderNo
+     */
+    private void closeOrder(String orderNo) throws Exception {
+
+        log.info("关单接口的调用,订单号 ===>"+ orderNo);
+
+        NativePayService service = new NativePayService.Builder().config(getConfig()).build();
+
+        CloseOrderRequest request = new CloseOrderRequest();
+
+        request.setMchid(wxPayConfig.getMchId());
+        request.setOutTradeNo(orderNo);
+
+        service.closeOrder(request);
+    }
+
+    /**
+     * 对称解密
+     * @param bodyMap
+     * @return
+     */
+    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
+
+        log.info("密文解密");
+
+        //通知数据
+        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
+        //数据密文
+        String ciphertext = resourceMap.get("ciphertext");
+        //随机串
+        String nonce = resourceMap.get("nonce");
+        //附加数据
+        String associatedData = resourceMap.get("associated_data");
+
+        log.info("associatedData ===>"+ associatedData);
+
+        log.info("密文 ===>"+ ciphertext);
+        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
+        String plainText = null;
+        try {
+            plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        log.info("明文 ===>"+ plainText);
+
+        return plainText;
+    }
+
+    /**
+     * 用户取消订单
+     * @param orderNo
+     */
+    public void cancelOrder(String orderNo) throws Exception {
+        //调用微信支付的关单接口
+        this.closeOrder(orderNo);
+        //更新商户端的订单状态
+        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
+    }
+
+    public String queryOrder(String orderNo) throws Exception {
+
+        log.info("查单接口调用 ===>"+ orderNo);
+
+        NativePayService service = new NativePayService.Builder().config(getConfig()).build();
+
+        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
+        request.setMchid(wxPayConfig.getMchId());
+        request.setOutTradeNo(orderNo);
+
+        Transaction transaction = service.queryOrderByOutTradeNo(request);
+
+        return JSONObject.toJSONString(transaction);
+    }
+
+    /**
+     * 退款
+     * @param orderNo
+     * @param reason
+     * @throws IOException
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void refund(String orderNo, String reason) throws Exception {
+
+        log.info("创建退款单记录");
+        //根据订单编号创建退款单
+        RefundInfo refundsInfo = refundInfoService.createRefundByOrderNo(orderNo, reason);
+
+        log.info("调用退款API");
+
+        RefundService service = new RefundService.Builder().config(getConfig()).build();
+
+        CreateRequest request = new CreateRequest();
+
+        request.setOutTradeNo(orderNo);//订单编号
+        request.setOutRefundNo(refundsInfo.getRefundNo());//退款单编号
+        request.setReason(reason);//退款原因
+        request.setNotifyUrl(wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址
+
+        // 元 转换 分
+        BigDecimal multiplier = new BigDecimal("100");
+        // 使用multiply方法乘以100
+        BigDecimal refund = refundsInfo.getRefund().multiply(multiplier);
+        BigDecimal totalFee = refundsInfo.getTotalFee().multiply(multiplier);
+
+        AmountReq amount = new AmountReq();
+        amount.setCurrency("CNY");
+        amount.setRefund(refund.longValue());//退款金额
+        amount.setTotal(totalFee.longValue());//原订单金额
+
+        request.setAmount(amount);
+
+        Refund refund1 = service.create(request);
+
+        //更新订单状态
+        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);
+
+        //更新退款单
+        refundInfoService.updateRefund(JSONObject.toJSONString(refund1));
+    }
+
+    /**
+     * 查询退款接口调用
+     * @param refundNo
+     * @return
+     */
+    public String queryRefund(String refundNo) throws Exception {
+
+        log.info("查询退款接口调用 ===>"+ refundNo);
+
+        RefundService service = new RefundService.Builder().config(getConfig()).build();
+
+        QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
+
+        request.setOutRefundNo(refundNo);
+        request.setSubMchid(wxPayConfig.getMchId());
+
+        Refund refund = service.queryByOutRefundNo(request);
+
+        return JSONObject.toJSONString(refund);
+
+    }
+
+    /**
+     * 处理退款单
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void processRefund(Map<String, Object> bodyMap) throws Exception {
+
+        log.info("退款单");
+
+        //解密报文
+        String plainText = decryptFromResource(bodyMap);
+
+        //将明文转换成map
+        Gson gson = new Gson();
+        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
+        String orderNo = (String)plainTextMap.get("outTradeNo");
+
+        if(lock.tryLock()){
+            try {
+
+                String orderStatus = orderInfoService.getOrderStatus(orderNo);
+                if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
+                    return;
+                }
+
+                //更新订单状态
+                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
+
+                //更新退款单
+                refundInfoService.updateRefund(plainText);
+
+            } finally {
+                //要主动释放锁
+                lock.unlock();
+            }
+        }
+    }
+
+    /**
+     * 根据订单号查询微信支付查单接口,核实订单状态
+     * 如果订单已支付,则更新商户端订单状态,并记录支付日志
+     * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
+     * @param orderNo
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void checkOrderStatus(String orderNo) throws Exception {
+
+        log.warn("根据订单号核实订单状态 ===>"+ orderNo);
+
+        //调用微信支付查单接口
+        String result = this.queryOrder(orderNo);
+
+        Gson gson = new Gson();
+        Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
+
+        //获取微信支付端的订单状态
+        String tradeState = resultMap.get("tradeState");
+
+        //判断订单状态
+        if(WxTradeState.SUCCESS.getType().equals(tradeState)){
+
+            log.warn("核实订单已支付 ===>"+ orderNo);
+
+            //如果确认订单已支付则更新本地订单状态
+            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
+            //记录支付日志
+            paymentInfoService.createPaymentInfo(result);
+        }
+
+        if(WxTradeState.NOTPAY.getType().equals(tradeState)){
+            log.warn("核实订单未支付 ===>"+ orderNo);
+
+            //如果订单未支付,则调用关单接口
+            this.closeOrder(orderNo);
+
+            //更新本地订单状态
+            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
+        }
+
+    }
+
+    /**
+     * 根据退款单号核实退款单状态
+     * @param refundNo
+     * @return
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void checkRefundStatus(String refundNo) throws Exception {
+
+        log.warn("根据退款单号核实退款单状态 ===>"+ refundNo);
+
+        //调用查询退款单接口
+        String result = this.queryRefund(refundNo);
+
+        //组装json请求体字符串
+        Gson gson = new Gson();
+        Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
+
+        //获取微信支付端退款状态
+        String status = resultMap.get("status");
+
+        String orderNo = resultMap.get("outTradeNo");
+
+        if (WxRefundStatus.SUCCESS.getType().equals(status)) {
+
+            log.warn("核实订单已退款成功 ===>"+ refundNo);
+
+            //如果确认退款成功,则更新订单状态
+            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
+
+            //更新退款单
+            refundInfoService.updateRefund(result);
+        }
+
+        if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
+
+            log.warn("核实订单退款异常  ===>"+ refundNo);
+
+            //如果确认退款成功,则更新订单状态
+            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
+
+            //更新退款单
+            refundInfoService.updateRefund(result);
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
+        log.info("处理订单");
+
+        //解密报文
+        String plainText = decryptFromResource(bodyMap);
+
+        log.info("处理订单");
+
+        //将明文转换成map
+        Gson gson = new Gson();
+        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
+        String orderNo = (String)plainTextMap.get("outTradeNo");
+
+
+        /*在对业务数据进行状态检查和处理之前,
+        要采用数据锁进行并发控制,
+        以避免函数重入造成的数据混乱*/
+        //尝试获取锁:
+        // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
+        if(lock.tryLock()){
+            try {
+                //处理重复的通知
+                //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
+                String orderStatus = orderInfoService.getOrderStatus(orderNo);
+                if(!OrderStatus.NOTPAY.getType().equals(orderStatus)){
+                    return;
+                }
+
+                //模拟通知并发
+                try {
+                    TimeUnit.SECONDS.sleep(5);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+
+                //更新订单状态
+                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
+
+                //记录支付日志
+                paymentInfoService.createPaymentInfo(plainText);
+            } finally {
+                //要主动释放锁
+                lock.unlock();
+            }
+        }
+    }
+
+
+
+}

+ 40 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/AesUtil.java

@@ -0,0 +1,40 @@
+package com.bosshand.virgo.api.workark.util;
+
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+public class AesUtil {
+    static final int KEY_LENGTH_BYTE = 32;
+    static final int TAG_LENGTH_BIT = 128;
+    private final byte[] aesKey;
+    public AesUtil(byte[] key) {
+        if (key.length != KEY_LENGTH_BYTE) {
+            throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节");
+        }
+        this.aesKey = key;
+    }
+
+    public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
+            throws GeneralSecurityException, IOException {
+        try {
+            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+            SecretKeySpec key = new SecretKeySpec(aesKey, "AES");
+            GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
+            cipher.init(Cipher.DECRYPT_MODE, key, spec);
+            cipher.updateAAD(associatedData);
+            return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
+        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+            throw new IllegalStateException(e);
+        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}

+ 168 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/HttpClientUtils.java

@@ -0,0 +1,168 @@
+package com.bosshand.virgo.api.workark.util;
+
+import org.apache.http.Consts;
+import org.apache.http.HttpEntity;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.*;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLContextBuilder;
+import org.apache.http.conn.ssl.TrustStrategy;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * http请求客户端
+ */
+public class HttpClientUtils {
+	private String url;
+	private Map<String, String> param;
+	private int statusCode;
+	private String content;
+	private String xmlParam;
+	private boolean isHttps;
+
+	public boolean isHttps() {
+		return isHttps;
+	}
+
+	public void setHttps(boolean isHttps) {
+		this.isHttps = isHttps;
+	}
+
+	public String getXmlParam() {
+		return xmlParam;
+	}
+
+	public void setXmlParam(String xmlParam) {
+		this.xmlParam = xmlParam;
+	}
+
+	public HttpClientUtils(String url, Map<String, String> param) {
+		this.url = url;
+		this.param = param;
+	}
+
+	public HttpClientUtils(String url) {
+		this.url = url;
+	}
+
+	public void setParameter(Map<String, String> map) {
+		param = map;
+	}
+
+	public void addParameter(String key, String value) {
+		if (param == null)
+			param = new HashMap<String, String>();
+		param.put(key, value);
+	}
+
+	public void post() throws ClientProtocolException, IOException {
+		HttpPost http = new HttpPost(url);
+		setEntity(http);
+		execute(http);
+	}
+
+	public void put() throws ClientProtocolException, IOException {
+		HttpPut http = new HttpPut(url);
+		setEntity(http);
+		execute(http);
+	}
+
+	public void get() throws ClientProtocolException, IOException {
+		if (param != null) {
+			StringBuilder url = new StringBuilder(this.url);
+			boolean isFirst = true;
+			for (String key : param.keySet()) {
+				if (isFirst) {
+					url.append("?");
+					isFirst = false;
+				}else {
+					url.append("&");
+				}
+				url.append(key).append("=").append(param.get(key));
+			}
+			this.url = url.toString();
+		}
+		HttpGet http = new HttpGet(url);
+		execute(http);
+	}
+
+	/**
+	 * set http post,put param
+	 */
+	private void setEntity(HttpEntityEnclosingRequestBase http) {
+		if (param != null) {
+			List<NameValuePair> nvps = new LinkedList<NameValuePair>();
+			for (String key : param.keySet())
+				nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
+			http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
+		}
+		if (xmlParam != null) {
+			http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
+		}
+	}
+
+	private void execute(HttpUriRequest http) throws ClientProtocolException,
+			IOException {
+		CloseableHttpClient httpClient = null;
+		try {
+			if (isHttps) {
+				SSLContext sslContext = new SSLContextBuilder()
+						.loadTrustMaterial(null, new TrustStrategy() {
+							// 信任所有
+							public boolean isTrusted(X509Certificate[] chain,
+									String authType)
+									throws CertificateException {
+								return true;
+							}
+						}).build();
+				SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
+						sslContext);
+				httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
+						.build();
+			} else {
+				httpClient = HttpClients.createDefault();
+			}
+			CloseableHttpResponse response = httpClient.execute(http);
+			try {
+				if (response != null) {
+					if (response.getStatusLine() != null)
+						statusCode = response.getStatusLine().getStatusCode();
+					HttpEntity entity = response.getEntity();
+					// 响应内容
+					content = EntityUtils.toString(entity, Consts.UTF_8);
+				}
+			} finally {
+				response.close();
+			}
+		} catch (Exception e) {
+			e.printStackTrace();
+		} finally {
+			httpClient.close();
+		}
+	}
+
+	public int getStatusCode() {
+		return statusCode;
+	}
+
+	public String getContent() throws ParseException, IOException {
+		return content;
+	}
+
+}

+ 39 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/HttpUtils.java

@@ -0,0 +1,39 @@
+package com.bosshand.virgo.api.workark.util;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.IOException;
+
+
+public class HttpUtils {
+
+    /**
+     * 将通知参数转化为字符串
+     * @param request
+     * @return
+     */
+    public static String readData(HttpServletRequest request) {
+        BufferedReader br = null;
+        try {
+            StringBuilder result = new StringBuilder();
+            br = request.getReader();
+            for (String line; (line = br.readLine()) != null; ) {
+                if (result.length() > 0) {
+                    result.append("\n");
+                }
+                result.append(line);
+            }
+            return result.toString();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+}

+ 58 - 0
virgo.api/src/main/java/com/bosshand/virgo/api/workark/util/QrRcodeGenUtil.java

@@ -0,0 +1,58 @@
+package com.bosshand.virgo.api.workark.util;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.BitMatrix;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+public class QrRcodeGenUtil {
+
+    /**
+     * 生成支付二维码
+     */
+    public static String jumpToQRcodeGen(String url) {
+        int width = 300; // 二维码的宽度
+        int height = 300; // 二维码的高度
+        String result = "";
+        // 定义二维码参数
+        Map<EncodeHintType, Object> hints = new HashMap<>();
+        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); // 设置字符集
+
+        try {
+            BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, width, height, hints);
+            BufferedImage bufferedImage = toImage(bitMatrix);
+
+            // 转换为 Base64
+            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+                ImageIO.write(bufferedImage, "png", outputStream);
+                byte[] qrCodeBytes = outputStream.toByteArray();
+                result = "data:image/png;base64," + Base64.getEncoder().encodeToString(qrCodeBytes);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return result;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static BufferedImage toImage(BitMatrix matrix) {
+        final int width = matrix.getWidth();
+        final int height = matrix.getHeight();
+        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                image.setRGB(x, y, matrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF); // 设置像素点颜色
+            }
+        }
+        return image;
+    }
+
+}

+ 5 - 1
virgo.api/src/main/resources/application.properties

@@ -87,4 +87,8 @@ wxpay.domain=https://api.mch.weixin.qq.com
 # \u63A5\u6536\u7ED3\u679C\u901A\u77E5\u5730\u5740
 wxpay.notify-domain=https://www.waywish.com
 # APIv2\u5BC6\u94A5
-wxpay.partnerKey=mabrdt9WNgXmIj5rFzFxeN9JShX5irBr
+wxpay.partnerKey=mabrdt9WNgXmIj5rFzFxeN9JShX5irBr
+# \u516C\u94A5ID
+wxpay.publicKeyId=PUB_KEY_ID_0117133421912025040800298600003163
+# \u5546\u6237\u516C\u94A5\u6587\u4EF6
+wxpay.public-key-path=pub_key.pem

+ 1 - 1
virgo.api/src/main/resources/mapper/OrderInfoMapper.xml

@@ -147,7 +147,7 @@
     <update id="update" parameterType="com.bosshand.virgo.api.workark.model.OrderInfo">
         UPDATE workark_orderInfo
         <trim prefix="set" suffixOverrides=",">
-            <if test="title!=null">name=#{name},</if>
+            <if test="title!=null">title=#{title},</if>
             <if test="codeUrl!=null">codeUrl=#{codeUrl},</if>
             <if test="orderStatus!=null">orderStatus=#{orderStatus},</if>
             <if test="paymentType!=null">paymentType=#{paymentType},</if>

+ 49 - 0
virgo.api/src/main/resources/mapper/PaymentInfoMapper.xml

@@ -0,0 +1,49 @@
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.bosshand.virgo.api.workark.dao.PaymentInfoDao">
+
+    <resultMap type="com.bosshand.virgo.api.workark.model.PaymentInfo" id="result">
+        <id column="id" property="id"/>
+        <result column="createTime" property="createTime"/>
+        <result column="updateTime" property="updateTime"/>
+        <result column="orderNo" property="orderNo"/>
+        <result column="transactionId" property="transactionId"/>
+        <result column="paymentType" property="paymentType"/>
+        <result column="tradeType" property="tradeType"/>
+        <result column="tradeState" property="tradeState"/>
+        <result column="payerTotal" property="payerTotal"/>
+        <result column="content" property="content"/>
+    </resultMap>
+
+    <select id="getList" resultMap="result">
+        select * from workark_paymentInfo
+        <where>
+            <if test="orderNo != null">
+                and orderNo = #{orderNo}
+            </if>
+        </where>
+    </select>
+
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO workark_paymentInfo (createTime, orderNo, transactionId, paymentType, tradeType, tradeState, payerTotal, content) VALUES
+                                     (now(), #{orderNo}, #{transactionId}, #{paymentType}, #{tradeType}, #{tradeType}, #{payerTotal}, #{content})
+    </insert>
+
+    <update id="update" parameterType="com.bosshand.virgo.api.workark.model.PaymentInfo">
+        UPDATE workark_paymentInfo
+        <trim prefix="set" suffixOverrides=",">
+            <if test="orderNo!=null">orderNo=#{orderNo},</if>
+            <if test="transactionId!=null">transactionId=#{transactionId},</if>
+            <if test="paymentType!=null">paymentType=#{paymentType},</if>
+            <if test="tradeType!=null">tradeType=#{tradeType},</if>
+            <if test="tradeState!=null">tradeState=#{tradeState},</if>
+            <if test="payerTotal!=null">payerTotal=#{payerTotal},</if>
+            <if test="content!=null">content=#{content},</if>
+            <if test="updateTime==null">updateTime=now(),</if>
+        </trim>
+        WHERE id=#{id}
+    </update>
+
+</mapper>

+ 61 - 0
virgo.api/src/main/resources/mapper/RefundInfoMapper.xml

@@ -0,0 +1,61 @@
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.bosshand.virgo.api.workark.dao.RefundInfoDao">
+
+    <resultMap type="com.bosshand.virgo.api.workark.model.RefundInfo" id="result">
+        <id column="id" property="id"/>
+        <result column="createTime" property="createTime"/>
+        <result column="updateTime" property="updateTime"/>
+        <result column="orderNo" property="orderNo"/>
+        <result column="refundNo" property="refundNo"/>
+        <result column="refundId" property="refundId"/>
+        <result column="totalFee" property="totalFee"/>
+        <result column="refund" property="refund"/>
+        <result column="reason" property="reason"/>
+        <result column="refundStatus" property="refundStatus"/>
+        <result column="contentReturn" property="contentReturn"/>
+        <result column="contentNotify" property="contentNotify"/>
+    </resultMap>
+
+    <select id="getList" resultMap="result">
+        select * from workark_refundInfo
+        <where>
+            <if test="orderNo != null">
+                and orderNo = #{orderNo}
+            </if>
+        </where>
+    </select>
+
+    <select id="getRefundNo" resultMap="result">
+        select * from workark_refundInfo where refundNo = #{refundNo}
+    </select>
+
+    <select id="selectList" resultMap="result">
+        select * from workark_refundInfo where refundStatus = #{refundStatus} and createTime &lt; #{createTime}
+    </select>
+
+    <insert id="save" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO workark_refundInfo (createTime, orderNo, refundNo, refundId, totalFee, refund, reason, refundStatus, contentReturn, contentNotify) VALUES
+            (now(), #{orderNo}, #{refundNo}, #{refundId}, #{totalFee}, #{refund}, #{reason}, #{refundStatus}, #{contentReturn}, #{contentNotify})
+    </insert>
+
+    <update id="update" parameterType="com.bosshand.virgo.api.workark.model.RefundInfo">
+        UPDATE workark_refundInfo
+        <trim prefix="set" suffixOverrides=",">
+            <if test="orderNo!=null">orderNo=#{orderNo},</if>
+            <if test="refundNo!=null">refundNo=#{refundNo},</if>
+            <if test="refundId!=null">refundId=#{refundId},</if>
+            <if test="totalFee!=null">totalFee=#{totalFee},</if>
+            <if test="refund!=null">refund=#{refund},</if>
+            <if test="reason!=null">reason=#{reason},</if>
+            <if test="refundStatus!=null">refundStatus=#{refundStatus},</if>
+            <if test="contentReturn!=null">contentReturn=#{contentReturn},</if>
+            <if test="contentNotify!=null">contentNotify=#{contentNotify},</if>
+            <if test="updateTime==null">updateTime=now(),</if>
+        </trim>
+        WHERE id=#{id}
+    </update>
+
+</mapper>

+ 9 - 0
virgo.api/src/main/resources/pub_key.pem

@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA58Ai+k0VVnPT14OGYmQm
+BIyAI8S0e/YvDJF3eWdedpMfLzYne5zjAFRAZcCrg88o3hz5gVxA5QwnKdV/L3ja
+1YRT51usifnSPBM6Cg1eYHo+V4J9yexrRuSXFNMyMPpp7TWei1b517ETYzt36QM2
+zrEXZJt1h2QJd9ZTiWUsB2MBn2Gjb4SSWjukeItoxy9Pwv4+NdzXYiZexrPMzNYS
+i+2Y88oFk1Yj6NTm+CDO7Dmn6MVJGsOTw6AQB3jBvKChqCC1ddqIUmeMJla/deTV
+UR3nPaRS2yUnqsq9xrBEUOBMTuGLugO0cpZY2cYD0Nwi5aY2/szcRhUD2j6SAjyA
+KQIDAQAB
+-----END PUBLIC KEY-----

+ 3 - 0
virgo.core/src/main/java/com/bosshand/virgo/core/config/ShiroConfig.java

@@ -61,6 +61,9 @@ public class ShiroConfig {
 		filterChainDefinitionMap.put("/configuration/**","anon");
 
 
+		filterChainDefinitionMap.put("/workarkWxPay/native/notify", "anon");
+		filterChainDefinitionMap.put("/workarkWxPay/refunds/notify", "anon");
+
 		filterChainDefinitionMap.put("/workarkProductLevel/list", "anon");
 		filterChainDefinitionMap.put("/workarkProductLevel/getParentId/**", "anon");
 		filterChainDefinitionMap.put("/workarkProduct/details/**", "anon");