[Rails][Amazon] Ruby on Rails で Amazon ショッピングカートを作成する  カート編

前回からの続き

5.カート画面

f:id:shohu33:20071219235628j:image

機能

・カートの商品数変更 (再計算ボタン押下)
・カートの商品削除 (削除リンク押下)
・Amazon購入画面へ遷移 (購入手続きへ進むボタン押下)

制限事項

・商品数量の入力チェックは行わない
・リロードの対応は行わない

amazon_ecs 0.5.3のソース 修正。

amazon_ecs 0.5.3のソースそのままではカート操作ができない。
このため、amazon_ecsを拡張したモジュールを追加する。

・拡張モジュール lib/amazon/ecsex.rb

# Amazon ECS 0.5.3 にはカートを操作するメソッドが無いため自作
module Ecsex
  class RequestError < StandardError; end
 
    # カートを作成し、カートIDと、HMACを保持したHASHを返却する
    def self.item_createCart(asin, quantity)
        opts = {}
        opts[:operation] = 'CartCreate'
        opts['Item.1.ASIN'] = asin
        opts['Item.1.Quantity'] = quantity
        Amazon::Ecs.send_request(opts)
    end
    
    # カートをクリア
    def self.item_clearCart(cart)
        opts = {}
        opts[:operation] = 'CartClear'
        opts['CartId'] = cart['cartid']
        opts['HMAC'] = cart['urlencodedhmac']
        Amazon::Ecs.send_request(opts)
    end
    
  # 数量1のカートを作成
  def self.item_createCart_single(asin)
    item_createCart(asin, 1)
  end
    
  # カートに商品を追加する
  def self.item_addCart(asin, cart)
    opts = {}
    opts[:operation] = 'CartAdd'
    opts['CartId'] = cart['cartid']
    opts['HMAC'] = cart['urlencodedhmac']
    opts['Item.1.ASIN'] = asin
    opts['Item.1.Quantity'] = 1
    Amazon::Ecs.send_request(opts)
  end
    
  # カート内容を参照
  def self.item_refCart(cart)
    opts = {}
    opts[:operation] = 'CartGet'
    opts['CartId'] = cart['cartid']
    opts['HMAC'] = cart['urlencodedhmac']
    Amazon::Ecs.send_request(opts)
  end
    
  # カートの商品を変更する
  def self.item_modCart(itemList, cart)
    opts = {}
    opts[:operation] = 'CartModify'
    opts['CartId'] = cart['cartid']
    opts['HMAC'] = cart['urlencodedhmac']
    cnt = 1
    itemList.each do |item|
      opts['Item.' + cnt.to_s + '.CartItemId'] = item['cartitemid']
      opts['Item.' + cnt.to_s + '.Quantity'] = item['quantity']
      cnt += 1
    end
    Amazon::Ecs.send_request(opts)
  end
end

さらに、amazon_ecs 0.5.3 をそのまま使用するとAmazonとやり取りするHMACが勝手にURLエンコードされてしまい、サーバー側で保持しているHMACの値と異なってカート処理が行えない場合がある。
このためもともとのソース(RUBY_HOME\lib\ruby\gems\1.8\gems\amazon-ecs-0.5.3\lib\amazon)を以下のように修正し、libディレクトリ(lib/amazon/ecs.rb)に配備する。

・修正前

      def self.prepare_url(opts)
        country = opts.delete(:country)
        country = (country.nil?) ? 'us' : country
        request_url = SERVICE_URLS[country.to_sym]
        raise Amazon::RequestError, "Invalid country '#{country}'" unless request_url
        
        qs = ''
        opts.each {|k,v|
          next unless v
          v = v.join(',') if v.is_a? Array
          qs << "&#{camelize(k.to_s)}=#{URI.encode(v.to_s)}"
        }
        "#{request_url}#{qs}"
      end

・修正後

      def self.prepare_url(opts)
        country = opts.delete(:country)
        country = (country.nil?) ? 'us' : country
        request_url = SERVICE_URLS[country.to_sym]
        raise Amazon::RequestError, "Invalid country '#{country}'" unless request_url
        
        qs = ''
        kind = opts[:operation][0..3] # ADD
        opts.each {|k,v|
          next unless v
          v = v.join(',') if v.is_a? Array
          if kind == 'Cart' # ADD
            qs << "&#{camelize(k.to_s)}=#{v.to_s}" # ADD
          else # ADD
            qs << "&#{camelize(k.to_s)}=#{URI.encode(v.to_s)}"
          end # ADD
        }
        "#{request_url}#{qs}"
      end
HTML

・app/views/amazon_ecs/cart.rhtml

<h1>Amazon カート画面</h1>
<table>
<% if @items.length != 0 %>
<%= form_tag :action => 'cart', :operate => 'mod' %>
			<tr>
				<td>1.&nbsp;ショッピングカートの中身を確認。</td>
			</tr>
			<tr>
				<td>
					<table>
						<tr bgcolor="#CA9947">
							<th width="90" nowrap="nowrap" height="25" align="center">
								商品コード
							</th>
							<th width="410" align="center">
								商品名
							</th>
							<th width="70" nowrap="nowrap" align="center">
								単価&nbsp;(税込)
							</th>
							<th width="50" nowrap="nowrap" align="center">
									数量
							</th>
							<th width="70" nowrap="nowrap" align="center">
								小計&nbsp;(税込)
							</th>
							<th width="70" nowrap="nowrap" align="center">
								削除
							</th>
						</tr>
						<!-- 繰り返し -->
<% count = 1 %>
<% @items.each do |item| -%>
						<tr>
							<td bgcolor="#F0F0F0" nowrap="nowrap" align="center">
								<%=h item['asin'] %>
							</td>
							<td bgcolor="#F0F0F0">
								<%= item['title'] %>
							</td>
							<td bgcolor="#F0F0F0" align="right">
								<%=h item['price'] %>&nbsp;
							</td>
							<td bgcolor="#F0F0F0" nowrap="nowrap" align="center">
									<%= text_field_tag('quantity' + count.to_s, item['quantity'], :size => '2', :maxlength => '3')  %>
							</td>
							<td bgcolor="#F0F0F0" nowrap="nowrap" align="right">
								<%= number_to_currency( item['totalprice'],:unit => "\",:precision => 0 ) %>&nbsp;
							</td>
							<td align="center" bgcolor="#F0F0F0" nowrap="nowrap">
								<%= hidden_field_tag 'cartitemid' + count.to_s, item['cartitemid'] %>
								<%=link_to('削除',
								 'http://localhost:3000/amazon_ecs/cart?operate=del&cartitemid=' + item['cartitemid']) %>
							</td>
						</tr>
<% count += 1 %>
<% end -%>
						<!-- //繰り返し -->
						<tr>
							<td height="5" colspan="6">
								<%=image_tag('spacer.gif', :width => 1, :height => 1) %>
							</td>
						</tr>
						<tr>
							<td>
								<%=image_tag('spacer.gif', :width => 1, :height => 1) %>
							</td>
							<td nowrap="nowrap" align="right" colspan="3">
								合計金額&nbsp;(税込)&nbsp;
							</td>
							<td colspan="2" bgcolor="#CA9947" nowrap="nowrap" align="right">
								<%=h @totalPrice %>&nbsp;&nbsp;
							</td>
						</tr>
					</table>
				</td>
			</tr>
			<tr>
				<td><%=image_tag('spacer.gif', :width => 1, :height => 15) %></td>
			</tr>
			<tr>
				<td valign="top">2.&nbsp;数量を変更した場合は、「再計算」をクリック。</td>
			</tr>
			<tr>
				<td height="50">
					<p align="center">
						<%= hidden_field_tag 'cartitemnum', @items.size %>
						<%=submit_tag('再計算') %>
					</p>
				</td>
			</tr>
<%= end_form_tag %>
<%= form_tag :action => 'cart', :operate => 'buy' %>
			<tr>
				<td><%=image_tag('spacer.gif', :width => 1, :height => 15) %></td>
			</tr>
			<tr>
				<td>3.&nbsp;「購入手続きへ進む」をクリックでAmazonの購入手続き画面へ。</td>
			</tr>
			<tr>
				<td height="70">
					<table border="0" cellpadding="5" cellspacing="0" width="375" align="center">
						<tr>
							<td valign="top" width="235" align="left">
								<%=submit_tag('購入手続きへ進む') %>
							</td>
						</tr>
					</table>
				</td>
			</tr>
<%= end_form_tag %>
			<tr>
				<td height="50">
					<%=image_tag('spacer.gif', :width => 1, :height => 1) %>
				</td>
			</tr>
<% else %>
			<tr>
				<td>
					ショッピングカートに商品は入っていません。
				</td>
			</tr>
<% end %>
	</table>
</div>
Controller

・app/controller/amazon_ecs_controller.rb

class AmazonEcsController < ApplicationController
  require 'amazon/ecs'
  require 'amazon/ecsex' # 追加
  :
  :
 # カート画面表示
  def cart
    @errmsg = params[:errmsg]
    # カート内容が存在しない場合は、カートを作成し終了
    if session[:cart] == nil || session[:cart]['cartid'] == '' || session[:cart]['cartid'] == nil
      @items = createCart # カート作成
    # 商品追加
    elsif params[:operate] == 'add'
      @items = addCart
    # カート内容参照
    elsif params[:operate] == 'ref'
      @items = refCart
    # カート内容変更
    elsif params[:operate] == 'mod'
      @items = modCart
    # 商品削除
    elsif params[:operate] == 'del'
      @items = delCart
    # 商品購入
    elsif params[:operate] == 'buy'
      buyItem
    end
  end
  
  # 商品購入処理
  def buyItem
    # 遷移するAmazon側URLを取得
    doc = Ecsex.item_refCart(session[:cart]).doc
    # Amazon へリダイレクト 
    redirect_to (doc/"purchaseurl").inner_html
  end
  
  # カートの商品削除
  def delCart
    # asid と quantityのHashをリストにして渡す
    paramList = []
    ary = ['cartitemid', params['cartitemid'],
           'quantity', 0]
    paramList.push(Hash[*ary])
    doc = Ecsex.item_modCart(paramList, session[:cart]).doc
    # 表示オブジェクト作成
    getShowObject4Cart(doc)
  end
  
  # カート内容参照
  def refCart
    doc = Ecsex.item_refCart(session[:cart]).doc
    # 表示オブジェクト作成
    getShowObject4Cart(doc)
  end
  
  # カートに商品を変更する
  def modCart
    # asid と quantityのHashをリストにして渡す
    max = params[:cartitemnum].to_i
    paramList = []
    for i in 1..max
      ary = ['cartitemid', params['cartitemid' + i.to_s],
             'quantity', params['quantity' + i.to_s]]
      paramList.push(Hash[*ary])
    end
    doc = Ecsex.item_modCart(paramList, session[:cart]).doc
    # 表示オブジェクト作成
    getShowObject4Cart(doc)
  end
  
  # カートに商品追加
  def addCart
    doc = Ecsex.item_addCart(params[:asin],session[:cart]).doc
    # 表示オブジェクト作成
    getShowObject4Cart(doc)
  end
  
  # カート作成
  def createCart
    doc = Ecsex.item_createCart_single(params[:asin]).doc
    ary = ['cartid', (doc/'cartid').inner_html,
           'urlencodedhmac', (doc/'urlencodedhmac').inner_html]
    session[:cart] = Hash[*ary]
    # 表示オブジェクト作成
    getShowObject4Cart(doc)
  end
  
  # カート表示オブジェクトの取得
  def getShowObject4Cart(doc)
    carts = (doc/"cartitems/cartitem").collect {|item| Amazon::Element.new(item)}
    rtnList = []
    carts.each do |cart|
      ary = ['asin', cart.get('asin'),
             'title', cart.get('title'),
             'price', cart.get('price/formattedprice'),
             'quantity', cart.get('quantity'),
             'totalprice', cart.get('price/amount').to_i * cart.get('quantity').to_i,
             'cartitemid', cart.get('cartitemid')
             ]
      rtnList.push(Hash[*ary])
    end
    prices = (doc/'subtotal').collect {|item| Amazon::Element.new(item)}
    prices.each do |price|
      @totalPrice = price.get('formattedprice')
    end
    rtnList
  end
end