credits.js
1.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import * as v from 'valibot'
/**
* Credits 经济契约 —— 扣点解锁 + 充值入账 + 流水条目的共享数据形态。
*
* 与 S3 的 `account.js`(role/subscription/credits 余额字段)严格分家:本文件只描述
* 「解锁请求/响应、充值下单请求、单条流水」四种形态,余额本身仍由 AccountT.credits 承载。
* 纯定义、零 IO,前后端共用。
*/
/**
* 解锁请求:解锁某剧(可选某集)。episode_id 省略 = 整剧级解锁。
* @typedef {{ drama_id: string, episode_id?: string }} UnlockReqT
*/
export const UnlockReq = v.object({
drama_id: v.pipe(v.string(), v.minLength(1)),
episode_id: v.optional(v.string()),
})
/**
* 解锁响应:是否已解锁 + 解锁后剩余点数 + 可选原因。
* reason='already' → 此前已解锁,幂等返回,未再扣点。
* reason='insufficient' → 余额不足,未扣点、未解锁。
* 无 reason → 本次成功扣点并解锁。
* @typedef {{ unlocked: boolean, remaining_credits: number, reason?: 'insufficient' | 'already' }} UnlockResT
*/
export const UnlockRes = v.object({
unlocked: v.boolean(),
remaining_credits: v.number(),
reason: v.optional(v.picklist(['insufficient', 'already'])),
})
/**
* 充值下单请求:以套餐 id 选点数包,server 据 pack_id 映射点数/价格发起真 Stripe checkout。
* @typedef {{ pack_id: string }} TopupCheckoutReqT
*/
export const TopupCheckoutReq = v.object({
pack_id: v.pipe(v.string(), v.minLength(1)),
})
/**
* 单条点数流水:账本对外展示形态(正入账 / 负扣减)。createdAt 为 ISO 8601 UTC 字符串。
* @typedef {{ id: string, delta: number, reason: 'topup' | 'unlock' | 'grant', created_at: string }} LedgerEntryT
*/
export const LedgerEntry = v.object({
id: v.string(),
delta: v.number(),
reason: v.picklist(['topup', 'unlock', 'grant']),
created_at: v.string(),
})