Commit dc46dff62c4d96705b73c968db04880bafc6ad33

Authored by owen
2 parents f30fa390 bf40767f

Merge remote-tracking branch 'origin/feature/mall_product' into brokerate

Showing 15 changed files with 361 additions and 127 deletions
sql/mysql/pay_wallet.sql
@@ -10,6 +10,7 @@ CREATE TABLE `pay_wallet` @@ -10,6 +10,7 @@ CREATE TABLE `pay_wallet`
10 `balance` int NOT NULL DEFAULT 0 COMMENT '余额,单位分', 10 `balance` int NOT NULL DEFAULT 0 COMMENT '余额,单位分',
11 `total_expense` int NOT NULL DEFAULT 0 COMMENT '累计支出,单位分', 11 `total_expense` int NOT NULL DEFAULT 0 COMMENT '累计支出,单位分',
12 `total_recharge` int NOT NULL DEFAULT 0 COMMENT '累计充值,单位分', 12 `total_recharge` int NOT NULL DEFAULT 0 COMMENT '累计充值,单位分',
  13 + `freeze_price` int NOT NULL DEFAULT 0 COMMENT '冻结金额,单位分',
13 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', 14 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
14 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 15 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
15 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', 16 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
@@ -62,6 +63,7 @@ CREATE TABLE `pay_wallet_recharge` @@ -62,6 +63,7 @@ CREATE TABLE `pay_wallet_recharge`
62 `refund_pay_price` int NOT NULL DEFAULT 0 COMMENT '退款支付金额', 63 `refund_pay_price` int NOT NULL DEFAULT 0 COMMENT '退款支付金额',
63 `refund_bonus_price` int NOT NULL DEFAULT 0 COMMENT '退款钱包赠送金额', 64 `refund_bonus_price` int NOT NULL DEFAULT 0 COMMENT '退款钱包赠送金额',
64 `refund_time` datetime NULL COMMENT '退款时间', 65 `refund_time` datetime NULL COMMENT '退款时间',
  66 + `refund_status` int NOT NULL DEFAULT 0 COMMENT '退款状态',
65 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', 67 `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
66 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 68 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
67 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', 69 `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/delivery/DeliveryExpressChargeModeEnum.java
@@ -16,7 +16,7 @@ import java.util.Arrays; @@ -16,7 +16,7 @@ import java.util.Arrays;
16 @Getter 16 @Getter
17 public enum DeliveryExpressChargeModeEnum implements IntArrayValuable { 17 public enum DeliveryExpressChargeModeEnum implements IntArrayValuable {
18 18
19 - PIECE(1, "按件"), 19 + COUNT(1, "按件"),
20 WEIGHT(2,"按重量"), 20 WEIGHT(2,"按重量"),
21 VOLUME(3, "按体积"); 21 VOLUME(3, "按体积");
22 22
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculator.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.module.member.api.address.AddressApi; 7 import cn.iocoder.yudao.module.member.api.address.AddressApi;
7 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; 8 import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO;
@@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
55 return; 56 return;
56 } 57 }
57 if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) { 58 if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) {
58 - calculateByPickUp(param, result); 59 + calculateByPickUp(param);
59 } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) { 60 } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) {
60 calculateExpress(param, result); 61 calculateExpress(param, result);
61 } 62 }
62 } 63 }
63 64
64 - private void calculateByPickUp(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { 65 + private void calculateByPickUp(TradePriceCalculateReqBO param) {
65 if (param.getPickUpStoreId() == null) { 66 if (param.getPickUpStoreId() == null) {
66 throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY); 67 throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY);
67 } 68 }
@@ -82,12 +83,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -82,12 +83,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
82 Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId()); 83 Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId());
83 84
84 // 情况一:全局包邮 85 // 情况一:全局包邮
85 - if (isGlobalExpressFree(param, result)) { 86 + if (isGlobalExpressFree(result)) {
86 return; 87 return;
87 } 88 }
88 89
89 - // 情况二:  
90 - // 2.1 过滤出已选中的商品SKU 90 + // 情况二:快递模版
  91 + // 2.1 过滤出已选中的商品 SKU
91 List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected); 92 List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected);
92 Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId); 93 Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId);
93 Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap = 94 Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap =
@@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
103 /** 104 /**
104 * 是否全局包邮 105 * 是否全局包邮
105 * 106 *
106 - * @param param 计算信息  
107 * @param result 计算结果 107 * @param result 计算结果
108 * @return 是否包邮 108 * @return 是否包邮
109 */ 109 */
110 - private boolean isGlobalExpressFree(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { 110 + private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) {
111 TradeConfigDO config = tradeConfigService.getTradeConfig(); 111 TradeConfigDO config = tradeConfigService.getTradeConfig();
112 return config != null 112 return config != null
113 && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮 113 && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮
@@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
118 Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap, 118 Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap,
119 TradePriceCalculateRespBO result) { 119 TradePriceCalculateRespBO result) {
120 // 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU 120 // 按商品运费模板来计算商品的运费:相同的运费模板可能对应多条订单商品 SKU
121 - Map<Long, List<OrderItem>> tplIdItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId); 121 + Map<Long, List<OrderItem>> template2ItemMap = convertMultiMap(selectedSkus, OrderItem::getDeliveryTemplateId);
122 // 依次计算快递运费 122 // 依次计算快递运费
123 - for (Map.Entry<Long, List<OrderItem>> entry : tplIdItemMap.entrySet()) { 123 + for (Map.Entry<Long, List<OrderItem>> entry : template2ItemMap.entrySet()) {
124 Long templateId = entry.getKey(); 124 Long templateId = entry.getKey();
125 List<OrderItem> orderItems = entry.getValue(); 125 List<OrderItem> orderItems = entry.getValue();
126 DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId); 126 DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId);
@@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
128 log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId); 128 log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId);
129 continue; 129 continue;
130 } 130 }
131 - // 总件数, 总金额, 总重量, 总体积  
132 - int totalCount = 0;  
133 - int totalPrice = 0;  
134 - double totalWeight = 0;  
135 - double totalVolume = 0;  
136 - for (OrderItem orderItem : orderItems) {  
137 - totalCount += orderItem.getCount();  
138 - totalPrice += orderItem.getPayPrice();  
139 - if (orderItem.getWeight() != null) {  
140 - totalWeight += totalWeight + orderItem.getWeight() * orderItem.getCount();  
141 - }  
142 - if (orderItem.getVolume() != null) {  
143 - totalVolume += totalVolume + orderItem.getVolume() * orderItem.getCount();  
144 - }  
145 - }  
146 - // 优先判断是否包邮. 如果包邮不计算快递运费  
147 - if (isExpressFree(templateBO.getChargeMode(), totalCount, totalWeight,  
148 - totalVolume, totalPrice, templateBO.getFree())) { 131 + // 1. 优先判断是否包邮。如果包邮不计算快递运费
  132 + if (isExpressTemplateFree(orderItems, templateBO.getChargeMode(), templateBO.getFree())) {
149 continue; 133 continue;
150 } 134 }
151 - // 计算快递运费  
152 - calculateExpressFeeByChargeMode(totalCount, totalWeight, totalVolume,  
153 - templateBO.getChargeMode(), templateBO.getCharge(), orderItems);  
154 - 135 + // 2. 计算快递运费
  136 + calculateExpressFeeByChargeMode(orderItems, templateBO.getChargeMode(), templateBO.getCharge());
155 } 137 }
156 TradePriceCalculatorHelper.recountAllPrice(result); 138 TradePriceCalculatorHelper.recountAllPrice(result);
157 } 139 }
@@ -159,73 +141,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -159,73 +141,44 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
159 /** 141 /**
160 * 按配送方式来计算运费 142 * 按配送方式来计算运费
161 * 143 *
162 - * @param totalCount 总件数  
163 - * @param totalWeight 总重量  
164 - * @param totalVolume 总体积 144 + * @param orderItems SKU 商品项目
165 * @param chargeMode 配送计费方式 145 * @param chargeMode 配送计费方式
166 * @param templateCharge 快递运费配置 146 * @param templateCharge 快递运费配置
167 - * @param orderItems SKU 商品项目  
168 */ 147 */
169 - private void calculateExpressFeeByChargeMode(double totalCount, double totalWeight, double totalVolume,  
170 - int chargeMode, DeliveryExpressTemplateRespBO.Charge templateCharge,  
171 - List<OrderItem> orderItems) { 148 + private void calculateExpressFeeByChargeMode(List<OrderItem> orderItems, Integer chargeMode,
  149 + DeliveryExpressTemplateRespBO.Charge templateCharge) {
172 if (templateCharge == null) { 150 if (templateCharge == null) {
173 log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems); 151 log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems);
174 return; 152 return;
175 } 153 }
176 - DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);  
177 - switch (chargeModeEnum) {  
178 - case PIECE: {  
179 - calculateExpressFee(totalCount, templateCharge, orderItems);  
180 - break;  
181 - }  
182 - case WEIGHT: {  
183 - calculateExpressFee(totalWeight, templateCharge, orderItems);  
184 - break;  
185 - }  
186 - case VOLUME: {  
187 - calculateExpressFee(totalVolume, templateCharge, orderItems);  
188 - break;  
189 - }  
190 - }  
191 - }  
192 -  
193 - /**  
194 - * 计算 SKU 商品快递费用  
195 - *  
196 - * @param total 总件数/总重量/总体积  
197 - * @param templateCharge 快递运费配置  
198 - * @param orderItems SKU 商品项目  
199 - */  
200 - private void calculateExpressFee(double total, DeliveryExpressTemplateRespBO.Charge templateCharge, List<OrderItem> orderItems) { 154 + double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
  155 + // 1. 计算 SKU 商品快递费用
201 int deliveryPrice; 156 int deliveryPrice;
202 - if (total <= templateCharge.getStartCount()) { 157 + if (totalChargeValue <= templateCharge.getStartCount()) {
203 deliveryPrice = templateCharge.getStartPrice(); 158 deliveryPrice = templateCharge.getStartPrice();
204 } else { 159 } else {
205 - double remainWeight = total - templateCharge.getStartCount(); 160 + double remainWeight = totalChargeValue - templateCharge.getStartCount();
206 // 剩余重量/ 续件 = 续件的次数. 向上取整 161 // 剩余重量/ 续件 = 续件的次数. 向上取整
207 int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount()); 162 int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount());
208 int extraPrice = templateCharge.getExtraPrice() * extraNum; 163 int extraPrice = templateCharge.getExtraPrice() * extraNum;
209 deliveryPrice = templateCharge.getStartPrice() + extraPrice; 164 deliveryPrice = templateCharge.getStartPrice() + extraPrice;
210 } 165 }
211 - // 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额  
212 - divideDeliveryPrice(deliveryPrice, orderItems);  
213 - }  
214 166
215 - /**  
216 - * 快递运费分摊到每个 SKU 商品上  
217 - *  
218 - * @param deliveryPrice 快递运费  
219 - * @param orderItems SKU 商品  
220 - */  
221 - private void divideDeliveryPrice(int deliveryPrice, List<OrderItem> orderItems) {  
222 - // TODO @jason:分摊的话,是不是要按照比例呀?重量、价格、数量等等,  
223 - // 按比例是不是有点复杂。后面看看是否需要;  
224 - // TODO 可以看看别的项目怎么搞的哈。  
225 - int dividePrice = deliveryPrice / orderItems.size();  
226 - for (OrderItem item : orderItems) { 167 + // 2. 分摊快递费用到 SKU. 退费的时候,可能按照 SKU 考虑退费金额
  168 + int remainPrice = deliveryPrice;
  169 + for (int i = 0; i < orderItems.size(); i++) {
  170 + TradePriceCalculateRespBO.OrderItem item = orderItems.get(i);
  171 + int partPrice;
  172 + double chargeValue = getChargeValue(item, chargeMode);
  173 + if (i < orderItems.size() - 1) { // 减一的原因,是因为拆分时,如果按照比例,可能会出现.所以最后一个,使用反减
  174 + partPrice = (int) (deliveryPrice * (chargeValue / totalChargeValue));
  175 + remainPrice -= partPrice;
  176 + } else {
  177 + partPrice = remainPrice;
  178 + }
  179 + Assert.isTrue(partPrice >= 0, "分摊金额必须大于等于 0");
227 // 更新快递运费 180 // 更新快递运费
228 - item.setDeliveryPrice(dividePrice); 181 + item.setDeliveryPrice(partPrice);
229 TradePriceCalculatorHelper.recountPayPrice(item); 182 TradePriceCalculatorHelper.recountPayPrice(item);
230 } 183 }
231 } 184 }
@@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { @@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator {
234 * 检查是否包邮 187 * 检查是否包邮
235 * 188 *
236 * @param chargeMode 配送计费方式 189 * @param chargeMode 配送计费方式
237 - * @param totalCount 总件数  
238 - * @param totalWeight 总重量  
239 - * @param totalVolume 总体积  
240 - * @param totalPrice 总金额  
241 * @param templateFree 包邮配置 190 * @param templateFree 包邮配置
242 */ 191 */
243 - private boolean isExpressFree(Integer chargeMode, int totalCount, double totalWeight,  
244 - double totalVolume, int totalPrice, DeliveryExpressTemplateRespBO.Free templateFree) { 192 + private boolean isExpressTemplateFree(List<OrderItem> orderItems, Integer chargeMode,
  193 + DeliveryExpressTemplateRespBO.Free templateFree) {
245 if (templateFree == null) { 194 if (templateFree == null) {
246 return false; 195 return false;
247 } 196 }
  197 + double totalChargeValue = getTotalChargeValue(orderItems, chargeMode);
  198 + double totalPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
  199 + return totalChargeValue >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice();
  200 + }
  201 +
  202 + private double getTotalChargeValue(List<OrderItem> orderItems, Integer chargeMode) {
  203 + double total = 0;
  204 + for (OrderItem orderItem : orderItems) {
  205 + total += getChargeValue(orderItem, chargeMode);
  206 + }
  207 + return total;
  208 + }
  209 +
  210 + private double getChargeValue(OrderItem orderItem, Integer chargeMode) {
248 DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); 211 DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode);
249 switch (chargeModeEnum) { 212 switch (chargeModeEnum) {
250 - case PIECE:  
251 - // 两个条件都满足才包邮  
252 - if (totalCount >= templateFree.getFreeCount() && totalPrice >= templateFree.getFreePrice()) {  
253 - return true;  
254 - }  
255 - break; 213 + case COUNT:
  214 + return orderItem.getCount();
256 case WEIGHT: 215 case WEIGHT:
257 - // freeCount 是不是应该是 double ??  
258 - // TODO @jason:要不配置的时候,把它的单位和商品对齐?到底是 kg、还是斤  
259 - // TODO @芋艿 目前 包邮 件数/重量/体积 都用的是这个字段  
260 - // TODO @jason:那要不快递模版也改成 kg?这样是不是就不用 double ?  
261 - if (totalWeight >= templateFree.getFreeCount()  
262 - && totalPrice >= templateFree.getFreePrice()) {  
263 - return true;  
264 - }  
265 - break; 216 + return orderItem.getWeight() != null ? orderItem.getWeight() * orderItem.getCount() : 0;
266 case VOLUME: 217 case VOLUME:
267 - if (totalVolume >= templateFree.getFreeCount()  
268 - && totalPrice >= templateFree.getFreePrice()) {  
269 - return true;  
270 - }  
271 - break; 218 + return orderItem.getVolume() != null ? orderItem.getVolume() * orderItem.getCount() : 0;
  219 + default:
  220 + throw new IllegalArgumentException(StrUtil.format("未知的计费模式({})", chargeMode));
272 } 221 }
273 - return false;  
274 } 222 }
  223 +
275 } 224 }
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeDeliveryPriceCalculatorTest.java
@@ -91,7 +91,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { @@ -91,7 +91,7 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
91 item -> item.setFreeCount(20).setFreePrice(100)); 91 item -> item.setFreeCount(20).setFreePrice(100));
92 // 准备 SP 运费模板数据 92 // 准备 SP 运费模板数据
93 templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class, 93 templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class,
94 - item -> item.setChargeMode(DeliveryExpressChargeModeEnum.PIECE.getType()) 94 + item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType())
95 .setCharge(chargeBO).setFree(freeBO)); 95 .setCharge(chargeBO).setFree(freeBO));
96 } 96 }
97 97
@@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { @@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest {
144 // 断言:SKU1 144 // 断言:SKU1
145 assertThat(resultBO.getItems().get(0)) 145 assertThat(resultBO.getItems().get(0))
146 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") 146 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
147 - .containsExactly(100, 2, 0, 0, 0, 1500, 1700); 147 + .containsExactly(100, 2, 0, 0, 0, 500, 700);
148 // 断言:SKU2 148 // 断言:SKU2
149 assertThat(resultBO.getItems().get(1)) 149 assertThat(resultBO.getItems().get(1))
150 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") 150 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
151 - .containsExactly(200, 10, 0, 0, 0, 1500, 3500); 151 + .containsExactly(200, 10, 0, 0, 0, 2500, 4500);
152 // 断言:SKU3 未选中 152 // 断言:SKU3 未选中
153 assertThat(resultBO.getItems().get(2)) 153 assertThat(resultBO.getItems().get(2))
154 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice") 154 .extracting("price", "count","discountPrice" ,"couponPrice", "pointPrice","deliveryPrice","payPrice")
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
@@ -46,6 +46,7 @@ public interface ErrorCodeConstants { @@ -46,6 +46,7 @@ public interface ErrorCodeConstants {
46 ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1007007002, "未找到对应的钱包交易"); 46 ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1007007002, "未找到对应的钱包交易");
47 ErrorCode WALLET_REFUND_AMOUNT_ERROR = new ErrorCode(1007007003, "钱包退款金额不对"); 47 ErrorCode WALLET_REFUND_AMOUNT_ERROR = new ErrorCode(1007007003, "钱包退款金额不对");
48 ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1007007004, "已经存在钱包退款"); 48 ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1007007004, "已经存在钱包退款");
  49 + ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1007007005, "钱包冻结余额不足");
49 50
50 // TODO @jason:把钱包充值,单独搞个错误码段哈; 51 // TODO @jason:把钱包充值,单独搞个错误码段哈;
51 52
@@ -54,7 +55,12 @@ public interface ErrorCodeConstants { @@ -54,7 +55,12 @@ public interface ErrorCodeConstants {
54 ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1007007007, "钱包充值更新支付状态失败,支付单编号不匹配"); 55 ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1007007007, "钱包充值更新支付状态失败,支付单编号不匹配");
55 ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1007007008, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态"); 56 ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1007007008, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态");
56 ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH = new ErrorCode(1007007009, "钱包充值更新支付状态失败,支付单金额不匹配"); 57 ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_PRICE_NOT_MATCH = new ErrorCode(1007007009, "钱包充值更新支付状态失败,支付单金额不匹配");
57 - 58 + ErrorCode WALLET_RECHARGE_REFUND_FAIL_NOT_PAID = new ErrorCode(1007900010, "钱包发起退款失败,钱包充值订单未支付");
  59 + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUNDED = new ErrorCode(1007900011, "钱包发起退款失败,钱包充值订单已退款");
  60 + ErrorCode WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH = new ErrorCode(1007900012, "钱包发起退款失败,钱包余额不足");
  61 + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1007900013, "钱包退款更新失败,钱包退款单编号不匹配");
  62 + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1007900014, "钱包退款更新失败,退款订单不存在");
  63 + ErrorCode WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1007900015, "钱包退款更新失败,退款单金额不匹配");
58 // ========== 示例订单 1007900000 ========== 64 // ========== 示例订单 1007900000 ==========
59 ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在"); 65 ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在");
60 ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, "示例订单更新支付状态失败,订单不是【未支付】状态"); 66 ErrorCode DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(1007900001, "示例订单更新支付状态失败,订单不是【未支付】状态");
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.controller.admin.wallet;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  5 +import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
  6 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService;
  7 +import io.swagger.v3.oas.annotations.Operation;
  8 +import io.swagger.v3.oas.annotations.Parameter;
  9 +import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.*;
  13 +
  14 +import javax.annotation.Resource;
  15 +import javax.annotation.security.PermitAll;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  18 +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
  19 +
  20 +@Tag(name = "管理后台 - 钱包充值")
  21 +@RestController
  22 +@RequestMapping("/pay/wallet-recharge")
  23 +@Validated
  24 +@Slf4j
  25 +public class PayWalletRechargeController {
  26 +
  27 + @Resource
  28 + private PayWalletRechargeService walletRechargeService;
  29 +
  30 +
  31 + @GetMapping("/refund")
  32 + @Operation(summary = "发起钱包充值退款")
  33 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  34 + public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) {
  35 + walletRechargeService.refundWalletRecharge(id,getClientIP());
  36 + return success(true);
  37 + }
  38 +
  39 + @PostMapping("/update-refunded")
  40 + @Operation(summary = "更新钱包充值为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
  41 + @PermitAll // 无需登录, 内部校验实现
  42 + @OperateLog(enable = false) // 禁用操作日志,因为没有操作人
  43 + public CommonResult<Boolean> updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
  44 + walletRechargeService.updateWalletRechargeRefunded(
  45 + Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId());
  46 + return success(true);
  47 + }
  48 +}
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/AppPayWalletRechargeController.java
@@ -53,7 +53,4 @@ public class AppPayWalletRechargeController { @@ -53,7 +53,4 @@ public class AppPayWalletRechargeController {
53 notifyReqDTO.getPayOrderId()); 53 notifyReqDTO.getPayOrderId());
54 return success(true); 54 return success(true);
55 } 55 }
56 -  
57 - // TODO @jason:管理后台,是不是可以搞个发起退款;  
58 -  
59 } 56 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java
@@ -43,6 +43,11 @@ public class PayWalletDO extends BaseDO { @@ -43,6 +43,11 @@ public class PayWalletDO extends BaseDO {
43 private Integer balance; 43 private Integer balance;
44 44
45 /** 45 /**
  46 + * 冻结金额,单位分
  47 + */
  48 + private Integer freezePrice;
  49 +
  50 + /**
46 * 累计支出,单位分 51 * 累计支出,单位分
47 */ 52 */
48 private Integer totalExpense; 53 private Integer totalExpense;
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletRechargeDO.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.wallet; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.dal.dataobject.wallet;
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; 4 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
5 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 5 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
  6 +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
6 import com.baomidou.mybatisplus.annotation.KeySequence; 7 import com.baomidou.mybatisplus.annotation.KeySequence;
7 import com.baomidou.mybatisplus.annotation.TableId; 8 import com.baomidou.mybatisplus.annotation.TableId;
8 import com.baomidou.mybatisplus.annotation.TableName; 9 import com.baomidou.mybatisplus.annotation.TableName;
@@ -99,4 +100,11 @@ public class PayWalletRechargeDO extends BaseDO { @@ -99,4 +100,11 @@ public class PayWalletRechargeDO extends BaseDO {
99 */ 100 */
100 private LocalDateTime refundTime; 101 private LocalDateTime refundTime;
101 102
  103 + /**
  104 + * 退款状态
  105 + *
  106 + * 枚举 {@link PayRefundStatusEnum}
  107 + */
  108 + private Integer refundStatus;
  109 +
102 } 110 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
@@ -56,7 +56,51 @@ public interface PayWalletMapper extends BaseMapperX&lt;PayWalletDO&gt; { @@ -56,7 +56,51 @@ public interface PayWalletMapper extends BaseMapperX&lt;PayWalletDO&gt; {
56 .eq(PayWalletDO::getId, id); 56 .eq(PayWalletDO::getId, id);
57 return update(null, lambdaUpdateWrapper); 57 return update(null, lambdaUpdateWrapper);
58 } 58 }
59 - 59 +
  60 + /**
  61 + * 冻结钱包部分余额
  62 + * @param id 钱包 id
  63 + * @param price 冻结金额
  64 + */
  65 + default int freezePrice(Long id, Integer price){
  66 + LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
  67 + .setSql(" balance = balance - " + price
  68 + + ", freeze_price = freeze_price + " + price)
  69 + .eq(PayWalletDO::getId, id)
  70 + .ge(PayWalletDO::getBalance, price); // cas 逻辑
  71 + return update(null, lambdaUpdateWrapper);
  72 + }
  73 +
  74 + /**
  75 + * 解冻钱包余额
  76 + * @param id 钱包 id
  77 + * @param price 解冻金额
  78 + */
  79 + default int unFreezePrice(Long id, Integer price){
  80 + LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
  81 + .setSql(" balance = balance + " + price
  82 + + ", freeze_price = freeze_price - " + price)
  83 + .eq(PayWalletDO::getId, id)
  84 + .ge(PayWalletDO::getFreezePrice, price); // cas 逻辑
  85 + return update(null, lambdaUpdateWrapper);
  86 + }
  87 +
  88 + /**
  89 + * 当充值退款时, 更新钱包
  90 + * @param id 钱包 id
  91 + * @param price 退款金额
  92 + */
  93 + default int updateWhenRechargeRefund(Long id, Integer price){
  94 + LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
  95 + .setSql(" freeze_price = freeze_price - " + price
  96 + + ", total_recharge = total_recharge - " + price)
  97 + .eq(PayWalletDO::getId, id)
  98 + .ge(PayWalletDO::getFreezePrice, price)
  99 + .ge(PayWalletDO::getTotalRecharge, price);// cas 逻辑
  100 + return update(null, lambdaUpdateWrapper);
  101 + }
  102 +
  103 +
60 } 104 }
61 105
62 106
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletRechargeMapper.java
@@ -13,6 +13,11 @@ public interface PayWalletRechargeMapper extends BaseMapperX&lt;PayWalletRechargeDO @@ -13,6 +13,11 @@ public interface PayWalletRechargeMapper extends BaseMapperX&lt;PayWalletRechargeDO
13 .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getPayStatus, wherePayStatus)); 13 .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getPayStatus, wherePayStatus));
14 } 14 }
15 15
  16 + default int updateByIdAndRefunded(Long id, Integer whereRefundStatus, PayWalletRechargeDO updateObj){
  17 + return update(updateObj, new LambdaQueryWrapperX<PayWalletRechargeDO>()
  18 + .eq(PayWalletRechargeDO::getId, id).eq(PayWalletRechargeDO::getRefundStatus, whereRefundStatus));
  19 + }
  20 +
16 } 21 }
17 22
18 23
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java
@@ -29,4 +29,17 @@ public interface PayWalletRechargeService { @@ -29,4 +29,17 @@ public interface PayWalletRechargeService {
29 */ 29 */
30 void updateWalletRechargerPaid(Long id, Long payOrderId); 30 void updateWalletRechargerPaid(Long id, Long payOrderId);
31 31
  32 + /**
  33 + * 发起钱包充值退款
  34 + * @param id 钱包充值编号
  35 + * @param userIp 用户 ip 地址
  36 + */
  37 + void refundWalletRecharge(Long id, String userIp);
  38 +
  39 + /**
  40 + * 更新钱包充值记录为已退款
  41 + * @param id 钱包充值 id
  42 + * @param payRefundId 退款单id
  43 + */
  44 + void updateWalletRechargeRefunded(Long id, Long payRefundId);
32 } 45 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java
1 package cn.iocoder.yudao.module.pay.service.wallet; 1 package cn.iocoder.yudao.module.pay.service.wallet;
2 2
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
3 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; 5 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
  6 +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
4 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; 7 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
5 import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert; 8 import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert;
6 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; 9 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
  10 +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
7 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; 11 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
8 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; 12 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
9 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper; 13 import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper;
10 import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum; 14 import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum;
11 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; 15 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
  16 +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
12 import cn.iocoder.yudao.module.pay.service.order.PayOrderService; 17 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  18 +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
13 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
14 import org.springframework.stereotype.Service; 20 import org.springframework.stereotype.Service;
15 import org.springframework.transaction.annotation.Transactional; 21 import org.springframework.transaction.annotation.Transactional;
@@ -24,6 +30,7 @@ import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.add @@ -24,6 +30,7 @@ import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.add
24 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; 30 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
25 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; 31 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
26 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; 32 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
  33 +import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*;
27 34
28 /** 35 /**
29 * 钱包充值 Service 实现类 36 * 钱包充值 Service 实现类
@@ -47,6 +54,8 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { @@ -47,6 +54,8 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
47 private PayWalletService payWalletService; 54 private PayWalletService payWalletService;
48 @Resource 55 @Resource
49 private PayOrderService payOrderService; 56 private PayOrderService payOrderService;
  57 + @Resource
  58 + private PayRefundService payRefundService;
50 59
51 @Override 60 @Override
52 @Transactional(rollbackFor = Exception.class) 61 @Transactional(rollbackFor = Exception.class)
@@ -98,6 +107,113 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { @@ -98,6 +107,113 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
98 PayWalletBizTypeEnum.RECHARGE, walletRecharge.getTotalPrice()); 107 PayWalletBizTypeEnum.RECHARGE, walletRecharge.getTotalPrice());
99 } 108 }
100 109
  110 + @Override
  111 + @Transactional(rollbackFor = Exception.class)
  112 + public void refundWalletRecharge(Long id, String userIp) {
  113 + // 1.1 获取钱包充值记录
  114 + PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
  115 + if (walletRecharge == null) {
  116 + log.error("[refundWalletRecharge][钱包充值记录不存在,钱包充值记录 id({})]", id);
  117 + throw exception(WALLET_RECHARGE_NOT_FOUND);
  118 + }
  119 + // 1.2 校验钱包充值是否可以发起退款
  120 + PayWalletDO wallet = validateWalletRechargeCanRefund(walletRecharge);
  121 + // 2 冻结退款的余额, 暂时只处理赠送的余额也全部退回
  122 + payWalletService.freezePrice(wallet.getId(), walletRecharge.getTotalPrice());
  123 + // 3 创建退款单
  124 + String walletRechargeId = String.valueOf(id);
  125 + String refundId = walletRechargeId + "-refund";
  126 + Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
  127 + .setAppId(WALLET_PAY_APP_ID).setUserIp(userIp)
  128 + .setMerchantOrderId(walletRechargeId)
  129 + .setMerchantRefundId(refundId)
  130 + .setReason("想退钱").setPrice(walletRecharge.getPayPrice()));
  131 + // 4 更新充值记录退款单号
  132 + walletRechargeMapper.updateById(new PayWalletRechargeDO().setPayRefundId(payRefundId)
  133 + .setRefundStatus(WAITING.getStatus()).setId(walletRecharge.getId()));
  134 + }
  135 +
  136 + @Override
  137 + @Transactional(rollbackFor = Exception.class)
  138 + public void updateWalletRechargeRefunded(Long id, Long payRefundId) {
  139 + // 1.1 获取钱包充值记录
  140 + PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
  141 + if (walletRecharge == null) {
  142 + log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id);
  143 + throw exception(WALLET_RECHARGE_NOT_FOUND);
  144 + }
  145 + // 1.2 校验钱包充值是否可以更新已退款
  146 + PayRefundDO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
  147 +
  148 + PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id);
  149 + // 退款成功
  150 + if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
  151 + // 2.1 更新钱包余额
  152 + payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id,
  153 + PayWalletBizTypeEnum.RECHARGE_REFUND, walletRecharge.getTotalPrice());
  154 +
  155 + updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime())
  156 + .setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice())
  157 + .setRefundBonusPrice(walletRecharge.getBonusPrice());
  158 + }
  159 + // 退款失败
  160 + if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
  161 + // 2.2 解冻余额
  162 + payWalletService.unFreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice());
  163 +
  164 + updateObj.setRefundStatus(FAILURE.getStatus());
  165 + }
  166 + // 3. 更新钱包充值的退款字段
  167 + walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
  168 + }
  169 +
  170 + private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
  171 + // 1. 校验退款订单匹配
  172 + if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) {
  173 + log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]",
  174 + walletRecharge.getId(), payRefundId, toJsonString(walletRecharge));
  175 + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
  176 + }
  177 +
  178 + // 2.1 校验退款订单
  179 + PayRefundDO payRefund = payRefundService.getRefund(payRefundId);
  180 + if (payRefund == null) {
  181 + log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId);
  182 + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
  183 + }
  184 + // 2.2 校验退款金额一致
  185 + if (notEqual(payRefund.getRefundPrice(), walletRecharge.getPayPrice())) {
  186 + log.error("[validateWalletRechargeCanRefunded][钱包({}) payRefund({}) 退款金额不匹配,请进行处理!钱包数据是:{},payRefund 数据是:{}]",
  187 + walletRecharge.getId(), payRefundId, toJsonString(walletRecharge), toJsonString(payRefund));
  188 + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
  189 + }
  190 + // 2.3 校验退款订单商户订单是否匹配
  191 + if (notEqual(payRefund.getMerchantOrderId(), walletRecharge.getId().toString())) {
  192 + log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
  193 + walletRecharge.getId(), payRefundId, toJsonString(payRefund));
  194 + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
  195 + }
  196 + return payRefund;
  197 + }
  198 +
  199 + private PayWalletDO validateWalletRechargeCanRefund(PayWalletRechargeDO walletRecharge) {
  200 + // 校验充值订单是否支付
  201 + if (!walletRecharge.getPayStatus()) {
  202 + throw exception(WALLET_RECHARGE_REFUND_FAIL_NOT_PAID);
  203 + }
  204 + // 校验充值订单是否已退款
  205 + if (walletRecharge.getPayRefundId() != null) {
  206 + throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUNDED);
  207 + }
  208 + // 校验钱包余额是否足够
  209 + PayWalletDO wallet = payWalletService.getWallet(walletRecharge.getWalletId());
  210 + Assert.notNull(wallet, "用户钱包({}) 不存在", wallet.getId());
  211 + if (wallet.getBalance() < walletRecharge.getTotalPrice()) {
  212 + throw exception(WALLET_RECHARGE_REFUND_BALANCE_NOT_ENOUGH);
  213 + }
  214 + return wallet;
  215 + }
  216 +
101 private PayOrderDO validateWalletRechargerCanPaid(PayWalletRechargeDO walletRecharge, Long payOrderId) { 217 private PayOrderDO validateWalletRechargerCanPaid(PayWalletRechargeDO walletRecharge, Long payOrderId) {
102 // 1.1 校验充值记录的支付状态 218 // 1.1 校验充值记录的支付状态
103 if (walletRecharge.getPayStatus()) { 219 if (walletRecharge.getPayStatus()) {
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
@@ -50,15 +50,14 @@ public interface PayWalletService { @@ -50,15 +50,14 @@ public interface PayWalletService {
50 /** 50 /**
51 * 扣减钱包余额 51 * 扣减钱包余额
52 * 52 *
53 - * @param userId 用户 id  
54 - * @param userType 用户类型 53 + * @param walletId 钱包 id
55 * @param bizId 业务关联 id 54 * @param bizId 业务关联 id
56 * @param bizType 业务关联分类 55 * @param bizType 业务关联分类
57 * @param price 扣减金额 56 * @param price 扣减金额
58 * @return 钱包流水 57 * @return 钱包流水
59 */ 58 */
60 - PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,  
61 - Long bizId, PayWalletBizTypeEnum bizType, Integer price); 59 + PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
  60 + PayWalletBizTypeEnum bizType, Integer price);
62 61
63 /** 62 /**
64 * 增加钱包余额 63 * 增加钱包余额
@@ -72,4 +71,18 @@ public interface PayWalletService { @@ -72,4 +71,18 @@ public interface PayWalletService {
72 PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, 71 PayWalletTransactionDO addWalletBalance(Long walletId, String bizId,
73 PayWalletBizTypeEnum bizType, Integer price); 72 PayWalletBizTypeEnum bizType, Integer price);
74 73
  74 + /**
  75 + * 冻结钱包部分余额
  76 + *
  77 + * @param id 钱包编号
  78 + * @param price 冻结金额
  79 + */
  80 + void freezePrice(Long id, Integer price);
  81 +
  82 + /**
  83 + * 解冻钱包余额
  84 + * @param id 钱包编号
  85 + * @param price 解冻金额
  86 + */
  87 + void unFreezePrice(Long id, Integer price);
75 } 88 }
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
@@ -68,8 +68,9 @@ public class PayWalletServiceImpl implements PayWalletService { @@ -68,8 +68,9 @@ public class PayWalletServiceImpl implements PayWalletService {
68 if (orderExtension == null) { 68 if (orderExtension == null) {
69 throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); 69 throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
70 } 70 }
  71 + PayWalletDO wallet = getOrCreateWallet(userId, userType);
71 // 2. 扣减余额 72 // 2. 扣减余额
72 - return reduceWalletBalance(userId, userType, orderExtension.getOrderId(), PAYMENT, price); 73 + return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price);
73 } 74 }
74 75
75 @Override 76 @Override
@@ -116,10 +117,14 @@ public class PayWalletServiceImpl implements PayWalletService { @@ -116,10 +117,14 @@ public class PayWalletServiceImpl implements PayWalletService {
116 } 117 }
117 118
118 @Override 119 @Override
119 - public PayWalletTransactionDO reduceWalletBalance(Long userId, Integer userType,  
120 - Long bizId, PayWalletBizTypeEnum bizType, Integer price) { 120 + public PayWalletTransactionDO reduceWalletBalance(Long walletId, Long bizId,
  121 + PayWalletBizTypeEnum bizType, Integer price) {
121 // 1. 获取钱包 122 // 1. 获取钱包
122 - PayWalletDO payWallet = getOrCreateWallet(userId, userType); 123 + PayWalletDO payWallet = getWallet(walletId);
  124 + if (payWallet == null) {
  125 + log.error("[reduceWalletBalance],用户钱包({})不存在.", walletId);
  126 + throw exception(WALLET_NOT_FOUND);
  127 + }
123 128
124 // 2.1 扣除余额 129 // 2.1 扣除余额
125 int updateCounts = 0 ; 130 int updateCounts = 0 ;
@@ -129,9 +134,13 @@ public class PayWalletServiceImpl implements PayWalletService { @@ -129,9 +134,13 @@ public class PayWalletServiceImpl implements PayWalletService {
129 break; 134 break;
130 } 135 }
131 case RECHARGE_REFUND: { 136 case RECHARGE_REFUND: {
132 - // TODO 137 + updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price);
133 break; 138 break;
134 } 139 }
  140 + default: {
  141 + // TODO 其它类型待实现
  142 + throw new UnsupportedOperationException("待实现");
  143 + }
135 } 144 }
136 if (updateCounts == 0) { 145 if (updateCounts == 0) {
137 throw exception(WALLET_BALANCE_NOT_ENOUGH); 146 throw exception(WALLET_BALANCE_NOT_ENOUGH);
@@ -163,7 +172,10 @@ public class PayWalletServiceImpl implements PayWalletService { @@ -163,7 +172,10 @@ public class PayWalletServiceImpl implements PayWalletService {
163 walletMapper.updateWhenRecharge(payWallet.getId(), price); 172 walletMapper.updateWhenRecharge(payWallet.getId(), price);
164 break; 173 break;
165 } 174 }
166 - // TODO 其它类型;这里可以先跑异常;避免有业务搞错; 175 + default: {
  176 + // TODO 其它类型待实现
  177 + throw new UnsupportedOperationException("待实现");
  178 + }
167 } 179 }
168 180
169 // 2. 生成钱包流水 181 // 2. 生成钱包流水
@@ -173,4 +185,20 @@ public class PayWalletServiceImpl implements PayWalletService { @@ -173,4 +185,20 @@ public class PayWalletServiceImpl implements PayWalletService {
173 return walletTransactionService.createWalletTransaction(transactionCreateReqBO); 185 return walletTransactionService.createWalletTransaction(transactionCreateReqBO);
174 } 186 }
175 187
  188 + @Override
  189 + public void freezePrice(Long id, Integer price) {
  190 + int updateCounts = walletMapper.freezePrice(id, price);
  191 + if (updateCounts == 0) {
  192 + throw exception(WALLET_BALANCE_NOT_ENOUGH);
  193 + }
  194 + }
  195 +
  196 + @Override
  197 + public void unFreezePrice(Long id, Integer price) {
  198 + int updateCounts = walletMapper.unFreezePrice(id, price);
  199 + if (updateCounts == 0) {
  200 + throw exception(WALLET_FREEZE_PRICE_NOT_ENOUGH);
  201 + }
  202 + }
  203 +
176 } 204 }