overleaf/overleaf · error · ManuallyCollectedError

This subscription is being collected manually

Error message

This subscription is being collected manually

What it means

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.

Source

Thrown at services/web/app/src/Features/Subscription/SubscriptionGroupHandler.mjs:93

async function ensureFlexibleLicensingEnabled(plan) {
  if (!plan?.canUseFlexibleLicensing) {
    throw new Error('The group plan does not support flexible licensing')
  }
}

async function ensureSubscriptionIsActive(subscription) {
  if (SubscriptionHelper.getPaidSubscriptionState(subscription) !== 'active') {
    throw new InactiveError('The subscription is not active', {
      subscriptionId: subscription._id.toString(),
    })
  }
}

async function ensureSubscriptionCollectionMethodIsNotManual(
  paymentProviderSubscription
) {
  if (paymentProviderSubscription.isCollectionMethodManual) {
    throw new ManuallyCollectedError(
      'This subscription is being collected manually',
      {
        subscription_id: paymentProviderSubscription.id,
      }
    )
  }
}

async function ensureSubscriptionHasNoPastDueInvoice(subscription) {
  const [paymentRecord] = await Modules.promises.hooks.fire(
    'getPaymentFromRecord',
    subscription
  )

  if (paymentRecord.account.hasPastDueInvoice) {
    throw new HasPastDueInvoiceError(
      'This subscription has a past due invoice',
      {

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Change the subscription's collection method to automatic in the payment provider dashboard (or via provider API: subscription.collection_method = 'charge_automatically') before retrying.
  2. Handle manually-collected subscriptions through the invoicing/enterprise workflow instead of the automated group-management endpoints.
  3. If this is a transient sync issue, refresh the subscription data from the payment provider and confirm isCollectionMethodManual is actually false before calling.

Example fix

// before
await ensureSubscriptionCollectionMethodIsNotManual(stripeSubscription)
// after
const fresh = await getSubscriptionFromProvider(subscription.id)
if (fresh.isCollectionMethodManual) {
  await setCollectionMethodAutomatic(subscription.id) // provider API call
}
await ensureSubscriptionCollectionMethodIsNotManual(fresh)
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling
const sub = await getSubscriptionFromProvider(subscriptionId)
if (sub.isCollectionMethodManual) {
  throw new SkipError('manually-collected; use invoicing workflow')
}

Type guard

const isAutomaticallyCollected = sub => sub.isCollectionMethodManual === false

Try / catch

try {
  await ensureSubscriptionCollectionMethodIsNotManual(sub)
} catch (e) {
  if (e.name === 'ManuallyCollectedError') {
    logger.warn({ subscriptionId: e.info?.subscription_id }, 'manual collection')
    return routeToInvoicingWorkflow(sub)
  }
  throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of overleaf/overleaf@28ad3b03b7 (2026-09-03). Data as JSON: /api/errors/0448ad73c34ef1cc. Report an issue: GitHub.