Browse files

more cart operations

  • Loading branch information...
1 parent 4355c49 commit 11d6729b256b4c7825261b8b414a862bc4b0f900 @phoet phoet committed Mar 19, 2011
Showing with 193 additions and 129 deletions.
  1. +119 −106 lib/asin.rb
  2. +26 −6 lib/asin/cart.rb
  3. +9 −10 lib/asin/configuration.rb
  4. +5 −5 lib/asin/item.rb
  5. +32 −1 spec/cart_spec.rb
  6. +2 −1 spec/spec_helper.rb
View
225 lib/asin.rb
@@ -12,109 +12,109 @@
# ASIN (Amazon Simple INterface) is a gem for easy access of the Amazon E-Commerce-API.
# It is simple to configure and use. Since it's very small and flexible, it is easy to extend it to your needs.
-#
+#
# Author:: Peter Schröder (mailto:phoetmail@googlemail.com)
-#
+#
# ==Usage
-#
+#
# The ASIN module is designed as a mixin.
-#
+#
# require 'asin'
# include ASIN
-#
+#
# In order to use the Amazon API properly, you need to be a registered user (http://aws.amazon.com).
-#
+#
# The registration process will give you a +secret-key+ and an +access-key+ (AWSAccessKeyId).
-#
+#
# Both are needed to use ASIN (see Configuration for more details):
-#
+#
# configure :secret => 'your-secret', :key => 'your-key'
-#
-# After configuring your environment you can call the +lookup+ method to retrieve an +Item+ via the
+#
+# After configuring your environment you can call the +lookup+ method to retrieve an +Item+ via the
# Amazon Standard Identification Number (ASIN):
-#
+#
# item = lookup '1430218150'
# item.title
# => "Learn Objective-C on the Mac (Learn Series)"
#
# OR search with fulltext/ASIN/ISBN
-#
+#
# items = search 'Learn Objective-C'
# items.first.title
# => "Learn Objective-C on the Mac (Learn Series)"
-#
+#
# The +Item+ uses a Hashie::Mash as its internal data representation and you can get fetched data from it:
-#
+#
# item.raw.ItemAttributes.ListPrice.FormattedPrice
# => "$39.99"
-#
+#
# ==Further Configuration
-#
-# If you need more controll over the request that is sent to the
+#
+# If you need more controll over the request that is sent to the
# Amazon API (http://docs.amazonwebservices.com/AWSEcommerceService/4-0/),
# you can override some defaults or add additional query-parameters to the REST calls:
-#
+#
# configure :host => 'webservices.amazon.de'
# lookup(asin, :ResponseGroup => :Medium)
-#
+#
module ASIN
-
+
DIGEST = OpenSSL::Digest::Digest.new('sha256')
PATH = '/onca/xml'
# Convenience method to create an ASIN client.
- #
+ #
# A client is not necessary though, you can simply include the ASIN module otherwise.
- #
+ #
def self.client
client = Object.new
client.extend ASIN
client
end
# Configures the basic request parameters for ASIN.
- #
+ #
# Expects at least +secret+ and +key+ for the API call:
- #
+ #
# configure :secret => 'your-secret', :key => 'your-key'
- #
+ #
# See ASIN::Configuration for more infos.
- #
+ #
def configure(options={})
Configuration.configure(options)
end
# Performs an +ItemLookup+ REST call against the Amazon API.
- #
+ #
# Expects an ASIN (Amazon Standard Identification Number) and returns an +Item+:
- #
+ #
# item = lookup '1430218150'
# item.title
# => "Learn Objective-C on the Mac (Learn Series)"
- #
+ #
# ==== Options:
- #
+ #
# Additional parameters for the API call like this:
- #
+ #
# lookup(asin, :ResponseGroup => :Medium)
- #
+ #
def lookup(asin, params={})
response = call(params.merge(:Operation => :ItemLookup, :ItemId => asin))
Item.new(response['ItemLookupResponse']['Items']['Item'])
end
-
+
# Performs an +ItemSearch+ REST call against the Amazon API.
- #
+ #
# Expects a search-string which can be an arbitrary array of strings (ASINs f.e.) and returns a list of +Items+:
- #
+ #
# items = search_keywords 'Learn', 'Objective-C'
# items.first.title
# => "Learn Objective-C on the Mac (Learn Series)"
- #
+ #
# ==== Options:
- #
+ #
# Additional parameters for the API call like this:
- #
+ #
# search_keywords('nirvana', 'never mind', :SearchIndex => :Music)
#
# Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
@@ -124,17 +124,17 @@ def search_keywords(*keywords)
response = call(params.merge(:Operation => :ItemSearch, :Keywords => keywords.join(' ')))
(response['ItemSearchResponse']['Items']['Item'] || []).map {|item| Item.new(item)}
end
-
+
# Performs an +ItemSearch+ REST call against the Amazon API.
- #
+ #
# Expects a Hash of search params where and returns a list of +Items+:
- #
+ #
# items = search :SearchIndex => :Music
- #
+ #
# ==== Options:
- #
+ #
# Additional parameters for the API call like this:
- #
+ #
# search(:Keywords => 'nirvana', :SearchIndex => :Music)
#
# Have a look at the different search index values on the Amazon-Documentation[http://docs.amazonwebservices.com/AWSEcommerceService/4-0/]
@@ -144,74 +144,87 @@ def search(params={:SearchIndex => :Books})
(response['ItemSearchResponse']['Items']['Item'] || []).map {|item| Item.new(item)}
end
- # TODO (ps) add decent documentation
- def cart(operation, items, params={})
- operations = {
- :add => :CartAdd,
- :clear => :CartClear,
- :create => :CartCreate,
- :get => :CartGet,
- :modify => :CartModify
- }
- items_hash = {}
- items.each_with_index do |item, i|
- items_hash["Item.#{i}.ASIN"] = item[:asin]
- items_hash["Item.#{i}.Quantity"] = item[:quantity]
- end
- response = call(params.merge(items_hash.merge(:Operation => operations[operation])))
- Cart.new(response["Cart#{operation.capitalize}Response"]['Cart'])
+ def create_cart(*items)
+ cart(:CartCreate, create_item_params(items))
end
- private
-
- def credentials_valid?
- !Configuration.secret.nil? && !Configuration.key.nil?
+ def get_cart(cart_id, hmac)
+ cart(:CartGet, {:CartId => cart_id, :HMAC => hmac})
end
- def call(params)
- raise "you have to configure ASIN: 'configure :secret => 'your-secret', :key => 'your-key''" unless credentials_valid?
-
- log(:debug, "calling with params=#{params}")
- signed = create_signed_query_string(params)
-
- url = "http://#{Configuration.host}#{PATH}?#{signed}"
- log(:info, "performing rest call to url='#{url}'")
-
- response = HTTPI.get(url)
- if response.code == 200
- # force utf-8 chars, works only on 1.9 string
- resp = response.body
- resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
- log(:debug, "got response='#{resp}'")
- Crack::XML.parse(resp)
- else
- log(:error, "got response='#{response.body}'")
- raise "request failed with response-code='#{response.code}'"
- end
+ def add_items(cart, *items)
+ cart(:CartAdd, create_item_params(items).merge({:CartId => cart.cart_id, :HMAC => cart.hmac}))
end
- def create_signed_query_string(params)
- # nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
- params[:Service] = :AWSECommerceService
- params[:AWSAccessKeyId] = Configuration.key
- # utc timestamp needed for signing
- params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
-
- # signing needs to order the query alphabetically
- query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
-
- # yeah, you really need to sign the get-request not the query
- request_to_sign = "GET\n#{Configuration.host}\n#{PATH}\n#{query}"
- hmac = OpenSSL::HMAC.digest(DIGEST, Configuration.secret, request_to_sign)
-
- # don't forget to remove the newline from base64
- signature = CGI.escape(Base64.encode64(hmac).chomp)
- "#{query}&Signature=#{signature}"
- end
-
- def log(severity, message)
- Configuration.logger.send severity, message if Configuration.logger
+ def clear_cart(cart)
+ cart(:CartClear, {:CartId => cart.cart_id, :HMAC => cart.hmac})
end
-end
+ private
+
+ def create_item_params(items)
+ params = {}
+ items.each_with_index do |item, i|
+ params["Item.#{i}.ASIN"] = item[:asin]
+ params["Item.#{i}.Quantity"] = item[:quantity]
+ end
+ params
+ end
+
+ def cart(operation, params={})
+ response = call(params.merge(:Operation => operation))
+ puts response
+ Cart.new(response["#{operation}Response"]['Cart'])
+ end
+
+
+ def credentials_valid?
+ !Configuration.secret.nil? && !Configuration.key.nil?
+ end
+
+ def call(params)
+ raise "you have to configure ASIN: 'configure :secret => 'your-secret', :key => 'your-key''" unless credentials_valid?
+
+ log(:debug, "calling with params=#{params}")
+ signed = create_signed_query_string(params)
+
+ url = "http://#{Configuration.host}#{PATH}?#{signed}"
+ log(:info, "performing rest call to url='#{url}'")
+
+ response = HTTPI.get(url)
+ if response.code == 200
+ # force utf-8 chars, works only on 1.9 string
+ resp = response.body
+ resp = resp.force_encoding('UTF-8') if resp.respond_to? :force_encoding
+ log(:debug, "got response='#{resp}'")
+ Crack::XML.parse(resp)
+ else
+ log(:error, "got response='#{response.body}'")
+ raise "request failed with response-code='#{response.code}'"
+ end
+ end
+ def create_signed_query_string(params)
+ # nice tutorial http://cloudcarpenters.com/blog/amazon_products_api_request_signing/
+ params[:Service] = :AWSECommerceService
+ params[:AWSAccessKeyId] = Configuration.key
+ # utc timestamp needed for signing
+ params[:Timestamp] = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
+
+ # signing needs to order the query alphabetically
+ query = params.map{|key, value| "#{key}=#{CGI.escape(value.to_s)}" }.sort.join('&').gsub('+','%20')
+
+ # yeah, you really need to sign the get-request not the query
+ request_to_sign = "GET\n#{Configuration.host}\n#{PATH}\n#{query}"
+ hmac = OpenSSL::HMAC.digest(DIGEST, Configuration.secret, request_to_sign)
+
+ # don't forget to remove the newline from base64
+ signature = CGI.escape(Base64.encode64(hmac).chomp)
+ "#{query}&Signature=#{signature}"
+ end
+
+ def log(severity, message)
+ Configuration.logger.send severity, message if Configuration.logger
+ end
+
+end
View
32 lib/asin/cart.rb
@@ -3,28 +3,48 @@
module ASIN
# =Item
- #
+ #
# The +Item+ class is a wrapper for the Amazon XML-REST-Response.
- #
+ #
# A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
- #
+ #
class Cart
attr_reader :raw
def initialize(hash)
@raw = Hashie::Mash.new(hash)
end
-
+
def cart_id
@raw.CartId
end
+ def hmac
+ @raw.HMAC
+ end
+
+ def url
+ @raw.PurchaseURL
+ end
+
+ def price
+ @raw.SubTotal.FormattedPrice
+ end
+
+ def items
+ @raw.CartItems.CartItem
+ end
+
def valid?
@raw.Request.IsValid == 'True'
end
+
+ def empty?
+ @raw.CartItems.nil?
+ end
end
-
+
end
# {"Request"=>
@@ -53,4 +73,4 @@ def valid?
# "Price"=>
# {"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"},
# "ItemTotal"=>
-# {"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"}}}}
+# {"Amount"=>"2639", "CurrencyCode"=>"USD", "FormattedPrice"=>"$26.39"}}}}
View
19 lib/asin/configuration.rb
@@ -54,7 +54,7 @@ def configure(options={})
end
self
end
-
+
# Resets configuration to defaults
#
def reset
@@ -63,15 +63,14 @@ def reset
private
- def init_config(force=false)
- return if @init && !force
- @init = true
- @secret = ''
- @key = ''
- @host = 'webservices.amazon.com'
- @logger = Logger.new(STDERR)
- end
+ def init_config(force=false)
+ return if @init && !force
+ @init = true
+ @secret = ''
+ @key = ''
+ @host = 'webservices.amazon.com'
+ @logger = Logger.new(STDERR)
+ end
end
end
end
-
View
10 lib/asin/item.rb
@@ -3,11 +3,11 @@
module ASIN
# =Item
- #
+ #
# The +Item+ class is a wrapper for the Amazon XML-REST-Response.
- #
+ #
# A Hashie::Mash is used for the internal data representation and can be accessed over the +raw+ attribute.
- #
+ #
class Item
attr_reader :raw
@@ -20,5 +20,5 @@ def title
@raw.ItemAttributes.Title
end
end
-
-end
+
+end
View
33 spec/cart_spec.rb
@@ -49,12 +49,43 @@ module ASIN
cart = Cart.new(@ok)
cart.valid?.should be(true)
cart.cart_id.should eql('186-7155396-8661342')
+ cart.hmac.should eql('kajZJ8BoZStXyHg4LfNEzyjvQJw=')
+ cart.url.should eql('https://www.amazon.com/gp/cart/aws-merge.html?cart-id=186-7155396-8661342%26associate-id=ws%26hmac=kajZJ8BoZStXyHg4LfNEzyjvQJw=%26SubscriptionId=AKIAJFA5X7RTOKFNPVZQ%26MergeCart=False')
+ cart.price.should eql('$26.39')
end
it "should create a cart" do
- cart = @helper.cart :create, [{:asin => ANY_ASIN, :quantity => 1}]
+ cart = @helper.create_cart({:asin => ANY_ASIN, :quantity => 1}, {:asin => OTHER_ASIN, :quantity => 2})
cart.valid?.should be(true)
+ cart.empty?.should be(false)
end
+
+ context "with an existing cart" do
+
+ before do
+ @cart = @helper.create_cart({:asin => ANY_ASIN, :quantity => 1})
+ @cart.valid?.should be(true)
+ end
+
+ it "should clear a cart" do
+ cart = @helper.clear_cart(@cart)
+ cart.valid?.should be(true)
+ cart.empty?.should be(true)
+ end
+
+ it "should get a cart" do
+ cart = @helper.get_cart(@cart.cart_id, @cart.hmac)
+ cart.valid?.should be(true)
+ cart.empty?.should be(false)
+ end
+
+ it "should add items to a cart" do
+ cart = @helper.add_items(@cart, {:asin => OTHER_ASIN, :quantity => 2})
+ cart.valid?.should be(true)
+ cart.empty?.should be(false)
+ end
+ end
+
end
end
end
View
3 spec/spec_helper.rb
@@ -3,7 +3,8 @@
require 'asin'
require 'pp'
-ANY_ASIN = '1430218150'
+ANY_ASIN = '1430218150'
+OTHER_ASIN = '1430216263'
Rspec.configure do |c|
c.mock_with :rspec

0 comments on commit 11d6729

Please sign in to comment.