Class: Shipping::AmazonSeller

Inherits:
Base
  • Object
show all
Defined in:
app/services/shipping/amazon_seller.rb

Overview

Shipping carrier class for Amazon Buy Shipping API
Provides Amazon's negotiated carrier rates with A-to-Z Buy Shipping protections

This class follows the same interface as other Shipping::* carriers
(specifically mirroring Shipping::WalmartSeller) and integrates with
the WyShipping rate comparison system.

Usage:
shipper = Shipping::AmazonSeller.new(
amazon_partner: :amazon_seller_central_us,
amazon_order_id: '111-2222222-3333333',
sender_address: '123 Warehouse St',
# ... other address fields
)
response = shipper.find_rates
if response[:success]
response[:rates].each { |rate| puts rate[:service_name] }
end

Constant Summary collapse

CARRIER_NAME =

Carrier name.

'AmazonSeller'
SERVICE_CODE_PREFIX =

Service code prefix to distinguish Amazon Buy Shipping rates from direct carrier rates

'AMZBS_'

Instance Attribute Summary collapse

Attributes inherited from Base

#address, #address2, #address3, #address_residential, #attention_name, #billing_account, #billing_country, #billing_zip, #ci_comments, #city, #close_report_only, #cod_amount, #cod_collection_type, #company, #country, #currency_code, #data, #debug, #declared_value, #delivery_instructions, #delivery_total_value, #description, #discount_price, #dropoff_type, #email, #eta, #export_reason, #freight_class, #freightquote_authorization_url, #freightquote_client_id, #freightquote_client_secret, #freightquote_customer_code, #freightquote_events_url, #freightquote_rating_url, #freightquote_shipping_url, #freightquote_voiding_url, #handling_instructions, #has_loading_dock, #image_type, #include_first_class_mail_options, #insured_value, #is_construction_site, #is_trade_show, #label_type, #last_request_payload, #last_request_quote_id, #last_response_payload, #limited_access, #line_items, #master_tracking_number, #measure_height, #measure_length, #measure_units, #measure_width, #media_mail, #multiple_piece_shipping, #negotiated_rates, #package, #package_count, #package_sequence_number, #package_total, #packages, #packaging_type, #paperless, #pay_type, #phone, #pickup_datetime, #pickup_instructions, #plain_response, #price, #rate_data, #reference_number_1, #reference_number_2, #reference_number_3, #reference_number_code_1, #reference_number_code_2, #required, #requires_appointment, #requires_inside_delivery, #requires_liftgate, #response, #response_headers, #response_status, #return_to_address, #return_to_address2, #return_to_address3, #return_to_address_residential, #return_to_attention_name, #return_to_city, #return_to_company, #return_to_country, #return_to_email, #return_to_has_loading_dock, #return_to_is_construction_site, #return_to_is_trade_show, #return_to_limited_access, #return_to_name, #return_to_phone, #return_to_requires_appointment, #return_to_requires_inside_delivery, #return_to_requires_liftgate, #return_to_state, #return_to_zip, #rl_carriers_api_key, #rl_carriers_shipping_url, #saturday_delivery, #sender_address, #sender_address2, #sender_address3, #sender_address_residential, #sender_attention_name, #sender_city, #sender_company, #sender_country, #sender_email, #sender_has_loading_dock, #sender_is_construction_site, #sender_is_trade_show, #sender_limited_access, #sender_name, #sender_phone, #sender_requires_appointment, #sender_requires_inside_delivery, #sender_requires_liftgate, #sender_state, #sender_tax_identification_number, #sender_zip, #service_code, #service_type, #services, #ship_date, #shipengine_api_key, #shipengine_canadapost_account_id, #shipengine_canadapost_parent_account_number, #shipengine_canpar_account_id, #shipengine_dhl_express_account_id, #shipengine_fed_ex_account_id, #shipengine_fed_ex_ca_account_id, #shipengine_purolator_account_id, #shipengine_ups_account_id, #shipengine_ups_ca_account_id, #shipengine_usps_account_id, #shipper_address, #shipper_address2, #shipper_address3, #shipper_address_residential, #shipper_attention_name, #shipper_city, #shipper_company, #shipper_country, #shipper_email, #shipper_has_loading_dock, #shipper_is_construction_site, #shipper_is_trade_show, #shipper_limited_access, #shipper_name, #shipper_phone, #shipper_requires_appointment, #shipper_requires_inside_delivery, #shipper_requires_liftgate, #shipper_state, #shipper_zip, #signature_confirmation, #skip_png_download, #skip_rate_test, #special_instructions, #state, #tax_identification_number, #time_in_transit, #total_shipment_weight, #transaction_type, #weight, #weight_units, #zip

Instance Method Summary collapse

Methods inherited from Base

#fedex, #purolator, state_from_zip, #ups, #ups_freight

Constructor Details

#initialize(options = {}) ⇒ AmazonSeller

Returns a new instance of AmazonSeller.



34
35
36
37
38
39
40
41
# File 'app/services/shipping/amazon_seller.rb', line 34

def initialize(options = {})
  super
  @amazon_partner = options[:amazon_partner]
  @amazon_order_id = options[:amazon_order_id]
  @deliver_by_date = options[:deliver_by_date]
  @ship_by_date = options[:ship_by_date]
  @api_log = []
end

Instance Attribute Details

#amazon_order_idObject

Returns the value of attribute amazon_order_id.



23
24
25
# File 'app/services/shipping/amazon_seller.rb', line 23

def amazon_order_id
  @amazon_order_id
end

#amazon_partnerObject

Returns the value of attribute amazon_partner.



23
24
25
# File 'app/services/shipping/amazon_seller.rb', line 23

def amazon_partner
  @amazon_partner
end

#api_logObject (readonly)

Accumulated API call log from all Amazon Buy Shipping operations on this shipper instance



32
33
34
# File 'app/services/shipping/amazon_seller.rb', line 32

def api_log
  @api_log
end

#deliver_by_dateObject

Returns the value of attribute deliver_by_date.



23
24
25
# File 'app/services/shipping/amazon_seller.rb', line 23

def deliver_by_date
  @deliver_by_date
end

#ship_by_dateObject

Returns the value of attribute ship_by_date.



23
24
25
# File 'app/services/shipping/amazon_seller.rb', line 23

def ship_by_date
  @ship_by_date
end

Instance Method Details

#create_label(rate, logger = nil) ⇒ Hash

Create a shipping label via Amazon Buy Shipping

Parameters:

  • rate (Hash)

    The rate to purchase (from find_rates, includes request_token and rate_id)

  • logger (Object, nil) (defaults to: nil)

Returns:

  • (Hash)

    Result with :success, :shipment_id, :tracking_number, :carrier, :label_data, :error
    For multi-package rates, also includes :additional_labels array with per-package results.



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/services/shipping/amazon_seller.rb', line 91

def create_label(rate, logger = nil)
  logger ||= Rails.logger

  return { success: false, error: 'Amazon partner and order ID are required' } unless @amazon_partner.present? && @amazon_order_id.present?

  per_pkg = rate[:per_package_rates] || rate.dig(:rate_data, :per_package_rates) || rate.dig(:rate_data, 'per_package_rates')
  per_pkg = per_pkg&.map { |pr| pr.respond_to?(:symbolize_keys) ? pr.symbolize_keys : pr }
  if per_pkg.is_a?(Array) && per_pkg.size > 1
    create_label_multi_package(rate, per_pkg, logger)
  else
    create_label_single(rate, logger)
  end
end

#find_rates(logger = nil) ⇒ Hash

Get shipping rate offerings from Amazon Buy Shipping

Amazon's GetRates API only supports 1 package per request.
For multi-package shipments, we make separate API calls per package
and aggregate the results — only services available for ALL packages
are returned, with charges summed across packages.

Returns:

  • (Hash)

    Response hash with :success, :rates, :message, :request, :xml keys



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'app/services/shipping/amazon_seller.rb', line 51

def find_rates(logger = nil)
  logger ||= Rails.logger

  return build_error_response('Amazon partner configuration is required') if @amazon_partner.blank?
  return build_error_response('Amazon order ID is required') if @amazon_order_id.blank?
  return build_error_response('Valid origin address is required') unless valid_origin_address?

  begin
    orchestrator = Edi::Amazon::Orchestrator.new(@amazon_partner)
    amz_client = Edi::Amazon::ShipWithAmazon.new(orchestrator)

    options = build_rate_options
    all_packages = options[:packages] || []
    logger.info("[AMZ-BS] Requesting rates for order: #{@amazon_order_id}, #{all_packages.size} package(s)")

    if all_packages.size <= 1
      result = amz_client.get_rates(options)
      @api_log.concat(amz_client.api_log)

      if result.success
        rates = build_rate_estimates(result.rates, result.request_token)
        build_success_response(rates, options)
      else
        build_error_response(result.error || 'Failed to retrieve Amazon Buy Shipping rates')
      end
    else
      find_rates_multi_package(amz_client, options, all_packages, logger)
    end
  rescue StandardError => e
    logger.error("[AMZ-BS] find_rates error: #{e.message}")
    logger.error(e.backtrace.first(5).join("\n"))
    build_error_response("Error retrieving Amazon Buy Shipping rates: #{e.message}")
  end
end

#get_documents(shipment_id, package_reference_id, logger = nil) ⇒ Hash

Download label documents for a previously purchased shipment
Only needed if label_data was not returned inline during purchase

Parameters:

  • shipment_id (String)
  • package_reference_id (String)
  • logger (Object, nil) (defaults to: nil)

Returns:

  • (Hash)

    Result with :success, :label_data, :error



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'app/services/shipping/amazon_seller.rb', line 239

def get_documents(shipment_id, package_reference_id, logger = nil)
  logger ||= Rails.logger

  return { success: false, error: 'Amazon partner configuration is required' } if @amazon_partner.blank?

  begin
    orchestrator = Edi::Amazon::Orchestrator.new(@amazon_partner)
    amz_client = Edi::Amazon::ShipWithAmazon.new(orchestrator)

    result = amz_client.get_shipment_documents(shipment_id, package_reference_id)
    @api_log.concat(amz_client.api_log)

    {
      success: result.success,
      label_data: result.label_data,
      format: result.format,
      error: result.error
    }
  rescue StandardError => e
    logger.error("[AMZ-BS] get_documents error: #{e.message}")
    { success: false, error: e.message }
  end
end

#void_label(shipment_id, logger = nil) ⇒ Hash

Cancel/void a purchased Amazon Buy Shipping label

Parameters:

  • shipment_id (String)

    The Amazon shipment ID to cancel

  • logger (Object, nil) (defaults to: nil)

Returns:

  • (Hash)

    Result with :success, :error



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'app/services/shipping/amazon_seller.rb', line 213

def void_label(shipment_id, logger = nil)
  logger ||= Rails.logger

  return { success: false, error: 'Amazon partner configuration is required' } if @amazon_partner.blank?
  return { success: false, error: 'Shipment ID is required' } if shipment_id.blank?

  begin
    orchestrator = Edi::Amazon::Orchestrator.new(@amazon_partner)
    amz_client = Edi::Amazon::ShipWithAmazon.new(orchestrator)

    result = amz_client.cancel_shipment(shipment_id)
    @api_log.concat(amz_client.api_log)

    { success: result.success, error: result.error }
  rescue StandardError => e
    logger.error("[AMZ-BS] void_label error: #{e.message}")
    { success: false, error: e.message }
  end
end