ups 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +107 -0
- data/lib/ups/client.rb +77 -0
- data/lib/ups/configuration.rb +17 -0
- data/lib/ups/errors.rb +10 -0
- data/lib/ups/models/address.rb +20 -0
- data/lib/ups/models/package.rb +23 -0
- data/lib/ups/models/rate_request.rb +33 -0
- data/lib/ups/models/ship_request.rb +34 -0
- data/lib/ups/rating.rb +146 -0
- data/lib/ups/shipping.rb +131 -0
- data/lib/ups/version.rb +4 -0
- data/lib/ups.rb +30 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8bbb686432266ab1abecf098744a453d0c1747eb51a40629335c6f7ce083475b
|
4
|
+
data.tar.gz: fbe3f140f3c29831f21cc340fe272916d26efc9365183f01cdd6dceb8c06ea58
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 791b16e26b54f1e9f0a02353d73d0a4b84c89f459461334d20292341e4ba707ab569a249e617cdc5eaa69f99964177b358ea2f13bd58295998f5627dec871292
|
7
|
+
data.tar.gz: 8483e06fa271c7a514b0d073a9763011f960b7df38d08843ff9d2760dc5fa457b9d5286b429718e39a082ca0002f232a0f63aa72ee0fbbadb7ca847a55c00460
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# UPS Shipping Ruby Gem
|
2
|
+
|
3
|
+
A Ruby wrapper for the UPS Shipping API that allows you to get shipping rates and create shipping labels.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'ups'
|
11
|
+
```
|
12
|
+
|
13
|
+
## Configuration
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
Ups.configure do |config|
|
17
|
+
config.client_id = Rails.application.credentials.ups.client_id
|
18
|
+
config.client_secret = Rails.application.credentials.ups.client_secret
|
19
|
+
config.account_number = Rails.application.credentials.ups.account
|
20
|
+
config.sandbox = false
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
### Getting Shipping Rates
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
# Create addresses
|
30
|
+
shipper = Ups::Address.new(
|
31
|
+
company_name: "ACME Corp",
|
32
|
+
address_line_1: "123 Main St",
|
33
|
+
city: "Atlanta",
|
34
|
+
state: "GA",
|
35
|
+
postal_code: "30309",
|
36
|
+
country_code: "US",
|
37
|
+
phone: "1234567890"
|
38
|
+
)
|
39
|
+
|
40
|
+
ship_to = Ups::Address.new(
|
41
|
+
name: "John Doe",
|
42
|
+
address_line_1: "456 Oak Ave",
|
43
|
+
city: "New York",
|
44
|
+
state: "NY",
|
45
|
+
postal_code: "10001",
|
46
|
+
country_code: "US"
|
47
|
+
)
|
48
|
+
|
49
|
+
# Create package
|
50
|
+
package = Ups:Package.new(
|
51
|
+
length: 10,
|
52
|
+
width: 8,
|
53
|
+
height: 6,
|
54
|
+
weight: 5
|
55
|
+
)
|
56
|
+
|
57
|
+
# Create rate request
|
58
|
+
rate_request = Ups::RateRequest.new(
|
59
|
+
shipper: shipper,
|
60
|
+
ship_to: ship_to,
|
61
|
+
ship_from: shipper,
|
62
|
+
service_code: "03" # UPS Ground
|
63
|
+
)
|
64
|
+
rate_request.add_package(package)
|
65
|
+
|
66
|
+
# Get rates
|
67
|
+
rating_service = Ups::Rating.new(Ups.client)
|
68
|
+
rates = rating_service.get_rates(rate_request)
|
69
|
+
|
70
|
+
rates.each do |rate|
|
71
|
+
puts "#{rate[:service_name]}: $#{rate[:total_cost]} #{rate[:currency]}"
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
### Creating Shipping Labels
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# Create ship request (using same addresses and packages from above)
|
79
|
+
ship_request = Ups::ShipRequest.new(
|
80
|
+
shipper: shipper,
|
81
|
+
ship_to: ship_to,
|
82
|
+
ship_from: shipper,
|
83
|
+
service_code: "03",
|
84
|
+
description: "Test shipment"
|
85
|
+
)
|
86
|
+
ship_request.add_package(package)
|
87
|
+
|
88
|
+
# Create shipment
|
89
|
+
shipping_service = Ups::Shipping.new(Ups.client)
|
90
|
+
result = shipping_service.create_shipment(ship_request)
|
91
|
+
|
92
|
+
puts "Tracking Number: #{result[:tracking_number]}"
|
93
|
+
puts "Total Cost: $#{result[:total_cost]} #{result[:currency]}"
|
94
|
+
puts "Label URL: #{result[:label_url]}"
|
95
|
+
```
|
96
|
+
|
97
|
+
## Service Codes
|
98
|
+
|
99
|
+
- 01: UPS Next Day Air
|
100
|
+
- 02: UPS 2nd Day Air
|
101
|
+
- 03: UPS Ground
|
102
|
+
- 07: UPS Worldwide Express
|
103
|
+
- 08: UPS Worldwide Expedited
|
104
|
+
- 11: UPS Standard
|
105
|
+
- 12: UPS 3 Day Select
|
106
|
+
- 13: UPS Next Day Air Saver
|
107
|
+
- 14: UPS Next Day Air Early AM
|
data/lib/ups/client.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module Ups
|
2
|
+
class Client
|
3
|
+
include HTTParty
|
4
|
+
|
5
|
+
attr_reader :config, :access_token
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
@access_token = nil
|
10
|
+
authenticate
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(endpoint, options = {})
|
14
|
+
request(:get, endpoint, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def post(endpoint, options = {})
|
18
|
+
request(:post, endpoint, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def auth_headers
|
24
|
+
{
|
25
|
+
'Authorization' => "Basic #{auth_string}",
|
26
|
+
'X-Merchant-Id' => @config.client_id,
|
27
|
+
'Accept' => 'application/json',
|
28
|
+
'Content-Type' => 'application/x-www-form-urlencoded'
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def auth_string
|
33
|
+
@auth_string ||= Base64.strict_encode64("#{@config.client_id}:#{@config.client_secret}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def authenticate
|
37
|
+
response = HTTParty.post(@config.oauth_url, { headers: auth_headers, body: 'grant_type=client_credentials' })
|
38
|
+
|
39
|
+
if response.success?
|
40
|
+
@access_token = response.parsed_response['access_token']
|
41
|
+
else
|
42
|
+
raise AuthenticationError, "Failed to authenticate: #{response.body}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def request_headers
|
47
|
+
{
|
48
|
+
'Authorization' => "Bearer #{@access_token}",
|
49
|
+
'Content-Type' => 'application/json'
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def request(method, endpoint, opts = {})
|
54
|
+
url = "#{@config.base_url}#{endpoint}"
|
55
|
+
response = HTTParty.send(method, url, { headers: request_headers.merge(opts[:headers] || {}), body: opts[:body]&.to_json })
|
56
|
+
handle_response(response)
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_response(response)
|
60
|
+
case response.code
|
61
|
+
when 200, 201
|
62
|
+
response.parsed_response
|
63
|
+
when 401
|
64
|
+
authenticate
|
65
|
+
raise AuthenticationError, "Authentication failed"
|
66
|
+
when 400
|
67
|
+
raise BadRequestError, response.body
|
68
|
+
when 404
|
69
|
+
raise NotFoundError, response.body
|
70
|
+
when 500
|
71
|
+
raise ServerError, response.body
|
72
|
+
else
|
73
|
+
raise APIError, "HTTP #{response.code}: #{response.body}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Ups
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :client_id, :client_secret, :account_number, :sandbox
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@sandbox = true
|
7
|
+
end
|
8
|
+
|
9
|
+
def base_url
|
10
|
+
@sandbox ? "https://wwwcie.ups.com" : "https://onlinetools.ups.com"
|
11
|
+
end
|
12
|
+
|
13
|
+
def oauth_url
|
14
|
+
@sandbox ? "https://wwwcie.ups.com/security/v1/oauth/token" : "https://onlinetools.ups.com/security/v1/oauth/token"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ups/errors.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module Ups
|
2
|
+
class Error < StandardError; end
|
3
|
+
class AuthenticationError < Error; end
|
4
|
+
class ValidationError < Error; end
|
5
|
+
class BadRequestError < Error; end
|
6
|
+
class NotFoundError < Error; end
|
7
|
+
class ServerError < Error; end
|
8
|
+
class APIError < Error; end
|
9
|
+
end
|
10
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Ups
|
2
|
+
class Address
|
3
|
+
attr_accessor :name, :company_name, :attention_name, :address_line_1, :address_line_2, :city, :state, :postal_code, :country_code, :phone, :tax_id
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
attrs.map{|k, v| send("#{k}=", v) if respond_to?("#{k}=") }
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate!
|
10
|
+
errors = []
|
11
|
+
errors << "Address line 1 is required" if address_line_1.nil? || address_line_1.strip.empty?
|
12
|
+
errors << "City is required" if city.nil? || city.strip.empty?
|
13
|
+
errors << "State is required" if state.nil? || state.strip.empty?
|
14
|
+
errors << "Postal code is required" if postal_code.nil? || postal_code.strip.empty?
|
15
|
+
errors << "Country code is required" if country_code.nil? || country_code.strip.empty?
|
16
|
+
|
17
|
+
raise ValidationError, errors.join(", ") unless errors.empty?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Ups
|
2
|
+
class Package
|
3
|
+
attr_accessor :description, :packaging_type, :length, :width, :height, :weight, :dimension_unit, :weight_unit, :value
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
attrs.map{|k,v| send("#{k}=", v) if respond_to?("#{k}=") }
|
7
|
+
|
8
|
+
@dimension_unit ||= 'IN'
|
9
|
+
@weight_unit ||= 'LBS'
|
10
|
+
@packaging_type ||= '02' # Customer Supplied Package
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate!
|
14
|
+
errors = []
|
15
|
+
errors << "Length is required" if length.nil? || length <= 0
|
16
|
+
errors << "Width is required" if width.nil? || width <= 0
|
17
|
+
errors << "Height is required" if height.nil? || height <= 0
|
18
|
+
errors << "Weight is required" if weight.nil? || weight <= 0
|
19
|
+
|
20
|
+
raise ValidationError, errors.join(", ") unless errors.empty?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Ups
|
2
|
+
class RateRequest
|
3
|
+
attr_accessor :shipper, :ship_to, :ship_from, :packages, :service_code, :reference, :dcis_type
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
attrs.map{|k, v| send("#{k}=", v) if respond_to?("#{k}=") }
|
7
|
+
@packages = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_package(package)
|
11
|
+
@packages << package
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate!
|
15
|
+
errors = []
|
16
|
+
errors << "Shipper is required" if shipper.nil?
|
17
|
+
errors << "Ship to address is required" if ship_to.nil?
|
18
|
+
errors << "Ship from address is required" if ship_from.nil?
|
19
|
+
errors << "At least one package is required" if packages.empty?
|
20
|
+
|
21
|
+
begin
|
22
|
+
shipper&.validate!
|
23
|
+
ship_to&.validate!
|
24
|
+
ship_from&.validate!
|
25
|
+
packages.each(&:validate!)
|
26
|
+
rescue ValidationError => e
|
27
|
+
errors << e.message
|
28
|
+
end
|
29
|
+
|
30
|
+
raise ValidationError, errors.join(", ") unless errors.empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Ups
|
2
|
+
class ShipRequest
|
3
|
+
attr_accessor :shipper, :ship_to, :ship_from, :packages, :service_code, :reference, :description, :label_format
|
4
|
+
|
5
|
+
def initialize(attrs = {})
|
6
|
+
attrs.map{|k, v| send("#{k}=", v) if respond_to?("#{k}=") }
|
7
|
+
@packages = []
|
8
|
+
@label_format ||= "GIF"
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_package(package)
|
12
|
+
@packages << package
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate!
|
16
|
+
errors = []
|
17
|
+
errors << "Shipper is required" if shipper.nil?
|
18
|
+
errors << "Ship to address is required" if ship_to.nil?
|
19
|
+
errors << "Ship from address is required" if ship_from.nil?
|
20
|
+
errors << "At least one package is required" if packages.empty?
|
21
|
+
|
22
|
+
begin
|
23
|
+
shipper&.validate!
|
24
|
+
ship_to&.validate!
|
25
|
+
ship_from&.validate!
|
26
|
+
packages.each(&:validate!)
|
27
|
+
rescue ValidationError => e
|
28
|
+
errors << e.message
|
29
|
+
end
|
30
|
+
|
31
|
+
raise ValidationError, errors.join(", ") unless errors.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/ups/rating.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
module Ups
|
2
|
+
class Rating
|
3
|
+
attr_reader :client, :account_number
|
4
|
+
|
5
|
+
def initialize(client, account_number: nil)
|
6
|
+
@client = client
|
7
|
+
@account_number = account_number || client.config.account_number
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_rates(rate_request)
|
11
|
+
endpoint = "/api/rating/v2409/Shop"
|
12
|
+
payload = build_rate_payload(rate_request)
|
13
|
+
response = client.post(endpoint, body: payload)
|
14
|
+
parse_rate_response(response)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_rate_payload(rate_request)
|
20
|
+
{
|
21
|
+
RateRequest: {
|
22
|
+
Request: {
|
23
|
+
RequestOption: "Rate",
|
24
|
+
TransactionReference: {
|
25
|
+
CustomerContext: rate_request.reference || "Rating Request"
|
26
|
+
}
|
27
|
+
},
|
28
|
+
Shipment: {
|
29
|
+
ShipmentDate: Date.today.to_s,
|
30
|
+
Shipper: {
|
31
|
+
Name: rate_request.shipper.company_name,
|
32
|
+
ShipperNumber: client.config.account_number,
|
33
|
+
Address: address_hash(rate_request.shipper)
|
34
|
+
},
|
35
|
+
ShipTo: {
|
36
|
+
Name: rate_request.ship_to.company_name || rate_request.ship_to.name,
|
37
|
+
Address: address_hash(rate_request.ship_to)
|
38
|
+
},
|
39
|
+
ShipFrom: {
|
40
|
+
Name: rate_request.ship_from.company_name || rate_request.ship_from.name,
|
41
|
+
Address: address_hash(rate_request.ship_from)
|
42
|
+
},
|
43
|
+
PaymentInformation: {
|
44
|
+
ShipmentCharge: {
|
45
|
+
Type: "01",
|
46
|
+
BillShipper: {
|
47
|
+
AccountNumber: account_number
|
48
|
+
}
|
49
|
+
}
|
50
|
+
},
|
51
|
+
# ShipmentServiceOptions: {
|
52
|
+
# DeliveryConfirmation: {
|
53
|
+
# DCISType: rate_request.dcis_type || 0 # "2" = Signature Required
|
54
|
+
# }
|
55
|
+
# },
|
56
|
+
Package: rate_request.packages.map { |pkg| package_hash(pkg) },
|
57
|
+
Service: {
|
58
|
+
Code: rate_request.service_code || "03"
|
59
|
+
}
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def address_hash(address)
|
66
|
+
{
|
67
|
+
AddressLine: [address.address_line_1, address.address_line_2].compact,
|
68
|
+
City: address.city,
|
69
|
+
StateProvinceCode: address.state,
|
70
|
+
PostalCode: address.postal_code,
|
71
|
+
CountryCode: address.country_code
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def package_hash(package)
|
76
|
+
{
|
77
|
+
PackagingType: {
|
78
|
+
Code: package.packaging_type || "02"
|
79
|
+
},
|
80
|
+
Dimensions: {
|
81
|
+
UnitOfMeasurement: {
|
82
|
+
Code: package.dimension_unit || "IN"
|
83
|
+
},
|
84
|
+
Length: package.length.to_s,
|
85
|
+
Width: package.width.to_s,
|
86
|
+
Height: package.height.to_s
|
87
|
+
},
|
88
|
+
PackageWeight: {
|
89
|
+
UnitOfMeasurement: {
|
90
|
+
Code: package.weight_unit || "LBS"
|
91
|
+
},
|
92
|
+
Weight: package.weight.to_s
|
93
|
+
},
|
94
|
+
PackageServiceOptions: {
|
95
|
+
DeclaredValue: {
|
96
|
+
CurrencyCode: "USD",
|
97
|
+
MonetaryValue: package.value
|
98
|
+
}
|
99
|
+
}
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_rate_response(response)
|
104
|
+
rates = []
|
105
|
+
|
106
|
+
if response['RateResponse'] && response['RateResponse']['RatedShipment']
|
107
|
+
rated_shipments = response['RateResponse']['RatedShipment']
|
108
|
+
rated_shipments = [rated_shipments] unless rated_shipments.is_a?(Array)
|
109
|
+
|
110
|
+
rated_shipments.each do |shipment|
|
111
|
+
rates << {
|
112
|
+
service_code: shipment['Service']['Code'],
|
113
|
+
service_name: get_service_name(shipment['Service']['Code']),
|
114
|
+
total: shipment['TotalCharges']['MonetaryValue'].to_f,
|
115
|
+
currency: shipment['TotalCharges']['CurrencyCode'],
|
116
|
+
transit_time: shipment['GuaranteedDelivery'] ? shipment['GuaranteedDelivery']['BusinessDaysInTransit'] : nil
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
rates
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_service_name(code)
|
125
|
+
service_names[code] || "Unknown Service (#{code})"
|
126
|
+
end
|
127
|
+
|
128
|
+
def service_names
|
129
|
+
@service_names ||= {
|
130
|
+
'01' => 'UPS Next Day Air',
|
131
|
+
'02' => 'UPS 2nd Day Air',
|
132
|
+
'03' => 'UPS Ground',
|
133
|
+
'07' => 'UPS Worldwide Express',
|
134
|
+
'08' => 'UPS Worldwide Expedited',
|
135
|
+
'11' => 'UPS Standard',
|
136
|
+
'12' => 'UPS 3 Day Select',
|
137
|
+
'13' => 'UPS Next Day Air Saver',
|
138
|
+
'14' => 'UPS Next Day Air Early AM',
|
139
|
+
'54' => 'UPS Worldwide Express Plus',
|
140
|
+
'59' => 'UPS 2nd Day Air AM',
|
141
|
+
'65' => 'UPS Saver'
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
data/lib/ups/shipping.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
module Ups
|
2
|
+
class Shipping
|
3
|
+
attr_reader :client, :account_number
|
4
|
+
|
5
|
+
def initialize(client, account_number: nil)
|
6
|
+
@client = client
|
7
|
+
@account_number = account_number || client.config.account_number
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_shipment(ship_request)
|
11
|
+
endpoint = "/api/shipments/v2409/ship"
|
12
|
+
payload = build_ship_payload(ship_request)
|
13
|
+
response = client.post(endpoint, body: payload)
|
14
|
+
parse_ship_response(response)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def build_ship_payload(ship_request)
|
20
|
+
{
|
21
|
+
ShipmentRequest: {
|
22
|
+
Request: {
|
23
|
+
RequestOption: "nonvalidate",
|
24
|
+
TransactionReference: {
|
25
|
+
CustomerContext: ship_request.reference || "Shipping Request"
|
26
|
+
}
|
27
|
+
},
|
28
|
+
Shipment: {
|
29
|
+
ShipmentDate: Date.today.to_s,
|
30
|
+
Description: ship_request.description || "Package",
|
31
|
+
Shipper: {
|
32
|
+
Name: ship_request.shipper.company_name,
|
33
|
+
AttentionName: ship_request.shipper.attention_name,
|
34
|
+
TaxIdentificationNumber: ship_request.shipper.tax_id,
|
35
|
+
Phone: {
|
36
|
+
Number: ship_request.shipper.phone
|
37
|
+
},
|
38
|
+
ShipperNumber: client.config.account_number,
|
39
|
+
Address: address_hash(ship_request.shipper)
|
40
|
+
},
|
41
|
+
ShipTo: {
|
42
|
+
Name: ship_request.ship_to.company_name || ship_request.ship_to.name,
|
43
|
+
AttentionName: ship_request.ship_to.attention_name,
|
44
|
+
Phone: {
|
45
|
+
Number: ship_request.ship_to.phone
|
46
|
+
},
|
47
|
+
Address: address_hash(ship_request.ship_to)
|
48
|
+
},
|
49
|
+
ShipFrom: {
|
50
|
+
Name: ship_request.ship_from.company_name || ship_request.ship_from.name,
|
51
|
+
AttentionName: ship_request.ship_from.attention_name,
|
52
|
+
Phone: {
|
53
|
+
Number: ship_request.ship_from.phone
|
54
|
+
},
|
55
|
+
Address: address_hash(ship_request.ship_from)
|
56
|
+
},
|
57
|
+
PaymentInformation: {
|
58
|
+
ShipmentCharge: {
|
59
|
+
Type: "01",
|
60
|
+
BillShipper: {
|
61
|
+
AccountNumber: account_number
|
62
|
+
}
|
63
|
+
}
|
64
|
+
},
|
65
|
+
Service: {
|
66
|
+
Code: ship_request.service_code || "02",
|
67
|
+
Description: "Service Code"
|
68
|
+
},
|
69
|
+
Package: ship_request.packages.map { |pkg| package_hash(pkg) }
|
70
|
+
},
|
71
|
+
LabelSpecification: {
|
72
|
+
LabelImageFormat: {
|
73
|
+
Code: ship_request.label_format || "GIF"
|
74
|
+
},
|
75
|
+
HTTPUserAgent: "UPS Rate Fetch"
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def address_hash(address)
|
82
|
+
{
|
83
|
+
AddressLine: [address.address_line_1, address.address_line_2].compact,
|
84
|
+
City: address.city,
|
85
|
+
StateProvinceCode: address.state,
|
86
|
+
PostalCode: address.postal_code,
|
87
|
+
CountryCode: address.country_code
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def package_hash(package)
|
92
|
+
{
|
93
|
+
Description: package.description || "Package",
|
94
|
+
Packaging: {
|
95
|
+
Code: package.packaging_type || "02"
|
96
|
+
},
|
97
|
+
Dimensions: {
|
98
|
+
UnitOfMeasurement: {
|
99
|
+
Code: package.dimension_unit || "IN"
|
100
|
+
},
|
101
|
+
Length: package.length.to_s,
|
102
|
+
Width: package.width.to_s,
|
103
|
+
Height: package.height.to_s
|
104
|
+
},
|
105
|
+
PackageWeight: {
|
106
|
+
UnitOfMeasurement: {
|
107
|
+
Code: package.weight_unit || "LBS"
|
108
|
+
},
|
109
|
+
Weight: package.weight.to_s
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse_ship_response(response)
|
115
|
+
if response['ShipmentResponse'] && response['ShipmentResponse']['ShipmentResults']
|
116
|
+
results = response['ShipmentResponse']['ShipmentResults']
|
117
|
+
{
|
118
|
+
tracking_number: results['ShipmentIdentificationNumber'],
|
119
|
+
label: results['PackageResults'][0]['ShippingLabel']['GraphicImage'],
|
120
|
+
extension: results['PackageResults'][0]['ShippingLabel']['ImageFormat']['Code'],
|
121
|
+
cost: results['ShipmentCharges']['TotalCharges']['MonetaryValue'].to_f,
|
122
|
+
currency: results['ShipmentCharges']['TotalCharges']['CurrencyCode'],
|
123
|
+
data: response.as_json(except: ["GraphicImage", "HTMLImage"]),
|
124
|
+
alerts: response['ShipmentResponse']['Response']['Alert']&.map{|s| s['Description'] }
|
125
|
+
}
|
126
|
+
else
|
127
|
+
raise APIError, "Invalid shipment response: #{response}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
data/lib/ups/version.rb
ADDED
data/lib/ups.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
require_relative 'ups/version'
|
6
|
+
require_relative 'ups/configuration'
|
7
|
+
require_relative 'ups/client'
|
8
|
+
require_relative 'ups/rating'
|
9
|
+
require_relative 'ups/shipping'
|
10
|
+
require_relative 'ups/models/address'
|
11
|
+
require_relative 'ups/models/package'
|
12
|
+
require_relative 'ups/models/rate_request'
|
13
|
+
require_relative 'ups/models/ship_request'
|
14
|
+
require_relative 'ups/errors'
|
15
|
+
|
16
|
+
module Ups
|
17
|
+
class << self
|
18
|
+
def configuration
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
yield(configuration)
|
24
|
+
end
|
25
|
+
|
26
|
+
def client
|
27
|
+
@client ||= Client.new(configuration)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ups
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- JD Warren
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-06-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: httparty
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.21'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.21'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: webmock
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: vcr
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '6.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '6.0'
|
83
|
+
description: A Ruby gem that provides easy access to UPS shipping rates and label
|
84
|
+
creation using the new OAuth authentication
|
85
|
+
email:
|
86
|
+
- johndavid400@gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- README.md
|
92
|
+
- lib/ups.rb
|
93
|
+
- lib/ups/client.rb
|
94
|
+
- lib/ups/configuration.rb
|
95
|
+
- lib/ups/errors.rb
|
96
|
+
- lib/ups/models/address.rb
|
97
|
+
- lib/ups/models/package.rb
|
98
|
+
- lib/ups/models/rate_request.rb
|
99
|
+
- lib/ups/models/ship_request.rb
|
100
|
+
- lib/ups/rating.rb
|
101
|
+
- lib/ups/shipping.rb
|
102
|
+
- lib/ups/version.rb
|
103
|
+
homepage: https://github.com/johndavid400/ups
|
104
|
+
licenses:
|
105
|
+
- MIT
|
106
|
+
metadata: {}
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
requirements: []
|
122
|
+
rubygems_version: 3.4.19
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: Ruby wrapper for UPS Shipping API
|
126
|
+
test_files: []
|