nbudin-google4r-checkout 1.0.6

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