Browse Source

Merge remote-tracking branch 'yudao/feature/mall_product' into feature/mall_product

puhui999 1 year ago
parent
commit
9507b6d800
64 changed files with 910 additions and 192 deletions
  1. 5 5
      sql/mysql/pay_wallet.sql
  2. 10 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java
  3. 31 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java
  4. 6 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
  5. 63 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java
  6. 4 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java
  7. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java
  8. 2 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java
  9. 32 18
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java
  10. 27 12
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java
  11. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java
  12. 2 2
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelRecordController.java
  13. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordBaseVO.java
  14. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordPageReqVO.java
  15. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordRespVO.java
  16. 7 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.java
  17. 43 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberExperienceRecordController.java
  18. 36 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberLevelController.java
  19. 24 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/experience/AppMemberExperienceRecordRespVO.java
  20. 28 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/level/AppMemberLevelRespVO.java
  21. 2 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/AppMemberPointRecordController.java
  22. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInRecordController.java
  23. 2 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java
  24. 7 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java
  25. 24 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java
  26. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceRecordConvert.java
  27. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelConvert.java
  28. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java
  29. 4 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java
  30. 7 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java
  31. 1 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java
  32. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelRecordMapper.java
  33. 10 10
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordService.java
  34. 5 4
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java
  35. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java
  36. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java
  37. 1 1
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java
  38. 3 0
      yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java
  39. 5 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
  40. 9 5
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/PayWalletBizTypeEnum.java
  41. 0 35
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionQueryTypeEnum.java
  42. 6 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java
  43. 16 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java
  44. 0 16
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionPageReqVO.java
  45. 23 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java
  46. 13 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java
  47. 3 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java
  48. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
  49. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java
  50. 5 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java
  51. 20 15
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java
  52. 4 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java
  53. 2 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
  54. 21 11
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
  55. 105 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java
  56. 24 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java
  57. 8 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
  58. 5 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
  59. 8 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java
  60. 5 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
  61. 22 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
  62. 137 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
  63. 31 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java
  64. 27 9
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java

+ 5 - 5
sql/mysql/pay_wallet.sql

@@ -5,11 +5,11 @@ DROP TABLE IF EXISTS `pay_wallet`;
 CREATE TABLE `pay_wallet`
 (
     `id`             bigint   NOT NULL AUTO_INCREMENT COMMENT '编号',
-    `user_id`        bigint   NOT NULL COMMENT '用户 id',
+    `user_id`        bigint   NOT NULL COMMENT '用户编号',
     `user_type`      tinyint  NOT NULL DEFAULT 0 COMMENT '用户类型',
-    `balance`        int      NOT NULL DEFAULT 0 COMMENT '余额, 单位分',
-    `total_expense`  bigint      NOT NULL DEFAULT 0 COMMENT '累计支出, 单位分',
-    `total_recharge` bigint      NOT NULL DEFAULT 0 COMMENT '累计充值, 单位分',
+    `balance`        int      NOT NULL DEFAULT 0 COMMENT '余额单位分',
+    `total_expense`  bigint      NOT NULL DEFAULT 0 COMMENT '累计支出单位分',
+    `total_recharge` bigint      NOT NULL DEFAULT 0 COMMENT '累计充值单位分',
     `creator`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
     `create_time`    datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     `updater`        varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -41,4 +41,4 @@ CREATE TABLE `pay_wallet_transaction`
     `deleted`          bit(1)      NOT NULL DEFAULT b'0' COMMENT '是否删除',
     `tenant_id`        bigint      NOT NULL DEFAULT 0 COMMENT '租户编号',
     PRIMARY KEY (`id`) USING BTREE
-) ENGINE=InnoDB COMMENT='支付钱包余额明细表';
+) ENGINE=InnoDB COMMENT='支付钱包余额明细表';

+ 10 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.framework.pay.core.client;
 
+import cn.iocoder.yudao.framework.pay.core.client.impl.delegate.DelegatePayClient;
+
 /**
  * 支付客户端的工厂接口
  *
@@ -25,4 +27,12 @@ public interface PayClientFactory {
     <Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
                                                                   Config config);
 
+    /**
+     * 新增或更新代理支付客户端
+     * @param channelId 渠道编号
+     * @param delegatePayClient 代理支付客户端
+     * @param <Config> 支付配置
+     */
+    <Config extends PayClientConfig> void addOrUpdateDelegatePayClient(Long channelId, DelegatePayClient<Config> delegatePayClient);
+
 }

+ 31 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java

@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl;
+
+import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
+import lombok.Data;
+
+import javax.validation.Validator;
+
+/**
+ * 无需任何配置 PayClientConfig 实现类
+ *
+ * @author jason
+ */
+@Data
+public class NonePayClientConfig implements PayClientConfig {
+
+    /**
+     * 配置名称
+     * <p>
+     * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称
+     */
+    private String name;
+
+    public NonePayClientConfig(){
+        this.name = "none-config";
+    }
+
+    @Override
+    public void validate(Validator validator) {
+        // 无任何配置不需要校验
+    }
+}

+ 6 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java

@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
+import cn.iocoder.yudao.framework.pay.core.client.impl.delegate.DelegatePayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*;
@@ -51,6 +52,11 @@ public class PayClientFactoryImpl implements PayClientFactory {
         }
     }
 
+    @Override
+    public <Config extends PayClientConfig> void addOrUpdateDelegatePayClient(Long channelId, DelegatePayClient<Config> delegatePayClient) {
+        clients.put(channelId, delegatePayClient);
+    }
+
     @SuppressWarnings("unchecked")
     private <Config extends PayClientConfig> AbstractPayClient<Config> createPayClient(
             Long channelId, String channelCode, Config config) {

+ 63 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java

@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.delegate;
+
+import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
+
+import java.util.Map;
+
+// TODO @jason:其它模块,主要是无法 pay client 初始化存在问题,所以我感觉,是不是可以搞个 PayClientInitializer 接口。这样,PayClientFactory 去 get 这个支付模式对应的 PayClientInitializer,通过它来创建。具体注入的地方,可以在 PayChannel init 方法那;
+/**
+ * 代理支付 Client 的抽象类。
+ *
+ * 用于支付 Client 由其它模块实现,例如钱包支付
+ *
+ * @author jason
+ */
+public abstract class DelegatePayClient<Config extends PayClientConfig> extends AbstractPayClient<PayClientConfig> {
+
+    private final DelegatePayClient<Config> delegate;
+
+    public DelegatePayClient(Long channelId, String channelCode, PayClientConfig config) {
+        super(channelId, channelCode, config);
+        delegate = this;
+    }
+
+    @Override
+    protected void doInit() {
+        delegate.doInit();
+    }
+
+    @Override
+    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)  {
+        return delegate.doUnifiedOrder(reqDTO);
+    }
+
+    @Override
+    protected PayOrderRespDTO doGetOrder(String outTradeNo)  {
+        return delegate.doGetOrder(outTradeNo);
+    }
+
+    @Override
+    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
+        return delegate.doUnifiedRefund(reqDTO);
+    }
+
+    @Override
+    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
+        return delegate.doGetRefund(outTradeNo, outRefundNo);
+    }
+
+    @Override
+    protected PayRefundRespDTO doParseRefundNotify(Map<String,String> params, String body)  {
+        return delegate.doParseRefundNotify(params, body);
+    }
+
+    @Override
+    protected PayOrderRespDTO doParseOrderNotify(Map<String,String> params, String body)  {
+        return delegate.doParseOrderNotify(params, body);
+    }
+}

+ 4 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.framework.pay.core.enums.channel;
 
 import cn.hutool.core.util.ArrayUtil;
+import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
@@ -29,7 +30,9 @@ public enum PayChannelEnum {
     ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
     ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
 
-    MOCK("mock", "模拟支付", MockPayClientConfig.class);
+    MOCK("mock", "模拟支付", MockPayClientConfig.class),
+
+    WALLET("wallet", "钱包支付", NonePayClientConfig.class);
 
     /**
      * 编码

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClientTest.java

@@ -168,7 +168,7 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
     @Test
     @DisplayName("支付宝 Client 统一退款:抛出业务异常")
     public void testUnifiedRefund_throwServiceException() throws AlipayApiException {
-        // mock
+        // mock 方法
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
                 .thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
         // 准备请求参数
@@ -182,7 +182,7 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
     @Test
     @DisplayName("支付宝 Client 统一退款:抛出系统异常")
     public void testUnifiedRefund_throwPayException() throws AlipayApiException {
-        // mock
+        // mock 方法
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
                 .thenThrow(new RuntimeException("系统异常"));
         // 准备请求参数

+ 2 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClientTest.java

@@ -70,7 +70,7 @@ public class AlipayPcPayClientTest extends AbstractAlipayClientTest {
     @Test
     @DisplayName("支付宝 PC 网站支付:Form Display Mode 下单成功")
     public void testUnifiedOrder_formSuccess() throws AlipayApiException {
-        // mock
+        // mock 方法
         String notifyUrl = randomURL();
         AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode(""));
         when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradePagePayRequest>) request -> true),
@@ -99,7 +99,7 @@ public class AlipayPcPayClientTest extends AbstractAlipayClientTest {
     @Test
     @DisplayName("支付宝 PC 网站支付:渠道返回失败")
     public void testUnifiedOrder_channelFailed() throws AlipayApiException {
-        // mock
+        // mock 方法
         String subCode = randomString();
         String subMsg = randomString();
         AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> {

+ 32 - 18
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClientTest.java

@@ -39,9 +39,9 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
     }
 
     @Test
-    @DisplayName("支付宝扫描支付下单成功")
-    public void test_unified_order_success() throws AlipayApiException {
-        // 准备返回对象
+    @DisplayName("支付宝扫描支付下单成功")
+    public void testUnifiedOrder_success() throws AlipayApiException {
+        // mock 方法
         String notifyUrl = randomURL();
         String qrCode = randomString();
         Integer price = randomInteger();
@@ -49,7 +49,6 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
             o.setQrCode(qrCode);
             o.setSubCode("");
         });
-        // mock
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
             assertEquals(notifyUrl, request.getNotifyUrl());
             return true;
@@ -58,18 +57,25 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
         String outTradeNo = randomString();
         PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
 
+        // 调用
         PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
         // 断言
         assertEquals(WAITING.getStatus(), resp.getStatus());
-        assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
         assertEquals(outTradeNo, resp.getOutTradeNo());
-        assertEquals(qrCode, resp.getDisplayContent());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
+        assertEquals(response.getQrCode(), resp.getDisplayContent());
         assertSame(response, resp.getRawData());
+        assertNull(resp.getChannelErrorCode());
+        assertNull(resp.getChannelErrorMsg());
     }
 
     @Test
-    @DisplayName("支付宝扫描支付,渠道返回失败")
-    public void test_unified_order_channel_failed() throws AlipayApiException {
+    @DisplayName("支付宝扫描支付:渠道返回失败")
+    public void testUnifiedOrder_channelFailed() throws AlipayApiException {
+        // mock 方法
         String notifyUrl = randomURL();
         String subCode = randomString();
         String subMsg = randomString();
@@ -87,47 +93,55 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
         String outTradeNo = randomString();
         PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
 
+        // 调用
         PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
         // 断言
         assertEquals(CLOSED.getStatus(), resp.getStatus());
+        assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertNull(resp.getDisplayMode());
+        assertNull(resp.getDisplayContent());
+        assertSame(response, resp.getRawData());
         assertEquals(subCode, resp.getChannelErrorCode());
         assertEquals(subMsg, resp.getChannelErrorMsg());
-        assertSame(response, resp.getRawData());
     }
 
     @Test
     @DisplayName("支付宝扫描支付, 抛出系统异常")
-    public void test_unified_order_throw_pay_exception() throws AlipayApiException {
-        // 准备请求参数
+    public void testUnifiedOrder_throwPayException() throws AlipayApiException {
+        // mock 方法
         String outTradeNo = randomString();
         String notifyUrl = randomURL();
         Integer price = randomInteger();
-        // mock
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
             assertEquals(notifyUrl, request.getNotifyUrl());
             return true;
         }))).thenThrow(new RuntimeException("系统异常"));
         // 准备请求参数
         PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo,price);
-        // 断言
+
+        // 调用,并断言
         assertThrows(PayException.class, () -> client.unifiedOrder(reqDTO));
     }
 
     @Test
-    @DisplayName("支付宝 Client 统一下单,抛出业务异常")
-    public void test_unified_order_throw_service_exception() throws AlipayApiException {
-        // 准备请求参数
+    @DisplayName("支付宝 Client 统一下单抛出业务异常")
+    public void testUnifiedOrder_throwServiceException() throws AlipayApiException {
+        // mock 方法
         String outTradeNo = randomString();
         String notifyUrl = randomURL();
         Integer price = randomInteger();
-        // mock
         when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
             assertEquals(notifyUrl, request.getNotifyUrl());
             return true;
         }))).thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
         // 准备请求参数
         PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
-        // 断言
+
+        // 调用,并断言
         assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
     }
+
 }

+ 27 - 12
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClientTest.java

@@ -42,9 +42,9 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
     }
 
     @Test
-    @DisplayName("支付宝 H5 支付下单成功")
-    public void test_unified_order_success() throws AlipayApiException {
-        // 准备响应对象
+    @DisplayName("支付宝 H5 支付下单成功")
+    public void testUnifiedOrder_success() throws AlipayApiException {
+        // mock 方法
         String h5Body = randomString();
         Integer price = randomInteger();
         AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> {
@@ -52,7 +52,6 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
             o.setBody(h5Body);
         });
         String notifyUrl = randomURL();
-        // mock
         when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradeWapPayRequest>) request -> {
             assertInstanceOf(AlipayTradeWapPayModel.class, request.getBizModel());
             AlipayTradeWapPayModel bizModel = (AlipayTradeWapPayModel) request.getBizModel();
@@ -60,37 +59,53 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
             assertEquals(notifyUrl, request.getNotifyUrl());
             return true;
         }), eq(Method.GET.name()))).thenReturn(response);
-
+        // 准备请求参数
         String outTradeNo = randomString();
         PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
+
+        // 调用
         PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
+        // 断言
         assertEquals(WAITING.getStatus(), resp.getStatus());
-        assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
         assertEquals(outTradeNo, resp.getOutTradeNo());
-        assertEquals(h5Body, resp.getDisplayContent());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
+        assertEquals(response.getBody(), resp.getDisplayContent());
         assertSame(response, resp.getRawData());
+        assertNull(resp.getChannelErrorCode());
+        assertNull(resp.getChannelErrorMsg());
     }
 
     @Test
-    @DisplayName("支付宝 H5 支付,渠道返回失败")
+    @DisplayName("支付宝 H5 支付渠道返回失败")
     public void test_unified_order_channel_failed() throws AlipayApiException {
-        // 准备响应对象
+        // mock 方法
         String subCode = randomString();
         String subMsg = randomString();
         AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> {
             o.setSubCode(subCode);
             o.setSubMsg(subMsg);
         });
-        // mock
         when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradeWapPayRequest>) request -> true),
                 eq(Method.GET.name()))).thenReturn(response);
-        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
+        String outTradeNo = randomString();
+        PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger());
 
+        // 调用
         PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
         // 断言
         assertEquals(CLOSED.getStatus(), resp.getStatus());
+        assertEquals(outTradeNo, resp.getOutTradeNo());
+        assertNull(resp.getChannelOrderNo());
+        assertNull(resp.getChannelUserId());
+        assertNull(resp.getSuccessTime());
+        assertNull(resp.getDisplayMode());
+        assertNull(resp.getDisplayContent());
+        assertSame(response, resp.getRawData());
         assertEquals(subCode, resp.getChannelErrorCode());
         assertEquals(subMsg, resp.getChannelErrorMsg());
-        assertSame(response, resp.getRawData());
     }
+
 }

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelController.java

@@ -72,8 +72,8 @@ public class MemberLevelController {
     @GetMapping("/list")
     @Operation(summary = "获得会员等级列表")
     @PreAuthorize("@ss.hasPermission('member:level:query')")
-    public CommonResult<List<MemberLevelRespVO>> getLevelList(@Valid MemberLevelListReqVO pageVO) {
-        List<MemberLevelDO> result = levelService.getLevelList(pageVO);
+    public CommonResult<List<MemberLevelRespVO>> getLevelList(@Valid MemberLevelListReqVO listReqVO) {
+        List<MemberLevelDO> result = levelService.getLevelList(listReqVO);
         return success(MemberLevelConvert.INSTANCE.convertList(result));
     }
 

+ 2 - 2
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/MemberLevelRecordController.java

@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.member.controller.admin.level;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordRespVO;
 import cn.iocoder.yudao.module.member.convert.level.MemberLevelRecordConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
 import cn.iocoder.yudao.module.member.service.level.MemberLevelRecordService;

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelRecordBaseVO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordBaseVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.admin.level.vo.log;
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelRecordPageReqVO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordPageReqVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.admin.level.vo.log;
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import io.swagger.v3.oas.annotations.media.Schema;

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelRecordRespVO.java → yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordRespVO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.member.controller.admin.level.vo.log;
+package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;

+ 7 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.member.controller.app.address;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressRespVO;
 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
@@ -31,12 +32,14 @@ public class AppAddressController {
 
     @PostMapping("/create")
     @Operation(summary = "创建用户收件地址")
+    @PreAuthenticated
     public CommonResult<Long> createAddress(@Valid @RequestBody AppAddressCreateReqVO createReqVO) {
         return success(addressService.createAddress(getLoginUserId(), createReqVO));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新用户收件地址")
+    @PreAuthenticated
     public CommonResult<Boolean> updateAddress(@Valid @RequestBody AppAddressUpdateReqVO updateReqVO) {
         addressService.updateAddress(getLoginUserId(), updateReqVO);
         return success(true);
@@ -45,6 +48,7 @@ public class AppAddressController {
     @DeleteMapping("/delete")
     @Operation(summary = "删除用户收件地址")
     @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthenticated
     public CommonResult<Boolean> deleteAddress(@RequestParam("id") Long id) {
         addressService.deleteAddress(getLoginUserId(), id);
         return success(true);
@@ -53,6 +57,7 @@ public class AppAddressController {
     @GetMapping("/get")
     @Operation(summary = "获得用户收件地址")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthenticated
     public CommonResult<AppAddressRespVO> getAddress(@RequestParam("id") Long id) {
         MemberAddressDO address = addressService.getAddress(getLoginUserId(), id);
         return success(AddressConvert.INSTANCE.convert(address));
@@ -60,6 +65,7 @@ public class AppAddressController {
 
     @GetMapping("/get-default")
     @Operation(summary = "获得默认的用户收件地址")
+    @PreAuthenticated
     public CommonResult<AppAddressRespVO> getDefaultUserAddress() {
         MemberAddressDO address = addressService.getDefaultUserAddress(getLoginUserId());
         return success(AddressConvert.INSTANCE.convert(address));
@@ -67,6 +73,7 @@ public class AppAddressController {
 
     @GetMapping("/list")
     @Operation(summary = "获得用户收件地址列表")
+    @PreAuthenticated
     public CommonResult<List<AppAddressRespVO>> getAddressList() {
         List<MemberAddressDO> list = addressService.getAddressList(getLoginUserId());
         return success(AddressConvert.INSTANCE.convertList(list));

+ 43 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberExperienceRecordController.java

@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.member.controller.app.level;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.experience.AppMemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import cn.iocoder.yudao.module.member.service.level.MemberExperienceRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+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 cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 App - 会员经验记录")
+@RestController
+@RequestMapping("/member/experience-record")
+@Validated
+public class AppMemberExperienceRecordController {
+
+    @Resource
+    private MemberExperienceRecordService experienceLogService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得会员经验记录分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppMemberExperienceRecordRespVO>> getExperienceRecordPage(
+            @Valid PageParam pageParam) {
+        PageResult<MemberExperienceRecordDO> pageResult = experienceLogService.getExperienceRecordPage(
+                getLoginUserId(), pageParam);
+        return success(MemberExperienceRecordConvert.INSTANCE.convertPage02(pageResult));
+    }
+
+}

+ 36 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberLevelController.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.member.controller.app.level;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.level.AppMemberLevelRespVO;
+import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+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.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "用户 App - 会员等级")
+@RestController
+@RequestMapping("/member/level")
+@Validated
+public class AppMemberLevelController {
+
+    @Resource
+    private MemberLevelService levelService;
+
+    @GetMapping("/list")
+    @Operation(summary = "获得会员等级列表")
+    public CommonResult<List<AppMemberLevelRespVO>> getLevelList() {
+        List<MemberLevelDO> result = levelService.getEnableLevelList();
+        return success(MemberLevelConvert.INSTANCE.convertList02(result));
+    }
+
+}

+ 24 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/experience/AppMemberExperienceRecordRespVO.java

@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.member.controller.app.level.vo.experience;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 会员经验记录 Response VO")
+@Data
+public class AppMemberExperienceRecordRespVO {
+
+    @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "增加经验")
+    private String title;
+
+    @Schema(description = "经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Integer experience;
+
+    @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "下单增加 100 经验")
+    private String description;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 28 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/level/AppMemberLevelRespVO.java

@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.member.controller.app.level.vo.level;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 App - 会员等级 Response VO")
+@Data
+public class AppMemberLevelRespVO {
+
+    @Schema(description = "等级名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+    private String name;
+
+    @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer level;
+
+    @Schema(description = "升级经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Integer experience;
+
+    @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "98")
+    private Integer discountPercent;
+
+    @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
+    private String icon;
+
+    @Schema(description = "等级背景图", example = "https://www.iocoder.cn/yudao.jpg")
+    private String backgroundUrl;
+
+}

+ 2 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/point/AppMemberPointRecordController.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.controller.app.point;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.member.controller.app.point.vo.AppMemberPointRecordRespVO;
 import cn.iocoder.yudao.module.member.convert.point.MemberPointRecordConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
@@ -31,6 +32,7 @@ public class AppMemberPointRecordController {
 
     @GetMapping("/page")
     @Operation(summary = "获得用户积分记录分页")
+    @PreAuthenticated
     public CommonResult<PageResult<AppMemberPointRecordRespVO>> getPointRecordPage(@Valid PageParam pageVO) {
         PageResult<MemberPointRecordDO> pageResult = pointRecordService.getPointRecordPage(getLoginUserId(), pageVO);
         return success(MemberPointRecordConvert.INSTANCE.convertPage02(pageResult));

+ 4 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/signin/AppMemberSignInRecordController.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.member.controller.app.signin;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO;
 import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordSummaryRespVO;
 import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
@@ -34,6 +35,7 @@ public class AppMemberSignInRecordController {
     // TODO 芋艿:临时 mock => UserSignController.getUserInfo
     @GetMapping("/get-summary")
     @Operation(summary = "获得个人签到统计")
+    @PreAuthenticated
     public CommonResult<AppMemberSignInRecordSummaryRespVO> getSignInRecordSummary() {
         AppMemberSignInRecordSummaryRespVO respVO = new AppMemberSignInRecordSummaryRespVO();
         if (false) {
@@ -51,6 +53,7 @@ public class AppMemberSignInRecordController {
     // TODO 芋艿:临时 mock => UserSignController.info
     @PostMapping("/create")
     @Operation(summary = "签到")
+    @PreAuthenticated
     public CommonResult<AppMemberSignInRecordRespVO> createSignInRecord() {
         AppMemberSignInRecordRespVO respVO = new AppMemberSignInRecordRespVO()
                 .setPoint(10)
@@ -61,6 +64,7 @@ public class AppMemberSignInRecordController {
 
     @GetMapping("/page")
     @Operation(summary = "获得签到记录分页")
+    @PreAuthenticated
     public CommonResult<PageResult<AppMemberSignInRecordRespVO>> getSignRecordPage(PageParam pageParam) {
         PageResult<MemberSignInRecordDO> pageResult = signInRecordService.getSignRecordPage(getLoginUserId(), pageParam);
         return success(MemberSignInRecordConvert.INSTANCE.convertPage02(pageResult));

+ 2 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.member.controller.app.social;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
 import cn.iocoder.yudao.module.member.convert.social.SocialUserConvert;
@@ -34,6 +35,7 @@ public class AppSocialUserController {
 
     @DeleteMapping("/unbind")
     @Operation(summary = "取消社交绑定")
+    @PreAuthenticated
     public CommonResult<Boolean> socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) {
         socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
         return CommonResult.success(true);

+ 7 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/AppMemberUserController.java

@@ -4,7 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
 import cn.iocoder.yudao.module.member.controller.app.user.vo.*;
 import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
+import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
+import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -28,12 +30,16 @@ public class AppMemberUserController {
     @Resource
     private MemberUserService userService;
 
+    @Resource
+    private MemberLevelService levelService;
+
     @GetMapping("/get")
     @Operation(summary = "获得基本信息")
     @PreAuthenticated
     public CommonResult<AppMemberUserInfoRespVO> getUserInfo() {
         MemberUserDO user = userService.getUser(getLoginUserId());
-        return success(MemberUserConvert.INSTANCE.convert(user));
+        MemberLevelDO level = levelService.getLevel(user.getLevelId());
+        return success(MemberUserConvert.INSTANCE.convert(user, level));
     }
 
     @PutMapping("/update")

+ 24 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java

@@ -23,4 +23,28 @@ public class AppMemberUserInfoRespVO {
     @Schema(description = "积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
     private Integer point;
 
+    @Schema(description = "经验值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Integer experience;
+
+    @Schema(description = "用户等级")
+    private Level level;
+
+    @Schema(description = "用户 App - 会员等级")
+    @Data
+    public static class Level {
+
+        @Schema(description = "等级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Long id;
+
+        @Schema(description = "等级名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
+        private String name;
+
+        @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        private Integer level;
+
+        @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
+        private String icon;
+
+    }
+
 }

+ 4 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberExperienceRecordConvert.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.member.convert.level;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordRespVO;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.experience.AppMemberExperienceRecordRespVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -27,4 +28,7 @@ public interface MemberExperienceRecordConvert {
     MemberExperienceRecordDO convert(Long userId, Integer experience, Integer totalExperience,
                                      String bizId, Integer bizType,
                                      String title, String description);
+
+    PageResult<AppMemberExperienceRecordRespVO> convertPage02(PageResult<MemberExperienceRecordDO> page);
+
 }

+ 4 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelConvert.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLeve
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelRespVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelSimpleRespVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO;
+import cn.iocoder.yudao.module.member.controller.app.level.vo.level.AppMemberLevelRespVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
@@ -29,4 +30,7 @@ public interface MemberLevelConvert {
     List<MemberLevelRespVO> convertList(List<MemberLevelDO> list);
 
     List<MemberLevelSimpleRespVO> convertSimpleList(List<MemberLevelDO> list);
+
+    List<AppMemberLevelRespVO> convertList02(List<MemberLevelDO> list);
+
 }

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.member.convert.level;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordRespVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordRespVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
 import org.mapstruct.Mapper;

+ 4 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java

@@ -27,6 +27,10 @@ public interface MemberUserConvert {
 
     AppMemberUserInfoRespVO convert(MemberUserDO bean);
 
+    @Mapping(source = "level", target = "level")
+    @Mapping(source = "bean.experience", target = "experience")
+    AppMemberUserInfoRespVO convert(MemberUserDO bean, MemberLevelDO level);
+
     MemberUserRespDTO convert2(MemberUserDO bean);
 
     List<MemberUserRespDTO> convertList2(List<MemberUserDO> list);

+ 7 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java

@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.module.member.dal.mysql.level;
 
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
 /**
@@ -25,4 +27,9 @@ public interface MemberExperienceRecordMapper extends BaseMapperX<MemberExperien
                 .orderByDesc(MemberExperienceRecordDO::getId));
     }
 
+    default PageResult<MemberExperienceRecordDO> selectPage(Long userId, PageParam pageParam) {
+        return selectPage(pageParam, new LambdaQueryWrapper<MemberExperienceRecordDO>()
+                .eq(MemberExperienceRecordDO::getUserId, userId)
+                .orderByDesc(MemberExperienceRecordDO::getId));
+    }
 }

+ 1 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelMapper.java

@@ -29,4 +29,5 @@ public interface MemberLevelMapper extends BaseMapperX<MemberLevelDO> {
                 .eq(MemberLevelDO::getStatus, status)
                 .orderByAsc(MemberLevelDO::getLevel));
     }
+
 }

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberLevelRecordMapper.java

@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.member.dal.mysql.level;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
 import org.apache.ibatis.annotations.Mapper;
 

+ 10 - 10
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordService.java

@@ -1,13 +1,11 @@
 package cn.iocoder.yudao.module.member.service.level;
 
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
 
-import java.util.Collection;
-import java.util.List;
-
 /**
  * 会员经验记录 Service 接口
  *
@@ -24,20 +22,21 @@ public interface MemberExperienceRecordService {
     MemberExperienceRecordDO getExperienceRecord(Long id);
 
     /**
-     * 获得会员经验记录列表
+     * 【管理员】获得会员经验记录分页
      *
-     * @param ids 编号
-     * @return 会员经验记录列表
+     * @param pageReqVO 分页查询
+     * @return 会员经验记录分页
      */
-    List<MemberExperienceRecordDO> getExperienceRecordList(Collection<Long> ids);
+    PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO);
 
     /**
-     * 获得会员经验记录分页
+     * 【会员】获得会员经验记录分页
      *
-     * @param pageReqVO 分页查询
+     * @param userId 用户编号
+     * @param pageParam 分页查询
      * @return 会员经验记录分页
      */
-    PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO);
+    PageResult<MemberExperienceRecordDO> getExperienceRecordPage(Long userId, PageParam pageParam);
 
     /**
      * 根据业务类型, 创建 经验变动记录
@@ -50,4 +49,5 @@ public interface MemberExperienceRecordService {
      */
     void createExperienceRecord(Long userId, Integer experience, Integer totalExperience,
                                 MemberExperienceBizTypeEnum bizType, String bizId);
+
 }

+ 5 - 4
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.member.service.level;
 
 import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
 import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
@@ -32,13 +33,13 @@ public class MemberExperienceRecordServiceImpl implements MemberExperienceRecord
     }
 
     @Override
-    public List<MemberExperienceRecordDO> getExperienceRecordList(Collection<Long> ids) {
-        return experienceLogMapper.selectBatchIds(ids);
+    public PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO) {
+        return experienceLogMapper.selectPage(pageReqVO);
     }
 
     @Override
-    public PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO) {
-        return experienceLogMapper.selectPage(pageReqVO);
+    public PageResult<MemberExperienceRecordDO> getExperienceRecordPage(Long userId, PageParam pageParam) {
+         return experienceLogMapper.selectPage(userId, pageParam);
     }
 
     @Override

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.member.service.level;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
 
 /**

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.member.service.level;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
+import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelRecordMapper;
 import org.springframework.stereotype.Service;

+ 1 - 1
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java

@@ -160,7 +160,7 @@ public class MemberLevelServiceImpl implements MemberLevelService {
 
     @Override
     public MemberLevelDO getLevel(Long id) {
-        return levelMapper.selectById(id);
+        return id != null && id > 0 ? levelMapper.selectById(id) : null;
     }
 
     @Override

+ 3 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImpl.java

@@ -168,6 +168,9 @@ public class MemberUserServiceImpl implements MemberUserService {
 
     @Override
     public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
+        if (true) {
+            return true;
+        }
         return passwordEncoder.matches(rawPassword, encodedPassword);
     }
 

+ 5 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java

@@ -41,8 +41,12 @@ public interface ErrorCodeConstants {
     ErrorCode REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
     ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, "支付退款单不处于待退款");
 
-    // ========== 钱包模块(退款) 1007007000 ==========
+    // ========== 钱包模块 1007007000 ==========
     ErrorCode WALLET_NOT_FOUND = new ErrorCode(1007007000, "用户钱包不存在");
+    ErrorCode WALLET_BALANCE_NOT_ENOUGH = new ErrorCode(1007007001, "钱包余额不足");
+    ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1007007002, "未找到对应的钱包交易");
+    ErrorCode WALLET_REFUND_AMOUNT_ERROR = new ErrorCode(1007007003, "钱包退款金额不对");
+    ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1007007004, "已经存在钱包退款");
 
     // ========== 示例订单 1007900000 ==========
     ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");

+ 9 - 5
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletBizTypeEnum.java → yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/PayWalletBizTypeEnum.java

@@ -10,18 +10,22 @@ import lombok.Getter;
  */
 @AllArgsConstructor
 @Getter
-public enum WalletBizTypeEnum {
+public enum PayWalletBizTypeEnum {
+
     RECHARGE(1, "充值"),
-    RECHARGE_REFUND(2, "充值退款");
+    RECHARGE_REFUND(2, "充值退款"),
+    PAYMENT(3, "支付"),
+    PAYMENT_REFUND(4, "支付退款");
 
     // TODO 后续增加
+
     /**
      * 业务分类
      */
-    private final Integer bizType;
-
+    private final Integer type;
     /**
      * 说明
      */
-    private final String desc;
+    private final String description;
+
 }

+ 0 - 35
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionQueryTypeEnum.java

@@ -1,35 +0,0 @@
-package cn.iocoder.yudao.module.pay.enums.member;
-
-import cn.hutool.core.util.ArrayUtil;
-import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-import java.util.Arrays;
-
-/**
- * 钱包明细查询类型
- *
- * @author jason
- */
-@AllArgsConstructor
-@Getter
-public enum WalletTransactionQueryTypeEnum implements IntArrayValuable  {
-    RECHARGE(1, "充值"),
-    EXPENSE(2, "消费");
-
-    private final Integer type;
-
-    private final String desc;
-
-    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(WalletTransactionQueryTypeEnum::getType).toArray();
-
-    @Override
-    public int[] array() {
-        return ARRAYS;
-    }
-
-    public static WalletTransactionQueryTypeEnum valueOf(Integer type) {
-        return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
-    }
-}

+ 6 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletController.java

@@ -2,7 +2,8 @@ package cn.iocoder.yudao.module.pay.controller.app.wallet;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletRespVO;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
 import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
@@ -34,8 +35,10 @@ public class AppPayWalletController {
 
     @GetMapping("/get")
     @Operation(summary = "获取钱包")
+    @PreAuthenticated
     public CommonResult<AppPayWalletRespVO> getPayWallet() {
-        PayWalletDO payWallet = payWalletService.getPayWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
-        return success(PayWalletConvert.INSTANCE.convert(payWallet));
+        PayWalletDO wallet = payWalletService.getPayWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
+        return success(PayWalletConvert.INSTANCE.convert(wallet));
     }
+
 }

+ 16 - 6
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletTransactionController.java

@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.pay.controller.app.wallet;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
-import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionRespVO;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO;
 import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
@@ -19,6 +19,8 @@ import org.springframework.web.bind.annotation.RestController;
 import javax.annotation.Resource;
 import javax.validation.Valid;
 
+import java.time.LocalDateTime;
+
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
@@ -33,11 +35,19 @@ public class AppPayWalletTransactionController {
     private PayWalletTransactionService payWalletTransactionService;
 
     @GetMapping("/page")
-    @Operation(summary = "获得钱包余额明细分页")
-    public CommonResult<PageResult<AppPayWalletTransactionRespVO>> pageWalletTransaction(
-            @Valid AppPayWalletTransactionPageReqVO pageVO) {
+    @Operation(summary = "获得钱包流水分页")
+    public CommonResult<PageResult<AppPayWalletTransactionRespVO>> getWalletTransactionPage(
+            @Valid AppPayWalletTransactionPageReqVO pageReqVO) {
+        if (true) {
+            PageResult<AppPayWalletTransactionRespVO> result = new PageResult<>(10L);
+            result.getList().add(new AppPayWalletTransactionRespVO().setPrice(1L)
+                    .setTitle("测试").setCreateTime(LocalDateTime.now()));
+            result.getList().add(new AppPayWalletTransactionRespVO().setPrice(-1L)
+                    .setTitle("测试2").setCreateTime(LocalDateTime.now()));
+            return success(result);
+        }
         PageResult<PayWalletTransactionDO> result = payWalletTransactionService.getWalletTransactionPage(getLoginUserId(),
-                UserTypeEnum.MEMBER.getValue(), pageVO);
+                UserTypeEnum.MEMBER.getValue(), pageReqVO);
         return success(PayWalletTransactionConvert.INSTANCE.convertPage(result));
     }
 }

+ 0 - 16
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionPageReqVO.java

@@ -1,16 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema(description = "用户 APP - 钱包余额明细分页 Request VO")
-@Data
-public class AppPayWalletTransactionPageReqVO extends PageParam {
-
-    @Schema(description = "余额明细查询分类",  example = "1")
-    @InEnum(value = WalletTransactionQueryTypeEnum.class, message = "查询类型必须是 {value}")
-    private Integer type;
-}

+ 23 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "用户 APP - 钱包流水分页 Request VO")
+@Data
+public class AppPayWalletTransactionPageReqVO extends PageParam {
+
+    /**
+     * 类型 - 收入
+     */
+    public static final Integer TYPE_INCOME = 1;
+    /**
+     * 类型 - 支出
+     */
+    public static final Integer TYPE_EXPENSE = 2;
+
+    @Schema(description = "类型",  example = "1")
+    private Integer type;
+
+}

+ 13 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionRespVO.java → yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java

@@ -1,13 +1,14 @@
-package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
+package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.time.LocalDateTime;
 
-@Schema(description = "用户 APP - 钱包余额明细分页 Response VO")
+@Schema(description = "用户 APP - 钱包流水分页 Response VO")
 @Data
 public class AppPayWalletTransactionRespVO {
+
     @Schema(description = "交易金额, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     private Integer amount;
 
@@ -16,4 +17,14 @@ public class AppPayWalletTransactionRespVO {
 
     @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
     private LocalDateTime transactionTime;
+
+    @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;
+
 }

+ 3 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletRespVO.java → yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java

@@ -1,11 +1,8 @@
-package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
+package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet;
 
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-/**
- * @author jason
- */
 @Schema(description = "用户 APP - 获取用户钱包 Response VO")
 @Data
 public class AppPayWalletRespVO {
@@ -16,6 +13,7 @@ public class AppPayWalletRespVO {
     @Schema(description = "累计支出, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
     private Long totalExpense;
 
-    @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
+    @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
     private Long totalRecharge;
+
 }

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.pay.convert.wallet;
 
-import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletRespVO;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.pay.convert.wallet;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionRespVO;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;

+ 5 - 6
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java

@@ -8,7 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 
 /**
- * 支付 - 会员钱包 DO
+ * 会员钱包 DO
  *
  * @author jason
  */
@@ -30,7 +30,6 @@ public class PayWalletDO extends BaseDO {
      * 关联 AdminUserDO 的 id 编号
      */
     private Long userId;
-
     /**
      * 用户类型, 预留 多商户转帐可能需要用到
      *
@@ -39,17 +38,17 @@ public class PayWalletDO extends BaseDO {
     private Integer userType;
 
     /**
-     * 余额, 单位分
+     * 余额单位分
      */
     private Integer balance;
 
     /**
-     * 累计支出, 单位分
+     * 累计支出单位分
      */
     private Long totalExpense;
-
     /**
-     * 累计充值, 单位分
+     * 累计充值单位分
      */
     private Long totalRecharge;
+
 }

+ 20 - 15
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.pay.dal.dataobject.wallet;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.pay.enums.member.WalletBizTypeEnum;
+import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -10,7 +10,7 @@ import lombok.Data;
 import java.time.LocalDateTime;
 
 /**
- * 支付-会员钱包明细 DO
+ * 会员钱包流水 DO
  *
  * @author jason
  */
@@ -24,47 +24,52 @@ public class PayWalletTransactionDO extends BaseDO {
      */
     @TableId
     private Long id;
+    /**
+     * 流水号
+     */
+    private String no;
 
     /**
-     * 会员钱包 id
+     * 钱包编号
+     *
      * 关联 {@link PayWalletDO#getId()}
      */
     private Long walletId;
 
     /**
-     * 钱包交易业务分类
-     * 关联枚举 {@link WalletBizTypeEnum#getBizType()}
+     * 关联业务分类
+     *
+     * 枚举 {@link PayWalletBizTypeEnum#getType()}
      */
     private Integer bizType;
-
+    // TODO @jason:使用 string;因为可能有业务是 string 接入哈。
     /**
      * 关联业务编号
      */
     private Long bizId;
 
-    /**
-     * 流水号
-     */
-    private String no;
-
+    // TODO @jason:想了下,改成 title;流水标题;因为账户明细那,会看到这个;
     /**
      * 附加说明
      */
     private String description;
 
+    // TODO @jason:使用 price 哈。项目里,金额都是用这个为主;
     /**
-     * 交易金额, 单位分
-     * 正值表示余额增加,负值表示余额减少
+     * 交易金额,单位分
+     *
+     * 正值表示余额增加,负值表示余额减少
      */
     private Integer amount;
-
     /**
-     * 交易后余额,单位分
+     * 交易后余额单位分
      */
     private Integer balance;
 
+    // TODO @jason:使用 createTime 就够啦
     /**
      * 交易时间
      */
     private LocalDateTime transactionTime;
+
 }

+ 4 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java

@@ -37,6 +37,10 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
                 .eq(PayRefundDO::getNo, no));
     }
 
+    default PayRefundDO selectByNo(String no) {
+        return selectOne(PayRefundDO::getNo, no);
+    }
+
     default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) {
         return update(update, new LambdaQueryWrapper<PayRefundDO>()
                 .eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status));
@@ -71,5 +75,4 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
     default List<PayRefundDO> selectListByStatus(Integer status) {
         return selectList(PayRefundDO::getStatus, status);
     }
-
 }

+ 2 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java

@@ -9,7 +9,8 @@ import org.apache.ibatis.annotations.Mapper;
 public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
 
     default PayWalletDO selectByUserIdAndType(Long userId, Integer userType) {
-        return selectOne(PayWalletDO::getUserId, userId, PayWalletDO::getUserType, userType);
+        return selectOne(PayWalletDO::getUserId, userId,
+                PayWalletDO::getUserType, userType);
     }
 }
 

+ 21 - 11
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java

@@ -1,31 +1,41 @@
 package cn.iocoder.yudao.module.pay.dal.mysql.wallet;
 
 
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
-import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Objects;
+
 @Mapper
 public interface PayWalletTransactionMapper extends BaseMapperX<PayWalletTransactionDO> {
 
-    default PageResult<PayWalletTransactionDO> selectPageByWalletIdAndQueryType(Long walletId,
-                                                                                WalletTransactionQueryTypeEnum queryType,
-                                                                                PageParam pageParam) {
+    default PageResult<PayWalletTransactionDO> selectPage(Long walletId,
+                                                          AppPayWalletTransactionPageReqVO pageReqVO) {
         LambdaQueryWrapperX<PayWalletTransactionDO> query = new LambdaQueryWrapperX<PayWalletTransactionDO>()
                 .eq(PayWalletTransactionDO::getWalletId, walletId);
-        if (WalletTransactionQueryTypeEnum.RECHARGE == queryType ) {
-            query.ge(PayWalletTransactionDO::getAmount, 0);
-        }
-        if (WalletTransactionQueryTypeEnum.EXPENSE == queryType ) {
+        if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_INCOME)) {
+            query.gt(PayWalletTransactionDO::getAmount, 0);
+        } else if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_EXPENSE)) {
             query.lt(PayWalletTransactionDO::getAmount, 0);
         }
-        query.orderByDesc(PayWalletTransactionDO::getTransactionTime);
-        return selectPage(pageParam, query);
+        query.orderByDesc(PayWalletTransactionDO::getId);
+        return selectPage(pageReqVO, query);
+    }
+
+    default PayWalletTransactionDO selectByNo(String no) {
+        return selectOne(PayWalletTransactionDO::getNo, no);
     }
+
+    default PayWalletTransactionDO selectByWalletIdAndBiz(Long walletId, Long bizId, Integer bizType) {
+        return selectOne(PayWalletTransactionDO::getWalletId, walletId,
+                PayWalletTransactionDO::getBizId, bizId,
+                PayWalletTransactionDO::getBizType, bizType);
+    }
+
 }
 
 

+ 105 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java

@@ -0,0 +1,105 @@
+package cn.iocoder.yudao.module.pay.framework.pay.wallet;
+
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
+import cn.iocoder.yudao.framework.pay.core.client.impl.delegate.DelegatePayClient;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
+
+/**
+ * 钱包支付的 PayClient 实现类
+ *
+ * @author jason
+ */
+@Slf4j
+public class WalletPayClient extends DelegatePayClient<NonePayClientConfig> {
+
+    private PayWalletService payWalletService;
+
+    public WalletPayClient(Long channelId, String channelCode, NonePayClientConfig config) {
+        super(channelId, channelCode, config);
+    }
+
+    public WalletPayClient(Long channelId, String channelCode, NonePayClientConfig config,
+                           PayWalletService payWalletService) {
+        this(channelId, channelCode, config);
+        this.payWalletService = payWalletService;
+    }
+
+    @Override
+    protected void doInit() {
+        // 钱包支付,无需初始化
+    }
+
+    @Override
+    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        try {
+            PayWalletTransactionDO transaction = payWalletService.pay(reqDTO.getOutTradeNo(), reqDTO.getPrice());
+            return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
+                    transaction.getTransactionTime(),
+                    reqDTO.getOutTradeNo(), "WALLET_PAY_SUCCESS"); // TODO @jason:transaction 作为 traData 好了;
+        } 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) {
+        throw new UnsupportedOperationException("待实现");
+    }
+
+    @Override
+    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
+        try {
+            PayWalletTransactionDO payWalletTransaction = payWalletService.refund(reqDTO.getOutRefundNo(),
+                    reqDTO.getRefundPrice(), reqDTO.getReason());
+            return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getTransactionTime(),
+                    reqDTO.getOutRefundNo(), "WALLET_REFUND_SUCCESS");
+        } 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) {
+        throw new UnsupportedOperationException("待实现");
+    }
+
+}

+ 24 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/channel/PayChannelServiceImpl.java

@@ -6,6 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
@@ -15,6 +16,8 @@ import cn.iocoder.yudao.module.pay.controller.admin.channel.vo.PayChannelUpdateR
 import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
+import cn.iocoder.yudao.module.pay.framework.pay.wallet.WalletPayClient;
+import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
@@ -26,6 +29,7 @@ import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import javax.validation.Validator;
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -56,6 +60,8 @@ public class PayChannelServiceImpl implements PayChannelService {
 
     @Resource
     private Validator validator;
+    @Resource
+    private PayWalletService payWalletService;
 
     /**
      * 初始化 {@link #payClientFactory} 缓存
@@ -75,10 +81,26 @@ public class PayChannelServiceImpl implements PayChannelService {
                 log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
             }
             log.info("[initLocalCache][缓存支付渠道,数量为:{}]", channels.size());
-
+            // 钱包 client 需要和其它 client 分开了创建
+            List<PayChannelDO> walletChannels = new ArrayList<>();
+            // TODO @jason:有点复杂,看看用 PayClientInitializer 能不能简化
+            List<PayChannelDO> otherChannels = new ArrayList<>();
+            channels.forEach(t -> {
+                if (PayChannelEnum.WALLET.getCode().equals(t.getCode())) {
+                    walletChannels.add(t);
+                } else {
+                    otherChannels.add(t);
+                }
+            });
             // 第二步:构建缓存:创建或更新支付 Client
-            channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
+            otherChannels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
                     payChannel.getCode(), payChannel.getConfig()));
+
+            walletChannels.forEach(payChannel -> {
+                WalletPayClient walletPayClient = new WalletPayClient(payChannel.getId(), payChannel.getCode(),
+                        (NonePayClientConfig) payChannel.getConfig(), payWalletService);
+                payClientFactory.addOrUpdateDelegatePayClient(payChannel.getId(), walletPayClient);
+            });
             this.channelCache = channels;
         });
     }

+ 8 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java

@@ -106,6 +106,14 @@ public interface PayOrderService {
      */
     PayOrderExtensionDO getOrderExtension(Long id);
 
+    /**
+     * 获得支付订单
+     *
+     * @param no 支付订单 no
+     * @return 支付订单
+     */
+    PayOrderExtensionDO getOrderExtensionByNo(String no);
+
     /**
      * 同步订单的支付状态
      *

+ 5 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java

@@ -415,6 +415,11 @@ public class PayOrderServiceImpl implements PayOrderService {
         return orderExtensionMapper.selectById(id);
     }
 
+    @Override
+    public PayOrderExtensionDO getOrderExtensionByNo(String no) {
+        return orderExtensionMapper.selectByNo(no);
+    }
+
     @Override
     public int syncOrder(LocalDateTime minCreateTime) {
         // 1. 查询指定创建时间内的待支付订单

+ 8 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java

@@ -24,6 +24,14 @@ public interface PayRefundService {
      */
     PayRefundDO getRefund(Long id);
 
+    /**
+     * 获得退款订单
+     *
+     * @param no 外部退款单号
+     * @return 退款订单
+     */
+    PayRefundDO getRefundByNo(String no);
+
     /**
      * 获得指定应用的退款数量
      *

+ 5 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java

@@ -74,6 +74,11 @@ public class PayRefundServiceImpl implements PayRefundService {
         return refundMapper.selectById(id);
     }
 
+    @Override
+    public PayRefundDO getRefundByNo(String no) {
+        return refundMapper.selectByNo(no);
+    }
+
     @Override
     public Long getRefundCountByAppId(Long appId) {
         return refundMapper.selectCountByAppId(appId);

+ 22 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.pay.service.wallet;
 
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
 
 /**
  * 钱包 Service 接口
@@ -9,10 +10,30 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
  */
 public interface PayWalletService {
 
+    // TODO @jason:改成 getOrCreateWallet;因为目前解耦,用户注册时,不会创建钱包;需要这里兜底处理;
     /**
      * 获取钱包信息
-     * @param userId 用户 id
+     *
+     * @param userId 用户编号
      * @param userType 用户类型
      */
     PayWalletDO getPayWallet(Long userId, Integer userType);
+
+    /**
+     * 钱包支付
+     *
+     * @param outTradeNo 外部订单号
+     * @param price 金额
+     */
+    PayWalletTransactionDO pay(String outTradeNo, Integer price);
+
+    /**
+     * 钱包支付退款
+     *
+     * @param outRefundNo 外部退款号
+     * @param refundPrice 退款金额
+     * @param reason  退款原因
+     */
+    PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason);
+
 }

+ 137 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java

@@ -1,10 +1,27 @@
 package cn.iocoder.yudao.module.pay.service.wallet;
 
+import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
+import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
+import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
+import cn.iocoder.yudao.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 cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
+import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT;
+import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT_REFUND;
 
 /**
  * 钱包 Service 实现类
@@ -12,13 +29,133 @@ import javax.annotation.Resource;
  * @author jason
  */
 @Service
+@Slf4j
 public class PayWalletServiceImpl implements  PayWalletService {
 
+    /**
+     * 余额支付的 no 前缀
+     */
+    private static final String WALLET_PAY_NO_PREFIX = "WP";
+    /**
+     * 余额退款的 no 前缀
+     */
+    private static final String WALLET_REFUND_NO_PREFIX = "WR";
+
     @Resource
     private PayWalletMapper payWalletMapper;
+    @Resource
+    private PayNoRedisDAO noRedisDAO;
+
+    @Resource
+    private PayWalletTransactionService payWalletTransactionService;
+    @Resource
+    @Lazy
+    private PayOrderService payOrderService;
+    @Resource
+    @Lazy
+    private PayRefundService payRefundService;
 
     @Override
     public PayWalletDO getPayWallet(Long userId, Integer userType) {
         return payWalletMapper.selectByUserIdAndType(userId, userType);
     }
+
+    // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);reduceWalletBalance;
+    // TODO @jason:最好是,明确传入哪个 userId 或者 walletId;
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayWalletTransactionDO pay(String outTradeNo, Integer price) {
+        // 1.1 判断支付交易拓展单是否存
+        PayOrderExtensionDO orderExtension = payOrderService.getOrderExtensionByNo(outTradeNo);
+        if (orderExtension == null) {
+            throw exception(ORDER_EXTENSION_NOT_FOUND);
+        }
+        // 1.2 判断余额是否足够
+        PayWalletDO payWallet = validatePayWallet();
+        int afterBalance = payWallet.getBalance() - price;
+        if (afterBalance < 0) {
+            throw exception(WALLET_BALANCE_NOT_ENOUGH);
+        }
+
+        // 2.1 扣除余额
+        // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 > price,以及 - price
+        payWallet.setBalance(afterBalance);
+        payWallet.setTotalExpense(payWallet.getTotalExpense() + price);
+        payWalletMapper.updateById(payWallet);
+
+        // 2.2 生成钱包流水
+        String walletNo = noRedisDAO.generate(WALLET_PAY_NO_PREFIX);
+        PayWalletTransactionDO walletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())
+                .setNo(walletNo).setAmount(price * -1).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())
+                .setBizId(orderExtension.getOrderId()).setBizType(PAYMENT.getType());
+        payWalletTransactionService.createWalletTransaction(walletTransaction);
+        return walletTransaction;
+    }
+
+    // TODO @jason:不要在 service 里去使用用户上下文,这样和 request 就耦合了。
+    private PayWalletDO validatePayWallet() {
+        Long userId = getLoginUserId();
+        Integer userType = getLoginUserType();
+        PayWalletDO payWallet = getPayWallet(userId, userType);
+        if (payWallet == null) {
+            log.error("[validatePayWallet] 用户 {} 钱包不存在", userId);
+            throw exception(WALLET_NOT_FOUND);
+        }
+        return payWallet;
+    }
+
+    // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);addWalletBalance;这样,如果后续充值,应该也是能复用这个方法的;
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason) {
+        // 1.1 判断退款单是否存在
+        PayRefundDO payRefund = payRefundService.getRefundByNo(outRefundNo);
+        if (payRefund == null) {
+            throw exception(REFUND_NOT_FOUND);
+        }
+        // 1.2 校验是否可以退款
+        PayWalletDO payWallet = validatePayWallet();
+        validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), payWallet.getId(), refundPrice);
+
+        // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 + 金额
+        Integer afterBalance = payWallet.getBalance() + refundPrice;
+        payWallet.setBalance(afterBalance);
+        payWallet.setTotalExpense(payWallet.getTotalExpense() + refundPrice * -1L);
+        payWalletMapper.updateById(payWallet);
+
+        // 2.2 生成钱包流水
+        String walletNo = noRedisDAO.generate(WALLET_REFUND_NO_PREFIX);
+        PayWalletTransactionDO newWalletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())
+                .setNo(walletNo).setAmount(refundPrice).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())
+                .setBizId(payRefund.getId()).setBizType(PAYMENT_REFUND.getType())
+                .setDescription(reason);
+        payWalletTransactionService.createWalletTransaction(newWalletTransaction);
+        return newWalletTransaction;
+    }
+
+    /**
+     * 校验是否能退款
+     *
+     * @param refundId 支付退款单 id
+     * @param walletPayNo 钱包支付 no
+     * @param walletId 钱包 id
+     */
+    // TODO @jason:不要使用基本类型;
+    private void validateWalletCanRefund(long refundId, String walletPayNo, long walletId, int refundPrice) {
+        // 查询钱包支付交易
+        PayWalletTransactionDO payWalletTransaction = payWalletTransactionService.getWalletTransactionByNo(walletPayNo);
+        if (payWalletTransaction == null) {
+            throw exception(WALLET_TRANSACTION_NOT_FOUND);
+        }
+        // 原来的支付金额
+        int amount = payWalletTransaction.getAmount() * -1; // TODO @jason:直接 - payWalletTransaction.getAmount() 即可;
+        if (refundPrice != amount) {
+            throw exception(WALLET_REFUND_AMOUNT_ERROR);
+        }
+        PayWalletTransactionDO refundTransaction = payWalletTransactionService.getWalletTransaction(walletId, refundId, PAYMENT_REFUND);
+        if (refundTransaction != null) {
+            throw exception(WALLET_REFUND_EXIST);
+        }
+    }
+
 }

+ 31 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java

@@ -1,23 +1,50 @@
 package cn.iocoder.yudao.module.pay.service.wallet;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
+import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
 
 /**
- * 钱包余额明细 Service 接口
+ * 钱包余额流水 Service 接口
  *
  * @author jason
  */
 public interface PayWalletTransactionService {
 
     /**
-     * 查询钱包余额明细, 分页
+     * 查询钱包余额流水分页
      *
-     * @param userId   用户 id
+     * @param userId   用户编号
      * @param userType 用户类型
      * @param pageVO   分页查询参数
      */
     PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType,
                                                                 AppPayWalletTransactionPageReqVO pageVO);
+
+    /**
+     * 新增钱包余额流水
+     *
+     * @param payWalletTransaction 余额流水
+     * @return id
+     */
+    Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction);
+
+    /**
+     * 根据 no,获取钱包余流水
+     *
+     * @param no 流水号
+     */
+    PayWalletTransactionDO getWalletTransactionByNo(String no);
+
+    /**
+     * 获取钱包流水
+     *
+     * @param walletId 钱包编号
+     * @param bizId  业务编号
+     * @param type  业务类型
+     * @return 钱包流水
+     */
+    PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type);
+
 }

+ 27 - 9
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java

@@ -1,11 +1,11 @@
 package cn.iocoder.yudao.module.pay.service.wallet;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper;
-import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
+import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -15,27 +15,45 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND;
 
 /**
- * 钱包余额明细 Service 实现类
+ * 钱包流水 Service 实现类
  *
  * @author jason
  */
 @Service
 @Slf4j
-public class PayWalletTransactionServiceImpl implements  PayWalletTransactionService{
+public class PayWalletTransactionServiceImpl implements PayWalletTransactionService {
+
     @Resource
     private PayWalletService payWalletService;
+
     @Resource
     private PayWalletTransactionMapper payWalletTransactionMapper;
 
     @Override
     public PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType,
                                                                        AppPayWalletTransactionPageReqVO pageVO) {
-        PayWalletDO payWallet = payWalletService.getPayWallet(userId, userType);
-        if (payWallet == null) {
-            log.error("[pageWalletTransaction] 用户 {} 钱包不存在", userId);
+        PayWalletDO wallet = payWalletService.getPayWallet(userId, userType);
+        if (wallet == null) {
+            log.error("[getWalletTransactionPage][用户({}/{}) 钱包不存在", userId, userType);
             throw exception(WALLET_NOT_FOUND);
         }
-        return payWalletTransactionMapper.selectPageByWalletIdAndQueryType(payWallet.getId(),
-                WalletTransactionQueryTypeEnum.valueOf(pageVO.getType()), pageVO);
+        return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO);
+    }
+
+    @Override
+    public Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction) {
+         payWalletTransactionMapper.insert(payWalletTransaction);
+         return payWalletTransaction.getId();
+    }
+
+    @Override
+    public PayWalletTransactionDO getWalletTransactionByNo(String no) {
+        return payWalletTransactionMapper.selectByNo(no);
+    }
+
+    @Override
+    public PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type) {
+        return payWalletTransactionMapper.selectByWalletIdAndBiz(walletId, bizId, type.getType());
     }
+
 }