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
@@ -0,0 +1,53 @@
1
+ require "walmart_open/order_item"
2
+ require "walmart_open/item"
3
+ require "walmart_open/shipping_address"
4
+ require "securerandom"
5
+
6
+ module WalmartOpen
7
+ class Order
8
+
9
+ attr_reader :shipping_address
10
+ attr_accessor :billing_id, :first_name, :last_name, :partner_order_time,
11
+ :partner_order_id, :phone, :items
12
+
13
+ def initialize(params)
14
+ @shipping_address = nil
15
+ @items = []
16
+ @billing_id = params[:billing_id]
17
+ @first_name = params[:first_name]
18
+ @last_name = params[:last_name]
19
+ @phone = params[:phone]
20
+ @partner_order_id = params[:partner_order_id] || "Order-#{Digest::SHA1.hexdigest("#{Time.now.to_i}:#{SecureRandom.hex(16)}")[0..19].upcase}"
21
+ @partner_order_time = params[:partner_order_time] || Time.now
22
+ end
23
+
24
+ def add_shipping_address(params)
25
+ @shipping_address = ShippingAddress.new(params)
26
+ end
27
+
28
+ def add_item(item_or_item_id, *args)
29
+ if item_or_item_id.is_a?(Item)
30
+ # add_item(item, quantity = 1)
31
+ @items << OrderItem.new(item_or_item_id.id, item_or_item_id.price, item_or_item_id.shipping_rate, args[0] || 1)
32
+ else
33
+ # add_item(item_id, item_price, shipping_price = 0, quantity = 1)
34
+ @items << OrderItem.new(item_or_item_id, args[0], args[1] || 0, args[2] || 1)
35
+ end
36
+ end
37
+
38
+ def valid?
39
+ base_values_valid? && !shipping_address.nil? && shipping_address.valid? && items_valid?
40
+ end
41
+
42
+ private
43
+
44
+ def base_values_valid?
45
+ billing_id && first_name && partner_order_time && partner_order_id
46
+ end
47
+
48
+ def items_valid?
49
+ items.any? && items.all?(&:valid?)
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,17 @@
1
+ module WalmartOpen
2
+ class OrderItem
3
+
4
+ attr_accessor :item_id, :quantity, :item_price, :shipping_price
5
+
6
+ def initialize(item_id, item_price, shipping_price = 0, quantity = 1)
7
+ @item_id = item_id
8
+ @quantity = quantity
9
+ @item_price = item_price
10
+ @shipping_price = shipping_price
11
+ end
12
+
13
+ def valid?
14
+ !!(item_id && quantity && item_price && shipping_price)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,40 @@
1
+ require "walmart_open/ordered_item"
2
+
3
+ module WalmartOpen
4
+ class OrderResults
5
+ attr_reader :error, :order_id, :partner_order_id, :items, :total, :shipping, :sales_tax, :surcharge, :raw_attributes
6
+
7
+ def initialize(attrs)
8
+ @raw_attributes = attrs
9
+
10
+ if attrs["errors"] && attrs["errors"]["error"]
11
+ @error = {code: attrs["errors"]["error"]["code"], message: attrs["errors"]["error"]["message"]}
12
+ else
13
+ response = attrs["response"]
14
+ @order_id = response["orderId"]
15
+ @partner_order_id = response["partnerOrderId"]
16
+ @total = response["itemTotal"]
17
+ @shipping = response["shipping"]
18
+ @sales_tax = response["salesTax"]
19
+ @surcharge = response["surcharge"]
20
+
21
+ @items = []
22
+ if response["items"] && response["items"]["item"]
23
+ if response["items"]["item"].is_a?(Array)
24
+ items = response["items"]["item"]
25
+ else
26
+ items = [response["items"]["item"]]
27
+ end
28
+ items.each do | item |
29
+ @items << OrderedItem.new(item)
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def error?
36
+ !@error.nil?
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,62 @@
1
+ require "builder"
2
+
3
+ module WalmartOpen
4
+ class OrderXMLBuilder
5
+ attr_reader :order
6
+
7
+ def initialize(order)
8
+ @order = order
9
+ end
10
+
11
+ def build
12
+ xml = Builder::XmlMarkup.new
13
+ xml.instruct!(:xml, version: "1.0", encoding: "UTF-8")
14
+ xml.order do |xml|
15
+ payment(xml)
16
+ shipping_address(xml)
17
+ xml.partnerOrderId(order.partner_order_id)
18
+ xml.partnerOrderTime(order.partner_order_time.strftime( "%H:%M:%S"))
19
+
20
+ xml.items do |xml|
21
+ order.items.each do |order_item|
22
+ item(xml, order_item)
23
+ end
24
+ end
25
+
26
+ xml.target!
27
+ end
28
+
29
+ end
30
+
31
+ private
32
+
33
+ def shipping_address(xml)
34
+ xml.shippingAddress do |xml|
35
+ xml.firstName(order.first_name)
36
+ xml.lastName(order.last_name)
37
+ xml.street1(order.shipping_address.street1)
38
+ xml.street2(order.shipping_address.street2) if order.shipping_address.street2
39
+ xml.city(order.shipping_address.city)
40
+ xml.state(order.shipping_address.state)
41
+ xml.zip(order.shipping_address.zipcode)
42
+ xml.country(order.shipping_address.country)
43
+ xml.phone(order.phone) if order.phone
44
+ end
45
+ end
46
+
47
+ def payment(xml)
48
+ xml.payment do |xml|
49
+ xml.billingRecordId(order.billing_id)
50
+ end
51
+ end
52
+
53
+ def item(xml, order_item)
54
+ xml.item do |xml|
55
+ xml.itemId(order_item.item_id)
56
+ xml.quantity(order_item.quantity)
57
+ xml.itemPrice(order_item.item_price)
58
+ xml.shippingPrice(order_item.shipping_price)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,31 @@
1
+ module WalmartOpen
2
+ class OrderedItem
3
+ API_ATTRIBUTES_MAPPING = {
4
+ "itemId" => "id",
5
+ "quantity" => "quantity",
6
+ "itemPrice" => "price"
7
+ }
8
+
9
+ API_ATTRIBUTES_MAPPING.each_value do |attr_name|
10
+ attr_reader attr_name
11
+ end
12
+
13
+ attr_reader :raw_attributes
14
+
15
+ def initialize(attrs)
16
+ @raw_attributes = attrs
17
+
18
+ extract_known_attributes
19
+ end
20
+
21
+ private
22
+
23
+ def extract_known_attributes
24
+ API_ATTRIBUTES_MAPPING.each do |api_attr, attr|
25
+ next unless raw_attributes.has_key?(api_attr)
26
+
27
+ instance_variable_set("@#{attr}", raw_attributes[api_attr])
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,28 @@
1
+ require "walmart_open/request"
2
+
3
+ module WalmartOpen
4
+ class ProductRequest < Request
5
+ private
6
+
7
+ def build_url(client)
8
+ url = "https://#{client.config.product_domain}"
9
+ url << "/#{client.config.product_version}"
10
+ url << "/#{path}"
11
+ url << params_to_query_string(build_params(client))
12
+ end
13
+
14
+ def build_params(client)
15
+ {
16
+ format: "json",
17
+ api_key: client.config.product_api_key
18
+ }.merge(params || {})
19
+ end
20
+
21
+ # Walmart API unofficially supports HTTPS so we rather hit that instead of
22
+ # HTTP. However, their SSL certificate is unverifiable so we have to tell
23
+ # HTTParty not to verify (otherwise it will complain).
24
+ def request_options(client)
25
+ { verify: false }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ require "httparty"
2
+ require "uri"
3
+ require "walmart_open/errors"
4
+
5
+ module WalmartOpen
6
+ class Request
7
+ attr_reader :path, :params
8
+
9
+ def submit(client)
10
+ raise "@path must be specified" unless path
11
+
12
+ response = HTTParty.public_send(request_method, build_url(client), request_options(client))
13
+ verify_response(response)
14
+ parse_response(response)
15
+ end
16
+
17
+ private
18
+
19
+ attr_writer :path, :params
20
+
21
+ def request_method
22
+ :get
23
+ end
24
+
25
+ def build_url(client)
26
+ raise NotImplementedError, "build_url must be implemented by subclass"
27
+ end
28
+
29
+ def build_params(client)
30
+ # noop
31
+ end
32
+
33
+ def request_options(client)
34
+ {}
35
+ end
36
+
37
+ # Subclasses can override this method to return a different response.
38
+ def parse_response(response)
39
+ response
40
+ end
41
+
42
+ def verify_response(response)
43
+ unless response.success?
44
+ raise WalmartOpen::AuthenticationError, response.parsed_response.inspect
45
+ end
46
+ end
47
+
48
+ def params_to_query_string(params_hash)
49
+ params_hash = convert_param_keys(params_hash || {})
50
+ query_str = URI.encode_www_form(params_hash)
51
+ query_str.prepend("?") unless query_str.size.zero?
52
+ query_str
53
+ end
54
+
55
+ # Converts foo_bar_param to fooBarParam.
56
+ def convert_param_keys(underscored_params)
57
+ pairs = underscored_params.map do |key, value|
58
+ key = key.to_s.gsub(/_([a-z])/i) { $1.upcase }
59
+ [key, value]
60
+ end
61
+ Hash[pairs]
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,27 @@
1
+ require "walmart_open/product_request"
2
+ require "walmart_open/item"
3
+ require "walmart_open/errors"
4
+
5
+ module WalmartOpen
6
+ module Requests
7
+ class Lookup < ProductRequest
8
+ def initialize(item_id, params = {})
9
+ self.path = "items/#{item_id}"
10
+ self.params = params
11
+ end
12
+
13
+ private
14
+
15
+ def parse_response(response)
16
+ Item.new(response.parsed_response)
17
+ end
18
+
19
+ def verify_response(response)
20
+ if response.code == 400
21
+ raise WalmartOpen::ItemNotFoundError, response.parsed_response.inspect
22
+ end
23
+ super
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,58 @@
1
+ require "walmart_open/commerce_request"
2
+ require "walmart_open/order_xml_builder"
3
+ require "walmart_open/order_results"
4
+ require "walmart_open/errors"
5
+ require "openssl"
6
+ require "base64"
7
+
8
+
9
+ module WalmartOpen
10
+ module Requests
11
+ class PlaceOrder < CommerceRequest
12
+ attr_accessor :order
13
+
14
+ def initialize(order)
15
+ self.path = "orders/place"
16
+ @order = order
17
+ end
18
+
19
+ private
20
+
21
+ def parse_response(response)
22
+ OrderResults.new(response.parsed_response)
23
+ end
24
+
25
+ def verify_response(response)
26
+ if response.code == 400
27
+ raise WalmartOpen::OrderError, response.parsed_response.inspect
28
+ end
29
+ super
30
+ end
31
+
32
+ def request_options(client)
33
+ body = build_xsd
34
+ signature = client.config.debug ? "FAKE_SIGNATURE" : sign(client.config.private_key, body)
35
+ {
36
+ headers: {
37
+ "Authorization" => client.auth_token.authorization_header,
38
+ "Content-Type" => "text/xml",
39
+ "X-Walmart-Body-Signature" => signature
40
+ },
41
+ body: body
42
+ }
43
+ end
44
+
45
+ def build_params(client)
46
+ { disablesigv: true } if client.config.debug
47
+ end
48
+
49
+ def build_xsd
50
+ OrderXMLBuilder.new(order).build
51
+ end
52
+
53
+ def sign(key, data)
54
+ Base64.urlsafe_encode64(key.sign(OpenSSL::Digest::SHA256.new, data))
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ require "walmart_open/product_request"
2
+ require "walmart_open/search_results"
3
+
4
+ module WalmartOpen
5
+ module Requests
6
+ class Search < ProductRequest
7
+ def initialize(query, params = {})
8
+ self.path = "search"
9
+ self.params = params.merge(query: query)
10
+ end
11
+
12
+ private
13
+
14
+ def parse_response(response)
15
+ SearchResults.new(response.parsed_response)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ require "walmart_open/product_request"
2
+
3
+ module WalmartOpen
4
+ module Requests
5
+ class Taxonomy < ProductRequest
6
+ def initialize
7
+ self.path = "taxonomy"
8
+ end
9
+
10
+ private
11
+
12
+ def parse_response(response)
13
+ response.parsed_response["categories"]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ require "walmart_open/commerce_request"
2
+ require "walmart_open/auth_token"
3
+
4
+ module WalmartOpen
5
+ module Requests
6
+ class Token < CommerceRequest
7
+ def initialize
8
+ self.path = "oauth2/token"
9
+ end
10
+
11
+ private
12
+
13
+ def request_options(client)
14
+ {
15
+ basic_auth: {
16
+ username: client.config.commerce_api_key,
17
+ password: client.config.commerce_api_secret
18
+ },
19
+ body: {
20
+ grant_type: "client_credentials"
21
+ }
22
+ }
23
+ end
24
+
25
+ def parse_response(response)
26
+ AuthToken.new(response.parsed_response, Time.parse(response.headers["date"]))
27
+ end
28
+ end
29
+ end
30
+ end