Created
May 26, 2015 15:58
-
-
Save jsqu99/f0735b88156dd52436df to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'active_support/core_ext/date/calculations' | |
require 'active_support/core_ext/numeric/time' | |
require 'base64' | |
#Unirest.timeout(240) # generous timeout | |
class ShipstationClient | |
class ResponseError < StandardError; end | |
class << self | |
def request(method, path, options) | |
response = Unirest.send method, "https://ssapi.shipstation.com/#{path}", options | |
return response if response.code == 200 | |
raise ResponseError, "#{response.code}, API error: #{response.body.inspect}" | |
end | |
end | |
end | |
class ShipStationApp | |
def initialize(config) | |
@config = config | |
end | |
# ShipStation appears to be recording their timestamps in local (PST) time but storing that timestamp | |
# as UTC (so it's basically 7-8 hours off the correct time (depending on daylight savings). To compensate | |
# for this the timestamp we use for "since" should be adjusted accordingly. | |
ZONE = "Pacific Time (US & Canada)" | |
# REST API doc https://www.mashape.com/shipstation/shipstation | |
def add_shipment(shipment) | |
# Shipstation wants orders and then it creates shipments. This integration assumes the | |
# shipments are already split and will just create "orders" that are identical to the | |
# storefront concept of a shipment. | |
order = populate_order(shipment) | |
options = { | |
headers: ship_headers.merge("content-type" => "application/json"), | |
parameters: order.to_json | |
} | |
response = ShipstationClient.request :post, "Orders/CreateOrder", options | |
puts "result 200 - Shipment transmitted to ShipStation: #{response.body['orderId']}" | |
end | |
private | |
def map_carrier(carrier_name) | |
response = ShipstationClient.request :get, "Carriers", headers: ship_headers | |
response.body.each do |carrier| | |
return carrier["code"] if carrier["name"] == carrier_name | |
end | |
raise "There is no carrier named '#{carrier_name}' configured with this ShipStation account" | |
end | |
def map_service(carrier_code, service_name) | |
response = ShipstationClient.request :get, "Carriers/ListServices?carrierCode=#{carrier_code}", headers: ship_headers | |
response.body.each do |service| | |
return service["code"] if service["name"] == service_name | |
end | |
raise "There is no service named '#{service_name}' associated with the carrier_code of '#{carrier_code}'" | |
end | |
def map_package(carrier_code, package_name) | |
response = ShipstationClient.request :get, "Carriers/ListPackages?carrierCode=#{carrier_code}", headers: ship_headers | |
response.body.each do |package| | |
return package["code"] if package["name"] == package_name | |
end | |
raise "There is no package named '#{package_name}' associated with the carrier_code of '#{carrier_code}'" | |
end | |
def populate_order(shipment) | |
order = { | |
"customerEmail" => shipment[:email], | |
"customerUsername" => shipment[:email], | |
"orderNumber" => shipment[:id], #required | |
"orderDate" => shipment[:created_at] || Time.now, | |
"paymentDate" => shipment[:created_at] || Time.now, | |
"orderStatus" => map_status(shipment[:status]), #required: hold, canceled, awaiting_shipment | |
"shipTo" => populate_address(shipment[:shipping_address]), #required (see populate_address for details) | |
"billTo" => populate_address(shipment[:billing_address]) || populate_address(shipment[:shipping_address]), | |
"customerNotes" => shipment[:delivery_instructions], | |
"internalNotes" => shipment[:internal_notes], | |
"gift" => shipment[:is_gift], | |
"packageCode" => 'package', | |
"advancedOptions" => populate_advanced(shipment), | |
"items" => populate_items(shipment[:items]) | |
} | |
if shipment[:amount_paid] | |
order["amountPaid"] = shipment[:amount_paid].to_f.to_s | |
end | |
if shipment[:shipping_amount] | |
order["shippingAmount"] = shipment[:shipping_amount].to_f.to_s | |
end | |
if shipment[:tax_amount] | |
order["taxAmount"] = shipment[:tax_amount].to_f.to_s | |
end | |
if shipment[:gift_message] | |
order["giftMessage"] = shipment[:gift_message] | |
end | |
if shipment[:confirmation] | |
order["confirmation"] = map_confirmation(shipment[:confirmation]) | |
end | |
if shipment[:requested_shipping_service] | |
order["requestedShippingService"] = shipment[:requested_shipping_service] | |
elsif shipment[:shipping_carrier] | |
carrier_code = map_carrier(shipment[:shipping_carrier]) | |
order["carrierCode"] = carrier_code | |
order["serviceCode"] = map_service(carrier_code, shipment[:shipping_method]) | |
order["packageCode"] = map_package(carrier_code, shipment[:package]) if shipment[:package] | |
end | |
order | |
end | |
def populate_advanced(shipment) | |
advanced = { | |
"storeId" => @config[:shipstation_store_id], | |
"customfield1" => shipment[:custom_field_1], | |
"customfield2" => shipment[:custom_field_2], | |
"customfield3" => shipment[:custom_field_3], | |
} | |
if shipment[:contains_alcohol] | |
advanced["containsAlcohol"] = shipment[:contains_alcohol] | |
end | |
if shipment[:saturday_delivery] | |
advanced["saturdayDelivery"] = shipment[:saturday_delivery] | |
end | |
if shipment[:non_machinable] | |
advanced["nonMachinable"] = shipment[:non_machinable] | |
end | |
advanced | |
end | |
def populate_items(line_items) | |
return if line_items.nil? | |
line_items.map do |item| | |
{ | |
"lineItemKey" => nil, | |
"sku" => item[:product_id], | |
"name" => item[:name], | |
"imageUrl" => item[:image_url], | |
"quantity" => item[:quantity], | |
"unitPrice" => item[:price] | |
} | |
end | |
end | |
def populate_address(address) | |
return if address.nil? || address.empty? | |
address_hash = { | |
:name => "#{address[:firstname]} #{address[:lastname]}", #required | |
:street1 => address[:address1], #required | |
:street2 => address[:address2], | |
:street3 => address[:address3], | |
:city => address[:city], #required | |
:state => address[:state], #required | |
:postalCode => address[:zipcode], #required | |
:country => address[:country], #required | |
:phone => address[:phone], | |
:company => address[:company] | |
} | |
if address[:is_residential].present? | |
address_hash[:residential] = address[:is_residential] | |
end | |
address_hash | |
end | |
def map_status(status) | |
case status | |
when 'hold' | |
'on_hold' | |
when 'canceled', 'cancelled' | |
'cancelled' | |
else | |
'awaiting_shipment' | |
end | |
end | |
def map_confirmation(confirmation) | |
case confirmation | |
when 'Delivery' | |
'delivery' | |
when 'Signature' | |
'signature' | |
when 'Adult Signature' | |
'adult_signature' | |
end | |
end | |
def ship_headers | |
# the parameter authorization is for old customers - legacy support (Mashape) | |
# new customers should use key & secret | |
if !@config[:key].to_s.empty? && !@config[:secret].to_s.empty? | |
authorization = Base64.strict_encode64("#{@config[:key]}:#{@config[:secret]}") | |
else | |
authorization = @config[:authorization] | |
end | |
headers = { 'Authorization' => "Basic #{authorization}" } | |
unless @config[:x_partner].to_s.empty? | |
headers['x-partner'] = @config[:x_partner] | |
end | |
headers | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment