overleaf/overleaf · error

Conflict between billing info and account fields, but no sub

Error message

Conflict between billing info and account fields, but no subscription found to determine collection method

What it means

When migrating a customer, the code compares billingInfo fields (name, address, company, VAT) against account-level fields. If they conflict, the correct value depends on the subscription's collection method (automatic = prefer billing info; manual = prefer account fields). This error is thrown when there is a conflict but fetchCollectionMethod() returns null — no subscription exists to disambiguate which source wins, so the migration cannot proceed safely.

Source

Thrown at services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.mjs:1324

  let name,
    address,
    companyName,
    vatNumber,
    collectionMethod,
    billingInfoForPaymentMethod

  if (!hasConflict) {
    name = billingName ?? accountName
    address = billingAddress ?? accountAddress
    companyName = billingCompany ?? accountCompany
    vatNumber = billingVat ?? accountVat
    collectionMethod = null
    billingInfoForPaymentMethod = null
  } else {
    collectionMethod = await fetchCollectionMethod()

    if (!collectionMethod) {
      throw new Error(
        'Conflict between billing info and account fields, but no subscription found to determine collection method'
      )
    }

    if (collectionMethod === 'automatic') {
      name = billingName ?? accountName
      address = billingAddress ?? accountAddress
      companyName = billingCompany ?? accountCompany
      vatNumber = billingVat ?? accountVat
      billingInfoForPaymentMethod = null
    } else if (collectionMethod === 'manual') {
      name = accountName ?? billingName
      address = accountAddress ?? billingAddress
      companyName = accountCompany ?? billingCompany
      vatNumber = accountVat ?? billingVat
      billingInfoForPaymentMethod = account.billingInfo
    } else {
      throw new Error(`Unexpected collectionMethod: ${collectionMethod}`)

View on GitHub (pinned to 28ad3b03b7)

Solutions

  1. Reconcile the conflicting fields in Recurly (make billingInfo match the account record, or vice versa) so hasConflict is false, then re-run
  2. Extend fetchCollectionMethod to also look up canceled/paused/past_due subscriptions, or default to a documented fallback collection method when no subscription exists
  3. Skip these customers and export them to a report for manual resolution instead of throwing
  4. If the customer has no subscription, migrate with a sensible default (e.g. account fields) rather than requiring a collection method

Example fix

// before
collectionMethod = await fetchCollectionMethod()
if (!collectionMethod) throw new Error('Conflict between billing info and account fields, but no subscription found to determine collection method')
// after
collectionMethod = await fetchCollectionMethod()
if (!collectionMethod) {
  logger.warn(`No subscription for conflicting customer; defaulting to account fields`)
  collectionMethod = 'manual' // documented fallback: account fields win
}
Defensive patterns

Strategy: validation

Validate before calling

function conflictsResolvable(customer) {
  return !customer.hasFieldConflict || customer.subscription != null
}
if (!conflictsResolvable(customerRecord)) {
  manualReviewQueue.push(customerRecord.code)
}

Type guard

function canDetermineCollectionMethod(customer) {
  return !customer.hasFieldConflict ||
    (customer.subscriptions?.data?.length > 0 &&
      ['automatic', 'manual'].includes(customer.subscriptions.data[0].collection_method))
}

Try / catch

try {
  result = await resolveBillingDetails(account, fetchCollectionMethod)
} catch (err) {
  if (err.message.includes('no subscription found to determine collection method')) {
    logger.warn({ accountId: account.code }, 'conflict without subscription; manual review')
    addToManualReviewQueue(account.code)
    return
  }
  throw err
}

Prevention

When it happens

Trigger: The customer has at least one conflict (nameConflict || addressConflict || companyConflict || vatConflict is true) AND the customer has no subscription in Recurly (or the subscription lookup fails), so await fetchCollectionMethod() resolves null and the throw executes.

Common situations: Canceled/expired customers whose subscriptions were deleted in Recurly but whose account and billing data still disagree; trial or future-dated customers with no active subscription yet; a subscription fetch filter (e.g. only 'active' state) that misses the customer's paused or past-due subscription; data entry errors where billing info was updated without updating the account record.

Related errors


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