diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/opentype/OpenTypeController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/opentype/OpenTypeController.java index 8cbebb0..a03ef20 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/opentype/OpenTypeController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/opentype/OpenTypeController.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.member.controller.admin.opentype; import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; +import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @@ -53,11 +54,10 @@ public class OpenTypeController { return success(true); } - @GetMapping("/refund") - @Operation(summary = "发起钱包充值退款") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - public CommonResult refundWalletRecharge(@RequestParam("id") Long id) { - openTypeService.refundWalletRecharge(id, getClientIP()); + @PostMapping("/update-refunded") + @Operation(summary = "更新钱包充值为已退款") + public CommonResult updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) { + openTypeService.updateWalletRechargeRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId()); return success(true); } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeService.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeService.java index cbecd31..c8e6e74 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeService.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeService.java @@ -51,6 +51,11 @@ public interface OpenTypeService { */ Long payOpenType(AppPayOpenReqType dto); + /** + * 发起退款 + */ + void refundWalletRecharge(Long id, String ip); + /** * 当前用户是否是会员 */ @@ -86,5 +91,5 @@ public interface OpenTypeService { /** * 退款的回调 */ - void refundWalletRecharge(Long id, String ip); + void updateWalletRechargeRefunded(Long merchantOrderId, Long payRefundId); } \ No newline at end of file diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeServiceImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeServiceImpl.java index 2c128a7..dea3bbf 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeServiceImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/opentype/OpenTypeServiceImpl.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.member.service.opentype; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.exception.ErrorCode; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.member.client.service.ChatApi; import cn.iocoder.yudao.module.member.client.vo.chat.privatemsg.ChatPrivateTotalMessageTotalReqVO; @@ -11,22 +10,22 @@ import cn.iocoder.yudao.module.member.client.vo.chat.privatemsg.ChatPrivateTotal import cn.iocoder.yudao.module.member.controller.app.opentype.vo.AppPayOpenReqType; import cn.iocoder.yudao.module.member.dal.dataobject.config.MemberConfigDO; import cn.iocoder.yudao.module.member.dal.dataobject.opentype.OpenTypeUserDO; -import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberRecentTagDO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import cn.iocoder.yudao.module.member.dal.mysql.opentype.OpenTypeUserMapper; import cn.iocoder.yudao.module.member.dal.mysql.user.MemberRecentTagMapper; import cn.iocoder.yudao.module.member.enums.MemberErrorCode; import cn.iocoder.yudao.module.member.enums.opentype.OpenTypeUserStatusEnum; -import cn.iocoder.yudao.module.member.enums.tag.MemberRecentTagEnum; import cn.iocoder.yudao.module.member.service.config.MemberConfigService; import cn.iocoder.yudao.module.member.service.opentype.BO.OpenTypeExtBO; -import cn.iocoder.yudao.module.member.service.user.MemberRecentTagService; import cn.iocoder.yudao.module.member.service.user.MemberUserService; import cn.iocoder.yudao.module.pay.api.order.PayOrderApi; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO; import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; -import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants; +import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; +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.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -45,7 +44,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.member.dal.mysql.opentype.OpenTypeMapper; import java.time.Duration; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -56,6 +54,7 @@ import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.add import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum.*; /** * 会员类型 Service 实现类 @@ -81,6 +80,8 @@ public class OpenTypeServiceImpl implements OpenTypeService { private ChatApi chatApi; @Resource private PayOrderApi payOrderApi; + @Resource + private PayRefundApi payRefundApi; /** @@ -189,6 +190,38 @@ public class OpenTypeServiceImpl implements OpenTypeService { return payOrderId; } + /** + * 发起退款 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void refundWalletRecharge(Long id, String clientIP) { + OpenTypeUserDO openTypeUserDO = openTypeUserMapper.selectById(id); + if (ObjectUtil.isEmpty(openTypeUserDO)) { + log.error("[会员开通退款][recharge({}) clientIP({}) 不存在开通会员的订单,请进行处理!]", id, clientIP); + throw exception(MemberErrorCode.EMPTY, "会员开通记录不存在"); + } + // 校验钱包充值是否可以发起退款 + validateOpenTypeCanRefund(openTypeUserDO); + String walletRechargeId = String.valueOf(id); + String refundId = walletRechargeId + "-refund"; + // 创建退款订单 + PayRefundCreateReqDTO refundDto = new PayRefundCreateReqDTO(); + refundDto.setAppKey("vip"); + refundDto.setUserIp(clientIP); + refundDto.setMerchantOrderId(String.valueOf(openTypeUserDO.getId())); + refundDto.setMerchantRefundId(refundId + "-refund"); + refundDto.setReason("想退钱"); + refundDto.setPrice(openTypeUserDO.getPayPrice()); + Long payOrderRefundId = payRefundApi.createRefund(refundDto); + // 更新订单 + OpenTypeUserDO updateObj = new OpenTypeUserDO(); + updateObj.setId(openTypeUserDO.getId()); + updateObj.setRefundStatus(WAITING.getStatus()); + updateObj.setPayRefundId(payOrderRefundId); + openTypeUserMapper.updateById(updateObj); + } + /** * 当前用户是否是会员 */ @@ -312,7 +345,7 @@ public class OpenTypeServiceImpl implements OpenTypeService { // 查询订单 PayOrderRespDTO payOrder = validatePayOrderPaid(openTypeUserDO, payOrderId); - // 更新钱包充值的支付状态 + // 更新会员开通的支付状态 LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper() .set(OpenTypeUserDO::getPayChannelCode, payOrder.getChannelCode()) .set(OpenTypeUserDO::getPayStatus, true) @@ -330,16 +363,33 @@ public class OpenTypeServiceImpl implements OpenTypeService { */ @Override @Transactional(rollbackFor = Exception.class) - public void refundWalletRecharge(Long id, String clientIP) { + public void updateWalletRechargeRefunded(Long id, Long payRefundId) { OpenTypeUserDO openTypeUserDO = openTypeUserMapper.selectById(id); if (ObjectUtil.isEmpty(openTypeUserDO)) { - log.error("[会员开通退款][recharge({}) clientIP({}) 不存在开通会员的订单,请进行处理!]", id, clientIP); + log.error("[会员开通支付,退款的回调][id({}) payRefundId({}) 不存在开通会员的订单,请进行处理!]", id, payRefundId); } + PayRefundRespDTO payRefund = validateOpenTypeCanRefunded(openTypeUserDO, payRefundId); + OpenTypeUserDO updateObj = new OpenTypeUserDO().setId(id); + // 退款成功 + if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) { + updateObj.setRefundStatus(SUCCESS.getStatus()) + .setRefundTime(payRefund.getSuccessTime()) + .setRefundPayPrice(openTypeUserDO.getPayPrice()); + } + // 退款失败 + if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) { + updateObj.setRefundStatus(FAILURE.getStatus()); + } + // 更新会员开通的退款状态 + openTypeUserMapper.update(updateObj, new LambdaQueryWrapperX() + .eq(OpenTypeUserDO::getId, openTypeUserDO.getOpenTypeId()) + .eq(OpenTypeUserDO::getRefundStatus, WAITING.getStatus()) + ); } /** - * 验证会员支付订单是否成功 + * 验证开通会员支付订单是否成功 */ private PayOrderRespDTO validatePayOrderPaid(OpenTypeUserDO recharge, Long payOrderId) { // 1. 校验支付单是否存在 @@ -351,21 +401,63 @@ public class OpenTypeServiceImpl implements OpenTypeService { // 2.1 校验支付单已支付 if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) { log.error("[validatePayOrderPaid][验证会员支付订单是否成功({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]", recharge.getId(), payOrderId, toJsonString(payOrder)); - throw exception(MemberErrorCode.EMPTY, "会员充值更新支付状态失败,支付单状态不是【支付成功】状态"); + throw exception(MemberErrorCode.EMPTY, "开通会员更新支付状态失败,支付单状态不是【支付成功】状态"); } // 2.2 校验支付金额一致 if (notEqual(payOrder.getPrice(), recharge.getPayPrice())) { log.error("[validatePayOrderPaid][验证会员支付订单是否成功({}) payOrder({}) 支付金额不匹配,请进行处理!钱包 数据是:{},payOrder 数据是:{}]", recharge.getId(), payOrderId, toJsonString(recharge), toJsonString(payOrder)); - throw exception(MemberErrorCode.EMPTY, "钱包充值更新支付状态失败,支付单金额不匹配"); + throw exception(MemberErrorCode.EMPTY, "开通会员更新支付状态失败,支付单金额不匹配"); } // 2.3 校验支付订单的商户订单匹配 if (notEqual(payOrder.getMerchantOrderId(), recharge.getId().toString())) { log.error("[validatePayOrderPaid][验证会员支付订单是否成功({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]", recharge.getId(), payOrderId, toJsonString(payOrder)); - throw exception(MemberErrorCode.EMPTY, "钱包充值更新支付状态失败,支付单编号不匹配"); + throw exception(MemberErrorCode.EMPTY, "开通会员更新支付状态失败,支付单编号不匹配"); } return payOrder; } + /** + * 校验开通会员是否已退款 + */ + private PayRefundRespDTO validateOpenTypeCanRefunded(OpenTypeUserDO openTypeUserDO, Long payRefundId) { + // 校验退款订单匹配 + if (notEqual(openTypeUserDO.getPayRefundId(), payRefundId)) { + log.error("[校验开通会员是否已退款][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]", openTypeUserDO.getId(), payRefundId, toJsonString(openTypeUserDO)); + throw exception(MemberErrorCode.EMPTY, "开通会员退款更新失败,钱包退款单编号不匹配"); + } + // 校验退款订单 + PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId); + if (payRefund == null) { + log.error("[校验开通会员是否已退款][payRefund({})不存在]", payRefundId); + throw exception(MemberErrorCode.EMPTY, "开通会员退款更新失败,退款订单不存在"); + } + // 校验退款金额一致 + if (notEqual(payRefund.getRefundPrice(), openTypeUserDO.getPayPrice())) { + log.error("[校验开通会员是否已退款][开通会员({}) payRefund({}) 退款金额不匹配,请进行处理!钱包数据是:{},payRefund数据是:{}]", openTypeUserDO.getId(), payRefundId, toJsonString(openTypeUserDO), toJsonString(payRefund)); + throw exception(MemberErrorCode.EMPTY, "开通会员退款更新失败,退款单金额不匹配"); + } + // 校验退款订单商户订单是否匹配 + if (notEqual(payRefund.getMerchantOrderId(), openTypeUserDO.getId().toString())) { + log.error("[校验开通会员是否已退款][开通会员({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]", openTypeUserDO.getId(), payRefundId, toJsonString(payRefund)); + throw exception(MemberErrorCode.EMPTY, "开通会员退款更新失败,钱包退款单编号不匹配"); + } + return payRefund; + } + + /** + * 校验开通会员是否可以发起退款 + */ + private void validateOpenTypeCanRefund(OpenTypeUserDO walletRecharge) { + // 校验充值订单是否支付 + if (!walletRecharge.getPayStatus()) { + throw exception(MemberErrorCode.EMPTY, "开通会员发起退款失败,钱包充值订单未支付"); + } + // 校验充值订单是否已退款 + if (walletRecharge.getPayRefundId() != null) { + throw exception(MemberErrorCode.EMPTY, "开通会员发起退款失败,钱包充值订单已退款"); + } + } + private void validateOpenTypeExists(Long id) { if (openTypeMapper.selectById(id) == null) { throw exception(MemberErrorCode.OPEN_TYPE_NOT_EXISTS);