ontrac-web-services 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ontrac-web-services.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Brian Abreu
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # ontrac-web-services
2
+ ## Description
3
+ This gem provides an interface to the OnTrac web services API. It interfaces with its HTTP/POST API to generate labels (looking up shipping rates and tracking coming soon).
4
+
5
+ ## Examples
6
+ ### Creating a shipment with multiple packages
7
+
8
+ ```ruby
9
+ require 'ontrac'
10
+
11
+ include ::Ontrac::WebServices
12
+ include ::Ontrac::WebServices::Definitions
13
+
14
+ credentials = Service::Credentials.new("ACCOUNT #", "PASSWORD", "production")
15
+ service = Service.new(credentials)
16
+
17
+ shipper = ShipperData.new
18
+ shipper.name = "Fulfillment Circle"
19
+ shipper.address = "343 third street"
20
+ shipper.suite = "suite 17"
21
+ shipper.city = "sparks"
22
+ shipper.state = "nv"
23
+ shipper.zip = "89434"
24
+ shipper.phone = "(415) 350-2608"
25
+
26
+ recipient = DeliveryData.new
27
+ recipient.name = "Joe Shmoe"
28
+ recipient.address = "123 4th St"
29
+ recipient.address2 = "Suite 315"
30
+ recipient.city = "San Luis Obispo"
31
+ recipient.state = "CA"
32
+ recipient.zip = "93401"
33
+ recipient.phone = "(805) 555-1234"
34
+
35
+ responses = service.request_shipment(SERVICE_TYPE_GROUND, shipper, recipient, LABEL_TYPE_PDF,
36
+ [ 22.0, 15, 10 ]) do |package_data, package_num|
37
+
38
+ package_data.package_detail.residential = false
39
+ package_data.package_detail.reference = "order #1234"
40
+ end
41
+
42
+ tracking_numbers = responses.map do |(tracking_number, label, charge)|
43
+ puts "tracking number: #{tracking_number}"
44
+ puts "charge: #{charge.to_f}"
45
+ File.open("#{tracking_number}.pdf", "w") { |f| f << label }
46
+ tracking_number
47
+ end
48
+ ```
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/lib/ontrac.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "ontrac/version"
2
+ require "ontrac/web_services.rb"
@@ -0,0 +1,3 @@
1
+ module Ontrac
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,5 @@
1
+ module Ontrac::WebServices
2
+ end
3
+
4
+ require 'ontrac/web_services/definitions'
5
+ require 'ontrac/web_services/service'
@@ -0,0 +1,24 @@
1
+ module Ontrac::WebServices
2
+ module Definitions
3
+ SERVICE_TYPE_SUNRISE = 'S'
4
+ SERVICE_TYPE_GOLD = 'G'
5
+ SERVICE_TYPE_GROUND = 'C'
6
+ SERVICE_TYPE_FREIGHT = 'H'
7
+
8
+ LABEL_TYPE_PDF = 1
9
+ LABEL_TYPE_JPG = 2
10
+ LABEL_TYPE_BMP = 3
11
+ LABEL_TYPE_GIF = 4
12
+ LABEL_TYPE_EPL_4X3 = 5
13
+ LABEL_TYPE_EPL_4X5 = 6
14
+ LABEL_TYPE_ZPL_4X5 = 7
15
+ end
16
+ end
17
+
18
+ require 'ontrac/web_services/definitions/base'
19
+ require 'ontrac/web_services/definitions/delivery_data'
20
+ require 'ontrac/web_services/definitions/package'
21
+ require 'ontrac/web_services/definitions/package_data'
22
+ require 'ontrac/web_services/definitions/package_detail'
23
+ require 'ontrac/web_services/definitions/shipment_request'
24
+ require 'ontrac/web_services/definitions/shipper_data'
@@ -0,0 +1,43 @@
1
+ require 'active_support'
2
+ require 'nokogiri'
3
+
4
+ module Ontrac::WebServices::Definitions
5
+ class Base
6
+ include Nokogiri
7
+
8
+ def self.attr_accessor(*args)
9
+ (@attributes ||= []).concat(args)
10
+ super
11
+ end
12
+
13
+ def self.attributes
14
+ return (@attributes ||= [])
15
+ end
16
+
17
+ def self.basename
18
+ ActiveSupport::Inflector.camelize(self.name.gsub(/^.*::/, ''))
19
+ end
20
+
21
+ def to_xml
22
+ xml_builder = Nokogiri::XML::Builder.new
23
+
24
+ xml_builder.send(self.class.basename) do |xml|
25
+ self.class.attributes.each do |attribute_name|
26
+ (values = *self.send(attribute_name)).each do |attribute_value|
27
+ xml_attr_name = respond_to?("#{attribute_name}_to_xml_name") ?
28
+ send("#{attribute_name}_to_xml_name") :
29
+ ActiveSupport::Inflector.camelize(attribute_name, false)
30
+
31
+ if (attribute_value.is_a?(Base))
32
+ xml.doc.root << attribute_value.to_xml
33
+ else
34
+ xml.send(xml_attr_name, attribute_value)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ xml_builder.doc.root
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ module Ontrac
2
+ module WebServices
3
+ module Definitions
4
+ class DeliveryData < Base
5
+ attr_accessor :name
6
+ attr_accessor :address
7
+ attr_accessor :address2
8
+ attr_accessor :address3
9
+ attr_accessor :city
10
+ attr_accessor :state
11
+ attr_accessor :zip
12
+ attr_accessor :phone
13
+ attr_accessor :contact
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ # [ DeliveryData, Package, PackageData, PackageDetail, ShipmentRequest, ShipperData ].map do |cls|
20
+ # accessors = cls.attributes.map { |a| " attr_accessor :#{a.to_s.underscore}" }
21
+ # <<-CLSDEF
22
+ # module Ontrac
23
+ # module WebServices
24
+ # module Definitions
25
+ # class #{cls.name.split("::").last} < Base
26
+ # #{accessors * "\n"}
27
+ # end
28
+ # end
29
+ # end
30
+ # end
31
+ # CLSDEF
32
+ # end
@@ -0,0 +1,9 @@
1
+ module Ontrac
2
+ module WebServices
3
+ module Definitions
4
+ class Package < Base
5
+ attr_accessor :package_data
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Ontrac
2
+ module WebServices
3
+ module Definitions
4
+ class PackageData < Base
5
+ attr_accessor :uid
6
+ attr_accessor :shipper_data
7
+ attr_accessor :delivery_data
8
+ attr_accessor :package_detail
9
+
10
+ def uid_to_xml_name
11
+ "UID"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ module Ontrac
2
+ module WebServices
3
+ module Definitions
4
+ class PackageDetail < Base
5
+ attr_accessor :ship_date
6
+ attr_accessor :reference
7
+ attr_accessor :tracking
8
+ attr_accessor :service
9
+ attr_accessor :declared
10
+ attr_accessor :cod
11
+ attr_accessor :cod_type
12
+ attr_accessor :saturday_delivery
13
+ attr_accessor :signature_rqd
14
+ attr_accessor :type
15
+ attr_accessor :weight
16
+ attr_accessor :bill_to
17
+ attr_accessor :instructions
18
+ attr_accessor :ship_email
19
+ attr_accessor :del_email
20
+ attr_accessor :label_type
21
+ attr_accessor :residential
22
+
23
+ def cod_type_to_xml_name
24
+ "cod_Type"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ module Ontrac
2
+ module WebServices
3
+ module Definitions
4
+ class ShipmentRequest < Base
5
+ attr_accessor :account
6
+ attr_accessor :password
7
+ attr_accessor :request_reference
8
+ attr_accessor :packages
9
+
10
+ def self.basename
11
+ "OntracShipmentRequest"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Ontrac
2
+ module WebServices
3
+ module Definitions
4
+ class ShipperData < Base
5
+ attr_accessor :name
6
+ attr_accessor :address
7
+ attr_accessor :suite
8
+ attr_accessor :city
9
+ attr_accessor :state
10
+ attr_accessor :zip
11
+ attr_accessor :phone
12
+ attr_accessor :contact
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,118 @@
1
+ require 'net/https'
2
+ require 'nokogiri'
3
+ require 'base64'
4
+
5
+ module Ontrac::WebServices
6
+ class ServiceException < RuntimeError
7
+ attr_accessor :details
8
+ end
9
+
10
+ class Service
11
+ Credentials = Struct.new(:account, :password, :environment)
12
+
13
+ def initialize(credentials)
14
+ @credentials = credentials
15
+ end
16
+
17
+ def service_url(request_name)
18
+ (@credentials.environment.to_sym == :production) ?
19
+ "https://www.shipontrac.net/OnTracAPI/#{request_name}.ashx" :
20
+ "https://www.shipontrac.net/OnTracAPItest/#{request_name}.ashx"
21
+ end
22
+
23
+ def request_shipment(service, shipper_data, delivery_data,
24
+ label_type, package_weights, &process_contents)
25
+
26
+ label_base64_encoded =
27
+ label_type != Definitions::LABEL_TYPE_EPL_4X3 &&
28
+ label_type != Definitions::LABEL_TYPE_EPL_4X5
29
+
30
+ request = Definitions::ShipmentRequest.new
31
+ request.account = @credentials.account
32
+ request.password = @credentials.password
33
+
34
+ request.packages = package_weights.map.with_index do |weight, ndx|
35
+ Definitions::Package.new.tap do |package|
36
+ package.package_data = Definitions::PackageData.new.tap do |data|
37
+ data.shipper_data = shipper_data
38
+ data.delivery_data = delivery_data
39
+
40
+ data.package_detail = Definitions::PackageDetail.new.tap do |detail|
41
+ detail.service = service
42
+ detail.weight = weight
43
+ detail.ship_email = ""
44
+ detail.del_email = ""
45
+ detail.label_type = label_type
46
+ end
47
+ end
48
+
49
+ process_contents.call(package.package_data, ndx) if (process_contents)
50
+ end
51
+ end
52
+
53
+ package_data_path = "/OnTracShipmentResponse/Package/PackageData"
54
+ num_packages = package_weights.size
55
+
56
+ request_xml = Nokogiri::XML::Document.new
57
+ request_xml << request.to_xml
58
+
59
+ issue_request("ShipmentRequest", request_xml) do |response_xml|
60
+ %w(Label Tracking Charges/TotalCharge).each do |node_name|
61
+ count = response_xml.xpath("count(#{package_data_path}/#{node_name})")
62
+ if (response_xml.xpath("count(#{package_data_path}/#{node_name})") != num_packages)
63
+ raise "Expected #{num_packages} #{node_name} response(s), received #{count}"
64
+ end
65
+ end
66
+
67
+ response_xml.xpath("/OnTracShipmentResponse/Package/PackageData").map do |xml|
68
+ label_img = xml.xpath("Label").text
69
+ label_img = Base64.decode64(label_img) if (label_base64_encoded)
70
+
71
+ [
72
+ xml.xpath("Tracking").text,
73
+ label_img,
74
+ Float(xml.xpath("Charges/TotalCharge").text),
75
+ xml
76
+ ]
77
+ end
78
+ end
79
+ end
80
+
81
+ private
82
+ def issue_request(request_type, request_xml, &process_response)
83
+ uri = URI.parse(service_url(request_type))
84
+ http = Net::HTTP.new(uri.host, uri.port)
85
+ http.use_ssl = true
86
+
87
+ response = http.request_post(uri.request_uri, request_xml.to_s)
88
+ response_xml = Nokogiri::XML(response.body)
89
+
90
+ check_response(response_xml)
91
+ process_response.call(response_xml)
92
+ rescue Timeout::Error, SocketError => root_exception
93
+ err = ServiceException.new("Network communication error")
94
+ err.set_backtrace([ "#{__FILE__}:#{__LINE__ + 1}", *root_exception.backtrace ])
95
+ raise err
96
+ rescue Exception => root_exception
97
+ err = ServiceException.new(root_exception.message)
98
+ err.details = { request: request_xml, response: response_xml }
99
+ err.set_backtrace([ "#{__FILE__}:#{__LINE__ + 1}", *root_exception.backtrace ])
100
+ raise err
101
+ end
102
+
103
+ def check_response(response_xml)
104
+ status = response_xml.xpath("/OnTracShipmentResponse/Status").text
105
+ message = response_xml.xpath("/OnTracShipmentResponse/Message").text
106
+ package_messages = response_xml.xpath("//PackageData/Message")
107
+
108
+ if (status.empty? || status != "1" || response_xml.xpath("//PackageData/Status != 1"))
109
+ msg = "No status information was returned for the request" if (status.empty?)
110
+ msg = message || "The request returned a status of #{status}" if (status != "1")
111
+
112
+ msg << " -- " + [ *package_messages.to_a ] * " ; " if (!package_messages.empty?)
113
+
114
+ raise msg
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/ontrac/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Brian Abreu"]
6
+ gem.email = ["brian@nuts.com"]
7
+ gem.description = %q{Provies an interface to the OnTrac web services API}
8
+ gem.summary = %q{Interfaces with the OnTrac web services API to look up shipping rates, generate labels, and track shipments}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "ontrac-web-services"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Ontrac::VERSION
17
+
18
+ gem.required_ruby_version = '>= 1.9.0'
19
+ gem.add_dependency("activesupport")
20
+ gem.add_dependency("nokogiri")
21
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ontrac-web-services
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Abreu
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Provies an interface to the OnTrac web services API
47
+ email:
48
+ - brian@nuts.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - lib/ontrac.rb
59
+ - lib/ontrac/version.rb
60
+ - lib/ontrac/web_services.rb
61
+ - lib/ontrac/web_services/definitions.rb
62
+ - lib/ontrac/web_services/definitions/base.rb
63
+ - lib/ontrac/web_services/definitions/delivery_data.rb
64
+ - lib/ontrac/web_services/definitions/package.rb
65
+ - lib/ontrac/web_services/definitions/package_data.rb
66
+ - lib/ontrac/web_services/definitions/package_detail.rb
67
+ - lib/ontrac/web_services/definitions/shipment_request.rb
68
+ - lib/ontrac/web_services/definitions/shipper_data.rb
69
+ - lib/ontrac/web_services/service.rb
70
+ - ontrac-web-services.gemspec
71
+ homepage: ''
72
+ licenses: []
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 1.9.0
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubyforge_project:
91
+ rubygems_version: 1.8.23
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Interfaces with the OnTrac web services API to look up shipping rates, generate
95
+ labels, and track shipments
96
+ test_files: []