Forráskód Böngészése

油管用户端售后

zoujiajian 8 hónapja
szülő
commit
d84c773ba8

+ 10 - 0
netflix-common/src/main/java/com/cyksj/common/constant/Constant.java

@@ -644,6 +644,16 @@ public interface Constant {
 	 */
 	String ERROR_NETFLIX_CHANGE_OTHER_SKU_KEY = "error_netflix_change_other_sku_key";
 
+	/**
+	 * 异常youtube车票更换其他规格key
+	 */
+	String ERROR_YOUTUBE_CHANGE_OTHER_SKU_KEY = "error_youtube_change_other_sku_key";
+
+	/**
+	 * 异常youtube车票退款配置key
+	 */
+	String YOUTUBE_REFUND_INFO_KEY = "youtube_refund_info_key";
+
 	/**
 	 * 银河币消费返回比例
 	 */

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/model/dto/OrderRefundDto.java

@@ -0,0 +1,20 @@
+package com.cyksj.model.dto;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * 项目名: yhlxj321312312312312312
+ * 文件名: OrderRefundDto
+ * 创建者: JavaZou
+ * 创建时间:2025/12/19 17:28
+ */
+@Getter
+@Setter
+public class OrderRefundDto {
+	private BigDecimal refundMoney;
+
+	private BigDecimal refundBalance;
+}

+ 1 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/UserBalanceSourceRecord.java

@@ -50,6 +50,7 @@ public class UserBalanceSourceRecord extends BaseEntity {
 		vip_refund("会员退款"),
 		TICKET_REPLACE("车票替换"),
 		vip_order_close("会员订单关闭"),
+		yt_refund("油管掉会员退款")
 		;
 		String desc;
 

+ 2 - 1
netflix-dao/src/main/java/com/cyksj/model/entity/UserTicketClearedRecord.java

@@ -69,7 +69,8 @@ public class UserTicketClearedRecord extends BaseEntity{
 		vip_deduct("会员抵扣"),
 		codex_deduct("codex抵扣"),
 		cc_deduct("claude code抵扣"),
-		recover_replace("奈飞恢复直接替换")
+		recover_replace("奈飞恢复直接替换"),
+		yt_refund("油管掉会员退款")
 		;
 		String desc;
 

+ 8 - 0
netflix-service/src/main/java/com/cyksj/service/relation/GroupRelationFrontService.java

@@ -124,4 +124,12 @@ public interface GroupRelationFrontService {
 	NetflixRecoverReceivedGptConditionDto netflixGptMirrorCondition(long userId);
 
 	void netflixRecoverGetGptMirror(long userId);
+
+	void youtubeSubmitFrozen(ErrorNetflixChangeReq req);
+
+	void errorYoutubeChangeOtherTicket(ErrorNetflixChangeReq req) throws Exception;
+
+	void errorYoutubeRefund(ErrorNetflixChangeReq req) throws Exception;
+
+	List<GoodsCategorySkuView> getYoutubeReplaceOtherSkus() throws Exception;
 }

+ 219 - 0
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java

@@ -32,6 +32,7 @@ import com.cyksj.mapper.vip.VipGoodsSkuMapper;
 import com.cyksj.mapper.vip.VipUserMapper;
 import com.cyksj.model.dto.ErrorNetflixChangeOtherSkuTicketDto;
 import com.cyksj.model.dto.NetflixRecoverReceivedGptConditionDto;
+import com.cyksj.model.dto.OrderRefundDto;
 import com.cyksj.model.dto.TicketChangeDto;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.AccountView;
@@ -2385,6 +2386,171 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 		}
 	}
 
+	@Override
+	public void youtubeSubmitFrozen(ErrorNetflixChangeReq req) {
+		Long relationId = req.getRelationId();
+		Long userId = req.getUserId();
+		//校验账号
+		GroupsRelationView netflixRelationView = checkYoutubeTime(relationId, userId);
+		Date expiryTime = netflixRelationView.getExpiryTime();
+		Long betweenDay = null;
+		if (expiryTime != null) {
+			//保留天数
+			betweenDay = DateUtil.betweenDay(DateTime.now(), expiryTime, false) + 1;
+		}
+		groupsRelationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
+				.set(GroupsRelation::getIsFrozen, Boolean.TRUE)
+				//冻结12个月
+				.set(GroupsRelation::getUnfrozenTime, DateUtil.offsetMonth(DateTime.now(), 12))
+				.set(betweenDay != null, GroupsRelation::getReservedDays, betweenDay)
+				.eq(GroupsRelation::getId, netflixRelationView.getId())
+				.eq(GroupsRelation::getUserId, netflixRelationView.getUserId()));
+	}
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	public void errorYoutubeChangeOtherTicket(ErrorNetflixChangeReq req) throws Exception {
+		Long relationId = req.getRelationId();
+		Long userId = req.getUserId();
+		//校验账号
+		GroupsRelationView relationView = checkYoutubeTime(relationId, userId);
+		String key = RedisService.key.NETFLIX_CHANGE_TICKET_KEY.getName() + relationId;
+		if (!redisService.setNx(key, relationId, RedisService.key.NETFLIX_CHANGE_TICKET_KEY.getTimeout())) {
+			throw BusinessRuntimeException.getInstance("正在更换其他车票中..");
+		}
+		Date expiryTime = relationView.getExpiryTime();
+		SysConfig sysConfig = sysConfigMapper.selectOne(Wrappers.lambdaQuery(SysConfig.class)
+				.eq(SysConfig::getSysKey, Constant.ERROR_YOUTUBE_CHANGE_OTHER_SKU_KEY)
+				.last("limit 1"));
+		List<ErrorNetflixChangeOtherSkuTicketDto> errorNetflixChangeOtherSkuTicketDtos = Jsons.parseList(sysConfig.getSysValue(), ErrorNetflixChangeOtherSkuTicketDto.class);
+		//替换车票
+		ErrorNetflixChangeOtherSkuTicketDto errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(0);
+		List<Long> skuIds = errorNetflixChangeOtherSkuTicketDto.getSkuIds();
+		try {
+			for (Long skuId : skuIds) {
+				TicketChangeDto changeDto = getTicketAndGenerateOrder(userId, skuId, null, null);
+				if (changeDto == null) {
+					throw BusinessRuntimeException.getInstance("系统繁忙,请重试");
+				}
+				//替换记录
+				NetflixUserChangeOtherTicketRecord changeOtherTicketRecord = new NetflixUserChangeOtherTicketRecord();
+				changeOtherTicketRecord.setUserId(userId);
+				changeOtherTicketRecord.setRelationId(relationId);
+				changeOtherTicketRecord.setOtherRelationId(changeDto.getOtherRelationId());
+				changeOtherTicketRecord.setRelateOrderId(changeDto.getOtherOrderId());
+				//该车票订单id
+				changeOtherTicketRecord.setOrderId(relationView.getNfOriOrderId());
+				changeOtherTicketRecord.setStartTime(relationView.getStartTime());
+				changeOtherTicketRecord.setExpiryTime(expiryTime);
+				netflixUserChangeOtherTicketRecordMapper.insert(changeOtherTicketRecord);
+			}
+		} finally {
+			redisService.del(key);
+		}
+	}
+
+	@Override
+	public void errorYoutubeRefund(ErrorNetflixChangeReq req) throws Exception {
+		Long relationId = req.getRelationId();
+		Long userId = req.getUserId();
+		//校验奈飞账号状态
+		GroupsRelationView relationView = checkYoutubeTime(relationId, userId);
+		Long ticketUserId = relationView.getUserId();
+		OrderDon orderDon = orderDonMapper.selectById(relationView.getNfOriOrderId());
+		if (orderDon != null) {
+			//paypal 或者超过一年 联系客服退款
+			if (orderDon.getType() == OrderDon.Type.PAYPAL || (orderDon.getMoney().compareTo(BigDecimal.ZERO) > 0 && orderDon.getCreatedTime().after(DateUtil.offsetMonth(orderDon.getCreatedTime(), -12)))) {
+				throw BusinessRuntimeException.getInstance("请联系客服退款");
+			}
+			SysConfig sysConfig = sysConfigMapper.selectOne(Wrappers.lambdaQuery(SysConfig.class)
+					.eq(SysConfig::getSysKey, Constant.YOUTUBE_REFUND_INFO_KEY)
+					.last("limit 1"));
+			OrderRefundDto orderRefundDto = Jsons.parseObject(sysConfig.getSysValue(), OrderRefundDto.class);
+			BigDecimal refundBalance = orderRefundDto.getRefundBalance();
+			BigDecimal refundMoney = orderRefundDto.getRefundMoney();
+			//超过订单金额 让联系客服退款
+			if (refundMoney.compareTo(orderDon.getMoney()) > 0) {
+				throw BusinessRuntimeException.getInstance("请联系客服退款");
+			}
+			String outNo = StrUtil.EMPTY;
+			//金额退款
+			if (refundMoney.compareTo(BigDecimal.ZERO) > 0) {
+				//调用第三方接口退款
+				OrderRefundReq refundReq = new OrderRefundReq();
+				refundReq.setRefundMoney(refundMoney);
+				refundReq.setRefundType("money");
+				try {
+					outNo = orderRefundService.centerRefund(refundReq, orderDon);
+				} catch (Exception e) {
+					throw BusinessRuntimeException.getInstance("请联系客服退款");
+				}
+				orderDon.setRefundMoney(refundMoney);
+			}
+			//余额退款
+			if (refundBalance.compareTo(BigDecimal.ZERO) > 0) {
+				userBenefitsService.addUserBalance(ticketUserId, refundBalance, UserBalanceSourceRecord.Source.yt_refund, 0L, orderDon.getId(), UserBalanceSourceRecord.Source.yt_refund.getDesc(), true);
+			}
+			orderDon.setStatus(OrderDon.Status.refund);
+			if (refundBalance.compareTo(BigDecimal.ZERO) > 0) {
+				orderDon.setRefundBalance(refundBalance);
+			}
+			orderDon.setRefundDesc("油管掉会员退款");
+			orderDonMapper.updateById(orderDon);
+
+			GoodsDon goodsDon = goodsDonMapper.selectById(orderDon.getGoodsId());
+			GoodsDonSku sku = goodsDonSkuMapper.selectById(orderDon.getSkuId());
+			//记录退款类型
+			String refundType = getRefundType(refundMoney, refundBalance, orderDon);
+			orderRefundService.refundRecord(orderDon, refundMoney, refundBalance, "系统", goodsDon, sku, outNo, refundType, null);
+			//清除车票
+			GroupsRelation clearRelation = groupsRelationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class)
+					.eq(GroupsRelation::getId, relationId)
+					.eq(GroupsRelation::getUserId, relationView.getUserId())
+					.last("limit 1"));
+			if (clearRelation != null) {
+				clearService.clearTicket(clearRelation, UserTicketClearedRecord.Source.yt_refund);
+			}
+		}
+	}
+
+	@Override
+	public List<GoodsCategorySkuView> getYoutubeReplaceOtherSkus() throws Exception {
+		SysConfig sysConfig = sysConfigMapper.selectOne(Wrappers.lambdaQuery(SysConfig.class)
+				.eq(SysConfig::getSysKey, Constant.ERROR_YOUTUBE_CHANGE_OTHER_SKU_KEY)
+				.last("limit 1"));
+		List<ErrorNetflixChangeOtherSkuTicketDto> errorNetflixChangeOtherSkuTicketDtos = Jsons.parseList(sysConfig.getSysValue(), ErrorNetflixChangeOtherSkuTicketDto.class);
+		ErrorNetflixChangeOtherSkuTicketDto errorNetflixChangeOtherSkuTicketDto = errorNetflixChangeOtherSkuTicketDtos.get(0);
+		List<GoodsCategorySkuView> goodsCategorySkuViews = beanSearcher.searchAll(GoodsCategorySkuView.class, MapUtils.builder()
+				.field(GoodsCategorySkuView::getSkuId, errorNetflixChangeOtherSkuTicketDto.getSkuIds()).op(Operator.InList)
+				.orderBy(GoodsCategorySkuView::getPrice).desc()
+				.build());
+		return goodsCategorySkuViews;
+	}
+
+	private GroupsRelationView checkYoutubeTime(Long relationId, Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		GroupsRelationView netflixRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getId, relationId)
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getGoodsId, Constant.NETFLIX_GID)
+				.build());
+		if (netflixRelationView == null) {
+			throw BusinessRuntimeException.getInstance("车票不存在");
+		}
+		if (netflixRelationView.getIsFrozen()) {
+			throw BusinessRuntimeException.getInstance("您的车票已冻结");
+		}
+		OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class).in(OrderDon::getUserId, userIdList).eq(OrderDon::getRelationId, relationId).notIn(OrderDon::getStatus, Constant.noOrderAllStatus).last("limit 1"));
+		if (orderDon == null) {
+			throw BusinessRuntimeException.getInstance("订单不存在");
+		}
+		DateTime availableHandleTime = DateUtil.parse("2025-12-19 17:00:00");
+		if (orderDon.getCreatedTime().after(availableHandleTime)) {
+			throw BusinessRuntimeException.getInstance("不可进行售后,请联系客服");
+		}
+		netflixRelationView.setNfOriOrderId(orderDon.getId());
+		return netflixRelationView;
+	}
+
 	private DistributePosterGoodsDetailView setGoodsPoster(RenewalView ticket, User user, List<Long> userIds) {
 		DistributePosterGoodsDetailView distributePosterGoodsDetailView = beanSearcher.searchFirst(DistributePosterGoodsDetailView.class, MapUtils.flatBuilder(request.getParameterMap())
 				.field(DistributePosterGoodsFrontView::getGoodsId, ticket.getGoodsId()).build());
@@ -2795,4 +2961,57 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 			}
 		}
 	}
+
+
+	public OrderRefundDto getYoutubeRefundInfo(GroupsRelationView netflixRelationView) {
+		OrderDon orderDon = orderDonMapper.selectById(netflixRelationView.getNfOriOrderId());
+		Long betweenDay = Long.valueOf(netflixRelationView.getReservedDays());
+		//未冻结时,使用车票时长
+		if (!netflixRelationView.getIsFrozen()) {
+			Date expiryTime = netflixRelationView.getExpiryTime();
+			if (expiryTime == null) {
+				//用订单时间
+				GoodsDonSku sku = goodsDonSkuMapper.selectById(orderDon.getSkuId());
+				expiryTime = DateUtil.offsetMonth(orderDon.getCreatedTime(), sku.getMonths());
+			}
+			betweenDay= DateUtil.betweenDay(DateTime.now(), expiryTime, false) + 1;
+		}
+		GoodsDonSku oriTicketSku = goodsDonSkuMapper.selectById(orderDon.getSkuId());
+		//每天单价
+		BigDecimal dayPrice = oriTicketSku.getPrice().divide(BigDecimal.valueOf(oriTicketSku.getMonths() * 30), RoundingMode.DOWN);
+		//需要退款金额
+		BigDecimal totalRefundMoney = dayPrice.multiply(BigDecimal.valueOf(betweenDay));
+		BigDecimal refundBalance = BigDecimal.ZERO;
+		BigDecimal refundMoney = BigDecimal.ZERO;
+		String outNo = StrUtil.EMPTY;
+		//订单金额为0 直接退款余额
+		if (orderDon.getMoney().compareTo(BigDecimal.ZERO) == 0) {
+			refundBalance = totalRefundMoney;
+		} else {
+			BigDecimal remainRefundBalanceMoney = null;
+			if (orderDon.getMoney().compareTo(totalRefundMoney) < 0) {
+				remainRefundBalanceMoney = totalRefundMoney.subtract(orderDon.getMoney());
+				refundMoney = orderDon.getMoney();
+			}
+			if (remainRefundBalanceMoney != null) {
+				refundBalance = refundBalance.add(remainRefundBalanceMoney);
+			}
+		}
+		OrderRefundDto orderRefundDto = new OrderRefundDto();
+		orderRefundDto.setRefundBalance(refundBalance);
+		orderRefundDto.setRefundMoney(refundMoney);
+		return orderRefundDto;
+	}
+
+	private String getRefundType(BigDecimal refundMoney, BigDecimal refundBalance, OrderDon orderDon) {
+		String refundType;
+		if (refundMoney.compareTo(BigDecimal.ZERO) > 0 && refundBalance.compareTo(BigDecimal.ZERO) > 0) {
+			refundType = "bxj";
+		} else if (refundMoney.compareTo(BigDecimal.ZERO) == 0 && refundBalance.compareTo(BigDecimal.ZERO) > 0) {
+			refundType = "balance";
+		} else {
+			refundType = orderDon.getType().name();
+		}
+		return refundType;
+	}
 }

+ 47 - 0
netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java

@@ -1570,4 +1570,51 @@ public class GroupRelationController {
         groupRelationFrontService.netflixRecoverGetGptMirror(userId);
         return GatewayResponse.SUCCESS.newBuilder().toResult();
     }
+
+
+
+    //=====================================================youtube===================================================================
+
+    /**
+     * youtube可替换其他规格车票列表
+     */
+    @GetMapping("/get/youtube/replace/other/skus")
+    public Result<List<GoodsCategorySkuView>> getYoutubeReplaceOtherSkus() throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        List<GoodsCategorySkuView> goodsCategorySkuViews = groupRelationFrontService.getYoutubeReplaceOtherSkus();
+        return GatewayResponse.SUCCESS.newBuilder().toResult(goodsCategorySkuViews);
+    }
+
+    /**
+     * 油管申请冻结
+     */
+    @PostMapping("/youtube/submit/frozen")
+    public Result<String> youtubeSubmitFrozen(@RequestBody ErrorNetflixChangeReq req) {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        req.setUserId(userId);
+        groupRelationFrontService.youtubeSubmitFrozen(req);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * youtube更换其他车票
+     */
+    @PostMapping("/youtube/change/otherTicket")
+    public Result<Integer> errorYoutubeChangeOtherTicket(@RequestBody ErrorNetflixChangeReq req) throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        req.setUserId(userId);
+        groupRelationFrontService.errorYoutubeChangeOtherTicket(req);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+    /**
+     * youtube 退款
+     */
+    @PostMapping("/youtube/refund")
+    public Result<Integer> errorYoutubeRefund(@RequestBody ErrorNetflixChangeReq req) throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        req.setUserId(userId);
+        groupRelationFrontService.errorYoutubeRefund(req);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
 }