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