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
- 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.
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
- 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.
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
- managed_by_app_store_or_play
- workspace_id_required_to_update_team_subscription
- access_denied
- unsupported_subscription_plan
- action_forbidden
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/8b22b67b5e81c57c.
Report an issue: GitHub.