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 =
'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, #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, #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, #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.



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

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



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

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)

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.



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

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



50
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
# File 'app/services/shipping/amazon_seller.rb', line 50

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

  return build_error_response('Amazon partner configuration is required') unless @amazon_partner.present?
  return build_error_response('Amazon order ID is required') unless @amazon_order_id.present?
  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)

Returns:

  • (Hash)

    Result with :success, :label_data, :error



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

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

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

  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

Returns:

  • (Hash)

    Result with :success, :error



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

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

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

  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