carousel-ruby-api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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