PAYATHON 2026

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。公共字段说明

处理顺序可以是:

  1. 使用 event.id 检查事件是否已经成功处理。
  2. 对带有交易字段的事件,用联合键查找或创建交易记录。
  3. 按事件类型更新业务状态,并记录事件处理结果。

同一笔交易可能收到不同类型的通知,因此不能因为交易记录已经存在,就直接丢弃后续事件。

不要用 original_transaction_id 替代单笔交易 ID

original_transaction_id 用于关联订阅的原始交易。如果需要保存每次续订记录,应保留 transaction_id,否则可能将同一订阅下的多笔交易合并。交易字段说明

缺少字段时不要默认归入 production

不同 webhook 事件包含的交易字段可能不同。创建交易记录前,应检查事件是否提供了有效的 store、environment 和 transaction_id。缺少字段时,应按事件类型单独处理,不要将未知环境自动写成 PRODUCTION。事件与字段适用范围

备注:内容仅供参考。