walmart_open 0.0.1

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