{"record":{"id":"e7328dbd1d028207","repo":"toeverything/AFFiNE","slug":"subscription-not-exists","errorCode":"subscription_not_exists","errorMessage":"You didn't subscribe to the ${plan} plan.","messagePattern":"You didn't subscribe to the (.+?) plan\\.","errorType":"exception","errorClass":"SubscriptionNotExists","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/plugins/payment/service.ts","lineNumber":168,"sourceCode":"        recurring,\n        variant: variant ?? null,\n      },\n      params,\n      args\n    );\n  }\n\n  async cancelSubscription(\n    identity: z.infer<typeof SubscriptionIdentity>,\n    idempotencyKey?: string\n  ): Promise<Subscription> {\n    this.assertSubscriptionIdentity(identity);\n\n    const manager = this.select(identity.plan);\n    const subscription = await manager.getActiveSubscription(identity);\n\n    if (!subscription) {\n      throw new SubscriptionNotExists({ plan: identity.plan });\n    }\n\n    // IAP read-only: RevenueCat-managed subscriptions cannot be modified on web\n    if (subscription.provider === 'revenuecat') {\n      throw new ManagedByAppStoreOrPlay();\n    }\n\n    if (!subscription.stripeSubscriptionId) {\n      throw new CantUpdateOnetimePaymentSubscription(\n        'Onetime payment subscription cannot be canceled.'\n      );\n    }\n\n    if (subscription.canceledAt) {\n      throw new SubscriptionHasBeenCanceled();\n    }\n\n    // update the subscription in db optimistically","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/payment/service.ts#L150-L186","documentation":"Thrown by SubscriptionService.cancelSubscription when manager.getActiveSubscription(identity) returns null, i.e. there is no active subscription for the given identity (user or workspace) and plan. Cancel is only valid against an existing active subscription.","triggerScenarios":"Calling cancelSubscription({ userId, plan: 'pro' }) when the user is on Free or already fully expired; canceling a team plan with a workspaceId that has no subscription; plan/recurring mismatch with what was actually purchased.","commonSituations":"Stale UI showing a Cancel button after the subscription lapsed or was removed by webhook; user canceled earlier and a sync race re-showed active state; wrong workspace selected in a multi-workspace client.","solutions":["Re-query the current subscription list (e.g. currentUser { subscriptions } or the workspace equivalent) and only offer Cancel for plans that are actually active.","Verify the identity matches the subscription: same userId (or workspaceId) and plan that getActiveSubscription searches for.","Treat this code in async flows as 'already gone': skip retry and refresh local state from the server."],"exampleFix":"// before\nsetBusy(true);\nawait svc.cancelSubscription({ userId, plan });\n\n// after\nconst subs = await listSubscriptions();\nconst active = subs.find((s) => s.plan === plan && s.status === 'active');\nif (!active) return refresh();\nawait svc.cancelSubscription({ userId, plan });","handlingStrategy":"validation","validationCode":"const subs = await listSubscriptions();\nconst active = subs.some((s) => s.plan === identity.plan && s.status === 'active');\nif (!active) throw new Error('No active subscription to cancel');\nawait svc.cancelSubscription(identity, idempotencyKey);","typeGuard":"interface SubLike { plan: string; status: string; }\nfunction hasActivePlan(subs: SubLike[], plan: string): boolean {\n  return subs.some((s) => s.plan === plan && s.status === 'active');\n}","tryCatchPattern":"catch (e) { if (gqlErrorCode(e) === 'subscription_not_exists') { await refreshSubscriptions(); notify('You are not subscribed to this plan.'); return; } throw e; }","preventionTips":["Derive cancel/resume buttons from a fresh server subscription query, not cache.","Send an Idempotency-Key header with cancel/resume calls.","Treat subscription_not_exists as authoritative state, never retry it."],"tags":["payment","subscription","cancel","not-found","state-mismatch"],"backgroundTag":"subscription-not-found","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"}