carousel-ruby-api 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9ea03c1d2f3784982a657f21471e6ad591f34dc9
4
+ data.tar.gz: 3434dcc47b424e2a7058cee5abb74f0b099ec0d2
5
+ SHA512:
6
+ metadata.gz: 73c45e3e3644978a9dcaf03e336d2796c87cb09182171af087ed98977640d63858bf944b779870d4a665c4b9bf706d324fb3bc237ca31421b23d0efe091a2344
7
+ data.tar.gz: 51b60c69a795adee15dc13baad5d5fa8b876c4140baa6a246a4b9af4d58d3c3668fc006fe8f695f8766585e3ac6f7d7fdb494538fd54ca46cbb5b7c300629e79
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/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+
5
+ matrix:
6
+ allow_failures:
7
+ - rvm: rbx-19mode
8
+
9
+ services:
10
+ - redis
11
+
12
+ script:
13
+ - bundle exec rake
14
+
15
+ # addons:
16
+ # code_climate:
17
+ # repo_token: f0884115cadbe82b33756e38a9cd32d5129304746a17126d6c7da02e7a69910a
18
+
19
+ notifications:
20
+ email:
21
+ - justin@jgrubbs.net
22
+
23
+ gemfile:
24
+ - Gemfile
25
+
26
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in carousel.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Justin Grubbs
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Carousel
2
+
3
+ [![Build Status](https://travis-ci.org/jGRUBBS/carousel-ruby-api.svg?branch=master)](https://travis-ci.org/jGRUBBS/carousel-ruby-api)
4
+ [![Code Climate](https://codeclimate.com/github/jGRUBBS/carousel-ruby-api.png)](https://codeclimate.com/github/jGRUBBS/carousel-ruby-api)
5
+
6
+ Ruby library for interfacing with the Carousel Fulfillment API
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'carousel-ruby-api'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install carousel-ruby-api
21
+
22
+ ## Usage
23
+
24
+ Send order request
25
+
26
+ ```ruby
27
+
28
+ order = {
29
+ shipping_address: {
30
+ first_name: "John",
31
+ last_name: "Smith",
32
+ address1: "123 Here Now",
33
+ address2: "2nd Floor",
34
+ city: "New York",
35
+ country: "US",
36
+ zipcode: "10012",
37
+ phone: "123-123-1234"
38
+ },
39
+ billing_address: {
40
+ first_name: "John",
41
+ last_name: "Smith",
42
+ address1: "123 Here Now",
43
+ address2: "2nd Floor",
44
+ city: "New York",
45
+ state: "NY",
46
+ country: "US",
47
+ zipcode: "10012",
48
+ phone: "123-123-1234"
49
+ },
50
+ number: "R123123123",
51
+ orderdate: "2014-06-16",
52
+ line_items: [
53
+ {
54
+ price: "127.23",
55
+ quantity: "1",
56
+ sku: "123332211"
57
+ }
58
+ ],
59
+ gift_message: "Happy B-Day!"
60
+ shipping_method: "next-day", # or "express" or "standard"
61
+ shipping_cost: "20.00",
62
+ invoice_url: "http://example.com/R123123123/invoice"
63
+ }
64
+
65
+ client = Carousel::Client.new("username", "password")
66
+ response = client.send_order_request(order)
67
+
68
+ if response.success?
69
+ # DO SOMETHING
70
+ else
71
+ # QUEUE REQUEST, STORE AND RAISE ERRORS
72
+ end
73
+
74
+ ```
75
+
76
+ Get inventory
77
+
78
+ ```ruby
79
+
80
+ client = Carousel::Client.new("username", "password")
81
+ inventory = client.get_inventory
82
+
83
+ inventory.each do |stock|
84
+ ...
85
+ end
86
+
87
+ ```
88
+
89
+ ## Contributing
90
+
91
+ 1. Fork it
92
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
93
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
94
+ 4. Push to the branch (`git push origin my-new-feature`)
95
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ task :default => :spec
data/carousel.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'carousel/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "carousel-ruby-api"
6
+ spec.version = Carousel::VERSION
7
+ spec.authors = ["Justin Grubbs"]
8
+ spec.email = ["justin@sellect.com"]
9
+ spec.description = %q{Ruby Library for Carousel Fulfillment API}
10
+ spec.summary = %q{This is a library for interfacing with the Carousel Fulfillment API}
11
+ spec.homepage = "http://sellect.com"
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files`.split($/)
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_dependency "builder"
20
+ spec.add_dependency "xml-simple"
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "debugger"
26
+ end
@@ -0,0 +1,114 @@
1
+ require 'net/https'
2
+ require 'xmlsimple'
3
+
4
+ module Carousel
5
+ class Client
6
+
7
+ TEST_HOST = "web.carousel.eu"
8
+ TEST_PATH = "carouselwms"
9
+ LIVE_HOST = "web.carousel.eu"
10
+ LIVE_PATH = "carouselwms"
11
+ PORT = 443
12
+
13
+ attr_accessor :username, :password, :response, :type, :request_uri, :path
14
+
15
+ def initialize(username, password, options = {})
16
+ raise "Username is required" unless username
17
+ raise "Password is required" unless password
18
+
19
+ @username = username
20
+ @password = password
21
+ @options = default_options.merge!(options)
22
+ end
23
+
24
+ def send_order_request(order)
25
+ request = order_request(order)
26
+ @path = build_path(Order::PATH)
27
+ post(request)
28
+ end
29
+
30
+ def get_inventory
31
+ request = Inventory.new(self).build_inventory_request
32
+ @path = build_path(Inventory::PATH)
33
+ post(request).response['stock']
34
+ end
35
+
36
+ def order_request(order)
37
+ @path = build_path(Order::PATH)
38
+ Order.new(self).build_order_request(order)
39
+ end
40
+
41
+ def build_path(path)
42
+ "/#{env_path}/default.asp#{path}"
43
+ end
44
+
45
+ def upcs(inventory)
46
+ inventory.collect { |s| s["stockid"][0] }
47
+ end
48
+
49
+ def mapped_inventory(upcs, inventory)
50
+ inventory.collect do |stock|
51
+ if upcs.include?(stock["stockid"][0])
52
+ { quantity: stock["qty"][0].to_i }
53
+ end
54
+ end.compact
55
+ end
56
+
57
+ def request_uri
58
+ "https://#{host}#{@path}"
59
+ end
60
+
61
+ private
62
+ def default_options
63
+ {
64
+ verbose: true,
65
+ test_mode: false
66
+ }
67
+ end
68
+
69
+ def testing?
70
+ @options[:test_mode]
71
+ end
72
+
73
+ def verbose?
74
+ @options[:verbose]
75
+ end
76
+
77
+ def env_path
78
+ testing? ? TEST_PATH : LIVE_PATH
79
+ end
80
+
81
+ def host
82
+ testing? ? TEST_HOST : LIVE_HOST
83
+ end
84
+
85
+ def log(message)
86
+ return unless verbose?
87
+ puts message
88
+ end
89
+
90
+ def http
91
+ @http ||= Net::HTTP.new(host, PORT)
92
+ end
93
+
94
+ def request(xml_request)
95
+ request = Net::HTTP::Post.new(@path)
96
+ request.body = xml_request
97
+ request.content_type = 'application/xml'
98
+ http.use_ssl = true
99
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
100
+ http.request(request)
101
+ end
102
+
103
+ def post(xml_request)
104
+ response = request(xml_request)
105
+ parse_response(response.body)
106
+ end
107
+
108
+ def parse_response(xml_response)
109
+ log(xml_response)
110
+ @response = Response.new(xml_response, @type)
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,223 @@
1
+ module Carousel
2
+ class Country
3
+
4
+ COUNTRY_MAP = {
5
+ "IE" => "IU", # Eire
6
+ "BA" => "BA", # Bosnia Herzegovina
7
+ "AD" => "AD", # Andorra
8
+ "AE" => "AE", # United Arab Emirates
9
+ "AF" => "AF", # Afghanistan
10
+ "AG" => "AG", # Antigua
11
+ "AI" => "AI", # Anguilla
12
+ "AL" => "AL", # Albania
13
+ "AM" => "AM", # Armenia
14
+ "AN" => "AN", # Netherlands Antilles
15
+ "AO" => "AO", # Angola
16
+ "AR" => "AR", # Argentina
17
+ "AS" => "PU", # American Samoa
18
+ "AT" => "AT", # Austria
19
+ "AU" => "AU", # Australia
20
+ "AW" => "UB", # Aruba
21
+ "AZ" => "AZ", # Azerbaijan
22
+ "BB" => "BB", # Barbados
23
+ "BD" => "BD", # Bangladesh
24
+ "BE" => "BE", # Belgium
25
+ "BF" => "BK", # Burkina
26
+ "BG" => "BG", # Bulgaria
27
+ "BH" => "BH", # Bahrain
28
+ "BI" => "BI", # Burundi
29
+ "BJ" => "BJ", # Benin
30
+ "BM" => "BM", # Bermuda
31
+ "BN" => "BN", # Brunei
32
+ "BO" => "BO", # Bolivia
33
+ "BR" => "BR", # Brazil
34
+ "BS" => "BS", # Bahamas
35
+ "BT" => "BT", # Bhutan
36
+ "BW" => "BW", # Botswana
37
+ "BY" => "BY", # Belarus
38
+ "BZ" => "BZ", # Belize
39
+ "CA" => "CA", # Canada
40
+ "CF" => "CF", # Central African Republic
41
+ "CG" => "CG", # Congo
42
+ "CG" => "CD", # Congo
43
+ "CH" => "CH", # Switzerland
44
+ "CK" => "CK", # Cook Islands
45
+ "CL" => "CL", # Chile
46
+ "CM" => "CM", # Cameroon
47
+ "CN" => "CN", # China
48
+ "CO" => "CO", # Colombia
49
+ "CR" => "CR", # Costa Rica
50
+ "CU" => "CU", # Cuba
51
+ "CV" => "CV", # Cape Verde
52
+ "CY" => "CY", # Cyprus
53
+ "CZ" => "CZ", # Czech Republic
54
+ "DE" => "DE", # Germany
55
+ "DJ" => "DJ", # Djibouti
56
+ "DK" => "DK", # Denmark
57
+ "DM" => "DM", # Dominica
58
+ "DO" => "DO", # Dominican Republic
59
+ "DZ" => "DZ", # Algeria
60
+ "EC" => "EC", # Ecuador inc Galapagos Isl.
61
+ "EE" => "EE", # Estonia
62
+ "EG" => "EG", # Egypt
63
+ "ER" => "ER", # Eritrea
64
+ "ES" => "ES", # Spain
65
+ "ET" => "ET", # Ethiopia
66
+ "FI" => "FI", # Finland
67
+ "FJ" => "FJ", # Fiji
68
+ "FK" => "FK", # Falkland Islands
69
+ "FM" => "FM", # Micronesia
70
+ "FO" => "FO", # Faroe Islands
71
+ "FR" => "FR", # France
72
+ "GA" => "GA", # Gabon
73
+ "GB" => "UK", # United Kingdom
74
+ "GD" => "GD", # Grenada
75
+ "GE" => "GE", # Georgia
76
+ "GF" => "GF", # French Guiana
77
+ "GH" => "GH", # Ghana
78
+ "GI" => "GI", # Gibraltar
79
+ "GL" => "GL", # Greenland
80
+ "GM" => "GM", # Gambia
81
+ "GN" => "GN", # Guinea
82
+ "GP" => "LU", # Guadeloupe
83
+ "GQ" => "GQ", # Equatorial Guinea
84
+ "GR" => "GR", # Greece
85
+ "GT" => "GT", # Guatemala
86
+ "GU" => "GU", # Guam
87
+ "GY" => "GY", # Guyana
88
+ "HK" => "HK", # Hong Kong
89
+ "HN" => "HN", # Honduras
90
+ "HR" => "HR", # Croatia
91
+ "HT" => "HT", # Haiti
92
+ "HU" => "HU", # Hungary
93
+ "ID" => "ID", # Indonesia
94
+ "IL" => "IL", # Israel
95
+ "IN" => "IN", # India
96
+ "IQ" => "IQ", # Iraq
97
+ "IR" => "IR", # Iran
98
+ "IS" => "IS", # Iceland
99
+ "IT" => "IT", # Italy
100
+ "JM" => "JM", # Jamaica
101
+ "JO" => "JO", # Jordan
102
+ "JP" => "JP", # Japan
103
+ "KE" => "KE", # Kenya
104
+ "KG" => "KG", # Kyrgyzstan
105
+ "KH" => "KH", # Cambodia
106
+ "KI" => "KI", # Kiribati
107
+ "KM" => "KM", # Comoros
108
+ "KN" => "KT", # St. Kitts
109
+ "KP" => "FP", # North Korea
110
+ "KR" => "KR", # South Korea
111
+ "KW" => "KW", # Kuwait
112
+ "KY" => "KY", # Cayman Islands
113
+ "KZ" => "KZ", # Kazakhstan
114
+ "LB" => "LB", # Lebanon
115
+ "LC" => "LC", # St Lucia
116
+ "LI" => "LI", # Liechtenstein
117
+ "LK" => "LK", # Sri Lanka
118
+ "LR" => "LY", # Libya
119
+ "LR" => "LR", # Liberia
120
+ "LS" => "LS", # Lesotho
121
+ "LT" => "LT", # Lithuania
122
+ "LU" => "LX", # Luxembourg
123
+ "LV" => "LV", # Latvia
124
+ "MA" => "MA", # Morocco
125
+ "MC" => "MC", # Monaco
126
+ "MD" => "MD", # Moldova
127
+ "MG" => "MG", # Madagascar
128
+ "MH" => "MH", # Marshall Islands
129
+ "MK" => "MK", # Macedonia
130
+ "ML" => "ML", # Mali
131
+ "MN" => "MN", # Mongolia
132
+ "MO" => "MO", # Macao
133
+ "MQ" => "MQ", # Martinique
134
+ "MR" => "MR", # Mauritania
135
+ "MS" => "MS", # Montserrat
136
+ "MT" => "MT", # Malta
137
+ "MU" => "MU", # Mauritius
138
+ "MV" => "MV", # Maldives
139
+ "MW" => "MW", # Malawi
140
+ "MX" => "MX", # Mexico
141
+ "MY" => "MY", # Malaysia
142
+ "MZ" => "MZ", # Mozambique
143
+ "NA" => "NM", # Namibia
144
+ "NC" => "NC", # New Caledonia + Depen.
145
+ "NE" => "NE", # Niger
146
+ "NF" => "OK", # Norfolk Islands
147
+ "NG" => "NG", # Nigeria
148
+ "NI" => "NI", # Nicaragua
149
+ "NL" => "NL", # Netherlands
150
+ "NO" => "NO", # Norway
151
+ "NP" => "NP", # Nepal
152
+ "NR" => "NR", # Nauru
153
+ "NU" => "NU", # Niue
154
+ "NZ" => "NZ", # New Zealand
155
+ "OM" => "OM", # Oman
156
+ "PA" => "PA", # Panama
157
+ "PE" => "PE", # Peru
158
+ "PF" => "PF", # French Polynesia
159
+ "PG" => "PG", # Papua New Guinea
160
+ "PH" => "PH", # Philippines
161
+ "PK" => "PK", # Pakistan
162
+ "PL" => "PD", # Poland
163
+ "PR" => "PR", # Puerto Rico
164
+ "PT" => "PT", # Portugal
165
+ "PY" => "PY", # Paraguay
166
+ "QA" => "QA", # Qatar
167
+ "RE" => "RE", # Reunion
168
+ "RO" => "RO", # Romania
169
+ "RS" => "NT", # Serbia
170
+ "RU" => "RU", # Russia
171
+ "RW" => "RW", # Rwanda
172
+ "SA" => "SA", # Saudi Arabia
173
+ "SB" => "SB", # Solomon Islands
174
+ "SC" => "SC", # Seychelles
175
+ "SD" => "SD", # Sudan
176
+ "SE" => "SE", # Sweden
177
+ "SG" => "SG", # Singapore
178
+ "SI" => "SI", # Slovenia
179
+ "SK" => "VV", # Slovakia
180
+ "SL" => "SL", # Sierra Leone
181
+ "SM" => "SM", # San Marino
182
+ "SN" => "SN", # Senegal
183
+ "SR" => "SR", # Surinam
184
+ "SV" => "SV", # El Salvador
185
+ "SY" => "SY", # Syria
186
+ "SZ" => "SZ", # Swaziland
187
+ "TC" => "TC", # Turks and Caicos Islands
188
+ "TD" => "TD", # Chad
189
+ "TG" => "TG", # Togo
190
+ "TH" => "TH", # Thailand
191
+ "TJ" => "TJ", # Tajikistan
192
+ "TM" => "TM", # Turkmenistan
193
+ "TN" => "TN", # Tunisia
194
+ "TR" => "TR", # Turkey
195
+ "TT" => "TT", # Trinidad and Tobago
196
+ "TV" => "TV", # Tuvalu
197
+ "TW" => "TW", # Taiwan
198
+ "TZ" => "TZ", # Tanzania
199
+ "UA" => "UA", # Ukraine
200
+ "UG" => "UG", # Uganda
201
+ "US" => "US", # United States of America
202
+ "UY" => "UY", # Uruguay
203
+ "UZ" => "UZ", # Uzbekistan
204
+ "VC" => "VC", # St Vincent
205
+ "VE" => "VE", # Venezuela
206
+ "VI" => "VI", # Virgin Islands of USA
207
+ "VN" => "VN", # Vietnam
208
+ "VU" => "VU", # Vanuatu
209
+ "WF" => "WF", # Wallis and Futuna Islands
210
+ "YE" => "YE", # Yemen
211
+ "ZA" => "ZA", # South Africa
212
+ "ZM" => "ZM", # Zambia
213
+ "ZW" => "ZW", # Zimbabwe
214
+ "IM" => "IM", # Isle of Man
215
+ "JE" => "JE" # Jersey
216
+ }
217
+
218
+ def self.map(code)
219
+ COUNTRY_MAP[code] || code
220
+ end
221
+
222
+ end
223
+ end
@@ -0,0 +1,13 @@
1
+ module Carousel
2
+ class Inventory < Request
3
+
4
+ PATH = "?action=stocklines"
5
+
6
+ def build_inventory_request
7
+ construct_xml "stock" do |xml|
8
+ build_user xml
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,51 @@
1
+ module Carousel
2
+ class Order < Request
3
+
4
+ PATH = "?action=order"
5
+
6
+ def build_order_request(order)
7
+ construct_xml "orders" do |xml|
8
+
9
+ xml.order do
10
+
11
+ build_user xml
12
+
13
+ xml.customerref order[:number]
14
+ xml.ordernumber order[:number]
15
+ xml.shippingmethod Shipping.map(order[:shipping_method])
16
+ xml.giftnote order[:gift_message] if order[:gift_message].present?
17
+
18
+ build_address xml, order[:shipping_address], :shipping
19
+ build_address xml, order[:billing_address], :billing
20
+ build_line_items xml, order
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def build_address(xml, address, type)
30
+ prefix = type == :billing ? 'inv' : ''
31
+ xml.tag! "#{prefix}name", "#{address[:first_name]} #{address[:last_name]}"
32
+ xml.tag! "#{prefix}addressline1", address[:address1]
33
+ xml.tag! "#{prefix}addressline2", address[:address2]
34
+ xml.tag! "#{prefix}towncity", address[:city]
35
+ xml.tag! "#{prefix}postcode", address[:zipcode]
36
+ xml.tag! "#{prefix}country", Country.map(address[:country])
37
+ xml.tag! "#{prefix}contactphone", address[:phone]
38
+ end
39
+
40
+ def build_line_items(xml, order)
41
+ order[:line_items].each do |line_item|
42
+ xml.tag! 'orderline', :number => order[:number] do
43
+ xml.ordernumber order[:number]
44
+ xml.sku line_item[:sku]
45
+ xml.qty line_item[:quantity]
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,27 @@
1
+ require 'builder'
2
+
3
+ module Carousel
4
+ class Request
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+
10
+ def construct_xml(type)
11
+ @client.type = type
12
+ xml = ::Builder::XmlMarkup.new :indent => 2
13
+ xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
14
+ xml.tag! type do
15
+ yield(xml) if block_given?
16
+ end
17
+ xml.target!
18
+ end
19
+
20
+
21
+ def build_user(xml)
22
+ xml.user @client.username
23
+ xml.password @client.password
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ module Carousel
2
+ class Response
3
+
4
+ attr_accessor :response
5
+
6
+ def initialize(raw_response, type)
7
+ @raw_response = raw_response
8
+ @response = parse_response(raw_response)
9
+ @type = type
10
+ end
11
+
12
+ def failure?
13
+ !success?
14
+ end
15
+
16
+ def success?
17
+ # FIXME: needs to give actual boolean parsed from response
18
+ # @response.parsed["#{@type}_response"]["#{@type}_result"][:status] == '0001'
19
+ true
20
+ end
21
+
22
+ def parse_response(xml_response)
23
+ return nil if xml_response.blank?
24
+ XmlSimple.xml_in(xml_response)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ module Carousel
2
+ class Shipping
3
+
4
+ SHIPPING_MAP = {
5
+ "next-day" => "ND",
6
+ "express" => "EXPRESS",
7
+ "standard" => "ECONOMY"
8
+ }
9
+
10
+ def self.map(code)
11
+ SHIPPING_MAP[code] || code
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module Carousel
2
+ class Tracking
3
+
4
+ attr_accessor :carrier
5
+
6
+ FEDEX = "http://www.fedexuk.net/accounts/QuickTrack.aspx?consignment=:tracking_number"
7
+ CAROUSEL = "https://web.carousel.eu/easyweb/default.asp?action=clienttrack&type=Carousel&acct1=BEC01&reference=:tracking_number"
8
+
9
+ def initialize(carrier, tracking_number)
10
+ @carrier = carrier
11
+ @tracking_number = tracking_number
12
+ end
13
+
14
+ def carrier_destination
15
+ self.class.const_get(carrier.upcase)
16
+ end
17
+
18
+ def url
19
+ carrier_destination.gsub(':tracking_number', @tracking_number)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Carousel
2
+ VERSION = "0.0.1"
3
+ end
data/lib/carousel.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "carousel/version"
2
+ require "carousel/response"
3
+ require "carousel/client"
4
+ require "carousel/request"
5
+ require "carousel/order"
6
+ require "carousel/inventory"
7
+ require "carousel/country"
8
+ require "carousel/shipping"
9
+ require "carousel/tracking"
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe Carousel::Client do
4
+ before do
5
+ @client = Carousel::Client.new("the_username", "the_password")
6
+ end
7
+
8
+ describe '#send_order_request' do
9
+ it 'should send order request and return parsed respose' do
10
+ Carousel::Order.any_instance.should_receive(:build_order_request).with(order_hash)
11
+ @client.should_receive(:post)
12
+ @client.send_order_request(order_hash)
13
+ end
14
+ end
15
+
16
+ describe 'private#default_options' do
17
+ it 'should return a hash' do
18
+ @client.send(:default_options).should be_a Hash
19
+ end
20
+ end
21
+
22
+ describe 'private#testing?' do
23
+ it 'should return boolean for test mode' do
24
+ @client.send(:testing?).should be_false
25
+ @client.instance_variable_set(:@options, { test_mode: true })
26
+ @client.send(:testing?).should be_true
27
+ end
28
+ end
29
+
30
+ describe 'private#verbose?' do
31
+ it 'should return boolean for verbose mode' do
32
+ @client.send(:verbose?).should be_true
33
+ @client.instance_variable_set(:@options, { verbose: false })
34
+ @client.send(:verbose?).should be_false
35
+ end
36
+ end
37
+
38
+ describe 'private#host' do
39
+ it 'should' do
40
+ @client.stub(:testing?).and_return(false)
41
+ @client.send(:host).should == Carousel::Client::LIVE_HOST
42
+ @client.stub(:testing?).and_return(true)
43
+ @client.send(:host).should == Carousel::Client::TEST_HOST
44
+ end
45
+ end
46
+
47
+ describe 'private#path' do
48
+ it 'should' do
49
+ @client.stub(:testing?).and_return(false)
50
+ @client.send(:path).should == Carousel::Client::LIVE_PATH
51
+ @client.stub(:testing?).and_return(true)
52
+ @client.send(:path).should == Carousel::Client::TEST_PATH
53
+ end
54
+ end
55
+
56
+ describe 'private#log' do
57
+ context 'not in verbose mode' do
58
+ it 'should not log message' do
59
+ @client.stub(:verbose?).and_return(false)
60
+ @client.should_not_receive(:puts)
61
+ @client.send(:log, "message")
62
+ end
63
+ end
64
+ context 'in verbose mode' do
65
+ it 'should' do
66
+ @client.stub(:verbose?).and_return(true)
67
+ @client.should_receive(:puts).with("message")
68
+ @client.send(:log, "message")
69
+ end
70
+ end
71
+ end
72
+
73
+ describe 'private#post' do
74
+ it 'should use net http to post xml request' do
75
+ path = @client.send(:path)
76
+ xml_request = "xml_request"
77
+ response = Carousel::Response.new("<xml>body</xml>", "new_request")
78
+ header = {'Content-Type' => 'text/xml'}
79
+ Net::HTTP.any_instance.should_receive(:post).with(path, xml_request, header).and_return(response)
80
+ @client.should_receive(:parse_response).with(response, nil)
81
+ @client.should_receive(:log).with(response)
82
+ @client.send(:post, xml_request)
83
+ end
84
+ end
85
+
86
+ describe 'private#parse_response' do
87
+ it 'should use xml simple to parse the response' do
88
+ xml_response = "xml_response"
89
+ XmlSimple.should_receive(:xml_in).with(xml_response)
90
+ response = @client.send(:parse_response, xml_response)
91
+ response.should be_a Carousel::Response
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe Carousel::Order do
4
+
5
+ before do
6
+ @client = Carousel::Client.new("the_username", "the_password")
7
+ @order_client = Carousel::Order.new(@client)
8
+ @xml = Builder::XmlMarkup.new
9
+ end
10
+
11
+ describe '#build_order_request' do
12
+ it 'should' do
13
+ response = @order_client.build_order_request(order_hash)
14
+ @client.type.should == "new_order_entry"
15
+ request_body = xml_order_request_string(order_hash)
16
+ response.should == xml_string("new_order_entry", request_body)
17
+ end
18
+ end
19
+
20
+ describe 'private#build_address' do
21
+ it 'should build the proper address xml' do
22
+ type = "CUSTOMER"
23
+ request = @order_client.send(:build_address, @xml, type, order_hash[:shipping_address])
24
+ request.should == xml_address_string(type, order_hash[:shipping_address])
25
+ end
26
+ end
27
+
28
+ describe 'private#build_line_items' do
29
+ it 'should build the proper line items xml' do
30
+ request = @order_client.send(:build_line_items, @xml, order_hash)
31
+ request.should == xml_line_items_string(order_hash)
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Carousel::Request do
4
+
5
+ before do
6
+ @client = Carousel::Client.new("the_username", "the_password")
7
+ end
8
+
9
+ describe '#construct_xml' do
10
+ it 'should build the main xml with header, user, and supplied body' do
11
+ request_type = "new_order_request"
12
+ soap_client = Carousel::Request.new(@client)
13
+ soap_request = soap_client.construct_xml request_type do |xml|
14
+ xml.test "test"
15
+ end
16
+ soap_request.should == xml_string(request_type, "<test>test</test>")
17
+ end
18
+ end
19
+
20
+ describe '#build_header' do
21
+ it 'should build xml header' do
22
+ request_type = "new_order_request"
23
+ soap_client = Carousel::Request.new(@client)
24
+ xml = Builder::XmlMarkup.new
25
+ soap_request = soap_client.build_header(xml, request_type)
26
+ soap_request.should == xml_header_string(request_type)
27
+ end
28
+ end
29
+
30
+ describe '#build_user' do
31
+ it 'should' do
32
+ soap_client = Carousel::Request.new(@client)
33
+ xml = Builder::XmlMarkup.new
34
+ soap_request = soap_client.build_user(xml)
35
+ soap_request.should == xml_user_string
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,129 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+
6
+ require 'rubygems'
7
+ require 'bundler/setup'
8
+ Bundler.require(:default, :development)
9
+ require 'hashie'
10
+ require 'carousel'
11
+
12
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
13
+ RSpec.configure do |config|
14
+ config.treat_symbols_as_metadata_keys_with_true_values = true
15
+ config.run_all_when_everything_filtered = true
16
+ config.filter_run :focus
17
+ config.order = 'random'
18
+ end
19
+
20
+ def xml_string(type, body)
21
+ xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
22
+ xml += "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:a=\"http://www.w3.org/2005/08/addressing\">"
23
+ xml += xml_header_string(type)
24
+ xml += "<s:Body><#{type} xmlns=\"SII\">"
25
+ xml += xml_user_string
26
+ xml += body
27
+ xml += "</#{type}></s:Body></s:Envelope>"
28
+ end
29
+
30
+ def xml_header_string(type)
31
+ xml = "<s:Header><a:Action s:mustUnderstand=\"1\">SII/ISIIService/#{type}</a:Action>"
32
+ xml += "<a:MessageID>urn:uuid:56b55a70-8bbc-471d-94bb-9ca060bcf99f</a:MessageID>"
33
+ xml += "<a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address></a:ReplyTo>"
34
+ xml += "<a:To s:mustUnderstand=\"1\">https://www.carousel.us/webservices/SIIService.svc</a:To></s:Header>"
35
+ end
36
+
37
+ def xml_user_string
38
+ xml = "<user xmlns:b=\"http://schemas.datacontract.org/2004/07/\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"
39
+ xml += "<b:user_name>&lt;![CDATA[the_username]]&gt;</b:user_name><b:user_password>&lt;![CDATA[the_password]]&gt;</b:user_password></user>"
40
+ end
41
+
42
+ def xml_address_string(type, address)
43
+ full_name = "#{address[:first_name]} #{address[:last_name]}"
44
+ xml = "<b:#{type}_NAME>&lt;![CDATA[#{full_name}]]&gt;</b:#{type}_NAME>"
45
+ xml += "<b:#{type}_ADDRESS_1>&lt;![CDATA[#{address[:address1]}]]&gt;</b:#{type}_ADDRESS_1>"
46
+ xml += "<b:#{type}_ADDRESS_2>&lt;![CDATA[#{address[:address2]}]]&gt;</b:#{type}_ADDRESS_2>"
47
+ xml += "<b:#{type}_ADDRESS_3>&lt;![CDATA[#{address[:address3]}]]&gt;</b:#{type}_ADDRESS_3>"
48
+ xml += "<b:#{type}_ADDRESS_CITY>#{address[:city]}</b:#{type}_ADDRESS_CITY>"
49
+ xml += "<b:#{type}_ADDRESS_COUNTRY>#{address[:country]}</b:#{type}_ADDRESS_COUNTRY>"
50
+ xml += "<b:#{type}_ADDRESS_STATE>#{address[:state]}</b:#{type}_ADDRESS_STATE>"
51
+ xml += "<b:#{type}_ADDRESS_ZIP>#{address[:zipcode]}</b:#{type}_ADDRESS_ZIP>"
52
+ xml += "<b:#{type}_TELEPHONE>#{address[:phone]}</b:#{type}_TELEPHONE>"
53
+ end
54
+
55
+ def xml_line_items_string(order)
56
+ xml = "<b:Order_Details>"
57
+ order[:line_items].each do |line_item|
58
+ xml += "<b:Order_Detail_New>"
59
+ xml += "<b:PRICE>#{line_item[:price]}</b:PRICE>"
60
+ xml += "<b:QUANTITY>#{line_item[:quantity]}</b:QUANTITY>"
61
+ xml += "<b:SIZE>#{line_item[:size]}</b:SIZE>"
62
+ xml += "<b:sku>#{line_item[:sku]}</b:sku>"
63
+ xml += "</b:Order_Detail_New>"
64
+ end
65
+ xml += "</b:Order_Details>"
66
+ end
67
+
68
+ def xml_order_request_string(order)
69
+ xml = "<ohn xmlns:b=\"http://schemas.datacontract.org/2004/07/\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">"
70
+ xml += "<b:SERVICE>90</b:SERVICE>"
71
+ xml += "<b:USER1>&lt;![CDATA[http://example.com/R123123123/invoice]]&gt;</b:USER1>"
72
+ xml += "<b:CARRIER>FEDEX</b:CARRIER>"
73
+ xml += "<b:CUSTOMER_CODE/><b:DELIVERY_GIFT_WRAP/>"
74
+ xml += "<b:DELIVERY_MESSAGE>&lt;![CDATA[Happy Birthday!]]&gt;</b:DELIVERY_MESSAGE>"
75
+ xml += "<b:EMAIL>someone@somehwere.com</b:EMAIL>"
76
+ xml += "<b:ORDER_ID>R123123123</b:ORDER_ID>"
77
+ xml += "<b:ORDER_TYPE>OO</b:ORDER_TYPE>"
78
+ xml += "<b:DELIVERY_FROM i:nil=\"true\"/><b:DELIVERY_TO i:nil=\"true\"/>"
79
+ xml += "<b:DELIVERY_ID i:nil=\"true\"/><b:FREIGHT_ACCOUNT i:nil=\"true\"/>"
80
+ xml += xml_address_string("CUSTOMER", order[:billing_address])
81
+ xml += xml_address_string("DELIVERY", order[:shipping_address])
82
+ xml += xml_line_items_string(order)
83
+ xml += "</ohn>"
84
+ end
85
+
86
+ def order_hash
87
+ {
88
+ carrier: "FEDEX",
89
+ billing_address: {
90
+ first_name: "John",
91
+ last_name: "Smith",
92
+ address1: "123 Here Now",
93
+ address2: "2nd Floor",
94
+ address3: "",
95
+ city: "New York",
96
+ state: "New York",
97
+ country: "US",
98
+ zipcode: "10012",
99
+ phone: "123-123-1234"
100
+ },
101
+ shipping_address: {
102
+ first_name: "John",
103
+ last_name: "Smith",
104
+ address1: "123 Here Now",
105
+ address2: "2nd Floor",
106
+ address3: "",
107
+ city: "New York",
108
+ state: "New York",
109
+ country: "US",
110
+ zipcode: "10012",
111
+ phone: "123-123-1234"
112
+ },
113
+ gift_wrap: "true",
114
+ gift_message: "Happy Birthday!",
115
+ email: "someone@somehwere.com",
116
+ number: "R123123123",
117
+ type: "OO",
118
+ line_items: [
119
+ {
120
+ price: "127.23",
121
+ quantity: "1",
122
+ sku: "123332211",
123
+ size: "XS"
124
+ }
125
+ ],
126
+ shipping_code: "90",
127
+ invoice_url: "http://example.com/R123123123/invoice"
128
+ }
129
+ end
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: carousel-ruby-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Justin Grubbs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: builder
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: xml-simple
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: debugger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Ruby Library for Carousel Fulfillment API
98
+ email:
99
+ - justin@sellect.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - .gitignore
105
+ - .rspec
106
+ - .travis.yml
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - carousel.gemspec
112
+ - lib/carousel.rb
113
+ - lib/carousel/client.rb
114
+ - lib/carousel/country.rb
115
+ - lib/carousel/inventory.rb
116
+ - lib/carousel/order.rb
117
+ - lib/carousel/request.rb
118
+ - lib/carousel/response.rb
119
+ - lib/carousel/shipping.rb
120
+ - lib/carousel/tracking.rb
121
+ - lib/carousel/version.rb
122
+ - spec/lib/client_spec.rb
123
+ - spec/lib/order_spec.rb
124
+ - spec/lib/soap_spec.rb
125
+ - spec/spec_helper.rb
126
+ homepage: http://sellect.com
127
+ licenses:
128
+ - MIT
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - '>='
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 2.0.6
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: This is a library for interfacing with the Carousel Fulfillment API
150
+ test_files:
151
+ - spec/lib/client_spec.rb
152
+ - spec/lib/order_spec.rb
153
+ - spec/lib/soap_spec.rb
154
+ - spec/spec_helper.rb