Commit 9507b6d800ef83f799316d3ded20b2d79d557d9e

Authored by puhui999
2 parents d5fe7708 cc71aabd

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

Showing 64 changed files with 910 additions and 192 deletions
sql/mysql/pay_wallet.sql
... ... @@ -5,11 +5,11 @@ DROP TABLE IF EXISTS `pay_wallet`;
5 5 CREATE TABLE `pay_wallet`
6 6 (
7 7 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
8   - `user_id` bigint NOT NULL COMMENT '用户 id',
  8 + `user_id` bigint NOT NULL COMMENT '用户编号',
9 9 `user_type` tinyint NOT NULL DEFAULT 0 COMMENT '用户类型',
10   - `balance` int NOT NULL DEFAULT 0 COMMENT '余额, 单位分',
11   - `total_expense` bigint NOT NULL DEFAULT 0 COMMENT '累计支出, 单位分',
12   - `total_recharge` bigint NOT NULL DEFAULT 0 COMMENT '累计充值, 单位分',
  10 + `balance` int NOT NULL DEFAULT 0 COMMENT '余额,单位分',
  11 + `total_expense` bigint NOT NULL DEFAULT 0 COMMENT '累计支出,单位分',
  12 + `total_recharge` bigint NOT NULL DEFAULT 0 COMMENT '累计充值,单位分',
13 13 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
14 14 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
15 15 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
... ... @@ -41,4 +41,4 @@ CREATE TABLE `pay_wallet_transaction`
41 41 `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
42 42 `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
43 43 PRIMARY KEY (`id`) USING BTREE
44   -) ENGINE=InnoDB COMMENT='支付钱包余额明细表';
45 44 \ No newline at end of file
  45 +) ENGINE=InnoDB COMMENT='支付钱包余额明细表';
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientFactory.java
1 1 package cn.iocoder.yudao.framework.pay.core.client;
2 2  
  3 +import cn.iocoder.yudao.framework.pay.core.client.impl.delegate.DelegatePayClient;
  4 +
3 5 /**
4 6 * 支付客户端的工厂接口
5 7 *
... ... @@ -25,4 +27,12 @@ public interface PayClientFactory {
25 27 <Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
26 28 Config config);
27 29  
  30 + /**
  31 + * 新增或更新代理支付客户端
  32 + * @param channelId 渠道编号
  33 + * @param delegatePayClient 代理支付客户端
  34 + * @param <Config> 支付配置
  35 + */
  36 + <Config extends PayClientConfig> void addOrUpdateDelegatePayClient(Long channelId, DelegatePayClient<Config> delegatePayClient);
  37 +
28 38 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.impl;
  2 +
  3 +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
  4 +import lombok.Data;
  5 +
  6 +import javax.validation.Validator;
  7 +
  8 +/**
  9 + * 无需任何配置 PayClientConfig 实现类
  10 + *
  11 + * @author jason
  12 + */
  13 +@Data
  14 +public class NonePayClientConfig implements PayClientConfig {
  15 +
  16 + /**
  17 + * 配置名称
  18 + * <p>
  19 + * 如果不加任何属性,JsonUtils.parseObject2 解析会报错,所以暂时加个名称
  20 + */
  21 + private String name;
  22 +
  23 + public NonePayClientConfig(){
  24 + this.name = "none-config";
  25 + }
  26 +
  27 + @Override
  28 + public void validate(Validator validator) {
  29 + // 无任何配置不需要校验
  30 + }
  31 +}
... ...
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;
5 5 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
6 6 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
7 7 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
  8 +import cn.iocoder.yudao.framework.pay.core.client.impl.delegate.DelegatePayClient;
8 9 import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient;
9 10 import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
10 11 import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*;
... ... @@ -51,6 +52,11 @@ public class PayClientFactoryImpl implements PayClientFactory {
51 52 }
52 53 }
53 54  
  55 + @Override
  56 + public <Config extends PayClientConfig> void addOrUpdateDelegatePayClient(Long channelId, DelegatePayClient<Config> delegatePayClient) {
  57 + clients.put(channelId, delegatePayClient);
  58 + }
  59 +
54 60 @SuppressWarnings("unchecked")
55 61 private <Config extends PayClientConfig> AbstractPayClient<Config> createPayClient(
56 62 Long channelId, String channelCode, Config config) {
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/delegate/DelegatePayClient.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.impl.delegate;
  2 +
  3 +import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
  4 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
  5 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  6 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
  7 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
  8 +import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
  9 +
  10 +import java.util.Map;
  11 +
  12 +// TODO @jason:其它模块,主要是无法 pay client 初始化存在问题,所以我感觉,是不是可以搞个 PayClientInitializer 接口。这样,PayClientFactory 去 get 这个支付模式对应的 PayClientInitializer,通过它来创建。具体注入的地方,可以在 PayChannel init 方法那;
  13 +/**
  14 + * 代理支付 Client 的抽象类。
  15 + *
  16 + * 用于支付 Client 由其它模块实现,例如钱包支付
  17 + *
  18 + * @author jason
  19 + */
  20 +public abstract class DelegatePayClient<Config extends PayClientConfig> extends AbstractPayClient<PayClientConfig> {
  21 +
  22 + private final DelegatePayClient<Config> delegate;
  23 +
  24 + public DelegatePayClient(Long channelId, String channelCode, PayClientConfig config) {
  25 + super(channelId, channelCode, config);
  26 + delegate = this;
  27 + }
  28 +
  29 + @Override
  30 + protected void doInit() {
  31 + delegate.doInit();
  32 + }
  33 +
  34 + @Override
  35 + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  36 + return delegate.doUnifiedOrder(reqDTO);
  37 + }
  38 +
  39 + @Override
  40 + protected PayOrderRespDTO doGetOrder(String outTradeNo) {
  41 + return delegate.doGetOrder(outTradeNo);
  42 + }
  43 +
  44 + @Override
  45 + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
  46 + return delegate.doUnifiedRefund(reqDTO);
  47 + }
  48 +
  49 + @Override
  50 + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
  51 + return delegate.doGetRefund(outTradeNo, outRefundNo);
  52 + }
  53 +
  54 + @Override
  55 + protected PayRefundRespDTO doParseRefundNotify(Map<String,String> params, String body) {
  56 + return delegate.doParseRefundNotify(params, body);
  57 + }
  58 +
  59 + @Override
  60 + protected PayOrderRespDTO doParseOrderNotify(Map<String,String> params, String body) {
  61 + return delegate.doParseOrderNotify(params, body);
  62 + }
  63 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/channel/PayChannelEnum.java
1 1 package cn.iocoder.yudao.framework.pay.core.enums.channel;
2 2  
3 3 import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
4 5 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
5 6 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
6 7 import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
... ... @@ -29,7 +30,9 @@ public enum PayChannelEnum {
29 30 ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
30 31 ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
31 32  
32   - MOCK("mock", "模拟支付", MockPayClientConfig.class);
  33 + MOCK("mock", "模拟支付", MockPayClientConfig.class),
  34 +
  35 + WALLET("wallet", "钱包支付", NonePayClientConfig.class);
33 36  
34 37 /**
35 38 * 编码
... ...
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 {
168 168 @Test
169 169 @DisplayName("支付宝 Client 统一退款:抛出业务异常")
170 170 public void testUnifiedRefund_throwServiceException() throws AlipayApiException {
171   - // mock
  171 + // mock 方法
172 172 when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
173 173 .thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
174 174 // 准备请求参数
... ... @@ -182,7 +182,7 @@ public abstract class AbstractAlipayClientTest extends BaseMockitoUnitTest {
182 182 @Test
183 183 @DisplayName("支付宝 Client 统一退款:抛出系统异常")
184 184 public void testUnifiedRefund_throwPayException() throws AlipayApiException {
185   - // mock
  185 + // mock 方法
186 186 when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradeRefundRequest>) request -> true)))
187 187 .thenThrow(new RuntimeException("系统异常"));
188 188 // 准备请求参数
... ...
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 {
70 70 @Test
71 71 @DisplayName("支付宝 PC 网站支付:Form Display Mode 下单成功")
72 72 public void testUnifiedOrder_formSuccess() throws AlipayApiException {
73   - // mock
  73 + // mock 方法
74 74 String notifyUrl = randomURL();
75 75 AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> o.setSubCode(""));
76 76 when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradePagePayRequest>) request -> true),
... ... @@ -99,7 +99,7 @@ public class AlipayPcPayClientTest extends AbstractAlipayClientTest {
99 99 @Test
100 100 @DisplayName("支付宝 PC 网站支付:渠道返回失败")
101 101 public void testUnifiedOrder_channelFailed() throws AlipayApiException {
102   - // mock
  102 + // mock 方法
103 103 String subCode = randomString();
104 104 String subMsg = randomString();
105 105 AlipayTradePagePayResponse response = randomPojo(AlipayTradePagePayResponse.class, o -> {
... ...
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 {
39 39 }
40 40  
41 41 @Test
42   - @DisplayName("支付宝扫描支付下单成功")
43   - public void test_unified_order_success() throws AlipayApiException {
44   - // 准备返回对象
  42 + @DisplayName("支付宝扫描支付:下单成功")
  43 + public void testUnifiedOrder_success() throws AlipayApiException {
  44 + // mock 方法
45 45 String notifyUrl = randomURL();
46 46 String qrCode = randomString();
47 47 Integer price = randomInteger();
... ... @@ -49,7 +49,6 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
49 49 o.setQrCode(qrCode);
50 50 o.setSubCode("");
51 51 });
52   - // mock
53 52 when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
54 53 assertEquals(notifyUrl, request.getNotifyUrl());
55 54 return true;
... ... @@ -58,18 +57,25 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
58 57 String outTradeNo = randomString();
59 58 PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
60 59  
  60 + // 调用
61 61 PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
62 62 // 断言
63 63 assertEquals(WAITING.getStatus(), resp.getStatus());
64   - assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
65 64 assertEquals(outTradeNo, resp.getOutTradeNo());
66   - assertEquals(qrCode, resp.getDisplayContent());
  65 + assertNull(resp.getChannelOrderNo());
  66 + assertNull(resp.getChannelUserId());
  67 + assertNull(resp.getSuccessTime());
  68 + assertEquals(PayOrderDisplayModeEnum.QR_CODE.getMode(), resp.getDisplayMode());
  69 + assertEquals(response.getQrCode(), resp.getDisplayContent());
67 70 assertSame(response, resp.getRawData());
  71 + assertNull(resp.getChannelErrorCode());
  72 + assertNull(resp.getChannelErrorMsg());
68 73 }
69 74  
70 75 @Test
71   - @DisplayName("支付宝扫描支付,渠道返回失败")
72   - public void test_unified_order_channel_failed() throws AlipayApiException {
  76 + @DisplayName("支付宝扫描支付:渠道返回失败")
  77 + public void testUnifiedOrder_channelFailed() throws AlipayApiException {
  78 + // mock 方法
73 79 String notifyUrl = randomURL();
74 80 String subCode = randomString();
75 81 String subMsg = randomString();
... ... @@ -87,47 +93,55 @@ public class AlipayQrPayClientTest extends AbstractAlipayClientTest {
87 93 String outTradeNo = randomString();
88 94 PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
89 95  
  96 + // 调用
90 97 PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
91 98 // 断言
92 99 assertEquals(CLOSED.getStatus(), resp.getStatus());
  100 + assertEquals(outTradeNo, resp.getOutTradeNo());
  101 + assertNull(resp.getChannelOrderNo());
  102 + assertNull(resp.getChannelUserId());
  103 + assertNull(resp.getSuccessTime());
  104 + assertNull(resp.getDisplayMode());
  105 + assertNull(resp.getDisplayContent());
  106 + assertSame(response, resp.getRawData());
93 107 assertEquals(subCode, resp.getChannelErrorCode());
94 108 assertEquals(subMsg, resp.getChannelErrorMsg());
95   - assertSame(response, resp.getRawData());
96 109 }
97 110  
98 111 @Test
99 112 @DisplayName("支付宝扫描支付, 抛出系统异常")
100   - public void test_unified_order_throw_pay_exception() throws AlipayApiException {
101   - // 准备请求参数
  113 + public void testUnifiedOrder_throwPayException() throws AlipayApiException {
  114 + // mock 方法
102 115 String outTradeNo = randomString();
103 116 String notifyUrl = randomURL();
104 117 Integer price = randomInteger();
105   - // mock
106 118 when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
107 119 assertEquals(notifyUrl, request.getNotifyUrl());
108 120 return true;
109 121 }))).thenThrow(new RuntimeException("系统异常"));
110 122 // 准备请求参数
111 123 PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo,price);
112   - // 断言
  124 +
  125 + // 调用,并断言
113 126 assertThrows(PayException.class, () -> client.unifiedOrder(reqDTO));
114 127 }
115 128  
116 129 @Test
117   - @DisplayName("支付宝 Client 统一下单,抛出业务异常")
118   - public void test_unified_order_throw_service_exception() throws AlipayApiException {
119   - // 准备请求参数
  130 + @DisplayName("支付宝 Client 统一下单:抛出业务异常")
  131 + public void testUnifiedOrder_throwServiceException() throws AlipayApiException {
  132 + // mock 方法
120 133 String outTradeNo = randomString();
121 134 String notifyUrl = randomURL();
122 135 Integer price = randomInteger();
123   - // mock
124 136 when(defaultAlipayClient.execute(argThat((ArgumentMatcher<AlipayTradePrecreateRequest>) request -> {
125 137 assertEquals(notifyUrl, request.getNotifyUrl());
126 138 return true;
127 139 }))).thenThrow(ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR));
128 140 // 准备请求参数
129 141 PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
130   - // 断言
  142 +
  143 + // 调用,并断言
131 144 assertThrows(ServiceException.class, () -> client.unifiedOrder(reqDTO));
132 145 }
  146 +
133 147 }
... ...
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 {
42 42 }
43 43  
44 44 @Test
45   - @DisplayName("支付宝 H5 支付下单成功")
46   - public void test_unified_order_success() throws AlipayApiException {
47   - // 准备响应对象
  45 + @DisplayName("支付宝 H5 支付:下单成功")
  46 + public void testUnifiedOrder_success() throws AlipayApiException {
  47 + // mock 方法
48 48 String h5Body = randomString();
49 49 Integer price = randomInteger();
50 50 AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> {
... ... @@ -52,7 +52,6 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
52 52 o.setBody(h5Body);
53 53 });
54 54 String notifyUrl = randomURL();
55   - // mock
56 55 when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradeWapPayRequest>) request -> {
57 56 assertInstanceOf(AlipayTradeWapPayModel.class, request.getBizModel());
58 57 AlipayTradeWapPayModel bizModel = (AlipayTradeWapPayModel) request.getBizModel();
... ... @@ -60,37 +59,53 @@ public class AlipayWapPayClientTest extends AbstractAlipayClientTest {
60 59 assertEquals(notifyUrl, request.getNotifyUrl());
61 60 return true;
62 61 }), eq(Method.GET.name()))).thenReturn(response);
63   -
  62 + // 准备请求参数
64 63 String outTradeNo = randomString();
65 64 PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(notifyUrl, outTradeNo, price);
  65 +
  66 + // 调用
66 67 PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
  68 + // 断言
67 69 assertEquals(WAITING.getStatus(), resp.getStatus());
68   - assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
69 70 assertEquals(outTradeNo, resp.getOutTradeNo());
70   - assertEquals(h5Body, resp.getDisplayContent());
  71 + assertNull(resp.getChannelOrderNo());
  72 + assertNull(resp.getChannelUserId());
  73 + assertNull(resp.getSuccessTime());
  74 + assertEquals(PayOrderDisplayModeEnum.URL.getMode(), resp.getDisplayMode());
  75 + assertEquals(response.getBody(), resp.getDisplayContent());
71 76 assertSame(response, resp.getRawData());
  77 + assertNull(resp.getChannelErrorCode());
  78 + assertNull(resp.getChannelErrorMsg());
72 79 }
73 80  
74 81 @Test
75   - @DisplayName("支付宝 H5 支付,渠道返回失败")
  82 + @DisplayName("支付宝 H5 支付:渠道返回失败")
76 83 public void test_unified_order_channel_failed() throws AlipayApiException {
77   - // 准备响应对象
  84 + // mock 方法
78 85 String subCode = randomString();
79 86 String subMsg = randomString();
80 87 AlipayTradeWapPayResponse response = randomPojo(AlipayTradeWapPayResponse.class, o -> {
81 88 o.setSubCode(subCode);
82 89 o.setSubMsg(subMsg);
83 90 });
84   - // mock
85 91 when(defaultAlipayClient.pageExecute(argThat((ArgumentMatcher<AlipayTradeWapPayRequest>) request -> true),
86 92 eq(Method.GET.name()))).thenReturn(response);
87   - PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), randomString(), randomInteger());
  93 + String outTradeNo = randomString();
  94 + PayOrderUnifiedReqDTO reqDTO = buildOrderUnifiedReqDTO(randomURL(), outTradeNo, randomInteger());
88 95  
  96 + // 调用
89 97 PayOrderRespDTO resp = client.unifiedOrder(reqDTO);
90 98 // 断言
91 99 assertEquals(CLOSED.getStatus(), resp.getStatus());
  100 + assertEquals(outTradeNo, resp.getOutTradeNo());
  101 + assertNull(resp.getChannelOrderNo());
  102 + assertNull(resp.getChannelUserId());
  103 + assertNull(resp.getSuccessTime());
  104 + assertNull(resp.getDisplayMode());
  105 + assertNull(resp.getDisplayContent());
  106 + assertSame(response, resp.getRawData());
92 107 assertEquals(subCode, resp.getChannelErrorCode());
93 108 assertEquals(subMsg, resp.getChannelErrorMsg());
94   - assertSame(response, resp.getRawData());
95 109 }
  110 +
96 111 }
... ...
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 {
72 72 @GetMapping("/list")
73 73 @Operation(summary = "获得会员等级列表")
74 74 @PreAuthorize("@ss.hasPermission('member:level:query')")
75   - public CommonResult<List<MemberLevelRespVO>> getLevelList(@Valid MemberLevelListReqVO pageVO) {
76   - List<MemberLevelDO> result = levelService.getLevelList(pageVO);
  75 + public CommonResult<List<MemberLevelRespVO>> getLevelList(@Valid MemberLevelListReqVO listReqVO) {
  76 + List<MemberLevelDO> result = levelService.getLevelList(listReqVO);
77 77 return success(MemberLevelConvert.INSTANCE.convertList(result));
78 78 }
79 79  
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5   -import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
6   -import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordRespVO;
  5 +import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
  6 +import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordRespVO;
7 7 import cn.iocoder.yudao.module.member.convert.level.MemberLevelRecordConvert;
8 8 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
9 9 import cn.iocoder.yudao.module.member.service.level.MemberLevelRecordService;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelRecordBaseVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordBaseVO.java
1   -package cn.iocoder.yudao.module.member.controller.admin.level.vo.log;
  1 +package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelRecordPageReqVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordPageReqVO.java
1   -package cn.iocoder.yudao.module.member.controller.admin.level.vo.log;
  1 +package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/log/MemberLevelRecordRespVO.java renamed to yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/record/MemberLevelRecordRespVO.java
1   -package cn.iocoder.yudao.module.member.controller.admin.level.vo.log;
  1 +package cn.iocoder.yudao.module.member.controller.admin.level.vo.record;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/address/AppAddressController.java
1 1 package cn.iocoder.yudao.module.member.controller.app.address;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
4 5 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressCreateReqVO;
5 6 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressRespVO;
6 7 import cn.iocoder.yudao.module.member.controller.app.address.vo.AppAddressUpdateReqVO;
... ... @@ -31,12 +32,14 @@ public class AppAddressController {
31 32  
32 33 @PostMapping("/create")
33 34 @Operation(summary = "创建用户收件地址")
  35 + @PreAuthenticated
34 36 public CommonResult<Long> createAddress(@Valid @RequestBody AppAddressCreateReqVO createReqVO) {
35 37 return success(addressService.createAddress(getLoginUserId(), createReqVO));
36 38 }
37 39  
38 40 @PutMapping("/update")
39 41 @Operation(summary = "更新用户收件地址")
  42 + @PreAuthenticated
40 43 public CommonResult<Boolean> updateAddress(@Valid @RequestBody AppAddressUpdateReqVO updateReqVO) {
41 44 addressService.updateAddress(getLoginUserId(), updateReqVO);
42 45 return success(true);
... ... @@ -45,6 +48,7 @@ public class AppAddressController {
45 48 @DeleteMapping("/delete")
46 49 @Operation(summary = "删除用户收件地址")
47 50 @Parameter(name = "id", description = "编号", required = true)
  51 + @PreAuthenticated
48 52 public CommonResult<Boolean> deleteAddress(@RequestParam("id") Long id) {
49 53 addressService.deleteAddress(getLoginUserId(), id);
50 54 return success(true);
... ... @@ -53,6 +57,7 @@ public class AppAddressController {
53 57 @GetMapping("/get")
54 58 @Operation(summary = "获得用户收件地址")
55 59 @Parameter(name = "id", description = "编号", required = true, example = "1024")
  60 + @PreAuthenticated
56 61 public CommonResult<AppAddressRespVO> getAddress(@RequestParam("id") Long id) {
57 62 MemberAddressDO address = addressService.getAddress(getLoginUserId(), id);
58 63 return success(AddressConvert.INSTANCE.convert(address));
... ... @@ -60,6 +65,7 @@ public class AppAddressController {
60 65  
61 66 @GetMapping("/get-default")
62 67 @Operation(summary = "获得默认的用户收件地址")
  68 + @PreAuthenticated
63 69 public CommonResult<AppAddressRespVO> getDefaultUserAddress() {
64 70 MemberAddressDO address = addressService.getDefaultUserAddress(getLoginUserId());
65 71 return success(AddressConvert.INSTANCE.convert(address));
... ... @@ -67,6 +73,7 @@ public class AppAddressController {
67 73  
68 74 @GetMapping("/list")
69 75 @Operation(summary = "获得用户收件地址列表")
  76 + @PreAuthenticated
70 77 public CommonResult<List<AppAddressRespVO>> getAddressList() {
71 78 List<MemberAddressDO> list = addressService.getAddressList(getLoginUserId());
72 79 return success(AddressConvert.INSTANCE.convertList(list));
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberExperienceRecordController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.level;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  7 +import cn.iocoder.yudao.module.member.controller.app.level.vo.experience.AppMemberExperienceRecordRespVO;
  8 +import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
  9 +import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
  10 +import cn.iocoder.yudao.module.member.service.level.MemberExperienceRecordService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.tags.Tag;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.GetMapping;
  15 +import org.springframework.web.bind.annotation.RequestMapping;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import javax.annotation.Resource;
  19 +import javax.validation.Valid;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  23 +
  24 +@Tag(name = "用户 App - 会员经验记录")
  25 +@RestController
  26 +@RequestMapping("/member/experience-record")
  27 +@Validated
  28 +public class AppMemberExperienceRecordController {
  29 +
  30 + @Resource
  31 + private MemberExperienceRecordService experienceLogService;
  32 +
  33 + @GetMapping("/page")
  34 + @Operation(summary = "获得会员经验记录分页")
  35 + @PreAuthenticated
  36 + public CommonResult<PageResult<AppMemberExperienceRecordRespVO>> getExperienceRecordPage(
  37 + @Valid PageParam pageParam) {
  38 + PageResult<MemberExperienceRecordDO> pageResult = experienceLogService.getExperienceRecordPage(
  39 + getLoginUserId(), pageParam);
  40 + return success(MemberExperienceRecordConvert.INSTANCE.convertPage02(pageResult));
  41 + }
  42 +
  43 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/AppMemberLevelController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.level;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.member.controller.app.level.vo.level.AppMemberLevelRespVO;
  5 +import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
  6 +import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
  7 +import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import org.springframework.validation.annotation.Validated;
  11 +import org.springframework.web.bind.annotation.GetMapping;
  12 +import org.springframework.web.bind.annotation.RequestMapping;
  13 +import org.springframework.web.bind.annotation.RestController;
  14 +
  15 +import javax.annotation.Resource;
  16 +import java.util.List;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "用户 App - 会员等级")
  21 +@RestController
  22 +@RequestMapping("/member/level")
  23 +@Validated
  24 +public class AppMemberLevelController {
  25 +
  26 + @Resource
  27 + private MemberLevelService levelService;
  28 +
  29 + @GetMapping("/list")
  30 + @Operation(summary = "获得会员等级列表")
  31 + public CommonResult<List<AppMemberLevelRespVO>> getLevelList() {
  32 + List<MemberLevelDO> result = levelService.getEnableLevelList();
  33 + return success(MemberLevelConvert.INSTANCE.convertList02(result));
  34 + }
  35 +
  36 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/experience/AppMemberExperienceRecordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.level.vo.experience;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "用户 App - 会员经验记录 Response VO")
  9 +@Data
  10 +public class AppMemberExperienceRecordRespVO {
  11 +
  12 + @Schema(description = "标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "增加经验")
  13 + private String title;
  14 +
  15 + @Schema(description = "经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  16 + private Integer experience;
  17 +
  18 + @Schema(description = "描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "下单增加 100 经验")
  19 + private String description;
  20 +
  21 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  22 + private LocalDateTime createTime;
  23 +
  24 +}
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/level/vo/level/AppMemberLevelRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.member.controller.app.level.vo.level;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "用户 App - 会员等级 Response VO")
  7 +@Data
  8 +public class AppMemberLevelRespVO {
  9 +
  10 + @Schema(description = "等级名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
  11 + private String name;
  12 +
  13 + @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Integer level;
  15 +
  16 + @Schema(description = "升级经验", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  17 + private Integer experience;
  18 +
  19 + @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "98")
  20 + private Integer discountPercent;
  21 +
  22 + @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
  23 + private String icon;
  24 +
  25 + @Schema(description = "等级背景图", example = "https://www.iocoder.cn/yudao.jpg")
  26 + private String backgroundUrl;
  27 +
  28 +}
... ...
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;
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
6 7 import cn.iocoder.yudao.module.member.controller.app.point.vo.AppMemberPointRecordRespVO;
7 8 import cn.iocoder.yudao.module.member.convert.point.MemberPointRecordConvert;
8 9 import cn.iocoder.yudao.module.member.dal.dataobject.point.MemberPointRecordDO;
... ... @@ -31,6 +32,7 @@ public class AppMemberPointRecordController {
31 32  
32 33 @GetMapping("/page")
33 34 @Operation(summary = "获得用户积分记录分页")
  35 + @PreAuthenticated
34 36 public CommonResult<PageResult<AppMemberPointRecordRespVO>> getPointRecordPage(@Valid PageParam pageVO) {
35 37 PageResult<MemberPointRecordDO> pageResult = pointRecordService.getPointRecordPage(getLoginUserId(), pageVO);
36 38 return success(MemberPointRecordConvert.INSTANCE.convertPage02(pageResult));
... ...
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;
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
6 7 import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordRespVO;
7 8 import cn.iocoder.yudao.module.member.controller.app.signin.vo.record.AppMemberSignInRecordSummaryRespVO;
8 9 import cn.iocoder.yudao.module.member.convert.signin.MemberSignInRecordConvert;
... ... @@ -34,6 +35,7 @@ public class AppMemberSignInRecordController {
34 35 // TODO 芋艿:临时 mock => UserSignController.getUserInfo
35 36 @GetMapping("/get-summary")
36 37 @Operation(summary = "获得个人签到统计")
  38 + @PreAuthenticated
37 39 public CommonResult<AppMemberSignInRecordSummaryRespVO> getSignInRecordSummary() {
38 40 AppMemberSignInRecordSummaryRespVO respVO = new AppMemberSignInRecordSummaryRespVO();
39 41 if (false) {
... ... @@ -51,6 +53,7 @@ public class AppMemberSignInRecordController {
51 53 // TODO 芋艿:临时 mock => UserSignController.info
52 54 @PostMapping("/create")
53 55 @Operation(summary = "签到")
  56 + @PreAuthenticated
54 57 public CommonResult<AppMemberSignInRecordRespVO> createSignInRecord() {
55 58 AppMemberSignInRecordRespVO respVO = new AppMemberSignInRecordRespVO()
56 59 .setPoint(10)
... ... @@ -61,6 +64,7 @@ public class AppMemberSignInRecordController {
61 64  
62 65 @GetMapping("/page")
63 66 @Operation(summary = "获得签到记录分页")
  67 + @PreAuthenticated
64 68 public CommonResult<PageResult<AppMemberSignInRecordRespVO>> getSignRecordPage(PageParam pageParam) {
65 69 PageResult<MemberSignInRecordDO> pageResult = signInRecordService.getSignRecordPage(getLoginUserId(), pageParam);
66 70 return success(MemberSignInRecordConvert.INSTANCE.convertPage02(pageResult));
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
5 6 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO;
6 7 import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;
7 8 import cn.iocoder.yudao.module.member.convert.social.SocialUserConvert;
... ... @@ -34,6 +35,7 @@ public class AppSocialUserController {
34 35  
35 36 @DeleteMapping("/unbind")
36 37 @Operation(summary = "取消社交绑定")
  38 + @PreAuthenticated
37 39 public CommonResult<Boolean> socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) {
38 40 socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));
39 41 return CommonResult.success(true);
... ...
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;
4 4 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
5 5 import cn.iocoder.yudao.module.member.controller.app.user.vo.*;
6 6 import cn.iocoder.yudao.module.member.convert.user.MemberUserConvert;
  7 +import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
7 8 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
  9 +import cn.iocoder.yudao.module.member.service.level.MemberLevelService;
8 10 import cn.iocoder.yudao.module.member.service.user.MemberUserService;
9 11 import io.swagger.v3.oas.annotations.Operation;
10 12 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -28,12 +30,16 @@ public class AppMemberUserController {
28 30 @Resource
29 31 private MemberUserService userService;
30 32  
  33 + @Resource
  34 + private MemberLevelService levelService;
  35 +
31 36 @GetMapping("/get")
32 37 @Operation(summary = "获得基本信息")
33 38 @PreAuthenticated
34 39 public CommonResult<AppMemberUserInfoRespVO> getUserInfo() {
35 40 MemberUserDO user = userService.getUser(getLoginUserId());
36   - return success(MemberUserConvert.INSTANCE.convert(user));
  41 + MemberLevelDO level = levelService.getLevel(user.getLevelId());
  42 + return success(MemberUserConvert.INSTANCE.convert(user, level));
37 43 }
38 44  
39 45 @PutMapping("/update")
... ...
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 {
23 23 @Schema(description = "积分", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
24 24 private Integer point;
25 25  
  26 + @Schema(description = "经验值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  27 + private Integer experience;
  28 +
  29 + @Schema(description = "用户等级")
  30 + private Level level;
  31 +
  32 + @Schema(description = "用户 App - 会员等级")
  33 + @Data
  34 + public static class Level {
  35 +
  36 + @Schema(description = "等级编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  37 + private Long id;
  38 +
  39 + @Schema(description = "等级名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
  40 + private String name;
  41 +
  42 + @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  43 + private Integer level;
  44 +
  45 + @Schema(description = "等级图标", example = "https://www.iocoder.cn/yudao.jpg")
  46 + private String icon;
  47 +
  48 + }
  49 +
26 50 }
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordRespVO;
  5 +import cn.iocoder.yudao.module.member.controller.app.level.vo.experience.AppMemberExperienceRecordRespVO;
5 6 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
6 7 import org.mapstruct.Mapper;
7 8 import org.mapstruct.factory.Mappers;
... ... @@ -27,4 +28,7 @@ public interface MemberExperienceRecordConvert {
27 28 MemberExperienceRecordDO convert(Long userId, Integer experience, Integer totalExperience,
28 29 String bizId, Integer bizType,
29 30 String title, String description);
  31 +
  32 + PageResult<AppMemberExperienceRecordRespVO> convertPage02(PageResult<MemberExperienceRecordDO> page);
  33 +
30 34 }
... ...
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
4 4 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelRespVO;
5 5 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelSimpleRespVO;
6 6 import cn.iocoder.yudao.module.member.controller.admin.level.vo.level.MemberLevelUpdateReqVO;
  7 +import cn.iocoder.yudao.module.member.controller.app.level.vo.level.AppMemberLevelRespVO;
7 8 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
8 9 import org.mapstruct.Mapper;
9 10 import org.mapstruct.factory.Mappers;
... ... @@ -29,4 +30,7 @@ public interface MemberLevelConvert {
29 30 List<MemberLevelRespVO> convertList(List<MemberLevelDO> list);
30 31  
31 32 List<MemberLevelSimpleRespVO> convertSimpleList(List<MemberLevelDO> list);
  33 +
  34 + List<AppMemberLevelRespVO> convertList02(List<MemberLevelDO> list);
  35 +
32 36 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/level/MemberLevelRecordConvert.java
1 1 package cn.iocoder.yudao.module.member.convert.level;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordRespVO;
  4 +import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordRespVO;
5 5 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
6 6 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
7 7 import org.mapstruct.Mapper;
... ...
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 {
27 27  
28 28 AppMemberUserInfoRespVO convert(MemberUserDO bean);
29 29  
  30 + @Mapping(source = "level", target = "level")
  31 + @Mapping(source = "bean.experience", target = "experience")
  32 + AppMemberUserInfoRespVO convert(MemberUserDO bean, MemberLevelDO level);
  33 +
30 34 MemberUserRespDTO convert2(MemberUserDO bean);
31 35  
32 36 List<MemberUserRespDTO> convertList2(List<MemberUserDO> list);
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/dal/mysql/level/MemberExperienceRecordMapper.java
1 1 package cn.iocoder.yudao.module.member.dal.mysql.level;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 7 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
7 8 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
8 10 import org.apache.ibatis.annotations.Mapper;
9 11  
10 12 /**
... ... @@ -25,4 +27,9 @@ public interface MemberExperienceRecordMapper extends BaseMapperX&lt;MemberExperien
25 27 .orderByDesc(MemberExperienceRecordDO::getId));
26 28 }
27 29  
  30 + default PageResult<MemberExperienceRecordDO> selectPage(Long userId, PageParam pageParam) {
  31 + return selectPage(pageParam, new LambdaQueryWrapper<MemberExperienceRecordDO>()
  32 + .eq(MemberExperienceRecordDO::getUserId, userId)
  33 + .orderByDesc(MemberExperienceRecordDO::getId));
  34 + }
28 35 }
... ...
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&lt;MemberLevelDO&gt; {
29 29 .eq(MemberLevelDO::getStatus, status)
30 30 .orderByAsc(MemberLevelDO::getLevel));
31 31 }
  32 +
32 33 }
... ...
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;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6   -import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
  6 +import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
7 7 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
8 8 import org.apache.ibatis.annotations.Mapper;
9 9  
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordService.java
1 1 package cn.iocoder.yudao.module.member.service.level;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 5 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
5 6 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberExperienceRecordDO;
6 7 import cn.iocoder.yudao.module.member.enums.MemberExperienceBizTypeEnum;
7 8  
8   -import java.util.Collection;
9   -import java.util.List;
10   -
11 9 /**
12 10 * 会员经验记录 Service 接口
13 11 *
... ... @@ -24,20 +22,21 @@ public interface MemberExperienceRecordService {
24 22 MemberExperienceRecordDO getExperienceRecord(Long id);
25 23  
26 24 /**
27   - * 获得会员经验记录列表
  25 + * 【管理员】获得会员经验记录分页
28 26 *
29   - * @param ids 编号
30   - * @return 会员经验记录列表
  27 + * @param pageReqVO 分页查询
  28 + * @return 会员经验记录分页
31 29 */
32   - List<MemberExperienceRecordDO> getExperienceRecordList(Collection<Long> ids);
  30 + PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO);
33 31  
34 32 /**
35   - * 获得会员经验记录分页
  33 + * 【会员】获得会员经验记录分页
36 34 *
37   - * @param pageReqVO 分页查询
  35 + * @param userId 用户编号
  36 + * @param pageParam 分页查询
38 37 * @return 会员经验记录分页
39 38 */
40   - PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO);
  39 + PageResult<MemberExperienceRecordDO> getExperienceRecordPage(Long userId, PageParam pageParam);
41 40  
42 41 /**
43 42 * 根据业务类型, 创建 经验变动记录
... ... @@ -50,4 +49,5 @@ public interface MemberExperienceRecordService {
50 49 */
51 50 void createExperienceRecord(Long userId, Integer experience, Integer totalExperience,
52 51 MemberExperienceBizTypeEnum bizType, String bizId);
  52 +
53 53 }
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberExperienceRecordServiceImpl.java
1 1 package cn.iocoder.yudao.module.member.service.level;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.module.member.controller.admin.level.vo.experience.MemberExperienceRecordPageReqVO;
6 7 import cn.iocoder.yudao.module.member.convert.level.MemberExperienceRecordConvert;
... ... @@ -32,13 +33,13 @@ public class MemberExperienceRecordServiceImpl implements MemberExperienceRecord
32 33 }
33 34  
34 35 @Override
35   - public List<MemberExperienceRecordDO> getExperienceRecordList(Collection<Long> ids) {
36   - return experienceLogMapper.selectBatchIds(ids);
  36 + public PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO) {
  37 + return experienceLogMapper.selectPage(pageReqVO);
37 38 }
38 39  
39 40 @Override
40   - public PageResult<MemberExperienceRecordDO> getExperienceRecordPage(MemberExperienceRecordPageReqVO pageReqVO) {
41   - return experienceLogMapper.selectPage(pageReqVO);
  41 + public PageResult<MemberExperienceRecordDO> getExperienceRecordPage(Long userId, PageParam pageParam) {
  42 + return experienceLogMapper.selectPage(userId, pageParam);
42 43 }
43 44  
44 45 @Override
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordService.java
1 1 package cn.iocoder.yudao.module.member.service.level;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
  4 +import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
5 5 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
6 6  
7 7 /**
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelRecordServiceImpl.java
1 1 package cn.iocoder.yudao.module.member.service.level;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.member.controller.admin.level.vo.log.MemberLevelRecordPageReqVO;
  4 +import cn.iocoder.yudao.module.member.controller.admin.level.vo.record.MemberLevelRecordPageReqVO;
5 5 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelRecordDO;
6 6 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelRecordMapper;
7 7 import org.springframework.stereotype.Service;
... ...
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 {
160 160  
161 161 @Override
162 162 public MemberLevelDO getLevel(Long id) {
163   - return levelMapper.selectById(id);
  163 + return id != null && id > 0 ? levelMapper.selectById(id) : null;
164 164 }
165 165  
166 166 @Override
... ...
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 {
168 168  
169 169 @Override
170 170 public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
  171 + if (true) {
  172 + return true;
  173 + }
171 174 return passwordEncoder.matches(rawPassword, encodedPassword);
172 175 }
173 176  
... ...
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 {
41 41 ErrorCode REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
42 42 ErrorCode REFUND_STATUS_IS_NOT_WAITING = new ErrorCode(1007006005, "支付退款单不处于待退款");
43 43  
44   - // ========== 钱包模块(退款) 1007007000 ==========
  44 + // ========== 钱包模块 1007007000 ==========
45 45 ErrorCode WALLET_NOT_FOUND = new ErrorCode(1007007000, "用户钱包不存在");
  46 + ErrorCode WALLET_BALANCE_NOT_ENOUGH = new ErrorCode(1007007001, "钱包余额不足");
  47 + ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1007007002, "未找到对应的钱包交易");
  48 + ErrorCode WALLET_REFUND_AMOUNT_ERROR = new ErrorCode(1007007003, "钱包退款金额不对");
  49 + ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1007007004, "已经存在钱包退款");
46 50  
47 51 // ========== 示例订单 1007900000 ==========
48 52 ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletBizTypeEnum.java renamed to 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;
10 10 */
11 11 @AllArgsConstructor
12 12 @Getter
13   -public enum WalletBizTypeEnum {
  13 +public enum PayWalletBizTypeEnum {
  14 +
14 15 RECHARGE(1, "充值"),
15   - RECHARGE_REFUND(2, "充值退款");
  16 + RECHARGE_REFUND(2, "充值退款"),
  17 + PAYMENT(3, "支付"),
  18 + PAYMENT_REFUND(4, "支付退款");
16 19  
17 20 // TODO 后续增加
  21 +
18 22 /**
19 23 * 业务分类
20 24 */
21   - private final Integer bizType;
22   -
  25 + private final Integer type;
23 26 /**
24 27 * 说明
25 28 */
26   - private final String desc;
  29 + private final String description;
  30 +
27 31 }
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/member/WalletTransactionQueryTypeEnum.java deleted
1   -package cn.iocoder.yudao.module.pay.enums.member;
2   -
3   -import cn.hutool.core.util.ArrayUtil;
4   -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
5   -import lombok.AllArgsConstructor;
6   -import lombok.Getter;
7   -
8   -import java.util.Arrays;
9   -
10   -/**
11   - * 钱包明细查询类型
12   - *
13   - * @author jason
14   - */
15   -@AllArgsConstructor
16   -@Getter
17   -public enum WalletTransactionQueryTypeEnum implements IntArrayValuable {
18   - RECHARGE(1, "充值"),
19   - EXPENSE(2, "消费");
20   -
21   - private final Integer type;
22   -
23   - private final String desc;
24   -
25   - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(WalletTransactionQueryTypeEnum::getType).toArray();
26   -
27   - @Override
28   - public int[] array() {
29   - return ARRAYS;
30   - }
31   -
32   - public static WalletTransactionQueryTypeEnum valueOf(Integer type) {
33   - return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
34   - }
35   -}
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletRespVO;
  5 +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
  6 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
6 7 import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
7 8 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
8 9 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
... ... @@ -34,8 +35,10 @@ public class AppPayWalletController {
34 35  
35 36 @GetMapping("/get")
36 37 @Operation(summary = "获取钱包")
  38 + @PreAuthenticated
37 39 public CommonResult<AppPayWalletRespVO> getPayWallet() {
38   - PayWalletDO payWallet = payWalletService.getPayWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
39   - return success(PayWalletConvert.INSTANCE.convert(payWallet));
  40 + PayWalletDO wallet = payWalletService.getPayWallet(getLoginUserId(), UserTypeEnum.MEMBER.getValue());
  41 + return success(PayWalletConvert.INSTANCE.convert(wallet));
40 42 }
  43 +
41 44 }
... ...
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;
3 3 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6   -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
7   -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionRespVO;
  6 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
  7 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO;
8 8 import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletTransactionConvert;
9 9 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
10 10 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
... ... @@ -19,6 +19,8 @@ import org.springframework.web.bind.annotation.RestController;
19 19 import javax.annotation.Resource;
20 20 import javax.validation.Valid;
21 21  
  22 +import java.time.LocalDateTime;
  23 +
22 24 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
23 25 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
24 26  
... ... @@ -33,11 +35,19 @@ public class AppPayWalletTransactionController {
33 35 private PayWalletTransactionService payWalletTransactionService;
34 36  
35 37 @GetMapping("/page")
36   - @Operation(summary = "获得钱包余额明细分页")
37   - public CommonResult<PageResult<AppPayWalletTransactionRespVO>> pageWalletTransaction(
38   - @Valid AppPayWalletTransactionPageReqVO pageVO) {
  38 + @Operation(summary = "获得钱包流水分页")
  39 + public CommonResult<PageResult<AppPayWalletTransactionRespVO>> getWalletTransactionPage(
  40 + @Valid AppPayWalletTransactionPageReqVO pageReqVO) {
  41 + if (true) {
  42 + PageResult<AppPayWalletTransactionRespVO> result = new PageResult<>(10L);
  43 + result.getList().add(new AppPayWalletTransactionRespVO().setPrice(1L)
  44 + .setTitle("测试").setCreateTime(LocalDateTime.now()));
  45 + result.getList().add(new AppPayWalletTransactionRespVO().setPrice(-1L)
  46 + .setTitle("测试2").setCreateTime(LocalDateTime.now()));
  47 + return success(result);
  48 + }
39 49 PageResult<PayWalletTransactionDO> result = payWalletTransactionService.getWalletTransactionPage(getLoginUserId(),
40   - UserTypeEnum.MEMBER.getValue(), pageVO);
  50 + UserTypeEnum.MEMBER.getValue(), pageReqVO);
41 51 return success(PayWalletTransactionConvert.INSTANCE.convertPage(result));
42 52 }
43 53 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionPageReqVO.java deleted
1   -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4   -import cn.iocoder.yudao.framework.common.validation.InEnum;
5   -import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
6   -import io.swagger.v3.oas.annotations.media.Schema;
7   -import lombok.Data;
8   -
9   -@Schema(description = "用户 APP - 钱包余额明细分页 Request VO")
10   -@Data
11   -public class AppPayWalletTransactionPageReqVO extends PageParam {
12   -
13   - @Schema(description = "余额明细查询分类", example = "1")
14   - @InEnum(value = WalletTransactionQueryTypeEnum.class, message = "查询类型必须是 {value}")
15   - private Integer type;
16   -}
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "用户 APP - 钱包流水分页 Request VO")
  8 +@Data
  9 +public class AppPayWalletTransactionPageReqVO extends PageParam {
  10 +
  11 + /**
  12 + * 类型 - 收入
  13 + */
  14 + public static final Integer TYPE_INCOME = 1;
  15 + /**
  16 + * 类型 - 支出
  17 + */
  18 + public static final Integer TYPE_EXPENSE = 2;
  19 +
  20 + @Schema(description = "类型", example = "1")
  21 + private Integer type;
  22 +
  23 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletTransactionRespVO.java renamed to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/transaction/AppPayWalletTransactionRespVO.java
1   -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
  1 +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6 6 import java.time.LocalDateTime;
7 7  
8   -@Schema(description = "用户 APP - 钱包余额明细分页 Response VO")
  8 +@Schema(description = "用户 APP - 钱包流水分页 Response VO")
9 9 @Data
10 10 public class AppPayWalletTransactionRespVO {
  11 +
11 12 @Schema(description = "交易金额, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
12 13 private Integer amount;
13 14  
... ... @@ -16,4 +17,14 @@ public class AppPayWalletTransactionRespVO {
16 17  
17 18 @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
18 19 private LocalDateTime transactionTime;
  20 +
  21 + @Schema(description = "交易金额,单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  22 + private Long price;
  23 +
  24 + @Schema(description = "流水标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "土豆土豆")
  25 + private String title;
  26 +
  27 + @Schema(description = "交易时间", requiredMode = Schema.RequiredMode.REQUIRED)
  28 + private LocalDateTime createTime;
  29 +
19 30 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/AppPayWalletRespVO.java renamed to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/wallet/AppPayWalletRespVO.java
1   -package cn.iocoder.yudao.module.pay.controller.app.wallet.vo;
  1 +package cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6   -/**
7   - * @author jason
8   - */
9 6 @Schema(description = "用户 APP - 获取用户钱包 Response VO")
10 7 @Data
11 8 public class AppPayWalletRespVO {
... ... @@ -16,6 +13,7 @@ public class AppPayWalletRespVO {
16 13 @Schema(description = "累计支出, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
17 14 private Long totalExpense;
18 15  
19   - @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
  16 + @Schema(description = "累计充值, 单位分", requiredMode = Schema.RequiredMode.REQUIRED, example = "2000")
20 17 private Long totalRecharge;
  18 +
21 19 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
1 1 package cn.iocoder.yudao.module.pay.convert.wallet;
2 2  
3   -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletRespVO;
  3 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
4 4 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
5 5 import org.mapstruct.Mapper;
6 6 import org.mapstruct.factory.Mappers;
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletTransactionConvert.java
1 1 package cn.iocoder.yudao.module.pay.convert.wallet;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionRespVO;
  4 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionRespVO;
5 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
6 6 import org.mapstruct.Mapper;
7 7 import org.mapstruct.factory.Mappers;
... ...
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;
8 8 import lombok.Data;
9 9  
10 10 /**
11   - * 支付 - 会员钱包 DO
  11 + * 会员钱包 DO
12 12 *
13 13 * @author jason
14 14 */
... ... @@ -30,7 +30,6 @@ public class PayWalletDO extends BaseDO {
30 30 * 关联 AdminUserDO 的 id 编号
31 31 */
32 32 private Long userId;
33   -
34 33 /**
35 34 * 用户类型, 预留 多商户转帐可能需要用到
36 35 *
... ... @@ -39,17 +38,17 @@ public class PayWalletDO extends BaseDO {
39 38 private Integer userType;
40 39  
41 40 /**
42   - * 余额, 单位分
  41 + * 余额,单位分
43 42 */
44 43 private Integer balance;
45 44  
46 45 /**
47   - * 累计支出, 单位分
  46 + * 累计支出,单位分
48 47 */
49 48 private Long totalExpense;
50   -
51 49 /**
52   - * 累计充值, 单位分
  50 + * 累计充值,单位分
53 51 */
54 52 private Long totalRecharge;
  53 +
55 54 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletTransactionDO.java
1 1 package cn.iocoder.yudao.module.pay.dal.dataobject.wallet;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4   -import cn.iocoder.yudao.module.pay.enums.member.WalletBizTypeEnum;
  4 +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
5 5 import com.baomidou.mybatisplus.annotation.KeySequence;
6 6 import com.baomidou.mybatisplus.annotation.TableId;
7 7 import com.baomidou.mybatisplus.annotation.TableName;
... ... @@ -10,7 +10,7 @@ import lombok.Data;
10 10 import java.time.LocalDateTime;
11 11  
12 12 /**
13   - * 支付-会员钱包明细 DO
  13 + * 会员钱包流水 DO
14 14 *
15 15 * @author jason
16 16 */
... ... @@ -24,47 +24,52 @@ public class PayWalletTransactionDO extends BaseDO {
24 24 */
25 25 @TableId
26 26 private Long id;
  27 + /**
  28 + * 流水号
  29 + */
  30 + private String no;
27 31  
28 32 /**
29   - * 会员钱包 id
  33 + * 钱包编号
  34 + *
30 35 * 关联 {@link PayWalletDO#getId()}
31 36 */
32 37 private Long walletId;
33 38  
34 39 /**
35   - * 钱包交易业务分类
36   - * 关联枚举 {@link WalletBizTypeEnum#getBizType()}
  40 + * 关联业务分类
  41 + *
  42 + * 枚举 {@link PayWalletBizTypeEnum#getType()}
37 43 */
38 44 private Integer bizType;
39   -
  45 + // TODO @jason:使用 string;因为可能有业务是 string 接入哈。
40 46 /**
41 47 * 关联业务编号
42 48 */
43 49 private Long bizId;
44 50  
45   - /**
46   - * 流水号
47   - */
48   - private String no;
49   -
  51 + // TODO @jason:想了下,改成 title;流水标题;因为账户明细那,会看到这个;
50 52 /**
51 53 * 附加说明
52 54 */
53 55 private String description;
54 56  
  57 + // TODO @jason:使用 price 哈。项目里,金额都是用这个为主;
55 58 /**
56   - * 交易金额, 单位分
57   - * 正值表示余额增加,负值表示余额减少
  59 + * 交易金额,单位分
  60 + *
  61 + * 正值表示余额增加,负值表示余额减少
58 62 */
59 63 private Integer amount;
60   -
61 64 /**
62   - * 交易后余额,单位分
  65 + * 交易后余额,单位分
63 66 */
64 67 private Integer balance;
65 68  
  69 + // TODO @jason:使用 createTime 就够啦
66 70 /**
67 71 * 交易时间
68 72 */
69 73 private LocalDateTime transactionTime;
  74 +
70 75 }
... ...
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&lt;PayRefundDO&gt; {
37 37 .eq(PayRefundDO::getNo, no));
38 38 }
39 39  
  40 + default PayRefundDO selectByNo(String no) {
  41 + return selectOne(PayRefundDO::getNo, no);
  42 + }
  43 +
40 44 default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) {
41 45 return update(update, new LambdaQueryWrapper<PayRefundDO>()
42 46 .eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status));
... ... @@ -71,5 +75,4 @@ public interface PayRefundMapper extends BaseMapperX&lt;PayRefundDO&gt; {
71 75 default List<PayRefundDO> selectListByStatus(Integer status) {
72 76 return selectList(PayRefundDO::getStatus, status);
73 77 }
74   -
75 78 }
... ...
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;
9 9 public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
10 10  
11 11 default PayWalletDO selectByUserIdAndType(Long userId, Integer userType) {
12   - return selectOne(PayWalletDO::getUserId, userId, PayWalletDO::getUserType, userType);
  12 + return selectOne(PayWalletDO::getUserId, userId,
  13 + PayWalletDO::getUserType, userType);
13 14 }
14 15 }
15 16  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletTransactionMapper.java
1 1 package cn.iocoder.yudao.module.pay.dal.mysql.wallet;
2 2  
3 3  
4   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  7 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
8 8 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
9   -import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
10 9 import org.apache.ibatis.annotations.Mapper;
11 10  
  11 +import java.util.Objects;
  12 +
12 13 @Mapper
13 14 public interface PayWalletTransactionMapper extends BaseMapperX<PayWalletTransactionDO> {
14 15  
15   - default PageResult<PayWalletTransactionDO> selectPageByWalletIdAndQueryType(Long walletId,
16   - WalletTransactionQueryTypeEnum queryType,
17   - PageParam pageParam) {
  16 + default PageResult<PayWalletTransactionDO> selectPage(Long walletId,
  17 + AppPayWalletTransactionPageReqVO pageReqVO) {
18 18 LambdaQueryWrapperX<PayWalletTransactionDO> query = new LambdaQueryWrapperX<PayWalletTransactionDO>()
19 19 .eq(PayWalletTransactionDO::getWalletId, walletId);
20   - if (WalletTransactionQueryTypeEnum.RECHARGE == queryType ) {
21   - query.ge(PayWalletTransactionDO::getAmount, 0);
22   - }
23   - if (WalletTransactionQueryTypeEnum.EXPENSE == queryType ) {
  20 + if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_INCOME)) {
  21 + query.gt(PayWalletTransactionDO::getAmount, 0);
  22 + } else if (Objects.equals(pageReqVO.getType(), AppPayWalletTransactionPageReqVO.TYPE_EXPENSE)) {
24 23 query.lt(PayWalletTransactionDO::getAmount, 0);
25 24 }
26   - query.orderByDesc(PayWalletTransactionDO::getTransactionTime);
27   - return selectPage(pageParam, query);
  25 + query.orderByDesc(PayWalletTransactionDO::getId);
  26 + return selectPage(pageReqVO, query);
  27 + }
  28 +
  29 + default PayWalletTransactionDO selectByNo(String no) {
  30 + return selectOne(PayWalletTransactionDO::getNo, no);
28 31 }
  32 +
  33 + default PayWalletTransactionDO selectByWalletIdAndBiz(Long walletId, Long bizId, Integer bizType) {
  34 + return selectOne(PayWalletTransactionDO::getWalletId, walletId,
  35 + PayWalletTransactionDO::getBizId, bizId,
  36 + PayWalletTransactionDO::getBizType, bizType);
  37 + }
  38 +
29 39 }
30 40  
31 41  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/wallet/WalletPayClient.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.framework.pay.wallet;
  2 +
  3 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
  4 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
  5 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  6 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
  7 +import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
  8 +import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
  9 +import cn.iocoder.yudao.framework.pay.core.client.impl.delegate.DelegatePayClient;
  10 +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
  11 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +
  14 +import java.util.Map;
  15 +
  16 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
  17 +
  18 +/**
  19 + * 钱包支付的 PayClient 实现类
  20 + *
  21 + * @author jason
  22 + */
  23 +@Slf4j
  24 +public class WalletPayClient extends DelegatePayClient<NonePayClientConfig> {
  25 +
  26 + private PayWalletService payWalletService;
  27 +
  28 + public WalletPayClient(Long channelId, String channelCode, NonePayClientConfig config) {
  29 + super(channelId, channelCode, config);
  30 + }
  31 +
  32 + public WalletPayClient(Long channelId, String channelCode, NonePayClientConfig config,
  33 + PayWalletService payWalletService) {
  34 + this(channelId, channelCode, config);
  35 + this.payWalletService = payWalletService;
  36 + }
  37 +
  38 + @Override
  39 + protected void doInit() {
  40 + // 钱包支付,无需初始化
  41 + }
  42 +
  43 + @Override
  44 + protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  45 + try {
  46 + PayWalletTransactionDO transaction = payWalletService.pay(reqDTO.getOutTradeNo(), reqDTO.getPrice());
  47 + return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
  48 + transaction.getTransactionTime(),
  49 + reqDTO.getOutTradeNo(), "WALLET_PAY_SUCCESS"); // TODO @jason:transaction 作为 traData 好了;
  50 + } catch (Throwable ex) {
  51 + log.error("[doUnifiedOrder] 失败", ex);
  52 + Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
  53 + String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
  54 + if (ex instanceof ServiceException) {
  55 + ServiceException serviceException = (ServiceException) ex;
  56 + errorCode = serviceException.getCode();
  57 + errorMsg = serviceException.getMessage();
  58 + }
  59 + return PayOrderRespDTO.closedOf(String.valueOf(errorCode), errorMsg,
  60 + reqDTO.getOutTradeNo(), "");
  61 + }
  62 + }
  63 +
  64 + @Override
  65 + protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
  66 + throw new UnsupportedOperationException("钱包支付无支付回调");
  67 + }
  68 +
  69 + @Override
  70 + protected PayOrderRespDTO doGetOrder(String outTradeNo) {
  71 + throw new UnsupportedOperationException("待实现");
  72 + }
  73 +
  74 + @Override
  75 + protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
  76 + try {
  77 + PayWalletTransactionDO payWalletTransaction = payWalletService.refund(reqDTO.getOutRefundNo(),
  78 + reqDTO.getRefundPrice(), reqDTO.getReason());
  79 + return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getTransactionTime(),
  80 + reqDTO.getOutRefundNo(), "WALLET_REFUND_SUCCESS");
  81 + } catch (Throwable ex) {
  82 + log.error("[doUnifiedRefund] 失败", ex);
  83 + Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
  84 + String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
  85 + if (ex instanceof ServiceException) {
  86 + ServiceException serviceException = (ServiceException) ex;
  87 + errorCode = serviceException.getCode();
  88 + errorMsg = serviceException.getMessage();
  89 + }
  90 + return PayRefundRespDTO.failureOf(String.valueOf(errorCode), errorMsg,
  91 + reqDTO.getOutRefundNo(), "");
  92 + }
  93 + }
  94 +
  95 + @Override
  96 + protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
  97 + throw new UnsupportedOperationException("钱包支付无退款回调");
  98 + }
  99 +
  100 + @Override
  101 + protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) {
  102 + throw new UnsupportedOperationException("待实现");
  103 + }
  104 +
  105 +}
... ...
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;
6 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  9 +import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
9 10 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
10 11 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
11 12 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
15 16 import cn.iocoder.yudao.module.pay.convert.channel.PayChannelConvert;
16 17 import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
17 18 import cn.iocoder.yudao.module.pay.dal.mysql.channel.PayChannelMapper;
  19 +import cn.iocoder.yudao.module.pay.framework.pay.wallet.WalletPayClient;
  20 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
18 21 import lombok.Getter;
19 22 import lombok.Setter;
20 23 import lombok.extern.slf4j.Slf4j;
... ... @@ -26,6 +29,7 @@ import javax.annotation.PostConstruct;
26 29 import javax.annotation.Resource;
27 30 import javax.validation.Validator;
28 31 import java.time.LocalDateTime;
  32 +import java.util.ArrayList;
29 33 import java.util.Collection;
30 34 import java.util.Collections;
31 35 import java.util.List;
... ... @@ -56,6 +60,8 @@ public class PayChannelServiceImpl implements PayChannelService {
56 60  
57 61 @Resource
58 62 private Validator validator;
  63 + @Resource
  64 + private PayWalletService payWalletService;
59 65  
60 66 /**
61 67 * 初始化 {@link #payClientFactory} 缓存
... ... @@ -75,10 +81,26 @@ public class PayChannelServiceImpl implements PayChannelService {
75 81 log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://doc.iocoder.cn/pay/build/ 开启]");
76 82 }
77 83 log.info("[initLocalCache][缓存支付渠道,数量为:{}]", channels.size());
78   -
  84 + // 钱包 client 需要和其它 client 分开了创建
  85 + List<PayChannelDO> walletChannels = new ArrayList<>();
  86 + // TODO @jason:有点复杂,看看用 PayClientInitializer 能不能简化
  87 + List<PayChannelDO> otherChannels = new ArrayList<>();
  88 + channels.forEach(t -> {
  89 + if (PayChannelEnum.WALLET.getCode().equals(t.getCode())) {
  90 + walletChannels.add(t);
  91 + } else {
  92 + otherChannels.add(t);
  93 + }
  94 + });
79 95 // 第二步:构建缓存:创建或更新支付 Client
80   - channels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
  96 + otherChannels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
81 97 payChannel.getCode(), payChannel.getConfig()));
  98 +
  99 + walletChannels.forEach(payChannel -> {
  100 + WalletPayClient walletPayClient = new WalletPayClient(payChannel.getId(), payChannel.getCode(),
  101 + (NonePayClientConfig) payChannel.getConfig(), payWalletService);
  102 + payClientFactory.addOrUpdateDelegatePayClient(payChannel.getId(), walletPayClient);
  103 + });
82 104 this.channelCache = channels;
83 105 });
84 106 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
... ... @@ -107,6 +107,14 @@ public interface PayOrderService {
107 107 PayOrderExtensionDO getOrderExtension(Long id);
108 108  
109 109 /**
  110 + * 获得支付订单
  111 + *
  112 + * @param no 支付订单 no
  113 + * @return 支付订单
  114 + */
  115 + PayOrderExtensionDO getOrderExtensionByNo(String no);
  116 +
  117 + /**
110 118 * 同步订单的支付状态
111 119 *
112 120 * @param minCreateTime 最小创建时间
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
... ... @@ -416,6 +416,11 @@ public class PayOrderServiceImpl implements PayOrderService {
416 416 }
417 417  
418 418 @Override
  419 + public PayOrderExtensionDO getOrderExtensionByNo(String no) {
  420 + return orderExtensionMapper.selectByNo(no);
  421 + }
  422 +
  423 + @Override
419 424 public int syncOrder(LocalDateTime minCreateTime) {
420 425 // 1. 查询指定创建时间内的待支付订单
421 426 List<PayOrderExtensionDO> orderExtensions = orderExtensionMapper.selectListByStatusAndCreateTimeGe(
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java
... ... @@ -25,6 +25,14 @@ public interface PayRefundService {
25 25 PayRefundDO getRefund(Long id);
26 26  
27 27 /**
  28 + * 获得退款订单
  29 + *
  30 + * @param no 外部退款单号
  31 + * @return 退款订单
  32 + */
  33 + PayRefundDO getRefundByNo(String no);
  34 +
  35 + /**
28 36 * 获得指定应用的退款数量
29 37 *
30 38 * @param appId 应用编号
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
... ... @@ -75,6 +75,11 @@ public class PayRefundServiceImpl implements PayRefundService {
75 75 }
76 76  
77 77 @Override
  78 + public PayRefundDO getRefundByNo(String no) {
  79 + return refundMapper.selectByNo(no);
  80 + }
  81 +
  82 + @Override
78 83 public Long getRefundCountByAppId(Long appId) {
79 84 return refundMapper.selectCountByAppId(appId);
80 85 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
1 1 package cn.iocoder.yudao.module.pay.service.wallet;
2 2  
3 3 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
  4 +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
4 5  
5 6 /**
6 7 * 钱包 Service 接口
... ... @@ -9,10 +10,30 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
9 10 */
10 11 public interface PayWalletService {
11 12  
  13 + // TODO @jason:改成 getOrCreateWallet;因为目前解耦,用户注册时,不会创建钱包;需要这里兜底处理;
12 14 /**
13 15 * 获取钱包信息
14   - * @param userId 用户 id
  16 + *
  17 + * @param userId 用户编号
15 18 * @param userType 用户类型
16 19 */
17 20 PayWalletDO getPayWallet(Long userId, Integer userType);
  21 +
  22 + /**
  23 + * 钱包支付
  24 + *
  25 + * @param outTradeNo 外部订单号
  26 + * @param price 金额
  27 + */
  28 + PayWalletTransactionDO pay(String outTradeNo, Integer price);
  29 +
  30 + /**
  31 + * 钱包支付退款
  32 + *
  33 + * @param outRefundNo 外部退款号
  34 + * @param refundPrice 退款金额
  35 + * @param reason 退款原因
  36 + */
  37 + PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason);
  38 +
18 39 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
1 1 package cn.iocoder.yudao.module.pay.service.wallet;
2 2  
  3 +import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
  4 +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
3 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
  6 +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
4 7 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletMapper;
  8 +import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
  9 +import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  10 +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.context.annotation.Lazy;
5 13 import org.springframework.stereotype.Service;
  14 +import org.springframework.transaction.annotation.Transactional;
6 15  
7 16 import javax.annotation.Resource;
  17 +import java.time.LocalDateTime;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  20 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
  21 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserType;
  22 +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
  23 +import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT;
  24 +import static cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum.PAYMENT_REFUND;
8 25  
9 26 /**
10 27 * 钱包 Service 实现类
... ... @@ -12,13 +29,133 @@ import javax.annotation.Resource;
12 29 * @author jason
13 30 */
14 31 @Service
  32 +@Slf4j
15 33 public class PayWalletServiceImpl implements PayWalletService {
16 34  
  35 + /**
  36 + * 余额支付的 no 前缀
  37 + */
  38 + private static final String WALLET_PAY_NO_PREFIX = "WP";
  39 + /**
  40 + * 余额退款的 no 前缀
  41 + */
  42 + private static final String WALLET_REFUND_NO_PREFIX = "WR";
  43 +
17 44 @Resource
18 45 private PayWalletMapper payWalletMapper;
  46 + @Resource
  47 + private PayNoRedisDAO noRedisDAO;
  48 +
  49 + @Resource
  50 + private PayWalletTransactionService payWalletTransactionService;
  51 + @Resource
  52 + @Lazy
  53 + private PayOrderService payOrderService;
  54 + @Resource
  55 + @Lazy
  56 + private PayRefundService payRefundService;
19 57  
20 58 @Override
21 59 public PayWalletDO getPayWallet(Long userId, Integer userType) {
22 60 return payWalletMapper.selectByUserIdAndType(userId, userType);
23 61 }
  62 +
  63 + // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);reduceWalletBalance;
  64 + // TODO @jason:最好是,明确传入哪个 userId 或者 walletId;
  65 + @Override
  66 + @Transactional(rollbackFor = Exception.class)
  67 + public PayWalletTransactionDO pay(String outTradeNo, Integer price) {
  68 + // 1.1 判断支付交易拓展单是否存
  69 + PayOrderExtensionDO orderExtension = payOrderService.getOrderExtensionByNo(outTradeNo);
  70 + if (orderExtension == null) {
  71 + throw exception(ORDER_EXTENSION_NOT_FOUND);
  72 + }
  73 + // 1.2 判断余额是否足够
  74 + PayWalletDO payWallet = validatePayWallet();
  75 + int afterBalance = payWallet.getBalance() - price;
  76 + if (afterBalance < 0) {
  77 + throw exception(WALLET_BALANCE_NOT_ENOUGH);
  78 + }
  79 +
  80 + // 2.1 扣除余额
  81 + // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 > price,以及 - price
  82 + payWallet.setBalance(afterBalance);
  83 + payWallet.setTotalExpense(payWallet.getTotalExpense() + price);
  84 + payWalletMapper.updateById(payWallet);
  85 +
  86 + // 2.2 生成钱包流水
  87 + String walletNo = noRedisDAO.generate(WALLET_PAY_NO_PREFIX);
  88 + PayWalletTransactionDO walletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())
  89 + .setNo(walletNo).setAmount(price * -1).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())
  90 + .setBizId(orderExtension.getOrderId()).setBizType(PAYMENT.getType());
  91 + payWalletTransactionService.createWalletTransaction(walletTransaction);
  92 + return walletTransaction;
  93 + }
  94 +
  95 + // TODO @jason:不要在 service 里去使用用户上下文,这样和 request 就耦合了。
  96 + private PayWalletDO validatePayWallet() {
  97 + Long userId = getLoginUserId();
  98 + Integer userType = getLoginUserType();
  99 + PayWalletDO payWallet = getPayWallet(userId, userType);
  100 + if (payWallet == null) {
  101 + log.error("[validatePayWallet] 用户 {} 钱包不存在", userId);
  102 + throw exception(WALLET_NOT_FOUND);
  103 + }
  104 + return payWallet;
  105 + }
  106 +
  107 + // TODO @jason:可以做的更抽象一点;pay(bizType, bizId, price);addWalletBalance;这样,如果后续充值,应该也是能复用这个方法的;
  108 + @Override
  109 + @Transactional(rollbackFor = Exception.class)
  110 + public PayWalletTransactionDO refund(String outRefundNo, Integer refundPrice, String reason) {
  111 + // 1.1 判断退款单是否存在
  112 + PayRefundDO payRefund = payRefundService.getRefundByNo(outRefundNo);
  113 + if (payRefund == null) {
  114 + throw exception(REFUND_NOT_FOUND);
  115 + }
  116 + // 1.2 校验是否可以退款
  117 + PayWalletDO payWallet = validatePayWallet();
  118 + validateWalletCanRefund(payRefund.getId(), payRefund.getChannelOrderNo(), payWallet.getId(), refundPrice);
  119 +
  120 + // TODO @jason:不要直接整个更新;而是 new 一个出来更新;然后要考虑并发,要 where 余额 + 金额
  121 + Integer afterBalance = payWallet.getBalance() + refundPrice;
  122 + payWallet.setBalance(afterBalance);
  123 + payWallet.setTotalExpense(payWallet.getTotalExpense() + refundPrice * -1L);
  124 + payWalletMapper.updateById(payWallet);
  125 +
  126 + // 2.2 生成钱包流水
  127 + String walletNo = noRedisDAO.generate(WALLET_REFUND_NO_PREFIX);
  128 + PayWalletTransactionDO newWalletTransaction = new PayWalletTransactionDO().setWalletId(payWallet.getId())
  129 + .setNo(walletNo).setAmount(refundPrice).setBalance(afterBalance).setTransactionTime(LocalDateTime.now())
  130 + .setBizId(payRefund.getId()).setBizType(PAYMENT_REFUND.getType())
  131 + .setDescription(reason);
  132 + payWalletTransactionService.createWalletTransaction(newWalletTransaction);
  133 + return newWalletTransaction;
  134 + }
  135 +
  136 + /**
  137 + * 校验是否能退款
  138 + *
  139 + * @param refundId 支付退款单 id
  140 + * @param walletPayNo 钱包支付 no
  141 + * @param walletId 钱包 id
  142 + */
  143 + // TODO @jason:不要使用基本类型;
  144 + private void validateWalletCanRefund(long refundId, String walletPayNo, long walletId, int refundPrice) {
  145 + // 查询钱包支付交易
  146 + PayWalletTransactionDO payWalletTransaction = payWalletTransactionService.getWalletTransactionByNo(walletPayNo);
  147 + if (payWalletTransaction == null) {
  148 + throw exception(WALLET_TRANSACTION_NOT_FOUND);
  149 + }
  150 + // 原来的支付金额
  151 + int amount = payWalletTransaction.getAmount() * -1; // TODO @jason:直接 - payWalletTransaction.getAmount() 即可;
  152 + if (refundPrice != amount) {
  153 + throw exception(WALLET_REFUND_AMOUNT_ERROR);
  154 + }
  155 + PayWalletTransactionDO refundTransaction = payWalletTransactionService.getWalletTransaction(walletId, refundId, PAYMENT_REFUND);
  156 + if (refundTransaction != null) {
  157 + throw exception(WALLET_REFUND_EXIST);
  158 + }
  159 + }
  160 +
24 161 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionService.java
1 1 package cn.iocoder.yudao.module.pay.service.wallet;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
  4 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
5 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
  6 +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
6 7  
7 8 /**
8   - * 钱包余额明细 Service 接口
  9 + * 钱包余额流水 Service 接口
9 10 *
10 11 * @author jason
11 12 */
12 13 public interface PayWalletTransactionService {
13 14  
14 15 /**
15   - * 查询钱包余额明细, 分页
  16 + * 查询钱包余额流水分页
16 17 *
17   - * @param userId 用户 id
  18 + * @param userId 用户编号
18 19 * @param userType 用户类型
19 20 * @param pageVO 分页查询参数
20 21 */
21 22 PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType,
22 23 AppPayWalletTransactionPageReqVO pageVO);
  24 +
  25 + /**
  26 + * 新增钱包余额流水
  27 + *
  28 + * @param payWalletTransaction 余额流水
  29 + * @return id
  30 + */
  31 + Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction);
  32 +
  33 + /**
  34 + * 根据 no,获取钱包余流水
  35 + *
  36 + * @param no 流水号
  37 + */
  38 + PayWalletTransactionDO getWalletTransactionByNo(String no);
  39 +
  40 + /**
  41 + * 获取钱包流水
  42 + *
  43 + * @param walletId 钱包编号
  44 + * @param bizId 业务编号
  45 + * @param type 业务类型
  46 + * @return 钱包流水
  47 + */
  48 + PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type);
  49 +
23 50 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletTransactionServiceImpl.java
1 1 package cn.iocoder.yudao.module.pay.service.wallet;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.AppPayWalletTransactionPageReqVO;
  4 +import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.transaction.AppPayWalletTransactionPageReqVO;
5 5 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
6 6 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
7 7 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletTransactionMapper;
8   -import cn.iocoder.yudao.module.pay.enums.member.WalletTransactionQueryTypeEnum;
  8 +import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
9 9 import lombok.extern.slf4j.Slf4j;
10 10 import org.springframework.stereotype.Service;
11 11  
... ... @@ -15,27 +15,45 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
15 15 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.WALLET_NOT_FOUND;
16 16  
17 17 /**
18   - * 钱包余额明细 Service 实现类
  18 + * 钱包流水 Service 实现类
19 19 *
20 20 * @author jason
21 21 */
22 22 @Service
23 23 @Slf4j
24   -public class PayWalletTransactionServiceImpl implements PayWalletTransactionService{
  24 +public class PayWalletTransactionServiceImpl implements PayWalletTransactionService {
  25 +
25 26 @Resource
26 27 private PayWalletService payWalletService;
  28 +
27 29 @Resource
28 30 private PayWalletTransactionMapper payWalletTransactionMapper;
29 31  
30 32 @Override
31 33 public PageResult<PayWalletTransactionDO> getWalletTransactionPage(Long userId, Integer userType,
32 34 AppPayWalletTransactionPageReqVO pageVO) {
33   - PayWalletDO payWallet = payWalletService.getPayWallet(userId, userType);
34   - if (payWallet == null) {
35   - log.error("[pageWalletTransaction] 用户 {} 钱包不存在", userId);
  35 + PayWalletDO wallet = payWalletService.getPayWallet(userId, userType);
  36 + if (wallet == null) {
  37 + log.error("[getWalletTransactionPage][用户({}/{}) 钱包不存在", userId, userType);
36 38 throw exception(WALLET_NOT_FOUND);
37 39 }
38   - return payWalletTransactionMapper.selectPageByWalletIdAndQueryType(payWallet.getId(),
39   - WalletTransactionQueryTypeEnum.valueOf(pageVO.getType()), pageVO);
  40 + return payWalletTransactionMapper.selectPage(wallet.getId(), pageVO);
  41 + }
  42 +
  43 + @Override
  44 + public Long createWalletTransaction(PayWalletTransactionDO payWalletTransaction) {
  45 + payWalletTransactionMapper.insert(payWalletTransaction);
  46 + return payWalletTransaction.getId();
  47 + }
  48 +
  49 + @Override
  50 + public PayWalletTransactionDO getWalletTransactionByNo(String no) {
  51 + return payWalletTransactionMapper.selectByNo(no);
  52 + }
  53 +
  54 + @Override
  55 + public PayWalletTransactionDO getWalletTransaction(Long walletId, Long bizId, PayWalletBizTypeEnum type) {
  56 + return payWalletTransactionMapper.selectByWalletIdAndBiz(walletId, bizId, type.getType());
40 57 }
  58 +
41 59 }
... ...