module ActiveMerchant
module Fulfillment
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'
}
TEST_URLS = SERVICE_URLS.merge({
:fulfillment => 'https://www.webgistix.com/XML/CreateOrderTest.asp'
})
SUCCESS, DUPLICATE, FAILURE = 'True', 'Duplicate', 'False'
SUCCESS_MESSAGE = 'Successfully submitted the order'
FAILURE_MESSAGE = 'Failed to submit the order'
DUPLICATE_MESSAGE = 'This order has already been successfully submitted'
INVALID_LOGIN = 'Invalid Credentials'
NOT_SHIPPED = 'Not Shipped'
TRACKING_COMPANIES = %w(UPS FedEx USPS)
# 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
[
["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"]
].inject(ActiveSupport::OrderedHash.new){|h, (k,v)| h[k] = v; h}
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 = 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 = REXML::Document.new("#{xml}")
rescue REXML::ParseException
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 = REXML::XPath.first(document, '//Completed')
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.each_element('//Item') 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']] = params['ItemQty'].to_i
end
response
end
def parse_tracking_response(document)
response = parse_errors(document)
response[:tracking_numbers] = {}
response[:tracking_companies] = {}
response[:tracking_urls] = {}
document.root.each_element('//Shipment') 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']
unless tracking == NOT_SHIPPED
response[:tracking_numbers][params['InvoiceNumber']] ||= []
response[:tracking_numbers][params['InvoiceNumber']] << tracking
end
company = params['Method'].split[0] if params['Method']
if TRACKING_COMPANIES.include? company
response[:tracking_companies][params['InvoiceNumber']] ||= []
response[:tracking_companies][params['InvoiceNumber']] << company
end
end
response
end
def parse_errors(document)
response = {}
REXML::XPath.match(document, "//Errors/Error").to_a.each_with_index do |e, i|
response["error_#{i}".to_sym] = e.text
end
response[:success] = response.empty? ? SUCCESS : FAILURE
response
end
end
end
end