From c44ace601160ae562e85e38240c318591257f93b Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 15 Jul 2023 20:36:04 +0800 Subject: [PATCH] =?UTF-8?q?mall=20+=20pay=EF=BC=9A=201.=20=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E6=94=AF=E4=BB=98=E5=AE=9D=E7=9A=84=E9=80=80=E6=AC=BE?= =?UTF-8?q?=E9=87=8D=E6=9E=84=202.=20=E5=AE=8C=E6=88=90=20demo=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E7=9A=84=E9=80=80=E6=AC=BE=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/dto/refund/PayRefundRespDTO.java | 7 + .../impl/alipay/AbstractAlipayPayClient.java | 2 +- .../api/refund/dto/PayRefundCreateReqDTO.java | 20 +- .../module/pay/enums/ErrorCodeConstants.java | 7 +- .../pay/enums/order/PayOrderStatusEnum.java | 10 + .../module/pay/api/order/PayOrderApiImpl.java | 2 +- .../pay/api/refund/PayRefundApiImpl.java | 4 +- .../admin/notify/PayNotifyController.java | 4 +- .../admin/order/PayOrderController.java | 2 +- .../app/order/AppPayOrderController.java | 2 +- .../pay/convert/refund/PayRefundConvert.java | 18 +- .../pay/dal/dataobject/order/PayOrderDO.java | 6 +- .../dal/dataobject/refund/PayRefundDO.java | 13 - .../pay/dal/mysql/refund/PayRefundMapper.java | 48 ++- .../PayOrderRefundStatusEnum.java} | 6 +- .../service/demo/PayDemoOrderServiceImpl.java | 11 +- .../order/PayOrderExtensionService.java | 1 + .../pay/service/order/PayOrderService.java | 31 +- .../service/order/PayOrderServiceImpl.java | 94 ++++-- .../pay/service/refund/PayRefundService.java | 2 +- .../service/refund/PayRefundServiceImpl.java | 283 +++++++++--------- .../service/order/PayOrderServiceTest.java | 14 +- .../service/refund/PayRefundServiceTest.java | 32 +- 23 files changed, 339 insertions(+), 280 deletions(-) rename yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/{refund/PayRefundTypeEnum.java => order/PayOrderRefundStatusEnum.java} (74%) diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java index 5268eddc4..6d10dc9a0 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundRespDTO.java @@ -20,6 +20,13 @@ public class PayRefundRespDTO { */ private Integer status; + /** + * 外部退款号 + * + * 对应 PayRefundDO 的 no 字段 + */ + private String outRefundNo; + /** * 渠道退款单号 * diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java index 1da5f2631..657c64360 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java @@ -68,7 +68,6 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { - PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP()); + PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP()); return success(respVO); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java index b634909df..9100d3498 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java @@ -40,7 +40,7 @@ public class AppPayOrderController { @PostMapping("/submit") @Operation(summary = "提交支付订单") public CommonResult submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { - PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP()); + PayOrderSubmitRespVO respVO = payOrderService.submitOrder(reqVO, getClientIP()); return success(PayOrderConvert.INSTANCE.convert3(respVO)); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java index 12141377a..eb2b2053d 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java @@ -1,12 +1,11 @@ package cn.iocoder.yudao.module.pay.convert.refund; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*; -import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; import java.math.BigDecimal; @@ -44,8 +43,6 @@ public interface PayRefundConvert { PageResult convertPage(PageResult page); - List convertList02(List list); - /** * 退款订单DO 转 导出excel VO * @@ -67,7 +64,6 @@ public interface PayRefundConvert { payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl()); payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus()); payRefundExcelVO.setStatus(bean.getStatus()); - payRefundExcelVO.setType(bean.getType()); payRefundExcelVO.setReason(bean.getReason()); payRefundExcelVO.setUserIp(bean.getUserIp()); payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo()); @@ -84,12 +80,8 @@ public interface PayRefundConvert { return payRefundExcelVO; } - //TODO 太多需要处理了, 暂时不用 - @Mappings(value = { - @Mapping(source = "price", target = "payPrice"), - @Mapping(source = "id", target = "orderId"), - @Mapping(target = "status",ignore = true) - }) - PayRefundDO convert(PayOrderDO orderDO); + PayRefundDO convert(PayRefundCreateReqDTO bean); + + PayRefundRespDTO convert02(PayRefundDO bean); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java index 485aa0c90..62b43721e 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/order/PayOrderDO.java @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; @@ -127,7 +127,7 @@ public class PayOrderDO extends BaseDO { /** * 退款状态 * - * 枚举 {@link PayRefundTypeEnum} + * 枚举 {@link PayOrderRefundStatusEnum} */ private Integer refundStatus; /** @@ -137,7 +137,7 @@ public class PayOrderDO extends BaseDO { /** * 退款总金额,单位:分 */ - private Long refundPrice; + private Integer refundPrice; // ========== 渠道相关字段 ========== /** diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java index 898a13699..1a13a8115 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -104,12 +103,6 @@ public class PayRefundDO extends BaseDO { */ private Integer status; - /** - * 退款类型(部分退款,全部退款) - * - * 枚举 {@link PayRefundTypeEnum} - */ - private Integer type; /** * 支付金额,单位:分 */ @@ -157,12 +150,6 @@ public class PayRefundDO extends BaseDO { */ private String channelErrorMsg; - /** - * 支付渠道的额外参数 - * - * 参见 参数说明 - */ - private String channelExtras; /** * 支付渠道异步通知的内容 * diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java index 42bd00b81..629650e0f 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/refund/PayRefundMapper.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.module.pay.dal.mysql.refund; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; -import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; +import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.apache.ibatis.annotations.Mapper; import java.util.List; @@ -13,6 +15,34 @@ import java.util.List; @Mapper public interface PayRefundMapper extends BaseMapperX { + default Long selectCountByAppId(Long appId) { + return selectCount(PayRefundDO::getAppId, appId); + } + + default PayRefundDO selectByAppIdAndMerchantRefundId(Long appId, String merchantRefundId) { + return selectOne(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getMerchantRefundId, merchantRefundId)); + } + + default Long selectCountByAppIdAndOrderId(Long appId, Long orderId, Integer status) { + return selectCount(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getOrderId, orderId) + .eq(PayRefundDO::getStatus, status)); + } + + default PayRefundDO selectByAppIdAndNo(Long appId, String no) { + return selectOne(new LambdaQueryWrapperX() + .eq(PayRefundDO::getAppId, appId) + .eq(PayRefundDO::getNo, no)); + } + + default int updateByIdAndStatus(Long id, Integer status, PayRefundDO update) { + return update(update, new LambdaQueryWrapper() + .eq(PayRefundDO::getId, id).eq(PayRefundDO::getStatus, status)); + } + default PageResult selectPage(PayRefundPageReqVO reqVO) { return selectPage(reqVO, new QueryWrapperX() .eqIfPresent("app_id", reqVO.getAppId()) @@ -37,18 +67,4 @@ public interface PayRefundMapper extends BaseMapperX { .orderByDesc("id")); } - default Long selectCountByApp(Long appId) { - return selectCount(PayRefundDO::getAppId, appId); - } - - default PayRefundDO selectByReqNo(String reqNo) { - return selectOne("req_no", reqNo); - } - - // TODO 芋艿:要重构 - default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){ -// return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo); - return null; - } - } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundTypeEnum.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderRefundStatusEnum.java similarity index 74% rename from yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundTypeEnum.java rename to yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderRefundStatusEnum.java index 5fb10a399..b9dbcb67b 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/refund/PayRefundTypeEnum.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/enums/order/PayOrderRefundStatusEnum.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.pay.enums.refund; +package cn.iocoder.yudao.module.pay.enums.order; import cn.iocoder.yudao.framework.common.core.IntArrayValuable; import lombok.AllArgsConstructor; @@ -11,10 +11,10 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum PayRefundTypeEnum implements IntArrayValuable { +public enum PayOrderRefundStatusEnum implements IntArrayValuable { NO(0, "未退款"), - SOME(10, "部分退款"), + PART(10, "部分退款"), ALL(20, "全部退款") ; diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java index 71968f19b..c9bcbbd56 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java @@ -184,12 +184,17 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService { // 1. 校验订单是否可以退款 PayDemoOrderDO order = validateDemoOrderCanRefund(id); - // 2.1 创建退款单 + // 2.1 生成退款单号 + // 一般来说,用户发起退款的时候,都会单独插入一个售后维权表,然后使用该表的 id 作为 refundId + // 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示 + String refundId = order.getId() + "-refund"; + // 2.2 创建退款单 Long payRefundId = payRefundApi.createPayRefund(new PayRefundCreateReqDTO() .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用 - .setPayOrderId(order.getPayOrderId()) // 支付单号 + .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号 + .setMerchantRefundId(refundId) .setReason("想退钱").setPrice(order.getPrice()));// 价格信息 - // 2.2 更新退款单到 demo 订单 + // 2.3 更新退款单到 demo 订单 payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id) .setPayRefundId(payRefundId).setRefundPrice(order.getPrice())); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionService.java index f707bc0df..2db86a832 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderExtensionService.java @@ -7,6 +7,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +// TODO 芋艿:合并到 PayOrder 里; /** * 支付订单 Service 接口 * diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java index 5f4c6aa21..8aadac856 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java @@ -31,6 +31,15 @@ public interface PayOrderService { */ PayOrderDO getOrder(Long id); + /** + * 获得支付订单 + * + * @param appId 应用编号 + * @param merchantOrderId 商户订单编号 + * @return 支付订单 + */ + PayOrderDO getOrder(Long appId, String merchantOrderId); + /** * 获得指定应用的订单数量 * @@ -70,11 +79,11 @@ public interface PayOrderService { /** * 根据订单 ID 集合获取订单商品名称Map集合 * - * @param idList 订单 ID 集合 + * @param ids 订单 ID 集合 * @return 订单商品 map 集合 */ - default Map getOrderSubjectMap(Collection idList) { - List list = getOrderSubjectList(idList); + default Map getOrderSubjectMap(Collection ids) { + List list = getOrderSubjectList(ids); return CollectionUtils.convertMap(list, PayOrderDO::getId); } @@ -84,7 +93,7 @@ public interface PayOrderService { * @param reqDTO 创建请求 * @return 支付单编号 */ - Long createPayOrder(@Valid PayOrderCreateReqDTO reqDTO); + Long createOrder(@Valid PayOrderCreateReqDTO reqDTO); /** * 提交支付 @@ -94,8 +103,8 @@ public interface PayOrderService { * @param userIp 提交 IP * @return 提交结果 */ - PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO, - @NotEmpty(message = "提交 IP 不能为空") String userIp); + PayOrderSubmitRespVO submitOrder(@Valid PayOrderSubmitReqVO reqVO, + @NotEmpty(message = "提交 IP 不能为空") String userIp); /** * 通知支付单成功 @@ -103,6 +112,14 @@ public interface PayOrderService { * @param channelId 渠道编号 * @param notify 通知 */ - void notifyPayOrder(Long channelId, PayOrderRespDTO notify); + void notifyOrder(Long channelId, PayOrderRespDTO notify); + + /** + * 更新支付订单的退款金额 + * + * @param id 编号 + * @param incrRefundPrice 增加的退款金额 + */ + void updateOrderRefundPrice(Long id, Integer incrRefundPrice); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java index 884b182df..df3c6ce90 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java @@ -26,11 +26,10 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper; import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; -import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; @@ -48,6 +47,7 @@ import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; /** * 支付订单 Service 实现类 @@ -82,6 +82,11 @@ public class PayOrderServiceImpl implements PayOrderService { return orderMapper.selectById(id); } + @Override + public PayOrderDO getOrder(Long appId, String merchantOrderId) { + return orderMapper.selectByAppIdAndMerchantOrderId(appId, merchantOrderId); + } + @Override public Long getOrderCountByAppId(Long appId) { return orderMapper.selectCountByAppId(appId); @@ -104,7 +109,7 @@ public class PayOrderServiceImpl implements PayOrderService { } @Override - public Long createPayOrder(PayOrderCreateReqDTO reqDTO) { + public Long createOrder(PayOrderCreateReqDTO reqDTO) { // 校验 App PayAppDO app = appService.validPayApp(reqDTO.getAppId()); @@ -112,7 +117,7 @@ public class PayOrderServiceImpl implements PayOrderService { PayOrderDO order = orderMapper.selectByAppIdAndMerchantOrderId( reqDTO.getAppId(), reqDTO.getMerchantOrderId()); if (order != null) { - log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(), + log.warn("[createOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(), order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况 return order.getId(); } @@ -124,16 +129,16 @@ public class PayOrderServiceImpl implements PayOrderService { // 订单相关字段 .setStatus(PayOrderStatusEnum.WAITING.getStatus()) // 退款相关字段 - .setRefundStatus(PayRefundTypeEnum.NO.getStatus()).setRefundTimes(0).setRefundPrice(0L); + .setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus()).setRefundTimes(0).setRefundPrice(0); orderMapper.insert(order); return order.getId(); } @Override @Transactional(rollbackFor = Exception.class) - public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) { + public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) { // 1. 获得 PayOrderDO ,并校验其是否存在 - PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId()); + PayOrderDO order = validateOrderCanSubmit(reqVO.getId()); // 1.2 校验支付渠道是否有效 PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode()); PayClient client = payClientFactory.getPayClient(channel.getId()); @@ -167,16 +172,16 @@ public class PayOrderServiceImpl implements PayOrderService { return PayOrderConvert.INSTANCE.convert(order, unifiedOrderRespDTO); } - private PayOrderDO validatePayOrderCanSubmit(Long id) { + private PayOrderDO validateOrderCanSubmit(Long id) { PayOrderDO order = orderMapper.selectById(id); if (order == null) { // 是否存在 - throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 - throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); } if (LocalDateTimeUtils.beforeNow(order.getExpireTime())) { // 校验是否过期 - throw exception(ErrorCodeConstants.PAY_ORDER_IS_EXPIRED); + throw exception(PAY_ORDER_IS_EXPIRED); } return order; } @@ -184,14 +189,12 @@ public class PayOrderServiceImpl implements PayOrderService { private PayChannelDO validatePayChannelCanSubmit(Long appId, String channelCode) { // 校验 App appService.validPayApp(appId); - // 校验支付渠道是否有效 PayChannelDO channel = channelService.validPayChannel(appId, channelCode); - // 校验支付客户端是否正确初始化 PayClient client = payClientFactory.getPayClient(channel.getId()); if (client == null) { log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); - throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND); + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); } return channel; } @@ -226,28 +229,55 @@ public class PayOrderServiceImpl implements PayOrderService { @Override @Transactional(rollbackFor = Exception.class) - public void notifyPayOrder(Long channelId, PayOrderRespDTO notify) { + public void notifyOrder(Long channelId, PayOrderRespDTO notify) { // 校验支付渠道是否有效 PayChannelDO channel = channelService.validPayChannel(channelId); // 更新支付订单为已支付 TenantUtils.execute(channel.getTenantId(), () -> notifyPayOrder(channel, notify)); } + @Override + public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) { + PayOrderDO order = orderMapper.selectById(id); + if (order == null) { + throw exception(PAY_ORDER_NOT_FOUND); + } + if (!PayOrderStatusEnum.isSuccess(order.getStatus())) { + throw exception(PAY_REFUND_PRICE_EXCEED); + } + if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) { + throw exception(PAY_REFUND_PRICE_EXCEED); + } + + // 更新订单 + PayOrderDO updateObj = new PayOrderDO() + .setRefundPrice(order.getRefundPrice() + incrRefundPrice) + .setRefundTimes(order.getRefundTimes() + 1); + if (Objects.equals(updateObj.getRefundPrice(), order.getPrice())) { + updateObj.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus()); + } else { + updateObj.setStatus(PayOrderStatusEnum.CLOSED.getStatus()) + .setRefundStatus(PayOrderRefundStatusEnum.PART.getStatus()); + } + orderMapper.updateByIdAndStatus(id, PayOrderStatusEnum.SUCCESS.getStatus(), updateObj); + } + private void notifyPayOrder(PayChannelDO channel, PayOrderRespDTO notify) { // 情况一:支付成功的回调 if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) { - notifyPayOrderSuccess(channel, notify); + notifyOrderSuccess(channel, notify); return; } // 情况二:非支付成功的回调,进行忽略 log.info("[notifyPayOrder][非支付成功的回调({}),直接忽略]", toJsonString(notify)); } - private void notifyPayOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) { + private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) { // 1. 更新 PayOrderExtensionDO 支付成功 - PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify); + PayOrderExtensionDO orderExtension = updateOrderExtensionSuccess(notify); // 2. 更新 PayOrderDO 支付成功 - Pair order = updatePayOrderSuccess(channel, orderExtension, notify); + Pair order = updateOrderExtensionSuccess(channel, orderExtension, notify); if (order.getKey()) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调 return; } @@ -263,27 +293,27 @@ public class PayOrderServiceImpl implements PayOrderService { * @param notify 通知 * @return PayOrderExtensionDO 对象 */ - private PayOrderExtensionDO updatePayOrderExtensionSuccess(PayOrderRespDTO notify) { + private PayOrderExtensionDO updateOrderExtensionSuccess(PayOrderRespDTO notify) { // 1. 查询 PayOrderExtensionDO PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo()); if (orderExtension == null) { - throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND); + throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); } if (PayOrderStatusEnum.isSuccess(orderExtension.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 - log.info("[updatePayOrderSuccess][支付拓展单({}) 已经是已支付,无需更新为已支付]", orderExtension.getId()); + log.info("[updateOrderExtensionSuccess][支付拓展单({}) 已经是已支付,无需更新为已支付]", orderExtension.getId()); return orderExtension; } if (ObjectUtil.notEqual(orderExtension.getStatus(), PayOrderStatusEnum.WAITING.getStatus())) { // 校验状态,必须是待支付 - throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } // 2. 更新 PayOrderExtensionDO - int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(), + int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), orderExtension.getStatus(), PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build()); if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); } - log.info("[updatePayOrderSuccess][支付拓展单({}) 更新为已支付]", orderExtension.getId()); + log.info("[updateOrderExtensionSuccess][支付拓展单({}) 更新为已支付]", orderExtension.getId()); return orderExtension; } @@ -296,20 +326,20 @@ public class PayOrderServiceImpl implements PayOrderService { * @return key:是否之前已经成功回调 * value:PayOrderDO 对象 */ - private Pair updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, + private Pair updateOrderExtensionSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension, PayOrderRespDTO notify) { // 1. 判断 PayOrderDO 是否处于待支付 PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId()); if (order == null) { - throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND); + throw exception(PAY_ORDER_NOT_FOUND); } if (PayOrderStatusEnum.isSuccess(order.getStatus()) // 如果已经是成功,直接返回,不用重复更新 && Objects.equals(order.getSuccessExtensionId(), orderExtension.getId())) { - log.info("[updatePayOrderSuccess][支付订单({}) 已经是已支付,无需更新为已支付]", order.getId()); + log.info("[updateOrderExtensionSuccess][支付订单({}) 已经是已支付,无需更新为已支付]", order.getId()); return Pair.of(true, order); } if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付 - throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); } // 2. 更新 PayOrderDO @@ -320,9 +350,9 @@ public class PayOrderServiceImpl implements PayOrderService { .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId()) .notifyTime(LocalDateTime.now()).build()); if (updateCounts == 0) { // 校验状态,必须是待支付 - throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING); + throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING); } - log.info("[updatePayOrderSuccess][支付订单({}) 更新为已支付]", order.getId()); + log.info("[updateOrderExtensionSuccess][支付订单({}) 更新为已支付]", order.getId()); return Pair.of(false, order); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java index f7485c486..ef5c15803 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java @@ -62,6 +62,6 @@ public interface PayRefundService { * @param channelId 渠道编号 * @param notify 通知 */ - void notifyPayRefund(Long channelId, PayRefundRespDTO notify); + void notifyRefund(Long channelId, PayRefundRespDTO notify); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java index 0950fe471..03d22943a 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java @@ -1,8 +1,7 @@ package cn.iocoder.yudao.module.pay.service.refund; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.pay.config.PayProperties; import cn.iocoder.yudao.framework.pay.core.client.PayClient; @@ -10,22 +9,23 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO; import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO; +import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; -import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum; -import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; @@ -38,8 +38,11 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.time.LocalDateTime; import java.util.List; -import java.util.Objects; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; /** * 退款订单 Service 实现类 @@ -59,8 +62,6 @@ public class PayRefundServiceImpl implements PayRefundService { @Resource private PayRefundMapper refundMapper; - @Resource - private PayOrderMapper orderMapper; // TODO @jason:需要改成不直接操作 db; @Resource private PayOrderService orderService; @@ -80,7 +81,7 @@ public class PayRefundServiceImpl implements PayRefundService { @Override public Long getRefundCountByAppId(Long appId) { - return refundMapper.selectCountByApp(appId); + return refundMapper.selectCountByAppId(appId); } @Override @@ -96,82 +97,82 @@ public class PayRefundServiceImpl implements PayRefundService { @Override @Transactional(rollbackFor = Exception.class) public Long createPayRefund(PayRefundCreateReqDTO reqDTO) { - // 获得 PayOrderDO - PayOrderDO order = orderService.getOrder(reqDTO.getPayOrderId()); - // 校验订单是否存在 - if (Objects.isNull(order) ) { - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND); - } - // 校验 App - PayAppDO app = appService.validPayApp(order.getAppId()); - // 校验支付渠道是否有效 + // 1.1 校验 App + PayAppDO app = appService.validPayApp(reqDTO.getAppId()); + // 1.2 校验支付订单 + PayOrderDO order = validatePayOrderCanRefund(reqDTO); + // 1.3 校验支付渠道是否有效 PayChannelDO channel = channelService.validPayChannel(order.getChannelId()); - // 校验支付客户端是否正确初始化 PayClient client = payClientFactory.getPayClient(channel.getId()); if (client == null) { log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND); + throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND); + } + // 1.4 校验退款订单是否已经存在 + PayRefundDO refund = refundMapper.selectByAppIdAndMerchantRefundId( + app.getId(), reqDTO.getMerchantRefundId()); + if (refund != null) { + throw exception(ErrorCodeConstants.PAY_REFUND_EXISTS); } - // TODO 芋艿:待实现 - String merchantRefundId = "rrr" + RandomUtil.randomNumbers(16); - - // 校验退款的条件 - validatePayRefund(reqDTO, order); - // 退款类型 - PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME; - if (Objects.equals(reqDTO.getPrice(), order.getPrice())) { - refundType = PayRefundTypeEnum.ALL; - } - PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId()); - PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), - merchantRefundId); // TODO 芋艿:需要优化 - if (Objects.nonNull(payRefundDO)) { - // 退款订单已经提交过。 - //TODO 校验相同退款单的金额 - // TODO @jason:咱要不封装一个 ObjectUtils.equalsAny - if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())) { - //已成功退款 - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_SUCCEED); - } - //可以重复提交,保证 退款请求号 一致,由渠道保证幂等 - } else { - // 成功,插入退款单 状态为生成.没有和渠道交互 - // TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下; - payRefundDO = PayRefundDO.builder() - .appId(order.getAppId()) - .channelOrderNo(order.getChannelOrderNo()) - .channelCode(order.getChannelCode()) - .channelId(order.getChannelId()) - .orderId(order.getId()) - .merchantRefundId(merchantRefundId) - .notifyUrl(app.getRefundNotifyUrl()) - .payPrice(order.getPrice()) - .refundPrice(reqDTO.getPrice()) - .userIp(reqDTO.getUserIp()) - .merchantOrderId(order.getMerchantOrderId()) - .no(orderExtensionDO.getNo()) - .status(PayRefundStatusEnum.WAITING.getStatus()) - .reason(reqDTO.getReason()) - .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus()) - .type(refundType.getStatus()) - .build(); - refundMapper.insert(payRefundDO); - } - // TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下; + // 2.1 插入退款单 + refund = PayRefundConvert.INSTANCE.convert(reqDTO) + .setNo(generateRefundNo()).setOrderId(order.getId()) + .setChannelId(order.getChannelId()).setChannelCode(order.getChannelCode()) + // 商户相关的字段 + .setNotifyUrl(app.getRefundNotifyUrl()).setNotifyStatus(PayNotifyStatusEnum.WAITING.getStatus()) + // 渠道相关字段 + .setChannelOrderNo(order.getChannelOrderNo()) + // 退款相关字段 + .setStatus(PayRefundStatusEnum.WAITING.getStatus()) + .setPayPrice(order.getPrice()).setRefundPrice(reqDTO.getPrice()); + refundMapper.insert(refund); + // 2.2 向渠道发起退款申请 + PayOrderExtensionDO orderExtension = orderExtensionService.getOrderExtension(order.getSuccessExtensionId()); PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO(); unifiedReqDTO.setPrice(reqDTO.getPrice()) - .setOutTradeNo(orderExtensionDO.getNo()) - .setOutRefundNo(merchantRefundId) // TODO 芋艿:需要优化 + .setOutTradeNo(orderExtension.getNo()) + .setOutRefundNo(refund.getNo()) .setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl .setReason(reqDTO.getReason()); - // 向渠道发起退款申请 - client.unifiedRefund(unifiedReqDTO); - // 检查是否失败,失败抛出业务异常。 - // TODO 渠道的异常记录。 - // TODO @jason:可以先打个 warn log 哈; + PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); // TODO 增加一个 channelErrorCode、channelErrorMsg 字段 + // 2.3 处理退款返回 + notifyRefund(channel, refundRespDTO); + // 成功在 退款回调中处理 - return payRefundDO.getId(); + return refund.getId(); + } + + /** + * 校验支付订单是否可以退款 + * + * @param reqDTO 退款申请信息 + * @return 支付订单 + */ + private PayOrderDO validatePayOrderCanRefund(PayRefundCreateReqDTO reqDTO) { + PayOrderDO order = orderService.getOrder(reqDTO.getAppId(), reqDTO.getMerchantOrderId()); + if (order == null) { + throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND); + } + // 校验状态,必须是支付状态 + if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) { + throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS); + } + + // 是否已经全额退款 + if (PayOrderRefundStatusEnum.ALL.getStatus().equals(order.getRefundStatus())) { + throw exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED); + } + // 校验金额 退款金额不能大于原定的金额 + if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){ + throw exception(ErrorCodeConstants.PAY_REFUND_PRICE_EXCEED); + } + // 是否有退款中的订单 + if (refundMapper.selectCountByAppIdAndOrderId(reqDTO.getAppId(), order.getId(), + PayRefundStatusEnum.WAITING.getStatus()) > 0) { + throw exception(ErrorCodeConstants.PAY_REFUND_HAS_REFUNDING); + } + return order; } /** @@ -184,88 +185,80 @@ public class PayRefundServiceImpl implements PayRefundService { return payProperties.getCallbackUrl() + "/" + channel.getId(); } + private String generateRefundNo() { +// wx +// 2014 +// 10 +// 27 +// 20 +// 09 +// 39 +// 5522657 +// a690389285100 + // 目前的算法 + // 时间序列,年月日时分秒 14 位 + // 纯随机,6 位 TODO 芋艿:此处估计是会有问题的,后续在调整 + return DateUtil.format(LocalDateTime.now(), "yyyyMMddHHmmss") + // 时间序列 + RandomUtil.randomInt(100000, 999999) // 随机。为什么是这个范围,因为偷懒 + ; + } + @Override @Transactional(rollbackFor = Exception.class) - public void notifyPayRefund(Long channelId, PayRefundRespDTO notify) { + public void notifyRefund(Long channelId, PayRefundRespDTO notify) { + // 校验支付渠道是否有效 + channelService.validPayChannel(channelId); + // 通知结果 + // 校验支付渠道是否有效 - // TODO 芋艿:需要重构下这块的逻辑 PayChannelDO channel = channelService.validPayChannel(channelId); - if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) { - payRefundSuccess(notify); - } else { - // TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知 - } - } - - private void payRefundSuccess(PayRefundRespDTO refundNotify) { - // 校验退款单存在 - PayRefundDO refundDO = null; // TODO 芋艿:临时注释 -// PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), -// refundNotify.getReqNo()); - if (refundDO == null) { - // TODO 芋艿:临时注释 -// log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo()); - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND); - } - - // 得到已退金额 - PayOrderDO payOrderDO = orderService.getOrder(refundDO.getOrderId()); - Long refundedAmount = payOrderDO.getRefundPrice(); - - PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS; - if(Objects.equals(payOrderDO.getPrice(), refundedAmount+ refundDO.getRefundPrice())){ - //支付金额 = 已退金额 + 本次退款金额。 - orderStatus = PayOrderStatusEnum.CLOSED; - } - // 更新支付订单 - PayOrderDO updateOrderDO = new PayOrderDO(); - updateOrderDO.setId(refundDO.getOrderId()) - .setRefundPrice(refundedAmount + refundDO.getRefundPrice()) - .setStatus(orderStatus.getStatus()) - .setRefundTimes(payOrderDO.getRefundTimes() + 1) - .setRefundStatus(refundDO.getType()); - orderMapper.updateById(updateOrderDO); - // 更新退款订单 - PayRefundDO updateRefundDO = new PayRefundDO(); - updateRefundDO.setId(refundDO.getId()) - .setSuccessTime(refundNotify.getSuccessTime()) - // TODO 芋艿:如下两行,临时注释 -// .setChannelRefundNo(refundNotify.getChannelOrderNo()) -// .setNo(refundNotify.getTradeNo()) - .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); - refundMapper.updateById(updateRefundDO); - - // 插入退款通知记录 - // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调 - notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder() - .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build()); + TenantUtils.execute(channel.getTenantId(), () -> notifyRefund(channel, notify)); } - /** - * 校验是否进行退款 - * - * @param reqDTO 退款申请信息 - * @param order 原始支付订单信息 - */ - private void validatePayRefund(PayRefundCreateReqDTO reqDTO, PayOrderDO order) { - // 校验状态,必须是支付状态 - if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) { - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS); + private void notifyRefund(PayChannelDO channel, PayRefundRespDTO notify) { + if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) { + notifyRefundSuccess(channel, notify); + } else { + notifyRefundFailure(channel, notify); } - // 是否已经全额退款 - if (PayRefundTypeEnum.ALL.getStatus().equals(order.getRefundStatus())) { - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED); + } + + private void notifyRefundSuccess(PayChannelDO channel, PayRefundRespDTO notify) { + // 1.1 查询 PayRefundDO + PayRefundDO refund = refundMapper.selectByAppIdAndNo( + channel.getAppId(), notify.getOutRefundNo()); + if (refund == null) { + throw exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND); } - // 校验金额 退款金额不能大于 原定的金额 - if (reqDTO.getPrice() + order.getRefundPrice() > order.getPrice()){ - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_PRICE_PRICE_EXCEED); + if (PayRefundStatusEnum.isSuccess(refund.getStatus())) { // 如果已经是成功,直接返回,不用重复更新 + return; } - // 校验渠道订单号 - if (StrUtil.isEmpty(order.getChannelOrderNo())) { - throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_CHN_ORDER_NO_IS_NULL); + if (!PayRefundStatusEnum.WAITING.getStatus().equals(refund.getStatus())) { + throw exception(ErrorCodeConstants.PAY_REFUND_STATUS_IS_NOT_WAITING); } - //TODO 退款的期限 退款次数的控制 + + // 1.2 更新 PayRefundDO + PayRefundDO updateRefundObj = new PayRefundDO() + .setSuccessTime(notify.getSuccessTime()) + .setChannelRefundNo(notify.getChannelRefundNo()) + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) + .setChannelNotifyData(toJsonString(notify)); + int updateCounts = refundMapper.updateByIdAndStatus(refund.getId(), refund.getStatus(), updateRefundObj); + if (updateCounts == 0) { // 校验状态,必须是等待状态 + throw exception(ErrorCodeConstants.PAY_REFUND_STATUS_IS_NOT_WAITING); + } + + // 2. 更新订单 + orderService.updateOrderRefundPrice(refund.getOrderId(), refund.getRefundPrice()); + + // 3. 插入退款通知记录 + notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder() + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refund.getId()).build()); + } + + private void notifyRefundFailure(PayChannelDO channel, PayRefundRespDTO notify) { + // TODO 芋艿:未实现 } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java index 7d62c3cc4..8048e38ca 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java @@ -13,7 +13,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper; import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; @@ -85,7 +85,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest { o.setSuccessTime(LocalDateTime.of(2018, 1, 1, 10, 10, 2)); o.setNotifyTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15)); o.setSuccessExtensionId(1L); - o.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + o.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus()); o.setRefundTimes(0); o.setRefundPrice(0L); o.setChannelUserId("1008611"); @@ -106,7 +106,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest { // 测试 status 不匹配 orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); // 测试 refundStatus 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus()))); + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus()))); // 测试 createTime 不匹配 orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(LocalDateTime.of(2019, 1, 1, 10, 10, 1)))); @@ -118,7 +118,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest { reqVO.setMerchantOrderId(merchantOrderId); reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + reqVO.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus()); reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2018, 1, 1, 10, 1, 0), LocalDateTime.of(2018, 1, 1, 10, 1, 0)})); // 调用 PageResult pageResult = orderService.getOrderPage(reqVO); @@ -153,7 +153,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest { o.setSuccessTime(LocalDateTime.of(2018, 1, 1, 10, 10, 2)); o.setNotifyTime(LocalDateTime.of(2018, 1, 1, 10, 10, 15)); o.setSuccessExtensionId(1L); - o.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + o.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus()); o.setRefundTimes(0); o.setRefundPrice(0L); o.setChannelUserId("1008611"); @@ -175,7 +175,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest { // 测试 status 不匹配 orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setStatus(PayOrderStatusEnum.CLOSED.getStatus()))); // 测试 refundStatus 不匹配 - orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayRefundTypeEnum.ALL.getStatus()))); + orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setRefundStatus(PayOrderRefundStatusEnum.ALL.getStatus()))); // 测试 createTime 不匹配 orderMapper.insert(cloneIgnoreId(dbOrder, o -> o.setCreateTime(LocalDateTime.of(2019, 1, 1, 10, 10, 1)))); @@ -187,7 +187,7 @@ public class PayOrderServiceTest extends BaseDbUnitTest { reqVO.setMerchantOrderId(merchantOrderId); reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); reqVO.setStatus(PayOrderStatusEnum.SUCCESS.getStatus()); - reqVO.setRefundStatus(PayRefundTypeEnum.NO.getStatus()); + reqVO.setRefundStatus(PayOrderRefundStatusEnum.NO.getStatus()); reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2018, 1, 1, 10, 1, 0), LocalDateTime.of(2018, 1, 1, 10, 1, 0)})); // 调用 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java index 8365f1ab6..480256c07 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java @@ -11,7 +11,7 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper; import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum; import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; -import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum; +import cn.iocoder.yudao.module.pay.enums.order.PayOrderRefundStatusEnum; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService; @@ -64,11 +64,11 @@ public class PayRefundServiceTest extends BaseDbUnitTest { o.setOrderId(1L); o.setNo("OT0000001"); o.setMerchantOrderId("MOT0000001"); - o.setMerchantRefundNo("MRF0000001"); + o.setMerchantRefundId("MRF0000001"); o.setNotifyUrl("https://www.cancanzi.com"); o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); - o.setType(PayRefundTypeEnum.SOME.getStatus()); + o.setType(PayOrderRefundStatusEnum.PART.getStatus()); o.setPayPrice(100); o.setRefundPrice(500); o.setReason("就是想退款了,你有意见吗"); @@ -77,10 +77,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest { o.setChannelRefundNo("CHR0000001"); o.setChannelErrorCode(""); o.setChannelErrorMsg(""); - o.setChannelExtras(""); - o.setExpireTime(LocalDateTime.of(2021, 1, 1, 10, 10, 30)); o.setSuccessTime(LocalDateTime.of(2021, 1, 1, 10, 10, 15)); - o.setNotifyTime(LocalDateTime.of(2021, 1, 1, 10, 10, 20)); o.setCreateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 10)); o.setUpdateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 35)); }); @@ -90,14 +87,14 @@ public class PayRefundServiceTest extends BaseDbUnitTest { // 测试 channelCode 不匹配 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); // 测试 merchantRefundNo 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112"))); + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112"))); // 测试 notifyStatus 不匹配 refundMapper.insert( cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus()))); // 测试 status 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus()))); + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()))); // 测试 type 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus()))); + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayOrderRefundStatusEnum.ALL.getStatus()))); // 测试 createTime 不匹配 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10)))); @@ -108,7 +105,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest { reqVO.setMerchantRefundNo("MRF0000001"); reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); - reqVO.setType(PayRefundTypeEnum.SOME.getStatus()); + reqVO.setType(PayOrderRefundStatusEnum.PART.getStatus()); reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)})); // 调用 @@ -129,11 +126,11 @@ public class PayRefundServiceTest extends BaseDbUnitTest { o.setOrderId(1L); o.setNo("OT0000001"); o.setMerchantOrderId("MOT0000001"); - o.setMerchantRefundNo("MRF0000001"); + o.setMerchantRefundId("MRF0000001"); o.setNotifyUrl("https://www.cancanzi.com"); o.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); o.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); - o.setType(PayRefundTypeEnum.SOME.getStatus()); + o.setType(PayOrderRefundStatusEnum.PART.getStatus()); o.setPayPrice(100); o.setRefundPrice(500); o.setReason("就是想退款了,你有意见吗"); @@ -142,10 +139,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest { o.setChannelRefundNo("CHR0000001"); o.setChannelErrorCode(""); o.setChannelErrorMsg(""); - o.setChannelExtras(""); - o.setExpireTime(LocalDateTime.of(2021, 1, 1, 10, 10, 30)); o.setSuccessTime(LocalDateTime.of(2021, 1, 1, 10, 10, 15)); - o.setNotifyTime(LocalDateTime.of(2021, 1, 1, 10, 10, 20)); o.setCreateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 10)); o.setUpdateTime(LocalDateTime.of(2021, 1, 1, 10, 10, 35)); }); @@ -155,14 +149,14 @@ public class PayRefundServiceTest extends BaseDbUnitTest { // 测试 channelCode 不匹配 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setChannelCode(PayChannelEnum.ALIPAY_APP.getCode()))); // 测试 merchantRefundNo 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundNo("MRF1111112"))); + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setMerchantRefundId("MRF1111112"))); // 测试 notifyStatus 不匹配 refundMapper.insert( cloneIgnoreId(dbRefund, o -> o.setNotifyStatus(PayOrderNotifyStatusEnum.FAILURE.getStatus()))); // 测试 status 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.CLOSE.getStatus()))); + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setStatus(PayRefundStatusEnum.FAILURE.getStatus()))); // 测试 type 不匹配 - refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayRefundTypeEnum.ALL.getStatus()))); + refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setType(PayOrderRefundStatusEnum.ALL.getStatus()))); // 测试 createTime 不匹配 refundMapper.insert(cloneIgnoreId(dbRefund, o -> o.setCreateTime(LocalDateTime.of(2022, 1, 1, 10, 10, 10)))); @@ -174,7 +168,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest { reqVO.setMerchantRefundNo("MRF0000001"); reqVO.setNotifyStatus(PayOrderNotifyStatusEnum.SUCCESS.getStatus()); reqVO.setStatus(PayRefundStatusEnum.SUCCESS.getStatus()); - reqVO.setType(PayRefundTypeEnum.SOME.getStatus()); + reqVO.setType(PayOrderRefundStatusEnum.PART.getStatus()); reqVO.setCreateTime((new LocalDateTime[]{LocalDateTime.of(2021, 1, 1, 10, 10, 10), LocalDateTime.of(2021, 1, 1, 10, 10, 12)})); // 调用