archerfinley-google4r-checkout-1.0.5 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/CHANGES +93 -0
  2. data/LICENSE +22 -0
  3. data/README +58 -0
  4. data/Rakefile +13 -0
  5. data/VERSION +1 -0
  6. data/google4r-checkout-1.0.5-1.0.5.gem +0 -0
  7. data/google4r-checkout-1.0.5.gemspec +174 -0
  8. data/lib/google4r/checkout.rb +34 -0
  9. data/lib/google4r/checkout/commands.rb +567 -0
  10. data/lib/google4r/checkout/frontend.rb +210 -0
  11. data/lib/google4r/checkout/merchant_calculation.rb +321 -0
  12. data/lib/google4r/checkout/notifications.rb +708 -0
  13. data/lib/google4r/checkout/shared.rb +1086 -0
  14. data/lib/google4r/checkout/utils.rb +94 -0
  15. data/lib/google4r/checkout/xml_generation.rb +880 -0
  16. data/test/integration/checkout_command_test.rb +174 -0
  17. data/test/unit/add_merchant_order_number_command_test.rb +70 -0
  18. data/test/unit/add_tracking_data_command_test.rb +75 -0
  19. data/test/unit/address_test.rb +131 -0
  20. data/test/unit/anonymous_address_test.rb +75 -0
  21. data/test/unit/archive_order_command_test.rb +66 -0
  22. data/test/unit/area_test.rb +44 -0
  23. data/test/unit/authorization_amount_notification_test.rb +69 -0
  24. data/test/unit/authorize_order_command_test.rb +66 -0
  25. data/test/unit/backorder_items_command_test.rb +83 -0
  26. data/test/unit/callback_handler_test.rb +83 -0
  27. data/test/unit/cancel_items_command_test.rb +89 -0
  28. data/test/unit/cancel_order_command_test.rb +83 -0
  29. data/test/unit/carrier_calculated_shipping_test.rb +57 -0
  30. data/test/unit/charge_amount_notification_test.rb +64 -0
  31. data/test/unit/charge_order_command_test.rb +77 -0
  32. data/test/unit/chargeback_amount_notification_test.rb +65 -0
  33. data/test/unit/checkout_command_test.rb +125 -0
  34. data/test/unit/checkout_command_xml_generator_test.rb +218 -0
  35. data/test/unit/command_test.rb +116 -0
  36. data/test/unit/deliver_order_command_test.rb +70 -0
  37. data/test/unit/delivery_method_test.rb +42 -0
  38. data/test/unit/digital_content_test.rb +105 -0
  39. data/test/unit/flat_rate_shipping_test.rb +132 -0
  40. data/test/unit/frontend_test.rb +144 -0
  41. data/test/unit/item_info_test.rb +69 -0
  42. data/test/unit/item_test.rb +171 -0
  43. data/test/unit/marketing_preferences_test.rb +65 -0
  44. data/test/unit/merchant_calculated_shipping_test.rb +172 -0
  45. data/test/unit/merchant_calculation_callback_test.rb +137 -0
  46. data/test/unit/merchant_calculation_result_test.rb +78 -0
  47. data/test/unit/merchant_calculation_results_test.rb +178 -0
  48. data/test/unit/merchant_code_result_test.rb +51 -0
  49. data/test/unit/merchant_code_test.rb +122 -0
  50. data/test/unit/new_order_notification_test.rb +115 -0
  51. data/test/unit/notification_acknowledgement_test.rb +67 -0
  52. data/test/unit/notification_handler_test.rb +113 -0
  53. data/test/unit/order_adjustment_test.rb +119 -0
  54. data/test/unit/order_report_command_test.rb +111 -0
  55. data/test/unit/order_state_change_notification_test.rb +158 -0
  56. data/test/unit/pickup_shipping_test.rb +70 -0
  57. data/test/unit/postal_area_test.rb +71 -0
  58. data/test/unit/private_data_parser_test.rb +68 -0
  59. data/test/unit/refund_amount_notification_test.rb +65 -0
  60. data/test/unit/refund_order_command_test.rb +86 -0
  61. data/test/unit/reset_items_shipping_information_command_test.rb +83 -0
  62. data/test/unit/return_items_command_test.rb +83 -0
  63. data/test/unit/risk_information_notification_test.rb +98 -0
  64. data/test/unit/send_buyer_message_command_test.rb +73 -0
  65. data/test/unit/ship_items_command_test.rb +101 -0
  66. data/test/unit/shipping_adjustment_test.rb +100 -0
  67. data/test/unit/shopping_cart_test.rb +146 -0
  68. data/test/unit/tax_rule_test.rb +70 -0
  69. data/test/unit/tax_table_test.rb +82 -0
  70. data/test/unit/tracking_data_test.rb +54 -0
  71. data/test/unit/unarchive_order_command_test.rb +66 -0
  72. data/test/unit/us_country_area_test.rb +76 -0
  73. data/test/unit/us_state_area_test.rb +70 -0
  74. data/test/unit/us_zip_area_test.rb +66 -0
  75. data/test/unit/world_area_test.rb +48 -0
  76. data/var/cacert.pem +7815 -0
  77. metadata +189 -0
@@ -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
@@ -0,0 +1,880 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/xml_generation.rb
4
+ # Authors: Manuel Holtgrewe <purestorm at ggnore dot net>
5
+ # Tony Chan <api.htchan at gmail dot com>
6
+ # Copyright: (c) 2007 by Manuel Holtgrewe
7
+ # License: MIT License as follows:
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to permit
14
+ # persons to whom the Software is furnished to do so, subject to the
15
+ # following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included
18
+ # in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
26
+ # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #++
28
+ # This file contains the classes that allow to persist the object hierarchies
29
+ # that are created with the Google Checkout API to XML.
30
+
31
+ require 'stringio'
32
+ require 'rexml/document'
33
+
34
+ module Google4R #:nodoc:
35
+ module Checkout #:nodoc:
36
+
37
+ class XmlGenerator
38
+ def initialize()
39
+ raise 'Cannot instantiate an abstract class.'
40
+ end
41
+
42
+ # Base method to generate the XML for a particular command
43
+ def generate
44
+ @document = REXML::Document.new
45
+
46
+ declaration = REXML::XMLDecl.new
47
+ declaration.encoding = 'utf-8'
48
+ @document << declaration
49
+ end
50
+ end
51
+
52
+ # Abstract super class for all CommandXmlGenerators
53
+ # It should never be instantiated directly
54
+ class CommandXmlGenerator < XmlGenerator
55
+
56
+ # The list of command tag names
57
+ COMMAND_TO_TAG =
58
+ {
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',
75
+ OrderReportCommand => 'order-list-request',
76
+ }
77
+
78
+ def initialize(command)
79
+ if COMMAND_TO_TAG.has_key?(command.class)
80
+ @command = command
81
+ else
82
+ raise 'Cannot instantiate an abstract class.'
83
+ end
84
+ end
85
+
86
+ # Base method to generate the XML for a particular command
87
+ def generate
88
+ super
89
+ self.process_command(@command)
90
+ io = StringIO.new
91
+ @document.write(io, 0) # TODO: Maybe replace 0 by -1 so no spaces are inserted?
92
+ return io.string
93
+ end
94
+
95
+ def tag_name_for_command(klass)
96
+ COMMAND_TO_TAG[klass]
97
+ end
98
+
99
+ protected
100
+
101
+ # Base method to generate root tag of a command
102
+ def process_command(command)
103
+ tag_name = self.tag_name_for_command(command.class)
104
+ root = @document.add_element(tag_name)
105
+ root.add_attribute('google-order-number', command.google_order_number)
106
+ root.add_attribute('xmlns', 'http://checkout.google.com/schema/2')
107
+ return root
108
+ end
109
+ end
110
+
111
+ # Use the CheckoutXmlGenerator to create an XML document from a CheckoutCommand
112
+ # object.
113
+ #
114
+ # Usage:
115
+ #
116
+ # checkout = CheckoutCommand.new
117
+ # # set up the CheckoutCommand
118
+ #
119
+ # generator = CheckoutCommandXmlGenerator.new(checkout)
120
+ # puts generator.generate # => "<xml? version=..."
121
+ # File.new('some.xml', 'w') { |f| f.write generator.generate }
122
+ #--
123
+ # TODO: Refactor the big, monolitic generator into smaller, easier testable ones. One for each major part of the resulting XML document. This will also reduce the overhead in generating other types of XML documents.
124
+ #++
125
+ class CheckoutCommandXmlGenerator < CommandXmlGenerator
126
+
127
+ def initialize(command)
128
+ @command = command
129
+ end
130
+
131
+ protected
132
+
133
+ def process_command(command)
134
+ root = @document.add_element("checkout-shopping-cart" , { 'xmlns' => 'http://checkout.google.com/schema/2' })
135
+
136
+ self.process_shopping_shopping_cart(root, command.shopping_cart)
137
+
138
+ # <merchant-checkout-flow-support>
139
+ flow_element = root.add_element('checkout-flow-support').add_element('merchant-checkout-flow-support')
140
+
141
+ # <tax-tables>
142
+ if command.tax_tables then
143
+ self.process_tax_tables(command.tax_tables, flow_element)
144
+ end
145
+
146
+ # <continue-shopping-url>
147
+ if not command.continue_shopping_url.nil? then
148
+ flow_element.add_element('continue-shopping-url').text = command.continue_shopping_url
149
+ end
150
+
151
+ # <edit-cart-url>
152
+ if not command.edit_cart_url.nil? then
153
+ flow_element.add_element('edit-cart-url').text = command.edit_cart_url
154
+ end
155
+
156
+ # <request-buyer-phone-number>
157
+ if not command.request_buyer_phone_number.nil? then
158
+ flow_element.add_element('request-buyer-phone-number').text =
159
+ if command.request_buyer_phone_number then
160
+ "true"
161
+ else
162
+ "false"
163
+ end
164
+ end
165
+
166
+ # <merchant-calculations>
167
+ if not command.merchant_calculations_url.nil? then
168
+ merchant_calculations = flow_element.add_element('merchant-calculations')
169
+ merchant_calculations.add_element('merchant-calculations-url').text =
170
+ command.merchant_calculations_url
171
+ if not command.accept_merchant_coupons.nil? then
172
+ merchant_calculations.add_element('accept-merchant-coupons').text =
173
+ command.accept_merchant_coupons.to_s
174
+ end
175
+ if not command.accept_gift_certificates.nil? then
176
+ merchant_calculations.add_element('accept-gift-certificates').text =
177
+ command.accept_gift_certificates.to_s
178
+ end
179
+ end
180
+
181
+ # <platform-id>
182
+ if not command.platform_id.nil? then
183
+ flow_element.add_element('platform-id').text = command.platform_id
184
+ end
185
+
186
+ # <shipping-methods>
187
+ shippings_element = flow_element.add_element('shipping-methods')
188
+ command.shipping_methods.each do |shipping_method|
189
+ self.process_shipping_method(shippings_element, shipping_method)
190
+ end
191
+
192
+ # <analytics-data>
193
+ unless command.analytics_data.nil? then
194
+ analytics_element = flow_element.add_element('analytics-data')
195
+ analytics_element.text = command.analytics_data
196
+ end
197
+
198
+ end
199
+
200
+ # adds the tax-tables to the parent xml element
201
+ # assumes that the first member of the tax_tables array is the default tax rule table
202
+ # and that all others are alternate tax rules
203
+ def process_tax_tables(tax_tables, parent)
204
+ tax_tables_element = parent.add_element('tax-tables')
205
+
206
+ # assumes that the first tax table is the default
207
+ default_table = tax_tables.first
208
+
209
+ default_table_element = tax_tables_element.add_element('default-tax-table')
210
+ rules_element = default_table_element.add_element('tax-rules')
211
+
212
+ default_table.rules.each do |rule|
213
+ default_rule_element = rules_element.add_element('default-tax-rule')
214
+ default_rule_element.add_element('shipping-taxed').text=rule.shipping_taxed.to_s
215
+ default_rule_element.add_element('rate').text=rule.rate.to_s
216
+ self.process_area(default_rule_element.add_element('tax-area'), rule.area)
217
+ end
218
+
219
+ # populate alternate tax tables
220
+ alt_tables = tax_tables.last(tax_tables.length-1)
221
+ alt_tables_element = tax_tables_element.add_element('alternate-tax-tables')
222
+
223
+ alt_tables.each do |table|
224
+ table_element = alt_tables_element.add_element('alternate-tax-table')
225
+ table_element.add_attribute('name', table.name)
226
+ table_element.add_attribute('standalone', table.standalone.to_s)
227
+
228
+ rules_element = table_element.add_element('alternate-tax-rules')
229
+ table.rules.each do |rule|
230
+ alt_rule_element = rules_element.add_element('alternate-tax-rule')
231
+ alt_rule_element.add_element('rate').text=rule.rate.to_s
232
+
233
+ self.process_area(alt_rule_element.add_element('tax-area'), rule.area)
234
+ end
235
+ end
236
+ end
237
+
238
+
239
+ def process_shopping_shopping_cart(parent, shopping_cart)
240
+ cart_element = parent.add_element('shopping-cart')
241
+
242
+ # add <cart-expiration> tag to the cart if a time has been added to the cart
243
+ if not shopping_cart.expires_at.nil? then
244
+ cart_element.add_element('cart-expiration').add_element('good-until-date').text =
245
+ shopping_cart.expires_at.iso8601
246
+ end
247
+
248
+ # add <merchant-private-data> to the cart if any has been set
249
+ if not shopping_cart.private_data.nil? then
250
+ self.process_hash(cart_element.add_element('merchant-private-data'), shopping_cart.private_data)
251
+ end
252
+
253
+ # process the items in the cart
254
+ items_element = cart_element.add_element('items')
255
+ shopping_cart.items.each do |item|
256
+ self.process_item(items_element, item)
257
+ end
258
+ end
259
+
260
+ # Adds an <item> tag to the tag parent with the appropriate values.
261
+ def process_item(parent, item)
262
+ item_element = parent.add_element('item')
263
+
264
+ item_element.add_element('item-name').text = item.name
265
+ item_element.add_element('item-description').text = item.description
266
+
267
+ item_element.add_element('unit-price', { 'currency' => item.unit_price.currency }).text = item.unit_price.to_s
268
+ item_element.add_element('quantity').text = item.quantity.to_i
269
+
270
+ if not item.id.nil? then
271
+ item_element.add_element('merchant-item-id').text = item.id
272
+ end
273
+
274
+ if not item.weight.nil? then
275
+ item_element.add_element('item-weight',
276
+ { 'unit' => item.weight.unit,
277
+ 'value' => item.weight.value })
278
+ end
279
+
280
+ if not item.private_data.nil? then
281
+ self.process_hash(item_element.add_element('merchant-private-item-data'), item.private_data)
282
+ end
283
+
284
+ # The above was easy; now we need to get the appropriate tax table for this
285
+ # item. The Item class makes sure that the table exists.
286
+ if not item.tax_table.nil? then
287
+ item_element.add_element('tax-table-selector').text = item.tax_table.name
288
+ end
289
+
290
+ if not item.digital_content.nil? then
291
+ self.process_digital_content(item_element, item.digital_content)
292
+ end
293
+ end
294
+
295
+ # Adds a <digital-content> element to a parent (<item>) element
296
+ def process_digital_content(parent, digital_content)
297
+ digital_content_element = parent.add_element('digital-content')
298
+
299
+ if not digital_content.description.nil? then
300
+ digital_content_element.add_element('description').text = digital_content.description.to_s
301
+ end
302
+
303
+ if digital_content.email_delivery.nil? then
304
+ digital_content_element.add_element('email-delivery').text = digital_content.email_delivery.to_s
305
+ end
306
+
307
+ if not digital_content.key.nil? then
308
+ digital_content_element.add_element('key').text = digital_content.key.to_s
309
+ end
310
+
311
+ if not digital_content.url.nil? then
312
+ digital_content_element.add_element('url').text = digital_content.url.to_s
313
+ end
314
+
315
+ digital_content_element.add_element('display-disposition').text = digital_content.display_disposition.to_s
316
+ end
317
+
318
+ # Adds an item for the given shipping method.
319
+ def process_shipping_method(parent, shipping_method)
320
+ if shipping_method.kind_of? PickupShipping then
321
+ process_pickup(parent, shipping_method)
322
+ elsif shipping_method.kind_of? FlatRateShipping then
323
+ process_shipping('flat-rate-shipping', parent, shipping_method)
324
+ elsif shipping_method.kind_of? MerchantCalculatedShipping then
325
+ process_shipping('merchant-calculated-shipping', parent, shipping_method)
326
+ elsif shipping_method.kind_of? CarrierCalculatedShipping then
327
+ process_carrier_calculated_shipping('carrier-calculated-shipping', parent, shipping_method)
328
+ else
329
+ raise "Unknown ShippingMethod type of #{shipping_method.inspect}!"
330
+ end
331
+ end
332
+
333
+ def process_shipping(shipping_type, parent, shipping)
334
+ element = parent.add_element(shipping_type)
335
+ element.add_attribute('name', shipping.name)
336
+ element.add_element('price', { 'currency' => shipping.price.currency }).text = shipping.price.to_s
337
+
338
+ if shipping.shipping_restrictions_excluded_areas.length +
339
+ shipping.shipping_restrictions_allowed_areas.length > 0 then
340
+ shipping_restrictions_tag = element.add_element('shipping-restrictions')
341
+
342
+ if shipping.shipping_restrictions_allowed_areas.length > 0 then
343
+ allowed_tag = shipping_restrictions_tag.add_element('allowed-areas')
344
+
345
+ shipping.shipping_restrictions_allowed_areas.each do |area|
346
+ self.process_area(allowed_tag, area)
347
+ end
348
+ end
349
+
350
+ if shipping.shipping_restrictions_excluded_areas.length > 0 then
351
+ excluded_tag = shipping_restrictions_tag.add_element('excluded-areas')
352
+
353
+ shipping.shipping_restrictions_excluded_areas.each do |area|
354
+ self.process_area(excluded_tag, area)
355
+ end
356
+ end
357
+ end
358
+
359
+ if shipping.kind_of? MerchantCalculatedShipping then
360
+ if shipping.address_filters_excluded_areas.length +
361
+ shipping.address_filters_allowed_areas.length > 0 then
362
+ address_filters_tag = element.add_element('address-filters')
363
+
364
+ if shipping.address_filters_allowed_areas.length > 0 then
365
+ allowed_tag = address_filters_tag.add_element('allowed-areas')
366
+
367
+ shipping.address_filters_allowed_areas.each do |area|
368
+ self.process_area(allowed_tag, area)
369
+ end
370
+ end
371
+
372
+ if shipping.address_filters_excluded_areas.length > 0 then
373
+ excluded_tag = address_filters_tag.add_element('excluded-areas')
374
+
375
+ shipping.address_filters_excluded_areas.each do |area|
376
+ self.process_area(excluded_tag, area)
377
+ end
378
+ end
379
+ end
380
+ end
381
+ end
382
+
383
+ def process_pickup(parent, shipping)
384
+ element = parent.add_element('pickup')
385
+ element.add_attribute('name', shipping.name)
386
+ element.add_element('price', { 'currency' => shipping.price.currency }).text = shipping.price.to_s
387
+ end
388
+
389
+ def process_carrier_calculated_shipping(shipping_type, parent, shipping)
390
+ element = parent.add_element(shipping_type)
391
+ options_element = element.add_element('carrier-calculated-shipping-options')
392
+ packages_element = element.add_element('shipping-packages')
393
+ shipping.carrier_calculated_shipping_options.each do | option |
394
+ process_carrier_calculated_shipping_option(options_element, option)
395
+ end
396
+ shipping.shipping_packages.each do | package |
397
+ process_shipping_package(packages_element, package)
398
+ end
399
+ end
400
+
401
+ def process_carrier_calculated_shipping_option(parent, option)
402
+ element = parent.add_element('carrier-calculated-shipping-option')
403
+ element.add_element('price', { 'currency' => option.price.currency }).text = option.price.to_s
404
+ element.add_element('shipping-company').text = option.shipping_company
405
+ element.add_element('shipping-type').text = option.shipping_type
406
+ if not option.carrier_pickup.nil?
407
+ element.add_element('carrier-pickup').text = option.carrier_pickup
408
+ end
409
+ if not option.additional_fixed_charge.nil?
410
+ element.add_element('additional-fixed-charge',
411
+ { 'currency' => option.additional_fixed_charge.currency }).text =
412
+ option.additional_fixed_charge.to_s
413
+ end
414
+ if not option.additional_variable_charge_percent.nil?
415
+ element.add_element('additional-variable-charge-percent').text =
416
+ option.additional_variable_charge_percent.to_s
417
+ end
418
+ end
419
+
420
+ def process_shipping_package(parent, package)
421
+ element = parent.add_element('shipping-package')
422
+ ship_from = package.ship_from
423
+ ship_from_element = element.add_element('ship-from')
424
+ ship_from_element.add_attribute('id', ship_from.address_id)
425
+ ship_from_element.add_element('city').text = ship_from.city
426
+ ship_from_element.add_element('region').text = ship_from.region
427
+ ship_from_element.add_element('country-code').text = ship_from.country_code
428
+ ship_from_element.add_element('postal-code').text = ship_from.postal_code
429
+ if not package.delivery_address_category.nil?
430
+ element.add_element('delivery-address-category').text =
431
+ package.delivery_address_category
432
+ end
433
+ if not package.height.nil?
434
+ height_element = element.add_element('height')
435
+ height_element.add_attribute('unit', package.height.unit)
436
+ height_element.add_attribute('value', package.height.value)
437
+ end
438
+ if not package.length.nil?
439
+ length_element = element.add_element('length')
440
+ length_element.add_attribute('unit', package.length.unit)
441
+ length_element.add_attribute('value', package.length.value)
442
+ end
443
+ if not package.width.nil?
444
+ width_element = element.add_element('width')
445
+ width_element.add_attribute('unit', package.width.unit)
446
+ width_element.add_attribute('value', package.width.value)
447
+ end
448
+ end
449
+
450
+ # Adds an appropriate tag for the given Area subclass instance to the parent Element.
451
+ def process_area(parent, area)
452
+ if area.kind_of? UsZipArea then
453
+ parent.add_element('us-zip-area').add_element('zip-pattern').text = area.pattern
454
+ elsif area.kind_of? UsCountryArea then
455
+ parent.add_element('us-country-area', { 'country-area' => area.area })
456
+ elsif area.kind_of? UsStateArea then
457
+ parent.add_element('us-state-area').add_element('state').text = area.state
458
+ elsif area.kind_of? WorldArea then
459
+ parent.add_element('world-area')
460
+ elsif area.kind_of? PostalArea then
461
+ postal_area_element = parent.add_element('postal-area')
462
+ postal_area_element.add_element('country-code').text = area.country_code
463
+ if area.postal_code_pattern then
464
+ postal_area_element.add_element('postal-code-pattern').text = area.postal_code_pattern
465
+ end
466
+ else
467
+ raise "Area of unknown type: #{area.inspect}."
468
+ end
469
+ end
470
+
471
+ # Converts a Hash into an XML structure. The keys are converted to tag names. If
472
+ # the values are Hashs themselves then process_hash is called upon them. If the
473
+ # values are Arrays then a new element with the key's name will be created.
474
+ #
475
+ # If a value is an Array then this array will be flattened before it is processed.
476
+ # Thus, nested arrays are not allowed.
477
+ #
478
+ # === Example
479
+ #
480
+ # process_hash(parent, { 'foo' => { 'bar' => 'baz' } })
481
+ #
482
+ # # will produce a structure that is equivalent to.
483
+ #
484
+ # <foo>
485
+ # <bar>baz</bar>
486
+ # </foo>
487
+ #
488
+ #
489
+ # process_hash(parent, { 'foo' => [ { 'bar' => 'baz' }, "d'oh", 2 ] })
490
+ #
491
+ # # will produce a structure that is equivalent to.
492
+ #
493
+ # <foo>
494
+ # <bar>baz</bar>
495
+ # </foo>
496
+ # <foo>d&amp;</foo>
497
+ # <foo>2</foo>
498
+ def process_hash(parent, hash)
499
+ hash.each do |key, value|
500
+ if value.kind_of? Array then
501
+ value.flatten.each do |arr_entry|
502
+ if arr_entry.kind_of? Hash then
503
+ self.process_hash(parent.add_element(self.str2tag_name(key.to_s)), arr_entry)
504
+ else
505
+ parent.add_element(self.str2tag_name(key.to_s)).text = arr_entry.to_s
506
+ end
507
+ end
508
+ elsif value.kind_of? Hash then
509
+ process_hash(parent.add_element(self.str2tag_name(key.to_s)), value)
510
+ else
511
+ parent.add_element(self.str2tag_name(key.to_s)).text = value.to_s
512
+ end
513
+ end
514
+ end
515
+
516
+ # Converts a string to a valid XML tag name. Whitespace will be converted into a dash/minus
517
+ # sign, non alphanumeric characters that are neither "-" nor "_" nor ":" will be stripped.
518
+ def str2tag_name(str)
519
+ str.gsub(%r{\s}, '-').gsub(%r{[^a-zA-Z0-9\-\_:]}, '')
520
+ end
521
+ end
522
+
523
+ class ChargeOrderCommandXmlGenerator < CommandXmlGenerator
524
+
525
+ protected
526
+
527
+ def process_command(command)
528
+ root = super
529
+ process_money(root, command.amount) if command.amount
530
+ end
531
+
532
+ # add the amount element to the charge command
533
+ def process_money(parent, money)
534
+ amount_element = parent.add_element('amount')
535
+ amount_element.text = money.to_s
536
+ amount_element.add_attribute('currency', money.currency)
537
+ end
538
+ end
539
+
540
+ class RefundOrderCommandXmlGenerator < CommandXmlGenerator
541
+
542
+ protected
543
+
544
+ def process_command(command)
545
+ root = super
546
+ process_money(root, command.amount) if command.amount
547
+ process_comment(root, command.comment) if command.comment
548
+ process_reason(root, command.reason)
549
+ end
550
+
551
+ # add the amount element to the refund command
552
+ def process_money(parent, money)
553
+ amount_element = parent.add_element('amount')
554
+ amount_element.text = money.to_s
555
+ amount_element.add_attribute('currency', money.currency)
556
+ end
557
+
558
+ # add the comment element to the refund command
559
+ def process_comment(parent, comment)
560
+ comment_element = parent.add_element('comment')
561
+ comment_element.text = comment
562
+ end
563
+
564
+ # add the reason element to the refund command
565
+ def process_reason(parent, reason)
566
+ reason_element = parent.add_element('reason')
567
+ reason_element.text = reason
568
+ end
569
+ end
570
+
571
+ class CancelOrderCommandXmlGenerator < CommandXmlGenerator
572
+
573
+ protected
574
+
575
+ def process_command(command)
576
+ root = super
577
+ root.add_element('reason').text = command.reason
578
+
579
+ if command.comment then
580
+ root.add_element('comment').text = command.comment
581
+ end
582
+ end
583
+
584
+ end
585
+
586
+ class AuthorizeOrderCommandXmlGenerator < CommandXmlGenerator
587
+
588
+ protected
589
+
590
+ def process_command(command)
591
+ super
592
+ end
593
+ end
594
+
595
+ class ProcessOrderCommandXmlGenerator < CommandXmlGenerator
596
+
597
+ protected
598
+
599
+ def process_command(command)
600
+ super
601
+ end
602
+ end
603
+
604
+ class AddMerchantOrderNumberCommandXmlGenerator < CommandXmlGenerator
605
+
606
+ protected
607
+
608
+ def process_command(command)
609
+ root = super
610
+ process_merchant_order_number(root, command.merchant_order_number)
611
+ end
612
+
613
+ def process_merchant_order_number(parent, merchant_order_number)
614
+ merchant_order_number_element = parent.add_element('merchant-order-number')
615
+ merchant_order_number_element.text = merchant_order_number
616
+ end
617
+ end
618
+
619
+ class DeliverOrderCommandXmlGenerator < CommandXmlGenerator
620
+
621
+ protected
622
+
623
+ def process_command(command)
624
+ root = super
625
+ # Add tracking info
626
+ process_tracking_data(root, command.carrier, command.tracking_number)
627
+ root.add_element('send-email').text = command.send_email.to_s
628
+ end
629
+
630
+ def process_tracking_data(parent, carrier, tracking_number)
631
+ if carrier and tracking_number then
632
+ element = parent.add_element('tracking-data')
633
+ element.add_element('carrier').text = carrier
634
+ element.add_element('tracking-number').text = tracking_number
635
+ end
636
+ end
637
+ end
638
+
639
+ class AddTrackingDataCommandXmlGenerator < CommandXmlGenerator
640
+
641
+ protected
642
+
643
+ def process_command(command)
644
+ root = super
645
+ # Add tracking info
646
+ process_tracking_data(root, command.carrier, command.tracking_number)
647
+ end
648
+
649
+ def process_tracking_data(parent, carrier, tracking_number)
650
+ if carrier and tracking_number then
651
+ element = parent.add_element('tracking-data')
652
+ element.add_element('carrier').text = carrier
653
+ element.add_element('tracking-number').text = tracking_number
654
+ end
655
+ end
656
+ end
657
+
658
+ class SendBuyerMessageCommandXmlGenerator < CommandXmlGenerator
659
+
660
+ protected
661
+
662
+ def process_command(command)
663
+ root = super
664
+ root.add_element('message').text = command.message
665
+ if not command.send_email.nil? then
666
+ root.add_element('send-email').text = command.send_email.to_s
667
+ end
668
+ end
669
+ end
670
+
671
+ class ArchiveOrderCommandXmlGenerator < CommandXmlGenerator
672
+
673
+ protected
674
+
675
+ def process_command(command)
676
+ super
677
+ end
678
+ end
679
+
680
+ class UnarchiveOrderCommandXmlGenerator < CommandXmlGenerator
681
+
682
+ protected
683
+
684
+ def process_command(command)
685
+ super
686
+ end
687
+ end
688
+
689
+ class MerchantCalculationResultsXmlGenerator < XmlGenerator
690
+
691
+ def initialize(merchant_calculation_results)
692
+ @merchant_calculation_results = merchant_calculation_results
693
+ end
694
+
695
+ def generate()
696
+ super
697
+ process_results(@merchant_calculation_results.merchant_calculation_results)
698
+ io = StringIO.new
699
+ @document.write(io, 0) # TODO: Maybe replace 0 by -1 so no spaces are inserted?
700
+ return io.string
701
+ end
702
+
703
+ protected
704
+
705
+ def process_results(merchant_calculation_results)
706
+ root = @document.add_element("merchant-calculation-results" , { 'xmlns' => 'http://checkout.google.com/schema/2' })
707
+ results = root.add_element("results")
708
+ for merchant_calculation_result in merchant_calculation_results do
709
+ process_result(results, merchant_calculation_result)
710
+ end
711
+ end
712
+
713
+ def process_result(parent, merchant_calculation_result)
714
+ element = parent.add_element("result")
715
+ element.add_attribute("shipping-name", merchant_calculation_result.shipping_name)
716
+ element.add_attribute("address-id", merchant_calculation_result.address_id)
717
+ shipping_rate = element.add_element("shipping-rate")
718
+ shipping_rate.text = merchant_calculation_result.shipping_rate.to_s
719
+ shipping_rate.add_attribute("currency", merchant_calculation_result.shipping_rate.currency)
720
+ element.add_element("shippable").text = merchant_calculation_result.shippable.to_s
721
+ if (!merchant_calculation_result.total_tax.nil?)
722
+ total_tax = element.add_element("total-tax")
723
+ total_tax.text = merchant_calculation_result.total_tax.to_s
724
+ total_tax.add_attribute("currency", merchant_calculation_result.total_tax.currency)
725
+ end
726
+ process_code_results(element, merchant_calculation_result.merchant_code_results)
727
+ end
728
+
729
+ def process_code_results(parent, merchant_code_results)
730
+ element = parent.add_element("merchant-code-results")
731
+ for merchant_code_result in merchant_code_results do
732
+ process_merchant_code_result(element, merchant_code_result)
733
+ end
734
+ end
735
+
736
+ def process_merchant_code_result(parent, merchant_code_result)
737
+ if merchant_code_result.kind_of?(CouponResult)
738
+ element = parent.add_element("coupon-result")
739
+ elsif merchant_code_result.kind_of?(GiftCertificateResult)
740
+ element = parent.add_element("gift-certificate-result")
741
+ else
742
+ raise "Code of unknown type: #{merchant_code_result.inspect}."
743
+ end
744
+ element.add_element("valid").text = merchant_code_result.valid.to_s
745
+ element.add_element("code").text = merchant_code_result.code.to_s
746
+ calculated_amount = element.add_element("calculated-amount")
747
+ calculated_amount.text = merchant_code_result.calculated_amount.to_s
748
+ calculated_amount.add_attribute("currency", merchant_code_result.calculated_amount.currency)
749
+ element.add_element("message").text = merchant_code_result.message
750
+ end
751
+ end
752
+
753
+ class NotificationAcknowledgementXmlGenerator < XmlGenerator
754
+
755
+ def initialize(notification_acknowledgement)
756
+ @notification_acknowledgement = notification_acknowledgement
757
+ end
758
+
759
+ def generate
760
+ super
761
+ self.process_notification_acknowledgement(@notification_acknowledgement)
762
+ io = StringIO.new
763
+ @document.write(io, -1)
764
+ return io.string
765
+ end
766
+
767
+ def process_notification_acknowledgement(notification_acknowledgement)
768
+ root = @document.add_element('notification-acknowledgment')
769
+ root.add_attribute('xmlns', 'http://checkout.google.com/schema/2')
770
+ if not notification_acknowledgement.serial_number.nil?
771
+ root.add_attribute('serial-number', notification_acknowledgement.serial_number)
772
+ end
773
+ end
774
+ end
775
+
776
+ # Line-item shipping commands
777
+ class ItemsCommandXmlGenerator < CommandXmlGenerator
778
+ protected
779
+
780
+ def process_command(command)
781
+ root = super
782
+ process_item_info_arr(root, command.item_info_arr)
783
+ process_send_email(root, command.send_email)
784
+ return root
785
+ end
786
+
787
+ def process_item_info_arr(parent, item_info_arr)
788
+ element = parent.add_element('item-ids')
789
+ item_info_arr.each do |item_info|
790
+ item_id = element.add_element('item-id')
791
+ item_id.add_element('merchant-item-id').text =
792
+ item_info.merchant_item_id
793
+ end
794
+ end
795
+
796
+ def process_send_email(parent, send_email)
797
+ parent.add_element('send-email').text = send_email.to_s
798
+ end
799
+ end
800
+
801
+ class ShipItemsCommandXmlGenerator < ItemsCommandXmlGenerator
802
+ protected
803
+
804
+ def process_item_info_arr(parent, item_info_arr)
805
+ e1 = parent.add_element('item-shipping-information-list')
806
+ item_info_arr.each do |item_info|
807
+ e2 = e1.add_element('item-shipping-information')
808
+ item_id = e2.add_element('item-id')
809
+ item_id.add_element('merchant-item-id').text =
810
+ item_info.merchant_item_id
811
+ if !item_info.tracking_data_arr.nil?
812
+ e3 = e2.add_element('tracking-data-list')
813
+ item_info.tracking_data_arr.each do |tracking_data|
814
+ e4 = e3.add_element('tracking-data')
815
+ e4.add_element('carrier').text = tracking_data.carrier
816
+ e4.add_element('tracking-number').text =
817
+ tracking_data.tracking_number
818
+ end
819
+ end
820
+ end
821
+ end
822
+ end
823
+
824
+ class BackorderItemsCommandXmlGenerator < ItemsCommandXmlGenerator
825
+ end
826
+
827
+ class CancelItemsCommandXmlGenerator < ItemsCommandXmlGenerator
828
+ protected
829
+
830
+ def process_command(command)
831
+ root = super
832
+ root.add_element('reason').text = command.reason
833
+
834
+ if command.comment then
835
+ root.add_element('comment').text = command.comment
836
+ end
837
+ end
838
+ end
839
+
840
+ class ReturnItemsCommandXmlGenerator < ItemsCommandXmlGenerator
841
+ end
842
+
843
+ class ResetItemsShippingInformationCommandXmlGenerator < ItemsCommandXmlGenerator
844
+ end
845
+
846
+ class ReturnOrderReportCommandXmlGenerator < CommandXmlGenerator
847
+ def initialize(command)
848
+ @command = command
849
+ end
850
+
851
+ protected
852
+
853
+ def process_command(command)
854
+ root = super
855
+ # TODO - sanity check format ?
856
+ root.add_attribute('start-date', command.start_date.to_s)
857
+ root.add_attribute('end-date', command.end_date.to_s)
858
+ flow_element = root
859
+
860
+ # <financial-state>
861
+ if command.financial_state then
862
+ financial_state_element = flow_element.add_element('financial-state')
863
+ financial_state_element.text = command.financial_state.to_s
864
+ end
865
+
866
+ # <fulfillment-state>
867
+ if command.fulfillment_state then
868
+ fulfillment_state_element = flow_element.add_element('fulfillment-state')
869
+ fulfillment_state_element.text = command.fulfillment_state.to_s
870
+ end
871
+
872
+ # <date-time-zone>
873
+ if command.date_time_zone then
874
+ dtz_element = flow_element.add_element('date-time-zone')
875
+ dtz_element.text = command.date_time_zone.to_s
876
+ end
877
+ end
878
+ end
879
+ end
880
+ end