Selaa lähdekoodia

1、角色列表
2、微信退款

skyline 2 vuotta sitten
vanhempi
säilyke
e1fa86b90a

+ 17 - 1
admin/src/main/java/com/kym/admin/controller/AdminUserController.java

@@ -8,6 +8,7 @@ import com.kym.common.controller.IController;
 import com.kym.entity.admin.queryParams.CommonQueryParam;
 import com.kym.entity.admin.vo.AdminUserVo;
 import com.kym.service.admin.AdminUserService;
+import com.kym.service.admin.RoleService;
 import org.springframework.web.bind.annotation.*;
 
 /**
@@ -24,8 +25,11 @@ public class AdminUserController extends IController {
 
     private final AdminUserService adminUserService;
 
-    public AdminUserController(AdminUserService adminUserService) {
+    private final RoleService roleService;
+
+    public AdminUserController(AdminUserService adminUserService, RoleService roleService) {
         this.adminUserService = adminUserService;
+        this.roleService = roleService;
     }
 
     @SysLog("登录")
@@ -49,6 +53,7 @@ public class AdminUserController extends IController {
 
     /**
      * 操作员列表
+     *
      * @param params
      * @return
      */
@@ -68,4 +73,15 @@ public class AdminUserController extends IController {
         adminUserService.createAdminUser(adminUserVo);
         return R.success();
     }
+
+
+    /**
+     * 角色列表
+     *
+     * @return
+     */
+    @GetMapping("listRole")
+    R<?> listRole() {
+        return R.success(roleService.list());
+    }
 }

+ 91 - 0
entity/src/main/java/com/kym/entity/miniapp/RefundLog.java

@@ -0,0 +1,91 @@
+package com.kym.entity.miniapp;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.kym.entity.BaseEntity;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 退款日志
+ * </p>
+ *
+ * @author skyline
+ * @since 2023-09-11
+ */
+@Getter
+@Setter
+@TableName("t_refund_log")
+@Accessors(chain = true)
+public class RefundLog extends BaseEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 微信支付退款单号
+     */
+    private String refundId;
+
+    /**
+     * 商户退款单号
+     */
+    private String outRefundNo;
+
+    /**
+     * 微信支付订单号
+     */
+    private String transactionId;
+
+    /**
+     * 商户订单号
+     */
+    private String outTradeNo;
+
+    /**
+     * 退款渠道
+     */
+    private String channel;
+
+    /**
+     * 退款入账账户
+     */
+    private String userReceivedAccount;
+
+    /**
+     * 退款成功时间
+     */
+    private LocalDateTime successTime;
+
+    /**
+     * 退款状态:SUCCESS:退款成功 CLOSED:退款关闭 PROCESSING:退款处理中 ABNORMAL:退款异常
+     */
+    private String status;
+
+    /**
+     * 资金账户:UNSETTLED : 未结算资金 AVAILABLE : 可用余额 UNAVAILABLE : 不可用余额 OPERATION : 运营户 BASIC : 基本账户(含可用余额和不可用余额)
+     */
+    private String fundsAccount;
+
+    /**
+     * 订单金额
+     */
+    private Integer total;
+
+    /**
+     * 退款金额
+     */
+    private Integer refund;
+
+    /**
+     * 用户支付币种
+     */
+    private String currency;
+}

+ 16 - 0
mapper/src/main/java/com/kym/mapper/miniapp/RefundLogMapper.java

@@ -0,0 +1,16 @@
+package com.kym.mapper.miniapp;
+
+import com.kym.entity.miniapp.RefundLog;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 退款日志 Mapper 接口
+ * </p>
+ *
+ * @author skyline
+ * @since 2023-09-11
+ */
+public interface RefundLogMapper extends BaseMapper<RefundLog> {
+
+}

+ 27 - 0
mapper/src/main/resources/mappers/miniapp/RefundLogMapper.xml

@@ -0,0 +1,27 @@
+<?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.kym.mapper.miniapp.RefundLogMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.kym.entity.miniapp.RefundLog">
+        <result column="user_id" property="userId" />
+        <result column="refund_id" property="refundId" />
+        <result column="out_refund_no" property="outRefundNo" />
+        <result column="transaction_id" property="transactionId" />
+        <result column="out_trade_no" property="outTradeNo" />
+        <result column="channel" property="channel" />
+        <result column="user_received_account" property="userReceivedAccount" />
+        <result column="success_time" property="successTime" />
+        <result column="status" property="status" />
+        <result column="funds_account" property="fundsAccount" />
+        <result column="total" property="total" />
+        <result column="refund" property="refund" />
+        <result column="currency" property="currency" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        user_id, refund_id, out_refund_no, transaction_id, out_trade_no, channel, user_received_account, success_time, status, funds_account, total, refund, currency
+    </sql>
+
+</mapper>

+ 34 - 8
miniapp/src/main/java/com/kym/miniapp/controller/PaymentController.java

@@ -28,9 +28,8 @@ import java.util.Map;
 @Controller
 @RequestMapping("/payment")
 public class PaymentController {
-    private Logger logger = LoggerFactory.getLogger(PaymentController.class);
-
     private final WxPayService wxPayService;
+    private Logger logger = LoggerFactory.getLogger(PaymentController.class);
 
     public PaymentController(WxPayService wxPayService) {
         this.wxPayService = wxPayService;
@@ -48,17 +47,44 @@ public class PaymentController {
     @PostMapping("/notify")
     String notify(HttpServletRequest request, HttpServletResponse response) {
         response.setContentType("text/xml");
-        Map<String,String> result = new HashMap<>();
+        Map<String, String> result = new HashMap<>();
         try {
             wxPayService.wxNotify(request);
-            result.put("return_code","SUCCESS");
-            result.put("return_msg","OK");
+            result.put("return_code", "SUCCESS");
+            result.put("return_msg", "OK");
+        } catch (Exception e) {
+            result.put("return_code", "FAIL");
+            result.put("return_msg", e.getMessage());
+        }
+        String resp = XmlUtil.mapToXmlStr(result);
+        logger.info("wxPay notify result>>>:{}", resp);
+        return resp;
+    }
+
+
+    @ApiLog("微信退款")
+    @PostMapping("/wxRefund")
+    @ResponseBody
+    R wxRefund(@RequestBody JSONObject params) {
+        return R.success(wxPayService.wxRefund(params));
+    }
+
+    @ApiLog(value = "微信退款回调", ignoreParams = true)
+    @PostMapping("/wxRefundNotify")
+    @ResponseBody
+    String wxRefundNotify(HttpServletRequest request, HttpServletResponse response) {
+        response.setContentType("text/xml");
+        Map<String, String> result = new HashMap<>();
+        try {
+            wxPayService.wxRefundNotify(request);
+            result.put("return_code", "SUCCESS");
+            result.put("return_msg", "OK");
         } catch (Exception e) {
-            result.put("return_code","FAIL");
-            result.put("return_msg",e.getMessage());
+            result.put("return_code", "FAIL");
+            result.put("return_msg", e.getMessage());
         }
         String resp = XmlUtil.mapToXmlStr(result);
-        logger.info("wxpay notify result>>>:{}",resp);
+        logger.info("wxRefund notify result>>>:{}", resp);
         return resp;
     }
 

+ 18 - 0
miniapp/src/main/java/com/kym/miniapp/controller/RefundLogController.java

@@ -0,0 +1,18 @@
+package com.kym.miniapp.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 退款日志 前端控制器
+ * </p>
+ *
+ * @author skyline
+ * @since 2023-09-11
+ */
+@RestController
+@RequestMapping("/refund-log")
+public class RefundLogController {
+
+}

+ 16 - 0
service/src/main/java/com/kym/service/miniapp/RefundLogService.java

@@ -0,0 +1,16 @@
+package com.kym.service.miniapp;
+
+import com.kym.entity.miniapp.RefundLog;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 退款日志 服务类
+ * </p>
+ *
+ * @author skyline
+ * @since 2023-09-11
+ */
+public interface RefundLogService extends IService<RefundLog> {
+
+}

+ 1 - 1
service/src/main/java/com/kym/service/miniapp/WalletDetailService.java

@@ -16,7 +16,7 @@ import java.util.List;
  */
 public interface WalletDetailService extends IService<WalletDetail> {
 
-    WalletDetail getWalletDetailByOrderNo(String orderNo);
+    WalletDetail getWalletDetailByOrderNo(String orderNo, Integer status);
 
     List<WalletDetail> listWalletDetail(int type);
 

+ 20 - 0
service/src/main/java/com/kym/service/miniapp/impl/RefundLogServiceImpl.java

@@ -0,0 +1,20 @@
+package com.kym.service.miniapp.impl;
+
+import com.kym.entity.miniapp.RefundLog;
+import com.kym.mapper.miniapp.RefundLogMapper;
+import com.kym.service.miniapp.RefundLogService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 退款日志 服务实现类
+ * </p>
+ *
+ * @author skyline
+ * @since 2023-09-11
+ */
+@Service
+public class RefundLogServiceImpl extends ServiceImpl<RefundLogMapper, RefundLog> implements RefundLogService {
+
+}

+ 2 - 2
service/src/main/java/com/kym/service/miniapp/impl/WalletDetailServiceImpl.java

@@ -24,8 +24,8 @@ public class WalletDetailServiceImpl extends ServiceImpl<WalletDetailMapper, Wal
 
 
     @Override
-    public WalletDetail getWalletDetailByOrderNo(String orderNo) {
-        return lambdaQuery().eq(WalletDetail::getOrderNo, orderNo).one();
+    public WalletDetail getWalletDetailByOrderNo(String orderNo,Integer status) {
+        return lambdaQuery().eq(WalletDetail::getOrderNo, orderNo).eq(WalletDetail::getStatus,status).one();
     }
 
     @Override

+ 1 - 2
service/src/main/java/com/kym/service/wechat/WxPayService.java

@@ -5,7 +5,6 @@ import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPayment
 import com.wechat.pay.java.service.refund.model.Refund;
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.SneakyThrows;
-import org.springframework.http.ResponseEntity;
 
 import java.io.IOException;
 
@@ -20,7 +19,7 @@ public interface WxPayService {
     Refund queryByOutRefundNo(String outRefundNo);
 
     @SneakyThrows
-    ResponseEntity.BodyBuilder wxRefundNotify(HttpServletRequest request);
+    void wxRefundNotify(HttpServletRequest request);
 
     PrepayWithRequestPaymentResponse wxPay(JSONObject rechargeAmount);
 

+ 99 - 44
service/src/main/java/com/kym/service/wechat/impl/WxPayServiceImpl.java

@@ -13,10 +13,9 @@ import com.kym.common.exception.BusinessException;
 import com.kym.common.utils.OrderUtils;
 import com.kym.entity.miniapp.Account;
 import com.kym.entity.miniapp.PayLog;
+import com.kym.entity.miniapp.RefundLog;
 import com.kym.entity.miniapp.WalletDetail;
-import com.kym.service.miniapp.AccountService;
-import com.kym.service.miniapp.PayLogService;
-import com.kym.service.miniapp.WalletDetailService;
+import com.kym.service.miniapp.*;
 import com.kym.service.wechat.WxPayService;
 import com.wechat.pay.java.core.Config;
 import com.wechat.pay.java.core.RSAAutoCertificateConfig;
@@ -37,8 +36,6 @@ import lombok.SneakyThrows;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.io.ClassPathResource;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -74,11 +71,29 @@ public class WxPayServiceImpl implements WxPayService {
 
     private final AccountService accountService;
 
-    public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService) {
+    private final ChargeOrderService chargeOrderService;
+
+    private final RefundLogService refundLogService;
+
+
+    public WxPayServiceImpl(WxPayConfig conf, WalletDetailService walletDetailService, PayLogService payLogService, AccountService accountService, ChargeOrderService chargeOrderService, RefundLogService refundLogService) {
         this.conf = conf;
         this.walletDetailService = walletDetailService;
         this.payLogService = payLogService;
         this.accountService = accountService;
+        this.chargeOrderService = chargeOrderService;
+        this.refundLogService = refundLogService;
+    }
+
+    /**
+     * 商户订单号查询订单
+     */
+    public static Transaction queryOrderByOutTradeNo() {
+
+        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
+        // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
+        // 调用接口
+        return jsapiService.queryOrderByOutTradeNo(request);
     }
 
     /**
@@ -106,19 +121,6 @@ public class WxPayServiceImpl implements WxPayService {
         refundService = new RefundService.Builder().config(config).build();
     }
 
-    /**
-     * 商户订单号查询订单
-     */
-    public static Transaction queryOrderByOutTradeNo() {
-
-        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
-        // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
-        // 调用接口
-        return jsapiService.queryOrderByOutTradeNo(request);
-    }
-
-
-
     @SneakyThrows
     Object[] handleWxNotify(HttpServletRequest request) {
         var no = RandomUtil.randomInt(1000, 9999);
@@ -156,8 +158,6 @@ public class WxPayServiceImpl implements WxPayService {
         return new Object[]{requestParam, parser, no};
     }
 
-
-
     /**
      * JSAPI支付下单
      */
@@ -171,7 +171,7 @@ public class WxPayServiceImpl implements WxPayService {
         var userId = StpUtil.getLoginIdAsLong();
         // 生成订单号
         String outTradeNo = OrderUtils.getOrderNo();
-        // 创建支付记录
+        // 创建钱包流水
         var walletDetail = new WalletDetail()
                 .setType(WalletDetail.TYPE_充值)
                 .setStatus(WalletDetail.STATUS_待确认)
@@ -198,6 +198,8 @@ public class WxPayServiceImpl implements WxPayService {
         return service.prepayWithRequestPayment(request);
     }
 
+    // TODO: 2023-09-11  判断退款金额需要几笔充值订单进行退款操作 这个放在外层做,这里只支持单笔订单退款
+
     /**
      * 微信支付订单号查询订单
      */
@@ -212,6 +214,8 @@ public class WxPayServiceImpl implements WxPayService {
 
     /**
      * 关闭订单
+     *
+     * @param outTradeNo
      */
     public void closeOrder(String outTradeNo) {
         CloseOrderRequest request = new CloseOrderRequest();
@@ -240,14 +244,14 @@ public class WxPayServiceImpl implements WxPayService {
             LOGGER.info("微信支付回调{}:验签解密完毕,数据:\n{}", notifyRes[2], transaction);
             // 判断是否已经接收处理过通知
             if (payLogService.lambdaQuery().eq(PayLog::getOutTradeNo, transaction.getOutTradeNo()).one() != null) {
-                return ;
+                return;
             }
 
             DateTime dt = DateUtil.parse(transaction.getSuccessTime());
             LocalDateTime successTime = LocalDateTimeUtil.of(dt);
 
-            // 资金流水
-            var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo());
+            // 钱包流水
+            var walletDetail = walletDetailService.getWalletDetailByOrderNo(transaction.getOutTradeNo(), WalletDetail.TYPE_充值);
             if (walletDetail != null) {
                 walletDetail.setStatus(WalletDetail.STATUS_已确认);  //已确认
                 walletDetail.setCurrency(transaction.getAmount().getCurrency());
@@ -291,7 +295,6 @@ public class WxPayServiceImpl implements WxPayService {
         }
     }
 
-
     /**
      * 创建退款申请
      *
@@ -299,13 +302,38 @@ public class WxPayServiceImpl implements WxPayService {
      */
     @Override
     public Refund wxRefund(JSONObject params) {
+        // 查询用户是否有未完结的订单,如果有则提示等待订单完成后再申请,如果无则查询可用余额,将金额转至冻结金额
+        // 生成订单号
+        String outRefundNo = OrderUtils.getOrderNo();
+
+        var userId = StpUtil.getLoginIdAsLong();
+        var chargeOrder = chargeOrderService.getChargingOrderByUserId(userId);
+        if (chargeOrder != null) {
+            throw new BusinessException("存在未完结的订单,请等待所有订单完结之后重试");
+        }
+        var account = accountService.getAccountByUserId(userId);
+        if (account.getBalance() <= 0) {
+            throw new BusinessException("账户余额不足,无需退款");
+        }
+        // 将余额转移至冻结余额
+        accountService.lambdaUpdate().setSql(" frozen_amount = balance ,balance = 0").eq(Account::getUserId, userId);
+
+        // 钱包流水
+        var walletDetail = new WalletDetail()
+                .setType(WalletDetail.TYPE_提现)
+                .setStatus(WalletDetail.STATUS_待确认)
+                .setUserId(userId)
+                .setAmount(params.getIntValue("amount"))
+                .setOrderNo(outRefundNo);
+        walletDetailService.save(walletDetail);
+
+
         CreateRequest request = new CreateRequest();
         // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
         // 原始第三方订单号
-        // TODO: 2023-09-05 填入业务数据
-        request.setOutTradeNo("");
+        request.setOutTradeNo(params.getString("outTradeNo"));
         // 退款订单号
-        request.setOutRefundNo("");
+        request.setOutRefundNo(outRefundNo);
         // 退款原因
         request.setReason("用户申请退款");
         // 回调通知地址
@@ -332,34 +360,61 @@ public class WxPayServiceImpl implements WxPayService {
         return refundService.queryByOutRefundNo(request);
     }
 
+
     /**
      * 微信退款结果通知
      *
      * @param request
-     * @return
      */
     @SneakyThrows
     @Override
-    public ResponseEntity.BodyBuilder wxRefundNotify(HttpServletRequest request) {
+    public void wxRefundNotify(HttpServletRequest request) {
         var notifyRes = handleWxNotify(request);
         try {
             // 以支付通知回调为例,验签、解密并转换成 RefundNotification
             RefundNotification refundNotification = ((NotificationParser) notifyRes[1]).parse((RequestParam) notifyRes[0], RefundNotification.class);
-            // TODO: 2023-09-05 处理业务逻辑
+            LOGGER.info("微信退款回调{}:验签解密完毕,数据:\n{}", notifyRes[2], refundNotification);
+            DateTime dt = DateUtil.parse(refundNotification.getSuccessTime());
+            LocalDateTime successTime = LocalDateTimeUtil.of(dt);
+
+            // 资金流水
+            var walletDetail = walletDetailService.getWalletDetailByOrderNo(refundNotification.getOutTradeNo(), WalletDetail.TYPE_提现);
+            walletDetail.setStatus(WalletDetail.STATUS_已确认)  //已确认
+                    .setCurrency(refundNotification.getAmount().getCurrency())
+                    .setAmount(refundNotification.getAmount().getRefund().intValue())
+                    .setTransactionTime(successTime);
+            walletDetailService.updateById(walletDetail);
+
+            // 退款日志
+            var refundLog = new RefundLog()
+                    .setUserId(walletDetail.getUserId())
+                    .setRefundId(refundNotification.getRefundId())
+                    .setTransactionId(refundNotification.getTransactionId())
+                    .setOutTradeNo(walletDetail.getOrderNo())
+                    .setOutRefundNo(refundNotification.getOutRefundNo())
+                    .setChannel(refundNotification.getChannel().name())
+                    .setUserReceivedAccount(refundNotification.getUserReceivedAccount())
+                    .setSuccessTime(successTime)
+                    .setStatus(refundNotification.getRefundStatus().name())
+                    .setFundsAccount(refundNotification.getFundsAccount().name())
+                    .setTotal(refundNotification.getAmount().getTotal().intValue())
+                    .setRefund(refundNotification.getAmount().getRefund().intValue())
+                    .setCurrency(refundNotification.getAmount().getCurrency());
+            refundLog.setCreateTime(LocalDateTimeUtil.of(DateUtil.parse(refundNotification.getCreateTime())));
+            refundLogService.save(refundLog);
+            if ("SUCCESS".equals(refundNotification.getRefundStatus().name())) {
+                // 退款成功将冻结余额清零,新增退款钱包流水,更新退款流水
+                accountService.lambdaUpdate().setSql("frozen_amount = 0").eq(Account::getUserId, walletDetail.getUserId());
+                LOGGER.info("微信退款回调{}:业务处理结束", notifyRes[2]);
+            } else {
+                // 退款失败
+                LOGGER.error("微信退款失败,用户id:{},退款状态:{} \n 退款结果通知详情:{}", walletDetail.getUserId(), refundNotification.getRefundStatus().name(), refundNotification);
+                throw new BusinessException("处理异常");
+            }
         } catch (ValidationException e) {
             // 签名验证失败,返回 401 UNAUTHORIZED 状态码
-            LOGGER.error("sign verification failed", e);
-            return ResponseEntity.status(HttpStatus.UNAUTHORIZED);
-        }
-
-        // 如果处理失败,应返回 4xx/5xx 的状态码,例如 500 INTERNAL_SERVER_ERROR
-        if (false) {
-            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
+            LOGGER.error("微信退款通知验签失败", e);
+            throw new BusinessException("验签失败");
         }
-
-        // 处理成功,返回 200 OK 状态码
-        return ResponseEntity.status(HttpStatus.OK);
-
     }
-
 }