RevenueCat 的 transaction_id 是否跨环境唯一?
建议把 environment 纳入唯一键
如果 sandbox 和 production 的交易保存在同一张表中,建议使用:
(store, environment, transaction_id)
RevenueCat 的 webhook 字段文档没有明确保证 transaction_id 在 sandbox 和 production 之间唯一,因此数据库设计不应依赖 store + transaction_id 的跨环境唯一性。
文档未作保证,也不意味着两个环境一定会出现相同 ID。加入 environment,可以避免依赖这个尚未确认的前提。
为什么不能直接认定跨环境唯一?
RevenueCat 将 transaction_id 定义为来自商店的交易标识,将 environment 定义为交易所属的商店环境。字段说明没有给出适用于所有商店、所有环境的统一唯一性约定。RevenueCat webhook 字段文档
仅凭“交易标识”这个名称,无法认定它在同一商店的测试与正式环境之间不会重复。某个商店的具体规则,也不能直接推广到 RevenueCat 支持的所有商店。
没有可靠依据时,同样不应断言 Apple 或 Google Play 的 sandbox 一定会重置或复用交易 ID。
数据库应该怎样设计?
唯一键的范围取决于数据如何存储:
- 两个环境共用一张交易表时,使用
(store, environment, transaction_id)。 - 两个环境使用独立数据库或独立表时,存储位置已经区分了环境,表内可以使用
(store, transaction_id)。仍建议保留environment,便于排查问题。 - 同一张表接收多个应用或项目的数据时,应在联合键中加入业务所需的应用或项目范围,避免假设交易 ID 在不同应用、项目之间也唯一。
RevenueCat webhook 中的环境值为大写的 SANDBOX 和 PRODUCTION,写入数据库时应保持一致。字段取值说明
PostgreSQL 示例
以下示例假设这张表只保存一个应用的商店交易:
CREATE TABLE store_transactions (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
store TEXT NOT NULL,
environment TEXT NOT NULL
CHECK (environment IN ('SANDBOX', 'PRODUCTION')),
transaction_id TEXT NOT NULL,
original_transaction_id TEXT,
UNIQUE (store, environment, transaction_id)
);
transaction_id 应使用 TEXT 保存,不要转换为整数。RevenueCat 将它定义为字符串,存储时也不应依赖某个商店当前的 ID 格式。
这个联合约束允许相同的 store 和 transaction_id 分别出现在两个环境中,同时阻止同一环境内重复创建交易记录。这是数据库层面的环境隔离措施,不能据此认定 RevenueCat 对所有商店都有统一的唯一性保证。
接收 webhook 时还要注意什么?
交易唯一键与事件去重键要分开
transaction_id 标识交易,event.id 标识 webhook 事件。RevenueCat 明确说明,重试投递会复用同一个事件 id。公共字段说明
处理顺序可以是:
- 使用
event.id检查事件是否已经成功处理。 - 对带有交易字段的事件,用联合键查找或创建交易记录。
- 按事件类型更新业务状态,并记录事件处理结果。
同一笔交易可能收到不同类型的通知,因此不能因为交易记录已经存在,就直接丢弃后续事件。
不要用 original_transaction_id 替代单笔交易 ID
original_transaction_id 用于关联订阅的原始交易。如果需要保存每次续订记录,应保留 transaction_id,否则可能将同一订阅下的多笔交易合并。交易字段说明
缺少字段时不要默认归入 production
不同 webhook 事件包含的交易字段可能不同。创建交易记录前,应检查事件是否提供了有效的 store、environment 和 transaction_id。缺少字段时,应按事件类型单独处理,不要将未知环境自动写成 PRODUCTION。事件与字段适用范围
备注:内容仅供参考。
