Class: GoogleCheckout::Cart

Inherits:
Command
  • Object
show all
Includes:
GoogleCheckout
Defined in:
lib/google-checkout/cart.rb

Overview

This class represents a cart for Google Checkout. After initializing it with a merchant_id and merchant_key, you add items via add_item, and can then get xml via to_xml, or html code for a form that provides a checkout button via checkout_button.

Example:

item = {
         :name => 'A Quarter',
         :description => 'One shiny quarter.',
         :price => 0.25
       }
@cart = GoogleCheckout::Cart.new(merchant_id, merchant_key, item)
@cart.add_item(:name => "Pancakes",
               :description => "Flapjacks by mail."
               :price => 0.50,
               :quantity => 10,
               "merchant-item-id" => '2938292839')

Then in your view:

Checkout here! <%= @cart.checkout_button %>

This object is also useful for getting back a url to the image for a Google Checkout button. You can use this image in forms that submit back to your own server for further processing via Google Checkout’s level 2 XML API.

Constant Summary collapse

SANDBOX_CHECKOUT_URL =
"https://sandbox.google.com/checkout/cws/v2/Merchant/%s/checkout"
PRODUCTION_CHECKOUT_URL =
"https://checkout.google.com/cws/v2/Merchant/%s/checkout"
DefaultButtonOpts =

The default options for drawing in the button that are filled in when checkout_button or button_url is called.

{
  :size => :medium,
  :style => 'white',
  :variant => 'text',
  :loc => 'en_US',
  :buy_or_checkout => nil,
}

Constants included from GoogleCheckout

ButtonSizes, VERSION

Constants inherited from Command

GoogleCheckout::Command::PRODUCTION_REQUEST_URL, GoogleCheckout::Command::SANDBOX_REQUEST_URL

Instance Attribute Summary collapse

Attributes inherited from Command

#currency, #merchant_id, #merchant_key

Instance Method Summary collapse

Methods included from GoogleCheckout

production?, sandbox?, use_production, use_sandbox

Methods inherited from Command

#post, #url, x509_store

Constructor Details

#initialize(merchant_id, merchant_key, *items) ⇒ Cart

You need to supply, as strings, the merchant_id and merchant_key used to identify your store to Google. You may optionally supply one or more items to put inside the cart.



86
87
88
89
# File 'lib/google-checkout/cart.rb', line 86

def initialize(merchant_id, merchant_key, *items)
  super(merchant_id, merchant_key)
  items.each { |i| add_item i }
end

Instance Attribute Details

#continue_shopping_urlObject

Returns the value of attribute continue_shopping_url.



66
67
68
# File 'lib/google-checkout/cart.rb', line 66

def continue_shopping_url
  @continue_shopping_url
end

#edit_cart_urlObject

You can provide extra data that will be sent to Google and returned with the NewOrderNotification.

This should be a Hash and will be turned into XML with proper escapes.

Beware using symbols as values. They may be set as sub-keys instead of values, so use a String or other datatype.



65
66
67
# File 'lib/google-checkout/cart.rb', line 65

def edit_cart_url
  @edit_cart_url
end

#merchant_calculations_urlObject

Returns the value of attribute merchant_calculations_url.



68
69
70
# File 'lib/google-checkout/cart.rb', line 68

def merchant_calculations_url
  @merchant_calculations_url
end

#parameterized_urlsObject



115
116
117
# File 'lib/google-checkout/cart.rb', line 115

def parameterized_urls
  @parameterized_urls ||= []
end

#shipping_methodsObject



119
120
121
# File 'lib/google-checkout/cart.rb', line 119

def shipping_methods
  @shipping_methods ||= []
end

Instance Method Details

#add_item(item) ⇒ Object

This method puts items in the cart. item may be a hash, or have a method named to_google_product that returns a hash with the required values.

  • name

  • description (a brief description as it will appear on the bill)

  • price

You may fill in some optional values as well:

  • quantity (defaults to 1)

  • currency (defaults to ‘USD’)

  • subscription

** type (defaults to ‘google’) ** period (defaults to ‘MONTHLY’) ** start_date ** no_charge_after ** payment_times ** max_charge ** name ** description ** price ** quantity

  • digital_content

** disposition ** description



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/google-checkout/cart.rb', line 165

def add_item(item)
  @xml = nil
  if item.respond_to? :to_google_product
    item = item.to_google_product
  end

  # We need to check that the necessary keys are in the hash,
  # Otherwise the error will happen in the middle of to_xml,
  # and the bug will be harder to track.
  missing_keys = [ :name, :description, :price ].select { |key|
    !item.include? key
  }

  unless missing_keys.empty?
    raise ArgumentError,
    "Required keys missing: #{missing_keys.inspect}"
  end

  contents << { :quantity => 1, :currency => 'USD' }.merge(item)
  item
end

#button_url(opts = {}) ⇒ Object

Given a set of options for the button, button_url returns the URL for the button image. The options are the same as those specified on checkout.google.com/seller/checkout_buttons.html , with a couple of extra options for convenience. Rather than specifying the width and height manually, you may specify :size to be one of :small, :medium, or :large, and that you may set :buy_or_checkout to :buy_now or :checkout to get a ‘Buy Now’ button versus a ‘Checkout’ button. If you don’t specify :buy_or_checkout, the Cart will try to guess based on if the cart has more than one item in it. Whatever you don’t pass will be filled in with the defaults from DefaultButtonOpts.

http://checkout.google.com/buttons/checkout.gif
http://sandbox.google.com/checkout/buttons/checkout.gif


380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/google-checkout/cart.rb', line 380

def button_url(opts = {})
  opts = DefaultButtonOpts.merge opts
  opts[:buy_or_checkout] ||= contents.size > 1 ? :checkout : :buy_now
  opts.merge! ButtonSizes[opts[:buy_or_checkout]][opts[:size]]
  bname = opts[:buy_or_checkout] == :buy_now ? 'buy.gif' : 'checkout.gif'
  opts.delete :size
  opts.delete :buy_or_checkout
  opts[:merchant_id] = @merchant_id

  path = opts.map { |k,v| "#{k}=#{v}" }.join('&')

  # HACK Sandbox graphics are in the checkout subdirectory
  subdir = ""
  if GoogleCheckout.sandbox? && (bname == "checkout.gif" || bname == "buy.gif")
    subdir = "checkout/"
  end

  protocol = opts[:https] ? 'https' : 'http'

  # TODO Use /checkout/buttons/checkout.gif if in sandbox.
  "#{protocol}://#{submit_domain}/#{ subdir }buttons/#{bname}?#{path}"
end

#checkout_button(button_opts = {}) ⇒ Object

Returns HTML for a checkout form for buying all the items in the cart.



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/google-checkout/cart.rb', line 335

def checkout_button(button_opts = {})
  @xml or to_xml
  burl = button_url(button_opts)
  html = Builder::XmlMarkup.new(:indent => 2)
  html.form({
    :action => submit_url,
    :style => 'border: 0;',
    :id => 'BB_BuyButtonForm',
    :method => 'post',
    :name => 'BB_BuyButtonForm'
  }) do
    html.input({
      :name => 'cart',
      :type => 'hidden',
      :value => Base64.encode64(@xml).gsub("\n", '')
    })
    html.input({
      :name => 'signature',
      :type => 'hidden',
      :value => Base64.encode64(signature).gsub("\n", '')
    })
    html.input({
      :alt => 'Google Checkout',
      :style => "width: auto;",
      :src => button_url(button_opts),
      :type => 'image'
    })
  end
end

#contentsObject



95
96
97
# File 'lib/google-checkout/cart.rb', line 95

def contents
  @contents ||= []
end

#empty?Boolean

Returns:

  • (Boolean)


91
92
93
# File 'lib/google-checkout/cart.rb', line 91

def empty?
  contents.empty?
end

#merchant_private_dataObject



99
100
101
# File 'lib/google-checkout/cart.rb', line 99

def merchant_private_data
  @merchant_private_data ||= ''
end

#merchant_private_data=(value) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/google-checkout/cart.rb', line 103

def merchant_private_data=(value)
  if value.is_a?(Hash)
    xml = Builder::XmlMarkup.new
    value.each do |key, value|
      xml.tag!(key, value)
    end
    @merchant_private_data = xml.target!
  elsif value.is_a?(String)
    @merchant_private_data = value
  end
end

#signatureObject

Returns the signature for the cart XML.



327
328
329
330
331
# File 'lib/google-checkout/cart.rb', line 327

def signature
  @xml or to_xml
  digest = OpenSSL::Digest::Digest.new('sha1')
  OpenSSL::HMAC.digest(digest, @merchant_key, @xml)
end

#sizeObject

Number of items in the cart.



124
125
126
# File 'lib/google-checkout/cart.rb', line 124

def size
  contents.size
end

#submit_domainObject



128
129
130
# File 'lib/google-checkout/cart.rb', line 128

def submit_domain
  (GoogleCheckout.production? ? 'checkout' : 'sandbox') + ".google.com"
end

#submit_urlObject

The Google Checkout form submission url.



135
136
137
# File 'lib/google-checkout/cart.rb', line 135

def submit_url
  GoogleCheckout.sandbox? ? (SANDBOX_CHECKOUT_URL % @merchant_id) : (PRODUCTION_CHECKOUT_URL % @merchant_id)
end

#to_xmlObject

This is the important method; it generatest the XML call. It’s fairly lengthy, but trivial. It follows the docs at code.google.com/apis/checkout/developer/index.html#checkout_api

It returns the raw XML string, not encoded.

Raises:

  • (RuntimeError)


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/google-checkout/cart.rb', line 192

def to_xml
  raise RuntimeError, "Empty cart" if self.empty?

  xml = Builder::XmlMarkup.new
  xml.instruct!
  @xml = xml.tag!('checkout-shopping-cart', :xmlns => "http://checkout.google.com/schema/2") {
    xml.tag!("shopping-cart") {
      xml.items {
        contents.each { |item|
          xml.item {
            if item.key?(:item_id)
              xml.tag!('merchant-item-id', item[:item_id])
            end
            xml.tag!('item-name') {
              xml.text! item[:name].to_s
            }
            xml.tag!('item-description') {
              xml.text! item[:description].to_s
            }
            xml.tag!('unit-price', :currency => (item[:currency] || 'USD')) {
              xml.text! item[:price].to_s
            }
            xml.quantity {
              xml.text! item[:quantity].to_s
            }
            
            # subscription item
            if item.key?(:subscription)
              sub = item[:subscription]
              
              sub_opts = {}
              sub_opts.merge!(:type => sub[:type].to_s) if sub[:type]
              sub_opts.merge!(:period => sub[:period].to_s) if sub[:period]
              sub_opts.merge!(:"start-date" => sub[:start_date].to_s) if sub[:start_date] 
              sub_opts.merge!(:"no-charge-after" => sub[:no_charge_after].to_s) if sub[:no_charge_after]
              
              xml.subscription(sub_opts) {
                xml.payments {
                  if sub.key?(:payment_times)
                    xml.tag!('subscription-payment', :times => sub[:payment_times].to_s) {
                      xml.tag!('maximum-charge', :currency => (item[:currency] || 'USD')) {
                        xml.text! sub[:max_charge].to_s
                      }
                    }
                  else
                    xml.tag!('subscription-payment') {
                      xml.tag!('maximum-charge', :currency => (item[:currency] || 'USD')) {
                        xml.text! sub[:max_charge].to_s
                      }
                    }
                  end
                }
                
                xml.tag!('recurrent-item') {
                  xml.tag!('item-name') {
                    xml.text! sub[:name].to_s
                  }
                  xml.tag!('item-description') {
                    xml.text! sub[:description].to_s
                  }
                  xml.tag!('unit-price', :currency => (item[:currency] || 'USD')) {
                    xml.text! sub[:price].to_s
                  }
                  xml.quantity {
                    xml.text! sub[:quantity].to_s
                  }
                }
              }
            end
            
            # digital delivery
            if item.key?(:digital_content)
              content = item[:digital_content]
              
              xml.tag!('digital-content') {
                xml.tag!('display-disposition') {
                  xml.text! content[:disposition]
                }
                xml.description {
                  xml.text! content[:description].to_s
                } if content[:description]
              }
            end
            
            xml.tag!('merchant-private-item-data') {
              xml << item[:merchant_private_item_data]
            } if item.key?(:merchant_private_item_data)
          }
        }
      }
      unless merchant_private_data.empty?
        xml.tag!("merchant-private-data") {
          xml << merchant_private_data
        }
      end
    }
    xml.tag!('checkout-flow-support') {
      xml.tag!('merchant-checkout-flow-support') {
        xml.tag!('edit-cart-url', @edit_cart_url) if @edit_cart_url
        xml.tag!('continue-shopping-url', @continue_shopping_url) if @continue_shopping_url
        xml.tag!("request-buyer-phone-number", false)
        xml.tag!('merchant-calculations') {
          xml.tag!('merchant-calculations-url') {
            xml.text! @merchant_calculations_url
          }
        } if @merchant_calculations_url

        # TODO tax-tables

        xml.tag!('parameterized-urls') {
          parameterized_urls.each do |param_url|
            xml.tag!('parameterized-url', :url => param_url[:url]) {
              next if param_url[:parameters].nil?
              next if param_url[:parameters].empty?
              xml.tag!('parameters') {
                param_url[:parameters].each do |parameter|
                  xml.tag!('url-parameter', :name => parameter[:name], :type => parameter[:type]) {}
                end
              }
            }
          end
        } unless parameterized_urls.empty?

        xml.tag!('shipping-methods') {
          shipping_methods.each do |shipping_method|
            xml << shipping_method.to_xml
          end
        }
      }
    }
  }
  @xml.dup
end