toeverything/AFFiNE · error · InvalidSubscriptionParameters

invalid_subscription_parameters

invalid_subscription_parameters

Error message

Invalid subscription parameters provided.

What it means

Thrown by requestApplySubscription when a providerSubscription row with provider 'revenuecat' and externalRef equal to the given transactionId already exists but belongs to a different targetId. The transaction was already credited to another user account, so re-applying it to the current user is rejected as a parameter conflict (not a retryable failure).

Source

Thrown at packages/backend/server/src/plugins/payment/resolver.ts:639

  async requestApplySubscription(
    @CurrentUser() user: CurrentUser,
    @Args('transactionId') transactionId: string
  ): Promise<Subscription[]> {
    if (!user) {
      throw new AuthenticationRequired();
    }

    const existsSubscription = await this.db.providerSubscription.findFirst({
      where: {
        provider: Provider.revenuecat,
        externalRef: transactionId,
      },
    });

    // subscription with the transactionId already exists
    if (existsSubscription) {
      if (existsSubscription.targetId !== user.id) {
        throw new InvalidSubscriptionParameters();
      } else {
        return this.currentUserSubscriptions(user.id);
      }
    }

    let current: Subscription[] = [];

    try {
      await this.rcHandler.syncAppUserWithExternalRef(user.id, transactionId);
      current = await this.currentUserSubscriptions(user.id);
      // ignore errors
    } catch {}

    return current;
  }

  @Throttle('strict')
  @Mutation(() => [SubscriptionType], {

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Sign back into the original account that claimed the transaction, or complete a new purchase under the current account.
  2. In the client, treat this code as 'transaction already used': stop retrying and show an explanatory message instead of re-sending.
  3. Cache which transactionIds were already applied and skip re-submitting them on restore flows.

Example fix

// before
await gql.requestApplySubscription({ transactionId }); // retried in a loop

// after
try {
  await gql.requestApplySubscription({ transactionId });
} catch (e) {
  if (gqlErrorCode(e) === 'invalid_subscription_parameters') {
    showNotice('This purchase is linked to another account.');
    return;
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const subs = await gql.requestApplySubscription({ transactionId });
} catch (e) {
  if (gqlErrorCode(e) === 'invalid_subscription_parameters') {
    notify('This purchase is already linked to another account.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling requestApplySubscription with a transactionId that another account has already claimed; restoring a purchase on a second account; replaying an old transaction id after switching accounts.

Common situations: User bought on device with account A, later signs into account B and the client calls apply again with the same store transaction; test environments reusing sandbox transaction ids across test users; duplicate restore-purchase buttons firing.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/8b22b67b5e81c57c. Report an issue: GitHub.