diff --git a/apps/web/src/api/points/types.ts b/apps/web/src/api/points/types.ts index 577c26ef1..db0208a59 100644 --- a/apps/web/src/api/points/types.ts +++ b/apps/web/src/api/points/types.ts @@ -1,76 +1,134 @@ /** - * 积分 & 会员系统 API 类型定义(v2 两档会员制) - * 接口契约对齐后端设计文档 membership-points-design-v2.md + * 积分系统类型定义 + * 对齐后端 staging 实测最终契约(2026-09-16) + * + * Base path: /api/v1/ + * 会员/订阅相关类型请从 @/api/subscription/types 引入,本文件仅保留积分核心类型。 */ -/** 会员类型 */ -export type MemberType = "free" | "monthly" | "quarterly" | "yearly" - -/** 积分流水类型 */ -export type PointsTxType = "earn" | "spend" | "refund" - -/** 积分来源/消耗场景 */ +/* ================================================================ + * 场景键 + * ================================================================ */ +/** + * 积分消耗场景键(9 个) + * - ai_script 已拆分为 douyin_extract / ai_rewrite / ai_title,前端禁止再传 ai_script + */ export type PointsSource = - | "recharge" // 充值 - | "task_reward" // 任务奖励 | "ai_voice" // AI 配音 + | "ai_video" // AI 视频生成 | "ai_digital_human" // AI 数字人 - | "ai_video" // 智能混剪 | "voice_clone_train" // 声音克隆训练 | "voice_clone_synth" // 声音克隆合成 - | "douyin_extract" // 抖音链接提取 - | "ai_rewrite" // AI 改写文案 + | "douyin_extract" // 抖音提取文案 + | "ai_rewrite" // AI 文案改写 | "ai_title" // AI 标题生成 | "ai_cover" // AI 封面生成 - | "subscription_bonus" // 会员赠送 - | "admin_adjust" // 管理员调整 - | "refund" // 失败退还 -/** 会员 & 积分余额响应 */ +/** 非消耗场景 source 前缀(用于流水 source 字段) */ +export type PointsSourceExtra = + PointsSource | `refund:${string}` | "recharge" | "sign_up" | "bind_phone" | "gift" | "admin" + +/* ================================================================ + * 通用 + * ================================================================ */ +/** ISO 8601 时间字符串 */ +export type ISODate = string + +/* ================================================================ + * 积分余额(GET /points/balance) + * ================================================================ */ export interface PointsBalance { /** 当前可用积分 */ balance: number - /** 累计获得 */ + /** 累计获得积分 */ total_earned: number - /** 累计消耗 */ + /** 累计消耗积分 */ total_spent: number - /** 是否付费会员(free 用户为 false) */ + /** 是否为付费会员 */ is_member: boolean - /** 会员类型:monthly / quarterly / yearly;free 用户为 null */ - member_type: Extract | null - /** 会员到期时间 ISO 字符串 */ - member_expires_at: string | null - /** 今日免费混剪已用次数 */ - free_clips_used?: number - /** 今日免费混剪额度上限 */ - free_clips_limit?: number - /** 今日免费混剪剩余 */ - free_clips_remaining?: number + /** 会员类型(monthly/quarterly/yearly,非会员 null)。推荐使用 /subscription/current 的 plan_id+billing_cycle 做判断 */ + member_type: "monthly" | "quarterly" | "yearly" | null + /** 会员到期时间 */ + member_expires_at: ISODate | null } -/** 积分流水记录 */ +/* ================================================================ + * 积分规则(GET /points/rules) + * ================================================================ */ +export interface PointsRule { + scene_key: PointsSource + /** 场景中文名 */ + name: string + /** 基准消耗积分(points_per_use 改名) */ + base_points: number + /** 单位描述,如「次」「分钟」「个」 */ + unit: string + /** 超过30秒后每30秒额外积分(视频/语音类) */ + extra_per_30s?: number + /** 场景说明(后端已补回) */ + description?: string +} + +export interface PointsRulesResponse { + rules: PointsRule[] + /** 非会员消耗倍率(如 1.15) */ + free_user_multiplier: number +} + +/* ================================================================ + * 充值包(GET /points/packages) + * ================================================================ */ +export interface PointsPackage { + /** 包编码(id 改名) */ + code: string + name: string + points: number + /** 原价,单位分 */ + price_cents: number + /** 每积分单价(元),展示用 */ + unit_price: number +} + +export interface PointsPackagesResponse { + packages: PointsPackage[] + /** 当前用户折扣(会员折扣或活动折扣),null 表示无折扣 */ + user_discount: number | null +} + +/** + * 充值包前端展示辅助:折后价(分) + * 后端废弃 4 档 discounted_price_for_*,前端按 price_cents * (user_discount ?? 1) 计算。 + */ +export function getDiscountPriceCents(pkg: PointsPackage, userDiscount: number | null): number { + return Math.round(pkg.price_cents * (userDiscount ?? 1)) +} + +/* ================================================================ + * 积分流水(GET /points/transactions) + * ================================================================ */ +export type PointsTxType = "add" | "deduct" + export interface PointsTransaction { - id: string - /** earn / spend / refund */ + id: number + /** 流水类型:add=获得/退款,deduct=消耗 */ type: PointsTxType - /** 来源场景 */ - source: PointsSource - /** 场景中文名称 */ - source_name: string - /** 变动数量(正数) */ + /** + * 消耗/获得来源: + * - 消耗场景直接用 PointsSource 值 + * - 充值/退款/赠送使用 recharge / refund: / sign_up / bind_phone / gift / admin + */ + source: string + /** 变动数量(绝对值,正负由 type 决定) */ amount: number - /** 带符号的变动数(收入+,支出-) */ - signed_amount: number /** 变动后余额 */ balance_after: number - /** 备注描述 */ + /** 中文描述 */ description: string - /** 关联业务 ID */ - ref_id: string | null - created_at: string + /** 关联订单/任务 ID,空字符串 "" 表示无关联(不是 null) */ + ref_id: string + created_at: ISODate } -/** 积分流水分页响应 */ export interface PointsTransactionsResponse { items: PointsTransaction[] total: number @@ -78,140 +136,92 @@ export interface PointsTransactionsResponse { page_size: number } -/** 积分流水查询参数 */ -export interface PointsTransactionsParams { - page?: number - page_size?: number - type?: PointsTxType - source?: PointsSource - start_date?: string - end_date?: string -} - -/** 积分包 */ -export interface PointsPackage { - id: "starter_pack" | "basic_pack" | "pro_pack" | string - /** 中文名称 */ - name: string - /** 积分数量 */ - points: number - /** 原价(分) */ - price: number - /** 各会员类型折扣价(分) */ - discounted_price_for_free: number - discounted_price_for_monthly: number - discounted_price_for_quarterly: number - discounted_price_for_yearly: number -} - -/** 积分包列表响应 */ -export interface PointsPackagesResponse { - packages: PointsPackage[] - /** 当前用户会员类型,用于前端计算折后价 */ - user_member_type: MemberType - /** 积分单价(元/积分,按会员价计) */ - unit_price_yuan: number -} - -/** 创建充值订单请求 */ -export interface PointsRechargeRequest { +/* ================================================================ + * 创建充值订单(POST /points/recharge) + * ================================================================ */ +export interface CreateRechargeOrderRequest { + /** 充值包 code(字段名保留 package_id 与后端一致) */ package_id: string - payment_method?: "wechat_pay" | "alipay" } -/** 订单状态 */ -export type OrderStatus = "pending" | "paid" | "failed" | "refunded" | "expired" - -/** 充值订单响应 */ -export interface PointsOrder { +export interface CreateRechargeOrderResponse { id: string - package_id: string - package_name: string + order_type: string + product_code: string + /** 订单金额(分) */ + amount_cents: number + /** 充值积分数量 */ points_amount: number - price_cents: number - original_price_cents: number - discount: number - currency: "CNY" - status: OrderStatus - payment_method: string | null - payment_id: string | null - paid_at: string | null - expire_at: string | null - created_at: string - /** 微信/支付宝支付参数(mock 阶段前端自行处理) */ - pay_params?: Record + status: string + /** + * 支付参数(支付通道未接入时返回空对象 {},前端可透传) + */ + pay_params: Record + /** 订单过期时间 */ + expire_at: ISODate + created_at: ISODate } -/** 订阅套餐(月/季/年) */ -export interface SubscriptionPlan { - id: "monthly" | "quarterly" | "yearly" - name: string - price_cents: number - price_yuan: number - per_month_yuan: number - savings_percent?: number - recommended?: boolean - billing_label: string -} - -/** 当前订阅详情 */ -export interface SubscriptionCurrent { - is_member: boolean - member_type: Extract | null - member_type_name: string - status: "active" | "expired" | "cancelled" | "none" - current_period_start: string | null - current_period_end: string | null - auto_renew: boolean - /** 订阅会员对应的积分折扣 */ - points_discount: number -} - -/** 开通/续费订阅请求 */ -export interface SubscribeRequest { - member_type: "monthly" | "quarterly" | "yearly" - payment_method?: "wechat_pay" | "alipay" -} - -/** 积分消耗规则 */ -export interface PointsRule { - scene_key: PointsSource - scene_name: string - /** 每次消耗基础积分(会员价) */ - points_per_use: number - /** 计量单位:条/分钟/次/张 */ - unit: string - /** 额外每 30s 加积分(ai_video 用) */ - extra_per_30s?: number - /** 说明文案 */ - description?: string -} - -export interface PointsRulesResponse { - rules: PointsRule[] - /** 免费用户消耗倍率 */ - free_user_multiplier: number - note: string -} - -/** 消费前余额检查请求 */ +/* ================================================================ + * 积分预检查(POST /points/check) + * ================================================================ */ export interface PointsCheckRequest { scene_key: PointsSource - /** 单位数量(时长/条数),默认 1 */ - units?: number + /** 数量(units 改名) */ + quantity: number + /** 预计时长(分钟),可选 */ + duration_minutes?: number } -/** 消费前余额检查响应 */ export interface PointsCheckResponse { + /** 是否可以执行 */ allowed: boolean + /** 需要消耗积分 */ required_points: number + /** 当前余额 */ current_balance: number + /** 扣除后剩余 */ remaining_after: number - /** 是否走免费额度(混剪场景) */ + /** 是否走免费额度 */ is_free_quota: boolean - /** 拒绝原因代码 */ - code?: "INSUFFICIENT_POINTS" | "FREE_QUOTA_EXCEEDED" | "SCENE_NOT_FOUND" - message?: string - /** 充值页跳转 URL */ - recharge_url?: string +} + +/* ================================================================ + * 每日使用情况(GET /usage/daily,新接口) + * ================================================================ */ +export interface DailyUsage { + /** 今日已用免费次数 */ + free_clips_used: number + /** 每日免费次数上限 */ + free_clips_limit: number + /** 今日剩余免费次数 */ + free_clips_remaining: number + /** 额度重置时间 */ + reset_at: ISODate +} + +/* ================================================================ + * 会员聚合信息(GET /points/subscription/membership,新接口) + * ================================================================ */ +export interface MembershipResponse { + is_member: boolean + /** 会员类型(monthly/quarterly/yearly,非会员 null) */ + member_type: "monthly" | "quarterly" | "yearly" | null + member_expires_at: ISODate | null + /** 当前积分余额(冗余,可与 balance 互校) */ + points_balance: number + /** 最大分辨率,如 "720p" / "1080p" / "4k" */ + max_resolution: string +} + +/* ================================================================ + * 错误响应(统一格式 {error:{code,message}}) + * ================================================================ */ +export interface ApiError { + error: { + code: number + message: string + /** 部分场景会返回,如 unknown scene_key */ + valid_scenes?: PointsSource[] + } }