google4r-checkout 1.0.2 → 1.0.3

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