{"record":{"id":"8b22b67b5e81c57c","repo":"toeverything/AFFiNE","slug":"invalid-subscription-parameters","errorCode":"invalid_subscription_parameters","errorMessage":"Invalid subscription parameters provided.","messagePattern":"Invalid subscription parameters provided\\.","errorType":"exception","errorClass":"InvalidSubscriptionParameters","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/plugins/payment/resolver.ts","lineNumber":639,"sourceCode":"  async requestApplySubscription(\n    @CurrentUser() user: CurrentUser,\n    @Args('transactionId') transactionId: string\n  ): Promise<Subscription[]> {\n    if (!user) {\n      throw new AuthenticationRequired();\n    }\n\n    const existsSubscription = await this.db.providerSubscription.findFirst({\n      where: {\n        provider: Provider.revenuecat,\n        externalRef: transactionId,\n      },\n    });\n\n    // subscription with the transactionId already exists\n    if (existsSubscription) {\n      if (existsSubscription.targetId !== user.id) {\n        throw new InvalidSubscriptionParameters();\n      } else {\n        return this.currentUserSubscriptions(user.id);\n      }\n    }\n\n    let current: Subscription[] = [];\n\n    try {\n      await this.rcHandler.syncAppUserWithExternalRef(user.id, transactionId);\n      current = await this.currentUserSubscriptions(user.id);\n      // ignore errors\n    } catch {}\n\n    return current;\n  }\n\n  @Throttle('strict')\n  @Mutation(() => [SubscriptionType], {","sourceCodeStart":621,"sourceCodeEnd":657,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/payment/resolver.ts#L621-L657","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Sign back into the original account that claimed the transaction, or complete a new purchase under the current account.","In the client, treat this code as 'transaction already used': stop retrying and show an explanatory message instead of re-sending.","Cache which transactionIds were already applied and skip re-submitting them on restore flows."],"exampleFix":"// before\nawait gql.requestApplySubscription({ transactionId }); // retried in a loop\n\n// after\ntry {\n  await gql.requestApplySubscription({ transactionId });\n} catch (e) {\n  if (gqlErrorCode(e) === 'invalid_subscription_parameters') {\n    showNotice('This purchase is linked to another account.');\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const subs = await gql.requestApplySubscription({ transactionId });\n} catch (e) {\n  if (gqlErrorCode(e) === 'invalid_subscription_parameters') {\n    notify('This purchase is already linked to another account.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Track applied transaction ids per account client-side and never re-submit them.","On account switch, clear pending-apply state for the previous account.","Do not auto-retry this code; it is a permanent conflict, not a transient error."],"tags":["payment","iap","revenuecat","duplicate-transaction","conflict"],"backgroundTag":"resource-already-exists","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}