Class: FriendlyShipping::Services::UpsJson

Inherits:
Object
  • Object
show all
Defined in:
lib/friendly_shipping/services/ups_json/label_options.rb,
lib/friendly_shipping/services/ups_json.rb,
lib/friendly_shipping/services/ups_json/label.rb,
lib/friendly_shipping/services/ups_json/api_error.rb,
lib/friendly_shipping/services/ups_json/access_token.rb,
lib/friendly_shipping/services/ups_json/rates_options.rb,
lib/friendly_shipping/services/ups_json/timings_options.rb,
lib/friendly_shipping/services/ups_json/parse_money_hash.rb,
lib/friendly_shipping/services/ups_json/shipping_methods.rb,
lib/friendly_shipping/services/ups_json/label_item_options.rb,
lib/friendly_shipping/services/ups_json/rates_item_options.rb,
lib/friendly_shipping/services/ups_json/parse_json_response.rb,
lib/friendly_shipping/services/ups_json/parse_void_response.rb,
lib/friendly_shipping/services/ups_json/parse_rates_response.rb,
lib/friendly_shipping/services/ups_json/generate_address_hash.rb,
lib/friendly_shipping/services/ups_json/generate_package_hash.rb,
lib/friendly_shipping/services/ups_json/label_billing_options.rb,
lib/friendly_shipping/services/ups_json/label_package_options.rb,
lib/friendly_shipping/services/ups_json/parse_labels_response.rb,
lib/friendly_shipping/services/ups_json/rates_package_options.rb,
lib/friendly_shipping/services/ups_json/generate_rates_payload.rb,
lib/friendly_shipping/services/ups_json/parse_timings_response.rb,
lib/friendly_shipping/services/ups_json/generate_labels_payload.rb,
lib/friendly_shipping/services/ups_json/generate_timings_payload.rb,
lib/friendly_shipping/services/ups_json/parse_rate_modifier_hash.rb,
lib/friendly_shipping/services/ups_json/parse_city_state_lookup_response.rb,
lib/friendly_shipping/services/ups_json/generate_city_state_lookup_payload.rb,
lib/friendly_shipping/services/ups_json/parse_address_classification_response.rb,
lib/friendly_shipping/services/ups_json/generate_address_classification_payload.rb

Overview

Option container for rating a shipment via UPS

Required:

Optional:

Defined Under Namespace

Classes: AccessToken, ApiError, GenerateAddressClassificationPayload, GenerateAddressHash, GenerateCityStateLookupPayload, GenerateLabelsPayload, GeneratePackageHash, GenerateRatesPayload, GenerateTimingsPayload, Label, LabelBillingOptions, LabelItemOptions, LabelOptions, LabelPackageOptions, ParseAddressClassificationResponse, ParseCityStateLookupResponse, ParseJsonResponse, ParseLabelsResponse, ParseMoneyHash, ParseRateModifierHash, ParseRatesResponse, ParseTimingsResponse, ParseVoidResponse, RatesItemOptions, RatesOptions, RatesPackageOptions, TimingsOptions

Constant Summary collapse

CARRIER =
FriendlyShipping::Carrier.new(
  id: 'ups',
  name: 'United Parcel Service',
  code: 'ups',
  shipping_methods: SHIPPING_METHODS
)
TEST_URL =
'https://wwwcie.ups.com'
LIVE_URL =
'https://onlinetools.ups.com'
EU_COUNTRIES =
%w(
  AT BE BG CY CZ DE DK EE ES FI FR GB GR HR HU IE IT LT LU LV MT NL PO PT RO SE SI SK
).map { |country_code| Carmen::Country.coded(country_code) }
SHIPPING_METHODS =
[
  ['US', 'international', 'UPS Standard', '11'],
  ['US', 'international', 'UPS Worldwide Express®', '07'],
  ['US', 'international', 'UPS Worldwide Expedited®', '08'],
  ['US', 'international', 'UPS Worldwide Express Plus®', '54'],
  ['US', 'international', 'UPS Worldwide Saver', '65'],
  ['US', 'domestic', 'UPS 2nd Day Air®', '02'],
  ['US', 'domestic', 'UPS 2nd Day Air A.M.', '59'],
  ['US', 'domestic', 'UPS 3 Day Select®', '12'],
  ['US', 'domestic', 'UPS Ground', '03'],
  ['US', 'domestic', 'UPS Next Day Air®', '01'],
  ['US', 'domestic', 'UPS Next Day Air® Early', '14'],
  ['US', 'domestic', 'UPS Next Day Air Saver®', '13'],
  ['US', 'domestic', 'UPS SurePost Less than 1LB', '92'],
  ['US', 'domestic', 'UPS SurePost 1LB or greater', '93'],
  ['US', 'domestic', 'UPS SurePost BPM', '94'],
  ['US', 'domestic', 'UPS SurePost Media Mail', '95'],
  ['CA', 'domestic', 'UPS Expedited Canadian domestic shipments', '02'],
  ['CA', 'domestic', 'UPS Express Saver Canadian domestic shipments', '13'],
  ['CA', 'domestic', 'UPS 3 Day Select Shipments originating in Canada to CA and US 48', '12'],
  ['CA', 'international', 'UPS 3 Day Select Shipments originating in Canada to CA and US 48', '12'],
  ['CA', 'domestic', 'UPS Access Point Economy Canadian domestic shipments', '70'],
  ['CA', 'domestic', 'UPS Express Canadian domestic shipments', '01'],
  ['CA', 'domestic', 'UPS Express Early Canadian domestic shipments', '14'],
  ['CA', 'international', 'UPS Express Saver International shipments originating in Canada', '65'],
  ['CA', 'international', 'UPS Standard Shipments originating in Canada (Domestic and Int’l)', '11'],
  ['CA', 'domestic', 'UPS Standard Shipments originating in Canada (Domestic and Int’l)', '11'],
  ['CA', 'international', 'UPS Worldwide Expedited International shipments originating in Canada', '08'],
  ['CA', 'international', 'UPS Worldwide Express International shipments originating in Canada', '07'],
  ['CA', 'international', 'UPS Worldwide Express Plus International shipments originating in Canada', '54'],
  ['CA', 'international', 'UPS Express Early Shipments originating in Canada to CA and US 48', '54'],
  ['CA', 'domestic', 'UPS Express Early Shipments originating in Canada to CA and US 48', '54'],
  ['EU', 'domestic', 'UPS Access Point Economy Shipments within the European Union', '70'],
  ['EU', 'international', 'UPS Expedited Shipments originating in the European Union', '08'],
  ['EU', 'international', 'UPS Express Shipments originating in the European Union', '07'],
  ['EU', 'international', 'UPS Standard Shipments originating in the European Union', '11'],
  ['EU', 'international', 'UPS Worldwide Express Plus Shipments originating in the European Union', '54'],
  ['EU', 'international', 'UPS Worldwide Saver Shipments originating in the European Union', '65'],
  ['MX', 'domestic', 'UPS Access Point Economy Shipments within Mexico', '70'],
  ['MX', 'international', 'UPS Expedited Shipments originating in Mexico', '08'],
  ['MX', 'international', 'UPS Express Shipments originating in Mexico', '07'],
  ['MX', 'international', 'UPS Standard Shipments originating in Mexico', '11'],
  ['MX', 'international', 'UPS Worldwide Express Plus Shipments originating in Mexico', '54'],
  ['MX', 'international', 'UPS Worldwide Saver Shipments originating in Mexico', '65'],
  ['PL', 'domestic', 'UPS Access Point Economy Polish domestic shipments', '70'],
  ['PL', 'domestic', 'UPS Today Dedicated Courier Polish domestic shipments', '83'],
  ['PL', 'domestic', 'UPS Today Express Polish domestic shipments', '85'],
  ['PL', 'domestic', 'UPS Today Express Saver Polish domestic shipments', '86'],
  ['PL', 'domestic', 'UPS Today Standard Polish domestic shipments', '82'],
  ['PL', 'international', 'UPS Expedited Shipments originating in Poland', '08'],
  ['PL', 'international', 'UPS Express Shipments originating in Poland', '07'],
  ['PL', 'international', 'UPS Express Plus Shipments originating in Poland', '54'],
  ['PL', 'international', 'UPS Express Saver Shipments originating in Poland', '65'],
  ['PL', 'international', 'UPS Standard Shipments originating in Poland', '11'],
  ['PR', 'domestic', 'UPS 2nd Day Air', '02'],
  ['PR', 'domestic', 'UPS Ground', '03'],
  ['PR', 'domestic', 'UPS Next Day Air', '01'],
  ['PR', 'domestic', 'UPS Next Day Air Early', '14'],
  ['PR', 'international', 'UPS Worldwide Expedited', '08'],
  ['PR', 'international', 'UPS Worldwide Express', '07'],
  ['PR', 'international', 'UPS Worldwide Express Plus', '54'],
  ['PR', 'international', 'UPS Worldwide Saver', '65'],
  ['DE', 'domestic', 'UPS Express 12:00 German domestic shipments', '74'],
  ['OTHER', 'domestic', 'UPS Express', '07'],
  ['OTHER', 'domestic', 'UPS Standard', '11'],
  ['OTHER', 'international', 'UPS Worldwide Expedited', '08'],
  ['OTHER', 'international', 'UPS Worldwide Express Plus', '54'],
  ['OTHER', 'international', 'UPS Worldwide Saver', '65'],
  ['ALL', 'international', 'UPS Worldwide Express Freight', '96'],
  ['ALL', 'international', 'UPS Worldwide Express Freight Midday', '71']
].freeze.map do |origin_country_code, dom_or_intl, name, code|
  FriendlyShipping::ShippingMethod.new(
    name: name,
    service_code: code,
    domestic: dom_or_intl == 'domestic',
    international: dom_or_intl == 'international',
    origin_countries: countries_by_code(origin_country_code),
    multi_package: true
  ).freeze
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(access_token:, test: true, client: nil) ⇒ UpsJson

Returns a new instance of UpsJson.



22
23
24
25
26
27
# File 'lib/friendly_shipping/services/ups_json.rb', line 22

def initialize(access_token:, test: true, client: nil)
  @access_token = access_token
  @test = test
  error_handler = ApiErrorHandler.new(api_error_class: UpsJson::ApiError)
  @client = client || HttpClient.new(error_handler: error_handler)
end

Instance Attribute Details

#access_tokenObject (readonly)

Returns the value of attribute access_token.



10
11
12
# File 'lib/friendly_shipping/services/ups_json.rb', line 10

def access_token
  @access_token
end

#clientObject (readonly)

Returns the value of attribute client.



10
11
12
# File 'lib/friendly_shipping/services/ups_json.rb', line 10

def client
  @client
end

#testObject (readonly)

Returns the value of attribute test.



10
11
12
# File 'lib/friendly_shipping/services/ups_json.rb', line 10

def test
  @test
end

Instance Method Details

#address_classification(location, debug: false) ⇒ Result<ApiResult<String>>

Classify an address.

Parameters:

  • location (Physical::Location)

    The address we want to classify

Returns:

  • (Result<ApiResult<String>>)

    Either "commercial", "residential", or "unknown"



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/friendly_shipping/services/ups_json.rb', line 145

def address_classification(location, debug: false)
  url = "#{base_url}/api/addressvalidation/v1/2"
  headers = required_headers(access_token)
  body = GenerateAddressClassificationPayload.call(location: location).to_json

  request = FriendlyShipping::Request.new(
    url: url,
    http_method: "POST",
    headers: headers,
    body: body,
    debug: debug
  )

  client.post(request).bind do |response|
    ParseAddressClassificationResponse.call(response: response, request: request)
  end
end

#carriersObject



29
30
31
# File 'lib/friendly_shipping/services/ups_json.rb', line 29

def carriers
  Success([CARRIER])
end

#city_state_lookup(location, debug: false) ⇒ Result<ApiResult<Array<Physical::Location>>>

Find city and state for a given ZIP code

Parameters:

  • location (Physical::Location)

    A location object with country and ZIP code set

Returns:

  • (Result<ApiResult<Array<Physical::Location>>>)

    The response data from UPS encoded in a Physical::Location object. Country, City and ZIP code will be set, everything else nil.



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/friendly_shipping/services/ups_json.rb', line 167

def city_state_lookup(location, debug: false)
  url = "#{base_url}/api/addressvalidation/v2/1"
  headers = required_headers(access_token)
  body = GenerateCityStateLookupPayload.call(location: location).to_json

  request = FriendlyShipping::Request.new(
    url: url,
    http_method: "POST",
    headers: headers,
    body: body,
    debug: debug
  )

  client.post(request).bind do |response|
    ParseCityStateLookupResponse.call(response: response, request: request)
  end
end

#create_access_token(client_id:, client_secret:, merchant_id:, debug: false) ⇒ ApiResult<AccessToken>

Creates an access token to be used for future API requests.

Parameters:

  • client_id (String)

    the Client ID of your UPS application

  • client_secret (String)

    the Client Secret of your UPS application

  • merchant_id (String)

    the shipper number associated with your UPS application

  • debug (Boolean) (defaults to: false)

    whether to append debug information to the API result

Returns:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/friendly_shipping/services/ups_json.rb', line 40

def create_access_token(
  client_id:,
  client_secret:,
  merchant_id:,
  debug: false
)
  request = FriendlyShipping::Request.new(
    url: "#{base_url}/security/v1/oauth/token",
    http_method: "POST",
    body: "grant_type=client_credentials",
    headers: {
      Authorization: "Basic #{Base64.urlsafe_encode64("#{client_id}:#{client_secret}")}",
      Content_Type: "application/x-www-form-urlencoded",
      'X-Merchant-Id': merchant_id,
      Accept: "application/json"
    },
    debug: debug
  )
  client.post(request).fmap do |response|
    hash = JSON.parse(response.body)
    FriendlyShipping::ApiResult.new(
      AccessToken.new(
        expires_in: hash['expires_in'],
        issued_at: hash['issued_at'],
        raw_token: hash['access_token']
      ),
      original_request: request,
      original_response: response
    )
  end
end

#labels(shipment, options:, debug: false) ⇒ Result<ApiResult<Array<Label>>>

Generate labels for a shipment, aka by UPS as the Shipping Shipment api: https://developer.ups.com/api/reference?loc=en_US#tag/Shipping_other

Parameters:

Returns:

  • (Result<ApiResult<Array<Label>>>)

    The labels returned from UPS encoded in a FriendlyShipping::ApiResult object.



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/friendly_shipping/services/ups_json.rb', line 129

def labels(shipment, options:, debug: false)
  url = "#{base_url}/api/shipments/v#{options.sub_version || '2205'}/ship"
  # the RequestOption field in the payload is documented as doing city validation but it does not
  url += "?additionaladdressvalidation=city" if options.validate_address
  headers = required_headers(access_token)
  body = GenerateLabelsPayload.call(shipment: shipment, options: options).to_json
  request = FriendlyShipping::Request.new(url: url, http_method: "POST", headers: headers, body: body, debug: debug)

  client.post(request).bind do |response|
    ParseLabelsResponse.call(response: response, request: request)
  end
end

#rates(shipment, options:, debug: false) ⇒ Result<ApiResult<Array<Rate>>> Also known as: rate_estimates

Get rates for a shipment

Parameters:

Returns:

  • (Result<ApiResult<Array<Rate>>>)

    The rates returned from UPS encoded in a FriendlyShipping::ApiResult object.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/friendly_shipping/services/ups_json.rb', line 78

def rates(shipment, options:, debug: false)
  rate_or_shop = options.shipping_method ? "Rate" : "Shop"
  url = "#{base_url}/api/rating/v#{options.sub_version || '1'}/#{rate_or_shop}"
  headers = required_headers(access_token)
  rates_request_body = GenerateRatesPayload.call(shipment: shipment, options: options).to_json

  request = FriendlyShipping::Request.new(
    url: url,
    http_method: "POST",
    headers: headers,
    body: rates_request_body,
    debug: debug
  )

  client.post(request).bind do |response|
    ParseRatesResponse.call(response: response, request: request, shipment: shipment)
  end
end

#timings(shipment, options:, debug: false) ⇒ Object

Get timing information for a shipment

Parameters:

  • shipment (Physical::Shipment)

    The shipment we want to estimate timings for

  • options (FriendlyShipping::Services::UpsJson::TimingOptions)

    Options for this call



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/friendly_shipping/services/ups_json.rb', line 102

def timings(shipment, options:, debug: false)
  url = "#{base_url}/api/shipments/v1/transittimes"
  headers = required_headers(access_token).merge(
    "transId" => SecureRandom.uuid,
    "transactionSrc" => "testing" # this is a required field according to https://developer.ups.com/api/reference?loc=en_US#operation/TimeInTransit
  )
  timings_request_body = GenerateTimingsPayload.call(shipment: shipment, options: options).to_json

  request = FriendlyShipping::Request.new(
    url: url,
    http_method: "POST",
    headers: headers,
    body: timings_request_body,
    debug: debug
  )

  client.post(request).bind do |response|
    ParseTimingsResponse.call(response: response, request: request, shipment: shipment)
  end
end

#void(label, debug: false) ⇒ Result<ApiResult>

Void a label, aka by UPS as the Shipping Void Shipment api: https://developer.ups.com/api/reference?loc=en_US#tag/Shipping_other

Parameters:

  • label (Label)

    The label to be voided

Returns:

  • (Result<ApiResult>)

    The VoidShipmentResponse body from UPS.



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/friendly_shipping/services/ups_json.rb', line 189

def void(label, debug: false)
  # The docs say to use both the shipment_id and tracking number, but the tracking number seems to work alone.
  # url = "#{base_url}/api/shipments/v1/void/cancel/#{label.shipment_id}?trackingNumber=#{label.tracking_number}"
  url = "#{base_url}/api/shipments/v1/void/cancel/#{label.tracking_number}"
  headers = required_headers(access_token)
  request = FriendlyShipping::Request.new(url: url, http_method: "DELETE", headers: headers, debug: debug)

  client.delete(request).bind do |response|
    ParseVoidResponse.call(response: response, request: request)
  end
end