zoujiajian 10 hónapja
szülő
commit
203ae26300

+ 5 - 2
netflix-dao/src/main/java/com/cyksj/mapper/ClaudeCodeUserRenewExpiryRecordMapper.java

@@ -2,6 +2,7 @@ package com.cyksj.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.cyksj.model.entity.ClaudeCodeUserRenewExpiryRecord;
+import com.cyksj.model.entity.OrderDon;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -13,5 +14,7 @@ import java.util.List;
  * 创建时间:2025/8/11 13:39
  */
 public interface ClaudeCodeUserRenewExpiryRecordMapper extends BaseMapper<ClaudeCodeUserRenewExpiryRecord> {
-	ClaudeCodeUserRenewExpiryRecord getOtherRenewSku(@Param("orderId") Long orderId, @Param("relationId") Long relationId, @Param("userIds") List<Long> userIds);;
-}
+	ClaudeCodeUserRenewExpiryRecord getOtherRenewSku(@Param("orderId") Long orderId, @Param("relationId") Long relationId, @Param("userIds") List<Long> userIds);
+
+	List<OrderDon> getOtherRenewSkuOrders(@Param("orderId") Long orderId, @Param("relationId") Long relationId, @Param("userIds") List<Long> userIdList);
+}

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/claudecode/ClaudeCodeDeductOrderRelateMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.claudecode;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.ClaudeCodeDeductOrderRelate;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeDeductOrderRelateMapper
+ * 创建者: JavaZou
+ * 创建时间:2025/10/15 10:43
+ */
+public interface ClaudeCodeDeductOrderRelateMapper extends BaseMapper<ClaudeCodeDeductOrderRelate> {
+}

+ 26 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/ClaudeCodeDeductOrderRelate.java

@@ -0,0 +1,26 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodeDeductOrderRelate
+ * 创建者: JavaZou
+ * 创建时间:2025/10/15 10:42
+ */
+@Getter
+@Setter
+public class ClaudeCodeDeductOrderRelate extends BaseEntity{
+	private Long orderId;
+
+	/**
+	 * 抵扣车票id
+	 */
+	private Long relationId;
+
+	/**
+	 * 新规格车票id
+	 */
+	private Long newRelationId;
+}

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

@@ -67,7 +67,8 @@ public class UserTicketClearedRecord extends BaseEntity{
 		vip_refund("退款"),
 		ticket_replace("替换车票"),
 		vip_deduct("会员抵扣"),
-		codex_deduct("codex抵扣")
+		codex_deduct("codex抵扣"),
+		cc_deduct("claude code抵扣")
 		;
 		String desc;
 

+ 14 - 1
netflix-dao/src/main/resources/mapper/ClaudeCodeUserRenewExpiryRecordMapper.xml

@@ -12,7 +12,20 @@
         and o.status not in ('close','noPayment','refund')
         and o.order_type = 2 and o.relation_id = #{relationId}
         inner join goods_don_sku sku on sku.id = o.sku_id
-        order by sku.months desc,sku.code_points desc
+        order by sku.months desc,sku.claude_quota desc
         limit 1
     </select>
+
+    <select id="getOtherRenewSkuOrders" resultType="com.cyksj.model.entity.OrderDon">
+        select o.* from (select order_id from claude_code_user_renew_expiry_record where order_id != #{orderId}
+        and is_valid = 0
+        and user_id in
+        <foreach collection="userIds" item="item" open="(" separator="," close=")">
+            #{item}
+        </foreach>) orr inner join order_don o on o.id = orr.order_id
+        and o.status not in ('close','noPayment','refund')
+        and o.order_type = 2 and o.relation_id = #{relationId}
+        inner join goods_don_sku sku on sku.id = o.sku_id
+        order by sku.months desc,sku.claude_quota desc
+    </select>
 </mapper>

+ 9 - 0
netflix-service/src/main/java/com/cyksj/service/claude/ClaudeCodeService.java

@@ -1,5 +1,6 @@
 package com.cyksj.service.claude;
 
+import cn.hutool.core.date.DateTime;
 import com.cyksj.model.entity.ClaudeCodeUser;
 import com.cyksj.model.entity.GoodsDonSku;
 import com.cyksj.model.entity.GroupsRelation;
@@ -17,6 +18,7 @@ import com.cyksj.model.views.ClaudeCodeUserInfoView;
 import com.cyksj.model.views.ClaudeCodeUserResetRecordView;
 import com.ejlchina.searcher.SearchResult;
 
+import java.math.BigDecimal;
 import java.util.Date;
 import java.util.List;
 
@@ -124,4 +126,11 @@ public interface ClaudeCodeService {
 	 * 用户是否存在订阅
 	 */
 	Boolean getUserSubs(long userId);
+
+	/**
+	 * cc抵扣升级
+	 */
+	BigDecimal getDeductMoney(Long relationId, Long relationSkuId, Long renewSkuId, Long renewOrderId, Date renewExpiryTime, Date relationStartTime, Date relationExpiryTime, DateTime now, List<Long> userIdList);
+
+	BigDecimal getDeductSkuMoney(Long userId, Long relationId, Long upgradeSkuId);
 }

+ 208 - 0
netflix-service/src/main/java/com/cyksj/service/claude/impl/ClaudeCodeServiceImpl.java

@@ -22,6 +22,7 @@ import com.cyksj.mapper.claudecode.ClaudeCodeUserResetRecordMapper;
 import com.cyksj.mapper.manage.distribute.DistributePointsExchangeMoneyMapper;
 import com.cyksj.mapper.manage.distribute.DistributeWaitingSendPointsMapper;
 import com.cyksj.model.entity.*;
+import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.model.request.ClaudeCodeApiKeysReq;
 import com.cyksj.model.request.ClaudeCodeCreditCardUseReq;
 import com.cyksj.model.request.ClaudeCodeDelReq;
@@ -1024,6 +1025,136 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		return true;
 	}
 
+	@Override
+	public BigDecimal getDeductSkuMoney(Long userId, Long relationId, Long skuId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		GroupsRelationView relationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
+				.field(GroupsRelationView::getId, relationId)
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+				.build());
+		if (relationView == null) {
+			return BigDecimal.ZERO;
+		}
+		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
+		GoodsDonSku upgradeSku = skuMapper.selectById(skuId);
+		if (upgradeSku.getGoodsId() != Constant.CLAUDE_CODE_GOODS_ID) {
+			return BigDecimal.ZERO;
+		}
+		GoodsDon goodsDon = goodsDonMapper.selectById(upgradeSku.getGoodsId());
+		GoodsDonSku relationPresentSku = null;
+		Long renewSkuId = claudeCodeUser.getRenewSkuId();
+		if (renewSkuId != null) {
+			relationPresentSku = skuMapper.selectById(renewSkuId);
+		}
+		if (relationPresentSku == null) {
+			relationPresentSku = skuMapper.selectById(relationView.getSkuId());
+		}
+		if (upgradeSku.getClaudeQuota().intValue() < relationPresentSku.getClaudeQuota().intValue()) {
+			return BigDecimal.ZERO;
+		}
+		BigDecimal deductMoney = getDeductMoney(relationId, relationView.getSkuId(), claudeCodeUser.getRenewOrderId(), claudeCodeUser.getRenewOrderId(), claudeCodeUser.getRenewExpiryTime(), relationView.getStartTime(), relationView.getExpiryTime(), DateTime.now(), userIdList);
+		return deductMoney;
+	}
+
+	@Override
+	public BigDecimal getDeductMoney(Long relationId, Long relationSkuId, Long renewSkuId, Long renewOrderId, Date renewExpiryTime, Date relationStartTime, Date relationExpiryTime, DateTime now, List<Long> userIdList) {
+		BigDecimal deductMoney = BigDecimal.ZERO;
+		//一个月当30天算单价 往上取整
+		//续费规格
+		if (renewSkuId != null && renewExpiryTime.compareTo(now) > 0) {
+			OrderDon renewOrderDon = orderDonMapper.selectById(renewOrderId);
+			BigDecimal orderMoney = renewOrderDon.getMoney().add(Optional.ofNullable(renewOrderDon.getBalance()).orElse(BigDecimal.ZERO));
+			GoodsDonSku presentSku = skuMapper.selectById(renewSkuId);
+			if (orderMoney.compareTo(BigDecimal.ZERO) == 0) {
+				orderMoney = presentSku.getPrice();
+			}
+			Long betweenDay = DateUtil.betweenDay(now, renewExpiryTime, false) + 1;
+			//当天购买的按规格原价抵扣
+			BigDecimal renewDeductMoney;
+			if (now.toDateStr().equals(DateUtil.format(renewOrderDon.getCreatedTime(), "yyyy-MM-dd"))) {
+				renewDeductMoney = orderMoney;
+				deductMoney = deductMoney.add(renewDeductMoney);
+				if (presentSku.getDays() != null) {
+					relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -presentSku.getDays());
+				} else {
+					relationExpiryTime = DateUtil.offsetMonth(relationExpiryTime, -presentSku.getMonths());
+				}
+			} else {
+				//此次续费规格单价
+				BigDecimal single = getPayOrderSingle(orderMoney, presentSku);
+				//该车票抵扣金额
+				renewDeductMoney = single.multiply(BigDecimal.valueOf(betweenDay));
+				log.info("车票id:{}目前续费升级规格可抵扣的金额为:{}", relationId, renewDeductMoney);
+				deductMoney = deductMoney.add(renewDeductMoney);
+				relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -betweenDay.intValue());
+			}
+			//上一个续费升级规格
+			List<OrderDon> orderDOns = claudeCodeUserRenewExpiryRecordMapper.getOtherRenewSkuOrders(renewOrderId, relationId, userIdList);
+			OrderDon presentRenewOrderDon = renewOrderDon;
+			for (OrderDon thisRenewOrderDon : orderDOns) {
+				Long skuId = thisRenewOrderDon.getSkuId();
+				GoodsDonSku thisRenewSku = skuMapper.selectById(skuId);
+				BigDecimal thisOrderMoney = thisRenewOrderDon.getMoney().add(Optional.ofNullable(thisRenewOrderDon.getBalance()).orElse(BigDecimal.ZERO));
+				if (thisOrderMoney.compareTo(BigDecimal.ZERO) == 0) {
+					thisOrderMoney = thisRenewSku.getPrice();
+				}
+				//当天购买的按规格原价抵扣
+				BigDecimal thisRenewDeductMoney = BigDecimal.ZERO;
+				if (now.toDateStr().equals(DateUtil.format(thisRenewOrderDon.getCreatedTime(), "yyyy-MM-dd"))) {
+					thisRenewDeductMoney = thisOrderMoney;
+					deductMoney = deductMoney.add(thisRenewDeductMoney);
+					if (thisRenewSku.getDays() != null) {
+						relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -thisRenewSku.getDays());
+					} else {
+						relationExpiryTime = DateUtil.offsetMonth(relationExpiryTime, -thisRenewSku.getMonths());
+					}
+				} else {
+					//此次续费订单过期时间
+					Date thisOrderExpiryTime;
+					if (thisRenewSku.getDays() != null) {
+						thisOrderExpiryTime = DateUtil.offsetDay(thisRenewOrderDon.getCreatedTime(), thisRenewSku.getDays());
+					} else {
+						thisOrderExpiryTime = DateUtil.offsetMonth(thisRenewOrderDon.getCreatedTime(), thisRenewSku.getMonths());
+					}
+					//续费同规格不处理
+					if (!ObjectUtil.equal(thisRenewOrderDon.getSkuId(), presentRenewOrderDon.getSkuId())) {
+						//先扣除已使用的天数
+						//相差多少天
+						Long thisRenewBetDay = DateUtil.betweenDay(thisRenewOrderDon.getCreatedTime(), presentRenewOrderDon.getCreatedTime(), false);
+						//扣除已使用的
+						thisOrderExpiryTime = DateUtil.offsetDay(thisOrderExpiryTime, -thisRenewBetDay.intValue());
+					}
+					//此订单过期时间
+					if (thisOrderExpiryTime.compareTo(now) > 0) {
+						//距离过期还剩多少天
+						Long thisDeductBetweenDay = DateUtil.betweenDay(now, thisOrderExpiryTime, false) + 1;
+						if (thisDeductBetweenDay != 0) {
+							//此次续费规格单价
+							BigDecimal single = getPayOrderSingle(thisOrderMoney, thisRenewSku);
+							//该车票抵扣金额
+							thisRenewDeductMoney = single.multiply(BigDecimal.valueOf(thisDeductBetweenDay));
+							deductMoney = deductMoney.add(thisRenewDeductMoney);
+						}
+						//目前级别续费订单
+						presentRenewOrderDon = thisRenewOrderDon;
+						relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -thisDeductBetweenDay.intValue());
+					}
+				}
+				log.info("车票id:{}目前次一级规格:{}可抵扣的金额为:{}", relationId, thisRenewSku.getSpecVal(), thisRenewDeductMoney);
+
+			}
+		}
+		if (relationExpiryTime.after(now)) {
+			//该车票抵扣金额
+			BigDecimal deductMoneyFromTicket = getDeductMoneyFromTicket(relationId, userIdList, relationSkuId, now, relationStartTime, relationExpiryTime);
+			deductMoney = deductMoney.add(deductMoneyFromTicket);
+		}
+		log.info("车票id:{}最终到期时间为:{}车票可返回 抵扣金额:{}", relationId, DateUtil.format(relationExpiryTime, "yyyy-MM-dd HH:mm:ss"), deductMoney);
+		return deductMoney;
+	}
+
 	private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception {
 		String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/claude/users/%s/limit", userId);
 		Map<String, Object> parmas = new HashMap<>();
@@ -1035,4 +1166,81 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			throw BusinessRuntimeException.getInstance("使用补充卡失败");
 		}
 	}
+
+	/**
+	 * 获取订单单价
+	 */
+	public BigDecimal getPayOrderSingle(BigDecimal orderMoney, GoodsDonSku sku) {
+		//此次续费规格单价
+		BigDecimal single;
+		//区分天/月
+		if (sku.getDays() != null) {
+			single = orderMoney.divide(BigDecimal.valueOf(sku.getDays()), 0, RoundingMode.HALF_UP);
+		} else {
+			single = orderMoney.divide(BigDecimal.valueOf(sku.getMonths()).multiply(BigDecimal.valueOf(30)), 0, RoundingMode.HALF_UP);
+		}
+		return single;
+	}
+
+	/**
+	 * 获取车票可抵扣金额
+	 */
+	public BigDecimal getDeductMoneyFromTicket(Long relationId, List<Long> userIdList, Long skuId, DateTime now, Date relationStartTime, Date relationExpiryTime) {
+		List<OrderDon> orderDonList = orderDonMapper.selectList(Wrappers.lambdaQuery(OrderDon.class)
+				.eq(OrderDon::getRelationId, relationId)
+				.in(OrderDon::getUserId, userIdList)
+				.eq(OrderDon::getSkuId, skuId)
+				.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
+				.orderByDesc(OrderDon::getId));
+		//部分退款
+		if (orderDonList.isEmpty()) {
+			orderDonList = orderDonMapper.selectList(Wrappers.lambdaQuery(OrderDon.class)
+					.eq(OrderDon::getRelationId, relationId)
+					.in(OrderDon::getUserId, userIdList)
+					.eq(OrderDon::getSkuId, skuId)
+					.notIn(OrderDon::getStatus, Constant.noOrderStatus)
+					.orderByDesc(OrderDon::getId));
+		}
+		//未有订单 返回抵扣金额0
+		if (orderDonList.isEmpty()) {
+			return BigDecimal.ZERO;
+		}
+		//原车票订单总实付金额
+		BigDecimal totalOrderMoney = BigDecimal.ZERO;
+		//原车票订单总天数
+		Integer totalDays = 0;
+		for (OrderDon orderDon : orderDonList) {
+			GoodsDonSku sku = skuMapper.selectById(skuId);
+			BigDecimal thisOrderMoney = orderDon.getMoney().add(Optional.ofNullable(orderDon.getBalance()).orElse(BigDecimal.ZERO)).subtract(Optional.ofNullable(orderDon.getRefundMoney()).orElse(BigDecimal.ZERO));
+			if (thisOrderMoney.compareTo(BigDecimal.ZERO) == 0) {
+				thisOrderMoney = sku.getPrice();
+			}
+			totalOrderMoney = totalOrderMoney.add(thisOrderMoney);
+			//总时间
+			if (sku.getDays() != null) {
+				totalDays += sku.getDays();
+			} else {
+				totalDays += sku.getMonths() * 30;
+			}
+			//原订单数
+			if (orderDonList.size() == 1) {
+				//此次订单过期时间
+				Date thisOrderExpiryTime;
+				if (sku.getDays() != null) {
+					thisOrderExpiryTime = DateUtil.offsetDay(orderDon.getCreatedTime(), sku.getDays());
+				} else {
+					thisOrderExpiryTime = DateUtil.offsetMonth(orderDon.getCreatedTime(), sku.getMonths());
+				}
+				//当天购买的按规格原价抵扣
+				if (now.toDateStr().equals(DateUtil.format(relationStartTime, "yyyy-MM-dd")) && (DateUtil.betweenDay(thisOrderExpiryTime, relationExpiryTime, true) == 0)) {
+					return thisOrderMoney;
+				}
+			}
+		}
+		Long thisDeductBetweenDay = DateUtil.betweenDay(now, relationExpiryTime, false) + 1;
+		BigDecimal single = totalOrderMoney.divide(BigDecimal.valueOf(totalDays), 0, RoundingMode.HALF_UP);
+		//该车票抵扣金额
+		BigDecimal deductMoney = single.multiply(BigDecimal.valueOf(thisDeductBetweenDay));
+		return deductMoney;
+	}
 }

+ 2 - 0
netflix-service/src/main/java/com/cyksj/service/order/OrderDonService.java

@@ -113,4 +113,6 @@ public interface OrderDonService extends IService<OrderDon> {
     void aliPaySpotifyUpgradeNotify(Map<String, String> params) throws Exception;
 
 	void receiveGptMirrorDayCard(Long orderId) throws Exception;
+
+    OrderDon ccDeductSubmitOrder(OrderPayRequest payRequest);
 }

+ 132 - 1
netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java

@@ -46,6 +46,7 @@ import com.cyksj.mapper.*;
 import com.cyksj.mapper.channel.ChannelPopularizeMapper;
 import com.cyksj.mapper.channel.ShopConfigMapper;
 import com.cyksj.mapper.channel.UserPayEquipmentAvailableBenefitsMapper;
+import com.cyksj.mapper.claudecode.ClaudeCodeDeductOrderRelateMapper;
 import com.cyksj.mapper.codex.CodexDeductRecordMapper;
 import com.cyksj.mapper.corp.CorpInteractiveUserOrderRelationMapper;
 import com.cyksj.mapper.corp.CorpUserMapper;
@@ -110,7 +111,6 @@ import com.cyksj.service.template.TemplateCommonService;
 import com.cyksj.service.user.UserBenefitsService;
 import com.cyksj.service.user.UserBindRelationService;
 import com.cyksj.service.user.UserCommonService;
-import com.cyksj.service.sora.SoraCodeService;
 import com.cyksj.service.wechat.WeChatService;
 import com.cyksj.task.JobManager;
 import com.ejlchina.searcher.BeanSearcher;
@@ -407,6 +407,8 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 
 	private final SoraCodeFrontService soraCodeFrontService;
 
+	private final ClaudeCodeDeductOrderRelateMapper claudeCodeDeductOrderRelateMapper;
+
 	private final List<String> noChekGoodsTemp = List.of("Apple One", "Youtube", "Spotify");
 
 	@Transactional(rollbackFor = Throwable.class)
@@ -3326,6 +3328,40 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 				groupsRelationMapper.updateById(relation);
 			}
 		}
+
+		//是否是cc抵扣升级
+		if (orderDon.getGoodsId() == Constant.CLAUDE_CODE_GOODS_ID) {
+			try {
+				ClaudeCodeDeductOrderRelate claudeCodeDeductOrderRelate = claudeCodeDeductOrderRelateMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeDeductOrderRelate.class).eq(ClaudeCodeDeductOrderRelate::getOrderId, orderDon.getId()));
+				Long relationId = claudeCodeDeductOrderRelate.getRelationId();
+				//清除旧的cc车票
+				GroupsRelation oldRelation = groupsRelationMapper.selectById(relationId);
+				Long relationUserId = oldRelation.getUserId();
+				groupRelationClearService.clearTicket(oldRelation, UserTicketClearedRecord.Source.cc_deduct);
+				//生成新规格车票
+				Long skuId = orderDon.getSkuId();
+				Long newRelationId = groupRelationFrontService.getTicket(relationUserId, skuId, Boolean.FALSE);
+				GroupsRelation newRelation = groupsRelationMapper.selectById(newRelationId);
+				ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
+				//同步用户cc的 车票id
+				if (claudeCodeUser != null) {
+					claudeCodeUser.setRelationId(newRelationId);
+					claudeCodeUser.setRenewSkuId(null);
+					claudeCodeUser.setRenewOrderId(null);
+					claudeCodeUser.setRenewExpiryTime(null);
+					claudeCodeUserMapper.updateById(claudeCodeUser);
+					//同步套餐
+					claudeCodeService.updateClaudeCodeUserPackageInfo(claudeCodeUser, newRelation.getExpiryTime(), sku);
+				}
+				claudeCodeDeductOrderRelate.setNewRelationId(newRelationId);
+				claudeCodeDeductOrderRelateMapper.updateById(claudeCodeDeductOrderRelate);
+				//将订单车票id同步成新规格车票id
+				orderDon.setRelationId(newRelationId);
+				orderDonMapper.updateById(orderDon);
+			} catch (Exception e) {
+				log.error("订单回调,cc抵扣升级报错:{}", StringUtil.getErrorText(e));
+			}
+		}
 	}
 
 	private void handleUserMirrorSHopOrderDon(OrderDon orderDon) throws Exception {
@@ -4715,6 +4751,94 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		return orderDon;
 	}
 
+	@Override
+	public OrderDon ccDeductSubmitOrder(OrderPayRequest payRequest) {
+		Long relationId = payRequest.getRelationId();
+		Long userId = payRequest.getUserId();
+		User user = userMapper.selectById(userId);
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		int noPayCount = orderDonMapper.selectCount(Wrappers.lambdaQuery(OrderDon.class)
+				.in(OrderDon::getUserId, userIdList)
+				.eq(OrderDon::getRelationId, relationId)
+				.eq(OrderDon::getStatus, OrderDon.Status.noPayment.toString()));
+		if (noPayCount > 0) {
+			throw new BusinessRuntimeException("您有未支付订单.");
+		}
+		GroupsRelationView relationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
+				.field(GroupsRelationView::getId, relationId)
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+				.build());
+		if (relationView == null) {
+			throw BusinessRuntimeException.getInstance("车票不存在");
+		}
+		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
+		Long skuId = payRequest.getSkuId();
+		GoodsDonSku upgradeSku = goodsDonSkuMapper.selectById(skuId);
+		if (upgradeSku.getGoodsId() != Constant.CLAUDE_CODE_GOODS_ID) {
+			throw BusinessRuntimeException.getInstance("升级规格错误");
+		}
+		GoodsDon goodsDon = goodsDonMapper.selectById(upgradeSku.getGoodsId());
+		GoodsDonSku relationPresentSku = null;
+		Long renewSkuId = claudeCodeUser.getRenewSkuId();
+		if (renewSkuId != null) {
+			relationPresentSku = goodsDonSkuMapper.selectById(renewSkuId);
+		}
+		if (relationPresentSku == null) {
+			relationPresentSku = goodsDonSkuMapper.selectById(relationView.getSkuId());
+		}
+		if (upgradeSku.getClaudeQuota().intValue() < relationPresentSku.getClaudeQuota().intValue()) {
+			throw BusinessRuntimeException.getInstance("无法抵扣升级该规格");
+		}
+		BigDecimal deductMoney = claudeCodeService.getDeductMoney(relationId, relationView.getSkuId(), claudeCodeUser.getRenewOrderId(), claudeCodeUser.getRenewOrderId(), claudeCodeUser.getRenewExpiryTime(), relationView.getStartTime(), relationView.getExpiryTime(), DateTime.now(), userIdList);
+		//订单
+		OrderDon orderDon = new OrderDon();
+		orderDon.setUserId(userId);
+		orderDon.setGoodsId(upgradeSku.getGoodsId());
+		orderDon.setRelationId(relationId);
+		orderDon.setSkuId(renewSkuId);
+		//续费订单
+		orderDon.setOrderType(2);
+		BigDecimal price = upgradeSku.getPrice();
+		orderDon.setOriMoney(price);
+		orderDon.setRealMoney(price);
+		//抵扣后的金额
+		BigDecimal discountPrice = price.subtract(deductMoney);
+		orderDon.setMoney(discountPrice);
+
+		StringBuilder payTitle = new StringBuilder();
+		payTitle.append(goodsDon.getTitle()).append(upgradeSku.getSpecVal());
+		orderDon.setPayTitle(payTitle.toString());
+		String donNo = SEQUENCE.nextId().toString();
+		orderDon.setOrderNo(donNo);
+		orderDon.setStatus(OrderDon.Status.noPayment);
+		//虚拟商品订单,是否是分销订单
+		setDistributeOrderMark(orderDon, goodsDon.getType(), userId);
+		//存在银河币
+		deductGalaxyCoinPayOrderMoney(orderDon, discountPrice, payRequest.getGalaxyCoin(), user);
+		//存在余额
+		deductBalancePayOrderMoney(orderDon, payRequest.getBalance(), user);
+		orderDonMapper.insert(orderDon);
+		//记录抵扣升级的规格
+		recordCcUpgradeOrder(orderDon, upgradeSku);
+		Long orderId = orderDon.getId();
+		//使用银河币记录
+		userGalaxyCoinService.handleUserGalaxyCoinOrderNotify(orderDon.getId(), orderDon.getUserId(), orderDon.getOrderNo(), OrderDonGalaxyCoinRecord.Source.ordinary);
+		if (orderDon.getMoney().compareTo(BigDecimal.ZERO) == 0) {
+			orderDon = orderDonMapper.selectById(orderId);
+			unifiedHandlerOrderNotify(orderDon, goodsDon, upgradeSku);
+		}
+		if (orderDon.getStatus() == OrderDon.Status.noPayment) {
+			jobManager.addJob(EXPIRY_TIME, () -> {
+				closeOrder(orderId, null);
+			});
+		}
+		//统一处理订单记录操作
+		unifiedHandlerOrderRecordFunc(orderId, payRequest);
+		return orderDon;
+	}
+
 	@Override
 	public OrderDon userBenefitsSubmit(OrderPayRequest orderPayRequest) throws Exception {
 		Long userId = orderPayRequest.getUserId();
@@ -5469,4 +5593,11 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			}
 		}
 	}
+
+	private void recordCcUpgradeOrder(OrderDon orderDon, GoodsDonSku upgradeSku) {
+		ClaudeCodeDeductOrderRelate claudeCodeDeductOrderRelate = new ClaudeCodeDeductOrderRelate();
+		claudeCodeDeductOrderRelate.setOrderId(orderDon.getId());
+		claudeCodeDeductOrderRelate.setRelationId(orderDon.getRelationId());
+		claudeCodeDeductOrderRelateMapper.insert(claudeCodeDeductOrderRelate);
+	}
 }

+ 1 - 1
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationClearServiceImpl.java

@@ -142,7 +142,7 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 					mirrorUserCleatService.delMirrorUser(groupsRelation.getGoodsId(), groupsRelation.getId(), userTicketClearedRecord.getId());
 				}
 				//claude code
-				if (groupsRelation.getGoodsId() != null && groupsRelation.getGoodsId() == Constant.CLAUDE_CODE_GOODS_ID) {
+				if (groupsRelation.getGoodsId() != null && groupsRelation.getGoodsId() == Constant.CLAUDE_CODE_GOODS_ID && source != UserTicketClearedRecord.Source.cc_deduct) {
 					//用户失效 车票用户id
 					claudeCodeService.invalidateUserCache(groupsRelation.getUserId());
 					//删除用户套餐

+ 16 - 2
netflix-web/src/main/java/com/cyksj/web/controller/claude/ClaudeCodeController.java

@@ -17,8 +17,11 @@ import com.cyksj.mapper.manage.coupon.*;
 import com.cyksj.mapper.manage.distribute.UserDistributeMapper;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.OrderDonView;
-import com.cyksj.model.request.*;
-import com.cyksj.model.response.*;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.request.ClaudeCodeCreditCardUseReq;
+import com.cyksj.model.request.ClaudeCodeDelReq;
+import com.cyksj.model.response.ClaudeCodeRewardDetailsResp;
+import com.cyksj.model.response.TimeRateConfigsResp;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
 import com.cyksj.model.views.*;
 import com.cyksj.service.claude.AnnouncementService;
@@ -39,6 +42,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -566,4 +570,14 @@ public class ClaudeCodeController {
 		});
 		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
 	}
+
+	/**
+	 * 车票可抵扣升级金额
+	 */
+	@GetMapping("/get/deduct/monty/{relationId}/{upgradeSkuId}")
+	public Result<BigDecimal> getDeductMoney(@PathVariable Long relationId, @PathVariable Long upgradeSkuId) {
+		long userId = StpUserUtil.getLoginIdAsLong();
+		BigDecimal deductMoney = claudeCodeService.getDeductSkuMoney(userId, relationId, upgradeSkuId);
+		return GatewayResponse.SUCCESS.newBuilder().toResult(deductMoney);
+	}
 }

+ 12 - 0
netflix-web/src/main/java/com/cyksj/web/controller/payment/OrderController.java

@@ -1,5 +1,6 @@
 package com.cyksj.web.controller.payment;
 
+import cn.dev33.satoken.stp.StpUtil;
 import cn.hutool.core.codec.Base64;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
@@ -907,6 +908,17 @@ public class OrderController {
         return GatewayResponse.SUCCESS.newBuilder().toResult(orderDon);
     }
 
+    /**
+     * cc抵扣升级 预支付
+     */
+    @PostMapping("/cc/deduct/submit")
+    public Result<OrderDon> deductSubmitOrder(@RequestBody OrderPayRequest payRequest) {
+        long userId = StpUtil.getLoginIdAsLong();
+        payRequest.setUserId(userId);
+        OrderDon orderDon = orderDonService.ccDeductSubmitOrder(payRequest);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(orderDon);
+    }
+
     /**
      * 获取续费套餐订单列表
      */