فهرست منبع

1、pay模块增加自定义的货币账户
2、优化已知bug

rayson 11 ماه پیش
والد
کامیت
79897f0431
64فایلهای تغییر یافته به همراه2981 افزوده شده و 98 حذف شده
  1. 36 4
      citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/util/SecurityFrameworkUtils.java
  2. 40 0
      citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/enums/currency/PayCurrencyBizTypeEnum.java
  3. 53 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyController.java
  4. 58 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyRechargeController.java
  5. 75 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyRechargePackageController.java
  6. 43 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyTransactionController.java
  7. 43 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyBaseVO.java
  8. 36 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyPageReqVO.java
  9. 22 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyRespVO.java
  10. 19 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyUserReqVO.java
  11. 31 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageBaseVO.java
  12. 14 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageCreateReqVO.java
  13. 30 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackagePageReqVO.java
  14. 22 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageRespVO.java
  15. 20 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageUpdateReqVO.java
  16. 14 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/transaction/PayCurrencyTransactionPageReqVO.java
  17. 35 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/transaction/PayCurrencyTransactionRespVO.java
  18. 46 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyController.java
  19. 69 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyRechargeController.java
  20. 42 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyRechargePackageController.java
  21. 61 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyTransactionController.java
  22. 19 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/currency/AppPayCurrencyRespVO.java
  23. 20 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyPackageRespVO.java
  24. 26 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyRechargeCreateReqVO.java
  25. 16 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyRechargeCreateRespVO.java
  26. 42 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyRechargeRespVO.java
  27. 31 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/transaction/AppPayCurrencyTransactionPageReqVO.java
  28. 24 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/transaction/AppPayCurrencyTransactionRespVO.java
  29. 16 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/transaction/AppPayCurrencyTransactionSummaryRespVO.java
  30. 21 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyConvert.java
  31. 44 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyRechargeConvert.java
  32. 29 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyRechargePackageConvert.java
  33. 19 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyTransactionConvert.java
  34. 58 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyDO.java
  35. 116 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyRechargeDO.java
  36. 47 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyRechargePackageDO.java
  37. 66 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyTransactionDO.java
  38. 124 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyMapper.java
  39. 30 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyRechargeMapper.java
  40. 33 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyRechargePackageMapper.java
  41. 66 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyTransactionMapper.java
  42. 193 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/framework/pay/core/CurrencyPayClient.java
  43. 2 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/channel/PayChannelServiceImpl.java
  44. 71 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargePackageService.java
  45. 112 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargePackageServiceImpl.java
  46. 65 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargeService.java
  47. 320 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargeServiceImpl.java
  48. 106 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyService.java
  49. 208 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyServiceImpl.java
  50. 75 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyTransactionService.java
  51. 86 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyTransactionServiceImpl.java
  52. 59 0
      citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/bo/CurrencyTransactionCreateReqBO.java
  53. 4 1
      citu-module-pay/citu-spring-boot-starter-biz-pay/src/main/java/com/citu/framework/pay/core/enums/channel/PayChannelEnum.java
  54. 9 10
      menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/workexp/ExpTypeEnum.java
  55. 0 7
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/job/vo/AppRecruitJobPageReqVO.java
  56. 0 5
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/job/vo/AppRecruitJobRespVO.java
  57. 0 3
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/job/vo/AppRecruitJobSaveReqVO.java
  58. 0 4
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobAdvertisedRespVO.java
  59. 0 3
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobAdvertisedSaveReqVO.java
  60. 0 4
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/job/JobAdvertisedDO.java
  61. 0 5
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/es/job/ESJobAdvertisedMergeDO.java
  62. 0 31
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/job/JobAdvertisedMapper.java
  63. 3 9
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/job/JobIntegrationServiceImpl.java
  64. 12 12
      menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/util/ESQueryBuildUtils.java

+ 36 - 4
citu-framework/citu-spring-boot-starter-security/src/main/java/com/citu/framework/security/core/util/SecurityFrameworkUtils.java

@@ -1,5 +1,6 @@
 package com.citu.framework.security.core.util;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import com.citu.framework.security.core.LoginUser;
@@ -15,6 +16,9 @@ import org.springframework.util.StringUtils;
 import javax.servlet.http.HttpServletRequest;
 import java.util.Collections;
 
+import static com.citu.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
+import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
+
 /**
  * 安全服务工具类
  *
@@ -29,13 +33,14 @@ public class SecurityFrameworkUtils {
 
     public static final String LOGIN_USER_HEADER = "login-user";
 
-    private SecurityFrameworkUtils() {}
+    private SecurityFrameworkUtils() {
+    }
 
     /**
      * 从请求中,获得认证 Token
      *
-     * @param request 请求
-     * @param headerName 认证 Token 对应的 Header 名字
+     * @param request       请求
+     * @param headerName    认证 Token 对应的 Header 名字
      * @param parameterName 认证 Token 对应的 Parameter 名字
      * @return 认证 Token
      */
@@ -114,11 +119,38 @@ public class SecurityFrameworkUtils {
         return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DEPT_ID) : null;
     }
 
+    /**
+     * 获得当前用户的数据编号,从上下文中
+     *
+     * @return 部门编号
+     */
+    @Nullable
+    public static Long getLoginUserDataId() {
+        LoginUser loginUser = getLoginUser();
+        return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DATA_ID) : null;
+    }
+
+    /**
+     * 获得当前用户的数据编号,从上下文中
+     *
+     * @return 部门编号
+     */
+    @Nullable
+    public static Long getLoginUserDataId2() {
+        LoginUser loginUser = getLoginUser();
+        if (CollUtil.isEmpty(loginUser.getInfo())
+                || null == loginUser.getInfo()
+                || !loginUser.getInfo().containsKey(LoginUser.INFO_KEY_DATA_ID)) {
+            throw exception(FORBIDDEN);
+        }
+        return MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DATA_ID);
+    }
+
     /**
      * 设置当前用户
      *
      * @param loginUser 登录用户
-     * @param request 请求
+     * @param request   请求
      */
     public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {
         // 创建 Authentication,并设置到上下文

+ 40 - 0
citu-module-pay/citu-module-pay-api/src/main/java/com/citu/module/pay/enums/currency/PayCurrencyBizTypeEnum.java

@@ -0,0 +1,40 @@
+package com.citu.module.pay.enums.currency;
+
+import com.citu.framework.common.core.IntArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * 货币账户交易业务分类
+ *
+ * @author Rayson
+ */
+@AllArgsConstructor
+@Getter
+public enum PayCurrencyBizTypeEnum implements IntArrayValuable {
+
+    RECHARGE(1, "充值"),
+    RECHARGE_REFUND(2, "充值退款"),
+    PAYMENT(3, "支付"),
+    PAYMENT_REFUND(4, "支付退款");
+
+    // TODO 后续增加
+
+    /**
+     * 业务分类
+     */
+    private final Integer type;
+    /**
+     * 说明
+     */
+    private final String description;
+
+    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayCurrencyBizTypeEnum::getType).toArray();
+
+    @Override
+    public int[] array() {
+         return ARRAYS;
+    }
+}

+ 53 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyController.java

@@ -0,0 +1,53 @@
+package com.citu.module.pay.controller.admin.currency;
+
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.currency.PayCurrencyPageReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.currency.PayCurrencyRespVO;
+import com.citu.module.pay.controller.admin.currency.vo.currency.PayCurrencyUserReqVO;
+import com.citu.module.pay.convert.currency.PayCurrencyConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import com.citu.module.pay.service.currency.PayCurrencyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static com.citu.framework.common.enums.UserTypeEnum.MEMBER;
+import static com.citu.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 货币账户")
+@RestController
+@RequestMapping("/pay/currency")
+@Validated
+@Slf4j
+public class PayCurrencyController {
+
+    @Resource
+    private PayCurrencyService payCurrencyService;
+
+    @GetMapping("/get")
+    @PreAuthorize("@ss.hasPermission('pay:currency:query')")
+    @Operation(summary = "获得用户货币账户明细")
+    public CommonResult<PayCurrencyRespVO> getCurrency(PayCurrencyUserReqVO reqVO) {
+        PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(
+                reqVO.getDataId(), reqVO.getUserId(), MEMBER.getValue());
+        return success(PayCurrencyConvert.INSTANCE.convert02(currency));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得货币账户分页")
+    @PreAuthorize("@ss.hasPermission('pay:currency:query')")
+    public CommonResult<PageResult<PayCurrencyRespVO>> getCurrencyPage(@Valid PayCurrencyPageReqVO pageVO) {
+        PageResult<PayCurrencyDO> pageResult = payCurrencyService.getCurrencyPage(pageVO);
+        return success(PayCurrencyConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 58 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyRechargeController.java

@@ -0,0 +1,58 @@
+package com.citu.module.pay.controller.admin.currency;
+
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
+import com.citu.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
+import com.citu.module.pay.service.currency.PayCurrencyRechargeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
+import javax.validation.Valid;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+import static com.citu.framework.common.util.servlet.ServletUtils.getClientIP;
+
+@Tag(name = "管理后台 - 货币账户充值")
+@RestController
+@RequestMapping("/pay/currency-recharge")
+@Validated
+@Slf4j
+public class PayCurrencyRechargeController {
+
+    @Resource
+    private PayCurrencyRechargeService currencyRechargeService;
+
+    @PostMapping("/update-paid")
+    @Operation(summary = "更新货币账户充值为已充值") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
+    @PermitAll // 无需登录, 内部校验实现
+    public CommonResult<Boolean> updateCurrencyRechargerPaid(@Valid @RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
+        currencyRechargeService.updateCurrencyRechargerPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+                notifyReqDTO.getPayOrderId());
+        return success(true);
+    }
+
+    // TODO @jason:发起退款,要 post 操作哈;
+    @GetMapping("/refund")
+    @Operation(summary = "发起货币账户充值退款")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    public CommonResult<Boolean> refundCurrencyRecharge(@RequestParam("id") Long id) {
+        currencyRechargeService.refundCurrencyRecharge(id, getClientIP());
+        return success(true);
+    }
+
+    @PostMapping("/update-refunded")
+    @Operation(summary = "更新货币账户充值为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
+    @PermitAll // 无需登录, 内部校验实现
+    public CommonResult<Boolean> updateCurrencyRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
+        currencyRechargeService.updateCurrencyRechargeRefunded(
+                Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId());
+        return success(true);
+    }
+
+}

+ 75 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyRechargePackageController.java

@@ -0,0 +1,75 @@
+package com.citu.module.pay.controller.admin.currency;
+
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageCreateReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackagePageReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageRespVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageUpdateReqVO;
+import com.citu.module.pay.convert.currency.PayCurrencyRechargePackageConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
+import com.citu.module.pay.service.currency.PayCurrencyRechargePackageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+
+
+@Tag(name = "管理后台 - 货币账户充值套餐")
+@RestController
+@RequestMapping("/pay/currency-recharge-package")
+@Validated
+public class PayCurrencyRechargePackageController {
+
+    @Resource
+    private PayCurrencyRechargePackageService currencyRechargePackageService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建货币账户充值套餐")
+    @PreAuthorize("@ss.hasPermission('pay:currency-recharge-package:create')")
+    public CommonResult<Long> createCurrencyRechargePackage(@Valid @RequestBody CurrencyRechargePackageCreateReqVO createReqVO) {
+        return success(currencyRechargePackageService.createCurrencyRechargePackage(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新货币账户充值套餐")
+    @PreAuthorize("@ss.hasPermission('pay:currency-recharge-package:update')")
+    public CommonResult<Boolean> updateCurrencyRechargePackage(@Valid @RequestBody CurrencyRechargePackageUpdateReqVO updateReqVO) {
+        currencyRechargePackageService.updateCurrencyRechargePackage(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除货币账户充值套餐")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('pay:currency-recharge-package:delete')")
+    public CommonResult<Boolean> deleteCurrencyRechargePackage(@RequestParam("id") Long id) {
+        currencyRechargePackageService.deleteCurrencyRechargePackage(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得货币账户充值套餐")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('pay:currency-recharge-package:query')")
+    public CommonResult<CurrencyRechargePackageRespVO> getCurrencyRechargePackage(@RequestParam("id") Long id) {
+        PayCurrencyRechargePackageDO currencyRechargePackage = currencyRechargePackageService.getCurrencyRechargePackage(id);
+        return success(PayCurrencyRechargePackageConvert.INSTANCE.convert(currencyRechargePackage));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得货币账户充值套餐分页")
+    @PreAuthorize("@ss.hasPermission('pay:currency-recharge-package:query')")
+    public CommonResult<PageResult<CurrencyRechargePackageRespVO>> getCurrencyRechargePackagePage(@Valid CurrencyRechargePackagePageReqVO pageVO) {
+        PageResult<PayCurrencyRechargePackageDO> pageResult = currencyRechargePackageService.getCurrencyRechargePackagePage(pageVO);
+        return success(PayCurrencyRechargePackageConvert.INSTANCE.convertPage(pageResult));
+    }
+
+}

+ 43 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/PayCurrencyTransactionController.java

@@ -0,0 +1,43 @@
+package com.citu.module.pay.controller.admin.currency;
+
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.transaction.PayCurrencyTransactionPageReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.transaction.PayCurrencyTransactionRespVO;
+import com.citu.module.pay.convert.currency.PayCurrencyTransactionConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.service.currency.PayCurrencyTransactionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 货币账户余额明细")
+@RestController
+@RequestMapping("/pay/currency-transaction")
+@Validated
+@Slf4j
+public class PayCurrencyTransactionController {
+
+    @Resource
+    private PayCurrencyTransactionService payCurrencyTransactionService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得货币账户流水分页")
+    @PreAuthorize("@ss.hasPermission('pay:currency:query')")
+    public CommonResult<PageResult<PayCurrencyTransactionRespVO>> getCurrencyTransactionPage(
+            @Valid PayCurrencyTransactionPageReqVO pageReqVO) {
+        PageResult<PayCurrencyTransactionDO> result = payCurrencyTransactionService.getCurrencyTransactionPage(pageReqVO);
+        return success(PayCurrencyTransactionConvert.INSTANCE.convertPage2(result));
+    }
+
+}

+ 43 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyBaseVO.java

@@ -0,0 +1,43 @@
+package com.citu.module.pay.controller.admin.currency.vo.currency;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 货币账户 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class PayCurrencyBaseVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20020")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Schema(description = "数据id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "数据id不能为空")
+    private Long dataId;
+
+    @Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "用户类型不能为空")
+    private Integer userType;
+
+    @Schema(description = "余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "余额,单位分不能为空")
+    private Long balance;
+
+    @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "累计支出,单位分不能为空")
+    private Long totalExpense;
+
+    @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "累计充值,单位分不能为空")
+    private Long totalRecharge;
+
+    @Schema(description = "冻结金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "20737")
+    @NotNull(message = "冻结金额,单位分不能为空")
+    private Long freezePrice;
+
+}

+ 36 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyPageReqVO.java

@@ -0,0 +1,36 @@
+package com.citu.module.pay.controller.admin.currency.vo.currency;
+
+import com.citu.framework.common.enums.UserTypeEnum;
+import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 货币账户分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayCurrencyPageReqVO extends PageParam {
+
+    @Schema(description = "用户编号", example = "1024")
+    private Long userId;
+
+    @Schema(description = "数据id", example = "1")
+    private Long dataId;
+
+    @Schema(description = "用户类型", example = "1")
+    @InEnum(value = UserTypeEnum.class)
+    private Integer userType;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyRespVO.java

@@ -0,0 +1,22 @@
+package com.citu.module.pay.controller.admin.currency.vo.currency;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 货币账户 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PayCurrencyRespVO extends PayCurrencyBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29528")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 19 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/currency/PayCurrencyUserReqVO.java

@@ -0,0 +1,19 @@
+package com.citu.module.pay.controller.admin.currency.vo.currency;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 用户货币账户明细 Request VO")
+@Data
+public class PayCurrencyUserReqVO {
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "用户编号不能为空")
+    private Long userId;
+
+    @Schema(description = "数据id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "数据id不能为空")
+    private Long dataId;
+}

+ 31 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageBaseVO.java

@@ -0,0 +1,31 @@
+package com.citu.module.pay.controller.admin.currency.vo.rechargepackage;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 货币账户充值套餐 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class CurrencyRechargePackageBaseVO {
+
+    @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    @NotNull(message = "套餐名不能为空")
+    private String name;
+
+    @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "16454")
+    @NotNull(message = "支付金额不能为空")
+    private Long payPrice;
+
+    @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20887")
+    @NotNull(message = "赠送金额不能为空")
+    private Long bonusPrice;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @NotNull(message = "状态不能为空")
+    private Byte status;
+
+}

+ 14 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageCreateReqVO.java

@@ -0,0 +1,14 @@
+package com.citu.module.pay.controller.admin.currency.vo.rechargepackage;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 货币账户充值套餐创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CurrencyRechargePackageCreateReqVO extends CurrencyRechargePackageBaseVO {
+
+}

+ 30 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackagePageReqVO.java

@@ -0,0 +1,30 @@
+package com.citu.module.pay.controller.admin.currency.vo.rechargepackage;
+
+import com.citu.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 货币账户充值套餐分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CurrencyRechargePackagePageReqVO extends PageParam {
+
+    @Schema(description = "套餐名", example = "李四")
+    private String name;
+
+    @Schema(description = "状态", example = "2")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageRespVO.java

@@ -0,0 +1,22 @@
+package com.citu.module.pay.controller.admin.currency.vo.rechargepackage;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 货币账户充值套餐 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CurrencyRechargePackageRespVO extends CurrencyRechargePackageBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 20 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/rechargepackage/CurrencyRechargePackageUpdateReqVO.java

@@ -0,0 +1,20 @@
+package com.citu.module.pay.controller.admin.currency.vo.rechargepackage;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 货币账户充值套餐更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class CurrencyRechargePackageUpdateReqVO extends CurrencyRechargePackageBaseVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9032")
+    @NotNull(message = "编号不能为空")
+    private Long id;
+
+}

+ 14 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/transaction/PayCurrencyTransactionPageReqVO.java

@@ -0,0 +1,14 @@
+package com.citu.module.pay.controller.admin.currency.vo.transaction;
+
+import com.citu.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 货币账户流水分页 Request VO")
+@Data
+public class PayCurrencyTransactionPageReqVO extends PageParam  {
+
+    @Schema(description = "货币账户编号",  example = "1")
+    private Long currencyId;
+
+}

+ 35 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/admin/currency/vo/transaction/PayCurrencyTransactionRespVO.java

@@ -0,0 +1,35 @@
+package com.citu.module.pay.controller.admin.currency.vo.transaction;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 APP - 货币账户流水分页 Response VO")
+@Data
+public class PayCurrencyTransactionRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "货币账户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
+    private Long currencyId;
+
+    @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer bizType;
+
+    @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long price;
+
+    @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆")
+    private String title;
+
+    @Schema(description = "交易后的余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long balance;
+
+    @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    // TODO @jason:merchantOrderId 字段,需要在 PayWalletTransaction 存储下;然后,前端也返回下这个字段,界面也展示下商户名
+
+}

+ 46 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyController.java

@@ -0,0 +1,46 @@
+package com.citu.module.pay.controller.app.currency;
+
+import com.citu.framework.common.enums.UserTypeEnum;
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.security.core.annotations.PreAuthenticated;
+import com.citu.module.pay.controller.app.currency.vo.currency.AppPayCurrencyRespVO;
+import com.citu.module.pay.convert.currency.PayCurrencyConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import com.citu.module.pay.service.currency.PayCurrencyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserDataId2;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+/**
+ * @author Rayson
+ */
+@Tag(name = "用户 APP - 货币账户")
+@RestController
+@RequestMapping("/pay/currency")
+@Validated
+@Slf4j
+public class AppPayCurrencyController {
+
+    @Resource
+    private PayCurrencyService payCurrencyService;
+
+    @GetMapping("/get")
+    @Operation(summary = "获取货币账户")
+    @PreAuthenticated
+    public CommonResult<AppPayCurrencyRespVO> getPayCurrency() {
+        PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(
+                getLoginUserDataId2(), getLoginUserId(), UserTypeEnum.MEMBER.getValue());
+        return success(PayCurrencyConvert.INSTANCE.convert(currency));
+    }
+
+}

+ 69 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyRechargeController.java

@@ -0,0 +1,69 @@
+package com.citu.module.pay.controller.app.currency;
+
+import cn.hutool.core.collection.CollUtil;
+import com.citu.framework.common.enums.UserTypeEnum;
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeCreateReqVO;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeCreateRespVO;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeRespVO;
+import com.citu.module.pay.convert.currency.PayCurrencyRechargeConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargeDO;
+import com.citu.module.pay.dal.dataobject.order.PayOrderDO;
+import com.citu.module.pay.service.currency.PayCurrencyRechargeService;
+import com.citu.module.pay.service.order.PayOrderService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.List;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+import static com.citu.framework.common.util.collection.CollectionUtils.convertList;
+import static com.citu.framework.common.util.servlet.ServletUtils.getClientIP;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserDataId;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserDataId2;
+import static com.citu.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+import static com.citu.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
+
+@Tag(name = "用户 APP - 货币账户充值")
+@RestController
+@RequestMapping("/pay/currency-recharge")
+@Validated
+@Slf4j
+public class AppPayCurrencyRechargeController {
+
+    @Resource
+    private PayCurrencyRechargeService currencyRechargeService;
+    @Resource
+    private PayOrderService payOrderService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建货币账户充值记录(发起充值)")
+    public CommonResult<AppPayCurrencyRechargeCreateRespVO> createCurrencyRecharge(
+            @Valid @RequestBody AppPayCurrencyRechargeCreateReqVO reqVO) {
+        PayCurrencyRechargeDO currencyRecharge = currencyRechargeService.createCurrencyRecharge(
+                getLoginUserDataId2(), getLoginUserId(), getLoginUserType(), getClientIP(), reqVO);
+        return success(PayCurrencyRechargeConvert.INSTANCE.convert(currencyRecharge));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得货币账户充值记录分页")
+    public CommonResult<PageResult<AppPayCurrencyRechargeRespVO>> getCurrencyRechargePage(@Valid PageParam pageReqVO) {
+        PageResult<PayCurrencyRechargeDO> pageResult = currencyRechargeService.getCurrencyRechargePackagePage(
+                getLoginUserDataId2(), getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO, true);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty(pageResult.getTotal()));
+        }
+        // 拼接数据
+        List<PayOrderDO> payOrderList = payOrderService.getOrderList(
+                convertList(pageResult.getList(), PayCurrencyRechargeDO::getPayOrderId));
+        return success(PayCurrencyRechargeConvert.INSTANCE.convertPage(pageResult, payOrderList));
+    }
+
+}

+ 42 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyRechargePackageController.java

@@ -0,0 +1,42 @@
+package com.citu.module.pay.controller.app.currency;
+
+import com.citu.framework.common.enums.CommonStatusEnum;
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.util.object.BeanUtils;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyPackageRespVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
+import com.citu.module.pay.service.currency.PayCurrencyRechargePackageService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Comparator;
+import java.util.List;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 APP - 货币账户充值套餐")
+@RestController
+@RequestMapping("/pay/currency-recharge-package")
+@Validated
+@Slf4j
+public class AppPayCurrencyRechargePackageController {
+
+    @Resource
+    private PayCurrencyRechargePackageService currencyRechargePackageService;
+
+    @GetMapping("/list")
+    @Operation(summary = "获得货币账户充值套餐列表")
+    public CommonResult<List<AppPayCurrencyPackageRespVO>> getCurrencyRechargePackageList() {
+        List<PayCurrencyRechargePackageDO> list = currencyRechargePackageService.getCurrencyRechargePackageList(
+                CommonStatusEnum.ENABLE.getStatus());
+        list.sort(Comparator.comparingLong(PayCurrencyRechargePackageDO::getPayPrice));
+        return success(BeanUtils.toBean(list, AppPayCurrencyPackageRespVO.class));
+    }
+
+}

+ 61 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/AppPayCurrencyTransactionController.java

@@ -0,0 +1,61 @@
+package com.citu.module.pay.controller.app.currency;
+
+import com.citu.framework.common.enums.UserTypeEnum;
+import com.citu.framework.common.pojo.CommonResult;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.common.util.object.BeanUtils;
+import com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionPageReqVO;
+import com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionRespVO;
+import com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionSummaryRespVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.service.currency.PayCurrencyTransactionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.time.LocalDateTime;
+
+import static com.citu.framework.common.pojo.CommonResult.success;
+import static com.citu.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserDataId2;
+import static com.citu.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 APP - 货币账户余额明细")
+@RestController
+@RequestMapping("/pay/currency-transaction")
+@Validated
+@Slf4j
+public class AppPayCurrencyTransactionController {
+
+    @Resource
+    private PayCurrencyTransactionService payCurrencyTransactionService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得货币账户流水分页")
+    public CommonResult<PageResult<AppPayCurrencyTransactionRespVO>> getCurrencyTransactionPage(
+            @Valid AppPayCurrencyTransactionPageReqVO pageReqVO) {
+        PageResult<PayCurrencyTransactionDO> pageResult = payCurrencyTransactionService.getCurrencyTransactionPage(
+                getLoginUserDataId2(), getLoginUserId(), UserTypeEnum.MEMBER.getValue(), pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AppPayCurrencyTransactionRespVO.class));
+    }
+
+    @GetMapping("/get-summary")
+    @Operation(summary = "获得货币账户流水统计")
+    @Parameter(name = "times", description = "时间段", required = true)
+    public CommonResult<AppPayCurrencyTransactionSummaryRespVO> getCurrencyTransactionSummary(
+            @RequestParam("createTime") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) LocalDateTime[] createTime) {
+        AppPayCurrencyTransactionSummaryRespVO summary = payCurrencyTransactionService.getCurrencyTransactionSummary(
+                getLoginUserDataId2(), getLoginUserId(), UserTypeEnum.MEMBER.getValue(), createTime);
+        return success(summary);
+    }
+
+}

+ 19 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/currency/AppPayCurrencyRespVO.java

@@ -0,0 +1,19 @@
+package com.citu.module.pay.controller.app.currency.vo.currency;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 APP - 用户货币账户 Response VO")
+@Data
+public class AppPayCurrencyRespVO {
+
+    @Schema(description = "货币账户余额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long balance;
+
+    @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Long totalExpense;
+
+    @Schema(description = "累计充值,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
+    private Long totalRecharge;
+
+}

+ 20 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyPackageRespVO.java

@@ -0,0 +1,20 @@
+package com.citu.module.pay.controller.app.currency.vo.recharge;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 APP - 货币账户充值套餐 Response VO")
+@Data
+public class AppPayCurrencyPackageRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+    @Schema(description = "套餐名", requiredMode = Schema.RequiredMode.REQUIRED, example = "小套餐")
+    private String name;
+
+    @Schema(description = "支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private Long payPrice;
+    @Schema(description = "赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    private Long bonusPrice;
+
+}

+ 26 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyRechargeCreateReqVO.java

@@ -0,0 +1,26 @@
+package com.citu.module.pay.controller.app.currency.vo.recharge;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.AssertTrue;
+import javax.validation.constraints.Min;
+import java.util.Objects;
+
+@Schema(description = "用户 APP - 创建货币账户充值 Request VO")
+@Data
+public class AppPayCurrencyRechargeCreateReqVO {
+
+    @Schema(description = "支付金额",  example = "1000")
+    @Min(value = 1,  message = "支付金额必须大于零")
+    private Long payPrice;
+
+    @Schema(description = "充值套餐编号", example = "1024")
+    private Long packageId;
+
+    @AssertTrue(message = "充值金额和充钱套餐不能同时为空")
+    public boolean isValidPayPriceAndPackageId() {
+        return Objects.nonNull(payPrice) || Objects.nonNull(packageId);
+    }
+
+}

+ 16 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyRechargeCreateRespVO.java

@@ -0,0 +1,16 @@
+package com.citu.module.pay.controller.app.currency.vo.recharge;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 APP - 创建货币账户充值 Resp VO")
+@Data
+public class AppPayCurrencyRechargeCreateRespVO {
+
+    @Schema(description = "货币账户充值编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long payOrderId;
+
+}

+ 42 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/recharge/AppPayCurrencyRechargeRespVO.java

@@ -0,0 +1,42 @@
+package com.citu.module.pay.controller.app.currency.vo.recharge;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 APP - 货币账户充值记录 Resp VO")
+@Data
+public class AppPayCurrencyRechargeRespVO {
+
+    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "用户实际到账余额", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long totalPrice;
+
+    @Schema(description = "实际支付金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
+    private Long payPrice;
+
+    @Schema(description = "钱包赠送金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
+    private Long bonusPrice;
+
+    @Schema(description = "支付成功的支付渠道", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String payChannelCode;
+
+    @Schema(description = "支付渠道名", example = "微信小程序支付")
+    private String payChannelName;
+
+    @Schema(description = "支付订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Long payOrderId;
+
+    @Schema(description = "支付成功的外部订单号", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String payOrderChannelOrderNo; // 从 PayOrderDO 的 channelOrderNo 字段
+
+    @Schema(description = "订单支付时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime payTime;
+
+    @Schema(description = "退款状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    private Integer refundStatus;
+
+}

+ 31 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/transaction/AppPayCurrencyTransactionPageReqVO.java

@@ -0,0 +1,31 @@
+package com.citu.module.pay.controller.app.currency.vo.transaction;
+
+import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.util.date.DateUtils;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 APP - 货币账户流水分页 Request VO")
+@Data
+public class AppPayCurrencyTransactionPageReqVO extends PageParam {
+
+    /**
+     * 类型 - 收入
+     */
+    public static final Integer TYPE_INCOME = 1;
+    /**
+     * 类型 - 支出
+     */
+    public static final Integer TYPE_EXPENSE = 2;
+
+    @Schema(description = "类型",  example = "1")
+    private Integer type;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 24 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/transaction/AppPayCurrencyTransactionRespVO.java

@@ -0,0 +1,24 @@
+package com.citu.module.pay.controller.app.currency.vo.transaction;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 APP - 货币账户流水分页 Response VO")
+@Data
+public class AppPayCurrencyTransactionRespVO {
+
+    @Schema(description = "业务分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer bizType;
+
+    @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long price;
+
+    @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆")
+    private String title;
+
+    @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 16 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/controller/app/currency/vo/transaction/AppPayCurrencyTransactionSummaryRespVO.java

@@ -0,0 +1,16 @@
+package com.citu.module.pay.controller.app.currency.vo.transaction;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 APP - 货币账户流水统计 Request VO")
+@Data
+public class AppPayCurrencyTransactionSummaryRespVO {
+
+    @Schema(description = "累计支出,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    private Long totalExpense;
+
+    @Schema(description = "累计收入,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
+    private Long totalIncome;
+
+}

+ 21 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyConvert.java

@@ -0,0 +1,21 @@
+package com.citu.module.pay.convert.currency;
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.currency.PayCurrencyRespVO;
+import com.citu.module.pay.controller.app.currency.vo.currency.AppPayCurrencyRespVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface PayCurrencyConvert {
+
+    PayCurrencyConvert INSTANCE = Mappers.getMapper(PayCurrencyConvert.class);
+
+    AppPayCurrencyRespVO convert(PayCurrencyDO bean);
+
+    PayCurrencyRespVO convert02(PayCurrencyDO bean);
+
+    PageResult<PayCurrencyRespVO> convertPage(PageResult<PayCurrencyDO> page);
+
+}

+ 44 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyRechargeConvert.java

@@ -0,0 +1,44 @@
+package com.citu.module.pay.convert.currency;
+
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.common.util.collection.CollectionUtils;
+import com.citu.framework.common.util.collection.MapUtils;
+import com.citu.framework.common.util.object.BeanUtils;
+import com.citu.framework.dict.core.DictFrameworkUtils;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeCreateRespVO;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeRespVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargeDO;
+import com.citu.module.pay.dal.dataobject.order.PayOrderDO;
+import com.citu.module.pay.enums.DictTypeConstants;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface PayCurrencyRechargeConvert {
+
+    PayCurrencyRechargeConvert INSTANCE = Mappers.getMapper(PayCurrencyRechargeConvert.class);
+
+    @Mapping(target = "totalPrice", expression = "java( payPrice + bonusPrice)")
+    PayCurrencyRechargeDO convert(Long walletId, Long payPrice, Long bonusPrice, Long packageId);
+
+    AppPayCurrencyRechargeCreateRespVO convert(PayCurrencyRechargeDO bean);
+
+    default PageResult<AppPayCurrencyRechargeRespVO> convertPage(PageResult<PayCurrencyRechargeDO> pageResult,
+                                                                 List<PayOrderDO> payOrderList) {
+        PageResult<AppPayCurrencyRechargeRespVO> voPageResult = BeanUtils.toBean(pageResult, AppPayCurrencyRechargeRespVO.class);
+        Map<Long, PayOrderDO> payOrderMap = CollectionUtils.convertMap(payOrderList, PayOrderDO::getId);
+        voPageResult.getList().forEach(recharge -> {
+            recharge.setPayChannelName(DictFrameworkUtils.getDictDataLabel(
+                    DictTypeConstants.CHANNEL_CODE, recharge.getPayChannelCode()));
+            MapUtils.findAndThen(payOrderMap, recharge.getPayOrderId(),
+                    order -> recharge.setPayOrderChannelOrderNo(order.getChannelOrderNo()));
+        });
+        return voPageResult;
+    }
+
+}

+ 29 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyRechargePackageConvert.java

@@ -0,0 +1,29 @@
+package com.citu.module.pay.convert.currency;
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageCreateReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageRespVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageUpdateReqVO;
+
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+@Mapper
+public interface PayCurrencyRechargePackageConvert {
+
+    PayCurrencyRechargePackageConvert INSTANCE = Mappers.getMapper(PayCurrencyRechargePackageConvert.class);
+
+    PayCurrencyRechargePackageDO convert(CurrencyRechargePackageCreateReqVO bean);
+
+    PayCurrencyRechargePackageDO convert(CurrencyRechargePackageUpdateReqVO bean);
+
+    CurrencyRechargePackageRespVO convert(PayCurrencyRechargePackageDO bean);
+
+    List<CurrencyRechargePackageRespVO> convertList(List<PayCurrencyRechargePackageDO> list);
+
+    PageResult<CurrencyRechargePackageRespVO> convertPage(PageResult<PayCurrencyRechargePackageDO> page);
+
+}

+ 19 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/convert/currency/PayCurrencyTransactionConvert.java

@@ -0,0 +1,19 @@
+package com.citu.module.pay.convert.currency;
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.transaction.PayCurrencyTransactionRespVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.service.currency.bo.CurrencyTransactionCreateReqBO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface PayCurrencyTransactionConvert {
+
+    PayCurrencyTransactionConvert INSTANCE = Mappers.getMapper(PayCurrencyTransactionConvert.class);
+
+    PageResult<PayCurrencyTransactionRespVO> convertPage2(PageResult<PayCurrencyTransactionDO> page);
+
+    PayCurrencyTransactionDO convert(CurrencyTransactionCreateReqBO bean);
+
+}

+ 58 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyDO.java

@@ -0,0 +1,58 @@
+package com.citu.module.pay.dal.dataobject.currency;
+
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.citu.framework.common.enums.UserTypeEnum;
+import com.citu.framework.mybatis.core.dataobject.BaseDO;
+import lombok.Data;
+
+/**
+ * 货币账户 DO
+ *
+ * @author Rayson
+ */
+@TableName(value ="pay_currency")
+@KeySequence("pay_currency_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class PayCurrencyDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 用户 id
+     *
+     */
+    private Long userId;
+    /**
+     * 依赖数据 id
+     */
+    private Long dataId;
+    /**
+     * 用户类型, 预留 多商户转帐可能需要用到
+     *
+     * 关联 {@link UserTypeEnum}
+     */
+    private Integer userType;
+    /**
+     * 余额,单位分
+     */
+    private Long balance;
+    /**
+     * 冻结金额,单位分
+     */
+    private Long freezePrice;
+    /**
+     * 累计支出,单位分
+     */
+    private Long totalExpense;
+    /**
+     * 累计充值,单位分
+     */
+    private Long totalRecharge;
+
+}

+ 116 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyRechargeDO.java

@@ -0,0 +1,116 @@
+package com.citu.module.pay.dal.dataobject.currency;
+
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.citu.framework.mybatis.core.dataobject.BaseDO;
+import com.citu.module.pay.dal.dataobject.order.PayOrderDO;
+import com.citu.module.pay.dal.dataobject.refund.PayRefundDO;
+import com.citu.module.pay.enums.refund.PayRefundStatusEnum;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 货币账户充值
+ */
+@TableName(value ="pay_currency_recharge")
+@KeySequence("pay_currency_recharge_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class PayCurrencyRechargeDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 货币账户编号
+     *
+     * 关联 {@link PayCurrencyDO#getId()}
+     */
+    private Long currencyId;
+
+    /**
+     * 用户实际到账余额
+     *
+     * 例如充 100 送 20,则该值是 120
+     */
+    private Long totalPrice;
+    /**
+     * 实际支付金额
+     */
+    private Long payPrice;
+    /**
+     * 赠送金额
+     */
+    private Long bonusPrice;
+
+    /**
+     * 充值套餐编号
+     *
+     * 关联 {@link PayCurrencyRechargeDO#getPackageId()} 字段
+     */
+    private Long packageId;
+
+    /**
+     * 是否已支付
+     *
+     * true - 已支付
+     * false - 未支付
+     */
+    private Boolean payStatus;
+
+    /**
+     * 支付订单编号
+     *
+     * 关联 {@link PayOrderDO#getId()}
+     */
+    private Long payOrderId;
+
+    /**
+     * 支付成功的支付渠道
+     *
+     * 冗余 {@link PayOrderDO#getChannelCode()}
+     */
+    private String payChannelCode;
+    /**
+     * 订单支付时间
+     */
+    private LocalDateTime payTime;
+
+    /**
+     * 支付退款单编号
+     *
+     * 关联 {@link PayRefundDO#getId()}
+     */
+    private Long payRefundId;
+
+    /**
+     * 退款金额,包含赠送金额
+     */
+    private Long refundTotalPrice;
+    /**
+     * 退款支付金额
+     */
+    private Long refundPayPrice;
+
+    /**
+     * 退款钱包赠送金额
+     */
+    private Long refundBonusPrice;
+
+    /**
+     * 退款时间
+     */
+    private LocalDateTime refundTime;
+
+    /**
+     * 退款状态
+     *
+     * 枚举 {@link PayRefundStatusEnum}
+     */
+    private Integer refundStatus;
+
+}

+ 47 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyRechargePackageDO.java

@@ -0,0 +1,47 @@
+package com.citu.module.pay.dal.dataobject.currency;
+
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.citu.framework.mybatis.core.dataobject.BaseDO;
+import lombok.Data;
+
+/**
+ * 货币账户充值套餐 DO
+ *
+ * 通过充值套餐时,可以赠送一定金额;
+ *
+ * @author Rayson
+ */
+@TableName(value ="pay_currency_recharge_package")
+@KeySequence("pay_currency_recharge_package_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class PayCurrencyRechargePackageDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 套餐名
+     */
+    private String name;
+
+    /**
+     * 支付金额
+     */
+    private Long payPrice;
+    /**
+     * 赠送金额
+     */
+    private Long bonusPrice;
+
+    /**
+     * 状态
+     *
+     * 枚举 {@link com.citu.framework.common.enums.CommonStatusEnum}
+     */
+    private Integer status;
+
+}

+ 66 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/dataobject/currency/PayCurrencyTransactionDO.java

@@ -0,0 +1,66 @@
+package com.citu.module.pay.dal.dataobject.currency;
+
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.citu.framework.mybatis.core.dataobject.BaseDO;
+import com.citu.module.pay.enums.wallet.PayWalletBizTypeEnum;
+import lombok.Data;
+
+/**
+ * 货币账户流水 DO
+ *
+ * @author Rayson
+ */
+@TableName(value ="pay_currency_transaction")
+@KeySequence("pay_currency_transaction_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+public class PayCurrencyTransactionDO extends BaseDO {
+
+    /**
+     * 编号
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 流水号
+     */
+    private String no;
+
+    /**
+     * 货币账户编号
+     *
+     * 关联 {@link PayCurrencyDO#getId()}
+     */
+    private Long currencyId;
+
+    /**
+     * 关联业务分类
+     *
+     * 枚举 {@link PayWalletBizTypeEnum#getType()}
+     */
+    private Integer bizType;
+
+    /**
+     * 关联业务编号
+     */
+    private String bizId;
+
+    /**
+     * 流水说明
+     */
+    private String title;
+
+    /**
+     * 交易金额,单位分
+     *
+     * 正值表示余额增加,负值表示余额减少
+     */
+    private Long price;
+
+    /**
+     * 交易后余额,单位分
+     */
+    private Long balance;
+}

+ 124 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyMapper.java

@@ -0,0 +1,124 @@
+package com.citu.module.pay.dal.mysql.currency;
+
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.mybatis.core.mapper.BaseMapperX;
+import com.citu.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.citu.module.pay.controller.admin.currency.vo.currency.PayCurrencyPageReqVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayCurrencyMapper extends BaseMapperX<PayCurrencyDO> {
+
+    default PayCurrencyDO selectByUserIdAndType(Long dataId, Long userId, Integer userType) {
+        return selectOne(PayCurrencyDO::getUserId, userId,
+                PayCurrencyDO::getDataId, dataId,
+                PayCurrencyDO::getUserType, userType);
+    }
+
+    default PageResult<PayCurrencyDO> selectPage(PayCurrencyPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<PayCurrencyDO>()
+                .eqIfPresent(PayCurrencyDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(PayCurrencyDO::getDataId, reqVO.getDataId())
+                .eqIfPresent(PayCurrencyDO::getUserType, reqVO.getUserType())
+                .betweenIfPresent(PayCurrencyDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(PayCurrencyDO::getId));
+    }
+
+    /**
+     * 当消费退款时候, 更新货币账户
+     *
+     * @param id    货币账户 id
+     * @param price 消费金额
+     */
+    default int updateWhenConsumptionRefund(Long id, Long price) {
+        LambdaUpdateWrapper<PayCurrencyDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayCurrencyDO>()
+                .setSql(" balance = balance + " + price
+                        + ", total_expense = total_expense - " + price)
+                .eq(PayCurrencyDO::getId, id);
+        return update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 当消费时候, 更新货币账户
+     *
+     * @param price 消费金额
+     * @param id    货币账户 id
+     */
+    default int updateWhenConsumption(Long id, Long price) {
+        LambdaUpdateWrapper<PayCurrencyDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayCurrencyDO>()
+                .setSql(" balance = balance - " + price
+                        + ", total_expense = total_expense + " + price)
+                .eq(PayCurrencyDO::getId, id)
+                .ge(PayCurrencyDO::getBalance, price); // cas 逻辑
+        return update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 当充值的时候,更新货币账户
+     *
+     * @param id    货币账户 id
+     * @param price 货币账户金额
+     */
+    default int updateWhenRecharge(Long id, Long price) {
+        LambdaUpdateWrapper<PayCurrencyDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayCurrencyDO>()
+                .setSql(" balance = balance + " + price
+                        + ", total_recharge = total_recharge + " + price)
+                .eq(PayCurrencyDO::getId, id);
+        return update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 冻结货币账户部分余额
+     *
+     * @param id    货币账户 id
+     * @param price 冻结金额
+     */
+    default int freezePrice(Long id, Long price) {
+        LambdaUpdateWrapper<PayCurrencyDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayCurrencyDO>()
+                .setSql(" balance = balance - " + price
+                        + ", freeze_price = freeze_price + " + price)
+                .eq(PayCurrencyDO::getId, id)
+                .ge(PayCurrencyDO::getBalance, price); // cas 逻辑
+        return update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 解冻货币账户余额
+     *
+     * @param id    货币账户 id
+     * @param price 解冻金额
+     */
+    default int unFreezePrice(Long id, Long price) {
+        LambdaUpdateWrapper<PayCurrencyDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayCurrencyDO>()
+                .setSql(" balance = balance + " + price
+                        + ", freeze_price = freeze_price - " + price)
+                .eq(PayCurrencyDO::getId, id)
+                .ge(PayCurrencyDO::getFreezePrice, price); // cas 逻辑
+        return update(null, lambdaUpdateWrapper);
+    }
+
+    /**
+     * 当充值退款时, 更新货币账户
+     *
+     * @param id    货币账户 id
+     * @param price 退款金额
+     */
+    default int updateWhenRechargeRefund(Long id, Long price) {
+        LambdaUpdateWrapper<PayCurrencyDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayCurrencyDO>()
+                .setSql(" freeze_price = freeze_price - " + price
+                        + ", total_recharge = total_recharge - " + price)
+                .eq(PayCurrencyDO::getId, id)
+                .ge(PayCurrencyDO::getFreezePrice, price)
+                .ge(PayCurrencyDO::getTotalRecharge, price);// cas 逻辑
+        return update(null, lambdaUpdateWrapper);
+    }
+
+
+}
+
+
+
+

+ 30 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyRechargeMapper.java

@@ -0,0 +1,30 @@
+package com.citu.module.pay.dal.mysql.currency;
+
+import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.mybatis.core.mapper.BaseMapperX;
+import com.citu.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargeDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface PayCurrencyRechargeMapper extends BaseMapperX<PayCurrencyRechargeDO> {
+
+    default int updateByIdAndPaid(Long id, boolean wherePayStatus, PayCurrencyRechargeDO updateObj) {
+        return update(updateObj, new LambdaQueryWrapperX<PayCurrencyRechargeDO>()
+                .eq(PayCurrencyRechargeDO::getId, id).eq(PayCurrencyRechargeDO::getPayStatus, wherePayStatus));
+    }
+
+    default int updateByIdAndRefunded(Long id, Integer whereRefundStatus, PayCurrencyRechargeDO updateObj) {
+        return update(updateObj, new LambdaQueryWrapperX<PayCurrencyRechargeDO>()
+                .eq(PayCurrencyRechargeDO::getId, id).eq(PayCurrencyRechargeDO::getRefundStatus, whereRefundStatus));
+    }
+
+    default PageResult<PayCurrencyRechargeDO> selectPage(PageParam pageReqVO, Long walletId, Boolean payStatus) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<PayCurrencyRechargeDO>()
+                .eq(PayCurrencyRechargeDO::getCurrencyId, walletId)
+                .eq(PayCurrencyRechargeDO::getPayStatus, payStatus)
+                .orderByDesc(PayCurrencyRechargeDO::getId));
+    }
+
+}

+ 33 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyRechargePackageMapper.java

@@ -0,0 +1,33 @@
+package com.citu.module.pay.dal.mysql.currency;
+
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.mybatis.core.mapper.BaseMapperX;
+import com.citu.framework.mybatis.core.query.LambdaQueryWrapperX;
+
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackagePageReqVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface PayCurrencyRechargePackageMapper extends BaseMapperX<PayCurrencyRechargePackageDO> {
+
+    default PageResult<PayCurrencyRechargePackageDO> selectPage(CurrencyRechargePackagePageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<PayCurrencyRechargePackageDO>()
+                .likeIfPresent(PayCurrencyRechargePackageDO::getName, reqVO.getName())
+                .eqIfPresent(PayCurrencyRechargePackageDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(PayCurrencyRechargePackageDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(PayCurrencyRechargePackageDO::getPayPrice));
+    }
+
+    default PayCurrencyRechargePackageDO selectByName(String name) {
+        return selectOne(PayCurrencyRechargePackageDO::getName, name);
+    }
+
+    default List<PayCurrencyRechargePackageDO> selectListByStatus(Integer status) {
+        return selectList(PayCurrencyRechargePackageDO::getStatus, status);
+    }
+
+}

+ 66 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/dal/mysql/currency/PayCurrencyTransactionMapper.java

@@ -0,0 +1,66 @@
+package com.citu.module.pay.dal.mysql.currency;
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.mybatis.core.mapper.BaseMapperX;
+import com.citu.framework.mybatis.core.query.LambdaQueryWrapperX;
+import com.citu.framework.mybatis.core.query.QueryWrapperX;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionPageReqVO.TYPE_EXPENSE;
+import static com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionPageReqVO.TYPE_INCOME;
+
+
+@Mapper
+public interface PayCurrencyTransactionMapper extends BaseMapperX<PayCurrencyTransactionDO> {
+
+    default PageResult<PayCurrencyTransactionDO> selectPage(Long currencyId, Integer type,
+                                                          PageParam pageParam, LocalDateTime[] createTime) {
+        LambdaQueryWrapperX<PayCurrencyTransactionDO> query = new LambdaQueryWrapperX<PayCurrencyTransactionDO>()
+                .eqIfPresent(PayCurrencyTransactionDO::getCurrencyId, currencyId);
+        if (Objects.equals(type, TYPE_INCOME)) {
+            query.gt(PayCurrencyTransactionDO::getPrice, 0);
+        } else if (Objects.equals(type, TYPE_EXPENSE)) {
+            query.lt(PayCurrencyTransactionDO::getPrice, 0);
+        }
+        query.betweenIfPresent(PayCurrencyTransactionDO::getCreateTime, createTime);
+        query.orderByDesc(PayCurrencyTransactionDO::getId);
+        return selectPage(pageParam, query);
+    }
+
+    default Long selectPriceSum(Long currencyId, Integer type, LocalDateTime[] createTime) {
+        // SQL sum 查询
+        List<Map<String, Object>> result = selectMaps(new QueryWrapperX<PayCurrencyTransactionDO>()
+                .select("SUM(price) AS priceSum")
+                .gt(Objects.equals(type, TYPE_INCOME), "price", 0) // 收入
+                .lt(Objects.equals(type, TYPE_EXPENSE), "price", 0) // 支出
+                .eq("currency_id", currencyId)
+                .between("create_time", createTime[0], createTime[1]));
+        // 获得 sum 结果
+        Map<String, Object> first = CollUtil.getFirst(result);
+        return MapUtil.getLong(first, "priceSum", 0L);
+    }
+
+    default PayCurrencyTransactionDO selectByNo(String no) {
+        return selectOne(PayCurrencyTransactionDO::getNo, no);
+    }
+
+    default PayCurrencyTransactionDO selectByBiz(String bizId, Integer bizType) {
+        return selectOne(PayCurrencyTransactionDO::getBizId, bizId,
+                PayCurrencyTransactionDO::getBizType, bizType);
+    }
+
+}
+
+
+
+

+ 193 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/framework/pay/core/CurrencyPayClient.java

@@ -0,0 +1,193 @@
+package com.citu.module.pay.framework.pay.core;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.citu.framework.common.exception.ServiceException;
+import com.citu.framework.pay.core.client.dto.order.PayOrderRespDTO;
+import com.citu.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import com.citu.framework.pay.core.client.dto.refund.PayRefundRespDTO;
+import com.citu.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import com.citu.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
+import com.citu.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
+import com.citu.framework.pay.core.client.impl.AbstractPayClient;
+import com.citu.framework.pay.core.client.impl.NonePayClientConfig;
+import com.citu.framework.pay.core.enums.channel.PayChannelEnum;
+import com.citu.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
+import com.citu.framework.pay.core.enums.transfer.PayTransferTypeEnum;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.dal.dataobject.order.PayOrderExtensionDO;
+import com.citu.module.pay.dal.dataobject.refund.PayRefundDO;
+import com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum;
+import com.citu.module.pay.enums.order.PayOrderStatusEnum;
+import com.citu.module.pay.service.currency.PayCurrencyService;
+import com.citu.module.pay.service.currency.PayCurrencyTransactionService;
+import com.citu.module.pay.service.order.PayOrderService;
+import com.citu.module.pay.service.refund.PayRefundService;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+
+import static com.citu.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
+import static com.citu.module.pay.enums.ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND;
+import static com.citu.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FOUND;
+
+/**
+ * 货币账户支付的 PayClient 实现类
+ *
+ * @author Rayson
+ */
+@Slf4j
+public class CurrencyPayClient extends AbstractPayClient<NonePayClientConfig> {
+
+    public static final String USER_ID_KEY = "user_id";
+    public static final String DATA_ID_KEY = "data_id";
+    public static final String USER_TYPE_KEY = "user_type";
+
+    private PayCurrencyService wallService;
+    private PayCurrencyTransactionService currencyTransactionService;
+
+    private PayOrderService orderService;
+    private PayRefundService refundService;
+
+    public CurrencyPayClient(Long channelId, NonePayClientConfig config) {
+        super(channelId, PayChannelEnum.CURRENCY.getCode(), config);
+    }
+
+    @Override
+    protected void doInit() {
+        if (wallService == null) {
+            wallService = SpringUtil.getBean(PayCurrencyService.class);
+        }
+        if (currencyTransactionService == null) {
+            currencyTransactionService = SpringUtil.getBean(PayCurrencyTransactionService.class);
+        }
+    }
+
+    @Override
+    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        try {
+            Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY);
+            Long dataId = MapUtil.getLong(reqDTO.getChannelExtras(), DATA_ID_KEY);
+            Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY);
+            Assert.notNull(userId, "用户 id 不能为空");
+            Assert.notNull(userId, "dataId id 不能为空");
+            Assert.notNull(userType, "用户类型不能为空");
+            PayCurrencyTransactionDO transaction = wallService.orderPay(dataId, userId, userType, reqDTO.getOutTradeNo(),
+                    Long.valueOf(reqDTO.getPrice()));
+            return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
+                    transaction.getCreateTime(),
+                    reqDTO.getOutTradeNo(), transaction);
+        } catch (Throwable ex) {
+            log.error("[doUnifiedOrder] 失败", ex);
+            Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
+            String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
+            if (ex instanceof ServiceException) {
+                ServiceException serviceException = (ServiceException) ex;
+                errorCode = serviceException.getCode();
+                errorMsg = serviceException.getMessage();
+            }
+            return PayOrderRespDTO.closedOf(String.valueOf(errorCode), errorMsg,
+                    reqDTO.getOutTradeNo(), "");
+        }
+    }
+
+    @Override
+    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
+        throw new UnsupportedOperationException("货币账户支付无支付回调");
+    }
+
+    @Override
+    protected PayOrderRespDTO doGetOrder(String outTradeNo) {
+        if (orderService == null) {
+            orderService = SpringUtil.getBean(PayOrderService.class);
+        }
+        PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
+        // 支付交易拓展单不存在, 返回关闭状态
+        if (orderExtension == null) {
+            return PayOrderRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()),
+                    PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, "");
+        }
+        // 关闭状态
+        if (PayOrderStatusEnum.isClosed(orderExtension.getStatus())) {
+            return PayOrderRespDTO.closedOf(orderExtension.getChannelErrorCode(),
+                    orderExtension.getChannelErrorMsg(), outTradeNo, "");
+        }
+        // 成功状态
+        if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) {
+            PayCurrencyTransactionDO currencyTransaction = currencyTransactionService.getCurrencyTransaction(
+                    String.valueOf(orderExtension.getOrderId()), PayCurrencyBizTypeEnum.PAYMENT);
+            Assert.notNull(currencyTransaction, "支付单 {} 货币账户流水不能为空", outTradeNo);
+            return PayOrderRespDTO.successOf(currencyTransaction.getNo(), currencyTransaction.getCreator(),
+                    currencyTransaction.getCreateTime(), outTradeNo, currencyTransaction);
+        }
+        // 其它状态为无效状态
+        log.error("[doGetOrder] 支付单 {} 的状态不正确", outTradeNo);
+        throw new IllegalStateException(String.format("支付单[%s] 状态不正确", outTradeNo));
+    }
+
+    @Override
+    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
+        try {
+            PayCurrencyTransactionDO payCurrencyTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(),
+                    Long.valueOf(reqDTO.getRefundPrice()), reqDTO.getReason());
+            return PayRefundRespDTO.successOf(payCurrencyTransaction.getNo(), payCurrencyTransaction.getCreateTime(),
+                    reqDTO.getOutRefundNo(), payCurrencyTransaction);
+        } catch (Throwable ex) {
+            log.error("[doUnifiedRefund] 失败", ex);
+            Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
+            String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
+            if (ex instanceof ServiceException) {
+                ServiceException serviceException = (ServiceException) ex;
+                errorCode = serviceException.getCode();
+                errorMsg = serviceException.getMessage();
+            }
+            return PayRefundRespDTO.failureOf(String.valueOf(errorCode), errorMsg,
+                    reqDTO.getOutRefundNo(), "");
+        }
+    }
+
+    @Override
+    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
+        throw new UnsupportedOperationException("货币账户支付无退款回调");
+    }
+
+    @Override
+    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
+        if (null == refundService) {
+            refundService = SpringUtil.getBean(PayRefundService.class);
+        }
+        PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo);
+        // 支付退款单不存在, 返回退款失败状态
+        if (payRefund == null) {
+            return PayRefundRespDTO.failureOf(String.valueOf(REFUND_NOT_FOUND), REFUND_NOT_FOUND.getMsg(),
+                    outRefundNo, "");
+        }
+        // 退款失败
+        if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
+            return PayRefundRespDTO.failureOf(payRefund.getChannelErrorCode(), payRefund.getChannelErrorMsg(),
+                    outRefundNo, "");
+        }
+        // 退款成功
+        if (PayRefundStatusRespEnum.isSuccess(payRefund.getStatus())) {
+            PayCurrencyTransactionDO currencyTransaction = currencyTransactionService.getCurrencyTransaction(
+                    String.valueOf(payRefund.getId()), PayCurrencyBizTypeEnum.PAYMENT_REFUND);
+            Assert.notNull(currencyTransaction, "支付退款单 {} 货币账户流水不能为空", outRefundNo);
+            return PayRefundRespDTO.successOf(currencyTransaction.getNo(), currencyTransaction.getCreateTime(),
+                    outRefundNo, currencyTransaction);
+        }
+        // 其它状态为无效状态
+        log.error("[doGetRefund] 支付退款单 {} 的状态不正确", outRefundNo);
+        throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo));
+    }
+
+    @Override
+    public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
+        throw new UnsupportedOperationException("待实现");
+    }
+
+    @Override
+    protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
+        throw new UnsupportedOperationException("待实现");
+    }
+}

+ 2 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/channel/PayChannelServiceImpl.java

@@ -13,6 +13,7 @@ import com.citu.module.pay.controller.admin.channel.vo.PayChannelUpdateReqVO;
 import com.citu.module.pay.convert.channel.PayChannelConvert;
 import com.citu.module.pay.dal.dataobject.channel.PayChannelDO;
 import com.citu.module.pay.dal.mysql.channel.PayChannelMapper;
+import com.citu.module.pay.framework.pay.core.CurrencyPayClient;
 import com.citu.module.pay.framework.pay.core.WalletPayClient;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -52,6 +53,7 @@ public class PayChannelServiceImpl implements PayChannelService {
     @PostConstruct
     public void init() {
         payClientFactory.registerPayClientClass(PayChannelEnum.WALLET, WalletPayClient.class);
+        payClientFactory.registerPayClientClass(PayChannelEnum.CURRENCY, CurrencyPayClient.class);
     }
 
     @Override

+ 71 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargePackageService.java

@@ -0,0 +1,71 @@
+package com.citu.module.pay.service.currency;
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageCreateReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackagePageReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageUpdateReqVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
+
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 货币账户充值套餐 Service 接口
+ *
+ * @author Rayson
+ */
+public interface PayCurrencyRechargePackageService {
+
+    /**
+     * 获取充值套餐
+     * @param packageId 充值套餐编号
+     */
+    PayCurrencyRechargePackageDO getCurrencyRechargePackage(Long packageId);
+
+    /**
+     * 校验充值套餐的有效性, 无效的话抛出 ServiceException 异常
+     *
+     * @param packageId 充值套餐编号
+     */
+    PayCurrencyRechargePackageDO validCurrencyRechargePackage(Long packageId);
+
+    /**
+     * 创建充值套餐
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createCurrencyRechargePackage(@Valid CurrencyRechargePackageCreateReqVO createReqVO);
+
+    /**
+     * 更新充值套餐
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateCurrencyRechargePackage(@Valid CurrencyRechargePackageUpdateReqVO updateReqVO);
+
+    /**
+     * 删除充值套餐
+     *
+     * @param id 编号
+     */
+    void deleteCurrencyRechargePackage(Long id);
+
+    /**
+     * 获得充值套餐分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 充值套餐分页
+     */
+    PageResult<PayCurrencyRechargePackageDO> getCurrencyRechargePackagePage(CurrencyRechargePackagePageReqVO pageReqVO);
+
+    /**
+     * 获得充值套餐列表
+     *
+     * @param status 状态
+     * @return 充值套餐列表
+     */
+    List<PayCurrencyRechargePackageDO> getCurrencyRechargePackageList(Integer status);
+
+}

+ 112 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargePackageServiceImpl.java

@@ -0,0 +1,112 @@
+package com.citu.module.pay.service.currency;
+
+import cn.hutool.core.util.StrUtil;
+import com.citu.framework.common.enums.CommonStatusEnum;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageCreateReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackagePageReqVO;
+import com.citu.module.pay.controller.admin.currency.vo.rechargepackage.CurrencyRechargePackageUpdateReqVO;
+import com.citu.module.pay.convert.currency.PayCurrencyRechargePackageConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
+import com.citu.module.pay.dal.mysql.currency.PayCurrencyRechargePackageMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.citu.module.pay.enums.ErrorCodeConstants.*;
+
+/**
+ * 货币账户充值套餐 Service 实现类
+ *
+ * @author Rayson
+ */
+@Service
+public class PayCurrencyRechargePackageServiceImpl implements PayCurrencyRechargePackageService {
+
+    @Resource
+    private PayCurrencyRechargePackageMapper currencyRechargePackageMapper;
+
+    @Override
+    public PayCurrencyRechargePackageDO getCurrencyRechargePackage(Long packageId) {
+        return currencyRechargePackageMapper.selectById(packageId);
+    }
+
+    @Override
+    public PayCurrencyRechargePackageDO validCurrencyRechargePackage(Long packageId) {
+        PayCurrencyRechargePackageDO rechargePackageDO = currencyRechargePackageMapper.selectById(packageId);
+        if (rechargePackageDO == null) {
+            throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND);
+        }
+        if (CommonStatusEnum.DISABLE.getStatus().equals(rechargePackageDO.getStatus())) {
+            throw exception(WALLET_RECHARGE_PACKAGE_IS_DISABLE);
+        }
+        return rechargePackageDO;
+    }
+
+    @Override
+    public Long createCurrencyRechargePackage(CurrencyRechargePackageCreateReqVO createReqVO) {
+        // 校验套餐名是否唯一
+        validateRechargePackageNameUnique(null, createReqVO.getName());
+
+        // 插入
+        PayCurrencyRechargePackageDO currencyRechargePackage = PayCurrencyRechargePackageConvert.INSTANCE.convert(createReqVO);
+        currencyRechargePackageMapper.insert(currencyRechargePackage);
+        // 返回
+        return currencyRechargePackage.getId();
+    }
+
+    @Override
+    public void updateCurrencyRechargePackage(CurrencyRechargePackageUpdateReqVO updateReqVO) {
+        // 校验存在
+        validateCurrencyRechargePackageExists(updateReqVO.getId());
+        // 校验套餐名是否唯一
+        validateRechargePackageNameUnique(updateReqVO.getId(), updateReqVO.getName());
+
+        // 更新
+        PayCurrencyRechargePackageDO updateObj = PayCurrencyRechargePackageConvert.INSTANCE.convert(updateReqVO);
+        currencyRechargePackageMapper.updateById(updateObj);
+    }
+
+    private void validateRechargePackageNameUnique(Long id, String name) {
+        if (StrUtil.isBlank(name)) {
+            return;
+        }
+        PayCurrencyRechargePackageDO rechargePackage = currencyRechargePackageMapper.selectByName(name);
+        if (rechargePackage == null) {
+            return ;
+        }
+        if (id == null) {
+            throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS);
+        }
+        if (!id.equals(rechargePackage.getId())) {
+            throw exception(WALLET_RECHARGE_PACKAGE_NAME_EXISTS);
+        }
+    }
+
+    @Override
+    public void deleteCurrencyRechargePackage(Long id) {
+        // 校验存在
+        validateCurrencyRechargePackageExists(id);
+        // 删除
+        currencyRechargePackageMapper.deleteById(id);
+    }
+
+    private void validateCurrencyRechargePackageExists(Long id) {
+        if (currencyRechargePackageMapper.selectById(id) == null) {
+            throw exception(WALLET_RECHARGE_PACKAGE_NOT_FOUND);
+        }
+    }
+
+    @Override
+    public PageResult<PayCurrencyRechargePackageDO> getCurrencyRechargePackagePage(CurrencyRechargePackagePageReqVO pageReqVO) {
+        return currencyRechargePackageMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<PayCurrencyRechargePackageDO> getCurrencyRechargePackageList(Integer status) {
+        return currencyRechargePackageMapper.selectListByStatus(status);
+    }
+
+}

+ 65 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargeService.java

@@ -0,0 +1,65 @@
+package com.citu.module.pay.service.currency;
+
+import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeCreateReqVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargeDO;
+
+/**
+ * 货币账户充值 Service 接口
+ *
+ * @author Rayson
+ */
+public interface PayCurrencyRechargeService {
+
+    /**
+     * 创建货币账户充值记录(发起充值)
+     *
+     * @param dataId      绑定的数据编号
+     * @param userId      用户 id
+     * @param userType    用户类型
+     * @param createReqVO 货币账户充值请求 VO
+     * @param userIp      用户Ip
+     * @return 货币账户充值记录
+     */
+    PayCurrencyRechargeDO createCurrencyRecharge(Long dataId, Long userId, Integer userType, String userIp,
+                                                 AppPayCurrencyRechargeCreateReqVO createReqVO);
+
+    /**
+     * 获得货币账户充值记录分页
+     *
+     * @param dataId    绑定的数据编号
+     * @param userId    用户编号
+     * @param userType  用户类型
+     * @param pageReqVO 分页请求
+     * @param payStatus 是否支付
+     * @return 货币账户充值记录分页
+     */
+    PageResult<PayCurrencyRechargeDO> getCurrencyRechargePackagePage(Long dataId, Long userId, Integer userType,
+                                                                     PageParam pageReqVO, Boolean payStatus);
+
+    /**
+     * 更新货币账户充值成功
+     *
+     * @param id         货币账户充值记录 id
+     * @param payOrderId 支付订单 id
+     */
+    void updateCurrencyRechargerPaid(Long id, Long payOrderId);
+
+    /**
+     * 发起货币账户充值退款
+     *
+     * @param id     货币账户充值编号
+     * @param userIp 用户 ip 地址
+     */
+    void refundCurrencyRecharge(Long id, String userIp);
+
+    /**
+     * 更新货币账户充值记录为已退款
+     *
+     * @param id          货币账户充值 id
+     * @param payRefundId 退款单id
+     */
+    void updateCurrencyRechargeRefunded(Long id, Long payRefundId);
+
+}

+ 320 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyRechargeServiceImpl.java

@@ -0,0 +1,320 @@
+package com.citu.module.pay.service.currency;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.extra.spring.SpringUtil;
+import com.citu.framework.common.pojo.PageParam;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
+import com.citu.module.pay.api.order.dto.PayOrderCreateReqDTO;
+import com.citu.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import com.citu.module.pay.controller.app.currency.vo.recharge.AppPayCurrencyRechargeCreateReqVO;
+import com.citu.module.pay.convert.currency.PayCurrencyRechargeConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargeDO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyRechargePackageDO;
+import com.citu.module.pay.dal.dataobject.order.PayOrderDO;
+import com.citu.module.pay.dal.dataobject.refund.PayRefundDO;
+import com.citu.module.pay.dal.mysql.currency.PayCurrencyRechargeMapper;
+import com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum;
+import com.citu.module.pay.enums.order.PayOrderStatusEnum;
+import com.citu.module.pay.enums.refund.PayRefundStatusEnum;
+import com.citu.module.pay.service.order.PayOrderService;
+import com.citu.module.pay.service.refund.PayRefundService;
+import com.citu.module.system.api.social.SocialClientApi;
+import com.citu.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+import static cn.hutool.core.util.ObjectUtil.notEqual;
+import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.citu.framework.common.util.date.LocalDateTimeUtils.addTime;
+import static com.citu.framework.common.util.json.JsonUtils.toJsonString;
+import static com.citu.framework.common.util.number.MoneyUtils.fenToYuanStr;
+import static com.citu.module.pay.enums.ErrorCodeConstants.*;
+import static com.citu.module.pay.enums.MessageTemplateConstants.WXA_WALLET_RECHARGER_PAID;
+import static com.citu.module.pay.enums.refund.PayRefundStatusEnum.*;
+
+/**
+ * 货币账户充值 Service 实现类
+ *
+ * @author Rayson
+ */
+@Service
+@Slf4j
+public class PayCurrencyRechargeServiceImpl implements PayCurrencyRechargeService {
+
+    /**
+     * TODO 芋艿:放到 payconfig
+     */
+    private static final Long WALLET_PAY_APP_ID = 8L;
+
+    private static final String WALLET_RECHARGE_ORDER_SUBJECT = "货币账户余额充值";
+    @Resource
+    public SocialClientApi socialClientApi;
+    @Resource
+    private PayCurrencyRechargeMapper currencyRechargeMapper;
+    @Resource
+    private PayCurrencyService payCurrencyService;
+    @Resource
+    private PayOrderService payOrderService;
+    @Resource
+    private PayRefundService payRefundService;
+    @Resource
+    private PayCurrencyRechargePackageService payCurrencyRechargePackageService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayCurrencyRechargeDO createCurrencyRecharge(Long dataId, Long userId, Integer userType, String userIp,
+                                                        AppPayCurrencyRechargeCreateReqVO reqVO) {
+        // 1.1 计算充值金额
+        long payPrice;
+        long bonusPrice = 0;
+        if (Objects.nonNull(reqVO.getPackageId())) {
+            PayCurrencyRechargePackageDO rechargePackage = payCurrencyRechargePackageService.validCurrencyRechargePackage(reqVO.getPackageId());
+            payPrice = rechargePackage.getPayPrice();
+            bonusPrice = rechargePackage.getBonusPrice();
+        } else {
+            payPrice = reqVO.getPayPrice();
+        }
+        // 1.2 插入充值记录
+        PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(dataId, userId, userType);
+        PayCurrencyRechargeDO recharge = PayCurrencyRechargeConvert.INSTANCE.convert(currency.getId(), payPrice, bonusPrice, reqVO.getPackageId());
+        currencyRechargeMapper.insert(recharge);
+
+        // 2.1 创建支付单
+        Long payOrderId = payOrderService.createOrder(new PayOrderCreateReqDTO()
+                .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+                .setMerchantOrderId(recharge.getId().toString()) // 业务的订单编号
+                .setSubject(WALLET_RECHARGE_ORDER_SUBJECT).setBody("")
+                .setPrice(Math.toIntExact(recharge.getPayPrice()))
+                .setExpireTime(addTime(Duration.ofHours(2L)))); // TODO @芋艿:支付超时时间
+        // 2.2 更新货币账户充值记录中支付订单
+        currencyRechargeMapper.updateById(new PayCurrencyRechargeDO().setId(recharge.getId()).setPayOrderId(payOrderId));
+        recharge.setPayOrderId(payOrderId);
+        return recharge;
+    }
+
+    @Override
+    public PageResult<PayCurrencyRechargeDO> getCurrencyRechargePackagePage(Long dataId, Long userId, Integer userType,
+                                                                            PageParam pageReqVO, Boolean payStatus) {
+        PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(dataId, userId, userType);
+        return currencyRechargeMapper.selectPage(pageReqVO, currency.getId(), payStatus);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateCurrencyRechargerPaid(Long id, Long payOrderId) {
+        // 1.1 获取货币账户充值记录
+        PayCurrencyRechargeDO currencyRecharge = currencyRechargeMapper.selectById(id);
+        if (currencyRecharge == null) {
+            log.error("[updateCurrencyRechargerPaid][货币账户充值记录不存在,货币账户充值记录 id({})]", id);
+            throw exception(WALLET_RECHARGE_NOT_FOUND);
+        }
+        // 1.2 校验货币账户充值是否可以支付
+        PayOrderDO payOrderDO = validateCurrencyRechargerCanPaid(currencyRecharge, payOrderId);
+
+        // 2. 更新货币账户充值的支付状态
+        int updateCount = currencyRechargeMapper.updateByIdAndPaid(id, false,
+                new PayCurrencyRechargeDO().setId(id).setPayStatus(true).setPayTime(LocalDateTime.now())
+                        .setPayChannelCode(payOrderDO.getChannelCode()));
+        if (updateCount == 0) {
+            throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
+        }
+
+        // 3. 更新货币账户余额
+        // TODO @jason:这样的话,未来提现会不会把充值的,也提现走哈。类似先充 100,送 110;然后提现 110;
+        // TODO 需要货币账户中加个可提现余额
+        payCurrencyService.addCurrencyBalance(currencyRecharge.getCurrencyId(), String.valueOf(id),
+                PayCurrencyBizTypeEnum.RECHARGE, currencyRecharge.getTotalPrice());
+
+        // 4. 发送订阅消息
+        getSelf().sendCurrencyRechargerPaidMessage(payOrderId, currencyRecharge);
+    }
+
+    @Async
+    public void sendCurrencyRechargerPaidMessage(Long payOrderId, PayCurrencyRechargeDO currencyRecharge) {
+        // 1. 获得会员货币账户信息
+        PayCurrencyDO currency = payCurrencyService.getCurrency(currencyRecharge.getCurrencyId());
+        // 2. 构建并发送模版消息
+        socialClientApi.sendWxaSubscribeMessage(new SocialWxaSubscribeMessageSendReqDTO()
+                .setUserId(currency.getUserId()).setUserType(currency.getUserType())
+                .setTemplateTitle(WXA_WALLET_RECHARGER_PAID)
+                .setPage("pages/user/currency/money") // 货币账户详情界面
+                .addMessage("character_string1", String.valueOf(payOrderId)) // 支付单编号
+                .addMessage("amount2", fenToYuanStr(Math.toIntExact(currencyRecharge.getTotalPrice()))) // 充值金额
+                .addMessage("time3", LocalDateTimeUtil.formatNormal(currencyRecharge.getCreateTime())) // 充值时间
+                .addMessage("phrase4", "充值成功")); // 充值状态
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void refundCurrencyRecharge(Long id, String userIp) {
+        // 1.1 获取货币账户充值记录
+        PayCurrencyRechargeDO currencyRecharge = currencyRechargeMapper.selectById(id);
+        if (currencyRecharge == null) {
+            log.error("[refundCurrencyRecharge][货币账户充值记录不存在,货币账户充值记录 id({})]", id);
+            throw exception(WALLET_RECHARGE_NOT_FOUND);
+        }
+        // 1.2 校验货币账户充值是否可以发起退款
+        PayCurrencyDO currency = validateCurrencyRechargeCanRefund(currencyRecharge);
+
+        // 2. 冻结退款的余额,暂时只处理赠送的余额也全部退回
+        payCurrencyService.freezePrice(currency.getId(), currencyRecharge.getTotalPrice());
+
+        // 3. 创建退款单
+        String currencyRechargeId = String.valueOf(id);
+        String refundId = currencyRechargeId + "-refund";
+        Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
+                .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
+                .setMerchantOrderId(currencyRechargeId)
+                .setMerchantRefundId(refundId)
+                .setReason("想退钱").setPrice(Math.toIntExact(currencyRecharge.getPayPrice())));
+
+        // 4. 更新充值记录退款单号
+        // TODO @jaosn:一般新建这种 update 对象,建议是,第一个 set id 属性,容易知道以它为更新
+        currencyRechargeMapper.updateById(new PayCurrencyRechargeDO().setPayRefundId(payRefundId)
+                .setRefundStatus(WAITING.getStatus()).setId(currencyRecharge.getId()));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateCurrencyRechargeRefunded(Long id, Long payRefundId) {
+        // 1.1 获取货币账户充值记录
+        PayCurrencyRechargeDO currencyRecharge = currencyRechargeMapper.selectById(id);
+        if (currencyRecharge == null) {
+            log.error("[updateCurrencyRechargerPaid][货币账户充值记录不存在,货币账户充值记录 id({})]", id);
+            throw exception(WALLET_RECHARGE_NOT_FOUND);
+        }
+        // 1.2 校验货币账户充值是否可以更新已退款
+        PayRefundDO payRefund = validateCurrencyRechargeCanRefunded(currencyRecharge, payRefundId);
+
+        PayCurrencyRechargeDO updateObj = new PayCurrencyRechargeDO().setId(id);
+        // 退款成功
+        if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+            // 2.1 更新货币账户余额
+            payCurrencyService.reduceCurrencyBalance(currencyRecharge.getCurrencyId(), id,
+                    PayCurrencyBizTypeEnum.RECHARGE_REFUND, currencyRecharge.getTotalPrice());
+
+            updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime())
+                    .setRefundTotalPrice(currencyRecharge.getTotalPrice()).setRefundPayPrice(currencyRecharge.getPayPrice())
+                    .setRefundBonusPrice(currencyRecharge.getBonusPrice());
+        }
+        // 退款失败
+        if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
+            // 2.2 解冻余额
+            payCurrencyService.unfreezePrice(currencyRecharge.getCurrencyId(), currencyRecharge.getTotalPrice());
+
+            updateObj.setRefundStatus(FAILURE.getStatus());
+        }
+        // 3. 更新货币账户充值的退款字段
+        currencyRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
+    }
+
+    private PayRefundDO validateCurrencyRechargeCanRefunded(PayCurrencyRechargeDO currencyRecharge, Long payRefundId) {
+        // 1. 校验退款订单匹配
+        if (notEqual(currencyRecharge.getPayRefundId(), payRefundId)) {
+            log.error("[validateCurrencyRechargeCanRefunded][货币账户充值({}) 退款单不匹配({}),请进行处理!货币账户充值的数据是:{}]",
+                    currencyRecharge.getId(), payRefundId, toJsonString(currencyRecharge));
+            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+        }
+
+        // 2.1 校验退款订单
+        PayRefundDO payRefund = payRefundService.getRefund(payRefundId);
+        if (payRefund == null) {
+            log.error("[validateCurrencyRechargeCanRefunded][payRefund({})不存在]", payRefundId);
+            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
+        }
+        // 2.2 校验退款金额一致
+        if (notEqual(payRefund.getRefundPrice(), currencyRecharge.getPayPrice())) {
+            log.error("[validateCurrencyRechargeCanRefunded][货币账户({}) payRefund({}) 退款金额不匹配,请进行处理!货币账户数据是:{},payRefund 数据是:{}]",
+                    currencyRecharge.getId(), payRefundId, toJsonString(currencyRecharge), toJsonString(payRefund));
+            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
+        }
+        // 2.3 校验退款订单商户订单是否匹配
+        if (notEqual(payRefund.getMerchantOrderId(), currencyRecharge.getId().toString())) {
+            log.error("[validateCurrencyRechargeCanRefunded][货币账户({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
+                    currencyRecharge.getId(), payRefundId, toJsonString(payRefund));
+            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+        }
+        return payRefund;
+    }
+
+    private PayCurrencyDO validateCurrencyRechargeCanRefund(PayCurrencyRechargeDO currencyRecharge) {
+        // 校验充值订单是否支付
+        if (!currencyRecharge.getPayStatus()) {
+            throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID);
+        }
+        // 校验充值订单是否已退款
+        if (currencyRecharge.getPayRefundId() != null) {
+            throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED);
+        }
+        // 校验货币账户余额是否足够
+        PayCurrencyDO currency = payCurrencyService.getCurrency(currencyRecharge.getCurrencyId());
+        Assert.notNull(currency, "用户货币账户({}) 不存在", currency.getId());
+        if (currency.getBalance() < currencyRecharge.getTotalPrice()) {
+            throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH);
+        }
+        // TODO @芋艿:需要考虑下,赠送的金额,会不会导致提现超过;
+        return currency;
+    }
+
+    private PayOrderDO validateCurrencyRechargerCanPaid(PayCurrencyRechargeDO currencyRecharge, Long payOrderId) {
+        // 1.1 校验充值记录的支付状态
+        if (currencyRecharge.getPayStatus()) {
+            log.error("[validateCurrencyRechargerCanPaid][货币账户({}) 不处于未支付状态!  货币账户数据是:{}]",
+                    currencyRecharge.getId(), toJsonString(currencyRecharge));
+            throw exception(WALLET_RECHARGE_UPDATE_PAID_STATUS_NOT_UNPAID);
+        }
+        // 1.2 校验支付订单匹配
+        if (notEqual(currencyRecharge.getPayOrderId(), payOrderId)) { // 支付单号
+            log.error("[validateCurrencyRechargerCanPaid][货币账户({}) 支付单不匹配({}),请进行处理! 货币账户数据是:{}]",
+                    currencyRecharge.getId(), payOrderId, toJsonString(currencyRecharge));
+            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
+        }
+
+        // 2.1 校验支付单是否存在
+        PayOrderDO payOrder = payOrderService.getOrder(payOrderId);
+        if (payOrder == null) {
+            log.error("[validateCurrencyRechargerCanPaid][货币账户({}) payOrder({}) 不存在,请进行处理!]",
+                    currencyRecharge.getId(), payOrderId);
+            throw exception(PAY_ORDER_NOT_FOUND);
+        }
+        // 2.2 校验支付单已支付
+        if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
+            log.error("[validateCurrencyRechargerCanPaid][货币账户({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
+                    currencyRecharge.getId(), payOrderId, toJsonString(payOrder));
+            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS);
+        }
+        // 2.3 校验支付金额一致
+        if (notEqual(payOrder.getPrice(), currencyRecharge.getPayPrice())) {
+            log.error("[validateDemoOrderCanPaid][货币账户({}) payOrder({}) 支付金额不匹配,请进行处理!货币账户 数据是:{},payOrder 数据是:{}]",
+                    currencyRecharge.getId(), payOrderId, toJsonString(currencyRecharge), toJsonString(payOrder));
+            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH);
+        }
+        // 2.4 校验支付订单的商户订单匹配
+        if (notEqual(payOrder.getMerchantOrderId(), currencyRecharge.getId().toString())) {
+            log.error("[validateDemoOrderCanPaid][货币账户({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
+                    currencyRecharge.getId(), payOrderId, toJsonString(payOrder));
+            throw exception(WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR);
+        }
+        return payOrder;
+    }
+
+    /**
+     * 获得自身的代理对象,解决 AOP 生效问题
+     *
+     * @return 自己
+     */
+    private PayCurrencyRechargeServiceImpl getSelf() {
+        return SpringUtil.getBean(getClass());
+    }
+
+}

+ 106 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyService.java

@@ -0,0 +1,106 @@
+package com.citu.module.pay.service.currency;
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.currency.PayCurrencyPageReqVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum;
+
+/**
+ * 货币账户 Service 接口
+ *
+ * @author Rayson
+ */
+public interface PayCurrencyService {
+
+    /**
+     * 获取货币账户信息
+     * <p>
+     * 如果不存在,则创建货币账户。由于用户注册时候不会创建货币账户
+     *
+     * @param dataId   绑定的数据编号
+     * @param userId   用户编号
+     * @param userType 用户类型
+     */
+    PayCurrencyDO getOrCreateCurrency(Long dataId, Long userId, Integer userType);
+
+    /**
+     * 获取货币账户信息
+     *
+     * @param currencyId 货币账户 id
+     */
+    PayCurrencyDO getCurrency(Long currencyId);
+
+    /**
+     * 获得会员货币账户分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 会员货币账户分页
+     */
+    PageResult<PayCurrencyDO> getCurrencyPage(PayCurrencyPageReqVO pageReqVO);
+
+    /**
+     * 货币账户订单支付
+     *
+     * @param dataId     绑定的数据编号
+     * @param userId     用户 id
+     * @param userType   用户类型
+     * @param outTradeNo 外部订单号
+     * @param price      金额
+     */
+    PayCurrencyTransactionDO orderPay(Long dataId,
+                                      Long userId,
+                                      Integer userType,
+                                      String outTradeNo,
+                                      Long price);
+
+    /**
+     * 货币账户订单支付退款
+     *
+     * @param outRefundNo 外部退款号
+     * @param refundPrice 退款金额
+     * @param reason      退款原因
+     */
+    PayCurrencyTransactionDO orderRefund(String outRefundNo, Long refundPrice, String reason);
+
+    /**
+     * 扣减货币账户余额
+     *
+     * @param currencyId 货币账户 id
+     * @param bizId      业务关联 id
+     * @param bizType    业务关联分类
+     * @param price      扣减金额
+     * @return 货币账户流水
+     */
+    PayCurrencyTransactionDO reduceCurrencyBalance(Long currencyId, Long bizId,
+                                                   PayCurrencyBizTypeEnum bizType, Long price);
+
+    /**
+     * 增加货币账户余额
+     *
+     * @param currencyId 货币账户 id
+     * @param bizId      业务关联 id
+     * @param bizType    业务关联分类
+     * @param price      增加金额
+     * @return 货币账户流水
+     */
+    PayCurrencyTransactionDO addCurrencyBalance(Long currencyId, String bizId,
+                                                PayCurrencyBizTypeEnum bizType, Long price);
+
+    /**
+     * 冻结货币账户部分余额
+     *
+     * @param id    货币账户编号
+     * @param price 冻结金额
+     */
+    void freezePrice(Long id, Long price);
+
+    /**
+     * 解冻货币账户余额
+     *
+     * @param id    货币账户编号
+     * @param price 解冻金额
+     */
+    void unfreezePrice(Long id, Long price);
+
+}

+ 208 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyServiceImpl.java

@@ -0,0 +1,208 @@
+package com.citu.module.pay.service.currency;
+
+import cn.hutool.core.lang.Assert;
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.currency.PayCurrencyPageReqVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.dal.dataobject.order.PayOrderExtensionDO;
+import com.citu.module.pay.dal.dataobject.refund.PayRefundDO;
+import com.citu.module.pay.dal.mysql.currency.PayCurrencyMapper;
+import com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum;
+import com.citu.module.pay.service.currency.bo.CurrencyTransactionCreateReqBO;
+import com.citu.module.pay.service.order.PayOrderService;
+import com.citu.module.pay.service.refund.PayRefundService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+import static com.citu.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.citu.module.pay.enums.ErrorCodeConstants.*;
+import static com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum.PAYMENT;
+import static com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum.PAYMENT_REFUND;
+
+/**
+ * 货币账户 Service 实现类
+ *
+ * @author Rayson
+ */
+@Service
+@Slf4j
+public class PayCurrencyServiceImpl implements PayCurrencyService {
+
+    @Resource
+    private PayCurrencyMapper currencyMapper;
+
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private PayCurrencyTransactionService currencyTransactionService;
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private PayOrderService orderService;
+    @Resource
+    @Lazy // 延迟加载,避免循环依赖
+    private PayRefundService refundService;
+
+    @Override
+    public PayCurrencyDO getOrCreateCurrency(Long dataId, Long userId, Integer userType) {
+        PayCurrencyDO currency = currencyMapper.selectByUserIdAndType(dataId, userId, userType);
+        if (currency == null) {
+            currency = new PayCurrencyDO().setDataId(dataId).setUserId(userId).setUserType(userType)
+                    .setBalance(0L).setTotalExpense(0L).setTotalRecharge(0L);
+            currency.setCreateTime(LocalDateTime.now());
+            currencyMapper.insert(currency);
+        }
+        return currency;
+    }
+
+    @Override
+    public PayCurrencyDO getCurrency(Long currencyId) {
+        return currencyMapper.selectById(currencyId);
+    }
+
+    @Override
+    public PageResult<PayCurrencyDO> getCurrencyPage(PayCurrencyPageReqVO pageReqVO) {
+        return currencyMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayCurrencyTransactionDO orderPay(Long dataId, Long userId, Integer userType, String outTradeNo, Long price) {
+        // 1. 判断支付交易拓展单是否存
+        PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
+        if (orderExtension == null) {
+            throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
+        }
+        PayCurrencyDO currency = getOrCreateCurrency(dataId, userId, userType);
+        // 2. 扣减余额
+        return reduceCurrencyBalance(currency.getId(), orderExtension.getOrderId(), PAYMENT, price);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayCurrencyTransactionDO orderRefund(String outRefundNo, Long refundPrice, String reason) {
+        // 1.1 判断退款单是否存在
+        PayRefundDO payRefund = refundService.getRefundByNo(outRefundNo);
+        if (payRefund == null) {
+            throw exception(REFUND_NOT_FOUND);
+        }
+        // 1.2 校验是否可以退款
+        Long currencyId = validateCurrencyCanRefund(payRefund.getId(), payRefund.getChannelOrderNo());
+        PayCurrencyDO currency = currencyMapper.selectById(currencyId);
+        Assert.notNull(currency, "货币账户 {} 不存在", currencyId);
+
+        // 2. 增加余额
+        return addCurrencyBalance(currencyId, String.valueOf(payRefund.getId()), PAYMENT_REFUND, refundPrice);
+    }
+
+    /**
+     * 校验是否能退款
+     *
+     * @param refundId      支付退款单 id
+     * @param currencyPayNo 货币账户支付 no
+     */
+    private Long validateCurrencyCanRefund(Long refundId, String currencyPayNo) {
+        // 1. 校验货币账户支付交易存在
+        PayCurrencyTransactionDO currencyTransaction = currencyTransactionService.getCurrencyTransactionByNo(currencyPayNo);
+        if (currencyTransaction == null) {
+            throw exception(WALLET_TRANSACTION_NOT_FOUND);
+        }
+        // 2. 校验退款是否存在
+        PayCurrencyTransactionDO refundTransaction = currencyTransactionService.getCurrencyTransaction(
+                String.valueOf(refundId), PAYMENT_REFUND);
+        if (refundTransaction != null) {
+            throw exception(WALLET_REFUND_EXIST);
+        }
+        return currencyTransaction.getCurrencyId();
+    }
+
+    @Override
+    public PayCurrencyTransactionDO reduceCurrencyBalance(Long currencyId, Long bizId,
+                                                          PayCurrencyBizTypeEnum bizType, Long price) {
+        // 1. 获取货币账户
+        PayCurrencyDO payCurrency = getCurrency(currencyId);
+        if (payCurrency == null) {
+            log.error("[reduceCurrencyBalance],用户货币账户({})不存在.", currencyId);
+            throw exception(WALLET_NOT_FOUND);
+        }
+
+        // 2.1 扣除余额
+        int updateCounts;
+        switch (bizType) {
+            case PAYMENT: {
+                updateCounts = currencyMapper.updateWhenConsumption(payCurrency.getId(), price);
+                break;
+            }
+            case RECHARGE_REFUND: {
+                updateCounts = currencyMapper.updateWhenRechargeRefund(payCurrency.getId(), price);
+                break;
+            }
+            default: {
+                // TODO 其它类型待实现
+                throw new UnsupportedOperationException("待实现");
+            }
+        }
+        if (updateCounts == 0) {
+            throw exception(WALLET_BALANCE_NOT_ENOUGH);
+        }
+        // 2.2 生成货币账户流水
+        Long afterBalance = payCurrency.getBalance() - price;
+        CurrencyTransactionCreateReqBO bo = new CurrencyTransactionCreateReqBO().setCurrencyId(payCurrency.getId())
+                .setPrice(-price).setBalance(afterBalance).setBizId(String.valueOf(bizId))
+                .setBizType(bizType.getType()).setTitle(bizType.getDescription());
+        return currencyTransactionService.createCurrencyTransaction(bo);
+    }
+
+    @Override
+    public PayCurrencyTransactionDO addCurrencyBalance(Long currencyId, String bizId,
+                                                       PayCurrencyBizTypeEnum bizType, Long price) {
+        // 1.1 获取货币账户
+        PayCurrencyDO payCurrency = getCurrency(currencyId);
+        if (payCurrency == null) {
+            log.error("[addCurrencyBalance],用户货币账户({})不存在.", currencyId);
+            throw exception(WALLET_NOT_FOUND);
+        }
+        // 1.2 更新货币账户金额
+        switch (bizType) {
+            case PAYMENT_REFUND: { // 退款更新
+                currencyMapper.updateWhenConsumptionRefund(payCurrency.getId(), price);
+                break;
+            }
+            case RECHARGE: { // 充值更新
+                currencyMapper.updateWhenRecharge(payCurrency.getId(), price);
+                break;
+            }
+            default: {
+                // TODO 其它类型待实现
+                throw new UnsupportedOperationException("待实现");
+            }
+        }
+
+        // 2. 生成货币账户流水
+        CurrencyTransactionCreateReqBO transactionCreateReqBO = new CurrencyTransactionCreateReqBO()
+                .setCurrencyId(payCurrency.getId()).setPrice(price).setBalance(payCurrency.getBalance() + price)
+                .setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());
+        return currencyTransactionService.createCurrencyTransaction(transactionCreateReqBO);
+    }
+
+    @Override
+    public void freezePrice(Long id, Long price) {
+        int updateCounts = currencyMapper.freezePrice(id, price);
+        if (updateCounts == 0) {
+            throw exception(WALLET_BALANCE_NOT_ENOUGH);
+        }
+    }
+
+    @Override
+    public void unfreezePrice(Long id, Long price) {
+        int updateCounts = currencyMapper.unFreezePrice(id, price);
+        if (updateCounts == 0) {
+            throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH);
+        }
+    }
+
+}

+ 75 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyTransactionService.java

@@ -0,0 +1,75 @@
+package com.citu.module.pay.service.currency;
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.transaction.PayCurrencyTransactionPageReqVO;
+import com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionPageReqVO;
+import com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionSummaryRespVO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum;
+import com.citu.module.pay.service.currency.bo.CurrencyTransactionCreateReqBO;
+
+import javax.validation.Valid;
+import java.time.LocalDateTime;
+
+/**
+ * 货币账户余额流水 Service 接口
+ *
+ * @author Rayson
+ */
+public interface PayCurrencyTransactionService {
+
+    /**
+     * 查询货币账户余额流水分页
+     *
+     * @param dataId   绑定的数据编号
+     * @param userId   用户编号
+     * @param userType 用户类型
+     * @param pageVO   分页查询参数
+     */
+    PageResult<PayCurrencyTransactionDO> getCurrencyTransactionPage(Long dataId, Long userId, Integer userType,
+                                                                AppPayCurrencyTransactionPageReqVO pageVO);
+
+    /**
+     * 查询货币账户余额流水分页
+     *
+     * @param pageVO   分页查询参数
+     */
+    PageResult<PayCurrencyTransactionDO> getCurrencyTransactionPage(PayCurrencyTransactionPageReqVO pageVO);
+
+    /**
+     * 新增货币账户余额流水
+     *
+     * @param bo 创建货币账户流水 bo
+     * @return 新建的货币账户 do
+     */
+    PayCurrencyTransactionDO createCurrencyTransaction(@Valid CurrencyTransactionCreateReqBO bo);
+
+    /**
+     * 根据 no,获取货币账户余流水
+     *
+     * @param no 流水号
+     */
+    PayCurrencyTransactionDO getCurrencyTransactionByNo(String no);
+
+    /**
+     * 获取货币账户流水
+     *
+     * @param bizId 业务编号
+     * @param type  业务类型
+     * @return 货币账户流水
+     */
+    PayCurrencyTransactionDO getCurrencyTransaction(String bizId, PayCurrencyBizTypeEnum type);
+
+    /**
+     * 获得货币账户流水统计
+     *
+     * @param dataId   绑定的数据编号
+     * @param userId 用户编号
+     * @param userType 用户类型
+     * @param createTime 时间段
+     * @return 货币账户流水统计
+     */
+    AppPayCurrencyTransactionSummaryRespVO getCurrencyTransactionSummary(Long dataId, Long userId, Integer userType,
+                                                                     LocalDateTime[] createTime);
+
+}

+ 86 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/PayCurrencyTransactionServiceImpl.java

@@ -0,0 +1,86 @@
+package com.citu.module.pay.service.currency;
+
+import com.citu.framework.common.pojo.PageResult;
+import com.citu.module.pay.controller.admin.currency.vo.transaction.PayCurrencyTransactionPageReqVO;
+import com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionPageReqVO;
+import com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionSummaryRespVO;
+import com.citu.module.pay.convert.currency.PayCurrencyTransactionConvert;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyDO;
+import com.citu.module.pay.dal.dataobject.currency.PayCurrencyTransactionDO;
+import com.citu.module.pay.dal.mysql.currency.PayCurrencyTransactionMapper;
+import com.citu.module.pay.dal.redis.no.PayNoRedisDAO;
+import com.citu.module.pay.enums.currency.PayCurrencyBizTypeEnum;
+import com.citu.module.pay.service.currency.bo.CurrencyTransactionCreateReqBO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+
+import static com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionPageReqVO.TYPE_EXPENSE;
+import static com.citu.module.pay.controller.app.currency.vo.transaction.AppPayCurrencyTransactionPageReqVO.TYPE_INCOME;
+
+/**
+ * 货币账户流水 Service 实现类
+ *
+ * @author Rayson
+ */
+@Service
+@Slf4j
+@Validated
+public class PayCurrencyTransactionServiceImpl implements PayCurrencyTransactionService {
+
+    /**
+     * 货币账户流水的 no 前缀
+     */
+    private static final String WALLET_NO_PREFIX = "W";
+
+    @Resource
+    private PayCurrencyService payCurrencyService;
+    @Resource
+    private PayCurrencyTransactionMapper payCurrencyTransactionMapper;
+    @Resource
+    private PayNoRedisDAO noRedisDAO;
+
+    @Override
+    public PageResult<PayCurrencyTransactionDO> getCurrencyTransactionPage(Long dataId, Long userId, Integer userType,
+                                                                           AppPayCurrencyTransactionPageReqVO pageVO) {
+        PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(dataId, userId, userType);
+        return payCurrencyTransactionMapper.selectPage(currency.getId(), pageVO.getType(), pageVO, pageVO.getCreateTime());
+    }
+
+    @Override
+    public PageResult<PayCurrencyTransactionDO> getCurrencyTransactionPage(PayCurrencyTransactionPageReqVO pageVO) {
+        return payCurrencyTransactionMapper.selectPage(pageVO.getCurrencyId(), null, pageVO, null);
+    }
+
+    @Override
+    public PayCurrencyTransactionDO createCurrencyTransaction(CurrencyTransactionCreateReqBO bo) {
+        PayCurrencyTransactionDO transaction = PayCurrencyTransactionConvert.INSTANCE.convert(bo)
+                .setNo(noRedisDAO.generate(WALLET_NO_PREFIX));
+        payCurrencyTransactionMapper.insert(transaction);
+        return transaction;
+    }
+
+    @Override
+    public PayCurrencyTransactionDO getCurrencyTransactionByNo(String no) {
+        return payCurrencyTransactionMapper.selectByNo(no);
+    }
+
+    @Override
+    public PayCurrencyTransactionDO getCurrencyTransaction(String bizId, PayCurrencyBizTypeEnum type) {
+        return payCurrencyTransactionMapper.selectByBiz(bizId, type.getType());
+    }
+
+    @Override
+    public AppPayCurrencyTransactionSummaryRespVO getCurrencyTransactionSummary(Long dataId, Long userId, Integer userType, LocalDateTime[] createTime) {
+        PayCurrencyDO currency = payCurrencyService.getOrCreateCurrency(dataId, userId, userType);
+        AppPayCurrencyTransactionSummaryRespVO summary = new AppPayCurrencyTransactionSummaryRespVO()
+                .setTotalExpense(1L).setTotalIncome(100L);
+        return new AppPayCurrencyTransactionSummaryRespVO()
+                .setTotalExpense(payCurrencyTransactionMapper.selectPriceSum(currency.getId(), TYPE_EXPENSE, createTime))
+                .setTotalIncome(payCurrencyTransactionMapper.selectPriceSum(currency.getId(), TYPE_INCOME, createTime));
+    }
+
+}

+ 59 - 0
citu-module-pay/citu-module-pay-biz/src/main/java/com/citu/module/pay/service/currency/bo/CurrencyTransactionCreateReqBO.java

@@ -0,0 +1,59 @@
+package com.citu.module.pay.service.currency.bo;
+
+import com.citu.framework.common.validation.InEnum;
+import com.citu.module.pay.enums.wallet.PayWalletBizTypeEnum;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 创建货币账户流水 BO
+ *
+ * @author Rayson
+ */
+@Data
+public class CurrencyTransactionCreateReqBO {
+
+    /**
+     * 货币账户编号
+     *
+     */
+    @NotNull(message = "货币账户编号不能为空")
+    private Long currencyId;
+
+    /**
+     * 交易金额,单位分
+     *
+     * 正值表示余额增加,负值表示余额减少
+     */
+    @NotNull(message = "交易金额不能为空")
+    private Long price;
+
+    /**
+     * 交易后余额,单位分
+     */
+    @NotNull(message = "交易后余额不能为空")
+    private Long balance;
+
+    /**
+     * 关联业务分类
+     *
+     * 枚举 {@link PayWalletBizTypeEnum#getType()}
+     */
+    @NotNull(message = "关联业务分类不能为空")
+    @InEnum(PayWalletBizTypeEnum.class)
+    private Integer bizType;
+
+    /**
+     * 关联业务编号
+     */
+    @NotEmpty(message = "关联业务编号不能为空")
+    private String bizId;
+
+    /**
+     * 流水说明
+     */
+    @NotEmpty(message = "流水说明不能为空")
+    private String title;
+}

+ 4 - 1
citu-module-pay/citu-spring-boot-starter-biz-pay/src/main/java/com/citu/framework/pay/core/enums/channel/PayChannelEnum.java

@@ -31,7 +31,10 @@ public enum PayChannelEnum {
     ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
     MOCK("mock", "模拟支付", NonePayClientConfig.class),
 
-    WALLET("wallet", "钱包支付", NonePayClientConfig.class);
+    WALLET("wallet", "钱包支付", NonePayClientConfig.class),
+
+    CURRENCY("currency","自定义货币支付",NonePayClientConfig.class),
+    ;
 
     /**
      * 编码

+ 9 - 10
menduner/menduner-system-api/src/main/java/com/citu/module/menduner/system/enums/workexp/ExpTypeEnum.java

@@ -1,6 +1,5 @@
 package com.citu.module.menduner.system.enums.workexp;
 
-import com.citu.module.menduner.system.enums.eduexp.EducationTypeEnum;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
@@ -10,15 +9,15 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum ExpTypeEnum {
-    IN_SCHOOL("0","在校生"),
-    NEW_GRADUATE("1","应届生"),
-    NO_LIMIT("2","经验不限"),
-    LESS_THAN_ONE_YEAR("3","1年以内"),
-    ONE_TO_THREE_YEARS("4","1-3年"),
-    THREE_TO_FIVE_YEARS("5","3-5年"),
-    FIVE_TO_TEN_YEARS("6","5-10年"),
-    MORE_THAN_TEN_YEARS("7","10-20年"),
-    MORE_THAN_TWENTY_YEARS("8","20年以上"),
+    IN_SCHOOL("0", "在校生"),
+    NEW_GRADUATE("1", "应届生"),
+    NO_LIMIT("2", "经验不限"),
+    LESS_THAN_ONE_YEAR("3", "1年以内"),
+    ONE_TO_THREE_YEARS("4", "1-3年"),
+    THREE_TO_FIVE_YEARS("5", "3-5年"),
+    FIVE_TO_TEN_YEARS("6", "5-10年"),
+    MORE_THAN_TEN_YEARS("7", "10-20年"),
+    MORE_THAN_TWENTY_YEARS("8", "20年以上"),
     ;
 
     private final String type;

+ 0 - 7
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/job/vo/AppRecruitJobPageReqVO.java

@@ -43,13 +43,6 @@ public class AppRecruitJobPageReqVO extends PageParam {
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 
-    @Schema(description = "过期时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] expireTime;
-
-    @Schema(description = "是否获取过期的数据")
-    private Boolean hasExpiredData;
-
     @Schema(description = "是否雇佣 (众聘)")
     private Boolean hire;
 

+ 0 - 5
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/job/vo/AppRecruitJobRespVO.java

@@ -4,15 +4,10 @@ package com.citu.module.menduner.system.controller.app.recruit.job.vo;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import java.time.LocalDateTime;
-
 @Schema(description = "招聘端 - 招聘职位 Response VO")
 @Data
 public class AppRecruitJobRespVO extends AppRecruitJobSimpleRespVO {
 
-    @Schema(description = "过期时间")
-    private LocalDateTime expireTime;
-
     @Schema(description = "是否置顶")
     private Boolean top;
 

+ 0 - 3
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/app/recruit/job/vo/AppRecruitJobSaveReqVO.java

@@ -59,9 +59,6 @@ public class AppRecruitJobSaveReqVO {
     @Schema(description = "职位要求")
     private String requirement;
 
-    @Schema(description = "过期时间")
-    private LocalDateTime expireTime;
-
     @Schema(description = "工作地址")
     private String address;
 

+ 0 - 4
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobAdvertisedRespVO.java

@@ -81,10 +81,6 @@ public class JobAdvertisedRespVO {
     @ExcelProperty("职位要求")
     private String requirement;
 
-    @Schema(description = "过期时间")
-    @ExcelProperty("过期时间")
-    private LocalDateTime expireTime;
-
     @Schema(description = "是否置顶")
     @ExcelProperty("是否置顶")
     private Boolean top;

+ 0 - 3
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/controller/base/job/JobAdvertisedSaveReqVO.java

@@ -70,9 +70,6 @@ public class JobAdvertisedSaveReqVO {
     @Schema(description = "工作地址")
     private String address;
 
-    @Schema(description = "过期时间")
-    private LocalDateTime expireTime;
-
     @Schema(description = "是否置顶")
     private Boolean top;
 

+ 0 - 4
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/dataobject/job/JobAdvertisedDO.java

@@ -92,10 +92,6 @@ public class JobAdvertisedDO extends TenantBaseDO {
      * 职位要求
      */
     private String requirement;
-    /**
-     * 过期时间
-     */
-    private LocalDateTime expireTime;
     /**
      * 是否置顶
      */

+ 0 - 5
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/es/job/ESJobAdvertisedMergeDO.java

@@ -100,11 +100,6 @@ public class ESJobAdvertisedMergeDO extends ESBaseDO {
      */
     @Field(analyzer = FieldAnalyzer.IK_MAX_WORD, type = FieldType.Text)
     private String requirement;
-    /**
-     * 过期时间
-     */
-    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime expireTime;
     /**
      * 是否置顶
      */

+ 0 - 31
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/dal/mysql/job/JobAdvertisedMapper.java

@@ -59,7 +59,6 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
         LambdaQueryWrapperX<JobAdvertisedDO> query = new LambdaQueryWrapperX<>();
         query.eq(JobAdvertisedDO::getEnterpriseId, enterpriseId);
         query.eq(JobAdvertisedDO::getStatus, MendunerStatusEnum.ENABLE.getStatus());
-        notExpireTime(query);
         return selectList(query);
     }
 
@@ -80,7 +79,6 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
         query.eqIfPresent(JobAdvertisedDO::getEnterpriseId, reqVO.getEnterpriseId());
         query.eqIfPresent(JobAdvertisedDO::getHire, reqVO.getHire());
         query.eqIfPresent(JobAdvertisedDO::getTop, reqVO.getTop());
-        notExpireTime(query);
 
         // 相识参数
         if (reqVO.getAcquainted()) {
@@ -126,7 +124,6 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
         query.innerJoin(EnterpriseDO.class, EnterpriseDO::getId, JobAdvertisedDO::getEnterpriseId);
         query.eq(EnterpriseDO::getStatus, MendunerStatusEnum.ENABLE.getStatus());
         query.eq(JobAdvertisedDO::getStatus, MendunerStatusEnum.ENABLE.getStatus());
-        notExpireTime(query);
         query.orderByDesc(JobAdvertisedDO::getUpdateTime);
         query.groupBy(JobAdvertisedDO::getEnterpriseId);
         return selectJoinPage(pageParam, AppEnterpriseJobHomeRespVO.class, query);
@@ -148,7 +145,6 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
         LambdaQueryWrapper<JobAdvertisedDO> query = new LambdaQueryWrapperX<JobAdvertisedDO>();
         query.in(JobAdvertisedDO::getEnterpriseId, ids);
         query.eq(JobAdvertisedDO::getStatus, MendunerStatusEnum.ENABLE.getStatus());
-        notExpireTime(query);
         return selectList(query);
     }
 
@@ -164,20 +160,11 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
                 .eqIfPresent(JobAdvertisedDO::getStatus, reqVO.getStatus())
                 .eqIfPresent(JobAdvertisedDO::getHire, reqVO.getHire())
                 .betweenIfPresent(JobAdvertisedDO::getCreateTime, reqVO.getCreateTime())
-                .betweenIfPresent(JobAdvertisedDO::getExpireTime, reqVO.getExpireTime())
                 .orderByDesc(JobAdvertisedDO::getTop, JobAdvertisedDO::getUpdateTime);
 
         query.eq(JobAdvertisedDO::getEnterpriseId, enterpriseId);
         query.eq(JobAdvertisedDO::getUserId, userId);
 
-        if (null != reqVO.getHasExpiredData()) {
-            if (reqVO.getHasExpiredData()) {
-                // 获取过期的数据 当前时间小于过期时间
-                query.le(JobAdvertisedDO::getExpireTime, LocalDateTime.now());
-            } else {
-                notExpireTime(query);
-            }
-        }
         return selectPage(reqVO, query);
     }
 
@@ -190,22 +177,4 @@ public interface JobAdvertisedMapper extends BaseMapperX<JobAdvertisedDO> {
         return selectJoinList(AppRecruitJobSimpleRespVO.class, query);
     }
 
-
-    default void notExpireTime(LambdaQueryWrapper<JobAdvertisedDO> query) {
-        query.and(wrapper -> wrapper
-                .ge(JobAdvertisedDO::getExpireTime, LocalDateTime.now())
-                .or()
-                .isNull(JobAdvertisedDO::getExpireTime)
-        );
-    }
-
-    default void notExpireTime(MPJLambdaWrapperX<JobAdvertisedDO> query) {
-        query.and(wrapper -> wrapper
-                .ge(JobAdvertisedDO::getExpireTime, LocalDateTime.now())
-                .or()
-                .isNull(JobAdvertisedDO::getExpireTime)
-        );
-    }
-
-
 }

+ 3 - 9
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/service/job/JobIntegrationServiceImpl.java

@@ -53,8 +53,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -314,7 +312,7 @@ public class JobIntegrationServiceImpl implements JobIntegrationService {
     @Override
     public PageResult<AppRecruitJobRespVO> page(AppRecruitJobPageReqVO reqVO) {
         PageResult<JobAdvertisedDO> pageResult = jobAdvertisedMapper.selectPage(reqVO,
-                LoginUserContext.getEnterpriseId(),LoginUserContext.getUserId());
+                LoginUserContext.getEnterpriseId(), LoginUserContext.getUserId());
         if (CollUtil.isEmpty(pageResult.getList())) {
             return PageResult.empty();
         }
@@ -332,10 +330,6 @@ public class JobIntegrationServiceImpl implements JobIntegrationService {
             Long count = jobCvRelDOList.stream()
                     .filter(jobCvRelDO -> jobCvRelDO.getJobId().equals(job.getId())).count();
             respVO.setCount(count);
-            // 计算多少天后到期
-            if (null != job.getExpireTime()) {
-                respVO.setExpireDay(ChronoUnit.DAYS.between(LocalDateTime.now(), respVO.getExpireTime()));
-            }
             list.add(respVO);
 
         });
@@ -345,7 +339,7 @@ public class JobIntegrationServiceImpl implements JobIntegrationService {
     @Override
     public PageResult<AppRecruitJobCvRelRespVO> page(AppRecruitJobCvRelPageReqVO reqVO) {
         PageResult<AppRecruitJobCvRelRespVO> pageResult = jobCvRelMapper.selectPage(reqVO,
-                LoginUserContext.getEnterpriseId(),LoginUserContext.getUserId());
+                LoginUserContext.getEnterpriseId(), LoginUserContext.getUserId());
         if (CollUtil.isEmpty(pageResult.getList())) {
             return PageResult.empty();
         }
@@ -356,7 +350,7 @@ public class JobIntegrationServiceImpl implements JobIntegrationService {
     @Override
     public PageResult<AppRecruitUnfitCandidateRespVO> unfitPage(AppRecruitUnfitCandidatePageReqVO reqVO) {
         PageResult<AppRecruitUnfitCandidateRespVO> pageResult = unfitCandidateMapper.unfitPage(reqVO,
-                LoginUserContext.getEnterpriseId(),LoginUserContext.getUserId());
+                LoginUserContext.getEnterpriseId(), LoginUserContext.getUserId());
         if (CollUtil.isEmpty(pageResult.getList())) {
             return PageResult.empty();
         }

+ 12 - 12
menduner/menduner-system-biz/src/main/java/com/citu/module/menduner/system/util/ESQueryBuildUtils.java

@@ -97,18 +97,18 @@ public class ESQueryBuildUtils {
         }
 
         // 获取当前时间并格式化为字符串
-        String formattedNow = LocalDateTime.now()
-                .format(DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND));
-
-        // 创建一个范围查询,expireTime >= 当前时间
-        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("expireTime").gte(formattedNow);
-
-        // 构建 should 子句,包含范围查询和 exists 查询
-        BoolQueryBuilder expireTimeQuery = QueryBuilders.boolQuery();
-        expireTimeQuery.should(rangeQuery);
-        expireTimeQuery.should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("expireTime")));
-
-        boolQuery.filter(expireTimeQuery);
+//        String formattedNow = LocalDateTime.now()
+//                .format(DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND));
+//
+//        // 创建一个范围查询,expireTime >= 当前时间
+//        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("expireTime").gte(formattedNow);
+//
+//        // 构建 should 子句,包含范围查询和 exists 查询
+//        BoolQueryBuilder expireTimeQuery = QueryBuilders.boolQuery();
+//        expireTimeQuery.should(rangeQuery);
+//        expireTimeQuery.should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("expireTime")));
+//
+//        boolQuery.filter(expireTimeQuery);
 
         // 状态
         boolQuery.must(QueryBuilders.termQuery("status", JobStatusEnum.ENABLE.getStatus()));