瀏覽代碼

codex、claude_code美金计算

zoujiajian 11 月之前
父節點
當前提交
f6cd2e97d7
共有 40 個文件被更改,包括 2081 次插入410 次删除
  1. 15 0
      netflix-common/src/main/java/com/cyksj/common/constant/Constant.java
  2. 13 0
      netflix-dao/src/main/java/com/cyksj/mapper/codex/CodexDeductRecordMapper.java
  3. 20 0
      netflix-dao/src/main/java/com/cyksj/mapper/codex/CodexUserRenewExpiryRecordMapper.java
  4. 4 4
      netflix-dao/src/main/java/com/cyksj/model/entity/ClaudeCodeUser.java
  5. 5 3
      netflix-dao/src/main/java/com/cyksj/model/entity/ClaudeCodeUserCreditCardRecord.java
  6. 28 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CodexDeductRecord.java
  7. 23 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CodexUser.java
  8. 27 0
      netflix-dao/src/main/java/com/cyksj/model/entity/CodexUserRenewExpiryRecord.java
  9. 4 4
      netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java
  10. 9 2
      netflix-dao/src/main/java/com/cyksj/model/entity/GroupsRelation.java
  11. 1 0
      netflix-dao/src/main/java/com/cyksj/model/entity/UserTicketClearedRecord.java
  12. 4 4
      netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSkuInsert.java
  13. 28 0
      netflix-dao/src/main/java/com/cyksj/model/request/CodexUpgradePayReq.java
  14. 23 0
      netflix-dao/src/main/java/com/cyksj/model/response/CodexDeductResp.java
  15. 20 0
      netflix-dao/src/main/java/com/cyksj/model/views/CcCodexInfoView.java
  16. 4 9
      netflix-dao/src/main/java/com/cyksj/model/views/ClaudeCodeUserInfoView.java
  17. 26 6
      netflix-dao/src/main/java/com/cyksj/model/views/CodexUserInfoView.java
  18. 4 4
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java
  19. 6 6
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuRenewView.java
  20. 6 6
      netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuView.java
  21. 6 6
      netflix-dao/src/main/java/com/cyksj/model/views/RenewUpgradePackageFrontView.java
  22. 4 4
      netflix-dao/src/main/java/com/cyksj/model/views/TicketRenewUpgradeView.java
  23. 6 6
      netflix-dao/src/main/java/com/cyksj/model/views/YhShopGoodsSkuFrontView.java
  24. 32 0
      netflix-dao/src/main/resources/mapper/CodexUserRenewExpiryRecordMapper.xml
  25. 61 0
      netflix-service/src/main/java/com/cyksj/service/api/ClaudeCodexApiService.java
  26. 227 0
      netflix-service/src/main/java/com/cyksj/service/api/impl/ClaudeCodexApiServiceImpl.java
  27. 0 8
      netflix-service/src/main/java/com/cyksj/service/claude/ClaudeCodeService.java
  28. 68 125
      netflix-service/src/main/java/com/cyksj/service/claude/impl/ClaudeCodeServiceImpl.java
  29. 45 10
      netflix-service/src/main/java/com/cyksj/service/codex/CodexService.java
  30. 921 132
      netflix-service/src/main/java/com/cyksj/service/codex/impl/CodexServiceImpl.java
  31. 1 1
      netflix-service/src/main/java/com/cyksj/service/distribute/impl/DistributeServiceImpl.java
  32. 41 8
      netflix-service/src/main/java/com/cyksj/service/exchange/impl/ExchangeCodeServiceImpl.java
  33. 5 1
      netflix-service/src/main/java/com/cyksj/service/groups/impl/GroupFunServiceImpl.java
  34. 81 4
      netflix-service/src/main/java/com/cyksj/service/mange/impl/MirrorUserCleatServiceImpl.java
  35. 97 12
      netflix-service/src/main/java/com/cyksj/service/order/impl/OrderDonServiceImpl.java
  36. 11 0
      netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationClearServiceImpl.java
  37. 35 9
      netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java
  38. 4 30
      netflix-web/src/main/java/com/cyksj/web/controller/claude/ClaudeCodeController.java
  39. 150 6
      netflix-web/src/main/java/com/cyksj/web/controller/codex/CodexController.java
  40. 16 0
      netflix-web/src/main/java/com/cyksj/web/controller/group/GroupRelationController.java

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

@@ -331,6 +331,11 @@ public interface Constant {
 	 */
 	String NANO_ONLY_ACCOUNT = "nano123";
 
+	/**
+	 * codex
+	 */
+	String CODEX_ONLY_ACCOUNT = "codex123";
+
 	/**
 	 * 免登录渠道
 	 */
@@ -649,6 +654,11 @@ public interface Constant {
 	 */
 	Long CLAUDE_CODE_GOODS_ID = 102L;
 
+	/**
+	 * codex平台id
+	 */
+	Long CODEX_GOODS_ID = 105L;
+
 	/**
 	 * 推广claude code分销链接
 	 */
@@ -715,4 +725,9 @@ public interface Constant {
 
 	//连接超时时间 毫秒
 	int CONNECT_MILLISECONDS = 10000;
+
+	/**
+	 * claude code补充额度
+	 */
+	Integer CLAUDE_CODE_SUP_QUOTA = 10;
 }

+ 13 - 0
netflix-dao/src/main/java/com/cyksj/mapper/codex/CodexDeductRecordMapper.java

@@ -0,0 +1,13 @@
+package com.cyksj.mapper.codex;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CodexDeductRecord;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexDeductRecordMapper
+ * 创建者: JavaZou
+ * 创建时间:2025/9/23 18:08
+ */
+public interface CodexDeductRecordMapper extends BaseMapper<CodexDeductRecord> {
+}

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/mapper/codex/CodexUserRenewExpiryRecordMapper.java

@@ -0,0 +1,20 @@
+package com.cyksj.mapper.codex;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.cyksj.model.entity.CodexUserRenewExpiryRecord;
+import com.cyksj.model.entity.OrderDon;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUserRenewExpiryRecordMapper
+ * 创建者: JavaZou
+ * 创建时间:2025/9/23 17:12
+ */
+public interface CodexUserRenewExpiryRecordMapper extends BaseMapper<CodexUserRenewExpiryRecord> {
+	CodexUserRenewExpiryRecord getOtherRenewSku(@Param("orderId") Long orderId, @Param("relationId") Long relationId, @Param("userIds") List<Long> userIds);
+
+	List<OrderDon> getOtherRenewSkus(Long renewOrderId, Long relationId, List<Long> userIdList);
+}

+ 4 - 4
netflix-dao/src/main/java/com/cyksj/model/entity/ClaudeCodeUser.java

@@ -31,16 +31,16 @@ public class ClaudeCodeUser extends BaseEntity{
 	private Integer oriCodePoints;
 
 	/**
-	 * claude code积分上限
+	 * claude code每日限额
 	 */
 	@TableField(updateStrategy = FieldStrategy.IGNORED)
-	private Integer codePoints;
+	private Integer claudeDailyLimit;
 
 	/**
-	 * claude code每小时恢复积分
+	 * Claude code 服务总额度
 	 */
 	@TableField(updateStrategy = FieldStrategy.IGNORED)
-	private Integer codeCreditRecovery;
+	private Integer claudeQuota;
 
 	/**
 	 * code 有效期

+ 5 - 3
netflix-dao/src/main/java/com/cyksj/model/entity/ClaudeCodeUserCreditCardRecord.java

@@ -3,6 +3,8 @@ package com.cyksj.model.entity;
 import lombok.Getter;
 import lombok.Setter;
 
+import java.math.BigDecimal;
+
 /**
  * 项目名: yhlxj11111111
  * 文件名: ClaudeCodeUserCreditCardRecord
@@ -22,17 +24,17 @@ public class ClaudeCodeUserCreditCardRecord extends BaseEntity {
     /**
      * 使用前积分值
      */
-    private Integer beforeCodePoints;
+    private BigDecimal beforeCodePoints;
     
     /**
      * 增加的积分值
      */
-    private Integer addCodePoints;
+    private BigDecimal addCodePoints;
     
     /**
      * 使用后积分值
      */
-    private Integer afterCodePoints;
+    private BigDecimal afterCodePoints;
     
     /**
      * 积分卡编号/批次号

+ 28 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CodexDeductRecord.java

@@ -0,0 +1,28 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexDeductRecord
+ * 创建者: JavaZou
+ * 创建时间:2025/9/23 18:07
+ */
+@Getter
+@Setter
+public class CodexDeductRecord extends BaseEntity{
+	private Long relationId;
+
+	/**
+	 * 需要清除cc codex
+	 */
+	private Long ccRelationId;
+
+	private Long orderId;
+
+	/**
+	 * 抵扣状态 默认false
+	 */
+	private Boolean status;
+}

+ 23 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CodexUser.java

@@ -49,8 +49,31 @@ public class CodexUser extends BaseEntity {
      */
     private Date expiryTime;
 
+    /**
+     * 续费升级订单id
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Long renewOrderId;
+
+    /**
+     * 续费升级skuid
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Long renewSkuId;
+
+    /**
+     * 续费升级过期时间
+     */
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Date renewExpiryTime;
+
     /**
      * 是否重试更新
      */
     private Boolean isRetry;
+
+    /**
+     * 是否购买 默认false
+     */
+    private Boolean isPay;
 }

+ 27 - 0
netflix-dao/src/main/java/com/cyksj/model/entity/CodexUserRenewExpiryRecord.java

@@ -0,0 +1,27 @@
+package com.cyksj.model.entity;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUserRenewExpiryRecord
+ * 创建者: JavaZou
+ * 创建时间:2025/9/23 17:11
+ */
+@Getter
+@Setter
+public class CodexUserRenewExpiryRecord extends BaseEntity{
+	private Long userId;
+
+	private Long relationId;
+
+	private Long renewSkuId;
+
+	private Long orderId;
+
+	/**
+	 * 是否失效 默认false
+	 */
+	private Boolean isValid;
+}

+ 4 - 4
netflix-dao/src/main/java/com/cyksj/model/entity/GoodsDonSku.java

@@ -269,16 +269,16 @@ public class GoodsDonSku extends BaseEntity {
     private String remark;
 
     /**
-     * claude code积分上限
+     * claude code每日限额
      */
     @TableField(updateStrategy = FieldStrategy.IGNORED)
-    private Integer codePoints;
+    private Integer claudeDailyLimit;
 
     /**
-     * claude code每小时恢复积分
+     * Claude code 服务总额度
      */
     @TableField(updateStrategy = FieldStrategy.IGNORED)
-    private Integer codeCreditRecovery;
+    private Integer claudeQuota;
 
     /**
      * nano总配额次数

+ 9 - 2
netflix-dao/src/main/java/com/cyksj/model/entity/GroupsRelation.java

@@ -171,11 +171,11 @@ public class GroupsRelation extends BaseEntity {
 
 
     /**
-     * 是否是claude code 续费
+     * 是否是claude code/codex 续费
      */
     @DbIgnore
     @TableField(exist = false)
-    private Boolean isClaudeCodeRenew;
+    private Boolean isRenew;
 
     /**
      * 是否是claude code 续费升级
@@ -184,6 +184,13 @@ public class GroupsRelation extends BaseEntity {
     @TableField(exist = false)
     private Boolean isClaudeCodeUpgrade;
 
+    /**
+     * 是否是codex 续费升级
+     */
+    @DbIgnore
+    @TableField(exist = false)
+    private Boolean isCodexUpgrade;
+
     /**
      * 是否被冻结
      * 默认false

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

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

+ 4 - 4
netflix-dao/src/main/java/com/cyksj/model/manage/request/ReqGoodsSkuInsert.java

@@ -192,14 +192,14 @@ public class ReqGoodsSkuInsert {
         private String remark;
 
         /**
-         * claude code积分上限
+         * claude code每日限额
          */
-        private Integer codePoints;
+        private Integer claudeDailyLimit;
 
         /**
-         * claude code每小时恢复积分
+         * Claude code 服务总额度
          */
-        private Integer codeCreditRecovery;
+        private Integer claudeQuota;
 
         /**
          * nano总配额次数

+ 28 - 0
netflix-dao/src/main/java/com/cyksj/model/request/CodexUpgradePayReq.java

@@ -0,0 +1,28 @@
+package com.cyksj.model.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexUpgradePayReq
+ * 创建者: JavaZou
+ * 创建时间:2025/9/23 17:54
+ */
+@Getter
+@Setter
+public class CodexUpgradePayReq {
+	private Long relationId;
+
+	private Long userId;
+
+	/**
+	 * 升级claude code规格id
+	 */
+	private Long skuId;
+
+	/**
+	 * 使用优惠券
+	 */
+	private Long couponId;
+}

+ 23 - 0
netflix-dao/src/main/java/com/cyksj/model/response/CodexDeductResp.java

@@ -0,0 +1,23 @@
+package com.cyksj.model.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CodexDeductResp
+ * 创建者: JavaZou
+ * 创建时间:2025/9/23 18:16
+ */
+@Getter
+@Setter
+public class CodexDeductResp {
+	private BigDecimal deductMoney;
+
+	/**
+	 * 是否可抵扣
+	 */
+	private Boolean isDeduct = Boolean.TRUE;
+}

+ 20 - 0
netflix-dao/src/main/java/com/cyksj/model/views/CcCodexInfoView.java

@@ -0,0 +1,20 @@
+package com.cyksj.model.views;
+
+import com.cyksj.model.entity.ClaudeCodeUser;
+import com.cyksj.model.entity.CodexUser;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: CcCodexInfoView
+ * 创建者: JavaZou
+ * 创建时间:2025/9/24 11:51
+ */
+@Getter
+@Setter
+public class CcCodexInfoView {
+	private ClaudeCodeUser claudeCodeUser;
+
+	private CodexUser codexUser;
+}

+ 4 - 9
netflix-dao/src/main/java/com/cyksj/model/views/ClaudeCodeUserInfoView.java

@@ -24,14 +24,14 @@ public class ClaudeCodeUserInfoView {
 	private Long relationId;
 
 	/**
-	 * claude code积分上限
+	 * claude code每日限额
 	 */
-	private Integer codePoints;
+	private Integer claudeDailyLimit;
 
 	/**
-	 * claude code每小时恢复积分
+	 * Claude code 服务总额度
 	 */
-	private Integer codeCreditRecovery;
+	private Integer claudeQuota;
 
 	/**
 	 * code 有效期
@@ -54,9 +54,4 @@ public class ClaudeCodeUserInfoView {
 	 * cc最高约可使用次数
 	 */
 	private Integer highestUsage;
-
-	/**
-	 * codex info
-	 */
-	private CodexUserInfoView codexUserInfo;
 }

+ 26 - 6
netflix-dao/src/main/java/com/cyksj/model/views/CodexUserInfoView.java

@@ -35,6 +35,21 @@ public class CodexUserInfoView {
      * 每月预算上限
      */
     private Integer openaiQuota;
+
+    private Date expiryTime;
+
+
+    private Long renewOrderId;
+
+    /**
+     * 续费升级skuId
+     */
+    private Long renewSkuId;
+
+    /**
+     * 续费升级过期时间
+     */
+    private Date renewExpiryTime;
     
     /**
      * 座位ID
@@ -45,6 +60,11 @@ public class CodexUserInfoView {
      * Claude Code套餐信息
      */
     private ClaudeCodeInfo claudeCodeInfo;
+
+    /**
+     * 是否是购买 默认false
+     */
+    private Boolean isPay;
     
     @Getter
     @Setter
@@ -59,16 +79,16 @@ public class CodexUserInfoView {
         private String planName;
 
         private Long relationId;
-        
+
         /**
-         * claude code积分上限
+         * claude code每日限额
          */
-        private Integer codePoints;
-        
+        private Integer claudeDailyLimit;
+
         /**
-         * claude code每小时恢复积分
+         * Claude code 服务总额度
          */
-        private Integer codeCreditRecovery;
+        private Integer claudeQuota;
         
         /**
          * code 有效期

+ 4 - 4
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuFrontView.java

@@ -228,14 +228,14 @@ public class GoodsDonSkuFrontView {
 	private Long otherLimitTime;
 
 	/**
-	 * claude code积分上限
+	 * claude code每日限额
 	 */
-	private Integer codePoints;
+	private Integer claudeDailyLimit;
 
 	/**
-	 * claude code每小时恢复积分
+	 * Claude code 服务总额度
 	 */
-	private Integer codeCreditRecovery;
+	private Integer claudeQuota;
 
 	/**
 	 * nano总配额次数

+ 6 - 6
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuRenewView.java

@@ -89,16 +89,16 @@ public class GoodsDonSkuRenewView {
 	private Long originalOrderId;
 
 	/**
-	 * claude code积分上限
+	 * claude code每日限额
 	 */
-	@DbField("sku.code_points")
-	private Integer codePoints;
+	@DbField("sku.claude_daily_limit")
+	private Integer claudeDailyLimit;
 
 	/**
-	 * claude code每小时恢复积分
+	 * Claude code 服务总额度
 	 */
-	@DbField("sku.code_credit_recovery")
-	private Integer codeCreditRecovery;
+	@DbField("sku.claude_quota")
+	private Integer claudeQuota;
 
 	/**
 	 * cc最高约可使用次数

+ 6 - 6
netflix-dao/src/main/java/com/cyksj/model/views/GoodsDonSkuView.java

@@ -69,16 +69,16 @@ public class GoodsDonSkuView {
 	private Boolean isMirror;
 
 	/**
-	 * claude code积分上限
+	 * claude code每日限额
 	 */
-	@DbField("gdk.code_points")
-	private Integer codePoints;
+	@DbField("gdk.claude_daily_limit")
+	private Integer claudeDailyLimit;
 
 	/**
-	 * claude code每小时恢复积分
+	 * Claude code 服务总额度
 	 */
-	@DbField("gdk.code_credit_recovery")
-	private Integer codeCreditRecovery;
+	@DbField("gdk.claude_quota")
+	private Integer claudeQuota;
 
 	/**
 	 * nano次数

+ 6 - 6
netflix-dao/src/main/java/com/cyksj/model/views/RenewUpgradePackageFrontView.java

@@ -92,16 +92,16 @@ public class RenewUpgradePackageFrontView {
 		private BigDecimal price;
 
 		/**
-		 * claude code积分上限
+		 * claude code每日限额
 		 */
-		@DbField("sku.code_points")
-		private Integer codePoints;
+		@DbField("sku.claude_daily_limit")
+		private Integer claudeDailyLimit;
 
 		/**
-		 * claude code每小时恢复积分
+		 * Claude code 服务总额度
 		 */
-		@DbField("sku.code_credit_recovery")
-		private Integer codeCreditRecovery;
+		@DbField("sku.claude_quota")
+		private Integer claudeQuota;
 
 		/**
 		 * cc最高约可使用次数

+ 4 - 4
netflix-dao/src/main/java/com/cyksj/model/views/TicketRenewUpgradeView.java

@@ -82,14 +82,14 @@ public class TicketRenewUpgradeView {
 	private GoodsDonSkuRenewView renewSku;
 
 	/**
-	 * claude code积分上限
+	 * claude code每日限额
 	 */
-	private Integer codePoints;
+	private Integer claudeDailyLimit;
 
 	/**
-	 * claude code每小时恢复积分
+	 * Claude code 服务总额度
 	 */
-	private Integer codeCreditRecovery;
+	private Integer claudeQuota;
 
 	/**
 	 * cc最高约可使用次数

+ 6 - 6
netflix-dao/src/main/java/com/cyksj/model/views/YhShopGoodsSkuFrontView.java

@@ -186,16 +186,16 @@ public class YhShopGoodsSkuFrontView {
 	private Boolean remark;
 
 	/**
-	 * claude code积分上限
+	 * claude code每日限额
 	 */
-	@DbField("sku.code_points")
-	private Integer codePoints;
+	@DbField("sku.claude_daily_limit")
+	private Integer claudeDailyLimit;
 
 	/**
-	 * claude code每小时恢复积分
+	 * Claude code 服务总额度
 	 */
-	@DbField("sku.code_credit_recovery")
-	private Integer codeCreditRecovery;
+	@DbField("sku.claude_quota")
+	private Integer claudeQuota;
 
 	/**
 	 * nano总配额次数

+ 32 - 0
netflix-dao/src/main/resources/mapper/CodexUserRenewExpiryRecordMapper.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="com.cyksj.mapper.codex.CodexUserRenewExpiryRecordMapper">
+
+    <select id="getOtherRenewSku" resultType="com.cyksj.model.entity.CodexUserRenewExpiryRecord">
+        select orr.* from (select * from codex_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.code_points desc
+        limit 1
+    </select>
+
+    <select id="getOtherRenewSkus" resultType="com.cyksj.model.entity.OrderDon">
+        select o.* from (select order_id from codex_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.gpt_limit_num desc
+    </select>
+</mapper>

+ 61 - 0
netflix-service/src/main/java/com/cyksj/service/api/ClaudeCodexApiService.java

@@ -0,0 +1,61 @@
+package com.cyksj.service.api;
+
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodexApiService
+ * 创建者: JavaZou
+ * 创建时间:2025/9/25 16:35
+ */
+public interface ClaudeCodexApiService {
+
+	/**
+	 * 创建keys
+	 * @param req
+	 * @return
+	 * @throws Exception
+	 */
+	ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception;
+
+	/**
+	 * 删除keys
+	 */
+	void deleteApiKeysById(Long userId, Long keyId) throws Exception;
+
+
+	/**
+	 * key列表
+	 */
+	List<ClaudeCodeUserApiKeysView> getUserApiKeys(Long codexUserId);
+
+	/**
+	 * 生成或更新codex套餐
+	 */
+	ClaudeCodeResp createOrUpdateCodexUserPackageApi(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception;
+
+	/**
+	 * 删除codex用户套餐
+	 */
+	boolean delCodexUserPackages(Long userId);
+
+	/**
+	 * 获取用户OpenAI使用统计
+	 */
+	ClaudeCodeResp getCodexUserOpenAIUsage(Long userId, String period);
+
+	/**
+	 * codex 用户仪表盘
+	 */
+	ClaudeCodeResp getCodexUserDashboard(Long userId);
+
+	/**
+	 * codex用户使用量统计
+	 */
+	ClaudeCodeResp getCodexUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order);
+}

+ 227 - 0
netflix-service/src/main/java/com/cyksj/service/api/impl/ClaudeCodexApiServiceImpl.java

@@ -0,0 +1,227 @@
+package com.cyksj.service.api.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.http.HttpException;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.http.Method;
+import com.cyksj.common.constant.Constant;
+import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.util.Jsons;
+import com.cyksj.common.util.StringUtil;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
+import com.cyksj.service.api.ClaudeCodexApiService;
+import com.github.rholder.retry.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 项目名: yhlxj11111111
+ * 文件名: ClaudeCodexApiServiceImpl
+ * 创建者: JavaZou
+ * 创建时间:2025/9/25 16:35
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class ClaudeCodexApiServiceImpl implements ClaudeCodexApiService {
+
+	@Override
+	public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception {
+		String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", req.getUserId());
+		Map<String, Object> parmas = new HashMap<>();
+		parmas.put("name", req.getName());
+		Integer expiresDays = req.getExpiresDays();
+		if (expiresDays != null) {
+			parmas.put("expires_days", expiresDays);
+		}
+		ClaudeCodeResp claudeCodeResp = executePostApi(apiKeyUrl, Jsons.toJson(parmas));
+		if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
+			throw BusinessRuntimeException.getInstance("创建失败");
+		}
+		return claudeCodeResp;
+	}
+
+	@Override
+	public void deleteApiKeysById(Long userId, Long keyId) throws Exception {
+		String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", userId, keyId);
+		HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
+		String responseBody = execute.body();
+		ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class);
+		if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
+			log.error("删除key失败:{}", claudeCodeResp.getMessage());
+			throw BusinessRuntimeException.getInstance("删除失败");
+		}
+	}
+
+	@Override
+	public List<ClaudeCodeUserApiKeysView> getUserApiKeys(Long codexUserId) {
+		String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", codexUserId);
+		ClaudeCodeResp claudeCodeResp = executeGetApi(url);
+		return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class);
+	}
+
+	@Override
+	public ClaudeCodeResp createOrUpdateCodexUserPackageApi(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception {
+		Map<String, Object> params = new HashMap<>();
+		params.put("plan_name", planName.replaceAll(";", ""));
+		params.put("openai_daily_limit", openaiDailyLimit);
+		params.put("openai_quota", openaiQuota);
+		params.put("expire_time", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
+		//claude 类型
+		params.put("service_type", "openai");
+
+		String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
+		ClaudeCodeResp codexResp = executePostApi(planUrl, Jsons.toJson(params));
+
+		if (!"success".equals(codexResp.getMessage())) {
+			log.info("用户codex用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, codexResp.getMessage());
+			throw BusinessRuntimeException.getInstance("调用codex 套餐接口失败");
+		}
+
+		return codexResp;
+	}
+
+	@Override
+	public boolean delCodexUserPackages(Long userId) {
+		String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan?service_type=openai", userId);
+		try {
+			ClaudeCodeResp claudeCodeResp = executeDeletedApi(url);
+			if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
+				log.error("删除用户:{} codex套餐失败:{}", userId, claudeCodeResp.getMessage());
+				return Boolean.FALSE;
+			}
+			return Boolean.TRUE;
+		} catch (Exception e) {
+			log.error("删除用户:{} codex套餐报错:{}", userId, StringUtil.getErrorText(e));
+		}
+		return Boolean.FALSE;
+	}
+
+	@Override
+	public ClaudeCodeResp getCodexUserOpenAIUsage(Long userId, String period) {
+		// 调用Codex API获取实时使用统计
+		String usageUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/usage?period=%s", userId, period);
+		ClaudeCodeResp codexResp = executeGetApi(usageUrl);
+		return codexResp;
+	}
+
+	@Override
+	public ClaudeCodeResp getCodexUserDashboard(Long userId) {
+		// 调用Codex API获取用户控制台数据
+		String dashboardUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/dashboard", userId);
+		ClaudeCodeResp codexResp = executeGetApi(dashboardUrl);
+		return codexResp;
+	}
+
+	@Override
+	public ClaudeCodeResp getCodexUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order) {
+		// 调用Codex API获取用户分析数据
+		String analyticsUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/analytics?start=%s&end=%s&page=%s&limit=%s&order=%s", userId, start, end, page, limit, order);
+		ClaudeCodeResp codexResp = executeGetApi(analyticsUrl);
+		return codexResp;
+	}
+
+	/**
+	 * GET请求
+	 */
+	public ClaudeCodeResp executeGetApi(String url) {
+		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
+		try {
+			return build.call(() -> {
+				try {
+					HttpResponse execute = HttpUtil.createGet(url)
+							.header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
+							.setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
+							.execute();
+					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
+					if (!Constant.SUCCESS.equals(resp.getMessage())) {
+						log.error("GET URL:{}接口返回msg:{}", url, resp.getMessage());
+					}
+					return resp;
+				} catch (HttpException e) {
+					return null;
+				}
+			});
+		} catch (ExecutionException | RetryException e) {
+			log.error("重试调用GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
+			throw BusinessRuntimeException.getInstance("接口请求失败");
+		}
+	}
+
+	/**
+	 * Post请求
+	 */
+	public ClaudeCodeResp executePostApi(String url, String body) {
+		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
+		try {
+			return build.call(() -> {
+				try {
+					HttpResponse execute = HttpUtil.createPost(url)
+							.header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
+							.setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
+							.body(body)
+							.execute();
+					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
+					if (!Constant.SUCCESS.equals(resp.getMessage())) {
+						log.error("POST URL:{}接口返回msg:{}", url, resp.getMessage());
+					}
+					return resp;
+				} catch (Exception e) {
+					return null;
+				}
+			});
+		} catch (ExecutionException | RetryException e) {
+			log.info("重试调用 POST请求 url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
+			throw BusinessRuntimeException.getInstance("接口请求失败");
+		}
+	}
+
+	/**
+	 * claude code DELETE请求
+	 */
+	public ClaudeCodeResp executeDeletedApi(String url) {
+		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
+		try {
+			return build.call(() -> {
+				try {
+					HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
+					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
+					if (!Constant.SUCCESS.equals(resp.getMessage())) {
+						log.error("DELETE URL:{}接口返回msg:{}", url, resp.getMessage());
+					}
+					return resp;
+				} catch (HttpException e) {
+					return null;
+				}
+			});
+		} catch (ExecutionException | RetryException e) {
+			log.error("重试调用DELETE请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
+			throw BusinessRuntimeException.getInstance("接口请求失败");
+		}
+	}
+
+	public Retryer getApiRetryer(Integer sleepSecond, Integer attemptNum) {
+		Retryer build = RetryerBuilder.newBuilder()
+				.retryIfException()
+				//运行时异常重试
+				.retryIfRuntimeException()
+				//false重试
+				.retryIfResult(res -> res == null)
+				//1s 间隔
+				.withWaitStrategy(WaitStrategies.fixedWait(sleepSecond, TimeUnit.SECONDS))
+				//停止策略 : 尝试请求3次
+				.withStopStrategy(StopStrategies.stopAfterAttempt(attemptNum)).build();
+		return build;
+	}
+}

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

@@ -7,7 +7,6 @@ import com.cyksj.model.request.ClaudeCodeApiKeysReq;
 import com.cyksj.model.request.ClaudeCodeCreditCardUseReq;
 import com.cyksj.model.request.ClaudeCodeDelReq;
 import com.cyksj.model.request.CreditResetReq;
-import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
 import com.cyksj.model.response.ClaudeCodeRewardDetailsResp;
 import com.cyksj.model.response.CreditResetResp;
 import com.cyksj.model.response.TimeRateConfigsResp;
@@ -49,8 +48,6 @@ public interface ClaudeCodeService {
 	 */
 	List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception;
 
-	ClaudeCodePointsHistoryResp getUserPointsDetail(Long userId, String type, Integer page, Integer limit) throws Exception;
-
 	ClaudeCodeResp getUserDashboard(Long userId) throws Exception;
 
 	ClaudeCodeResp getUserBalance(Long userId) throws Exception;
@@ -85,11 +82,6 @@ public interface ClaudeCodeService {
 	 */
 	CreditResetResp resetUserCredits(Long userId, CreditResetReq req) throws Exception;
 
-	/**
-	 * 重置用户codePoints到原始值并记录重置次数
-	 */
-	void resetUserCodePoints(Long userId, Integer resetType, String resetReason);
-
 	/**
 	 * 用户使用重置次数
 	 */

+ 68 - 125
netflix-service/src/main/java/com/cyksj/service/claude/impl/ClaudeCodeServiceImpl.java

@@ -28,13 +28,13 @@ import com.cyksj.model.request.ClaudeCodeCreditCardUseReq;
 import com.cyksj.model.request.ClaudeCodeDelReq;
 import com.cyksj.model.request.CreditResetReq;
 import com.cyksj.model.request.codex.CodexUserPackageReq;
-import com.cyksj.model.response.ClaudeCodePointsHistoryResp;
 import com.cyksj.model.response.ClaudeCodeRewardDetailsResp;
 import com.cyksj.model.response.CreditResetResp;
 import com.cyksj.model.response.TimeRateConfigsResp;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
 import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
+import com.cyksj.service.api.ClaudeCodexApiService;
 import com.cyksj.service.claude.ClaudeCodeService;
 import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.user.UserBindRelationService;
@@ -103,6 +103,8 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	
 	private final CodexUserMapper codexUserMapper;
 
+	private final ClaudeCodexApiService claudeCodexApiService;
+
 	@Override
 	public void generateOrUpdateClaudeCodeUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) {
 		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
@@ -112,14 +114,14 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		Long thisCodeOriSkuId = relationSku.getId();
 		Long relationUserId = relation.getUserId();
 		//此时车票的套餐规格积分
-		Integer thisRelationCodePoints = null;
+		Integer thisRelationCodeDayilyLimit = null;
 		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
 		if (claudeCodeUser != null) {
-			thisRelationCodePoints = claudeCodeUser.getCodePoints();
+			thisRelationCodeDayilyLimit = claudeCodeUser.getClaudeDailyLimit();
 		}
 		Date expiryTime = relation.getExpiryTime();
-		Integer codePoints = sku.getCodePoints();
-		Integer codeCreditRecovery = sku.getCodeCreditRecovery();
+		Integer claudeDailyLimit = sku.getClaudeDailyLimit();
+		Integer claudeQuota = sku.getClaudeQuota();
 		Long skuId = sku.getId();
 		Boolean isUpgrade = false;
 		Date renewExpiryTime = null;
@@ -128,7 +130,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			//非车票原规格id 续费升级记录
 			saveClaudeCodeRenewExpiryRecord(orderId, relationUserId, relationId, skuId);
 			//是否需要修改当前claude code user 套餐等级
-			if (thisRelationCodePoints != null && thisRelationCodePoints <= codePoints) {
+			if (thisRelationCodeDayilyLimit != null && thisRelationCodeDayilyLimit <= claudeDailyLimit) {
 				if (sku.getDays() != null) {
 					renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays());
 				} else {
@@ -137,12 +139,12 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 				isUpgrade = true;
 			}
 		}
-		generateOrUpdateClaudeCodeUser(relationUserId, relationId, codePoints, codeCreditRecovery, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal(), userIdList);
+		generateOrUpdateClaudeCodeUser(relationUserId, relationId, claudeDailyLimit, claudeQuota, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal(), userIdList);
 		try {
 			//获取最新的claude code user
 			claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
 			//生成claude code或更新用户套餐
-			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
+			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(relation.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
 			if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
 				//重试更新code userInfo数据
 				setClaudeCodeUserRetryUpdateInfo(relationId);
@@ -159,7 +161,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		}
 	}
 
-	private void generateOrUpdateClaudeCodeUser(Long relationUserId, Long relationId, Integer codePoints, Integer codeCreditRecovery, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName, List<Long> userIdList) {
+	private void generateOrUpdateClaudeCodeUser(Long relationUserId, Long relationId, Integer claudeDailyLimit, Integer claudeQuota, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName, List<Long> userIdList) {
 		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).eq(ClaudeCodeUser::getRelationId, relationId).last("limit 1"));
 		if (claudeCodeUser == null) {
 			claudeCodeUser = new ClaudeCodeUser();
@@ -169,16 +171,16 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			claudeCodeUser.setRenewSkuId(renewSkuId);
 			claudeCodeUser.setRenewExpiryTime(renewExpiryTime);
 			claudeCodeUser.setPlanName(planName);
-			claudeCodeUser.setCodePoints(codePoints);
-			claudeCodeUser.setCodeCreditRecovery(codeCreditRecovery);
+			claudeCodeUser.setClaudeDailyLimit(claudeDailyLimit);
+			claudeCodeUser.setClaudeQuota(claudeQuota);
 		}
 		claudeCodeUser.setUserId(relationUserId);
 		claudeCodeUser.setRelationId(relationId);
 		claudeCodeUser.setExpiryTime(expiryTime);
 		if (claudeCodeUser.getId() == null) {
 			claudeCodeUser.setPlanName(planName);
-			claudeCodeUser.setCodePoints(codePoints);
-			claudeCodeUser.setCodeCreditRecovery(codeCreditRecovery);
+			claudeCodeUser.setClaudeDailyLimit(claudeDailyLimit);
+			claudeCodeUser.setClaudeQuota(claudeQuota);
 			//可重置次数
 			Integer resetCount = claudeCodeUserResetRecordMapper.getUserResetCountByUserIds(userIdList);
 			if (resetCount != null && resetCount > 0) {
@@ -200,18 +202,9 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		if (checkClaudeCodeUser(userId)) {
 			Long claudeCodeUserId = getClaudeCodeUserId(userId);
 			Assert.notNull(claudeCodeUserId, "您的claude code账号已过期");
-			String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
-			Map<String, Object> parmas = new HashMap<>();
-			parmas.put("name", req.getName());
-			Integer expiresDays = req.getExpiresDays();
-			if (expiresDays != null) {
-				parmas.put("expires_days", expiresDays);
-			}
-			ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
-			if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
-				throw BusinessRuntimeException.getInstance("创建失败");
-			}
-			return claudeCodeResp;
+
+			req.setUserId(claudeCodeUserId);
+			return claudeCodexApiService.createApiKeys(req);
 		}
 		throw new BusinessRuntimeException("您还没有claudecode账号");
 	}
@@ -222,14 +215,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		Long keyId = delReq.getKeyId();
 		if (checkClaudeCodeUser(userId)) {
 			Long claudeCodeUserId = getClaudeCodeUserId(userId);
-			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/keys/%s/%s", claudeCodeUserId, keyId);
-			HttpResponse execute = HttpUtil.createRequest(Method.DELETE, url).header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY).setConnectionTimeout(Constant.CONNECT_MILLISECONDS).execute();
-			String responseBody = execute.body();
-			ClaudeCodeResp claudeCodeResp = Jsons.parseObject(responseBody, ClaudeCodeResp.class);
-			if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
-				log.error("删除key失败:{}", claudeCodeResp.getMessage());
-				throw BusinessRuntimeException.getInstance("删除失败");
-			}
+			claudeCodexApiService.deleteApiKeysById(claudeCodeUserId, keyId);
 		}
 	}
 
@@ -237,21 +223,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	public List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception {
 		Long claudeCodeUserId = getClaudeCodeUserId(userId);
 		if (claudeCodeUserId != null) {
-			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/keys", claudeCodeUserId);
-			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
-			return Jsons.parseList(claudeCodeResp.getData(), ClaudeCodeUserApiKeysView.class);
-		}
-		return null;
-	}
-
-	@Override
-	public ClaudeCodePointsHistoryResp getUserPointsDetail(Long userId, String type, Integer page, Integer limit) throws Exception {
-		Long claudeCodeUserId = getClaudeCodeUserId(userId);
-		if (claudeCodeUserId != null) {
-			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/history?page=%s&limit=%s&type=%s", claudeCodeUserId, page, limit, type);
-			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
-			ClaudeCodePointsHistoryResp historyResp = Jsons.parseObject(claudeCodeResp.getData(), ClaudeCodePointsHistoryResp.class);
-			return historyResp;
+			return claudeCodexApiService.getUserApiKeys(claudeCodeUserId);
 		}
 		return null;
 	}
@@ -260,7 +232,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	public ClaudeCodeResp getUserDashboard(Long userId) throws Exception {
 		Long claudeCodeUserId = getClaudeCodeUserId(userId);
 		if (claudeCodeUserId != null) {
-			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/dashboard", claudeCodeUserId);
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/claude/users/%s/dashboard", claudeCodeUserId);
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			return claudeCodeResp;
 		}
@@ -287,7 +259,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			end = now.toString();
 		}
 		if (claudeCodeUserId != null) {
-			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/analytics?start=%s&end=%s&tz=%s&page=%s&limit=%s&order=%s&type=%s", claudeCodeUserId, start, end, tz, page, limit, order, type);
+			String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/claude/users/%s/analytics?start=%s&end=%s&tz=%s&page=%s&limit=%s&order=%s&type=%s", claudeCodeUserId, start, end, tz, page, limit, order, type);
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeGetApi(url);
 			return claudeCodeResp;
 		}
@@ -318,6 +290,8 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
 				.build());
 		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
+		CodexUser codexUser = null;
+		CodexUserInfoView codexUserInfoView = null;
 		//车票不存在
 		if (groupsRelationView == null) {
 			//套餐是否存在
@@ -365,7 +339,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		//是否需要重试更新 cladueCode userInfo信息
 		if (claudeCodeUser.getIsRetry()) {
 			try {
-				ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
+				ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), claudeCodeUser.getExpiryTime(), claudeCodeUser.getRelationId(), sku);
 				if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
 					claudeCodeUser.setIsRetry(Boolean.TRUE);
 					claudeCodeUserMapper.updateById(claudeCodeUser);
@@ -377,7 +351,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 
 			}
 		}
-		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList).last("limit 1"));
+		codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, claudeCodeUser.getRelationId()).in(CodexUser::getUserId, userIdList).last("limit 1"));
 		if (codexUser == null) {
 			//是否赠送codex
 			if (sku.getIsCodex()) {
@@ -391,36 +365,36 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 				codexService.createOrUpdateUserPackage(codexPackageReq);
 			}
 		}
-		CodexUserInfoView codexUserInfoView = null;
-		if (codexUser != null) {
-			codexUserInfoView = CodexUserInfoView.builder().planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).build();
-		}
-
 		return ClaudeCodeUserInfoView.builder()
 				.planName(claudeCodeUser.getPlanName())
 				.relationId(claudeCodeUser.getRelationId())
-				.codePoints(claudeCodeUser.getCodePoints())
-				.codeCreditRecovery(claudeCodeUser.getCodeCreditRecovery())
+				.claudeDailyLimit(claudeCodeUser.getClaudeDailyLimit())
+				.claudeQuota(claudeCodeUser.getClaudeQuota())
 				.expiryTime(claudeCodeUser.getExpiryTime())
-				.renewSkuId(claudeCodeUser.getRenewSkuId())
+				.renewSkuId(Optional.ofNullable(claudeCodeUser.getRenewSkuId()).orElse(groupsRelationView.getSkuId()))
 				.renewOrderId(claudeCodeUser.getRenewOrderId())
 				.renewExpiryTime(claudeCodeUser.getRenewExpiryTime())
 				.highestUsage(sku.getHighestUsage())
-				.codexUserInfo(codexUserInfoView)
 				.build();
 	}
 
-	public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer codePoints, Integer codeCreditRecovery, Date expiryTime, Long relationId, GoodsDonSku sku) throws Exception {
+	public ClaudeCodeResp createOrUpdateClaudeCodeUserPackage(Long userId, String planName, Integer claudeDailyLimit, Integer claudeQuota, Date expiryTime, Long relationId, GoodsDonSku sku) throws Exception {
 		Map<String, Object> parmas = new HashMap<>();
 		parmas.put("plan_name", planName.replaceAll(";", ""));
-		parmas.put("credit_limit", codePoints);
-		parmas.put("credit_recovery", codeCreditRecovery);
+		parmas.put("claude_daily_limit", claudeDailyLimit);
+		parmas.put("claude_quota", claudeQuota);
+		//服务类型
+		parmas.put("service_type", "claude");
 		if (sku.getIsCodex()) {
 			parmas.put("openai_daily_limit", sku.getOpenaiDailyLimit());
 			parmas.put("openai_quota", sku.getOpenaiQuota());
+			//组合
+			parmas.put("service_type", "combined");
 		} else {
 			parmas.put("openai_daily_limit", 0);
 			parmas.put("openai_quota", 0);
+			//单独 claude类型
+			parmas.put("service_type", "claude");
 		}
 		parmas.put("expire_time", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
 		String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
@@ -461,7 +435,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	@Override
 	public void updateClaudeCodeUserPackageInfo(ClaudeCodeUser claudeCodeUser, Date expiryTime, GoodsDonSku sku) {
 		try {
-			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getCodePoints(), claudeCodeUser.getCodeCreditRecovery(), expiryTime, claudeCodeUser.getRelationId(), sku);
+			ClaudeCodeResp codeUserPackage = createOrUpdateClaudeCodeUserPackage(claudeCodeUser.getUserId(), claudeCodeUser.getPlanName(), claudeCodeUser.getClaudeDailyLimit(), claudeCodeUser.getClaudeQuota(), expiryTime, claudeCodeUser.getRelationId(), sku);
 			if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
 				//重试标记
 				claudeCodeUser.setIsRetry(Boolean.TRUE);
@@ -478,7 +452,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 
 	@Override
 	public Boolean delUserPackages(Long userId) {
-		String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
+		String url = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan?service_type=claude", userId);
 		try {
 			ClaudeCodeResp claudeCodeResp = executeClaudeCodeDeletedApi(url);
 			if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
@@ -677,36 +651,6 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 				.withStopStrategy(StopStrategies.stopAfterAttempt(attemptNum)).build();
 		return build;
 	}
-
-	@Override
-	public void resetUserCodePoints(Long userId, Integer resetType, String resetReason) {
-		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
-		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(
-			Wrappers.<ClaudeCodeUser>lambdaQuery().in(ClaudeCodeUser::getUserId, userIdList)
-		);
-
-		if (claudeCodeUser == null) {
-			throw new BusinessRuntimeException("用户不存在或未开通Claude Code服务");
-		}
-
-		Integer beforeCodePoints = claudeCodeUser.getCodePoints();
-		Integer afterCodePoints = claudeCodeUser.getOriCodePoints();
-
-		claudeCodeUser.setCodePoints(afterCodePoints);
-		claudeCodeUserMapper.updateById(claudeCodeUser);
-
-		ClaudeCodeUserResetRecord resetRecord = new ClaudeCodeUserResetRecord();
-		resetRecord.setUserId(userId);
-		resetRecord.setBeforeCodePoints(beforeCodePoints);
-		resetRecord.setAfterCodePoints(afterCodePoints);
-		resetRecord.setResetType(resetType);
-		resetRecord.setResetReason(resetReason);
-
-		claudeCodeUserResetRecordMapper.insert(resetRecord);
-
-		log.info("用户{}的积分已重置", userId);
-	}
-
 	@Override
 	@Transactional(rollbackFor = Throwable.class)
 	public void useResetCount(Long userId) throws Exception {
@@ -777,16 +721,16 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		);
 
 		if (creditCard == null) {
-			throw new BusinessRuntimeException("无可用积分卡");
+			throw new BusinessRuntimeException("无可用补充卡");
 		}
 
 		if (creditCard.getStatus() != 0) {
 			if (creditCard.getStatus() == 1) {
-				throw new BusinessRuntimeException("积分卡已被使用");
+				throw new BusinessRuntimeException("补充卡已被使用");
 			} else if (creditCard.getStatus() == 2) {
-				throw new BusinessRuntimeException("积分卡已过期");
+				throw new BusinessRuntimeException("补充卡已过期");
 			} else {
-				throw new BusinessRuntimeException("积分卡状态异常");
+				throw new BusinessRuntimeException("补充卡状态异常");
 			}
 		}
 		String cardNumber = creditCard.getCardNumber();
@@ -794,21 +738,21 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		if (creditCard.getExpiryTime() != null && creditCard.getExpiryTime().before(new Date())) {
 			creditCard.setStatus(2);
 			claudeCodeCreditCardMapper.updateById(creditCard);
-			throw new BusinessRuntimeException("积分卡已过期");
+			throw new BusinessRuntimeException("补充卡已过期");
 		}
 
-		ClaudeCodeResp userBalance = getUserBalance(userId);
-		JSONObject data = Jsons.parseObject(userBalance.getData(), JSONObject.class);
-		Integer availableCredits = data.getInt("available_credits");
-		Integer creditLimit = data.getInt("credit_limit");
+		ClaudeCodeResp dashboard = getUserDashboard(userId);
+		JSONObject data = Jsons.parseObject(dashboard.getData(), JSONObject.class);
+		BigDecimal daily_remaining = data.getBigDecimal("daily_remaining");
+		BigDecimal daily_total = BigDecimal.valueOf(claudeCodeUser.getClaudeDailyLimit());
 		//超出积分 异常
-		if (availableCredits + creditCard.getCreditAmount() > creditLimit) {
-			throw BusinessRuntimeException.getInstance("目前使用只能补充到{0},建议您消耗更多积分后再兑换使用", creditLimit);
+		if (daily_remaining.add(BigDecimal.valueOf(creditCard.getCreditAmount())).compareTo(daily_total) > 0) {
+			throw BusinessRuntimeException.getInstance("目前使用只能补充到{0},建议您消耗更多额度后再补充使用", daily_total);
 		}
 
-		Integer beforeCodePoints = availableCredits;
-		Integer addCodePoints = creditCard.getCreditAmount();
-		Integer afterCodePoints = beforeCodePoints + addCodePoints;
+		BigDecimal beforeCodePoints = daily_remaining;
+		BigDecimal addCodePoints = BigDecimal.valueOf(creditCard.getCreditAmount());
+		BigDecimal afterCodePoints = beforeCodePoints.add(addCodePoints);
 
 		creditCard.setStatus(1);
 		creditCard.setUsedUserId(userId);
@@ -828,15 +772,15 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			creditCardRecord.setCardNumber(cardNumber);
 			//使用一次
 			creditCardRecord.setNum(-1);
-			creditCardRecord.setUseReason("使用积分卡");
+			creditCardRecord.setUseReason("使用补充卡");
 
 			claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord);
 
-			log.info("用户{}成功使用积分卡{},增加{}积分", userId, cardNumber, addCodePoints);
+			log.info("用户{}成功使用补充卡{},增加{}预算", userId, cardNumber, addCodePoints);
 			//调用API调整用户积分
-			adjClaudeCodeUserPoints(claudeCodeUser.getUserId(), creditCard.getCreditAmount(), "adjustment", "用户使用积分卡");
+			adjClaudeCodeUserPoints(claudeCodeUser.getUserId(), creditCard.getCreditAmount(), "adjustment", "用户使用补充卡");
 		} else {
-			throw new BusinessRuntimeException("积分卡使用失败,请重试");
+			throw new BusinessRuntimeException("补充卡使用失败,请重试");
 		}
 	}
 
@@ -883,7 +827,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			response.setResetCount(0);
 		}
 
-		// 获取可用积分卡数量
+		// 获取可用补充卡数量
 		Integer availableCreditCards = claudeCodeCreditCardMapper.selectCount(
 				Wrappers.lambdaQuery(ClaudeCodeCreditCard.class)
 						.eq(ClaudeCodeCreditCard::getStatus, 0)
@@ -891,11 +835,6 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 						.isNull(ClaudeCodeCreditCard::getUsedUserId)
 		);
 		response.setAvailableCreditCards(availableCreditCards != null ? availableCreditCards : 0);
-		if (claudeCodeUser.getCodePoints() > 5000) {
-			Integer selectCount = claudeCodeDayResetRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeDayResetRecord.class).in(ClaudeCodeDayResetRecord::getUserId, userIdList).gt(ClaudeCodeDayResetRecord::getCreatedTime, DateUtil.beginOfDay(DateTime.now())));
-			//每日可用重置次数
-			response.setDayRestNum(selectCount > 0 ? 0 : 1);
-		}
 
 		//是否是普通推广
 		if (general != null && general) {
@@ -947,6 +886,10 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 
 	@Override
 	public void sendCreditCard(Long orderId, Long userId, Integer points, String cardNumber, String remark) {
+		//暂时不送积分卡
+		if (true) {
+			return;
+		}
 		ClaudeCodeCreditCard creditCard = new ClaudeCodeCreditCard();
 		creditCard.setUserId(userId);
 		creditCard.setOrderId(orderId);
@@ -955,13 +898,13 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 		creditCard.setRemark(remark);
 		claudeCodeCreditCardMapper.insert(creditCard);
 
-		//积分卡明细
+		//补充卡明细
 		ClaudeCodeUserCreditCardRecord creditCardRecord = new ClaudeCodeUserCreditCardRecord();
 		creditCardRecord.setUserId(userId);
 		creditCardRecord.setCardNumber(cardNumber);
 		creditCardRecord.setUseReason(remark);
 		creditCardRecord.setNum(1);
-		creditCardRecord.setAddCodePoints(points);
+		creditCardRecord.setAddCodePoints(BigDecimal.valueOf(points));
 		claudeCodeUserCreditCardRecordMapper.insert(creditCardRecord);
 	}
 
@@ -1027,7 +970,7 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 			return;
 		}
 		//非标准版、专业版 不给使用
-		if (claudeCodeUser.getCodePoints() < 5000) {
+		if (claudeCodeUser.getClaudeDailyLimit() < 5000) {
 			return;
 		}
 		Integer selectCount = claudeCodeDayResetRecordMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeDayResetRecord.class).in(ClaudeCodeDayResetRecord::getUserId, userIdList).gt(ClaudeCodeDayResetRecord::getCreatedTime, DateUtil.beginOfDay(DateTime.now())));
@@ -1080,14 +1023,14 @@ public class ClaudeCodeServiceImpl implements ClaudeCodeService {
 	}
 
 	private void adjClaudeCodeUserPoints(Long userId, Integer points, String type, String description) throws Exception {
-		String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/credits/adjust", userId);
+		String apiKeyUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/claude/users/%s/limit", userId);
 		Map<String, Object> parmas = new HashMap<>();
 		parmas.put("amount", points);
 		parmas.put("type", type);
 		parmas.put("description", description);
 		ClaudeCodeResp claudeCodeResp = executeClaudeCodePostApi(apiKeyUrl, Jsons.toJson(parmas));
 		if (!Constant.SUCCESS.equals(claudeCodeResp.getMessage())) {
-			throw BusinessRuntimeException.getInstance("使用积分卡失败");
+			throw BusinessRuntimeException.getInstance("使用补充卡失败");
 		}
 	}
 }

+ 45 - 10
netflix-service/src/main/java/com/cyksj/service/codex/CodexService.java

@@ -1,10 +1,22 @@
 package com.cyksj.service.codex;
 
+import com.cyksj.model.entity.CodexUser;
+import com.cyksj.model.entity.GoodsDonSku;
+import com.cyksj.model.entity.GroupsRelation;
+import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.request.ClaudeCodeDelReq;
+import com.cyksj.model.request.CodexUpgradePayReq;
 import com.cyksj.model.request.UpdateDailyLimitRequest;
 import com.cyksj.model.request.codex.CodexUserPackageReq;
+import com.cyksj.model.response.CodexDeductResp;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
 import com.cyksj.model.views.CodexUserInfoView;
 
+import java.util.Date;
+import java.util.List;
+
 /**
  * 项目名: yhlxj11111111
  * 文件名: CodexService
@@ -13,11 +25,6 @@ import com.cyksj.model.views.CodexUserInfoView;
  */
 public interface CodexService {
     
-    /**
-     * 获取用户Codex信息
-     */
-    CodexUserInfoView getCodexUserInfo(Long userId);
-    
     /**
      * 创建或更新用户套餐
      */
@@ -33,11 +40,6 @@ public interface CodexService {
      */
     ClaudeCodeResp updateUserOpenAIDailyLimit(Long userId, UpdateDailyLimitRequest request);
     
-    /**
-     * 获取系统OpenAI指标统计
-     */
-    ClaudeCodeResp getSystemMetrics(String period, String metric);
-    
     /**
      * 获取Java系统专用的用户OpenAI控制台数据
      */
@@ -49,4 +51,37 @@ public interface CodexService {
     ClaudeCodeResp getJavaUserAnalytics(Long userId, String start, String end, Integer page, Integer limit, String order);
 
     void delCodexUser(Long userId, Long relationId);
+
+    void createOrUpdateCodexUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType);
+
+    /**
+     * 调用第三方api 更新codexUser
+     */
+	void updateCodexUserPackageInfo(CodexUser codexUser, Date expiryTime, GoodsDonSku finalSku);
+
+    ClaudeCodeResp createOrUpdateCodexUserPackageApi(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception;
+
+    CodexDeductResp getDeductMoney(Long userId, Long relationId);
+
+    OrderDon deductSubmitOrder(CodexUpgradePayReq payReq) throws Exception;
+
+    Boolean getUserSubs(Long userId);
+
+    CodexUserInfoView getUserSubsInfo(long userId);
+
+    ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception;
+
+    List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception;
+
+    void deleteApiKeysById(ClaudeCodeDelReq delReq) throws Exception;
+
+    /**
+     * 删除用户套餐
+     */
+    boolean delUserPackages(Long userId);
+
+    /**
+     * 续费升级
+     */
+    void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long id);
 }

+ 921 - 132
netflix-service/src/main/java/com/cyksj/service/codex/impl/CodexServiceImpl.java

@@ -1,23 +1,53 @@
 package com.cyksj.service.codex.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpResponse;
 import cn.hutool.http.HttpUtil;
 import cn.hutool.http.Method;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.constant.Constant;
 import com.cyksj.common.exception.BusinessRuntimeException;
+import com.cyksj.common.snowflake.Sequence;
 import com.cyksj.common.util.Jsons;
 import com.cyksj.common.util.StringUtil;
+import com.cyksj.dto.RedisKey;
 import com.cyksj.mapper.*;
+import com.cyksj.mapper.codex.CodexDeductRecordMapper;
+import com.cyksj.mapper.codex.CodexUserRenewExpiryRecordMapper;
+import com.cyksj.mapper.manage.coupon.CouponMapper;
+import com.cyksj.mapper.manage.coupon.CouponSkuMapper;
+import com.cyksj.mapper.manage.coupon.CouponUserMapper;
+import com.cyksj.mapper.manage.distribute.UserDistributeMapper;
+import com.cyksj.mapper.manage.distribute.UserDistributeSharedMapper;
+import com.cyksj.model.dto.CouponPopularizeDto;
 import com.cyksj.model.entity.*;
+import com.cyksj.model.manage.views.GroupsRelationView;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.request.ClaudeCodeDelReq;
+import com.cyksj.model.request.CodexUpgradePayReq;
 import com.cyksj.model.request.UpdateDailyLimitRequest;
 import com.cyksj.model.request.codex.CodexUserPackageReq;
+import com.cyksj.model.response.CodexDeductResp;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
+import com.cyksj.model.views.ClaudeCodeUserApiKeysView;
 import com.cyksj.model.views.CodexUserInfoView;
+import com.cyksj.model.views.CouponNewUserView;
+import com.cyksj.model.views.CouponUserView;
+import com.cyksj.redis.RedisService;
+import com.cyksj.service.api.ClaudeCodexApiService;
 import com.cyksj.service.codex.CodexService;
+import com.cyksj.service.coupon.CouponFontService;
+import com.cyksj.service.groups.GroupsFuncService;
 import com.cyksj.service.user.UserBindRelationService;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapBuilder;
+import com.ejlchina.searcher.util.MapUtils;
 import com.github.rholder.retry.Retryer;
 import com.github.rholder.retry.RetryerBuilder;
 import com.github.rholder.retry.StopStrategies;
@@ -26,10 +56,14 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.*;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 项目名: yhlxj11111111
@@ -50,65 +84,41 @@ public class CodexServiceImpl implements CodexService {
 
 	private final GoodsDonSkuMapper skuMapper;
 
+	private final GoodsDonMapper goodsDonMapper;
+
+	private final CouponMapper couponMapper;
+
 	private final GroupsRelationMapper relationMapper;
 
 	private final GroupsMapper groupsMapper;
 
-	@Override
-	public CodexUserInfoView getCodexUserInfo(Long userId) {
-		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
-		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
-		// 查询Claude Code信息
-		CodexUserInfoView.ClaudeCodeInfo claudeCodeInfo = getClaudeCodeInfo(userId);
+	private final CodexUserRenewExpiryRecordMapper codexUserRenewExpiryRecordMapper;
 
-		if (codexUser == null) {
-			//是否有claude code
-			if (claudeCodeInfo != null && claudeCodeInfo.getCodePoints() != null) {
-				GoodsDonSku sku = null;
-				if (claudeCodeInfo.getRenewSkuId() != null) {
-					sku = skuMapper.selectById(claudeCodeInfo.getRenewSkuId());
-				}
-				//续费升级规格不存在 或者 不赠送codex
-				if (sku == null || !sku.getIsCodex()) {
-					GroupsRelation relation = relationMapper.selectById(claudeCodeInfo.getRelationId());
-					GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
-					sku = skuMapper.selectById(groupsTrips.getSkuId());
-				}
+	private final BeanSearcher beanSearcher;
 
-				//sku 存在并且开启赠送codex
-				if (sku != null && sku.getIsCodex()) {
-					CodexUserPackageReq codexPackageReq = CodexUserPackageReq.builder()
-							.userId(claudeCodeInfo.getUserId())
-							.planName(sku.getCodexPlanName())
-							.openaiDailyLimit(sku.getOpenaiDailyLimit())
-							.openaiQuota(sku.getOpenaiQuota())
-							.expiryTime(claudeCodeInfo.getExpiryTime())
-							.build();
-					createOrUpdateUserPackage(codexPackageReq);
-					codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
-				}
-			}
-			if (codexUser == null) {
-				return null;
-			}
-		}
+	private final OrderDonMapper orderDonMapper;
 
+	private final GroupsFuncService groupsFuncService;
 
-		return CodexUserInfoView.builder().planName(codexUser.getPlanName()).openaiDailyLimit(codexUser.getOpenaiDailyLimit()).openaiQuota(codexUser.getOpenaiQuota()).relationId(codexUser.getRelationId()).claudeCodeInfo(claudeCodeInfo).build();
-	}
+	private final RedisService redisService;
 
-	private CodexUserInfoView.ClaudeCodeInfo getClaudeCodeInfo(Long userId) {
-		// 获取用户关联的ID列表
-		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+	private final CodexDeductRecordMapper codexDeductRecordMapper;
 
-		ClaudeCodeUser claudeCodeUser = claudeCodeUserMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList).last("limit 1"));
+	private final CouponSkuMapper couponSkuMapper;
 
-		if (claudeCodeUser == null) {
-			return null;
-		}
+	private final CouponUserMapper couponUserMapper;
 
-		return CodexUserInfoView.ClaudeCodeInfo.builder().userId(claudeCodeUser.getUserId()).planName(claudeCodeUser.getPlanName()).relationId(claudeCodeUser.getRelationId()).codePoints(claudeCodeUser.getCodePoints()).codeCreditRecovery(claudeCodeUser.getCodeCreditRecovery()).expiryTime(claudeCodeUser.getExpiryTime()).renewSkuId(claudeCodeUser.getRenewSkuId()).renewExpiryTime(claudeCodeUser.getRenewExpiryTime()).build();
-	}
+	private final UserDistributeMapper userDistributeMapper;
+
+	private final UserDistributeSharedMapper userDistributeSharedMapper;
+
+	private final UserMapper userMapper;
+
+	private final CouponFontService couponFontService;
+
+	private static final Sequence SEQUENCE = new Sequence(0);
+
+	private final ClaudeCodexApiService claudeCodexApiService;
 
 	@Override
 	public void createOrUpdateUserPackage(CodexUserPackageReq req) {
@@ -143,31 +153,420 @@ public class CodexServiceImpl implements CodexService {
 	/**
 	 * 创建或更新Codex用户套餐
 	 */
-	public ClaudeCodeResp createOrUpdateCodexUserPackage(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception {
-		Map<String, Object> params = new HashMap<>();
-		params.put("plan_name", planName.replaceAll(";", ""));
-		params.put("openai_daily_limit", openaiDailyLimit);
-		params.put("openai_quota", openaiQuota);
-		params.put("end_date", DateUtil.format(expiryTime, "yyyy-MM-dd HH:mm:ss"));
+	@Override
+	public ClaudeCodeResp createOrUpdateCodexUserPackageApi(Long userId, String planName, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime) throws Exception {
+		return claudeCodexApiService.createOrUpdateCodexUserPackageApi(userId, planName, openaiDailyLimit, openaiQuota, expiryTime);
+	}
+
+	@Override
+	public CodexDeductResp getDeductMoney(Long userId, Long relationId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		CodexDeductResp codexDeductResp = new CodexDeductResp();
+		List<Long> sendCodexSkuIds = skuMapper.selectList(Wrappers.lambdaQuery(GoodsDonSku.class).eq(GoodsDonSku::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID).eq(GoodsDonSku::getIsCodex, Boolean.TRUE)).stream().map(GoodsDonSku::getId).collect(Collectors.toList());
+		if (CollUtil.isNotEmpty(sendCodexSkuIds)) {
+			GroupsRelationView ccRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder().field(GroupsRelationView::getSkuId, sendCodexSkuIds).op(Operator.InList).field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan).field(GroupsRelationView::getUserId, userIdList).op(Operator.InList).field(GroupsRelationView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID).build());
+			if (ccRelationView != null) {
+				codexDeductResp.setIsDeduct(Boolean.FALSE);
+				return codexDeductResp;
+			}
+		}
+		MapBuilder builder = MapUtils.builder();
+		if (relationId != null) {
+			builder.field(GroupsRelationView::getId, relationId);
+		}
+		GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, builder
+				.field(GroupsRelationView::getGoodsId, Constant.CODEX_GOODS_ID)
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+				.build());
+		BigDecimal deductMoney;
+		if (groupsRelationView == null || groupsRelationView.getExpiryTime().before(DateTime.now())) {
+			deductMoney = BigDecimal.ZERO;
+		} else {
+			relationId = groupsRelationView.getId();
+			CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
+			Assert.notNull(codexUser, "系统异常,请联系客服");
+			deductMoney = getFinalDeductMoney(relationId, groupsRelationView.getSkuId(), codexUser.getRenewSkuId(), codexUser.getRenewOrderId(), codexUser.getRenewExpiryTime(), groupsRelationView.getStartTime(), groupsRelationView.getExpiryTime(), DateTime.now(), userIdList);
+		}
+		codexDeductResp.setDeductMoney(deductMoney);
+		return codexDeductResp;
+	}
+
+	@Override
+	@Transactional(rollbackFor = Throwable.class)
+	public OrderDon deductSubmitOrder(CodexUpgradePayReq payReq) throws Exception {
+		Long userId = payReq.getUserId();
+		//codex 车票id
+		Long relationId = payReq.getRelationId();
+		CodexDeductResp codexDeductResp = getDeductMoney(userId, relationId);
+		if (!codexDeductResp.getIsDeduct()) {
+			throw BusinessRuntimeException.getInstance("你存在claude code 无法抵扣");
+		}
+		//抵扣金额
+		BigDecimal deductMoney = codexDeductResp.getDeductMoney();
+		Long skuId = payReq.getSkuId();
+
+		GoodsDonSku sku = skuMapper.selectById(skuId);
+		if (sku == null || sku.getGoodsId() != Constant.CLAUDE_CODE_GOODS_ID || !sku.getIsCodex()) {
+			throw BusinessRuntimeException.getInstance("系统异常,请刷新页面重试");
+		}
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		int noPayCount = orderDonMapper.selectCount(Wrappers.lambdaQuery(OrderDon.class).in(OrderDon::getUserId, userIdList).eq(OrderDon::getGoodsId, sku.getGoodsId())
+				.eq(OrderDon::getStatus, OrderDon.Status.noPayment.toString()));
+		if (noPayCount > 0) {
+			throw new BusinessRuntimeException("您有未支付订单.");
+		}
+		String cacheKye = "codex_deduct_cache_key:" + relationId;
+		boolean b = redisService.setNx(cacheKye, relationId, 10L);
+		if (!b) {
+			throw BusinessRuntimeException.getInstance("系统繁忙,请刷新页面重试");
+		}
+		CouponUser couponUser = null;
+		if (payReq.getCouponId() != null) {
+			CouponPopularizeDto couponPopularizeDto = checkCouponStatus(skuId, null, payReq.getCouponId(), userId, false);
+			//记录优惠券使用状态
+			couponUser = couponStatusRecord(null, payReq.getCouponId(), userId, couponPopularizeDto);
+		}
+
+		OrderDon orderDon;
+		try {
+			orderDon = new OrderDon();
+			orderDon.setGoodsId(sku.getGoodsId());
+			orderDon.setSkuId(sku.getId());
+			orderDon.setUserId(userId);
+			//优惠 --> 订单金额
+			deductOrderMoney(orderDon, couponUser);
+			orderDon.setMoney(sku.getPrice().subtract(deductMoney));
+			orderDon.setType(OrderDon.Type.ALI_PAY);
+
+			/* 生成订单 */
+			String donNo = SEQUENCE.nextId().toString();
+			orderDon.setOrderNo(donNo);
+			orderDon.setStatus(OrderDon.Status.noPayment);
+
+			//锁一个cc的车票id
+			//是否存在无赠送的claude code 直接原规格上抵扣
+			List<Long> sendCodexSkuIds = skuMapper.selectList(Wrappers.lambdaQuery(GoodsDonSku.class).eq(GoodsDonSku::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID).eq(GoodsDonSku::getIsCodex, Boolean.TRUE)).stream().map(GoodsDonSku::getId).collect(Collectors.toList());
+			//cc车票id
+			Long ccRelationId = null;
+			if (CollUtil.isEmpty(sendCodexSkuIds)) {
+				//是否存在cc
+				Integer selectCount = claudeCodeUserMapper.selectCount(Wrappers.lambdaQuery(ClaudeCodeUser.class).in(ClaudeCodeUser::getUserId, userIdList));
+				if (selectCount > 0) {
+					throw BusinessRuntimeException.getInstance("你存在claude code 无法抵扣");
+				}
+			} else {
+				GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+						.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+						.field(GroupsRelationView::getSkuId, sendCodexSkuIds).op(Operator.NotIn)
+						.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+						.build());
+				if (groupsRelationView != null) {
+					ccRelationId = groupsRelationView.getId();
+				}
+			}
+
+			GroupsRelation relation = getClaudeCodeRelation(userId, sku);
+			orderDon.setRelationId(relation.getId());
+			orderDonMapper.insert(orderDon);
+
+			//记录抵扣记录
+			CodexDeductRecord codexDeductRecord = new CodexDeductRecord();
+			codexDeductRecord.setCcRelationId(ccRelationId);
+			codexDeductRecord.setOrderId(orderDon.getId());
+			codexDeductRecord.setRelationId(relationId);
+			codexDeductRecordMapper.insert(codexDeductRecord);
+		} finally {
+			if (redisService.hasKey(cacheKye)) {
+				redisService.del(cacheKye);
+			}
+		}
+		return orderDon;
+	}
+
+	@Override
+	public Boolean getUserSubs(Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		//是否有该车票
+		GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getGoodsId, Constant.CODEX_GOODS_ID)
+				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+				.build());
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+		//车票不存在
+		if (groupsRelationView == null) {
+			//套餐是否存在
+			if (codexUser != null) {
+				//清除用户套餐
+				if (delUserPackages(codexUser.getUserId())) {
+					codexUserMapper.deleteById(codexUser.getId());
+				}
+			}
+			return false;
+		}
+		return true;
+	}
+
+
+	@Override
+	public CodexUserInfoView getUserSubsInfo(long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		//是否有该车票
+		GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getGoodsId, Constant.CODEX_GOODS_ID)
+				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+				.build());
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+		//车票不存在
+		if (groupsRelationView == null) {
+			//套餐是否存在
+			if (codexUser != null) {
+				//清除用户套餐
+				if (delUserPackages(codexUser.getUserId())) {
+					codexUserMapper.deleteById(codexUser.getId());
+				}
+			}
+			return null;
+		}
 
-		String planUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/users/%s/plan", userId);
-		ClaudeCodeResp codexResp = executeCodexPostApi(planUrl, Jsons.toJson(params));
+		GoodsDonSku sku = null;
+		if (codexUser == null) {
+			OrderDon orderDon = orderDonMapper.selectOne(Wrappers.lambdaQuery(OrderDon.class)
+					.eq(OrderDon::getGoodsId, Constant.CODEX_GOODS_ID)
+					.eq(OrderDon::getRelationId, groupsRelationView.getId())
+					.in(OrderDon::getUserId, userIdList)
+					.notIn(OrderDon::getStatus, Constant.noOrderAllStatus)
+					.orderByDesc(OrderDon::getId)
+					.last("limit 1"));
+			if (orderDon != null) {
+				sku = skuMapper.selectById(orderDon.getSkuId());
+				createOrUpdateCodexUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
+			} else {
+				//试用车票
+				Long skuId = groupsRelationView.getSkuId();
+				sku = skuMapper.selectById(skuId);
+				createOrUpdateCodexUserInfo(null, groupsRelationView.getId(), groupsRelationView.getUserId(), sku, null);
+			}
+			codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+		} else {
+			codexUser.setExpiryTime(groupsRelationView.getExpiryTime());
+			codexUserMapper.updateById(codexUser);
+		}
+		//续费升级
+		if (codexUser.getRenewSkuId() != null) {
+			sku = skuMapper.selectById(codexUser.getRenewSkuId());
+		}
+		if (sku == null) {
+			sku = skuMapper.selectById(groupsRelationView.getSkuId());
+		}
+		//是否需要重试更新 cladueCode userInfo信息
+		if (codexUser.getIsRetry()) {
+			try {
+				ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime());
+				if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
+					codexUser.setIsRetry(Boolean.TRUE);
+					codexUserMapper.updateById(codexUser);
+				} else {
+					codexUser.setIsRetry(Boolean.FALSE);
+					codexUserMapper.updateById(codexUser);
+				}
+			} catch (Exception e) {
 
-		if (!"success".equals(codexResp.getMessage())) {
-			log.info("用户codex用户:{}套餐:{}创建或更新失败,info:{}", userId, planName, codexResp.getMessage());
-			throw BusinessRuntimeException.getInstance("调用codex 套餐接口失败");
+			}
 		}
+		return CodexUserInfoView.builder()
+				.planName(codexUser.getPlanName())
+				.relationId(codexUser.getRelationId())
+				.openaiDailyLimit(codexUser.getOpenaiDailyLimit())
+				.openaiQuota(codexUser.getOpenaiQuota())
+				.expiryTime(codexUser.getExpiryTime())
+				.renewSkuId(Optional.ofNullable(codexUser.getRenewSkuId()).orElse(groupsRelationView.getSkuId()))
+				.renewOrderId(codexUser.getRenewOrderId())
+				.renewExpiryTime(codexUser.getRenewExpiryTime())
+				.build();
+	}
+
+	@Override
+	public ClaudeCodeResp createApiKeys(ClaudeCodeApiKeysReq req) throws Exception {
+		Long userId = req.getUserId();
+		if (checkCodexUser(userId)) {
+			Long codexUserId = getCodexUserId(userId);
+			Assert.notNull(codexUserId, "您的codex账号已过期");
 
-		return codexResp;
+			req.setUserId(codexUserId);
+			ClaudeCodeResp claudeCodeResp = claudeCodexApiService.createApiKeys(req);
+			return claudeCodeResp;
+		}
+		throw new BusinessRuntimeException("您还没有codex账号");
 	}
 
+	@Override
+	public void deleteApiKeysById(ClaudeCodeDelReq delReq) throws Exception {
+		Long userId = delReq.getUserId();
+		Long keyId = delReq.getKeyId();
+		if (checkCodexUser(userId)) {
+			Long codexUserId = getCodexUserId(userId);
+			claudeCodexApiService.deleteApiKeysById(codexUserId, keyId);
+		}
+	}
+
+	@Override
+	public List<ClaudeCodeUserApiKeysView> getUserApiKeys(long userId) throws Exception {
+		Long codexUserId = getCodexUserId(userId);
+		if (codexUserId != null) {
+			List<ClaudeCodeUserApiKeysView> keysViews = claudeCodexApiService.getUserApiKeys(codexUserId);
+			return keysViews;
+		}
+		return null;
+	}
+
+	private GroupsRelation getClaudeCodeRelation(Long userId, GoodsDonSku sku) {
+		//获取车票车位
+		GroupsRelation relation = getRelation(sku);
+		//锁定座位
+		setRelation(relation);
+		//校验座位
+		int update = relationMapper.update(null, Wrappers.lambdaUpdate(GroupsRelation.class)
+				.set(GroupsRelation::getUserId, userId)
+				.eq(GroupsRelation::getUserId, 0)
+				.eq(GroupsRelation::getId, relation.getId()));
+		if (update == 0) {
+			throw BusinessRuntimeException.getInstance("系统繁忙,请重试");
+		}
+		relation.setUserId(userId);
+		//重新置为锁定
+		relation.setStatus(GroupsRelation.Status.locking);
+		relationMapper.updateById(relation);
+		return relation;
+	}
+
+	private GroupsRelation getRelation(GoodsDonSku sku) {
+		GroupsRelation relation = null;
+		Integer maxNum = sku.getNum();
+		GroupsTrips groups = groupsMapper.selectTicketGroups(sku.getId(), GroupsTrips.Status.validity, maxNum, null, null);
+		if (groups == null) {
+			//待发车
+			groups = groupsMapper.selectTicketGroups(sku.getId(), GroupsTrips.Status.waiting, maxNum, null, null);
+		}
+		//新增空车队
+		if (groups == null) {
+			relation = groupsFuncService.createNewGroupsTrips(sku);
+		}
+		//获取车位
+		if (relation == null) {
+			//寻找快满编车队进行占座
+			relation = relationMapper.selectRandomOneRelationByGroupsId(groups.getId());
+			if (relation == null) {
+				throw BusinessRuntimeException.getInstance("系统繁忙,请重试");
+			}
+		}
+		return relation;
+	}
+
+	public BigDecimal getFinalDeductMoney(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("codex车票id:{}目前续费升级规格可抵扣的金额为:{}", relationId, renewDeductMoney);
+				deductMoney = deductMoney.add(renewDeductMoney);
+				relationExpiryTime = DateUtil.offsetDay(relationExpiryTime, -betweenDay.intValue());
+			}
+			//上一个续费升级规格
+			List<OrderDon> orderDOns = codexUserRenewExpiryRecordMapper.getOtherRenewSkus(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("codex车票id:{}目前次一级规格:{}可抵扣的金额为:{}", relationId, thisRenewSku.getSpecVal(), thisRenewDeductMoney);
+
+			}
+		}
+		if (relationExpiryTime.after(now)) {
+			//该车票抵扣金额
+			BigDecimal deductMoneyFromTicket = getDeductMoneyFromTicket(relationId, userIdList, relationSkuId, now, relationStartTime, relationExpiryTime);
+			deductMoney = deductMoney.add(deductMoneyFromTicket);
+		}
+		log.info("codex车票id:{}最终到期时间为:{}车票可返回 抵扣金额:{}", relationId, DateUtil.format(relationExpiryTime, "yyyy-MM-dd HH:mm:ss"), deductMoney);
+		return deductMoney;
+	}
 
 	private Retryer<ClaudeCodeResp> getApiRetryer(int wait, int stop) {
 		return RetryerBuilder.<ClaudeCodeResp>newBuilder().retryIfResult(result -> result == null).retryIfException().withWaitStrategy(WaitStrategies.fixedWait(wait, TimeUnit.SECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(stop)).build();
 	}
 
 	/**
-	 * Codex POST请求 - 复制ClaudeCodeServiceImpl的实现模式
+	 * Codex POST请求
 	 */
 	public ClaudeCodeResp executeCodexPostApi(String url, String body) {
 		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
@@ -210,10 +609,7 @@ public class CodexServiceImpl implements CodexService {
 		if (codexUser == null) {
 			return null;
 		}
-		// 调用Codex API获取实时使用统计
-		String usageUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/usage?period=%s", codexUser.getUserId(), period);
-		ClaudeCodeResp codexResp = executeCodexGetApi(usageUrl);
-		return codexResp;
+		return claudeCodexApiService.getCodexUserOpenAIUsage(codexUser.getUserId(), period);
 	}
 
 	@Override
@@ -267,42 +663,6 @@ public class CodexServiceImpl implements CodexService {
 			throw new BusinessRuntimeException("更新每日限额失败");
 		}
 	}
-
-	@Override
-	public ClaudeCodeResp getSystemMetrics(String period, String metric) {
-		try {
-			// 构建系统指标API URL
-			String metricsUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/system/metrics");
-			
-			// 添加查询参数
-			List<String> queryParams = new ArrayList<>();
-			if (period != null && !period.isEmpty()) {
-				queryParams.add("period=" + period);
-			}
-			if (metric != null && !metric.isEmpty()) {
-				queryParams.add("metric=" + metric);
-			}
-			
-			if (!queryParams.isEmpty()) {
-				metricsUrl += "?" + String.join("&", queryParams);
-			}
-			
-			// 调用Codex API获取系统指标
-			ClaudeCodeResp codexResp = executeCodexGetApi(metricsUrl);
-			
-			if (codexResp == null) {
-				log.error("获取系统指标失败: API返回null");
-				throw new BusinessRuntimeException("获取系统指标失败");
-			}
-			
-			log.info("成功获取系统指标, period={}, metric={}", period, metric);
-			return codexResp;
-			
-		} catch (Exception e) {
-			log.error("获取系统指标异常, period={}, metric={}, error={}", period, metric, StringUtil.getErrorText(e));
-			throw new BusinessRuntimeException("获取系统指标失败");
-		}
-	}
 	
 	@Override
 	public ClaudeCodeResp getJavaUserDashboard(Long userId) {
@@ -312,11 +672,7 @@ public class CodexServiceImpl implements CodexService {
 		if (codexUser == null) {
 			return null;
 		}
-
-		// 调用Codex API获取用户控制台数据
-		String dashboardUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/dashboard", codexUser.getUserId());
-		ClaudeCodeResp codexResp = executeCodexGetApi(dashboardUrl);
-		return codexResp;
+		return claudeCodexApiService.getCodexUserDashboard(codexUser.getUserId());
 	}
 	
 	@Override
@@ -333,12 +689,7 @@ public class CodexServiceImpl implements CodexService {
 			start = DateUtil.offsetDay(now, -1).toString();
 			end = now.toString();
 		}
-
-		// 调用Codex API获取用户分析数据
-		String analyticsUrl = String.format(Constant.CLAUDE_CODE_API_PREFIX + "api/openai/users/%s/analytics?start=%s&end=%s&page=%s&limit=%s&order=%s", codexUser.getUserId(), start, end, page, limit, order);
-		ClaudeCodeResp codexResp = executeCodexGetApi(analyticsUrl);
-
-		return codexResp;
+		return claudeCodexApiService.getCodexUserAnalytics(codexUser.getUserId(), start, end, page, limit, order);
 	}
 
 	@Override
@@ -350,6 +701,114 @@ public class CodexServiceImpl implements CodexService {
 		}
 	}
 
+	@Override
+	public void createOrUpdateCodexUserInfo(Long orderId, Long relationId, Long userId, GoodsDonSku sku, Integer orderType) {
+		//新增或续费升级
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		GroupsRelation relation = relationMapper.selectOne(Wrappers.lambdaQuery(GroupsRelation.class).eq(GroupsRelation::getId, relationId).in(GroupsRelation::getUserId, userIdList));
+		GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
+		GoodsDonSku relationSku = skuMapper.selectById(groupsTrips.getSkuId());
+		Long thisCodeOriSkuId = relationSku.getId();
+		Long relationUserId = relation.getUserId();
+		//此时车票的套餐规格积分
+		Integer thisRelationDayilyLimit = null;
+		CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
+		if (codexUser != null) {
+			thisRelationDayilyLimit = codexUser.getOpenaiDailyLimit();
+		}
+		Date expiryTime = relation.getExpiryTime();
+		Integer openaiDailyLimit = sku.getOpenaiDailyLimit();
+		Integer openaiQuota = sku.getOpenaiQuota();
+		Long skuId = sku.getId();
+		Boolean isUpgrade = false;
+		Date renewExpiryTime = null;
+		//若是续费升级 记录此时续费升级的过期时间
+		if (!ObjectUtil.equal(thisCodeOriSkuId, skuId)) {
+			//非车票原规格id 续费升级记录
+			saveCodexRenewExpiryRecord(orderId, relationUserId, relationId, skuId);
+			//是否需要修改当前claude code user 套餐等级
+			if (thisRelationDayilyLimit != null && thisRelationDayilyLimit <= openaiDailyLimit) {
+				if (sku.getDays() != null) {
+					renewExpiryTime = DateUtil.offsetDay(DateTime.now(), sku.getDays());
+				} else {
+					renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), sku.getMonths());
+				}
+				isUpgrade = true;
+			}
+		}
+		//更新db codexUser
+		generateOrUpdateCodexUser(relationUserId, relationId, openaiDailyLimit, openaiQuota, expiryTime, isUpgrade, orderId, skuId, renewExpiryTime, sku.getSpecVal(), userIdList, Boolean.TRUE);
+
+		try {
+			//获取最新的claude code user
+			codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"));
+			//生成codex或更新用户套餐
+			ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(relation.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), codexUser.getExpiryTime());
+			if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
+				//重试更新code userInfo数据
+				setCodexUserRetryUpdateInfo(relationId);
+				return;
+			}
+		} catch (Exception e) {
+			log.error("生成或更新claude code用户:{}套餐skuId:{} 失败,error_info:{}", userId, skuId, StringUtil.getErrorText(e));
+			//重试更新code userInfo数据
+			setCodexUserRetryUpdateInfo(relationId);
+		}
+	}
+
+	@Override
+	public void updateCodexUserPackageInfo(CodexUser codexUser, Date expiryTime, GoodsDonSku finalSku) {
+		try {
+			ClaudeCodeResp codeUserPackage = createOrUpdateCodexUserPackageApi(codexUser.getUserId(), codexUser.getPlanName(), codexUser.getOpenaiDailyLimit(), codexUser.getOpenaiQuota(), expiryTime);
+			if (!Constant.SUCCESS.equals(codeUserPackage.getMessage())) {
+				//重试标记
+				codexUser.setIsRetry(Boolean.TRUE);
+				codexUserMapper.updateById(codexUser);
+				return;
+			}
+		} catch (Exception e) {
+			codexUser.setIsRetry(Boolean.TRUE);
+			codexUserMapper.updateById(codexUser);
+		}
+	}
+
+	private void generateOrUpdateCodexUser(Long relationUserId, Long relationId, Integer openaiDailyLimit, Integer openaiQuota, Date expiryTime, Boolean isUpgrade, Long orderId, Long renewSkuId, Date renewExpiryTime, String planName, List<Long> userIdList, Boolean isPay) {
+		CodexUser codexUser = Optional.ofNullable(codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId).last("limit 1"))).orElse(new CodexUser());
+		if (isUpgrade) {
+			codexUser.setRenewOrderId(orderId);
+			codexUser.setRenewSkuId(renewSkuId);
+			codexUser.setRenewExpiryTime(renewExpiryTime);
+			codexUser.setPlanName(planName);
+			codexUser.setOpenaiDailyLimit(openaiDailyLimit);
+			codexUser.setOpenaiQuota(openaiQuota);
+		}
+		codexUser.setIsPay(isPay);
+		codexUser.setUserId(relationUserId);
+		codexUser.setRelationId(relationId);
+		codexUser.setExpiryTime(expiryTime);
+		if (codexUser.getId() == null) {
+			codexUser.setPlanName(planName);
+			codexUser.setOpenaiDailyLimit(openaiDailyLimit);
+			codexUser.setOpenaiQuota(openaiQuota);
+		}
+		if (codexUser.getId() == null) {
+			try {
+				codexUserMapper.insert(codexUser);
+			} catch (DuplicateKeyException e) {
+				log.info("插入codex user 重复");
+			}
+		} else codexUserMapper.updateById(codexUser);
+	}
+
+	private void saveCodexRenewExpiryRecord(Long orderId, Long relationUserId, Long relationId, Long skuId) {
+		CodexUserRenewExpiryRecord renewExpiryRecord = new CodexUserRenewExpiryRecord();
+		renewExpiryRecord.setOrderId(orderId);
+		renewExpiryRecord.setUserId(relationUserId);
+		renewExpiryRecord.setRelationId(relationId);
+		renewExpiryRecord.setRenewSkuId(skuId);
+		codexUserRenewExpiryRecordMapper.insert(renewExpiryRecord);
+	}
+
 	/**
 	 * Codex DELETE请求
 	 */
@@ -379,30 +838,360 @@ public class CodexServiceImpl implements CodexService {
 	}
 
 	/**
-	 * Codex GET请求
+	 * 获取订单单价
 	 */
-	public ClaudeCodeResp executeCodexGetApi(String url) {
-		Retryer<ClaudeCodeResp> build = getApiRetryer(1, 3);
-		try {
-			return build.call(() -> {
-				try {
-					HttpResponse execute = HttpUtil.createGet(url)
-							.header(Constant.CLAUDE_CODE_HEARD_KEY, Constant.CLAUDE_CODE_API_ADMIN_KEY)
-							.setConnectionTimeout(Constant.CONNECT_MILLISECONDS)
-							.execute();
-					ClaudeCodeResp resp = Jsons.parseObject(execute.body(), ClaudeCodeResp.class);
-					if (!"success".equals(resp.getMessage())) {
-						log.error("codex GET URL:{}接口返回msg:{}", url, resp.getMessage());
+	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));
+		}
+		//原车票订单总实付金额
+		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;
+	}
+
+	public void setRelation(GroupsRelation relation) {
+		Long relationId = relation.getId();
+		Long userId = relation.getUserId();
+		Long groupsId = relation.getGroupsId();
+		GroupsRelation.Status status = relation.getStatus();
+		String relation_num_key = RedisKey.GROUPS_RELATION_NUM_KEY + relationId;
+		if (!redisService.setNx(relation_num_key, userId, 60 * 5L)) {
+			GroupsRelation dbRelation = relationMapper.selectById(relationId);
+			if (dbRelation.getUserId() != 0) {
+				log.info("=====>用户:{}未抢到座位:{}", userId, relationId);
+				throw new BusinessRuntimeException("系统繁忙,请重试...");
+			}
+		}
+		if (status == GroupsRelation.Status.outside) {
+			return;
+		}
+		int count = groupsMapper.decrAvailableNum(groupsId);
+		if (count == 0) {
+			log.error("车位异常 groupId:{}", groupsId);
+			groupsMapper.updateAvailableNum(groupsId);
+			//清除座位缓存key
+			delRelationKey(relationId, userId);
+			throw new BusinessRuntimeException("车位异常");
+		}
+	}
+
+	/**
+	 * 清除座位缓存key
+	 */
+	public void delRelationKey(Long relationId, Long userId) {
+		String relation_num_key = RedisKey.GROUPS_RELATION_NUM_KEY + relationId;
+		Object object = redisService.get(relation_num_key);
+		if (object != null) {
+			try {
+				Long keyUserId = Long.parseLong(object.toString());
+				//清除车票key
+				if (ObjectUtil.equal(userId, keyUserId)) {
+					redisService.del(relation_num_key);
+				}
+			} catch (Exception e) {
+				log.error("删除缓存key错误:{}", StringUtil.getErrorText(e));
+			}
+		}
+	}
+
+	public CouponPopularizeDto checkCouponStatus(Long skuId, String couponExCode, Long couponId, Long userId, Boolean isRenew) throws Exception {
+		log.info("开始校验兑换码:{},优惠券id:{}状态", couponExCode, couponId);
+		//实物还是虚物商品
+		GoodsDonSku goodsDonSku = skuMapper.selectById(skuId);
+		GoodsDon goodsDon = goodsDonMapper.selectById(goodsDonSku.getGoodsId());
+		if (StrUtil.isNotBlank(couponExCode) && couponId != null) {
+			throw BusinessRuntimeException.getInstance("优惠券与优惠码不可同时使用..");
+		}
+		if (StrUtil.isNotBlank(couponExCode)) {
+			CouponPopularizeDto exists = couponMapper.checkExCode(couponExCode);
+			if (exists == null) {
+				throw BusinessRuntimeException.getInstance("该优惠码不存在");
+			}
+			//推广码
+			if (exists.getIsGeneral() != null) {
+				if (exists.getIsGeneral() && userId.equals(exists.getUserId())) {
+					throw BusinessRuntimeException.getInstance("不可以使用自己的优惠码");
+				}
+				if (!exists.getIsGeneral()) {
+					UserDistribute userDistribute = userDistributeMapper.selectById(exists.getDistributeId());
+					if (userDistribute != null && userDistribute.getUserId().equals(userId)) {
+						throw BusinessRuntimeException.getInstance("不可以使用自己的优惠码");
 					}
-					return resp;
-				} catch (Exception e) {
-					log.error("codex GET请求异常: url={}, error={}", url, StringUtil.getErrorText(e));
-					return null;
 				}
-			});
-		} catch (ExecutionException | com.github.rholder.retry.RetryException e) {
-			log.error("重试调用codex GET请求url->{}失败,msg->{}", url, StringUtil.getErrorText(e));
-			throw BusinessRuntimeException.getInstance("获取codex使用统计失败");
+				//是否有上下级关系
+				if (exists.getIsGeneral()) {
+					UserDistributeShared userDistributeShared = userDistributeSharedMapper.selectOne(Wrappers.lambdaQuery(UserDistributeShared.class)
+							.eq(UserDistributeShared::getUserId, userId).last("limit 1"));
+					if (userDistributeShared == null) {
+						//是否是 当天注册新用户 且下过单
+						User user = userMapper.selectById(userId);
+						if (user.getCreatedTime().before(DateUtil.beginOfDay(DateTime.now()))) {
+							throw BusinessRuntimeException.getInstance("该优惠码只适用于新用户");
+						}
+						CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
+						if (couponNewUserView != null)
+							throw BusinessRuntimeException.getInstance("该优惠码只适用于新用户");
+					}
+					if (userDistributeShared != null && !userDistributeShared.getSharedId().equals(exists.getUserId())) {
+						throw BusinessRuntimeException.getInstance("您已有推广,无法使用其他人推广码");
+					}
+				}
+			}
+			checkCouponGoods(goodsDon, skuId, exists.getCouponId(), true);
+			//检验是优惠券使用范围
+			if (isRenew && exists.getUseScope() == Coupon.UseScope.orders) {
+				throw BusinessRuntimeException.getInstance("该优惠码续费时不可使用");
+			}
+			if (!isRenew && exists.getUseScope() == Coupon.UseScope.renew) {
+				throw BusinessRuntimeException.getInstance("该优惠码下单时不可使用");
+			}
+			if (exists.getScope() == Coupon.Scope.newUser) {
+				CouponNewUserView couponNewUserView = beanSearcher.searchFirst(CouponNewUserView.class, MapUtils.builder().field(CouponNewUserView::getUserId, userId).op(Operator.Equal).build());
+				if (couponNewUserView != null) {
+					throw BusinessRuntimeException.getInstance("该新用户优惠码有且仅可使用一次");
+				}
+			}
+			if (exists.getIsValidTime() && exists.getValidEndTime().before(DateTime.now())) {
+				throw BusinessRuntimeException.getInstance("该优惠码已失效");
+			}
+			if (exists.getIsValidTime() && exists.getValidStartTime().after(DateTime.now())) {
+				throw BusinessRuntimeException.getInstance("该优惠码待生效");
+			}
+			return exists;
+		} else if (couponId != null) {
+			CouponUserView couponUserView = beanSearcher.searchFirst(CouponUserView.class, MapUtils.builder()
+					.field(CouponUserView::getCouponId, couponId).op(Operator.Equal)
+					.field(CouponUserView::getUserId, userId).op(Operator.Equal)
+					.build());
+			if (couponUserView == null) {
+				throw BusinessRuntimeException.getInstance("该优惠券不存在");
+			}
+			if (couponUserView.getCouponUserStatus() == CouponUser.Status.used) {
+				throw BusinessRuntimeException.getInstance("该优惠券已使用");
+			}
+			couponFontService.setCouponUserStatus(couponUserView, DateTime.now());
+			if (couponUserView.getCouponUserStatus() != CouponUser.Status.unused) {
+				throw BusinessRuntimeException.getInstance(couponUserView.getReason());
+			}
+			checkCouponGoods(goodsDon, skuId, couponUserView.getCouponId(), false);
+			//检验是优惠券使用范围
+			if (isRenew && couponUserView.getUseScope() == Coupon.UseScope.orders) {
+				throw BusinessRuntimeException.getInstance("该优惠券续费时不可使用");
+			}
+			if (!isRenew && couponUserView.getUseScope() == Coupon.UseScope.renew) {
+				throw BusinessRuntimeException.getInstance("该优惠券下单时不可使用");
+			}
+		}
+		return null;
+	}
+
+
+	private CouponUser couponStatusRecord(String couponExCode, Long couponId, Long userId, CouponPopularizeDto exists) {
+		log.info("兑换码:{},优惠券id:{},更新其优惠券状态", couponExCode, couponId);
+		CouponUser couponUser = null;
+		if (StrUtil.isNotBlank(couponExCode)) {
+			if (exists == null) {
+				throw BusinessRuntimeException.getInstance("该优惠码不存在");
+			}
+			couponUser = new CouponUser();
+			couponUser.setCouponId(exists.getCouponId());
+			if (exists.getPopularizeId() != null) {
+				couponUser.setCouponId(exists.getPopularizeCouponId());
+				couponUser.setPopularizeId(exists.getPopularizeId());
+			}
+			//续费优惠码可重复使用
+			if (exists.getUseScope() != Coupon.UseScope.renew) {
+				//非渠道推广 推广优惠码只可使用一次
+				if ((exists.getPopularizeId() != null && exists.getIsGeneral()) || exists.getPopularizeId() == null) {
+					CouponUser received = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getUserId, userId).eq(CouponUser::getCouponId, couponUser.getCouponId()).last("limit 1"));
+					if (received != null && received.getStatus() != CouponUser.Status.unused) {
+						throw BusinessRuntimeException.getInstance("您已使用该优惠码所对应的优惠券");
+					}
+					couponUser.setUserId(userId);
+					couponUser.setStatus(CouponUser.Status.used);
+					couponUser.setChannel(CouponUser.Channel.exCode);
+					couponFontService.updateCouponUserValidTime(couponUser.getCouponId(), couponUser);
+					try {
+						couponUserMapper.insert(couponUser);
+					} catch (DuplicateKeyException e) {
+						log.info("用户已领取该{}优惠券", couponUser.getCouponId());
+						throw BusinessRuntimeException.getInstance("您已领取该优惠码对应的优惠券,请使用优惠券");
+					}
+				}
+			}
+		} else {
+			couponUser = couponUserMapper.selectOne(Wrappers.lambdaQuery(CouponUser.class).eq(CouponUser::getUserId, userId).eq(CouponUser::getCouponId, couponId).last("limit 1"));
+			if (couponUser == null) {
+				throw BusinessRuntimeException.getInstance("该优惠券不存在");
+			}
+			couponUser.setStatus(CouponUser.Status.used);
+			couponUserMapper.updateById(couponUser);
+		}
+		return couponUser;
+	}
+
+	public void checkCouponGoods(GoodsDon goodsDon, Long skuId, Long couponId, Boolean isCouponCode) throws Exception {
+		if (goodsDon.getType() == 1) {
+			CouponSku couponSku = couponSkuMapper.selectOne(Wrappers.lambdaQuery(CouponSku.class)
+					.eq(CouponSku::getSkuId, skuId)
+					.eq(CouponSku::getCouponId, couponId)
+					.select(CouponSku::getId).last("limit 1"));
+			if (couponSku == null) {
+				throw BusinessRuntimeException.getInstance(String.format("该%s不适用于该规格", isCouponCode ? "优惠码" : "优惠券"));
+			}
+		} else {
+			//实物、潮玩 优惠码优惠券 针对平台
+			Long gid = goodsDon.getId();
+			CouponSku couponSku = couponSkuMapper.selectOne(Wrappers.lambdaQuery(CouponSku.class)
+					.eq(CouponSku::getGoodsId, gid)
+					.eq(CouponSku::getCouponId, couponId)
+					.select(CouponSku::getId).last("limit 1"));
+			if (couponSku == null) {
+				throw BusinessRuntimeException.getInstance(String.format("该%s不可用于该商品", isCouponCode ? "优惠码" : "优惠券"));
+			}
+		}
+	}
+
+	/**
+	 * 使用优惠营销 订单金额抵扣
+	 */
+	public void deductOrderMoney(OrderDon orderDon, CouponUser couponUser) throws Exception {
+		BigDecimal first_money = orderDon.getMoney();
+		//存在优惠券
+		if (couponUser != null) {
+			Long couponId = couponUser.getCouponId();
+			Coupon coupon = couponMapper.getCouponById(couponId);
+			BigDecimal couponMoney = null;
+			BigDecimal payMoney = orderDon.getMoney();
+			BigDecimal needDeductMoney = payMoney;
+			if (orderDon.getOriMoney() == null) {
+				orderDon.setOriMoney(payMoney);
+			}
+			if (orderDon.getIsDp() != null && orderDon.getIsDp()) {
+				needDeductMoney = needDeductMoney.subtract(orderDon.getDpMoney());
+			}
+			if (coupon.getType() == Coupon.Type.discount) {
+				couponMoney = needDeductMoney.multiply(BigDecimal.valueOf(1).subtract(coupon.getDiscount()));
+				//需支付金额
+				orderDon.setMoney(payMoney.subtract(couponMoney));
+
+				orderDon.setOriMoney(orderDon.getOriMoney().multiply(coupon.getDiscount()));
+				//记录折扣 用于分销
+				couponUser.setDiscount(coupon.getDiscount());
+				couponUserMapper.updateById(couponUser);
+			} else if (coupon.getType() == Coupon.Type.reduce) {
+				BigDecimal subtractMoney = payMoney.subtract(coupon.getReduceMoney());
+				orderDon.setMoney(subtractMoney);
+				couponMoney = coupon.getReduceMoney();
+
+				orderDon.setOriMoney(orderDon.getOriMoney().subtract(coupon.getReduceMoney()));
+			}
+			if (couponMoney != null && couponMoney.compareTo(first_money) >= 0) {
+				throw BusinessRuntimeException.getInstance("支付金额不能小于优惠金额");
+			}
+			orderDon.setCouponMoney(couponMoney);
+			orderDon.setCouponUserId(couponUser.getId());
+			orderDon.setCouponId(couponUser.getCouponId());
+		}
+	}
+
+	@Override
+	public boolean delUserPackages(Long userId) {
+		return claudeCodexApiService.delCodexUserPackages(userId);
+	}
+
+	@Override
+	public void handleClaudeCodeUserPackageSku(GroupsRelation relation, GoodsDonSku sku, Long orderId) {
+		//更新套餐
+		createOrUpdateCodexUserInfo(orderId, relation.getId(), relation.getUserId(), sku, 2);
+	}
+
+	/**
+	 * 校验是否是codex用户
+	 * @param userId
+	 * @return
+	 */
+	public Boolean checkCodexUser(Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		Integer count = relationMapper.selectCountByGoodsId(userIdList, Constant.CODEX_GOODS_ID, DateTime.now());
+		return count > 0;
+	}
+
+	/**
+	 * 获取codex用户id
+	 */
+	public Long getCodexUserId(Long userId) {
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+		GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder()
+				.field(GroupsRelationView::getUserId, userIdList).op(Operator.InList)
+				.field(GroupsRelationView::getGoodsId, Constant.CODEX_GOODS_ID)
+				.field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan)
+				.build());
+		if (groupsRelationView != null) {
+			return groupsRelationView.getUserId();
 		}
+		return null;
 	}
 }

+ 1 - 1
netflix-service/src/main/java/com/cyksj/service/distribute/impl/DistributeServiceImpl.java

@@ -348,7 +348,7 @@ public class DistributeServiceImpl implements DistributeService {
 			userDistributeSharedMapper.insert(userDistributeShared);
 			if (Constant.CC_DISTRIBUTE_MARK.equals(userDistributeShared.getRemark())) {
 				//若是cc普通推广 发送claude code积分卡
-				claudeCodeService.sendCreditCard(null, userDistributeShared.getSharedId(), 1000, Constant.CLAUDE_CODE_CARD_NUMBER + System.currentTimeMillis(), "新用户注册");
+				claudeCodeService.sendCreditCard(null, userDistributeShared.getSharedId(), Constant.CLAUDE_CODE_SUP_QUOTA, Constant.CLAUDE_CODE_CARD_NUMBER + System.currentTimeMillis(), "新用户注册");
 				//存在Claude code车票不发送体验卡
 				if (!claudeCodeService.checkClaudeCodeUser(userId)) {
 					//发送claude code 体验卡

+ 41 - 8
netflix-service/src/main/java/com/cyksj/service/exchange/impl/ExchangeCodeServiceImpl.java

@@ -28,6 +28,7 @@ import com.cyksj.model.request.ExchangeCodeReq;
 import com.cyksj.model.request.ExchangeCodeTicket;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.exchange.ExchangeCodeService;
 import com.cyksj.service.relation.GroupRelationFrontService;
 import com.cyksj.service.relation.GroupsRelationExpiryRecordService;
@@ -99,10 +100,10 @@ public class ExchangeCodeServiceImpl extends ServiceImpl<ExchangeCodeMapper, Exc
 
 	private final UserBindRelationService userBindRelationService;
 
-	private final ClaudeCodeUserMapper claudeCodeUserMapper;
-
 	private final ClaudeCodeService claudeCodeService;
 
+	private final CodexService codexService;
+
 	@Override
 	@Transactional(rollbackFor = Throwable.class)
 	public List<ExcelExchangeCodeData> createExchangeCode(ExchangeCodeReq exchangeCodeReq) throws Exception {
@@ -391,14 +392,18 @@ public class ExchangeCodeServiceImpl extends ServiceImpl<ExchangeCodeMapper, Exc
 			orderDonMapper.insert(orderDon);
 			log.info("用户{}兑换规格:{}车位成功", user.getId(), orderDon.getSkuId());
 
-			if (relation.getIsClaudeCodeRenew() != null && relation.getIsClaudeCodeRenew()) {
-				//claude code续费
+			if (relation.getIsRenew() != null && relation.getIsRenew()) {
+				//claude code/codex续费
 				orderDon.setOrderType(2);
 				orderDonMapper.updateById(orderDon);
-				//续费且升级
+				//claude code续费且升级
 				if (relation.getIsClaudeCodeUpgrade() != null && relation.getIsClaudeCodeUpgrade()) {
 					claudeCodeService.handleClaudeCodeUserPackageSku(relation, sku, orderDon.getId());
 				}
+				//codex 续费且升级
+				if (relation.getIsCodexUpgrade() != null && relation.getIsCodexUpgrade()) {
+					codexService.handleClaudeCodeUserPackageSku(relation, sku, orderDon.getId());
+				}
 			}
 		}
 		if (orderDonTye == OrderDon.Type.EX_CODE) {
@@ -434,7 +439,7 @@ public class ExchangeCodeServiceImpl extends ServiceImpl<ExchangeCodeMapper, Exc
 			if (groupsRelationView != null) {
 				GoodsDonSku relationSku = goodsDonSkuMapper.selectById(groupsRelationView.getSkuId());
 				//兑换的规格 低于当前规格 不让兑换
-				if (relationSku.getCodePoints() > sku.getCodePoints()) {
+				if (relationSku.getClaudeDailyLimit() > sku.getClaudeDailyLimit()) {
 					throw BusinessRuntimeException.getInstance("请在claude code当前版本过期后再兑换");
 				}
 				Date expiryTime = groupsRelationView.getExpiryTime();
@@ -447,13 +452,41 @@ public class ExchangeCodeServiceImpl extends ServiceImpl<ExchangeCodeMapper, Exc
 				relation.setExpiryTime(expiryTime);
 				groupsRelationMapper.updateById(relation);
 				//同规格只加时长
-				relation.setIsClaudeCodeRenew(Boolean.TRUE);
-				if (relationSku.getCodePoints() != sku.getCodePoints()) {
+				relation.setIsRenew(Boolean.TRUE);
+				if (relationSku.getClaudeDailyLimit() != sku.getClaudeDailyLimit()) {
 					relation.setIsClaudeCodeUpgrade(true);
 				}
 				return relation;
 			}
 		}
+
+		//codex 商品兑换
+		if (sku.getGoodsId() == Constant.CODEX_GOODS_ID) {
+			List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+			GroupsRelationView groupsRelationView = beanSearcher.searchFirst(GroupsRelationView.class, MapUtils.builder().field(GroupsRelationView::getUserId, userIdList).op(Operator.InList).field(GroupsRelationView::getGoodsId, Constant.CODEX_GOODS_ID).field(GroupsRelationView::getExpiryTime, DateTime.now()).op(Operator.GreaterThan).build());
+			if (groupsRelationView != null) {
+				GoodsDonSku relationSku = goodsDonSkuMapper.selectById(groupsRelationView.getSkuId());
+				//兑换的规格 低于当前规格 不让兑换
+				if (relationSku.getOpenaiDailyLimit() > sku.getOpenaiDailyLimit()) {
+					throw BusinessRuntimeException.getInstance("请在codex当前版本过期后再兑换");
+				}
+				Date expiryTime = groupsRelationView.getExpiryTime();
+				if (sku.getDays() != null) {
+					expiryTime = DateUtil.offsetDay(expiryTime, sku.getDays());
+				} else {
+					expiryTime = DateUtil.offsetMonth(expiryTime, sku.getMonths());
+				}
+				GroupsRelation relation = groupsRelationMapper.selectById(groupsRelationView.getId());
+				relation.setExpiryTime(expiryTime);
+				groupsRelationMapper.updateById(relation);
+				//同规格只加时长
+				relation.setIsRenew(Boolean.TRUE);
+				if (relationSku.getOpenaiDailyLimit() != sku.getOpenaiDailyLimit()) {
+					relation.setIsCodexUpgrade(true);
+				}
+				return relation;
+			}
+		}
 		Integer retryNum = 1;
 		List<Long> errorsRelationIds = new ArrayList<>();
 		GroupsRelation relation = getFinalRelation(sku, userId, retryNum, yhsId, errorsRelationIds);

+ 5 - 1
netflix-service/src/main/java/com/cyksj/service/groups/impl/GroupFunServiceImpl.java

@@ -100,7 +100,7 @@ public class GroupFunServiceImpl implements GroupsFuncService {
 		Boolean isMirror = sku.getIsMirror();
 		Boolean isCar = sku.getIsCar();
 		String account = null;
-		if ((goodsId == Constant.NANO_GOODS_ID) || (goodsId == Constant.CLAUDE_CODE_GOODS_ID) || (goodsId == 88 && isMirror) || (goodsId == 87 && isMirror) || (goodsId == 18 && isCar) || (goodsId == 26 && isMirror) || (goodsId == 75 && isCar)) {
+		if ((goodsId == Constant.CODEX_GOODS_ID) || (goodsId == Constant.NANO_GOODS_ID) || (goodsId == Constant.CLAUDE_CODE_GOODS_ID) || (goodsId == 88 && isMirror) || (goodsId == 87 && isMirror) || (goodsId == 18 && isCar) || (goodsId == 26 && isMirror) || (goodsId == 75 && isCar)) {
 			if (goodsId == 18) {
 				account = Constant.GPT_CAR_ONLY_ACCOUNT;
 			}
@@ -124,6 +124,10 @@ public class GroupFunServiceImpl implements GroupsFuncService {
 			if (goodsId == Constant.NANO_GOODS_ID) {
 				account = Constant.NANO_ONLY_ACCOUNT;
 			}
+			//codex
+			if (goodsId == Constant.CODEX_GOODS_ID) {
+				account = Constant.CODEX_ONLY_ACCOUNT;
+			}
 			AccountView accountView = beanSearcher.searchFirst(AccountView.class, MapUtils.builder()
 					.field(AccountView::getGoodsId, goodsId)
 					.field(AccountView::getAccount, account).

+ 81 - 4
netflix-service/src/main/java/com/cyksj/service/mange/impl/MirrorUserCleatServiceImpl.java

@@ -7,9 +7,11 @@ import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.cyksj.common.constant.Constant;
 import com.cyksj.mapper.*;
+import com.cyksj.mapper.codex.CodexUserRenewExpiryRecordMapper;
 import com.cyksj.model.entity.*;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.mange.MirrorUserCleatService;
 import com.cyksj.service.user.UserBindRelationService;
 import lombok.RequiredArgsConstructor;
@@ -68,6 +70,10 @@ public class MirrorUserCleatServiceImpl implements MirrorUserCleatService {
 
 	private final CodexUserMapper codexUserMapper;
 
+	private final CodexUserRenewExpiryRecordMapper codexUserRenewExpiryRecordMapper;
+
+	private final CodexService codexService;
+
 	/**
 	 * 删除镜像User
 	 */
@@ -139,6 +145,77 @@ public class MirrorUserCleatServiceImpl implements MirrorUserCleatService {
 		Long goodsId = orderDon.getGoodsId();
 		Long orderId = orderDon.getId();
 		List<Long> userIdList = userBindRelationService.getRelationUserIdList(orderDon.getUserId(), null);
+		//codex user
+		if (goodsId == Constant.CODEX_GOODS_ID) {
+			CodexUserRenewExpiryRecord renewExpiryRecord = codexUserRenewExpiryRecordMapper.selectOne(Wrappers.lambdaQuery(CodexUserRenewExpiryRecord.class).eq(CodexUserRenewExpiryRecord::getOrderId, orderId).eq(CodexUserRenewExpiryRecord::getIsValid, Boolean.FALSE).last("limit 1"));
+			if (renewExpiryRecord != null) {
+				//将续费有效置为过期
+				renewExpiryRecord.setIsValid(true);
+				codexUserRenewExpiryRecordMapper.updateById(renewExpiryRecord);
+			}
+			CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class)
+					.eq(CodexUser::getRelationId, orderDon.getRelationId())
+					.last("limit 1"));
+			if (codexUser == null) {
+				return;
+			}
+			Long presentRenewSkuId = codexUser.getRenewSkuId();
+			if (presentRenewSkuId == null) {
+				return;
+			}
+			GroupsRelation relation = groupsRelationMapper.selectById(orderDon.getRelationId());
+			GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
+			GoodsDonSku finalSku = skuMapper.selectById(groupsTrips.getSkuId());
+			Date renewExpiryTime = null;
+			//当前续费订单id
+			Long presentUserCodePackageOrderId = codexUser.getRenewOrderId();
+			codexUser.setRenewSkuId(null);
+			codexUser.setRenewOrderId(null);
+			if (renewExpiryRecord != null) {
+				//当前续费
+				OrderDon presentRenewOrderDon = orderDonMapper.selectById(presentUserCodePackageOrderId);
+				//是否存在其他续费升级
+				CodexUserRenewExpiryRecord otherRenewRecord = codexUserRenewExpiryRecordMapper.getOtherRenewSku(orderDon.getId(), relation.getId(), userIdList);
+				GoodsDonSku otherRenewSku = null;
+				if (otherRenewRecord != null) {
+					otherRenewSku = skuMapper.selectById(otherRenewRecord.getRenewSkuId());
+				}
+				//续费升级
+				if (otherRenewSku != null) {
+					finalSku = otherRenewSku;
+					if (otherRenewSku.getDays() != null && otherRenewSku.getDays() > 0) {
+						renewExpiryTime = DateUtil.offsetDay(DateTime.now(), otherRenewSku.getDays());
+					} else {
+						renewExpiryTime = DateUtil.offsetMonth(DateTime.now(), otherRenewSku.getMonths());
+					}
+					//相差多少天
+					Long betDay = 0l;
+					if (presentRenewOrderDon != null && presentRenewOrderDon.getCreatedTime().compareTo(otherRenewRecord.getCreatedTime()) > 0) {
+						betDay = DateUtil.betweenDay(otherRenewRecord.getCreatedTime(), presentRenewOrderDon.getCreatedTime(), false);
+					}
+					renewExpiryTime = DateUtil.offsetDay(renewExpiryTime, -betDay.intValue());
+
+					codexUser.setRenewOrderId(otherRenewRecord.getOrderId());
+					codexUser.setRenewSkuId(otherRenewRecord.getRenewSkuId());
+				}
+			}
+			//非当前套餐续费订单id 不修改claudeCode
+			if (!ObjectUtil.equal(presentUserCodePackageOrderId, orderId)) {
+				return;
+			}
+			//还原镜像user
+			codexUser.setOpenaiDailyLimit(finalSku.getOpenaiDailyLimit());
+			codexUser.setOpenaiQuota(finalSku.getOpenaiQuota());
+			codexUser.setRenewExpiryTime(renewExpiryTime);
+			codexUser.setPlanName(finalSku.getSpecVal());
+			codexUserMapper.updateById(codexUser);
+			log.info("还原镜像codex user套餐成功");
+			//调用api更新codex用户信息
+			Date expiryTime = relation.getExpiryTime();
+			codexService.updateCodexUserPackageInfo(codexUser, expiryTime, finalSku);
+			return;
+		}
+
 		//claude code user
 		if (goodsId == Constant.CLAUDE_CODE_GOODS_ID) {
 			ClaudeCodeUserRenewExpiryRecord renewExpiryRecord = claudeCodeUserRenewExpiryRecordMapper.selectOne(Wrappers.lambdaQuery(ClaudeCodeUserRenewExpiryRecord.class).eq(ClaudeCodeUserRenewExpiryRecord::getOrderId, orderId).eq(ClaudeCodeUserRenewExpiryRecord::getIsValid, Boolean.FALSE).last("limit 1"));
@@ -161,8 +238,8 @@ public class MirrorUserCleatServiceImpl implements MirrorUserCleatService {
 			GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
 			GoodsDonSku finalSku = skuMapper.selectById(groupsTrips.getSkuId());
 			Date renewExpiryTime = null;
-			Integer oriCodePoints = claudeCodeUser.getCodePoints();
-			claudeCodeUser.setOriCodePoints(oriCodePoints);
+			Integer oriClaudeDailyLimit = claudeCodeUser.getClaudeDailyLimit();
+			claudeCodeUser.setOriCodePoints(oriClaudeDailyLimit);
 			//当前续费订单id
 			Long presentUserCodePackageOrderId = claudeCodeUser.getRenewOrderId();
 			claudeCodeUser.setRenewSkuId(null);
@@ -200,8 +277,8 @@ public class MirrorUserCleatServiceImpl implements MirrorUserCleatService {
 				return;
 			}
 			//还原镜像user
-			claudeCodeUser.setCodePoints(finalSku.getCodePoints());
-			claudeCodeUser.setCodeCreditRecovery(finalSku.getCodeCreditRecovery());
+			claudeCodeUser.setClaudeDailyLimit(finalSku.getClaudeDailyLimit());
+			claudeCodeUser.setClaudeQuota(finalSku.getClaudeQuota());
 			claudeCodeUser.setRenewExpiryTime(renewExpiryTime);
 			claudeCodeUser.setPlanName(finalSku.getSpecVal());
 			claudeCodeUserMapper.updateById(claudeCodeUser);

+ 97 - 12
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.codex.CodexDeductRecordMapper;
 import com.cyksj.mapper.corp.CorpInteractiveUserOrderRelationMapper;
 import com.cyksj.mapper.corp.CorpUserMapper;
 import com.cyksj.mapper.corp.CorpUserTagMapper;
@@ -81,6 +82,7 @@ import com.cyksj.model.views.*;
 import com.cyksj.redis.RedisService;
 import com.cyksj.service.chatgpt.GptUserRechargeRecordService;
 import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.coin.UserGalaxyCoinService;
 import com.cyksj.service.corp.WxCorpOps;
 import com.cyksj.service.coupon.CouponFontService;
@@ -393,6 +395,14 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 
 	private final NanoUserQuotaMapper nanoUserQuotaMapper;
 
+	private final UserTicketClearedRecordMapper userTicketClearedRecordMapper;
+
+	private final CodexService codexService;
+
+	private final CodexDeductRecordMapper codexDeductRecordMapper;
+
+	private final CodexUserMapper codexUserMapper;
+
 	private final List<String> noChekGoodsTemp = List.of("Apple One", "Youtube", "Spotify");
 
 	@Transactional(rollbackFor = Throwable.class)
@@ -466,15 +476,40 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 				throw new BusinessRuntimeException("您有未支付订单.");
 			}
 		}
+		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
 		//claude code限购
 		if (goodsId == Constant.CLAUDE_CODE_GOODS_ID) {
 			//是否拥有这张车票
-			List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
 			Integer selectCount = groupsRelationMapper.selectCountByGoodsId(userIdList, Constant.CLAUDE_CODE_GOODS_ID, DateTime.now());
 			if (selectCount > 0) {
 				throw BusinessRuntimeException.getInstance("claude code车票限购一次");
 			}
+			//购买cc体验卡 仅可体检一次
+			if (sku != null && ObjectUtil.equal(sku.getId(), Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID)) {
+				checkCCTrialCardNum(userIdList);
+			}
+//			//是否是赠送codex sku
+//			if (sku.getIsCodex()) {
+//				//是否存在codex
+//				CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+//				if (codexUser != null) {
+//					throw BusinessRuntimeException.getInstance("您已购买了codex套餐,如需claude可用codex抵扣升级,不可重复购买");
+//				}
+//			}
 		}
+		//codex 限购
+		if (goodsId == Constant.CODEX_GOODS_ID) {
+			Integer selectCount = groupsRelationMapper.selectCountByGoodsId(userIdList, Constant.CODEX_GOODS_ID, DateTime.now());
+			if (selectCount > 0) {
+				throw BusinessRuntimeException.getInstance("codex车票限购一次");
+			}
+			//是否拥有cc赠送codex
+			CodexUser codexUser = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class).in(CodexUser::getUserId, userIdList).last("limit 1"));
+			if (codexUser != null) {
+				throw BusinessRuntimeException.getInstance("codex车票限购一次");
+			}
+		}
+
 		//校验奈飞限购信息
 		checkNfPayLimit(goodsId, userId);
 		GroupsRelation relation = null;
@@ -574,7 +609,6 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 		//租赁商品
 		if (goodsDon.getType() == 4) {
 			//是否已实名认证过
-			List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, user);
 			Integer count = userCertRecordMapper.selectCount(Wrappers.lambdaQuery(UserCertRecord.class).in(UserCertRecord::getUserId, userIdList));
 			if (count == 0) throw BusinessRuntimeException.getInstance("请您先完成实名认证..");
 
@@ -1260,7 +1294,7 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			if (thisCodeRenewSkuId != null) {
 				//claude code积分上限
 				GoodsDonSku renewSku = goodsDonSkuMapper.selectById(renewSkuId);
-				if (claudeCodeUser.getCodePoints() < renewSku.getCodePoints()) {
+				if (claudeCodeUser.getClaudeDailyLimit() < renewSku.getClaudeDailyLimit()) {
 					throw BusinessRuntimeException.getInstance("选择的续费套餐错误");
 				}
 			}
@@ -2128,15 +2162,15 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 						}
 						//是否是cc普通推广
 						//并且购买claude code作为首单
-						if (userDistribute == null && Constant.CC_DISTRIBUTE_MARK.equals(userDistributeShared.getRemark()) && order.getGoodsId() == Constant.CLAUDE_CODE_GOODS_ID) {
-							//普通推广用户首单,给上级渠道发放 claude code 积分卡
-							try {
-								claudeCodeService.adjUserRestNum(order.getId(), userDistributeShared.getSharedId(), 1, "新用户下单");
-								log.info("用户{}邀请新用户{}首单,发放claude code积分卡成功", userDistributeShared.getSharedId(), order.getUserId());
-							} catch (Exception e) {
-								log.error("给用户{}发放claude code积分卡失败:{}", userDistributeShared.getSharedId(), e.getMessage());
-							}
-						}
+//						if (userDistribute == null && Constant.CC_DISTRIBUTE_MARK.equals(userDistributeShared.getRemark()) && order.getGoodsId() == Constant.CLAUDE_CODE_GOODS_ID) {
+//							//普通推广用户首单,给上级渠道发放 claude code 积分卡
+//							try {
+//								claudeCodeService.adjUserRestNum(order.getId(), userDistributeShared.getSharedId(), 1, "新用户下单");
+//								log.info("用户{}邀请新用户{}首单,发放claude code积分卡成功", userDistributeShared.getSharedId(), order.getUserId());
+//							} catch (Exception e) {
+//								log.error("给用户{}发放claude code积分卡失败:{}", userDistributeShared.getSharedId(), e.getMessage());
+//							}
+//						}
 					} catch (DuplicateKeyException e) {
 
 					}
@@ -3046,6 +3080,23 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			return;
 		}
 
+		//codex 抵扣
+		CodexDeductRecord codexDeductRecord = codexDeductRecordMapper.selectOne(Wrappers.lambdaQuery(CodexDeductRecord.class).eq(CodexDeductRecord::getOrderId, orderDon.getId()).last("limit 1"));
+		if (codexDeductRecord != null) {
+			//codex 抵扣车票id
+			Long relationId = codexDeductRecord.getRelationId();
+			//清除codex 抵扣的车票
+			groupRelationClearService.clearTicket(groupsRelationMapper.selectById(relationId), UserTicketClearedRecord.Source.codex_deduct);
+			//是否需要清除cc 无赠送codex
+			Long ccRelationId = codexDeductRecord.getCcRelationId();
+			if (ccRelationId != null) {
+				//清除codex 抵扣的车票
+				groupRelationClearService.clearTicket(groupsRelationMapper.selectById(ccRelationId), UserTicketClearedRecord.Source.codex_deduct);
+			}
+			codexDeductRecord.setStatus(Boolean.TRUE);
+			codexDeductRecordMapper.updateById(codexDeductRecord);
+		}
+
 		if (orderDon.getIsNoLogin() != null && orderDon.getIsNoLogin()) {
 			orderDon.setStatus(OrderDon.Status.hasPayment);
 		}
@@ -3246,6 +3297,11 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			//生成所需的claude code用户信息
 			claudeCodeService.generateOrUpdateClaudeCodeUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
 		}
+		//购买codex
+		if (orderDon.getGoodsId() == Constant.CODEX_GOODS_ID) {
+			codexService.createOrUpdateCodexUserInfo(orderDon.getId(), orderDon.getRelationId(), orderDon.getUserId(), sku, orderDon.getOrderType());
+		}
+
 		//GPT代充 回调兜底
 		if (orderDon.getGoodsId() == Constant.GPT_RECHARGE_GOODS_ID) {
 			GroupsRelation relation = groupsRelationMapper.selectById(orderDon.getRelationId());
@@ -4572,6 +4628,16 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			throw BusinessRuntimeException.getInstance("不支持单独续费");
 		}
 
+		//claude code 续费升级
+//		if (goodsDon.getId() == Constant.CLAUDE_CODE_GOODS_ID) {
+//			//是否存在已购买的codex
+//			List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+//			Integer selectCount = groupsRelationMapper.selectCountByGoodsId(userIdList, Constant.CODEX_GOODS_ID, DateTime.now());
+//			if (selectCount > 0) {
+//				throw BusinessRuntimeException.getInstance("您已购买了codex套餐,如需claude可用codex抵扣升级,不可重复购买");
+//			}
+//		}
+
 		OrderDon orderDon = new OrderDon();
 		orderDon.setUserId(userId);
 		orderDon.setGoodsId(goodsDonSku.getGoodsId());
@@ -5373,4 +5439,23 @@ public class OrderDonServiceImpl extends ServiceImpl<OrderDonMapper, OrderDon> i
 			userBalanceSourceRecordMapper.deleteById(userBalanceSourceRecord.getId());
 		}
 	}
+
+	private void checkCCTrialCardNum(List<Long> userIdList) {
+		Integer payCcTy = orderDonMapper.selectCount(Wrappers.lambdaQuery(OrderDon.class).eq(OrderDon::getSkuId, Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID).in(OrderDon::getUserId, userIdList).notIn(OrderDon::getStatus, Constant.noOrderAllStatus));
+		if (payCcTy > 0) {
+			throw BusinessRuntimeException.getInstance("claude code体验天卡车票仅可体验一次");
+		}
+		if (payCcTy == 0) {
+			Number number = beanSearcher.searchCount(GroupsRelationView.class, MapUtils.builder().field(GroupsRelationView::getUserId, userIdList).op(Operator.InList).field(GroupsRelationView::getSkuId, Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID).build());
+			if (number.intValue() > 0) {
+				throw BusinessRuntimeException.getInstance("claude code体验天卡车票仅可体验一次");
+			}
+			if (number.intValue() == 0) {
+				Integer ccCount = userTicketClearedRecordMapper.selectCount(Wrappers.lambdaQuery(UserTicketClearedRecord.class).in(UserTicketClearedRecord::getUserId, userIdList).eq(UserTicketClearedRecord::getSkuId, Constant.DISTRIBUTE_CLAUDE_CODE_TRIAL_SKU_ID));
+				if (ccCount > 0) {
+					throw BusinessRuntimeException.getInstance("claude code体验天卡车票仅可体验一次");
+				}
+			}
+		}
+	}
 }

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

@@ -7,6 +7,7 @@ import com.cyksj.mapper.claudecode.OAuthTokenMapper;
 import com.cyksj.model.entity.*;
 import com.cyksj.model.manage.views.GroupsRelationView;
 import com.cyksj.service.claude.ClaudeCodeService;
+import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.mange.MirrorUserCleatService;
 import com.cyksj.service.relation.GroupRelationClearService;
 import com.ejlchina.searcher.BeanSearcher;
@@ -49,6 +50,8 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 
 	private final CodexUserMapper codexUserMapper;
 
+	private final CodexService codexService;
+
 	@Override
 	public void clearTicket(GroupsRelation relation, UserTicketClearedRecord.Source source) {
 		Long relationId = relation.getId();
@@ -155,6 +158,14 @@ public class GroupRelationClearServiceImpl implements GroupRelationClearService
 				if (source != UserTicketClearedRecord.Source.vip_deduct && groupsRelation.getGoodsId() != null && groupsRelation.getGoodsId() == Constant.NANO_GOODS_ID) {
 					nanoUserQuotaMapper.delete(Wrappers.lambdaQuery(NanoUserQuota.class).eq(NanoUserQuota::getRelationId, relationId));
 				}
+
+				//codex
+				if (groupsRelation.getGoodsId() != null && groupsRelation.getGoodsId() == Constant.CODEX_GOODS_ID) {
+					//清除套餐
+					codexService.delUserPackages(groupsRelation.getUserId());
+					//清除codex
+					codexUserMapper.delete(Wrappers.lambdaQuery(CodexUser.class).eq(CodexUser::getRelationId, relationId));
+				}
 			}
 		}
 	}

+ 35 - 9
netflix-service/src/main/java/com/cyksj/service/relation/impl/GroupRelationFrontServiceImpl.java

@@ -19,6 +19,7 @@ import com.cyksj.common.util.Jsons;
 import com.cyksj.common.util.StringUtil;
 import com.cyksj.enums.GatewayApiCode;
 import com.cyksj.mapper.*;
+import com.cyksj.mapper.codex.CodexUserRenewExpiryRecordMapper;
 import com.cyksj.mapper.manage.distribute.DistributeGeneralSkuRateMapper;
 import com.cyksj.mapper.manage.distribute.UserDistributeMapper;
 import com.cyksj.mapper.manage.distribute.UserPlatDistributeRateMapper;
@@ -47,6 +48,7 @@ import com.cyksj.service.chatgpt.ChatGptAccountService;
 import com.cyksj.service.chatgpt.ChatGptAuthService;
 import com.cyksj.service.claude.ClaudeCodeService;
 import com.cyksj.service.claude.ClaudeService;
+import com.cyksj.service.codex.CodexService;
 import com.cyksj.service.groups.GroupsFuncService;
 import com.cyksj.service.luma.LumaService;
 import com.cyksj.service.mail.CommonMailService;
@@ -213,6 +215,10 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 
 	private final NanoUserQuotaMapper nanoUserQuotaMapper;
 
+	private final CodexService codexService;
+
+	private final CodexUserRenewExpiryRecordMapper codexUserRenewExpiryRecordMapper;
+
 	private final AccountNetflixFrozenTicketReceivedRecordMapper accountNetflixFrozenTicketReceivedRecordMapper;
 
 	@Override
@@ -2040,16 +2046,10 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 				try {
 					//cc用户镜像
 					ClaudeCodeUserInfoView userInfo = claudeCodeService.getClaudeCodeUserSubsInfo(userId);
-					ticketRenewUpgradeView.setCodePoints(userInfo.getCodePoints());
-					ticketRenewUpgradeView.setCodeCreditRecovery(userInfo.getCodeCreditRecovery());
+					ticketRenewUpgradeView.setClaudeDailyLimit(userInfo.getClaudeDailyLimit());
+					ticketRenewUpgradeView.setClaudeQuota(userInfo.getClaudeQuota());
 					ticketRenewUpgradeView.setIsRenewPackage(userInfo.getRenewSkuId() != null ? true : false);
 					ticketRenewUpgradeView.setHighestUsage(userInfo.getHighestUsage());
-					CodexUserInfoView codexUserInfo = userInfo.getCodexUserInfo();
-					if (codexUserInfo != null) {
-						ticketRenewUpgradeView.setCodexPlanName(codexUserInfo.getPlanName());
-						ticketRenewUpgradeView.setOpenaiDailyLimit(codexUserInfo.getOpenaiDailyLimit());
-						ticketRenewUpgradeView.setOpenaiQuota(codexUserInfo.getOpenaiQuota());
-					}
 					Long renewSkuId = userInfo.getRenewSkuId();
 					if (renewSkuId != null) {
 						GoodsDonSkuRenewView renewSku = beanSearcher.searchFirst(GoodsDonSkuRenewView.class, MapUtils.builder().field(GoodsDonSkuRenewView::getSkuId, renewSkuId).build());
@@ -2067,6 +2067,32 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 				} catch (Exception e) {
 				}
 			}
+
+			//codex
+			if (groupsRelationView.getGoodsId() == Constant.CODEX_GOODS_ID) {
+				try {
+					//codex用户镜像
+					CodexUserInfoView userInfo = codexService.getUserSubsInfo(userId);
+					ticketRenewUpgradeView.setOpenaiDailyLimit(userInfo.getOpenaiDailyLimit());
+					ticketRenewUpgradeView.setOpenaiQuota(userInfo.getOpenaiQuota());
+					ticketRenewUpgradeView.setIsRenewPackage(userInfo.getRenewSkuId() != null ? true : false);
+					Long renewSkuId = userInfo.getRenewSkuId();
+					if (renewSkuId != null) {
+						GoodsDonSkuRenewView renewSku = beanSearcher.searchFirst(GoodsDonSkuRenewView.class, MapUtils.builder().field(GoodsDonSkuRenewView::getSkuId, renewSkuId).build());
+						Optional.ofNullable(codexUserRenewExpiryRecordMapper.selectOne(Wrappers.lambdaQuery(CodexUserRenewExpiryRecord.class)
+								.eq(CodexUserRenewExpiryRecord::getOrderId, userInfo.getRenewOrderId())
+								.last("limit 1"))).ifPresent(e -> renewSku.setOriginalOrderId(e.getOrderId()));
+						if (lang != null && !StrUtil.equals(UserPageLanguage.Language.zh_CN.name(), lang)) {
+							String languageSpecVal = backInfoTranslationsFrontService.getLanguageSpecVal(renewSku.getSkuId(), lang);
+							if (StrUtil.isNotBlank(languageSpecVal)) {
+								renewSku.setSpecVal(languageSpecVal);
+							}
+						}
+						ticketRenewUpgradeView.setRenewSku(renewSku);
+					}
+				} catch (Exception e) {
+				}
+			}
 		}
 		return ticketRenewUpgradeView;
 	}
@@ -2158,7 +2184,7 @@ public class GroupRelationFrontServiceImpl implements GroupRelationFrontService
 
 	private BigDecimal getRenewDiscount(Long goodsId, Long skuId, Boolean isMirror, Integer skuNum) {
 		//镜像或者独立规格
-		if (goodsId == Constant.CLAUDE_CODE_GOODS_ID || isMirror || (skuNum != null && skuNum == 1)) {
+		if (goodsId == Constant.CODEX_GOODS_ID || goodsId == Constant.CLAUDE_CODE_GOODS_ID || isMirror || (skuNum != null && skuNum == 1)) {
 			RenewUpgradePackage renewUpgradePackage = renewUpgradePackageMapper.selectOne(Wrappers.lambdaQuery(RenewUpgradePackage.class)
 					.eq(RenewUpgradePackage::getGoodsId, goodsId)
 					.eq(RenewUpgradePackage::getStatus, 1)

+ 4 - 30
netflix-web/src/main/java/com/cyksj/web/controller/claude/ClaudeCodeController.java

@@ -155,27 +155,13 @@ public class ClaudeCodeController {
 		List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
 		SearchResult<OrderDonView> search = beanSearcher.search(OrderDonView.class, MapUtils.flatBuilder(request.getParameterMap())
 				.field(OrderDonView::getUserId, userIdList).op(Operator.InList)
-				.field(OrderDonView::getGoodsId, Constant.CLAUDE_CODE_GOODS_ID)
 				.field(OrderDonView::getStatus, Constant.noOrderAllStatus).op(Operator.NotIn)
 				.build());
 		return GatewayResponse.SUCCESS.newBuilder().toResult(search);
 	}
 
 	/**
-	 * 用户积分明细
-	 * - `page` (int, optional): 页码,默认1
-	 * - `limit` (int, optional): 每页数量,默认20,最大100
-	 * - `type` (string, optional): 积分类型过滤
-	 */
-	@GetMapping("/get/user/points/detail")
-	public ClaudeCodePointsHistoryResp getUserPointsDetail(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer limit, String type) throws Exception {
-		long userId = StpUserUtil.getLoginIdAsLong();
-		ClaudeCodePointsHistoryResp resp = claudeCodeService.getUserPointsDetail(userId, type, page, limit);
-		return resp;
-	}
-
-	/**
-	 * 仪表盘聚合
+	 * claude 仪表盘聚合
 	 * 返回 当前订阅/余额卡片 + 今日汇总 + 今日请求列表
 	 */
 	@GetMapping("/get/dashboard")
@@ -185,18 +171,6 @@ public class ClaudeCodeController {
 		return resp;
 	}
 
-
-	/**
-	 * 余额卡片
-	 * 单独返回余额卡片数据
-	 */
-	@GetMapping("/get/balance")
-	public ClaudeCodeResp getUserBalance() throws Exception {
-		long userId = StpUserUtil.getLoginIdAsLong();
-		ClaudeCodeResp resp = claudeCodeService.getUserBalance(userId);
-		return resp;
-	}
-
 	/**
 	 * 图表 + 明细联动(每小时聚合)
 	 * - `start` (string, required): `YYYY-MM-DD HH:MM:SS`,例如 `2025-08-08 12:00:00`
@@ -227,7 +201,7 @@ public class ClaudeCodeController {
 					.in(ClaudeCodeUser::getUserId, userIdList)
 					.last("limit 1"));
 			if (user != null) {
-				Integer userCodePoints = user.getCodePoints();
+				Integer claudeDailyLimit = user.getClaudeDailyLimit();
 				GroupsRelation relation = relationMapper.selectById(user.getRelationId());
 				GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
 				Long skuId = groupsTrips.getSkuId();
@@ -242,7 +216,7 @@ public class ClaudeCodeController {
 						if (CollUtil.isNotEmpty(upgradeSkuIds)) {
 							renewSkuViews = beanSearcher.searchAll(RenewUpgradePackageFrontView.RenewSkuView.class, MapUtils.builder()
 									//高套餐
-									.field(RenewUpgradePackageFrontView.RenewSkuView::getCodePoints, userCodePoints).op(Operator.GreaterThan)
+									.field(RenewUpgradePackageFrontView.RenewSkuView::getClaudeDailyLimit, claudeDailyLimit).op(Operator.GreaterThan)
 									.field(RenewUpgradePackageFrontView.RenewSkuView::getSkuId, upgradeSkuIds).op(Operator.InList)
 									.build());
 						}
@@ -516,7 +490,7 @@ public class ClaudeCodeController {
 	/**
 	 * 用户使用每日重置
 	 */
-	@PostMapping("/use/day/reset-count")
+//	@PostMapping("/use/day/reset-count")
 	public Result<Map<String, Object>> useDayResetCount() throws Exception {
 		long userId = StpUserUtil.getLoginIdAsLong();
 		claudeCodeService.useDayResetCount(userId);

+ 150 - 6
netflix-web/src/main/java/com/cyksj/web/controller/codex/CodexController.java

@@ -1,17 +1,41 @@
 package com.cyksj.web.controller.codex;
 
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.cyksj.common.constant.Constant;
+import com.cyksj.common.util.Jsons;
 import com.cyksj.dto.Result;
 import com.cyksj.enums.GatewayResponse;
+import com.cyksj.mapper.CodexUserMapper;
+import com.cyksj.mapper.GroupsMapper;
+import com.cyksj.mapper.GroupsRelationMapper;
+import com.cyksj.model.entity.CodexUser;
+import com.cyksj.model.entity.GroupsRelation;
+import com.cyksj.model.entity.GroupsTrips;
+import com.cyksj.model.entity.OrderDon;
+import com.cyksj.model.request.ClaudeCodeApiKeysReq;
+import com.cyksj.model.request.ClaudeCodeDelReq;
+import com.cyksj.model.request.CodexUpgradePayReq;
 import com.cyksj.model.request.UpdateDailyLimitRequest;
+import com.cyksj.model.response.CodexDeductResp;
 import com.cyksj.model.response.claudecode.ClaudeCodeResp;
-import com.cyksj.model.views.CodexUserInfoView;
+import com.cyksj.model.views.*;
 import com.cyksj.service.codex.CodexService;
+import com.cyksj.service.user.UserBindRelationService;
 import com.cyksj.web.util.StpUserUtil;
+import com.ejlchina.searcher.BeanSearcher;
+import com.ejlchina.searcher.param.Operator;
+import com.ejlchina.searcher.util.MapUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
+ * server/codex
  * server/codex
  * 项目名: yhlxj11111111
  * 文件名: CodexController
@@ -25,14 +49,68 @@ import org.springframework.web.bind.annotation.*;
 public class CodexController {
     
     private final CodexService codexService;
-    
+
+    private final UserBindRelationService userBindRelationService;
+
+    private final CodexUserMapper codexUserMapper;
+
+    private final GroupsRelationMapper relationMapper;
+
+    private final GroupsMapper groupsMapper;
+
+    private final BeanSearcher beanSearcher;
+
+
     /**
-     * 获取用户Codex信息
+     * 用户创建API KEY
      */
-    @GetMapping("/info")
-    public Result<CodexUserInfoView> getCodexUserInfo() {
+    @PostMapping("/create/apiKeys")
+    public ClaudeCodeResp createApiKeys(@RequestBody @Validated ClaudeCodeApiKeysReq req) throws Exception {
         long userId = StpUserUtil.getLoginIdAsLong();
-        CodexUserInfoView codexUserInfoView = codexService.getCodexUserInfo(userId);
+        req.setUserId(userId);
+        ClaudeCodeResp resp = codexService.createApiKeys(req);
+        return resp;
+    }
+
+    /**
+     * 用户api keys列表
+     */
+    @GetMapping("/get/apiKeys")
+    public Result<List<ClaudeCodeUserApiKeysView>> getUserApiKeys() throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        List<ClaudeCodeUserApiKeysView> userApiKeys = codexService.getUserApiKeys(userId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(userApiKeys);
+    }
+
+    /**
+     * 删除api key
+     */
+    @DeleteMapping("/delete/apiKeys")
+    public Result<String> deleteApiKeysById(@RequestBody ClaudeCodeDelReq delReq) throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        delReq.setUserId(userId);
+        codexService.deleteApiKeysById(delReq);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
+
+
+    /**
+     * codex 用户是否存在订阅
+     */
+    @GetMapping("/get/user/subs")
+    public Result<Boolean> getUserSubs() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        Boolean flag = codexService.getUserSubs(userId);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(flag);
+    }
+
+    /**
+     * codex 用户订阅车票信息
+     */
+    @GetMapping("/get/user/subs/info")
+    public Result<CodexUserInfoView> getUserSubsInfo() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        CodexUserInfoView codexUserInfoView = codexService.getUserSubsInfo(userId);
         return GatewayResponse.SUCCESS.newBuilder().toResult(codexUserInfoView);
     }
 
@@ -59,6 +137,7 @@ public class CodexController {
     }
     
     /**
+     * codex 仪表盘
      * 用户OpenAI控制台数据 (Java系统专用接口)
      */
     @GetMapping("/openai/users/dashboard")
@@ -77,4 +156,69 @@ public class CodexController {
         ClaudeCodeResp analytics = codexService.getJavaUserAnalytics(userId, start, end, page, limit, order);
         return analytics;
     }
+
+    /**
+     * 抵扣车票金额
+     */
+    @GetMapping("/get/deduct/money")
+    public Result<CodexDeductResp> getDeductMoney() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        CodexDeductResp codexDeductResp = codexService.getDeductMoney(userId, null);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(codexDeductResp);
+    }
+
+    /**
+     * codex 抵扣升级 预下单
+     */
+    @PostMapping("/deduct/submit/order")
+    public Result<OrderDon> deductSubmitOrder(@RequestBody CodexUpgradePayReq payReq) throws Exception {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        payReq.setUserId(userId);
+        OrderDon orderDon = codexService.deductSubmitOrder(payReq);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(orderDon);
+    }
+
+    /**
+     * codex续费升级 可选套餐
+     */
+    @GetMapping("/get/packages")
+    public Result<ClaudeCodePackageView> getClaudeCodePackages(String dsCode) {
+        Long userId = StpUserUtil.getUserIdAfterLogin();
+        ClaudeCodePackageView claudeCodePackageView = new ClaudeCodePackageView();
+        if (userId != null) {
+            List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+            CodexUser user = codexUserMapper.selectOne(Wrappers.lambdaQuery(CodexUser.class)
+                    .in(CodexUser::getUserId, userIdList)
+                    .last("limit 1"));
+            if (user != null) {
+                Integer openaiDailyLimit = user.getOpenaiDailyLimit();
+                GroupsRelation relation = relationMapper.selectById(user.getRelationId());
+                GroupsTrips groupsTrips = groupsMapper.selectById(relation.getGroupsId());
+                Long skuId = groupsTrips.getSkuId();
+                List<RenewUpgradePackageFrontView> renewUpgradePackageFrontViews = beanSearcher.searchAll(RenewUpgradePackageFrontView.class, MapUtils.builder().field(RenewUpgradePackageFrontView::getSkuId, skuId).build());
+                renewUpgradePackageFrontViews.forEach(renew -> {
+                    try {
+                        List<Long> upgradeSkuIds = Jsons.parseList(renew.getUpgradeSkuIds(), Long.class);
+                        if (user.getRenewSkuId() != null) {
+                            upgradeSkuIds.remove(user.getRenewSkuId());
+                        }
+                        List<RenewUpgradePackageFrontView.RenewSkuView> renewSkuViews = new ArrayList<>();
+                        if (CollUtil.isNotEmpty(upgradeSkuIds)) {
+                            renewSkuViews = beanSearcher.searchAll(RenewUpgradePackageFrontView.RenewSkuView.class, MapUtils.builder()
+                                    //高套餐
+                                    .field(RenewUpgradePackageFrontView.RenewSkuView::getOpenaiDailyLimit, openaiDailyLimit).op(Operator.GreaterThan)
+                                    .field(RenewUpgradePackageFrontView.RenewSkuView::getSkuId, upgradeSkuIds).op(Operator.InList)
+                                    .build());
+                        }
+                        renew.setRenewSkus(renewSkuViews);
+                    } catch (Exception e) {
+                    }
+                });
+                claudeCodePackageView.setUpgrades(renewUpgradePackageFrontViews);
+            }
+        }
+        List<GoodsDonSkuView> goodsDonSkuViews = beanSearcher.searchAll(GoodsDonSkuView.class, MapUtils.builder().field(GoodsDonSkuView::getGoodsId, Constant.CODEX_GOODS_ID).field(GoodsDonSkuView::getGoodsDonStatus, Boolean.TRUE).field(GoodsDonSkuView::getGoodsDonSkuStatus, Boolean.TRUE).field(GoodsDonSkuView::getUserOwns).op(Operator.IsNull).build());
+        claudeCodePackageView.setSkus(goodsDonSkuViews);
+        return GatewayResponse.SUCCESS.newBuilder().toResult(claudeCodePackageView);
+    }
 }

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

@@ -1478,4 +1478,20 @@ public class GroupRelationController {
                 .build());
         return GatewayResponse.SUCCESS.newBuilder().toResult(search);
     }
+
+    /**
+     * claude code+codex 套餐情况
+     */
+    @GetMapping("/get/cc/codex/info")
+    public Result<CcCodexInfoView> getCcCodexInfo() {
+        long userId = StpUserUtil.getLoginIdAsLong();
+        List<Long> userIdList = userBindRelationService.getRelationUserIdList(userId, null);
+        ClaudeCodeUser claudeCodeUser = beanSearcher.searchFirst(ClaudeCodeUser.class, MapUtils.builder().field(ClaudeCodeUser::getUserId, userIdList).op(Operator.InList).build());
+        CodexUser codexUser = beanSearcher.searchFirst(CodexUser.class, MapUtils.builder().field(CodexUser::getUserId, userIdList).op(Operator.InList).build());
+        //cc codex
+        CcCodexInfoView ccCodexInfoView = new CcCodexInfoView();
+        ccCodexInfoView.setClaudeCodeUser(claudeCodeUser);
+        ccCodexInfoView.setCodexUser(codexUser);
+        return GatewayResponse.SUCCESS.newBuilder().toResult();
+    }
 }