{"record":{"id":"0448ad73c34ef1cc","repo":"overleaf/overleaf","slug":"this-subscription-is-being-collected-manually","errorCode":null,"errorMessage":"This subscription is being collected manually","messagePattern":"This subscription is being collected manually","errorType":"exception","errorClass":"ManuallyCollectedError","httpStatus":null,"severity":"error","filePath":"services/web/app/src/Features/Subscription/SubscriptionGroupHandler.mjs","lineNumber":93,"sourceCode":"async function ensureFlexibleLicensingEnabled(plan) {\n  if (!plan?.canUseFlexibleLicensing) {\n    throw new Error('The group plan does not support flexible licensing')\n  }\n}\n\nasync function ensureSubscriptionIsActive(subscription) {\n  if (SubscriptionHelper.getPaidSubscriptionState(subscription) !== 'active') {\n    throw new InactiveError('The subscription is not active', {\n      subscriptionId: subscription._id.toString(),\n    })\n  }\n}\n\nasync function ensureSubscriptionCollectionMethodIsNotManual(\n  paymentProviderSubscription\n) {\n  if (paymentProviderSubscription.isCollectionMethodManual) {\n    throw new ManuallyCollectedError(\n      'This subscription is being collected manually',\n      {\n        subscription_id: paymentProviderSubscription.id,\n      }\n    )\n  }\n}\n\nasync function ensureSubscriptionHasNoPastDueInvoice(subscription) {\n  const [paymentRecord] = await Modules.promises.hooks.fire(\n    'getPaymentFromRecord',\n    subscription\n  )\n\n  if (paymentRecord.account.hasPastDueInvoice) {\n    throw new HasPastDueInvoiceError(\n      'This subscription has a past due invoice',\n      {","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.mjs#L75-L111","documentation":"ManuallyCollectedError is thrown by ensureSubscriptionCollectionMethodIsNotManual when a payment-provider subscription has its collection method set to manual, meaning invoices are collected outside the automated billing flow. The app refuses to operate on such subscriptions through the normal automated path. It carries the payment provider's subscription id as metadata.","triggerScenarios":"Any code path that calls ensureSubscriptionCollectionMethodIsNotManual with a paymentProviderSubscription where paymentProviderSubscription.isCollectionMethodManual is true — e.g. attempting to change plan, add seats, or process automated billing for a manually-collected (invoiced) subscription.","commonSituations":"Enterprise/invoiced subscriptions created with manual collection in the payment provider (e.g. Stripe collection_method=charge_automatically vs send_invoice); admins trying to apply automated changes to an invoiced team subscription; migration scripts syncing subscriptions where collection method drifted.","solutions":["Change the subscription's collection method to automatic in the payment provider dashboard (or via provider API: subscription.collection_method = 'charge_automatically') before retrying.","Handle manually-collected subscriptions through the invoicing/enterprise workflow instead of the automated group-management endpoints.","If this is a transient sync issue, refresh the subscription data from the payment provider and confirm isCollectionMethodManual is actually false before calling."],"exampleFix":"// before\nawait ensureSubscriptionCollectionMethodIsNotManual(stripeSubscription)\n// after\nconst fresh = await getSubscriptionFromProvider(subscription.id)\nif (fresh.isCollectionMethodManual) {\n  await setCollectionMethodAutomatic(subscription.id) // provider API call\n}\nawait ensureSubscriptionCollectionMethodIsNotManual(fresh)","handlingStrategy":"try-catch","validationCode":"// before calling\nconst sub = await getSubscriptionFromProvider(subscriptionId)\nif (sub.isCollectionMethodManual) {\n  throw new SkipError('manually-collected; use invoicing workflow')\n}","typeGuard":"const isAutomaticallyCollected = sub => sub.isCollectionMethodManual === false","tryCatchPattern":"try {\n  await ensureSubscriptionCollectionMethodIsNotManual(sub)\n} catch (e) {\n  if (e.name === 'ManuallyCollectedError') {\n    logger.warn({ subscriptionId: e.info?.subscription_id }, 'manual collection')\n    return routeToInvoicingWorkflow(sub)\n  }\n  throw e\n}","preventionTips":["Check collection_method right after fetching the subscription and branch before mutating calls.","Keep manually-collected (invoiced) subscriptions in a separate ops workflow, not the self-serve API.","Alert on subscriptions that flip collection method unexpectedly in provider webhooks."],"tags":["subscription","billing","payment-provider","manual-collection"],"backgroundTag":"subscription-manual-collection-method","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}