Commit dc46dff62c4d96705b73c968db04880bafc6ad33
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 | 10 | `balance` int NOT NULL DEFAULT 0 COMMENT '余额,单位分', |
| 11 | 11 | `total_expense` int NOT NULL DEFAULT 0 COMMENT '累计支出,单位分', |
| 12 | 12 | `total_recharge` int NOT NULL DEFAULT 0 COMMENT '累计充值,单位分', |
| 13 | + `freeze_price` int NOT NULL DEFAULT 0 COMMENT '冻结金额,单位分', | |
| 13 | 14 | `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', |
| 14 | 15 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
| 15 | 16 | `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', |
| ... | ... | @@ -62,6 +63,7 @@ CREATE TABLE `pay_wallet_recharge` |
| 62 | 63 | `refund_pay_price` int NOT NULL DEFAULT 0 COMMENT '退款支付金额', |
| 63 | 64 | `refund_bonus_price` int NOT NULL DEFAULT 0 COMMENT '退款钱包赠送金额', |
| 64 | 65 | `refund_time` datetime NULL COMMENT '退款时间', |
| 66 | + `refund_status` int NOT NULL DEFAULT 0 COMMENT '退款状态', | |
| 65 | 67 | `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', |
| 66 | 68 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
| 67 | 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
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 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.lang.Assert; |
| 5 | +import cn.hutool.core.util.StrUtil; | |
| 5 | 6 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 7 | import cn.iocoder.yudao.module.member.api.address.AddressApi; |
| 7 | 8 | import cn.iocoder.yudao.module.member.api.address.dto.AddressRespDTO; |
| ... | ... | @@ -55,13 +56,13 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { |
| 55 | 56 | return; |
| 56 | 57 | } |
| 57 | 58 | if (DeliveryTypeEnum.PICK_UP.getType().equals(param.getDeliveryType())) { |
| 58 | - calculateByPickUp(param, result); | |
| 59 | + calculateByPickUp(param); | |
| 59 | 60 | } else if (DeliveryTypeEnum.EXPRESS.getType().equals(param.getDeliveryType())) { |
| 60 | 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 | 66 | if (param.getPickUpStoreId() == null) { |
| 66 | 67 | throw exception(PRICE_CALCULATE_DELIVERY_PRICE_PICK_UP_STORE_IS_EMPTY); |
| 67 | 68 | } |
| ... | ... | @@ -82,12 +83,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { |
| 82 | 83 | Assert.notNull(address, "收件人({})的地址,不能为空", param.getUserId()); |
| 83 | 84 | |
| 84 | 85 | // 情况一:全局包邮 |
| 85 | - if (isGlobalExpressFree(param, result)) { | |
| 86 | + if (isGlobalExpressFree(result)) { | |
| 86 | 87 | return; |
| 87 | 88 | } |
| 88 | 89 | |
| 89 | - // 情况二: | |
| 90 | - // 2.1 过滤出已选中的商品SKU | |
| 90 | + // 情况二:快递模版 | |
| 91 | + // 2.1 过滤出已选中的商品 SKU | |
| 91 | 92 | List<OrderItem> selectedItem = filterList(result.getItems(), OrderItem::getSelected); |
| 92 | 93 | Set<Long> deliveryTemplateIds = convertSet(selectedItem, OrderItem::getDeliveryTemplateId); |
| 93 | 94 | Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap = |
| ... | ... | @@ -103,11 +104,10 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { |
| 103 | 104 | /** |
| 104 | 105 | * 是否全局包邮 |
| 105 | 106 | * |
| 106 | - * @param param 计算信息 | |
| 107 | 107 | * @param result 计算结果 |
| 108 | 108 | * @return 是否包邮 |
| 109 | 109 | */ |
| 110 | - private boolean isGlobalExpressFree(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) { | |
| 110 | + private boolean isGlobalExpressFree(TradePriceCalculateRespBO result) { | |
| 111 | 111 | TradeConfigDO config = tradeConfigService.getTradeConfig(); |
| 112 | 112 | return config != null |
| 113 | 113 | && Boolean.TRUE.equals(config.getDeliveryExpressFreeEnabled()) // 开启包邮 |
| ... | ... | @@ -118,9 +118,9 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { |
| 118 | 118 | Map<Long, DeliveryExpressTemplateRespBO> expressTemplateMap, |
| 119 | 119 | TradePriceCalculateRespBO result) { |
| 120 | 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 | 124 | Long templateId = entry.getKey(); |
| 125 | 125 | List<OrderItem> orderItems = entry.getValue(); |
| 126 | 126 | DeliveryExpressTemplateRespBO templateBO = expressTemplateMap.get(templateId); |
| ... | ... | @@ -128,30 +128,12 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { |
| 128 | 128 | log.error("[calculateDeliveryPrice][不能计算快递运费,找不到 templateId({}) 对应的运费模板配置]", templateId); |
| 129 | 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 | 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 | 138 | TradePriceCalculatorHelper.recountAllPrice(result); |
| 157 | 139 | } |
| ... | ... | @@ -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 | 145 | * @param chargeMode 配送计费方式 |
| 166 | 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 | 150 | if (templateCharge == null) { |
| 173 | 151 | log.error("[calculateExpressFeeByChargeMode][计算快递运费时,找不到 SKU({}) 对应的运费模版]", orderItems); |
| 174 | 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 | 156 | int deliveryPrice; |
| 202 | - if (total <= templateCharge.getStartCount()) { | |
| 157 | + if (totalChargeValue <= templateCharge.getStartCount()) { | |
| 203 | 158 | deliveryPrice = templateCharge.getStartPrice(); |
| 204 | 159 | } else { |
| 205 | - double remainWeight = total - templateCharge.getStartCount(); | |
| 160 | + double remainWeight = totalChargeValue - templateCharge.getStartCount(); | |
| 206 | 161 | // 剩余重量/ 续件 = 续件的次数. 向上取整 |
| 207 | 162 | int extraNum = (int) Math.ceil(remainWeight / templateCharge.getExtraCount()); |
| 208 | 163 | int extraPrice = templateCharge.getExtraPrice() * extraNum; |
| 209 | 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 | 182 | TradePriceCalculatorHelper.recountPayPrice(item); |
| 230 | 183 | } |
| 231 | 184 | } |
| ... | ... | @@ -234,42 +187,38 @@ public class TradeDeliveryPriceCalculator implements TradePriceCalculator { |
| 234 | 187 | * 检查是否包邮 |
| 235 | 188 | * |
| 236 | 189 | * @param chargeMode 配送计费方式 |
| 237 | - * @param totalCount 总件数 | |
| 238 | - * @param totalWeight 总重量 | |
| 239 | - * @param totalVolume 总体积 | |
| 240 | - * @param totalPrice 总金额 | |
| 241 | 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 | 194 | if (templateFree == null) { |
| 246 | 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 | 211 | DeliveryExpressChargeModeEnum chargeModeEnum = DeliveryExpressChargeModeEnum.valueOf(chargeMode); |
| 249 | 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 | 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 | 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 | 91 | item -> item.setFreeCount(20).setFreePrice(100)); |
| 92 | 92 | // 准备 SP 运费模板数据 |
| 93 | 93 | templateRespBO = randomPojo(DeliveryExpressTemplateRespBO.class, |
| 94 | - item -> item.setChargeMode(DeliveryExpressChargeModeEnum.PIECE.getType()) | |
| 94 | + item -> item.setChargeMode(DeliveryExpressChargeModeEnum.COUNT.getType()) | |
| 95 | 95 | .setCharge(chargeBO).setFree(freeBO)); |
| 96 | 96 | } |
| 97 | 97 | |
| ... | ... | @@ -144,11 +144,11 @@ public class TradeDeliveryPriceCalculatorTest extends BaseMockitoUnitTest { |
| 144 | 144 | // 断言:SKU1 |
| 145 | 145 | assertThat(resultBO.getItems().get(0)) |
| 146 | 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 | 148 | // 断言:SKU2 |
| 149 | 149 | assertThat(resultBO.getItems().get(1)) |
| 150 | 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 | 152 | // 断言:SKU3 未选中 |
| 153 | 153 | assertThat(resultBO.getItems().get(2)) |
| 154 | 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 | 46 | ErrorCode WALLET_TRANSACTION_NOT_FOUND = new ErrorCode(1007007002, "未找到对应的钱包交易"); |
| 47 | 47 | ErrorCode WALLET_REFUND_AMOUNT_ERROR = new ErrorCode(1007007003, "钱包退款金额不对"); |
| 48 | 48 | ErrorCode WALLET_REFUND_EXIST = new ErrorCode(1007007004, "已经存在钱包退款"); |
| 49 | + ErrorCode WALLET_FREEZE_PRICE_NOT_ENOUGH = new ErrorCode(1007007005, "钱包冻结余额不足"); | |
| 49 | 50 | |
| 50 | 51 | // TODO @jason:把钱包充值,单独搞个错误码段哈; |
| 51 | 52 | |
| ... | ... | @@ -54,7 +55,12 @@ public interface ErrorCodeConstants { |
| 54 | 55 | ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_ID_ERROR = new ErrorCode(1007007007, "钱包充值更新支付状态失败,支付单编号不匹配"); |
| 55 | 56 | ErrorCode WALLET_RECHARGE_UPDATE_PAID_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(1007007008, "钱包充值更新支付状态失败,支付单状态不是【支付成功】状态"); |
| 56 | 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 | 64 | // ========== 示例订单 1007900000 ========== |
| 59 | 65 | ErrorCode DEMO_ORDER_NOT_FOUND = new ErrorCode(1007900000, "示例订单不存在"); |
| 60 | 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
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/wallet/PayWalletDO.java
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 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | 4 | import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; |
| 5 | 5 | import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; |
| 6 | +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; | |
| 6 | 7 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 7 | 8 | import com.baomidou.mybatisplus.annotation.TableId; |
| 8 | 9 | import com.baomidou.mybatisplus.annotation.TableName; |
| ... | ... | @@ -99,4 +100,11 @@ public class PayWalletRechargeDO extends BaseDO { |
| 99 | 100 | */ |
| 100 | 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<PayWalletDO> { |
| 56 | 56 | .eq(PayWalletDO::getId, id); |
| 57 | 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<PayWalletRechargeDO |
| 13 | 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 | 29 | */ |
| 30 | 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 | 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 | 5 | import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; |
| 6 | +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; | |
| 4 | 7 | import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO; |
| 5 | 8 | import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletRechargeConvert; |
| 6 | 9 | import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; |
| 10 | +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; | |
| 7 | 11 | import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; |
| 8 | 12 | import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO; |
| 9 | 13 | import cn.iocoder.yudao.module.pay.dal.mysql.wallet.PayWalletRechargeMapper; |
| 10 | 14 | import cn.iocoder.yudao.module.pay.enums.member.PayWalletBizTypeEnum; |
| 11 | 15 | import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; |
| 16 | +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; | |
| 12 | 17 | import cn.iocoder.yudao.module.pay.service.order.PayOrderService; |
| 18 | +import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; | |
| 13 | 19 | import lombok.extern.slf4j.Slf4j; |
| 14 | 20 | import org.springframework.stereotype.Service; |
| 15 | 21 | import org.springframework.transaction.annotation.Transactional; |
| ... | ... | @@ -24,6 +30,7 @@ import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.add |
| 24 | 30 | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; |
| 25 | 31 | import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; |
| 26 | 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 | 36 | * 钱包充值 Service 实现类 |
| ... | ... | @@ -47,6 +54,8 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { |
| 47 | 54 | private PayWalletService payWalletService; |
| 48 | 55 | @Resource |
| 49 | 56 | private PayOrderService payOrderService; |
| 57 | + @Resource | |
| 58 | + private PayRefundService payRefundService; | |
| 50 | 59 | |
| 51 | 60 | @Override |
| 52 | 61 | @Transactional(rollbackFor = Exception.class) |
| ... | ... | @@ -98,6 +107,113 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService { |
| 98 | 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 | 217 | private PayOrderDO validateWalletRechargerCanPaid(PayWalletRechargeDO walletRecharge, Long payOrderId) { |
| 102 | 218 | // 1.1 校验充值记录的支付状态 |
| 103 | 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 | 50 | /** |
| 51 | 51 | * 扣减钱包余额 |
| 52 | 52 | * |
| 53 | - * @param userId 用户 id | |
| 54 | - * @param userType 用户类型 | |
| 53 | + * @param walletId 钱包 id | |
| 55 | 54 | * @param bizId 业务关联 id |
| 56 | 55 | * @param bizType 业务关联分类 |
| 57 | 56 | * @param price 扣减金额 |
| 58 | 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 | 71 | PayWalletTransactionDO addWalletBalance(Long walletId, String bizId, |
| 73 | 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 | 68 | if (orderExtension == null) { |
| 69 | 69 | throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); |
| 70 | 70 | } |
| 71 | + PayWalletDO wallet = getOrCreateWallet(userId, userType); | |
| 71 | 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 | 76 | @Override |
| ... | ... | @@ -116,10 +117,14 @@ public class PayWalletServiceImpl implements PayWalletService { |
| 116 | 117 | } |
| 117 | 118 | |
| 118 | 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 | 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 | 129 | // 2.1 扣除余额 |
| 125 | 130 | int updateCounts = 0 ; |
| ... | ... | @@ -129,9 +134,13 @@ public class PayWalletServiceImpl implements PayWalletService { |
| 129 | 134 | break; |
| 130 | 135 | } |
| 131 | 136 | case RECHARGE_REFUND: { |
| 132 | - // TODO | |
| 137 | + updateCounts = walletMapper.updateWhenRechargeRefund(payWallet.getId(), price); | |
| 133 | 138 | break; |
| 134 | 139 | } |
| 140 | + default: { | |
| 141 | + // TODO 其它类型待实现 | |
| 142 | + throw new UnsupportedOperationException("待实现"); | |
| 143 | + } | |
| 135 | 144 | } |
| 136 | 145 | if (updateCounts == 0) { |
| 137 | 146 | throw exception(WALLET_BALANCE_NOT_ENOUGH); |
| ... | ... | @@ -163,7 +172,10 @@ public class PayWalletServiceImpl implements PayWalletService { |
| 163 | 172 | walletMapper.updateWhenRecharge(payWallet.getId(), price); |
| 164 | 173 | break; |
| 165 | 174 | } |
| 166 | - // TODO 其它类型;这里可以先跑异常;避免有业务搞错; | |
| 175 | + default: { | |
| 176 | + // TODO 其它类型待实现 | |
| 177 | + throw new UnsupportedOperationException("待实现"); | |
| 178 | + } | |
| 167 | 179 | } |
| 168 | 180 | |
| 169 | 181 | // 2. 生成钱包流水 |
| ... | ... | @@ -173,4 +185,20 @@ public class PayWalletServiceImpl implements PayWalletService { |
| 173 | 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 | } | ... | ... |