we-promise/sure · error · Merchant::Merger::UnauthorizedMerchantError
#{label} does not belong to this family
Error message
#{label} does not belong to this family What it means
Merchant::Merger#validate_merchant_belongs_to_family! raises UnauthorizedMerchantError when a target or source merchant's id is not in the union of family.merchants and family.assigned_merchants ids. The check runs in the constructor, so instantiating Merchant::Merger with a foreign merchant fails before any merge work. This is a tenant-isolation guard: a family can only merge its own (or assigned) merchants.
Source
Thrown at app/models/merchant/merger.rb:24
def initialize(family:, target_merchant:, source_merchants:)
@family = family
@target_merchant = target_merchant
@merged_count = 0
validate_merchant_belongs_to_family!(target_merchant, "Target merchant")
sources = Array(source_merchants)
sources.each { |m| validate_merchant_belongs_to_family!(m, "Source merchant '#{m.name}'") }
@source_merchants = sources.reject { |m| m.id == target_merchant.id }
end
private
def validate_merchant_belongs_to_family!(merchant, label)
return if family_merchant_ids.include?(merchant.id)
raise UnauthorizedMerchantError, "#{label} does not belong to this family"
end
def family_merchant_ids
@family_merchant_ids ||= begin
family_ids = family.merchants.pluck(:id)
assigned_ids = family.assigned_merchants.pluck(:id)
(family_ids + assigned_ids).uniq
end
end
public
def merge!
return false if source_merchants.empty?
Merchant.transaction do
source_merchants.each do |source|
scope = family.transactions.where(merchant_id: source.id)View on GitHub (pinned to e69894adb9)
Solutions
- Scope lookups to the family: family.merchants.find(params[:id]) / family.assigned_merchants, so foreign ids raise RecordNotFound instead.
- Ensure the merchant is actually assigned to the family before merging (create the FamilyMerchant/assignment link).
- If the merchant is legitimately shared, verify family.assigned_merchants includes it or reassign ownership first.
- Rescue Merchant::Merger::UnauthorizedMerchantError in the controller and return 403/404 rather than 500.
Example fix
# before merger = Merchant::Merger.new(family: family, target_merchant: Merchant.find(params[:target_id]), source_merchants: Merchant.where(id: params[:source_ids])) # after target = family.merchants.find(params[:target_id]) sources = family.merchants.where(id: params[:source_ids]) merger = Merchant::Merger.new(family: family, target_merchant: target, source_merchants: sources)
Defensive patterns
Strategy: validation
Validate before calling
allowed = (family.merchants.pluck(:id) + family.assigned_merchants.pluck(:id)).uniq allowed.include?(merchant.id)
Try / catch
begin Merchant::Merger.new(family: family, target_merchant: target, source_merchants: sources).merge! rescue Merchant::Merger::UnauthorizedMerchantError head :forbidden end
Prevention
- Always scope merchant lookups through the current family (family.merchants.find), never Merchant.find on raw params.
- Never trust merchant ids from the client; verify membership server-side.
- Return 403/404 on this error so cross-tenant probing learns nothing.
When it happens
Trigger: Passing a merchant from another family (e.g., fetched by id from params without scoping); stale object from a different tenant in a multi-family request; a ProviderMerchant that is used by the family but never assigned via family.assigned_merchants.
Common situations: IDOR-style param tampering (merchant_id from another account); copied seed data where merchant-family links were not recreated; frontend passing a merchant from a cached list after the family switched.
Related errors
- Your account is not authorized to use the external assistant
- #{label} does not belong to this family
- request_failed
- access_forbidden
- access_forbidden
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/6d72e214fe337630.
Report an issue: GitHub.