- 八篇:总览与共性 / 登录与会话 / 全员总览 / key 管理 / 支付记录 / 成员管理 / 审计日志 / 成员视图 - 关键定案:R12 携带形态(来源徽章 + 展开行,两种空值场景区分)、纯用量模式(部门实例——无支付记录的完整子集,共性 §3.8)、响应式只做布局响应、组件能力需求清单(选型验收标准)、审计日志保留操作者列(前向兼容多管理员)、Key 与成员管理分页 + 「+ 新建成员」缝合流程 - 审阅期修订全记录见需求清单 §9「交互设计八篇定稿」
7.6 KiB
7.6 KiB
payments 表字段设计
数据模型第三张表:支付记录——每笔支付事件(订阅购买 / 充值)一行。平台只存事实(支付金额、换得额度、来源参数),「支付金额与额度之间的比率」是查询时的派生视图,不存储、不做任何分摊计算。本文记录字段清单、语义与决策理由。
| 项目 | 内容 |
|---|---|
| 状态 | 定稿(2026-10-01) |
| 关联 | docs/design/usage_logs表字段设计.md 3.6(subscription_id 直连与本表)· docs/design/qpu_history表字段设计.md(quota → 额度美元的换算时间线)· docs/references/NewAPI-接口范围.md §2.⑨(other.subscription_* 字段依据) |
| 范围 | 列结构 + 语义 + 写入时机;方言映射归数据访问层设计 |
| 字段数 | 9 = 1 主键 + 8 人工录入(支付 4 · 期间 2 · 订阅 2) |
1. 表定位
- 一行 = 一笔支付事件(订阅购买 / 充值)——同一模型兼容两种形态(
kind列区分) - 本表是「只有作者知道」的信息的唯一落点:支付金额、额度、订阅 ID——上游没有免 auth 接口可查,全靠作者手工抄录
- 币种无关:
paid_amount是支付金额数值,系统不预设币种——币种是使用者之间的约定;「金额 ↔ 额度」的比率同样是派生视图(查询时相除即得) - 不做分摊计算:平台不把支付金额按用量比例分摊到日志或成员——分账是社交层问题;本表与日志的交汇只发生在事实关联(
subscription_id直连)与参数展示(订阅信息对使用者完全公开透明) - 数据规模:年个位数到十位数行——无需索引设计
2. 字段清单
2.1 主键与支付字段(5 个)
| 列 | 类型 | 值来源 | 说明 |
|---|---|---|---|
id |
bigserial PK | 自增 | |
kind |
text | 人工选择 | subscription(订阅)/ topup(充值) |
paid_at |
timestamptz | 人工填写 | 支付时间 |
paid_amount |
numeric | 人工必填 | 支付金额(币种无关——数值本身,币种由使用者约定) |
remark |
text | 人工填写 | 备注 |
2.2 期间与订阅字段(4 个)
| 列 | 类型 | 值来源 | 说明 |
|---|---|---|---|
period_start |
timestamptz? | 人工录入(钱包页面) | 仅订阅:该笔覆盖的用量期间起点;左闭 |
period_end |
timestamptz? | 人工录入(钱包页面) | 期间终点;左开;未结束可空 |
subscription_id |
bigint? | 人工录入(钱包页面「订阅 #N」) | 上游订阅实例 ID;与日志 subscription_id 直连(事实关联,不推算) |
quota_granted |
bigint? | 人工录入(钱包页面「总额度」/ 充值记录) | 本笔换得的额度——单位钉死为原始 quota 整数(如 $25 额度 = 12,500,000;见 3.6) |
3. 决策记录(含理由)
3.1 登记 = 手工抄录(来源只有两处)
支付记录全部由作者手工录入,来源仅两处:支付凭证(kind / paid_at / paid_amount)与钱包页面(订阅 ID / 期间 / 总额度)。订阅关键信息只有作者可见(没有免 auth 接口可查)——系统不做回填与推断,日志观测仅用于核对提示(见 §4);必填 / 可空的校验细节归技术稿。
3.2 只存事实,不存派生数
paid_amount与quota_granted是不可替代的事实;「金额 ↔ 额度」的比率是派生数——查询时相除即得,事后核对永远可用;只存比率会丢失核对依据- 日志同样不加任何金额 / 比率列(
usage_logs零分摊字段):把支付金额按用量分摊到日志行的任何计算都不做——分摊口径是社交层问题,账本上只有客观事实 - 关联维度(
billing_source/subscription_id)已在日志侧提为独立列(见usage_logs表字段设计.md3.6),统计按事实关联
3.3 为什么额度也手工录入(不采用日志感知)
日志逐条自带 subscription_total(实测),但回填它需要「订阅 ID → 该 ID 日志 total」的关联逻辑;钱包页面已直接给出总额度——手工抄录一步到位。系统对支付记录零回填、零推断,观测仅用于核对(见 §4)。
3.4 币种无关(不预设人民币)
paid_amount 是纯数值。作者记账用什么货币是他的事,展示层的货币符号是界面约定——数据层与币种彻底解耦,更换支付渠道 / 币种时零 schema 变化。
3.5 命名与类型
- 表名
payments(支付记录):订阅与充值同构,purchases(购买)只覆盖前者,payments是两者的上位词 - 金额列
numeric(金额不用浮点);时间列统一timestamptz - 期间左闭右开(
[period_start, period_end))——与全项目期间惯例一致 - 订阅实例 ID 与日志同名
subscription_id——同一体系、同一含义(直连即用此列)
3.6 quota_granted 的单位:原始 quota,不是美元
- 存真值,不存换算快照:上游订阅套餐的额度本体是 quota 整数,「$25」只是界面显示形态(quota ÷
quota_per_unit,随quota_display_type而变)——本列存本体 - 域一致性:日志的
quota同为原始整数——SUM(usage_logs.quota)与quota_granted同域直接可比,零换算;若存美元则每次比较都要过 ÷QPU,而 QPU 是时间线变量(qpu_history),凭空引入取值时刻问题 - 类型精确:quota 恒为整数;美元额度可能有小数,
bigint装不下 - 录入体验归交互层:钱包页面显示美元形态——管理面表单支持输入美元、按当时 QPU 换算落库(或展示两态),换算逻辑不进数据模型
- 美元形态随时可派生:
quota_granted ÷ QPU(展示层) - 与 qpu_history 无概念重复:quota 是量,QPU 是换算率——记录量不等于记录单位定义(记「¥170」不涵盖「1 美元 = 6.8 人民币」)。真值域(quota)不携带任何换算,换算率只在一处(
qpu_history时间线),美元永远现算现用;反过来若直接存美元,每个值都是某次换算的快照、各自隐含一个换算时刻——那才是真正的冗余,且上游改 QPU 后与日志统计失去同域可比性
4. 写入时机与维护
| 时机 | 动作 |
|---|---|
| 人工登记(管理通道) | INSERT:录入支付信息(时间 / 金额)与订阅信息(ID / 期间 / 总额度,抄自钱包页面)→ 确认 |
| 系统观测(仅核对) | 日志观测到的订阅 ID / subscription_total 仅作核对提示(如「观测到 #N 未对应任何支付记录」——即「未关联」分组的健康信号),不参与回填 |
| 编辑 / 删除 | 数据为人工登记,改错即修 |
5. 展示层边界(界面设计的输入)
- 订阅与支付参数对使用者完全公开透明(访问主体模型裁决):期间、额度、支付金额、订阅 ID——成员视图与管理面同源可见;不存在「管理面独占的金额信息」
- 「按订阅维度统计」(某笔订阅的消耗占比等)数据天然支持——按
subscription_id直连聚合即可 - 成员视角的日志行携带所属订阅/充值信息的具体形态,交互设计期定(见
docs/design/需求清单.mdR12)
6. 待定事项(关联后续环节)
| 事项 | 归属 |
|---|---|
| 核对提示(观测到的订阅 ID ↔ 已录入记录比对)的交互 | 交互设计(待立) |
充值(topup)的 quota_granted 抄录来源(充值记录页)核实 |
实现期真实数据核查 |