Class: Edi::MarketplaceLabelPurchaser

Inherits:
Object
  • Object
show all
Defined in:
app/services/edi/marketplace_label_purchaser.rb

Overview

Base class for marketplace-specific shipping label purchasers

Each marketplace (Walmart, Amazon, Wayfair, etc.) that offers discounted
shipping labels through their platform should have a subclass that handles:

  • Purchasing labels via the marketplace's API
  • Downloading label PDFs
  • Attaching labels to shipments
  • Voiding/canceling labels

Examples:

Creating a marketplace-specific purchaser

class Edi::Amazon::ShippingLabelPurchaser < Edi::MarketplaceLabelPurchaser
  def purchase_label(rate)
    # Amazon Buy Shipping implementation
  end
end

Using the factory to get the right purchaser

purchaser_class = Edi::MarketplaceLabelPurchaser.for_shipment(shipment)
if purchaser_class
  result = purchaser_class.new(shipment).purchase_label(rate)
end

Defined Under Namespace

Classes: PurchaseResult

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(shipment, options = {}) ⇒ MarketplaceLabelPurchaser

Returns a new instance of MarketplaceLabelPurchaser.



105
106
107
108
109
110
111
112
# File 'app/services/edi/marketplace_label_purchaser.rb', line 105

def initialize(shipment, options = {})
  @shipment = shipment
  @delivery = shipment.delivery
  @order = delivery.order
  @logger = options[:logger] || Rails.logger

  validate_order!
end

Instance Attribute Details

#deliveryObject (readonly)

Returns the value of attribute delivery.



27
28
29
# File 'app/services/edi/marketplace_label_purchaser.rb', line 27

def delivery
  @delivery
end

#loggerObject (readonly)

Returns the value of attribute logger.



27
28
29
# File 'app/services/edi/marketplace_label_purchaser.rb', line 27

def logger
  @logger
end

#orderObject (readonly)

Returns the value of attribute order.



27
28
29
# File 'app/services/edi/marketplace_label_purchaser.rb', line 27

def order
  @order
end

#shipmentObject (readonly)

Returns the value of attribute shipment.



27
28
29
# File 'app/services/edi/marketplace_label_purchaser.rb', line 27

def shipment
  @shipment
end

Class Method Details

.for_delivery(delivery) ⇒ Class?

Factory method to get the appropriate purchaser class for a delivery

Parameters:

  • delivery (Delivery)

    The delivery to check

Returns:

  • (Class, nil)

    The purchaser class or nil if marketplace labels not supported



66
67
68
# File 'app/services/edi/marketplace_label_purchaser.rb', line 66

def self.for_delivery(delivery)
  for_order(delivery&.order)
end

.for_order(order) ⇒ Class?

Factory method to get the appropriate purchaser class for an order

Parameters:

  • order (Order)

    The order to check

Returns:

  • (Class, nil)

    The purchaser class or nil if marketplace labels not supported



49
50
51
52
53
54
55
56
57
58
59
60
# File 'app/services/edi/marketplace_label_purchaser.rb', line 49

def self.for_order(order)
  return nil unless order&.edi_orchestrator_partner.present?

  # Map EDI partner prefixes to their label purchaser classes
  # Add new marketplaces here as they're implemented
  case order.edi_orchestrator_partner
  when /^walmart_seller/ then Edi::Walmart::ShippingLabelPurchaser
  when /^amazon_seller/ then Edi::Amazon::ShippingLabelPurchaser
  # Future marketplaces:
  # when /^wayfair/ then Edi::Wayfair::ShippingLabelPurchaser
  end
end

.for_shipment(shipment) ⇒ Class?

Factory method to get the appropriate purchaser class for a shipment

Parameters:

  • shipment (Shipment)

    The shipment to purchase a label for

Returns:

  • (Class, nil)

    The purchaser class or nil if marketplace labels not supported



38
39
40
41
42
43
# File 'app/services/edi/marketplace_label_purchaser.rb', line 38

def self.for_shipment(shipment)
  order = shipment.delivery&.order
  return nil unless order

  for_order(order)
end

.marketplace_carrier?(carrier) ⇒ Boolean

Check if the carrier is a marketplace-provided carrier

Parameters:

  • carrier (String)

    The carrier name

Returns:

  • (Boolean)


88
89
90
# File 'app/services/edi/marketplace_label_purchaser.rb', line 88

def self.marketplace_carrier?(carrier)
  %w[WalmartSeller AmazonSeller WayfairShipping].include?(carrier)
end

.marketplace_name(order) ⇒ String?

Get the marketplace name for display

Parameters:

  • order (Order)

    The order

Returns:

  • (String, nil)


96
97
98
99
100
101
102
103
# File 'app/services/edi/marketplace_label_purchaser.rb', line 96

def self.marketplace_name(order)
  case order&.edi_orchestrator_partner
  when /^walmart_seller/ then 'Walmart'
  when /^amazon_seller/ then 'Amazon'
  when /^wayfair/ then 'Wayfair'
  else nil
  end
end

.supports_marketplace_labels?(delivery) ⇒ Boolean

Check if a delivery supports marketplace label purchasing

Parameters:

  • delivery (Delivery)

    The delivery to check

Returns:

  • (Boolean)


74
75
76
77
78
79
80
81
82
# File 'app/services/edi/marketplace_label_purchaser.rb', line 74

def self.supports_marketplace_labels?(delivery)
  return false unless delivery&.order

  # Must have a marketplace purchaser available
  return false unless for_delivery(delivery)

  # Must have a marketplace carrier selected
  marketplace_carrier?(delivery.carrier)
end

Instance Method Details

#attach_label_pdf(label_data, filename) ⇒ Upload? (protected)

Download a label PDF and attach it to the shipment
Uses 'ship_label_pdf' category since it's a carrier-generated label
(purchased through marketplace API, but still a real carrier label)

Parameters:

  • label_data (String)

    Raw PDF data

  • filename (String)

    Filename for the upload

Returns:



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/services/edi/marketplace_label_purchaser.rb', line 177

def attach_label_pdf(label_data, filename)
  upload = Upload.uploadify_from_data(
    file_name: filename,
    data: label_data,
    category: 'ship_label_pdf',
    resource: shipment
  )

  if upload&.persisted?
    shipment.uploads << upload
    logger.info("[#{self.class.name}] Label attached as upload #{upload.id}")
  end

  upload
rescue StandardError => e
  logger.error("[#{self.class.name}] Error attaching label: #{e.message}")
  nil
end

#failure_result(error) ⇒ Object (protected)

Create a failure result



158
159
160
161
162
163
164
165
166
167
168
# File 'app/services/edi/marketplace_label_purchaser.rb', line 158

def failure_result(error)
  PurchaseResult.new(
    success: false,
    label_id: nil,
    tracking_number: nil,
    carrier: nil,
    service_type: nil,
    label_upload: nil,
    error: error
  )
end

#map_carrier_name(marketplace_carrier) ⇒ String (protected)

Map marketplace carrier codes to internal carrier names
Subclasses can override for marketplace-specific mappings

Parameters:

  • marketplace_carrier (String)

Returns:

  • (String)


216
217
218
219
220
221
222
223
# File 'app/services/edi/marketplace_label_purchaser.rb', line 216

def map_carrier_name(marketplace_carrier)
  case marketplace_carrier&.upcase
  when 'USPS' then 'USPS'
  when 'FEDEX', 'FEDEX_GROUND', 'FEDEX_EXPRESS' then 'FedEx'
  when 'UPS' then 'UPS'
  else marketplace_carrier
  end
end

#marketplace_nameString

Human-readable name of this marketplace

Returns:

  • (String)

Raises:

  • (NotImplementedError)


132
133
134
# File 'app/services/edi/marketplace_label_purchaser.rb', line 132

def marketplace_name
  raise NotImplementedError, "#{self.class.name} must implement #marketplace_name"
end

#purchase_label(_rate) ⇒ PurchaseResult

Purchase a shipping label using a marketplace rate

Parameters:

  • rate (Hash)

    The rate hash with carrier/service info

Returns:

Raises:

  • (NotImplementedError)


118
119
120
# File 'app/services/edi/marketplace_label_purchaser.rb', line 118

def purchase_label(_rate)
  raise NotImplementedError, "#{self.class.name} must implement #purchase_label"
end

#success_result(label_id:, tracking_number:, carrier:, service_type:, label_upload: nil) ⇒ Object (protected)

Create a successful result



145
146
147
148
149
150
151
152
153
154
155
# File 'app/services/edi/marketplace_label_purchaser.rb', line 145

def success_result(label_id:, tracking_number:, carrier:, service_type:, label_upload: nil)
  PurchaseResult.new(
    success: true,
    label_id: label_id,
    tracking_number: tracking_number,
    carrier: carrier,
    service_type: service_type,
    label_upload: label_upload,
    error: nil
  )
end

#update_shipment_tracking(tracking_number, carrier, label_id) ⇒ Object (protected)

Update shipment with tracking information

Parameters:

  • tracking_number (String)
  • carrier (String)
  • label_id (String)


201
202
203
204
205
206
207
208
209
# File 'app/services/edi/marketplace_label_purchaser.rb', line 201

def update_shipment_tracking(tracking_number, carrier, label_id)
  shipment.update!(
    tracking_number: tracking_number,
    carrier: map_carrier_name(carrier),
    marketplace_label_id: label_id
  )

  logger.info("[#{self.class.name}] Updated shipment #{shipment.id} with tracking: #{tracking_number}")
end

#validate_order!Object (protected)

Validate the order is appropriate for this purchaser
Subclasses should override to add marketplace-specific validation

Raises:

  • (ArgumentError)


140
141
142
# File 'app/services/edi/marketplace_label_purchaser.rb', line 140

def validate_order!
  raise ArgumentError, 'Shipment must have an order' unless order.present?
end

#void_labelHash

Void/discard a previously purchased label

Returns:

  • (Hash)

    Result with :success and :error keys

Raises:

  • (NotImplementedError)


125
126
127
# File 'app/services/edi/marketplace_label_purchaser.rb', line 125

def void_label
  raise NotImplementedError, "#{self.class.name} must implement #void_label"
end