团队 SaaS 订阅对账中,Figma 席位计费是常见的归因难题:成员刚加入,下一张发票就多了一项席位费用。内部系统如果只记了一条 member_added,就无法判断这笔钱来自复用已购空席位,还是审批时新增了付费席位。本文给出一套 Figma 席位审批、库存观测、发票与付款卡交易分层对账的数据模型和测试用例。
说明:以下是团队自建订阅台账的设计示例,不代表 Figma 或 MPChat 的现网接口。
一、为什么成员数推不出费用
Figma 的规则是建模起点:同类型席位有空余时,批准请求可以直接分配现有席位;没有空余时,才可能需要增加席位。所以"成员 +1"和"付费席位 +1"是两个独立事实,成员、席位库存和发票不能合成一条"加人收费"事件。
二、先说清数据从哪来
数据可以来自管理员核对、账单导出,或团队实际获准使用的集成能力。不要假定每个方案都提供同样的事件接口。模型里把"内部发生了什么"和"在 Figma 后台观测到了什么"分开记录,后者统一用 Observation 表示,并保留证据引用。
三、对象模型
|----------------------|---------------------------------------------------------------|----------------|
| 对象 | 关键字段 | 记录什么 |
| SeatRequest | request_ref、member_ref、requested_type、requested_at | 谁申请了什么席位 |
| ApprovalDecision | decision_ref、request_ref、approver_ref、decision、decided_at | 内部谁在何时批准 |
| SeatObservation | member_ref、seat_type、observed_at、evidence_ref | 后台实际看到此人拿到的席位 |
| InventoryObservation | seat_type、purchased_count、assigned_count、observed_at | 某类席位买了多少、分配了多少 |
| InvoiceLine | invoice_ref、line_ref、period、description、amount、currency | 商户发票上的具体条目 |
| CardTransaction | card_txn_ref、merchant、amount、currency、card_status、observed_at | 卡侧交易及其状态 |
发票和卡交易各自保留来源。审批已完成、商户发票还没出来时,系统可以如实显示"等待账单",不会提前生成一笔已付款记录。
四、审批与归因流程
TypeScript
on_seat_request(request):
record request once
check intended seat type and available inventory
send to the team's configured approval path
record the decision
after_provider_review:
observe actual member seat and seat totals
if an existing seat was assigned:
mark allocation = EXISTING_SEAT
else if evidence supports an added paid seat:
mark allocation = ADDED_SEAT
else:
mark allocation = UNDETERMINED
after_invoice:
link relevant invoice lines for review
keep card reconciliation separate
审批前的"检查库存"是内部预算控制,实际分配结果以 Figma 后台为准。
两个管理员在相近时间批准请求时,前后两张库存快照未必能把新增席位准确归到某个人。这种情况保留 UNDETERMINED,交给管理员核对后台记录,不要按"时间最近"硬分配费用。
幂等方面,重复导入同一审批决定时,可以用"团队标识 + 请求引用 + 决定版本"做幂等键。缺少可靠的来源编号,就标记待人工核对,不要拿姓名和时间拼出一个看似唯一的事件。
五、三条状态线分开走
|-----|--------------------------------------------------------------|------------|
| 状态线 | 典型状态 | 判定依据 |
| 席位 | REQUESTED、APPROVED、EXISTING_SEAT / ADDED_SEAT / UNDETERMINED | Figma 后台观测 |
| 发票 | PENDING_INVOICE、INVOICED | 商户发票 |
| 卡交易 | Authorized、Settled | MPChat 卡记录 |
这三条线经常不同步:席位已经分配,新增费用要到后续发票才出现;发票已经生成,卡侧交易可能还停在 Authorized。MPChat 显示 Settled,只说明卡片侧这笔交易已结算,从中读不出 Figma 的 member_ref。
六、对账关系可能是一对多或多对一
Professional 年付方案在订阅期内新增席位,可能产生额外的月度订阅账单,其他方案的结算节奏不同。一张发票可能同时包含多类费用,卡侧也可能只看到一次合并付款。
amount + merchant + date 适合用来找候选记录,不适合自动判定某位成员造成了扣款。
七、上线前的六个测试用例
|---|--------------------|------------------------------|
| # | 场景 | 预期结果 |
| 1 | 同类型空席位可用,成员增加 | 购买席位总数不变,标记 EXISTING_SEAT |
| 2 | 无空席位,批准请求 | 购买席位数增加,发票稍后出现,标记 ADDED_SEAT |
| 3 | 两个请求相近时间获批 | 库存快照不足以归因时保持 UNDETERMINED |
| 4 | 成员从 Collab 改为 Full | 旧席位重新可用,新席位按实际观测结果核对 |
| 5 | 成员离开团队 | 席位可再次分配,既有账期费用不被系统自动冲回 |
| 6 | 发票含多条费用,卡侧只有一笔交易 | 匹配结果保持可复核,不拆分卡交易 |
八、小结
这个模型要能回答五个问题:谁申请了什么,审批了什么,Figma 实际增加了什么,商户开了什么账,卡侧结算到了哪一步。五个问题各有独立的数据来源,出了问题就能逐层定位,用不着从一条付款通知倒推整条权限链。