Code Climate Build Status

ShippingEasy

This is the official wrapper for the ShippingEasy API.

The ShippingEasy API supports the following features:

  • Secure, authenticated intake of orders for shipment
  • Cancellation of orders that no longer need to be shipped
  • A callback facility that provides shipment information (carrier, cost, tracking number, etc.) for an order
  • Query capabilities to read order data from ShippingEasy

We will keep this library up to date as we expand the ShippingEasy API.

Setup

Installation

Add this line to your application's Gemfile:

gem 'shipping_easy'

And then execute:

$ bundle

Or install it yourself as:

$ gem install shipping_easy

Configuration

You will need a ShippingEasy API key and secret to sign your API requests. These can be found in your account's settings (https://app.shippingeasy.com/settings/api_credentials).

Once you have the credentials, add them to the libary's configuration. Do this in an intializer if you're running a Rails app:

ShippingEasy.configure do |config|
  config.api_key = 'd8e8fca2dc0f896fd7cb4cb0031ba249'
  config.api_secret = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
end

If you are a 3rd party plugin developer and have a staging account with ShippingEasy, you can change the base URL like so:

config.base_url = 'https://staging.shippingeasy.com'

Authentication

The ShippingEasy API will hit a callback URL when an order, or a part of an order, has been shipped. The request to the callback URL will be also signed with the same shared secret found in the store's API settings.

This gem provides an Authenticator to handle verifying the signed request from ShippingEasy. Here's an example of how to use it (after you configured the library with your credentials in the step above):

ShippingEasy::Authenticator.authenticate(method: :post, path: "/callback", body: "{\"shipment\":{\"id\":\"1234\"}}")
# => true

The arguments for the constructor are as follows:

  • method - The method of the http request. E.g. "post" or "get".
  • path - The path of the request's uri. E.g. "/orders/callback"
  • params - An associative array of the request's query string parameters. E.g. array("api_signature" => "asdsadsad", "api_timestamp" => "1234567899")
  • body - The request body as a JSON string.
  • api_secret - Optional. The ShippingEasy API secret for the customer account. Defaults to the global configuration if set.

API Calls

Finding an order

To retrieve a specific order, call the find method on the Order resource class.

ShippingEasy::Resources::Order.find(id: 876)

If successful the call will return a JSON hash with the ShippingEasy order.

Example payload may be found here:

https://gist.github.com/twmills/491b44fb6e78b20c1266

Possible Exceptions

ShippingEasy::AccessDeniedError

Your credentials could not be authenticated or the store api_key could not be found.

ShippingEasy::ResourceNotFoundError

The requested resource could not be found.

ShippingEasy::InvalidRequestError

The order could not be created on the server for one or more of the following reasons:

  • The API timestamp could not be parsed.

The exception will contain a message that indicates which of these conditions failed.

Retrieving multiple orders

To retrieve multiple orders, call the find_all method on the Order resource class with a ShippingEasy order ID specified.

ShippingEasy::Resources::Order.find_all

If successful the call will return a JSON hash included an array of orders and a hash metadata detailing the conditions used in the search as well as pagination details regarding the response.

Filtering Parameters

page : The page to return in the paginated result set.

per_page : The number of result to include per pagein the paginated result set. Defaults to 50 if not specified and the maximum number of results returned per page is 200.

last_updated_at : Filters the results by the orders' last updated at timestamp and only returns results with a timestamp newer than or equal to the specified value. Defaults to 1 week ago if not specified. The maxiumum time this value can be set to is 3 months ago.

status : Filters the results by the orders' ShippingEasy order status. Defaults to "shipped". Possible values are "shipped" and "ready_for_shipment". It is possible to pass an array of statuses, e.g. ["shipping", "ready_for_shipment"].

page : The page to return in the paginated result set.

store_api_key : By default orders are searched across all the stores on a customer's account. If you would like to filter on a specific API-driven store, include its API key.

Filtering Example

ShippingEasy::Resources::Order.find_all(page: 1, per_page: 1, status: ["ready_for_shipment", "shipped"], last_updated_at: "2014-05-07 14:42:18 UTC")

An example JSON response may be found here:

https://gist.github.com/twmills/005b3c4ab9c85330a801

Possible Exceptions

ShippingEasy::AccessDeniedError

Your credentials could not be authenticated or the store api_key could not be found.

ShippingEasy::InvalidRequestError

The orders could not retrieved for one or more of the following reasons:

  • The API timestamp could not be parsed.

The exception will contain a message that indicates which of these conditions failed.

Adding an order

To add an order to a store, simply call the create method on the Order resource class. (A comprehensive list of the data attributes and their definitions can be found below.)

payload = { order: "external_order_identifier" => "ABC123", "subtotal_including_tax" => "12.38", ... }
ShippingEasy::Resources::Order.create(store_api_key: "d8821dde1d32f408def40b77273d5c11", payload: payload)

If successful the call will return a JSON hash with the ShippingEasy order ID, as well as the external order identifier originally supplied in your call.

{ "order" => { "id" => "27654", "external_order_identifier" => "ABC123" } }

Possible Exceptions

ShippingEasy::AccessDeniedError

Your credentials could not be authenticated or the store api_key could not be found.

ShippingEasy::InvalidRequestError

The order could not be created on the server for one or more of the following reasons:

  • The JSON payload could not be parsed.
  • One or more of the supplied data attributes failed validation and is missing or incorrect.
  • An order with the supplied external_order_identifier already exists for that store.

The exception will contain a message that indicates which of these conditions failed.

Order Attributes

The following is a list of attributes that should be provided to the ShippingEasy_Order object as a associative array.

An example hash for the create order API call may be found here: https://gist.github.com/twmills/9349053.

external_order_identifier : Required. The e-commerce system's order ID.

ordered_at : Required. Timestamp when the order was created.

order_status : Possible values are "awaiting_shipment", "awaiting_payment", "awaiting_fulfillment", "awaiting_shipment", "partially_shipped". Default is "awaiting_shipment".

total_including_tax : Defaults to 0.0 if not specified.

total_excluding_tax : Defaults to 0.0 if not specified.

discount_amount : Defaults to 0.0 if not specified.

coupon_discount : Defaults to 0.0 if not specified.

subtotal_including_tax : Defaults to 0.0 if not specified.

subtotal_excluding_tax : Defaults to 0.0 if not specified.

subtotal_tax : Defaults to 0.0 if not specified.

total_tax : Defaults to 0.0 if not specified.

base_shipping_cost : Defaults to 0.0 if not specified.

shipping_cost_including_tax : Defaults to 0.0 if not specified.

shipping_cost_excluding_tax : Defaults to 0.0 if not specified.

shipping_cost_tax : Defaults to 0.0 if not specified.

base_handling_cost : Defaults to 0.0 if not specified.

handling_cost_excluding_tax : Defaults to 0.0 if not specified.

handling_cost_including_tax : Defaults to 0.0 if not specified.

handling_cost_tax : Defaults to 0.0 if not specified.

base_wrapping_cost : Defaults to 0.0 if not specified.

wrapping_cost_excluding_tax : Defaults to 0.0 if not specified.

wrapping_cost_including_tax : Defaults to 0.0 if not specified.

wrapping_cost_tax : Defaults to 0.0 if not specified.

notes : Customer notes on the order.

billing_company : Company name for billing address

billing_first_name : Customer first name for billing address

billing_last_name : Customer last name for billing address

billing_address : First address line for billing address

billing_address2 : Additional address line for billing address

billing_city : City name for billing address

billing_state : State name for billing address

billing_country : Country name for billing address

billing_postal_code : Postal code for billing address

billing_phone_number : Phone number.

billing_email : Email address

recipients : A nested associative array of recipient attributes. At least one recipient is required.

recipients > company : Company name for shipping address

recipients > first_name : Customer first name for shipping address

recipients > last_name : Customer last name for shipping address

recipients > address : Required. First address line for shipping address

recipients > address2 : Additional address line for shipping address

recipients > city : City name for shipping address

recipients > state : State name for shipping address

recipients > country : Country name for shipping address

recipients > residential : Whether or not address is residential or not. Value can be "true" or "false".

recipients > postal_code : Required. Postal code for shipping address

recipients > postal_code_plus_4 : Postal code plus 4 for shipping address

recipients > phone_number : Customer phone number

recipients > email : Customer email address

recipients > base_cost : Cost before tax for all line items sent to this recipient

recipients > cost_excluding_tax : Cost before tax for all line items sent to this recipient

recipients > cost_including_tax : Cost including tax for all line items sent to this recipient

recipients > cost_tax : Cost of the tax for all line items sent to this recipient

recipients > base_handling_cost : Handling cost before tax for all line items sent to this recipient

recipients > handling_cost_excluding_tax : Handling cost before tax for all line items sent to this recipient

recipients > handling_cost_including_tax : Handling cost including tax for all line items sent to this recipient

recipients > handling_cost_tax : Handling cost of the tax for all line items sent to this recipient

recipients > shipping_zone_id : ID of the shipping zone.

recipients > shipping_zone_name : Name of the shipping zone.

recipients > shipping_method : Method of shipment.

recipients > items_total : Total number of items.

recipients > items_shipped : Total number of items shipped.

recipients > line_items : A nested associative array of line item attributes. At least one line item is required.

recipients > line_items > item_name : Name of the item/product.

recipients > line_items > sku : SKU of the item/product.

recipients > line_items > bin_picking_number : Bin number where the item may be stored in a warehouse.

recipients > line_items > weight_in_ounces : Weight of the line item in ounces.

recipients > line_items > quantity : Quantity of the the items for the line item.

recipients > line_items > total_excluding_tax : Total excluding tax for the line item.

recipients > line_items > unit_price : Unit price of the item.

recipients > line_items > product_options : Hash of product variations applicable to this line item. E.g. "size":"XXL"

Cancelling an order

Sometimes an e-commerce system will mark an order as shipped outside of the ShippingEasy system. Therefore an API call is required to remove this order from ShippingEasy so that it is not double-shipped.

Here's an example using your store's API key and the e-commerce order identifier used to create the order in ShippingEasy:

ShippingEasy::Resources::Cancellation.create(store_api_key: "d8821dde1d32f408def40b77273d5c11", external_order_identifier: "ABC123")

If successful the call will return a JSON hash with the ShippingEasy order ID, as well as the external order identifier originally supplied in your call.

{ "order" => { "id" => "27654", "external_order_identifier" => "ABC123" } }

Rate Quote API

ShippingEasy provides an API for our partners to fetch rate quotes from USPS, UPS or FedEx.

Once you've setup your Partner credentials with a ShippingEasy account manager and have been issued an API key/secret, configure the client with your API credentials in the following block, which should be setup in an initializer:

ShippingEasy.configure do |config|
  config.partner_api_key = REPLACE_WITH_API_KEY # Use your provided Partner API Key
  config.partner_api_secret = REPLACE_WITH_API_SECRET # Use your provided Partner API Secret
  config.base_url = "https://app.shippingeasy.com"
end

Rate Quote API Attributes

The following is a list of attributes that should be provided to the ShippingEasy::Resources::RateQuote object as an associative array, using the fetch method.

Some example hashes of rate quote request data for each carrier can be found at https://gist.github.com/nkrupa/3a76e592377f12bd96be

carrier : Required. Must be one of the following (case-sensitive): ['usps', 'ups', 'fedex'].

carrier_service : Required. Must match the carrier-specific service API key (and is case-sensitive). For example, for USPS First Class International, use FirstClassMailInternational. See the carrier's documentation for possible service keys.

packaging : Required. Use custom for customer-provided, otherwise specify the carrier-specific packaging key. For example, for USPS Small Flat Rate Box, use SmallFlatRateBox.

packaging_dimensions : Valid only for customer-provided packaging. Must be a hash with all three keys of :length, :width, :height.

from_address_1 : Required for UPS and FedEx

from_address_2

from_city : Required for UPS and FedEx

from_state : Required for UPS and FedEx

from_postal_code : Required for all carriers

from_phone_number : Required for FedEx

to_residential : boolean (defaults to false) represented if destination address is residential. Used by UPS and FedEx.

to_postal_code : Required for USPS domestic rates, and for UPS and FedEx

to_address_1 : Required for UPS and FedEx

to_address_2

to_city : Required for UPS and FedEx

to_state : Required for UPS and FedEx

to_country_code : Required for all international quotes Defaults to 'US', must be a two-digit ISO-3166 country code.

to_phone_number : Required for FedEx

weight : Required for most carrier services - represented in ounces.

confirmation_option : Available options (not all available for all carriers/services): ["delivery_confirmation", "signature_required", "adult_signature_required", "verbal_confirmation", "DIRECT", "INDIRECT", "ADULT", "included_signature_confirmation", "restricted_delivery", "signature_confirmation", "adult_signature", "delivery_confirmation", "am_delivery"]

requires_additional_handling : Available for UPS only

saturday_pickup : Available for UPS/FedEx only. Cannot be combined with saturday_delivery

saturday_delivery : Available for UPS/FedEx only. Cannot be combined with saturday_pickup

ship_date : Required for FedEx

Possible Exceptions

ShippingEasy::AccessDeniedError

Your credentials could not be authenticated or the store api_key could not be found.

ShippingEasy::InvalidRequestError

The cancellation could not complete for one or more of the following reasons:

  • The order could not be found.
  • The order has already been marked as shipped in the ShippingEasy system and cannot be cancelled.

The exception will contain a message that indicates which of these conditions failed.

Making requests via curl

First you will need to create an API signature. Concatenate these into a plaintext string using the following order:

  1. Capitilized method of the request. E.g. "POST"
  2. The URI path
  3. The query parameters sorted alphabetically and concatenated together into a URL friendly format: param1=ABC&param2=XYZ
  4. The request body as a string if one exists. Escape all double quotes with backslashes.

All parts are then concatenated together with an ampersand. The result resembles something like this:

"POST&/api/orders&api_key=f9a7c8ebdfd34beaf260d9b0296c7059&api_timestamp=1401803554&{\"orders\":{\"name\":\"Flip flops\",\"cost\":\"10.00\",\"shipping_cost\":\"2.00\"}}"

Finally, using your API secret encrypt the string using HMAC sha256. In ruby, it looks like this:

OpenSSL::HMAC::hexdigest("sha256", api_secret, "POST&/api/stores/27aa472e16faa83dd13b7758d31974ed/orders&api_key=f9a7c8ebdfd34beaf260d9b0296c7059&api_timestamp=1401803554&{\"orders\":{\"name\":\"Flip flops\",\"cost\":\"10.00\",\"shipping_cost\":\"2.00\"}}")

API timestamp

You must include an API timestamp in your requests. The timestamp should be a Unix epoch timestamp (integer) of the current time.

Example curl requests

Creating an order

curl -H "Content-Type: application/json" --data @body.json "https://app.shippingeasy.com/api/stores/27aa472e16faa83dd13b7758d31974ed/orders?api_key=f9a7c8ebdfd34beaf260d9b0296c7059&api_timestamp=1401803554&api_signature=c65f43beed46e581939898a78acd10064cfa146845e97885ec02124d7ad648e4"

An example body.json can be found here:

https://gist.github.com/twmills/3f4636b835c611ab3f7f

Cancelling an order

curl -X POST "https://app.shippingeasy.com/api/stores/86eac265e87ea9a74a86fe248f2e79d2/orders/ABC123/cancellations?api_key=XXX&api_signature=c24c5118a2b2af27557af721145991594d00a0d914f0d9bc2b9a2fa26efb30cc&api_timestamp=1409173624"

Searching orders

curl "https://app.shippingeasy.com/api/orders?api_key=XXX&api_signature=a6ab10918d435ad2abfbbe6004dc2fc7faac62a291f4d66e4a36e5a654d15a54&api_timestamp=1409174329&page=3&per_page=10&status%5B%5D=ready_for_shipment&status%5B%5D=shipped"

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request