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,708 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/notifications.rb
4
+ # Author: Manuel Holtgrewe <purestorm at ggnore dot net>
5
+ # Copyright: (c) 2007 by Manuel Holtgrewe
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 in the notification
28
+ # handling code.
29
+
30
+ require 'rexml/document'
31
+
32
+ module Google4R #:nodoc:
33
+ module Checkout #:nodoc:
34
+ # Thrown by Notification on unimplemented and unknown notification from Google.
35
+ class UnknownNotificationType < Exception
36
+ end
37
+
38
+ # Represents a notification acknowledgment to tell Google that the
39
+ # notification has been recieved and processed. Google guarantees not to
40
+ # resend any notification it has recieved an acknowledgment for.
41
+ #
42
+ # === For example, in a Rails app
43
+ #
44
+ # # Let Google Checkout know we handled this notification with handshake
45
+ # notification_acknowledgement = Google4R::Checkout::NotificationAcknowledgement.new(notification).to_xml
46
+ # render :text => notification_acknowledgement, :status => 200
47
+ #
48
+ # or
49
+ #
50
+ # # Without handshake
51
+ # notification_acknowledgement = Google4R::Checkout::NotificationAcknowledgement.new.to_xml
52
+ # render :text => notification_acknowledgement, :status => 200
53
+ #
54
+ class NotificationAcknowledgement
55
+
56
+ attr_reader :serial_number
57
+
58
+ def initialize(notification=nil)
59
+ @serial_number = notification.serial_number unless notification.nil?
60
+ end
61
+
62
+ def to_xml
63
+ NotificationAcknowledgementXmlGenerator.new(self).generate
64
+ end
65
+ end
66
+
67
+ # This class expects the message sent by Google. It parses the XMl document and returns
68
+ # the appropriate Notification. If the notification sent by Google is invalid then a
69
+ # UnknownNotificationType is raised that you should catch and then send a 404 to Google
70
+ # to indicate that the notification handler has not been implemented yet.
71
+ #
72
+ # See http://code.google.com/apis/checkout/developer/index.html#notification_api for
73
+ # details.
74
+ #
75
+ # Note that you must protect the HTTPS request to the piece of code using a
76
+ # NotificationHandler by HTTP Auth Basic. If you are using Ruby On Rails then you can
77
+ # use the great "simple_http_auth" plugin you can find here:
78
+ # http://blog.codahale.com/2006/05/11/basic-http-authentication-with-rails-simple_http_auth/
79
+ #
80
+ # === Usage Example
81
+ #
82
+ # When you use a Rails controller to handle the calbacks by Google then your action to handle
83
+ # the callbacks could use a NotificationHandler as follows:
84
+ #
85
+ # def google_checkout_api
86
+ # frontend = Google4R::Checkout::Frontend.new(FRONTEND_CONFIGURATION)
87
+ # frontend.tax_table_factory = CheckoutCommandFactory.new
88
+ # handler = frontend.create_notification_handler
89
+ #
90
+ # begin
91
+ # notification = handler.handle(request.raw_post) # raw_post contains the XML
92
+ # rescue Google4R::Checkout::UnknownNotificationType => e
93
+ # # This can happen if Google adds new commands and Google4R has not been
94
+ # # upgraded yet. It is not fatal.
95
+ # render :text => 'ignoring unknown notification type', :status => 200
96
+ # return
97
+ # end
98
+ #
99
+ # # ...
100
+ # end
101
+ class NotificationHandler
102
+ # The Frontend object that created this NotificationHandler
103
+ attr_accessor :frontend
104
+
105
+ # Create a new NotificationHandler and assign value of the parameter frontend to
106
+ # the frontend attribute.
107
+ def initialize(frontend)
108
+ @frontend = frontend
109
+ end
110
+
111
+ # Parses the given xml_str and returns the appropriate *Notification class. At the
112
+ # moment, only NewOrderNotification and OrderStateChangeNotification objects can be
113
+ # returned.
114
+ #--
115
+ # TODO: Add parsing of other notifications here (merchant calculation and the like) when they have been implemented.
116
+ #++
117
+ def handle(xml_str)
118
+ root = REXML::Document.new(xml_str).root
119
+
120
+ case root.name
121
+ when 'new-order-notification' then
122
+ NewOrderNotification.create_from_element(root, frontend)
123
+ when 'order-state-change-notification' then
124
+ OrderStateChangeNotification.create_from_element(root, frontend)
125
+ when 'risk-information-notification' then
126
+ RiskInformationNotification.create_from_element(root, frontend)
127
+ when 'charge-amount-notification' then
128
+ ChargeAmountNotification.create_from_element(root, frontend)
129
+ when 'refund-amount-notification' then
130
+ RefundAmountNotification.create_from_element(root, frontend)
131
+ when 'chargeback-amount-notification' then
132
+ ChargebackAmountNotification.create_from_element(root, frontend)
133
+ when 'authorization-amount-notification' then
134
+ AuthorizationAmountNotification.create_from_element(root, frontend)
135
+ else
136
+ raise UnknownNotificationType, "Unknown notification type: #{root.name}"
137
+ end
138
+ end
139
+ end
140
+
141
+ # Abstract class for all the notifications. It should not be instantiated
142
+ # directly.
143
+ class Notification
144
+ # The frontend this notification belongs to.
145
+ attr_accessor :frontend
146
+
147
+ # The serial number of the new order notification (String).
148
+ attr_accessor :serial_number
149
+
150
+ # The Google order number the new order notification belongs to (String).
151
+ attr_accessor :google_order_number
152
+
153
+ # The timestamp of the notification. Also see #timestamp=
154
+ attr_accessor :timestamp
155
+
156
+ # Initializes the RiskInformationNotification instance with the given Frontend instance.
157
+ def initialize(frontend)
158
+ @frontend = frontend
159
+ end
160
+
161
+ end
162
+
163
+ # Google Checkout sends <new-order-notification> messages to the web service when a new
164
+ # order has been successfully filed with Google Checkout. These messages will be parsed
165
+ # into NewOrderNotification instances.
166
+ class NewOrderNotification < Notification
167
+
168
+ # The buyer's billing address (Address).
169
+ attr_accessor :buyer_billing_address
170
+
171
+ # The buyer's shipping adress (Address).
172
+ attr_accessor :buyer_shipping_address
173
+
174
+ # The buyer's ID from Google Checkout (String).
175
+ attr_accessor :buyer_id
176
+
177
+ # The buyer's marketing preferences (MarketingPreferences).
178
+ attr_accessor :marketing_preferences
179
+
180
+ # The order's financial order state (String, one of FinancialOrderState::*).
181
+ attr_accessor :financial_order_state
182
+
183
+ # The order's fulfillment state (String, one of FulfillmentOrderState::*).
184
+ attr_accessor :fulfillment_order_state
185
+
186
+ # The order's number at Google Checkout (String).
187
+ attr_accessor :google_order_number
188
+
189
+ # The order's total adjustment (OrderAdjustment).
190
+ attr_accessor :order_adjustment
191
+
192
+ # The order's total amount (Money).
193
+ attr_accessor :order_total
194
+
195
+ # The order's shopping cart (ShoppingCart)
196
+ attr_accessor :shopping_cart
197
+
198
+ # The tax tables for the items in the order notification.
199
+ attr_reader :tax_tables
200
+
201
+ # Set the order's timestamp (Time). When the timestamp is set then the tax tables valid
202
+ # at the given point of time are set into the attribute tax tables from the frontend's
203
+ # tax_table_factory.
204
+ def timestamp=(time)
205
+ @timestamp = time
206
+ @tax_tables = frontend.tax_table_factory.effective_tax_tables_at(time)
207
+ end
208
+
209
+ # Factory method to create a new CheckoutNotification object from the REXML:Element object
210
+ #
211
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
212
+ # elements.
213
+ #
214
+ # You have to pass in the Frontend class this notification belongs to.
215
+ def self.create_from_element(element, frontend)
216
+ result = NewOrderNotification.new(frontend)
217
+
218
+ result.timestamp = Time.parse(element.elements['timestamp'].text)
219
+ result.serial_number = element.attributes['serial-number']
220
+ result.google_order_number = element.elements['google-order-number'].text
221
+ result.buyer_billing_address = Address.create_from_element(element.elements['buyer-billing-address'])
222
+ result.buyer_shipping_address = Address.create_from_element(element.elements['buyer-shipping-address'])
223
+ result.buyer_id = element.elements['buyer-id'].text
224
+ result.marketing_preferences = MarketingPreferences.create_from_element(element.elements['buyer-marketing-preferences'])
225
+ result.financial_order_state = element.elements['financial-order-state'].text
226
+ result.fulfillment_order_state = element.elements['fulfillment-order-state'].text
227
+ result.order_adjustment = OrderAdjustment.create_from_element(element.elements['order-adjustment'])
228
+ result.shopping_cart = ShoppingCart.create_from_element(element.elements['shopping-cart'], result)
229
+
230
+ amount = (element.elements['order-total'].text.to_f*100).to_i
231
+ currency = element.elements['order-total'].attributes['currency']
232
+ result.order_total = Money.new(amount, currency)
233
+
234
+ return result
235
+ end
236
+ end
237
+
238
+ # GoogleCheckout sends <order-change-notification> messages to the web service when the
239
+ # order's state changes. They will get parsed into OrderStateChangeNotification objects.
240
+ class OrderStateChangeNotification < Notification
241
+
242
+ # The previous financial state of the order (String, one of FinancialOrderState::*).
243
+ attr_accessor :previous_financial_order_state
244
+
245
+ # The new financial state of the order (String, one of FinancialOrderState::*).
246
+ attr_accessor :new_financial_order_state
247
+
248
+ # The previous fulfillment state of the order (String, one of FulfillmentOrderState::*).
249
+ attr_accessor :previous_fulfillment_order_state
250
+
251
+ # The new fulfillment state of the order (String, one of FulfillmentOrderState::*).
252
+ attr_accessor :new_fulfillment_order_state
253
+
254
+ # The reason for the change (String, can be nil).
255
+ attr_accessor :reason
256
+
257
+ # The tax tables for the items in the order notification.
258
+ attr_reader :tax_tables
259
+
260
+ # Set the order's timestamp (Time). When the timestamp is set then the tax tables valid
261
+ # at the given point of time are set into the attribute tax tables from the frontend's
262
+ # tax_table_factory.
263
+ def timestamp=(time)
264
+ @timestamp = time
265
+ @tax_tables = frontend.tax_table_factory.effective_tax_tables_at(time)
266
+ end
267
+
268
+ # Factory method that creates a new OrderStateChangeNotification from an REXML::Element instance.
269
+ # Use this to create instances of OrderStateChangeNotification.
270
+ #
271
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
272
+ # elements.
273
+ def self.create_from_element(element, frontend)
274
+ result = OrderStateChangeNotification.new(frontend)
275
+
276
+ result.timestamp = Time.parse(element.elements['timestamp'].text)
277
+ result.serial_number = element.attributes['serial-number']
278
+ result.google_order_number = element.elements['google-order-number'].text
279
+ result.new_financial_order_state = element.elements['new-financial-order-state'].text
280
+ result.previous_financial_order_state = element.elements['previous-financial-order-state'].text
281
+ result.new_fulfillment_order_state = element.elements['new-fulfillment-order-state'].text
282
+ result.previous_fulfillment_order_state = element.elements['previous-fulfillment-order-state'].text
283
+ result.reason = element.elements['reason'].text rescue nil
284
+
285
+ return result
286
+ end
287
+ end
288
+
289
+ # Google Checkout sends <charge-amount-notification> messages to the web service when the
290
+ # to confirm that the charge was successfully executed.
291
+ class ChargeAmountNotification < Notification
292
+
293
+ # The amount most recently charged for an order (Money)
294
+ attr_accessor :latest_charge_amount
295
+
296
+ # The total amount charged for an order (Money)
297
+ attr_accessor :total_charge_amount
298
+
299
+ # Factory method that creates a new ChargeAmountNotification from an REXML::Element instance.
300
+ # Use this to create instances of ChargeAmountNotification.
301
+ #
302
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
303
+ # elements.
304
+ def self.create_from_element(element, frontend)
305
+ charge = ChargeAmountNotification.new(frontend)
306
+
307
+ charge.serial_number = element.attributes['serial-number']
308
+ charge.google_order_number = element.elements['google-order-number'].text
309
+
310
+ currency = element.elements['latest-charge-amount'].attributes['currency']
311
+ amount = (element.elements['latest-charge-amount'].text.to_f*100).to_i
312
+ charge.latest_charge_amount = Money.new(amount, currency)
313
+
314
+ currency = element.elements['total-charge-amount'].attributes['currency']
315
+ amount = (element.elements['total-charge-amount'].text.to_f*100).to_i
316
+ charge.total_charge_amount = Money.new(amount, currency)
317
+
318
+ charge.timestamp = Time.parse(element.elements['timestamp'].text)
319
+
320
+ return charge
321
+ end
322
+ end
323
+
324
+ # Google Checkout sends a <refund-amount-notification> after successfully executing
325
+ # a <refund-order> order processing command. See the Google Checkout documentation for more details:
326
+ # http://code.google.com/apis/checkout/developer/index.html#refund_amount_notification
327
+ class RefundAmountNotification < Notification
328
+
329
+ # The amount most recently refunded for an order (Money)
330
+ attr_accessor :latest_refund_amount
331
+
332
+ # The total amount refunded for an order (Money)
333
+ attr_accessor :total_refund_amount
334
+
335
+ # Factory method that creates a new RefundAmountNotification from an REXML::Element instance.
336
+ # Use this to create instances of RefundAmountNotification.
337
+ #
338
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
339
+ # elements.
340
+ def self.create_from_element(element, frontend)
341
+ refund = RefundAmountNotification.new(frontend)
342
+
343
+ refund.serial_number = element.attributes['serial-number']
344
+ refund.google_order_number = element.elements['google-order-number'].text
345
+
346
+ currency = element.elements['latest-refund-amount'].attributes['currency']
347
+ amount = (element.elements['latest-refund-amount'].text.to_f*100).to_i
348
+ refund.latest_refund_amount = Money.new(amount, currency)
349
+
350
+ currency = element.elements['total-refund-amount'].attributes['currency']
351
+ amount = (element.elements['total-refund-amount'].text.to_f*100).to_i
352
+ refund.total_refund_amount = Money.new(amount, currency)
353
+
354
+ refund.timestamp = Time.parse(element.elements['timestamp'].text)
355
+
356
+ return refund
357
+ end
358
+ end
359
+
360
+ # Google Checkout sends a <chargeback-amount-notification> when a customer initiates a
361
+ # chargeback against the order and Google approves the chargeback.
362
+ # See the Google Checkout documentation for more details:
363
+ # http://code.google.com/apis/checkout/developer/index.html#chargeback_amount_notification
364
+ class ChargebackAmountNotification < Notification
365
+
366
+ # The amount most recently charged back for an order (Money)
367
+ attr_accessor :latest_chargeback_amount
368
+
369
+ # The total amount charged back for an order (Money)
370
+ attr_accessor :total_chargeback_amount
371
+
372
+ # Factory method that creates a new ChargebackAmountNotification from an REXML::Element instance.
373
+ # Use this to create instances of ChargebackAmountNotification.
374
+ #
375
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
376
+ # elements.
377
+ def self.create_from_element(element, frontend)
378
+ chargeback = ChargebackAmountNotification.new(frontend)
379
+
380
+ chargeback.serial_number = element.attributes['serial-number']
381
+ chargeback.google_order_number = element.elements['google-order-number'].text
382
+
383
+ currency = element.elements['latest-chargeback-amount'].attributes['currency']
384
+ amount = (element.elements['latest-chargeback-amount'].text.to_f*100).to_i
385
+ chargeback.latest_chargeback_amount = Money.new(amount, currency)
386
+
387
+ currency = element.elements['total-chargeback-amount'].attributes['currency']
388
+ amount = (element.elements['total-chargeback-amount'].text.to_f*100).to_i
389
+ chargeback.total_chargeback_amount = Money.new(amount, currency)
390
+
391
+ chargeback.timestamp = Time.parse(element.elements['timestamp'].text)
392
+
393
+ return chargeback
394
+ end
395
+ end
396
+
397
+ # Google Checkout sends an <authorization-amount-notification> in response to a successful
398
+ # request for an explicit credit card reauthorization.
399
+ # See the Google Checkout documentation for more details:
400
+ # http://code.google.com/apis/checkout/developer/index.html#authorization_amount_notification
401
+ class AuthorizationAmountNotification < Notification
402
+
403
+ # The amount that is reauthorized to be charged to the customer's credit card (Money)
404
+ attr_accessor :authorization_amount
405
+
406
+ # The time that a credit card authorization for an order expires.
407
+ attr_accessor :authorization_expiration_date
408
+
409
+ # The address verification response (String)
410
+ attr_accessor :avs_response
411
+
412
+ # Credit verification value for the order (String)
413
+ attr_accessor :cvn_response
414
+
415
+ # Factory method that creates a new AuthorizationAmountNotification from an REXML::Element instance.
416
+ # Use this to create instances of AuthorizationAmountNotification.
417
+ #
418
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
419
+ # elements.
420
+ def self.create_from_element(element, frontend)
421
+ authorization = AuthorizationAmountNotification.new(frontend)
422
+
423
+ authorization.serial_number = element.attributes['serial-number']
424
+ authorization.google_order_number = element.elements['google-order-number'].text
425
+
426
+ currency = element.elements['authorization-amount'].attributes['currency']
427
+ amount = (element.elements['authorization-amount'].text.to_f*100).to_i
428
+ authorization.authorization_amount = Money.new(amount, currency)
429
+
430
+ authorization.authorization_expiration_date = Time.parse(element.elements['authorization-expiration-date'].text)
431
+
432
+ authorization.avs_response = element.elements['avs-response'].text
433
+ authorization.cvn_response = element.elements['cvn-response'].text
434
+
435
+ authorization.timestamp = Time.parse(element.elements['timestamp'].text)
436
+
437
+ return authorization
438
+ end
439
+ end
440
+
441
+ # Google Checkout sends out <risk-information-notification> messages for fraud detection
442
+ # related information. See the Google Checkout documentation for more details:
443
+ # http://code.google.com/apis/checkout/developer/index.html#risk_information_notification
444
+ class RiskInformationNotification < Notification
445
+
446
+ # Is the order eligible for Google Checkout's payment guarantee policy (boolean).
447
+ attr_accessor :eligible_for_protection
448
+
449
+ # The buyer's billing address (Address).
450
+ attr_accessor :buyer_billing_address
451
+
452
+ # The address verification response (String)
453
+ attr_accessor :avs_response
454
+
455
+ # Credit verification value for the order (String)
456
+ attr_accessor :cvn_response
457
+
458
+ # The last 4 digits of the credit card used to pay for the order (integer)
459
+ attr_accessor :partial_card_number
460
+
461
+ # The buyer's IP address (String)
462
+ attr_accessor :ip_address
463
+
464
+ # The age of the buyer's google checkout account in days
465
+ attr_accessor :buyer_account_age
466
+
467
+ # Factory method that creates a new RiskInformationNotification from an REXML::Element instance.
468
+ # Use this to create instances of RiskInformationNotification
469
+ #
470
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
471
+ # elements.
472
+ def self.create_from_element(element, frontend)
473
+ risk = RiskInformationNotification.new(frontend)
474
+
475
+ risk.serial_number = element.attributes['serial-number']
476
+ risk.timestamp = Time.parse(element.elements['timestamp'].text)
477
+ risk.partial_card_number = element.elements['risk-information/partial-cc-number'].text
478
+ risk.ip_address = element.elements['risk-information/ip-address'].text
479
+ risk.google_order_number = element.elements['google-order-number'].text
480
+ risk.eligible_for_protection = element.elements['risk-information/eligible-for-protection'].text == 'true'
481
+ risk.cvn_response = element.elements['risk-information/cvn-response'].text
482
+ risk.buyer_billing_address = Address.create_from_element(element.elements['risk-information/billing-address'])
483
+ risk.buyer_account_age = element.elements['risk-information/buyer-account-age'].text.to_i
484
+ risk.avs_response = element.elements['risk-information/avs-response'].text
485
+
486
+ return risk
487
+ end
488
+ end
489
+
490
+ # Container for the valid financial order states as defined in the
491
+ # Google Checkout API.
492
+ module FinancialOrderState
493
+ REVIEWING = "REVIEWING".freeze
494
+ CHARGEABLE = "CHARGEABLE".freeze
495
+ CHARGING = "CHARGING".freeze
496
+ CHARGED = "CHARGED".freeze
497
+ PAYMENT_DECLINED = "PAYMENT_DECLINED".freeze
498
+ CANCELLED = "CANCELLED".freeze
499
+ CANCELLED_BY_GOOGLE = "CANCELLED_BY_GOOGLE".freeze
500
+ end
501
+
502
+ # Container for the valid fulfillment order states as defined in the
503
+ # Google Checkout API.
504
+ module FulfillmentOrderState
505
+ NEW = "NEW".freeze
506
+ PROCESSING = "PROCESSING".freeze
507
+ DELIVERED = "DELIVERED".freeze
508
+ WILL_NOT_DELIVER = "WILL_NOT_DELIVER".freeze
509
+ end
510
+
511
+ # The marketing preferences of a customer.
512
+ class MarketingPreferences
513
+ # Boolean, true if the customer wants to receive emails.
514
+ attr_accessor :email_allowed
515
+
516
+ # Creates a new MarketingPreferences object from a given REXML::Element instance.
517
+ def self.create_from_element(element)
518
+ result = MarketingPreferences.new
519
+
520
+ result.email_allowed = (element.elements['email-allowed'].text.downcase == 'true')
521
+
522
+ return result
523
+ end
524
+ end
525
+
526
+ # MerchantCodes represent gift certificates or coupons that have been used in an order.
527
+ #
528
+ # Only used with Merchant Calculations.
529
+ class MerchantCode
530
+ GIFT_CERTIFICATE = "GIFT_CERTIFICATE".freeze
531
+ COUPON = "COUPON".freeze
532
+
533
+ # The type of the adjustment. Can be one of GIFT_CERTIFICATE and COUPON.
534
+ attr_accessor :type
535
+
536
+ # The adjustment's code (String).
537
+ attr_accessor :code
538
+
539
+ # The amount of money that has been calculated as the adjustment's worth (Money, optional).
540
+ attr_accessor :calculated_amount
541
+
542
+ # The amount of the adjustment that has been applied to the cart's total (Money).
543
+ attr_accessor :applied_amount
544
+
545
+ # The message associated with the direct adjustment (String, optional).
546
+ attr_accessor :message
547
+
548
+ # Creates the MerchantCode from the given REXML::Element instance. The Element's
549
+ # name must be "gift-certificate-adjustment" or "coupon-adjustment".
550
+ def self.create_from_element(element)
551
+ result = MerchantCode.new
552
+
553
+ result.type =
554
+ case element.name
555
+ when 'gift-certificate-adjustment' then
556
+ GIFT_CERTIFICATE
557
+ when 'coupon-adjustment' then
558
+ COUPON
559
+ else
560
+ raise ArgumentError, "Invalid tag name: #{element.name} in \n—-\n#{element.to_s}\n—-."
561
+ end
562
+
563
+ result.code = element.elements['code'].text
564
+
565
+ amount = (element.elements['calculated-amount'].text.to_f*100).to_i rescue nil
566
+ currency = element.elements['calculated-amount'].attributes['currency'] rescue nil
567
+ result.calculated_amount = Money.new(amount, currency) unless amount.nil?
568
+
569
+ amount = (element.elements['applied-amount'].text.to_f*100).to_i
570
+ currency = element.elements['applied-amount'].attributes['currency']
571
+ result.applied_amount = Money.new(amount, currency)
572
+
573
+ result.message = element.elements['message'].text rescue nil
574
+
575
+ return result
576
+ end
577
+ end
578
+
579
+ # ShippingAdjustments represent the chosen shipping method.
580
+ class ShippingAdjustment
581
+ MERCHANT_CALCULATED = "MERCHANT_CALCULATED".freeze
582
+ FLAT_RATE = "FLAT_RATE".freeze
583
+ PICKUP = "PICKUP".freeze
584
+
585
+ # The type of the shipping adjustment, one of MERCHANT_CALCULATED, FLAT_RATE
586
+ # PICKUP.
587
+ attr_accessor :type
588
+
589
+ # The name of the shipping adjustment.
590
+ attr_accessor :name
591
+
592
+ # The cost of the selected shipping (Money).
593
+ attr_accessor :cost
594
+
595
+ # Creates a new ShippingAdjustment object from a REXML::Element object.
596
+ #
597
+ # Can raise a RuntimeException if the given Element is invalid.
598
+ def self.create_from_element(element)
599
+ result = ShippingAdjustment.new
600
+
601
+ result.type =
602
+ case element.name
603
+ when 'flat-rate-shipping-adjustment' then
604
+ FLAT_RATE
605
+ when 'pickup-shipping-adjustment' then
606
+ PICKUP
607
+ when 'merchant-calculated-shipping-adjustment' then
608
+ MERCHANT_CALCULATED
609
+ else
610
+ raise "Unexpected shipping adjustment '#{element.name}'"
611
+ end
612
+
613
+ result.name = element.elements['shipping-name'].text
614
+
615
+ amount = (element.elements['shipping-cost'].text.to_f*100).to_i
616
+ currency = element.elements['shipping-cost'].attributes['currency']
617
+ result.cost = Money.new(amount, currency)
618
+
619
+ return result
620
+ end
621
+ end
622
+
623
+ # OrderAdjustment objects contain the adjustments (i.e. the entities in the cart that
624
+ # represent positive and negative amounts (at the moment Google Checkout support coupons,
625
+ # gift certificates and shipping)).
626
+ class OrderAdjustment
627
+ # The <adjustment-total> tag contains the total adjustment to an order total based
628
+ # on tax, shipping, gift certificates and coupon codes (optional).
629
+ attr_accessor :adjustment_total
630
+
631
+ # Boolean, true iff the merchant calculations have been successful (optional).
632
+ attr_accessor :merchant_calculation_successful
633
+
634
+ # Array of MerchantCode objects.
635
+ attr_accessor :merchant_codes
636
+
637
+ # The chosen ShippingAdjustment object for this order.
638
+ attr_accessor :shipping
639
+
640
+ # The total amount of tax that has been paid for this order (Money, optional).
641
+ attr_accessor :total_tax
642
+
643
+ # Creates a new OrderAdjustment from a given REXML::Element object.
644
+ def self.create_from_element(element)
645
+ result = OrderAdjustment.new
646
+
647
+ amount = (element.elements['total-tax'].text.to_f*100).to_i rescue nil
648
+ currency = element.elements['total-tax'].attributes['currency'] rescue nil
649
+ result.total_tax = Money.new(amount, currency) unless amount.nil?
650
+
651
+ shipping_element = element.elements["shipping/*"]
652
+ result.shipping = ShippingAdjustment.create_from_element(shipping_element) unless shipping_element.nil?
653
+
654
+ result.merchant_codes = Array.new
655
+ element.elements.each(%q{merchant-codes/*}) { |elem| result.merchant_codes << MerchantCode.create_from_element(elem) }
656
+
657
+ result.merchant_calculation_successful = (element.elements['merchant-calculation-successful'].text.downcase == 'true') rescue nil
658
+
659
+ amount = (element.elements['adjustment-total'].text.to_f*100).to_i rescue nil
660
+ currency = element.elements['adjustment-total'].attributes['currency'] rescue nil
661
+ result.adjustment_total = Money.new(amount, currency) unless amount.nil?
662
+
663
+ return result
664
+ end
665
+ end
666
+
667
+ # Class with static methods to parse the <merchant-private(-item)-data> tags.
668
+ class PrivateDataParser
669
+ # Returns a Hash with the representation of the structure in
670
+ # item/merchant-private-item-data.
671
+ def self.element_to_value(element)
672
+ # The return value; We will iterate over all children below. When we see a REXML::Element
673
+ # child then we set result to a Hash if it is nil. After the result is a Hash, we will
674
+ # ignore all REXML::Text children.
675
+ # Otherwise, result will be set to the REXML::Text node's value if it is not whitespace
676
+ # only.
677
+ result = nil
678
+
679
+ element.each_child do |child|
680
+ case child
681
+ when REXML::Element
682
+ result ||= Hash.new
683
+ child_value = self.element_to_value(child)
684
+
685
+ # <foo>bar</foo> becomes 'foo' => 'bar
686
+ # <foo>foo</foo><foo>bar</foo> becomes 'foo' => [ 'foo', 'bar' ]
687
+ if result[child.name].nil? then
688
+ result[child.name] = child_value
689
+ elsif result[child.name].kind_of?(Array) then
690
+ result[child.name] << child_value
691
+ else
692
+ tmp = result[child.name]
693
+ result[child.name] = [ tmp, child_value ]
694
+ end
695
+ when REXML::Text
696
+ next if result.kind_of?(Hash) # ignore text if we already found a tag
697
+ str = child.value.strip
698
+ result = str if str.length > 0
699
+ else
700
+ # ignore
701
+ end
702
+ end
703
+
704
+ return result
705
+ end
706
+ end
707
+ end
708
+ end