google4r-checkout 1.0.2 → 1.0.3

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.
@@ -0,0 +1,94 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/utils.rb
4
+ # Author: Tony Chan <api.htchan@gmail.com>
5
+ # Copyright: (c) 2007 by Tony Chan
6
+ # License: MIT License as follows:
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to permit
13
+ # persons to whom the Software is furnished to do so, subject to the
14
+ # following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included
17
+ # in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
25
+ # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #++
27
+ # This file contains the classes and modules that are used by the command
28
+ # generating code.
29
+
30
+ require 'cgi'
31
+ require 'openssl'
32
+ require 'base64'
33
+
34
+ module Google4R #:nodoc:
35
+ module Checkout #:nodoc:
36
+ # HTML Signing
37
+ #
38
+ # Args:
39
+ # - params -- html form parameters
40
+ # - merchant_key -- Google Checkout merchant key
41
+ #
42
+ # Returns:
43
+ # - signature -- The base-64 encoded result of hashing the serialized
44
+ # parameters with the merchant key
45
+ #
46
+ # Example
47
+ # -------
48
+ # require 'google4r/checkout/utils'
49
+ #
50
+ # Google4R::Checkout.sign({:a=>'123', :b=>'456'}, 'merchantkey')
51
+ # => "5qBQYatFZk5BMS1hm5gSUS+9yrg="
52
+ #
53
+ def self.sign(params, merchant_key)
54
+ raise "params must be a Hash (e.g. {param1 => value1, param2 => value2, ...})" unless params.kind_of? Hash
55
+ raise "merchant_key must be a String" unless merchant_key.kind_of? String
56
+
57
+ # Remove unwanted parameters
58
+ params.delete_if do |key, value|
59
+ key = key.to_s
60
+ key == '_charset_' || key == 'analyticsdata' ||
61
+ key == 'urchindata' || key =~ /^(.+\.)*[xy]$/
62
+ end
63
+
64
+ # Strip away whitespaces and url-encode the values
65
+ params.each do |key, value|
66
+ params[key] = CGI::escape(value.to_s.strip)
67
+ end
68
+
69
+ # Sort parameters alphabetically by value and then key
70
+ params_arr = params.sort do |x, y|
71
+ if x[0] != y[0] then
72
+ x[0].to_s <=> y[0].to_s
73
+ else
74
+ x[1].to_s <=> y[1].to_s
75
+ end
76
+ end
77
+
78
+ # Create parameter string to be hashed
79
+ params_str = ''
80
+ params_arr.each do |x|
81
+ if params_str != '' then params_str += '&' end
82
+ params_str += x[0].to_s + '=' + x[1]
83
+ end
84
+
85
+ # Generate hashed signature
86
+ signature = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new,
87
+ merchant_key,
88
+ params_str)
89
+
90
+ # Encode the hash value in Base64 before returning it
91
+ return Base64.encode64(signature).chomp
92
+ end
93
+ end
94
+ end
@@ -35,6 +35,10 @@ module Google4R #:nodoc:
35
35
  module Checkout #:nodoc:
36
36
 
37
37
  class XmlGenerator
38
+ def initialize()
39
+ raise 'Cannot instantiate an abstract class.'
40
+ end
41
+
38
42
  # Base method to generate the XML for a particular command
39
43
  def generate
40
44
  @document = REXML::Document.new
@@ -52,27 +56,35 @@ module Google4R #:nodoc:
52
56
  # The list of command tag names
53
57
  COMMAND_TO_TAG =
54
58
  {
55
- ChargeOrderCommand => "charge-order",
56
- RefundOrderCommand => "refund-order",
57
- CancelOrderCommand => "cancel-order",
58
- AuthorizeOrderCommand => "authorize-order",
59
- ProcessOrderCommand => "process-order",
60
- AddMerchantOrderNumberCommand => "add-merchant-order-number",
61
- DeliverOrderCommand => "deliver-order",
62
- AddTrackingDataCommand => "add-tracking-data",
63
- SendBuyerMessageCommand => "send-buyer-message",
64
- ArchiveOrderCommand => "archive-order",
65
- UnarchiveOrderCommand => "unarchive-order"
59
+ ChargeOrderCommand => 'charge-order',
60
+ RefundOrderCommand => 'refund-order',
61
+ CancelOrderCommand => 'cancel-order',
62
+ AuthorizeOrderCommand => 'authorize-order',
63
+ ProcessOrderCommand => 'process-order',
64
+ AddMerchantOrderNumberCommand => 'add-merchant-order-number',
65
+ DeliverOrderCommand => 'deliver-order',
66
+ AddTrackingDataCommand => 'add-tracking-data',
67
+ SendBuyerMessageCommand => 'send-buyer-message',
68
+ ArchiveOrderCommand => 'archive-order',
69
+ UnarchiveOrderCommand => 'unarchive-order',
70
+ ShipItemsCommand => 'ship-items',
71
+ BackorderItemsCommand => 'backorder-items',
72
+ CancelItemsCommand => 'cancel-items',
73
+ ReturnItemsCommand => 'return-items',
74
+ ResetItemsShippingInformationCommand => 'reset-items-shipping-information'
66
75
  }
67
76
 
68
77
  def initialize(command)
69
- super()
70
- @command = command
78
+ if COMMAND_TO_TAG.has_key?(command.class)
79
+ @command = command
80
+ else
81
+ raise 'Cannot instantiate an abstract class.'
82
+ end
71
83
  end
72
84
 
73
85
  # Base method to generate the XML for a particular command
74
86
  def generate
75
- super()
87
+ super
76
88
  self.process_command(@command)
77
89
  io = StringIO.new
78
90
  @document.write(io, 0) # TODO: Maybe replace 0 by -1 so no spaces are inserted?
@@ -91,7 +103,7 @@ module Google4R #:nodoc:
91
103
  root = @document.add_element(tag_name)
92
104
  root.add_attribute('google-order-number', command.google_order_number)
93
105
  root.add_attribute('xmlns', 'http://checkout.google.com/schema/2')
94
- return root;
106
+ return root
95
107
  end
96
108
  end
97
109
 
@@ -111,6 +123,10 @@ module Google4R #:nodoc:
111
123
  #++
112
124
  class CheckoutCommandXmlGenerator < CommandXmlGenerator
113
125
 
126
+ def initialize(command)
127
+ @command = command
128
+ end
129
+
114
130
  protected
115
131
 
116
132
  def process_command(command)
@@ -171,6 +187,13 @@ module Google4R #:nodoc:
171
187
  command.shipping_methods.each do |shipping_method|
172
188
  self.process_shipping_method(shippings_element, shipping_method)
173
189
  end
190
+
191
+ # <analytics-data>
192
+ unless command.analytics_data.nil? then
193
+ analytics_element = flow_element.add_element('analytics-data')
194
+ analytics_element.text = command.analytics_data
195
+ end
196
+
174
197
  end
175
198
 
176
199
  # adds the tax-tables to the parent xml element
@@ -247,6 +270,12 @@ module Google4R #:nodoc:
247
270
  item_element.add_element('merchant-item-id').text = item.id
248
271
  end
249
272
 
273
+ if not item.weight.nil? then
274
+ item_element.add_element('item-weight',
275
+ { 'unit' => item.weight.unit,
276
+ 'value' => item.weight.value })
277
+ end
278
+
250
279
  if not item.private_data.nil? then
251
280
  self.process_hash(item_element.add_element('merchant-private-item-data'), item.private_data)
252
281
  end
@@ -256,6 +285,33 @@ module Google4R #:nodoc:
256
285
  if not item.tax_table.nil? then
257
286
  item_element.add_element('tax-table-selector').text = item.tax_table.name
258
287
  end
288
+
289
+ if not item.digital_content.nil? then
290
+ self.process_digital_content(item_element, item.digital_content)
291
+ end
292
+ end
293
+
294
+ # Adds a <digital-content> element to a parent (<item>) element
295
+ def process_digital_content(parent, digital_content)
296
+ digital_content_element = parent.add_element('digital-content')
297
+
298
+ if not digital_content.description.nil? then
299
+ digital_content_element.add_element('description').text = digital_content.description.to_s
300
+ end
301
+
302
+ if digital_content.email_delivery? then
303
+ digital_content_element.add_element('email-delivery').text = digital_content.email_delivery.to_s
304
+ end
305
+
306
+ if not digital_content.key.nil? then
307
+ digital_content_element.add_element('key').text = digital_content.key.to_s
308
+ end
309
+
310
+ if not digital_content.url.nil? then
311
+ digital_content_element.add_element('url').text = digital_content.url.to_s
312
+ end
313
+
314
+ digital_content_element.add_element('display-disposition').text = digital_content.display_disposition.to_s
259
315
  end
260
316
 
261
317
  # Adds an item for the given shipping method.
@@ -266,6 +322,8 @@ module Google4R #:nodoc:
266
322
  process_shipping('flat-rate-shipping', parent, shipping_method)
267
323
  elsif shipping_method.kind_of? MerchantCalculatedShipping then
268
324
  process_shipping('merchant-calculated-shipping', parent, shipping_method)
325
+ elsif shipping_method.kind_of? CarrierCalculatedShipping then
326
+ process_carrier_calculated_shipping('carrier-calculated-shipping', parent, shipping_method)
269
327
  else
270
328
  raise "Unknown ShippingMethod type of #{shipping_method.inspect}!"
271
329
  end
@@ -327,6 +385,67 @@ module Google4R #:nodoc:
327
385
  element.add_element('price', { 'currency' => shipping.price.currency }).text = shipping.price.to_s
328
386
  end
329
387
 
388
+ def process_carrier_calculated_shipping(shipping_type, parent, shipping)
389
+ element = parent.add_element(shipping_type)
390
+ options_element = element.add_element('carrier-calculated-shipping-options')
391
+ packages_element = element.add_element('shipping-packages')
392
+ shipping.carrier_calculated_shipping_options.each do | option |
393
+ process_carrier_calculated_shipping_option(options_element, option)
394
+ end
395
+ shipping.shipping_packages.each do | package |
396
+ process_shipping_package(packages_element, package)
397
+ end
398
+ end
399
+
400
+ def process_carrier_calculated_shipping_option(parent, option)
401
+ element = parent.add_element('carrier-calculated-shipping-option')
402
+ element.add_element('price', { 'currency' => option.price.currency }).text = option.price.to_s
403
+ element.add_element('shipping-company').text = option.shipping_company
404
+ element.add_element('shipping-type').text = option.shipping_type
405
+ if not option.carrier_pickup.nil?
406
+ element.add_element('carrier-pickup').text = option.carrier_pickup
407
+ end
408
+ if not option.additional_fixed_charge.nil?
409
+ element.add_element('additional-fixed-charge',
410
+ { 'currency' => option.additional_fixed_charge.currency }).text =
411
+ option.additional_fixed_charge.to_s
412
+ end
413
+ if not option.additional_variable_charge_percent.nil?
414
+ element.add_element('additional-variable-charge-percent').text =
415
+ option.additional_variable_charge_percent.to_s
416
+ end
417
+ end
418
+
419
+ def process_shipping_package(parent, package)
420
+ element = parent.add_element('shipping-package')
421
+ ship_from = package.ship_from
422
+ ship_from_element = element.add_element('ship-from')
423
+ ship_from_element.add_attribute('id', ship_from.address_id)
424
+ ship_from_element.add_element('city').text = ship_from.city
425
+ ship_from_element.add_element('region').text = ship_from.region
426
+ ship_from_element.add_element('country-code').text = ship_from.country_code
427
+ ship_from_element.add_element('postal-code').text = ship_from.postal_code
428
+ if not package.delivery_address_category.nil?
429
+ element.add_element('delivery-address-category').text =
430
+ package.delivery_address_category
431
+ end
432
+ if not package.height.nil?
433
+ height_element = element.add_element('height')
434
+ height_element.add_attribute('unit', package.height.unit)
435
+ height_element.add_attribute('value', package.height.value)
436
+ end
437
+ if not package.length.nil?
438
+ length_element = element.add_element('length')
439
+ length_element.add_attribute('unit', package.length.unit)
440
+ length_element.add_attribute('value', package.length.value)
441
+ end
442
+ if not package.width.nil?
443
+ width_element = element.add_element('width')
444
+ width_element.add_attribute('unit', package.width.unit)
445
+ width_element.add_attribute('value', package.width.value)
446
+ end
447
+ end
448
+
330
449
  # Adds an appropriate tag for the given Area subclass instance to the parent Element.
331
450
  def process_area(parent, area)
332
451
  if area.kind_of? UsZipArea then
@@ -458,7 +577,7 @@ module Google4R #:nodoc:
458
577
 
459
578
  if command.comment then
460
579
  root.add_element('comment').text = command.comment
461
- end
580
+ end
462
581
  end
463
582
 
464
583
  end
@@ -573,7 +692,7 @@ module Google4R #:nodoc:
573
692
  end
574
693
 
575
694
  def generate()
576
- super()
695
+ super
577
696
  process_results(@merchant_calculation_results.merchant_calculation_results)
578
697
  io = StringIO.new
579
698
  @document.write(io, 0) # TODO: Maybe replace 0 by -1 so no spaces are inserted?
@@ -629,5 +748,98 @@ module Google4R #:nodoc:
629
748
  element.add_element("message").text = merchant_code_result.message
630
749
  end
631
750
  end
751
+
752
+ class NotificationAcknowledgementXmlGenerator < XmlGenerator
753
+
754
+ def initialize(notification_acknowledgement)
755
+ @notification_acknowledgement = notification_acknowledgement
756
+ end
757
+
758
+ def generate
759
+ super
760
+ self.process_notification_acknowledgement(@notification_acknowledgement)
761
+ io = StringIO.new
762
+ @document.write(io, -1)
763
+ return io.string
764
+ end
765
+
766
+ def process_notification_acknowledgement(notification_acknowledgement)
767
+ root = @document.add_element('notification-acknowledgment')
768
+ root.add_attribute('xmlns', 'http://checkout.google.com/schema/2')
769
+ if not notification_acknowledgement.serial_number.nil?
770
+ root.add_attribute('serial-number', notification_acknowledgement.serial_number)
771
+ end
772
+ end
773
+ end
774
+
775
+ # Line-item shipping commands
776
+ class ItemsCommandXmlGenerator < CommandXmlGenerator
777
+ protected
778
+
779
+ def process_command(command)
780
+ root = super
781
+ process_item_info_arr(root, command.item_info_arr)
782
+ process_send_email(root, command.send_email)
783
+ return root
784
+ end
785
+
786
+ def process_item_info_arr(parent, item_info_arr)
787
+ element = parent.add_element('item-ids')
788
+ item_info_arr.each do |item_info|
789
+ item_id = element.add_element('item-id')
790
+ item_id.add_element('merchant-item-id').text =
791
+ item_info.merchant_item_id
792
+ end
793
+ end
794
+
795
+ def process_send_email(parent, send_email)
796
+ parent.add_element('send-email').text = send_email.to_s
797
+ end
798
+ end
799
+
800
+ class ShipItemsCommandXmlGenerator < ItemsCommandXmlGenerator
801
+ protected
802
+
803
+ def process_item_info_arr(parent, item_info_arr)
804
+ e1 = parent.add_element('item-shipping-information-list')
805
+ item_info_arr.each do |item_info|
806
+ e2 = e1.add_element('item-shipping-information')
807
+ item_id = e2.add_element('item-id')
808
+ item_id.add_element('merchant-item-id').text =
809
+ item_info.merchant_item_id
810
+ if !item_info.tracking_data_arr.nil?
811
+ e3 = e2.add_element('tracking-data-list')
812
+ item_info.tracking_data_arr.each do |tracking_data|
813
+ e4 = e3.add_element('tracking-data')
814
+ e4.add_element('carrier').text = tracking_data.carrier
815
+ e4.add_element('tracking-number').text =
816
+ tracking_data.tracking_number
817
+ end
818
+ end
819
+ end
820
+ end
821
+ end
822
+
823
+ class BackorderItemsCommandXmlGenerator < ItemsCommandXmlGenerator
824
+ end
825
+
826
+ class CancelItemsCommandXmlGenerator < ItemsCommandXmlGenerator
827
+ protected
828
+
829
+ def process_command(command)
830
+ root = super
831
+ root.add_element('reason').text = command.reason
832
+
833
+ if command.comment then
834
+ root.add_element('comment').text = command.comment
835
+ end
836
+ end
837
+ end
838
+
839
+ class ReturnItemsCommandXmlGenerator < ItemsCommandXmlGenerator
840
+ end
841
+
842
+ class ResetItemsShippingInformationCommandXmlGenerator < ItemsCommandXmlGenerator
843
+ end
632
844
  end
633
845
  end
@@ -49,6 +49,22 @@ class Google4R::Checkout::CheckoutCommandIntegrationTest < Test::Unit::TestCase
49
49
  assert_kind_of CheckoutRedirectResponse, result
50
50
  end
51
51
 
52
+ def test_sending_to_google_works_with_merchant_calculated_shipping
53
+ setup_command(@command, MerchantCalculatedShipping)
54
+ result = @command.send_to_google_checkout
55
+ assert_kind_of CheckoutRedirectResponse, result
56
+ end
57
+
58
+ def test_sending_to_google_works_with_carrier_calculated_shipping
59
+ setup_command(@command, CarrierCalculatedShipping)
60
+ result = @command.send_to_google_checkout
61
+ # Uncomment the two lines below to see the shopping cart xml and
62
+ # the redirect URL
63
+ #puts @command.to_xml
64
+ #puts result
65
+ assert_kind_of CheckoutRedirectResponse, result
66
+ end
67
+
52
68
  def test_using_invalid_credentials_raise_google_checkout_error
53
69
  invalid_patches = [ [ :merchant_id, 'invalid' ], [ :merchant_key, 'invalid' ] ]
54
70
 
@@ -77,9 +93,11 @@ class Google4R::Checkout::CheckoutCommandIntegrationTest < Test::Unit::TestCase
77
93
 
78
94
  protected
79
95
 
80
- # Sets up the given CheckoutCommand so it contains some
81
- # shipping methods and its cart contains some items.
82
- def setup_command(command)
96
+ # Sets up the given CheckoutCommand so it contains some
97
+ # shipping methods and its cart contains some items.
98
+ def setup_command(command, shipping_type=FlatRateShipping)
99
+
100
+ if shipping_type == FlatRateShipping
83
101
  # Add shipping methods.
84
102
  command.create_shipping_method(FlatRateShipping) do |shipping|
85
103
  shipping.name = 'UPS Ground Shipping'
@@ -88,16 +106,59 @@ class Google4R::Checkout::CheckoutCommandIntegrationTest < Test::Unit::TestCase
88
106
  area.area = UsCountryArea::ALL
89
107
  end
90
108
  end
109
+ end
110
+
111
+ if shipping_type == MerchantCalculatedShipping
112
+ command.merchant_calculations_url = 'http://www.example.com'
91
113
 
92
- # Add items to the cart.
93
- 1.upto(5) do |i|
94
- command.shopping_cart.create_item do |item|
95
- item.name = "Test Item #{i}"
96
- item.description = "This is a test item (#{i})"
97
- item.unit_price = Money.new(350)
98
- item.quantity = i * 3
99
- item.id = "test-#{i}-123456789"
114
+ command.create_shipping_method(MerchantCalculatedShipping) do |shipping|
115
+ shipping.name = 'International Shipping'
116
+ shipping.price = Money.new(2000)
117
+ shipping.create_address_filters_allowed_area(PostalArea) do |area|
118
+ area.country_code = 'US'
119
+ area.postal_code_pattern = '12*'
120
+ end
121
+ end
122
+ end
123
+
124
+ if shipping_type == CarrierCalculatedShipping
125
+ command.create_shipping_method(CarrierCalculatedShipping) do |shipping|
126
+ shipping.create_carrier_calculated_shipping_option do | option |
127
+ option.shipping_company =
128
+ CarrierCalculatedShipping::CarrierCalculatedShippingOption::FEDEX
129
+ option.price = Money.new(3000)
130
+ option.shipping_type = 'Priority Overnight'
131
+ option.carrier_pickup = 'REGULAR_PICKUP'
132
+ option.additional_fixed_charge = Money.new(500)
133
+ option.additional_variable_charge_percent = 15.5
134
+ end
135
+ shipping.create_shipping_package do | package |
136
+ ship_from = AnonymousAddress.new
137
+ ship_from.address_id = 'ABC'
138
+ ship_from.city = 'Ann Arbor'
139
+ ship_from.region = 'MI'
140
+ ship_from.country_code = 'US'
141
+ ship_from.postal_code = '48104'
142
+ package.ship_from = ship_from
143
+ package.delivery_address_category =
144
+ CarrierCalculatedShipping::ShippingPackage::COMMERCIAL
145
+ package.height = Dimension.new(1)
146
+ package.length = Dimension.new(2)
147
+ package.width = Dimension.new(3)
100
148
  end
101
149
  end
102
150
  end
151
+
152
+ # Add items to the cart.
153
+ 1.upto(5) do |i|
154
+ command.shopping_cart.create_item do |item|
155
+ item.name = "Test Item #{i}"
156
+ item.description = "This is a test item (#{i})"
157
+ item.unit_price = Money.new(350)
158
+ item.quantity = i * 3
159
+ item.id = "test-#{i}-123456789"
160
+ item.weight = Weight.new(2.2)
161
+ end
162
+ end
163
+ end
103
164
  end