walmart_open 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.
Files changed (45) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +171 -0
  4. data/Rakefile +1 -0
  5. data/lib/walmart_open.rb +6 -0
  6. data/lib/walmart_open/auth_token.rb +25 -0
  7. data/lib/walmart_open/client.rb +48 -0
  8. data/lib/walmart_open/commerce_request.rb +23 -0
  9. data/lib/walmart_open/config.rb +41 -0
  10. data/lib/walmart_open/connection_manager.rb +47 -0
  11. data/lib/walmart_open/errors.rb +13 -0
  12. data/lib/walmart_open/item.rb +41 -0
  13. data/lib/walmart_open/order.rb +53 -0
  14. data/lib/walmart_open/order_item.rb +17 -0
  15. data/lib/walmart_open/order_results.rb +40 -0
  16. data/lib/walmart_open/order_xml_builder.rb +62 -0
  17. data/lib/walmart_open/ordered_item.rb +31 -0
  18. data/lib/walmart_open/product_request.rb +28 -0
  19. data/lib/walmart_open/request.rb +64 -0
  20. data/lib/walmart_open/requests/lookup.rb +27 -0
  21. data/lib/walmart_open/requests/place_order.rb +58 -0
  22. data/lib/walmart_open/requests/search.rb +19 -0
  23. data/lib/walmart_open/requests/taxonomy.rb +17 -0
  24. data/lib/walmart_open/requests/token.rb +30 -0
  25. data/lib/walmart_open/search_results.rb +25 -0
  26. data/lib/walmart_open/shipping_address.rb +36 -0
  27. data/lib/walmart_open/version.rb +3 -0
  28. data/spec/spec_helper.rb +16 -0
  29. data/spec/walmart_open/auth_token_spec.rb +53 -0
  30. data/spec/walmart_open/client_spec.rb +92 -0
  31. data/spec/walmart_open/config_spec.rb +35 -0
  32. data/spec/walmart_open/connection_manager_spec.rb +58 -0
  33. data/spec/walmart_open/item_spec.rb +52 -0
  34. data/spec/walmart_open/order_item_spec.rb +18 -0
  35. data/spec/walmart_open/order_results_spec.rb +83 -0
  36. data/spec/walmart_open/order_spec.rb +124 -0
  37. data/spec/walmart_open/request_spec.rb +79 -0
  38. data/spec/walmart_open/requests/lookup_spec.rb +91 -0
  39. data/spec/walmart_open/requests/place_order_spec.rb +149 -0
  40. data/spec/walmart_open/requests/search_spec.rb +86 -0
  41. data/spec/walmart_open/requests/taxonomy_spec.rb +69 -0
  42. data/spec/walmart_open/requests/token_spec.rb +49 -0
  43. data/spec/walmart_open/search_results_spec.rb +60 -0
  44. data/spec/walmart_open/shipping_address_spec.rb +63 -0
  45. metadata +204 -0
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MGFiOWIwMDA1YjYxYjY3Yjc4OWM2OTlhZmU3ZjlkNDA2MDIwZmQ0Mw==
5
+ data.tar.gz: !binary |-
6
+ NTMzNWFhODAxZDRkNWRmNTE4MzNkYTVlYTI4NjdmNjc1YTAxY2U5Yg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NzNlNDhmYzg0YTgzOGY0ZTE5MTJiYmM3YzQwMDk1YzQxNTZhNWJiMmQ3MDNh
10
+ MjFkMTI1ZTExYjI4ZWQzZGI3MjYxMWE4MmQ2NmJiMWFmY2JjNTdmYTBkOTkz
11
+ YWNmMjU3ODAyN2I0M2FlMTYzYTY4ODA4MDQxMmNhMDA2ZGVkMGY=
12
+ data.tar.gz: !binary |-
13
+ NmMzZDZkZjQ5ODZlZTFmN2FiNWM0NWI2MTdmODI5YzA0YWJjZGRhOTg0Nzlm
14
+ YWEyODBmYThlNmM1NTY1ZTkxZjM4MmY3NTFlODIzZDUwZDkzYzRlMTEzZjc1
15
+ Y2QxNjA2ODgwOTExNmFmZGY1ZWI2NjViZTFiN2FjMDQ5NjkxOTk=
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ngan Pham
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,171 @@
1
+ # Walmart Open
2
+
3
+ [Walmart Open](https://developer.walmartlabs.com) is Walmart's official products search and ordering API. There are actually two distinct API endpoints: one for product search and lookup and one for ordering.
4
+
5
+ ## Products API
6
+
7
+ To use the Products API, you must register for a Walmart Open account and obtain an API key. You can also API calls from their [interacive API tool](https://developer.walmartlabs.com/io-docs). For documentation can be found here: https://developer.walmartlabs.com/docs.
8
+
9
+ ## Commerce API
10
+
11
+ As of right now, the Commerce API is [only available to select partners](https://developer.walmartlabs.com/forum/read/162059). Once you've established a relationship with them, you will need the following to access the Commerce API:
12
+ - API Key
13
+ - API Secret
14
+ - Generated Private/Public Key for message signing. You will need to give them your public key before proceeding.
15
+ - A billing record id; Provided to you by Walmart when you give them your public key.
16
+ - A [walmart.com](http://www.walmart.com) account that is connected to your Walmart Open account.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ gem 'walmart_open'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install walmart_open
31
+
32
+ ## Usage
33
+
34
+ ### Configuring the Client
35
+ ```ruby
36
+ require "walmart_open"
37
+
38
+ client = WalmartOpen::Client.new do |config|
39
+ ## Product API
40
+ config.product_api_key = "GggS6aPxDteRCyRxYnqz9bPp"
41
+
42
+ # This value defaults to 5.
43
+ config.product_calls_per_second = 4
44
+
45
+ ## Commerce API
46
+ config.commerce_api_key = "7XQLSmhqTXJHQ5xdGG7ZUh8d"
47
+ config.commerce_api_secret = "Mm5BX4c7QC"
48
+ config.commerce_private_key = File.read("/path/to/your/private/key/file")
49
+
50
+ # This value defaults to 2.
51
+ config.commerce_calls_per_second = 1
52
+
53
+ # Set this to true for development mode.
54
+ # This mainly applies to the Commerce API; orders will not actually be placed.
55
+ config.debug = true
56
+ end
57
+ ```
58
+
59
+ ### Making Product API Calls
60
+ ```ruby
61
+ # Search
62
+ res = client.search("ipod")
63
+ #=> SearchResults
64
+ # example of res
65
+ # res.query = "ipod"
66
+ # res.total = 53259
67
+ # res.start = 1
68
+ # res.page = ?
69
+ # res.items = [ Item_1, Item_2 ....]
70
+
71
+ # Lookup (by item id)
72
+ item = client.lookup(15076191)
73
+ #=> Item
74
+ # when success: example of item
75
+ # item.error? == false
76
+ # item.id = "15076191"
77
+ # item.name = "Apple iPod Touch 4th Generation 32GB with Bonus Accessory Kit"
78
+ # item.price = "189.0"
79
+ # item.upc = nil
80
+ # item.category_node = "3944_96469_164001"
81
+ # item.short_description = "The world's most popular portable gaming device ... "
82
+ # item.long_description = "<br><b>Apple iPod touch 32GB (4th Gen) ..."
83
+ # item.brand = nil
84
+ # item.shipping_rate = 0.0
85
+ # item.size = nil
86
+ # item.color = "Black"
87
+ # item.model_number = ?
88
+ # item.url = "http://www.walmart.com/ip/Apple-iPod-Touch-8GB-32GB-and-64GB-newest-model/15076191"
89
+ # item.raw_attributes = {"itemId" => 15076191, .....}
90
+
91
+ # when fail: example of item
92
+ # item.error? == true
93
+ # item.error[:code] = 4002
94
+ # item.error[:message] = "Invalid itemId"
95
+ # item.raw_attributes = {"errors"=>[{"code"=>4002, "message"=>"Invalid itemId"}]}
96
+
97
+ # Taxonomy
98
+ taxonomies = client.taxonomy
99
+ #=> Array
100
+ # when success, example of one of taxonomies
101
+ # taxonomies.categories = {"id"=>"5438", "name"=>"Apparel", "path"=>"Apparel", "children"=>[{"id"=>"5438_426265",
102
+ "name"=>"Accessories", "path"=>"Apparel/Accessories", "children"=>[{"id"=>"5438_426265_1043621",
103
+ "name"=>"Bandanas", "path"=>"Apparel/Accessories/Bandanas"}, ...]]}
104
+ ```
105
+
106
+ ### Making Commerce API Calls
107
+ ```ruby
108
+ # Placing an Order
109
+ order = WalmartOpen::Order.new({billing_id: 1, first_name: "James",
110
+ last_name: "Fong", partner_order_id: "38",
111
+ partner_order_time: "16:15:47"})
112
+ # required fields:
113
+ # billing_id: long, first_name: string
114
+ # optional fields:
115
+ # last_name: string
116
+ # partner_order_time: string in format of HH:MM:SS, default to Time.now
117
+ # partner_order_id: string, default to "Order-#{HASH_OF_RAND_AND_ORDER_TIME}"
118
+
119
+ order.add_shipping_address({street1: "200 Blossom Ln", street2: "100 Flynn Avenue", city: "Mountain View", state: "CA", zipcode: "94043", country: "USA"})
120
+ # required:
121
+ # street1: string, city: string, state: string, zipcode: string, country: string
122
+ # optional:
123
+ # street2: string
124
+
125
+ # add_item method 1, add_item(item_id, item_price, shipping_price = nil, quantity = 1)
126
+ order.add_item(10371356, 5, 22.94)
127
+ order.add_item(25174174, 214.99)
128
+
129
+ # add_item method 2, add_item(item, quantity = 1)
130
+ order.add_item(client.lookup(10371356))
131
+ order.add_item(client.lookup(25174174))
132
+
133
+ # order.valid? == true
134
+
135
+ res = client.order(order)
136
+ #=> OrderResults
137
+ # when order succeeds, we see res as example below
138
+ # res.error? = true
139
+ # res.order_id = "2677911409503"
140
+ # res.partner_order_id = "13"
141
+ # res.items = [ ordered_item, ...]
142
+ # res.total = "237.96" # sum of ordered_item.price for all ordered_items
143
+ # res.sales_tax = "20.82"
144
+ # res.surcharge = "0.0"
145
+ # res.raw_attributes = {"response"=>{"orderId"=>"2677914395453", "partnerOrderId"=>"41", "items"=>{"item"=>{"itemId"=>"10371356", "quantity"=>"1", "itemPrice"=>"22.97"}}, "total"=>"29.95", "itemTotal"=>"22.97", "shipping"=>"4.97", "salesTax"=>"2.01", "surcharge"=>"0.00"}}
146
+
147
+ # Note that order_item have class OrderedItem. As an example
148
+ # ordered_item.id = 25174174
149
+ # ordered_item.quantity = 1
150
+ # ordered_item.price = 214.99
151
+ # ordered_item.raw_attributes = {"itemId"=>"25174174", "quantity"=>"1", "itemPrice"=>"214.99"}
152
+
153
+ # when order fails, we see res as below
154
+ # res.error? == true
155
+ # res.error = {:code=>"10001", :message=>"Invalid xml"}
156
+ # res.error[:code] = "10001"
157
+ # res.error[:message] = "Invalid xml"
158
+ # res.raw_attributes = {"errors"=>{"error"=>{"code"=>"10001", "message"=>"Invalid xml"}}}
159
+ ```
160
+
161
+ ### Authentication failure
162
+ In the case of authentication failure during an API call, a
163
+ WalmartOpen::AuthenticationError exception will be raised
164
+
165
+ ## Contributing
166
+
167
+ 1. Fork it
168
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
169
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
170
+ 4. Push to the branch (`git push origin my-new-feature`)
171
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ require "walmart_open/version"
2
+ require "walmart_open/client"
3
+ require "walmart_open/errors"
4
+
5
+ module WalmartOpen
6
+ end
@@ -0,0 +1,25 @@
1
+ module WalmartOpen
2
+ class AuthToken
3
+ BUFFER_TIME = 30 # seconds
4
+
5
+ attr_reader :token_type,
6
+ :access_token,
7
+ :time,
8
+ :expiration_time
9
+
10
+ def initialize(attrs, grant_time = Time.now)
11
+ @expiration_time = grant_time + attrs["expires_in"]
12
+ @token_type = attrs["token_type"]
13
+ @access_token = attrs["access_token"]
14
+ @time = grant_time
15
+ end
16
+
17
+ def expired?
18
+ Time.now + BUFFER_TIME >= expiration_time
19
+ end
20
+
21
+ def authorization_header
22
+ "#{token_type.capitalize} #{access_token}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ require "walmart_open/config"
2
+ require "walmart_open/connection_manager"
3
+ require "walmart_open/requests/search"
4
+ require "walmart_open/requests/lookup"
5
+ require "walmart_open/requests/taxonomy"
6
+ require "walmart_open/requests/token"
7
+ require "walmart_open/requests/place_order"
8
+
9
+ module WalmartOpen
10
+ class Client
11
+ attr_reader :connection
12
+ attr_reader :config
13
+ attr_reader :auth_token
14
+
15
+ def initialize(config_attrs = {})
16
+ @config = Config.new(config_attrs)
17
+ @connection = ConnectionManager.new(self)
18
+
19
+ yield config if block_given?
20
+ end
21
+
22
+ def search(query, params = {})
23
+ connection.request(Requests::Search.new(query, params))
24
+ end
25
+
26
+ def lookup(item_id, params = {})
27
+ connection.request(Requests::Lookup.new(item_id, params))
28
+ end
29
+
30
+ def taxonomy
31
+ connection.request(Requests::Taxonomy.new)
32
+ end
33
+
34
+ def order(order_info)
35
+ authenticate!
36
+
37
+ connection.request(Requests::PlaceOrder.new(order_info))
38
+ end
39
+
40
+ private
41
+
42
+ def authenticate!
43
+ if !@auth_token || @auth_token.expired?
44
+ @auth_token = connection.request(Requests::Token.new)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ require "walmart_open/request"
2
+
3
+ module WalmartOpen
4
+ class CommerceRequest < Request
5
+ private
6
+
7
+ def request_method
8
+ :post
9
+ end
10
+
11
+ def build_url(client)
12
+ url = "https://#{client.config.commerce_domain}"
13
+ url << "/#{client.config.commerce_version}"
14
+ url << "/qa" if client.config.debug
15
+ url << "/#{path}"
16
+ url << params_to_query_string(build_params(client))
17
+ end
18
+
19
+ def parse_response(response)
20
+ OrderResults.new(response.parsed_response)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,41 @@
1
+ require "uri"
2
+
3
+ module WalmartOpen
4
+ class Config
5
+ attr_accessor :debug,
6
+ :product_domain,
7
+ :product_version,
8
+ :product_api_key,
9
+ :product_calls_per_second,
10
+ :commerce_domain,
11
+ :commerce_version,
12
+ :commerce_api_key,
13
+ :commerce_api_secret,
14
+ :commerce_private_key,
15
+ :commerce_private_key_password,
16
+ :commerce_calls_per_second
17
+
18
+ def initialize(options = {})
19
+ # Default to production mode.
20
+ self.debug = false
21
+
22
+ # Set some defaults for Product API.
23
+ self.product_domain = "walmartlabs.api.mashery.com"
24
+ self.product_version = "v1"
25
+ self.product_calls_per_second = 5
26
+
27
+ # Set some defaults for Commerce API.
28
+ self.commerce_domain = "api.walmartlabs.com"
29
+ self.commerce_version = "v1"
30
+ self.commerce_calls_per_second = 2
31
+
32
+ options.each do |key, value|
33
+ public_send("#{key}=", value)
34
+ end
35
+ end
36
+
37
+ def private_key
38
+ @private_key ||= OpenSSL::PKey.read(commerce_private_key, commerce_private_key_password)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,47 @@
1
+ module WalmartOpen
2
+ class ConnectionManager
3
+ def initialize(client)
4
+ @client = client
5
+ @calls = {}
6
+ end
7
+
8
+ def request(request_obj)
9
+ throttle(request_obj) do
10
+ request_obj.submit(@client)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def throttle(request_obj)
17
+ type = request_type(request_obj)
18
+ @calls[type] ||= []
19
+ calls = @calls[type]
20
+
21
+ now = Time.now
22
+ calls.delete_if { |time| now.to_f - time.to_f >= 1 }
23
+
24
+ if calls.size >= calls_per_second(type)
25
+ sleep((calls.first + 1) - now)
26
+ end
27
+
28
+ ret_val = yield
29
+
30
+ calls << Time.now
31
+
32
+ ret_val
33
+ end
34
+
35
+ def request_type(request_obj)
36
+ case request_obj
37
+ when ProductRequest then :product
38
+ when CommerceRequest then :commerce
39
+ else raise "Unknown request type"
40
+ end
41
+ end
42
+
43
+ def calls_per_second(type)
44
+ @client.config.public_send("#{type}_calls_per_second")
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,13 @@
1
+ module WalmartOpen
2
+ class WalmartOpenError < ::StandardError
3
+ end
4
+
5
+ class AuthenticationError < WalmartOpenError
6
+ end
7
+
8
+ class ItemNotFoundError < WalmartOpenError
9
+ end
10
+
11
+ class OrderError < WalmartOpenError
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ module WalmartOpen
2
+ class Item
3
+ API_ATTRIBUTES_MAPPING = {
4
+ "itemId" => "id",
5
+ "name" => "name",
6
+ "salePrice" => "price",
7
+ "upc" => "upc",
8
+ "categoryNode" => "category_node",
9
+ "shortDescription" => "short_description",
10
+ "longDescription" => "long_description",
11
+ "branchName" => "brand",
12
+ "standardShipRate" => "shipping_rate",
13
+ "size" => "size",
14
+ "color" => "color",
15
+ "modelNumber" => "model_number",
16
+ "productUrl" => "url",
17
+ "availableOnline" => "available_online"
18
+ }
19
+
20
+ API_ATTRIBUTES_MAPPING.each_value do |attr_name|
21
+ attr_reader attr_name
22
+ end
23
+
24
+ attr_reader :raw_attributes
25
+
26
+ def initialize(attrs)
27
+ @raw_attributes = attrs
28
+ extract_known_attributes
29
+ end
30
+
31
+ private
32
+
33
+ def extract_known_attributes
34
+ API_ATTRIBUTES_MAPPING.each do |api_attr, attr|
35
+ next unless raw_attributes.has_key?(api_attr)
36
+
37
+ instance_variable_set("@#{attr}", raw_attributes[api_attr])
38
+ end
39
+ end
40
+ end
41
+ end