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.
- data/CHANGES +93 -0
- data/LICENSE +22 -0
- data/README +58 -0
- data/Rakefile +13 -0
- data/VERSION +1 -0
- data/google4r-checkout-1.0.5-1.0.5.gem +0 -0
- data/google4r-checkout-1.0.5.gemspec +174 -0
- data/lib/google4r/checkout.rb +34 -0
- data/lib/google4r/checkout/commands.rb +567 -0
- data/lib/google4r/checkout/frontend.rb +210 -0
- data/lib/google4r/checkout/merchant_calculation.rb +321 -0
- data/lib/google4r/checkout/notifications.rb +708 -0
- data/lib/google4r/checkout/shared.rb +1086 -0
- data/lib/google4r/checkout/utils.rb +94 -0
- data/lib/google4r/checkout/xml_generation.rb +880 -0
- data/test/integration/checkout_command_test.rb +174 -0
- data/test/unit/add_merchant_order_number_command_test.rb +70 -0
- data/test/unit/add_tracking_data_command_test.rb +75 -0
- data/test/unit/address_test.rb +131 -0
- data/test/unit/anonymous_address_test.rb +75 -0
- data/test/unit/archive_order_command_test.rb +66 -0
- data/test/unit/area_test.rb +44 -0
- data/test/unit/authorization_amount_notification_test.rb +69 -0
- data/test/unit/authorize_order_command_test.rb +66 -0
- data/test/unit/backorder_items_command_test.rb +83 -0
- data/test/unit/callback_handler_test.rb +83 -0
- data/test/unit/cancel_items_command_test.rb +89 -0
- data/test/unit/cancel_order_command_test.rb +83 -0
- data/test/unit/carrier_calculated_shipping_test.rb +57 -0
- data/test/unit/charge_amount_notification_test.rb +64 -0
- data/test/unit/charge_order_command_test.rb +77 -0
- data/test/unit/chargeback_amount_notification_test.rb +65 -0
- data/test/unit/checkout_command_test.rb +125 -0
- data/test/unit/checkout_command_xml_generator_test.rb +218 -0
- data/test/unit/command_test.rb +116 -0
- data/test/unit/deliver_order_command_test.rb +70 -0
- data/test/unit/delivery_method_test.rb +42 -0
- data/test/unit/digital_content_test.rb +105 -0
- data/test/unit/flat_rate_shipping_test.rb +132 -0
- data/test/unit/frontend_test.rb +144 -0
- data/test/unit/item_info_test.rb +69 -0
- data/test/unit/item_test.rb +171 -0
- data/test/unit/marketing_preferences_test.rb +65 -0
- data/test/unit/merchant_calculated_shipping_test.rb +172 -0
- data/test/unit/merchant_calculation_callback_test.rb +137 -0
- data/test/unit/merchant_calculation_result_test.rb +78 -0
- data/test/unit/merchant_calculation_results_test.rb +178 -0
- data/test/unit/merchant_code_result_test.rb +51 -0
- data/test/unit/merchant_code_test.rb +122 -0
- data/test/unit/new_order_notification_test.rb +115 -0
- data/test/unit/notification_acknowledgement_test.rb +67 -0
- data/test/unit/notification_handler_test.rb +113 -0
- data/test/unit/order_adjustment_test.rb +119 -0
- data/test/unit/order_report_command_test.rb +111 -0
- data/test/unit/order_state_change_notification_test.rb +158 -0
- data/test/unit/pickup_shipping_test.rb +70 -0
- data/test/unit/postal_area_test.rb +71 -0
- data/test/unit/private_data_parser_test.rb +68 -0
- data/test/unit/refund_amount_notification_test.rb +65 -0
- data/test/unit/refund_order_command_test.rb +86 -0
- data/test/unit/reset_items_shipping_information_command_test.rb +83 -0
- data/test/unit/return_items_command_test.rb +83 -0
- data/test/unit/risk_information_notification_test.rb +98 -0
- data/test/unit/send_buyer_message_command_test.rb +73 -0
- data/test/unit/ship_items_command_test.rb +101 -0
- data/test/unit/shipping_adjustment_test.rb +100 -0
- data/test/unit/shopping_cart_test.rb +146 -0
- data/test/unit/tax_rule_test.rb +70 -0
- data/test/unit/tax_table_test.rb +82 -0
- data/test/unit/tracking_data_test.rb +54 -0
- data/test/unit/unarchive_order_command_test.rb +66 -0
- data/test/unit/us_country_area_test.rb +76 -0
- data/test/unit/us_state_area_test.rb +70 -0
- data/test/unit/us_zip_area_test.rb +66 -0
- data/test/unit/world_area_test.rb +48 -0
- data/var/cacert.pem +7815 -0
- 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
|