{"record":{"id":"451266076803d943","repo":"koala73/worldmonitor","slug":"business-not-active","errorCode":"BUSINESS_NOT_ACTIVE","errorMessage":"BUSINESS_NOT_ACTIVE","messagePattern":"BUSINESS_NOT_ACTIVE","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/payments/businessSeats.ts","lineNumber":490,"sourceCode":"    }\n    if (grant.inviteeEmail !== inviteeEmail) {\n      throw new ConvexError({ kind: \"INVITE_EMAIL_MISMATCH\" });\n    }\n    if (!sameDomain(grant.inviteeEmail, inviteeEmail)) {\n      throw new ConvexError({ kind: \"INVITE_EMAIL_MISMATCH\" });\n    }\n    if (!isCorporateDomain(inviteeEmail)) {\n      throw new ConvexError({ kind: \"INVITEE_DOMAIN_NOT_CORPORATE\" });\n    }\n\n    const businessSub = await ctx.db\n      .query(\"subscriptions\")\n      .withIndex(\"by_dodoSubscriptionId\", (q) =>\n        q.eq(\"dodoSubscriptionId\", grant.businessSubscriptionId),\n      )\n      .unique();\n    if (!businessSub || !isBusinessPlan(businessSub.planKey) || !isCoveringAt(businessSub, now)) {\n      throw new ConvexError({ kind: \"BUSINESS_NOT_ACTIVE\" });\n    }\n\n    await ctx.db.patch(args.grantId, {\n      status: \"accepted\",\n      inviteeUserId: userId,\n      acceptedAt: now,\n    });\n\n    await ctx.runMutation(\n      internal.payments.subscriptionHelpers.recomputeEntitlementForUser,\n      { userId, eventTimestamp: now },\n    );\n\n    return { ok: true as const };\n  },\n});\n","sourceCodeStart":472,"sourceCodeEnd":507,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/payments/businessSeats.ts#L472-L507","documentation":"Thrown by acceptBusinessInvite when the Business subscription backing the seat grant is unusable: the subscriptions row looked up by grant.businessSubscriptionId is missing, its planKey is not \"api_business\", or isCoveringAt(businessSub, now) is false (subscription expired or no longer covering the current instant). The seat cannot be attached because there is no active Business plan to draw it from.","triggerScenarios":"The owner's api_business subscription was cancelled, failed renewal, or expired between invite creation and acceptance; the grant references a dodoSubscriptionId that no longer maps to any subscriptions row (e.g. after a plan migration created a new Dodo subscription).","commonSituations":"Trial expired before the invitee clicked the link; owner's card failed during the invite window; subscription replaced/migrated leaving stale grants pointing at the old dodoSubscriptionId.","solutions":["Have the owner check Manage Billing: renew or restart the api_business subscription, then send a fresh invite (an expired grant stays dead)","Owner verifies the grant's businessSubscriptionId still matches the current active subscription's dodoSubscriptionId; if stale, revoke and re-create the invite","Catch err.data.kind === \"BUSINESS_NOT_ACTIVE\" and tell the invitee to contact their admin rather than retrying","If the subscription is active and the id matches, check isCoveringAt inputs (current_period end / status) for a webhook that never landed"],"exampleFix":"// before — invitee retries a dead grant forever\nawait accept(ctx, { grantId, token });\n// after — degrade gracefully for the invitee\ntry {\n  await accept(ctx, { grantId, token });\n} catch (e) {\n  if (kindOf(e) === \"BUSINESS_NOT_ACTIVE\") return showContactAdmin();\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isBusinessNotActive(e: unknown): boolean {\n  return e instanceof ConvexError && (e.data as { kind?: string } | undefined)?.kind === \"BUSINESS_NOT_ACTIVE\";\n}","tryCatchPattern":"try {\n  await acceptInvite({ grantId, token });\n} catch (e) {\n  if (isBusinessNotActive(e)) return showContactAdmin(\"The Business plan is no longer active\");\n  throw e;\n}","preventionTips":["Check invite age client-side and warn before accepting long-lived invites (the sub may have lapsed)","Owners: keep grants short-lived so a lapsed subscription invalidates them quickly","Monitor webhook-driven subscription cancellations and proactively revoke outstanding invites"],"tags":["convex","billing","seats","subscription","expired"],"backgroundTag":"subscription-not-active","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}