Class: Edi::Wayfair::CatalogItemMediaSender

Inherits:
BaseEdiService show all
Includes:
CatalogApiTransport
Defined in:
app/services/edi/wayfair/catalog_item_media_sender.rb

Overview

Pushes product imagery to Wayfair via the Product Catalog Update API's
updateCatalogItemsMedia mutation, using public image URLs (our ImageKit
endpoint) — so we control the lead image and gallery instead of inheriting
whatever Wayfair scraped. Media updates are global (one call covers every
market the product sells in), batch up to 500, and support validateOnly.

API: https://developer.wayfair.io/posts/catalog-product-update (Step 2C).
Wire format verified in sandbox 2026-06-28 (returns requestId; input is
UpdateCatalogItemsMediaInput { supplierId, catalogItemsToUpdate:
[CatalogItemMediaInput { supplierPartNumber, mediaUrl, mediaType, leadImageOverride }],
validateOnly }).

Solves the borrowed-lead-image + "only one image" problems the Wayfair listing
audit surfaced (see ListingIssues::WayfairAdapter).

Examples:

sender = Edi::Wayfair::CatalogItemMediaSender.new(Edi::Wayfair::Orchestrator.build(:wayfair_us))
result = sender.process(catalog_item:, validate_only: true) # dry-run server-side

Defined Under Namespace

Classes: MediaResult

Constant Summary collapse

CATALOG_UPDATE_API_URL =

Product Catalog Update API endpoints (same host as the update sender).

'https://api.wayfair.io/v1/product-catalog-api/graphql'
SANDBOX_CATALOG_UPDATE_API_URL =

Sandbox endpoint.

'https://api.wayfair.io/sandbox/v1/product-catalog-api/graphql'
UPDATE_MEDIA_MUTATION =

GraphQL mutation: async, returns a requestId to poll via
Edi::Wayfair::CatalogItemUpdateSender#check_status.

<<~GRAPHQL.squish
  mutation UpdateCatalogItemsMedia($input: UpdateCatalogItemsMediaInput!) {
    updateCatalogEntitiesMutations {
      updateCatalogItemsMedia(input: $input) {
        requestId
      }
    }
  }
GRAPHQL
WAY_LEAD_IMAGE_TYPE =

Image profile types we publish to Wayfair, in priority order: the main shot
leads, then the numbered gallery images.

We prefer the Wayfair-specific set (WAY_MAIN + WAY_I01…) when the item has any
WAY_* profiles; otherwise we default to the website set (WYS_MAIN + WYS_I01…).
So a Wayfair image classification, when present, overrides — and when absent,
Wayfair simply inherits the WarmlyYours.com imagery.

'WAY_MAIN'
'WAY_I'
LEAD_IMAGE_TYPE =

Website lead image type, used when no WAY_* profiles exist.

'WYS_MAIN'
'WYS_I'

Constants included from CatalogApiTransport

Edi::Wayfair::CatalogApiTransport::CATALOG_API_BASE, Edi::Wayfair::CatalogApiTransport::CATALOG_AUTH_URL

Constants included from RequestIdentifiable

RequestIdentifiable::REQUEST_ID_HEADERS

Constants included from AddressAbbreviator

AddressAbbreviator::MAX_LENGTH

Instance Attribute Summary

Attributes inherited from BaseEdiService

#orchestrator

Attributes inherited from BaseService

#options

Instance Method Summary collapse

Methods inherited from BaseEdiService

#amazon_feed_product_type, #duplicate_po_already_notified?, #initialize, #mark_duplicate_po_as_notified, #onboard_ordered_catalog_items, #report_order_creation_issues, #safe_process_edi_communication_log

Methods included from RequestIdentifiable

#partner_request_id

Methods included from AddressAbbreviator

#abbreviate_street, #collect_street_originals, #record_address_abbreviation_notes

Methods inherited from BaseService

#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #tagged_logger

Constructor Details

This class inherits a constructor from Edi::BaseEdiService

Instance Method Details

#media_for(catalog_item) ⇒ Array<Hash>

The ordered public image URLs we'd publish for an item — lead first.

Parameters:

Returns:

  • (Array<Hash>)

    [{ url: String, lead: Boolean }]



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'app/services/edi/wayfair/catalog_item_media_sender.rb', line 96

def media_for(catalog_item)
  profiles = catalog_item.store_item&.item&.image_profiles.to_a
  # Prefer the Wayfair-specific set; default to the website set when absent.
  lead_type, gallery_prefix = if profiles.any? { |p| p.image_type.to_s.start_with?('WAY_') }
                                [WAY_LEAD_IMAGE_TYPE, WAY_GALLERY_IMAGE_PREFIX]
                              else
                                [LEAD_IMAGE_TYPE, GALLERY_IMAGE_PREFIX]
                              end
  lead = profiles.find { |p| p.image_type == lead_type }
  gallery = profiles.select { |p| p.image_type.to_s.start_with?(gallery_prefix) }
  [lead, *gallery].compact
                  .map { |profile| profile.image_url.to_s }
                  .compact_blank
                  .uniq
                  .map.with_index { |url, index| { url:, lead: index.zero? } }
end

#process(catalog_item:, media: nil, validate_only: false, dry_run: false) ⇒ MediaResult

Push imagery for a catalog item.

Parameters:

  • catalog_item (CatalogItem)
  • media (Array<Hash>, nil) (defaults to: nil)

    explicit media list ({ url:, lead: }); when
    nil it is derived from the item's website image profiles.

  • validate_only (Boolean) (defaults to: false)

    server-side validation without committing

  • dry_run (Boolean) (defaults to: false)

    build the payload only; no request

Returns:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'app/services/edi/wayfair/catalog_item_media_sender.rb', line 76

def process(catalog_item:, media: nil, validate_only: false, dry_run: false)
  supplier_part_number = supplier_part_number_for(catalog_item)
  media ||= media_for(catalog_item)

  return MediaResult.new(success: false, errors: ['No supplier part number for item'], media: []) if supplier_part_number.blank?
  return MediaResult.new(success: false, errors: ['No public image URLs to send'], media: []) if media.blank?

  input = build_media_input(supplier_part_number, media, validate_only:)

  if dry_run
    logger.info "Wayfair Media [DRY RUN]: would send #{media.size} image(s) for #{supplier_part_number}"
    return MediaResult.new(success: true, request_id: 'dry_run', errors: [], media:)
  end

  send_media(catalog_item, input, media)
end