{"record":{"id":"37ea168b87e3b174","repo":"overleaf/overleaf","slug":"conflict-between-billing-info-and-account-fields","errorCode":null,"errorMessage":"Conflict between billing info and account fields, but no subscription found to determine collection method","messagePattern":"Conflict between billing info and account fields, but no subscription found to determine collection method","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.mjs","lineNumber":1324,"sourceCode":"  let name,\n    address,\n    companyName,\n    vatNumber,\n    collectionMethod,\n    billingInfoForPaymentMethod\n\n  if (!hasConflict) {\n    name = billingName ?? accountName\n    address = billingAddress ?? accountAddress\n    companyName = billingCompany ?? accountCompany\n    vatNumber = billingVat ?? accountVat\n    collectionMethod = null\n    billingInfoForPaymentMethod = null\n  } else {\n    collectionMethod = await fetchCollectionMethod()\n\n    if (!collectionMethod) {\n      throw new Error(\n        'Conflict between billing info and account fields, but no subscription found to determine collection method'\n      )\n    }\n\n    if (collectionMethod === 'automatic') {\n      name = billingName ?? accountName\n      address = billingAddress ?? accountAddress\n      companyName = billingCompany ?? accountCompany\n      vatNumber = billingVat ?? accountVat\n      billingInfoForPaymentMethod = null\n    } else if (collectionMethod === 'manual') {\n      name = accountName ?? billingName\n      address = accountAddress ?? billingAddress\n      companyName = accountCompany ?? billingCompany\n      vatNumber = accountVat ?? billingVat\n      billingInfoForPaymentMethod = account.billingInfo\n    } else {\n      throw new Error(`Unexpected collectionMethod: ${collectionMethod}`)","sourceCodeStart":1306,"sourceCodeEnd":1342,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/web/scripts/helpers/migrate_recurly_customers_to_stripe.helpers.mjs#L1306-L1342","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Reconcile the conflicting fields in Recurly (make billingInfo match the account record, or vice versa) so hasConflict is false, then re-run","Extend fetchCollectionMethod to also look up canceled/paused/past_due subscriptions, or default to a documented fallback collection method when no subscription exists","Skip these customers and export them to a report for manual resolution instead of throwing","If the customer has no subscription, migrate with a sensible default (e.g. account fields) rather than requiring a collection method"],"exampleFix":"// before\ncollectionMethod = await fetchCollectionMethod()\nif (!collectionMethod) throw new Error('Conflict between billing info and account fields, but no subscription found to determine collection method')\n// after\ncollectionMethod = await fetchCollectionMethod()\nif (!collectionMethod) {\n  logger.warn(`No subscription for conflicting customer; defaulting to account fields`)\n  collectionMethod = 'manual' // documented fallback: account fields win\n}","handlingStrategy":"validation","validationCode":"function conflictsResolvable(customer) {\n  return !customer.hasFieldConflict || customer.subscription != null\n}\nif (!conflictsResolvable(customerRecord)) {\n  manualReviewQueue.push(customerRecord.code)\n}","typeGuard":"function canDetermineCollectionMethod(customer) {\n  return !customer.hasFieldConflict ||\n    (customer.subscriptions?.data?.length > 0 &&\n      ['automatic', 'manual'].includes(customer.subscriptions.data[0].collection_method))\n}","tryCatchPattern":"try {\n  result = await resolveBillingDetails(account, fetchCollectionMethod)\n} catch (err) {\n  if (err.message.includes('no subscription found to determine collection method')) {\n    logger.warn({ accountId: account.code }, 'conflict without subscription; manual review')\n    addToManualReviewQueue(account.code)\n    return\n  }\n  throw err\n}","preventionTips":["Run a pre-migration audit comparing billingInfo vs account fields and flag conflicts","Broaden subscription lookups to include canceled/paused/past_due states before concluding none exists","Keep Recurly account fields and billing info in sync via webhooks to avoid drift","Exclude subscription-less customers from collection-method-dependent logic or define an explicit default"],"tags":["data-conflict","recurly","migration","missing-subscription"],"backgroundTag":"unresolvable-data-conflict","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}