module ActiveFulfillment
class WebgistixService < Service
SERVICE_URLS = {
:fulfillment => 'https://www.webgistix.com/XML/CreateOrder.asp',
:inventory => 'https://www.webgistix.com/XML/GetInventory.asp',
:tracking => 'https://www.webgistix.com/XML/GetTracking.asp'
}.freeze
TEST_URLS = SERVICE_URLS.dup.merge({
:fulfillment => 'https://www.webgistix.com/XML/CreateOrderTest.asp'
}).freeze
SUCCESS, DUPLICATE, FAILURE = 'True'.freeze, 'Duplicate'.freeze, 'False'.freeze
SUCCESS_MESSAGE = 'Successfully submitted the order'.freeze
FAILURE_MESSAGE = 'Failed to submit the order'.freeze
DUPLICATE_MESSAGE = 'This order has already been successfully submitted'.freeze
INVALID_LOGIN = 'Invalid Credentials'.freeze
NOT_SHIPPED = 'Not Shipped'.freeze
TRACKING_COMPANIES = %w(UPS FedEx USPS).freeze
SHIPPING_PROVIDERS = {
'UPS Ground Shipping' => 'Ground',
'UPS Ground' => 'Ground',
'UPS Standard Shipping (Canada Only)' => 'Standard',
'UPS Standard Shipping (CA & MX Only)' => 'Standard',
'UPS 3-Business Day' => '3-Day Select',
'UPS 2-Business Day' => '2nd Day Air',
'UPS 2-Business Day AM' => '2nd Day Air AM',
'UPS Next Day' => 'Next Day Air',
'UPS Next Day Saver' => 'Next Day Air Saver',
'UPS Next Day Early AM' => 'Next Day Air Early AM',
'UPS Worldwide Express (Next Day)' => 'Worldwide Express',
'UPS Worldwide Expedited (2nd Day)' => 'Worldwide Expedited',
'UPS Worldwide Express Saver' => 'Worldwide Express Saver',
'FedEx Priority Overnight' => 'FedEx Priority Overnight',
'FedEx Standard Overnight' => 'FedEx Standard Overnight',
'FedEx First Overnight' => 'FedEx First Overnight',
'FedEx 2nd Day' => 'FedEx 2nd Day',
'FedEx Express Saver' => 'FedEx Express Saver',
'FedEx International Priority' => 'FedEx International Priority',
'FedEx International Economy' => 'FedEx International Economy',
'FedEx International First' => 'FedEx International First',
'FedEx Ground' => 'FedEx Ground',
'USPS Priority Mail' => 'Priority Mail',
'USPS Priority Mail International' => 'Priority Mail International',
'USPS Priority Mail Small Flat Rate Box' => 'Priority Mail Small Flat Rate Box',
'USPS Priority Mail Medium Flat Rate Box' => 'Priority Mail Medium Flat Rate Box',
'USPS Priority Mail Large Flat Rate Box' => "Priority Mail Large Flat Rate Box",
'USPS Priority Mail Flat Rate Envelope' => 'Priority Mail Flat Rate Envelope',
'USPS First Class Mail' => 'First Class',
'USPS First Class International' => 'First Class International',
'USPS Express Mail' => 'Express',
'USPS Express Mail International' => 'Express Mail International',
'USPS Parcel Post' => 'Parcel',
'USPS Media Mail' => 'Media Mail'
}.freeze
# If a request is detected as a duplicate only the original data will be
# used by Webgistix, and the subsequent responses will have a
# :duplicate parameter set in the params hash.
self.retry_safe = true
# The first is the label, and the last is the code
def self.shipping_methods
SHIPPING_PROVIDERS
end
# Pass in the login and password for the shipwire account.
# Optionally pass in the :test => true to force test mode
def initialize(options = {})
requires!(options, :login, :password)
super
end
def fulfill(order_id, shipping_address, line_items, options = {})
requires!(options, :shipping_method)
commit :fulfillment, build_fulfillment_request(order_id, shipping_address, line_items, options)
end
def fetch_stock_levels(options = {})
commit :inventory, build_inventory_request(options)
end
def fetch_tracking_data(order_ids, options = {})
commit :tracking, build_tracking_request(order_ids, options)
end
def valid_credentials?
response = fulfill('', {}, [], :shipping_method => '')
response.message != INVALID_LOGIN
end
def test_mode?
true
end
private
#
#
# Webgistix
# 3
#
#
# Test Company
# Joe Smith
# 123 Main St.
#
#
# Olean
# NY
# 14760
# United States
# info@webgistix.com
# 1-123-456-7890
# Ground
# Test Order
# 0
# -
# testitem
# 2
#
#
#
def build_fulfillment_request(order_id, shipping_address, line_items, options)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
xml.tag! 'OrderXML' do
add_credentials(xml)
add_order(xml, order_id, shipping_address, line_items, options)
end
xml.target!
end
#
#
# Webgistix
# 3
#
def build_inventory_request(options)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
xml.tag! 'InventoryXML' do
add_credentials(xml)
end
end
#
#
# Webgistix
# 3
#
# AB12345
#
#
# XY4567
#
#
def build_tracking_request(order_ids, options)
xml = Builder::XmlMarkup.new :indent => 2
xml.instruct!
xml.tag! 'TrackingXML' do
add_credentials(xml)
order_ids.each do |o_id|
xml.tag! 'Tracking' do
xml.tag! 'Order', o_id
end
end
end
end
def add_credentials(xml)
xml.tag! 'CustomerID', @options[:login]
xml.tag! 'Password', @options[:password]
end
def add_order(xml, order_id, shipping_address, line_items, options)
xml.tag! 'Order' do
xml.tag! 'ReferenceNumber', order_id
xml.tag! 'ShippingInstructions', options[:shipping_method]
xml.tag! 'Approve', 1
xml.tag! 'OrderComments', options[:comment] unless options[:comment].blank?
add_address(xml, shipping_address, options)
Array(line_items).each_with_index do |line_item, index|
add_item(xml, line_item, index)
end
end
end
def add_address(xml, address, options)
xml.tag! 'Name', address[:name]
xml.tag! 'Address1', address[:address1]
xml.tag! 'Address2', address[:address2] unless address[:address2].blank?
xml.tag! 'Address3', address[:address3] unless address[:address3].blank?
xml.tag! 'City', address[:city]
xml.tag! 'State', address[:state]
xml.tag! 'ZipCode', address[:zip]
xml.tag! 'Company', address[:company]
unless address[:country].blank?
country = ActiveUtils::Country.find(address[:country])
xml.tag! 'Country', country.name
end
xml.tag! 'Phone', address[:phone]
xml.tag! 'Email', options[:email] unless options[:email].blank?
end
def add_item(xml, item, index)
xml.tag! 'Item' do
xml.tag! 'ItemID', item[:sku] unless item[:sku].blank?
xml.tag! 'ItemQty', item[:quantity] unless item[:quantity].blank?
end
end
def commit(action, request)
url = test? ? TEST_URLS[action] : SERVICE_URLS[action]
data = ssl_post(url, request,
'EndPointURL' => url,
'Content-Type' => 'text/xml; charset="utf-8"'
)
response = parse_response(action, data)
Response.new(success?(response), message_from(response), response, :test => test?)
end
def success?(response)
response[:success] == SUCCESS || response[:success] == DUPLICATE
end
def message_from(response)
if response[:duplicate]
DUPLICATE_MESSAGE
elsif success?(response)
SUCCESS_MESSAGE
elsif response[:error_0] == INVALID_LOGIN
INVALID_LOGIN
else
FAILURE_MESSAGE
end
end
def parse_response(action, xml)
begin
document = Nokogiri::XML("#{xml}")
rescue Nokogiri::XML::SyntaxError
return {:success => FAILURE}
end
case action
when :fulfillment
parse_fulfillment_response(document)
when :inventory
parse_inventory_response(document)
when :tracking
parse_tracking_response(document)
else
raise ArgumentError, "Unknown action #{action}"
end
end
def parse_fulfillment_response(document)
response = parse_errors(document)
# Check if completed
if completed = document.at_xpath('//Completed'.freeze)
completed.elements.each do |e|
response[e.name.underscore.to_sym] = e.text
end
else
response[:success] = FAILURE
end
response[:duplicate] = response[:success] == DUPLICATE
response
end
def parse_inventory_response(document)
response = parse_errors(document)
response[:stock_levels] = {}
document.root.xpath('//Item'.freeze).each do |node|
# {ItemID => 'SOME-ID', ItemQty => '101'}
params = node.elements.to_a.each_with_object({}) {|elem, hash| hash[elem.name] = elem.text}
response[:stock_levels][params['ItemID'.freeze]] = params['ItemQty'.freeze].to_i
end
response
end
def parse_tracking_response(document)
response = parse_errors(document)
response = response.merge(tracking_numbers: {}, tracking_companies: {}, tracking_urls: {})
document.root.xpath('//Shipment'.freeze).each do |node|
# {InvoiceNumber => 'SOME-ID', ShipmentTrackingNumber => 'SOME-TRACKING-NUMBER'}
params = node.elements.to_a.each_with_object({}) {|elem, hash| hash[elem.name] = elem.text}
tracking = params['ShipmentTrackingNumber'.freeze]
unless tracking == NOT_SHIPPED
response[:tracking_numbers][params['InvoiceNumber'.freeze]] ||= []
response[:tracking_numbers][params['InvoiceNumber'.freeze]] << tracking
end
company = params['Method'.freeze].split[0] if params['Method'.freeze]
if TRACKING_COMPANIES.include? company
response[:tracking_companies][params['InvoiceNumber'.freeze]] ||= []
response[:tracking_companies][params['InvoiceNumber'.freeze]] << company
end
end
response
end
def parse_errors(document)
response = {}
document.xpath('//Errors/Error'.freeze).each_with_index do |e, i|
response["error_#{i}".to_sym] = e.text
end
response[:success] = response.empty? ? SUCCESS : FAILURE
response
end
end
end