google4r-checkout 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +40 -0
- data/LICENSE +22 -0
- data/README +76 -0
- data/lib/google4r/checkout.rb +33 -0
- data/lib/google4r/checkout/commands.rb +260 -0
- data/lib/google4r/checkout/frontend.rb +108 -0
- data/lib/google4r/checkout/notifications.rb +549 -0
- data/lib/google4r/checkout/shared.rb +541 -0
- data/lib/google4r/checkout/xml_generation.rb +313 -0
- data/test/integration/checkout_command_test.rb +103 -0
- data/test/unit/address_test.rb +131 -0
- data/test/unit/area_test.rb +44 -0
- data/test/unit/checkout_command_test.rb +116 -0
- data/test/unit/checkout_command_xml_generator_test.rb +203 -0
- data/test/unit/command_test.rb +115 -0
- data/test/unit/flat_rate_shipping_test.rb +114 -0
- data/test/unit/frontend_test.rb +63 -0
- data/test/unit/item_test.rb +159 -0
- data/test/unit/marketing_preferences_test.rb +65 -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 +43 -0
- data/test/unit/notification_handler_test.rb +93 -0
- data/test/unit/order_adjustment_test.rb +119 -0
- data/test/unit/order_state_change_notification_test.rb +159 -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/shipping_adjustment_test.rb +100 -0
- data/test/unit/shipping_method_test.rb +41 -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/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 +92 -0
@@ -0,0 +1,549 @@
|
|
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
|
+
# Use this objects of this class to tell Google that everything went fine.
|
39
|
+
#
|
40
|
+
# === Example
|
41
|
+
#
|
42
|
+
# render :text => NotificationAcknowledgement.to_xml
|
43
|
+
#--
|
44
|
+
# TODO: Should this become a Singleton?
|
45
|
+
#++
|
46
|
+
class NotificationAcknowledgement
|
47
|
+
def to_xml
|
48
|
+
%q{<?xml version="1.0" encoding="UTF-8"?><notification-acknowledgment xmlns="http://checkout.google.com/schema/2"/>}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# This class expects the message sent by Google. It parses the XMl document and returns
|
53
|
+
# the appropriate Notification. If the notification sent by Google is invalid then a
|
54
|
+
# UnknownNotificationType is raised that you should catch and then send a 404 to Google
|
55
|
+
# to indicate that the notification handler has not been implemented yet.
|
56
|
+
#
|
57
|
+
# See http://code.google.com/apis/checkout/developer/index.html#notification_api for
|
58
|
+
# details.
|
59
|
+
#
|
60
|
+
# Note that you must protect the HTTPS request to the piece of code using a
|
61
|
+
# NotificationHandler by HTTP Auth Basic. If you are using Ruby On Rails then you can
|
62
|
+
# use the great "simple_http_auth" plugin you can find here:
|
63
|
+
# http://blog.codahale.com/2006/05/11/basic-http-authentication-with-rails-simple_http_auth/
|
64
|
+
#
|
65
|
+
# === Usage Example
|
66
|
+
#
|
67
|
+
# When you use a Rails controller to handle the calbacks by Google then your action to handle
|
68
|
+
# the callbacks could use a NotificationHandler as follows:
|
69
|
+
#
|
70
|
+
# def google_checkout_api
|
71
|
+
# frontend = Google4R::Checkout::Frontend.new(FRONTEND_CONFIGURATION)
|
72
|
+
# frontend.tax_table_factory = CheckoutCommandFactory.new
|
73
|
+
# handler = frontend.create_notification_handler
|
74
|
+
#
|
75
|
+
# begin
|
76
|
+
# notification = handler.handle(request.raw_post) # raw_post contains the XML
|
77
|
+
# rescue Google4R::Checkout::UnknownNotificationType => e
|
78
|
+
# # This can happen if Google adds new commands and Google4R has not been
|
79
|
+
# # upgraded yet. It is not fatal.
|
80
|
+
# render :text => 'ignoring unknown notification type', :status => 200
|
81
|
+
# return
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# # ...
|
85
|
+
# end
|
86
|
+
class NotificationHandler
|
87
|
+
# The Frontend object that created this NotificationHandler
|
88
|
+
attr_accessor :frontend
|
89
|
+
|
90
|
+
# Create a new NotificationHandler and assign value of the parameter frontend to
|
91
|
+
# the frontend attribute.
|
92
|
+
def initialize(frontend)
|
93
|
+
@frontend = frontend
|
94
|
+
end
|
95
|
+
|
96
|
+
# Parses the given xml_str and returns the appropriate *Notification class. At the
|
97
|
+
# moment, only NewOrderNotification and OrderStateChangeNotification objects can be
|
98
|
+
# returned.
|
99
|
+
#--
|
100
|
+
# TODO: Add parsing of other notifications here (merchant calculation and the like) when they have been implemented.
|
101
|
+
#++
|
102
|
+
def handle(xml_str)
|
103
|
+
root = REXML::Document.new(xml_str).root
|
104
|
+
|
105
|
+
case root.name
|
106
|
+
when 'new-order-notification' then
|
107
|
+
NewOrderNotification.create_from_element(root, frontend)
|
108
|
+
when 'order-state-change-notification' then
|
109
|
+
OrderStateChangeNotification.create_from_element(root, frontend)
|
110
|
+
else
|
111
|
+
raise UnknownNotificationType, "Unknown notification type: #{root.name}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Google Checkout sends <new-order-notification> messages to the web service when a new
|
117
|
+
# order has been successfully filed with Google Checkout. These messages will be parsed
|
118
|
+
# into NewOrderNotification instances.
|
119
|
+
class NewOrderNotification
|
120
|
+
# The frontend this notification belongs to.
|
121
|
+
attr_accessor :frontend
|
122
|
+
|
123
|
+
# The serial number of the new order notification (String).
|
124
|
+
attr_accessor :serial_number
|
125
|
+
|
126
|
+
# The Google order number the new order notification belongs to (String).
|
127
|
+
attr_accessor :google_order_number
|
128
|
+
|
129
|
+
# The buyer's billing address (Address).
|
130
|
+
attr_accessor :buyer_billing_address
|
131
|
+
|
132
|
+
# The buyer's shipping adress (Address).
|
133
|
+
attr_accessor :buyer_shipping_address
|
134
|
+
|
135
|
+
# The buyer's ID from Google Checkout (String).
|
136
|
+
attr_accessor :buyer_id
|
137
|
+
|
138
|
+
# The buyer's marketing preferences (MarketingPreferences).
|
139
|
+
attr_accessor :marketing_preferences
|
140
|
+
|
141
|
+
# The order's financial order state (String, one of FinancialOrderState::*).
|
142
|
+
attr_accessor :financial_order_state
|
143
|
+
|
144
|
+
# The order's fulfillment state (String, one of FulfillmentOrderState::*).
|
145
|
+
attr_accessor :fulfillment_order_state
|
146
|
+
|
147
|
+
# The order's number at Google Checkout (String).
|
148
|
+
attr_accessor :google_order_number
|
149
|
+
|
150
|
+
# The order's total adjustment (OrderAdjustment).
|
151
|
+
attr_accessor :order_adjustment
|
152
|
+
|
153
|
+
# The order's total amount (Money).
|
154
|
+
attr_accessor :order_total
|
155
|
+
|
156
|
+
# The order's shopping cart (ShoppingCart)
|
157
|
+
attr_accessor :shopping_cart
|
158
|
+
|
159
|
+
# The order's timestamp (Time), also see #timestamp=
|
160
|
+
attr_accessor :timestamp
|
161
|
+
|
162
|
+
# Set the order's timestamp (Time). When the timestamp is set then the tax tables valid
|
163
|
+
# at the given point of time are set into the attribute tax tables from the frontend's
|
164
|
+
# tax_table_factory.
|
165
|
+
def timestamp=(time)
|
166
|
+
@timestamp = time
|
167
|
+
@tax_tables = frontend.tax_table_factory.effective_tax_tables_at(time)
|
168
|
+
end
|
169
|
+
|
170
|
+
# The tax tables for the items in the order notification.
|
171
|
+
attr_reader :tax_tables
|
172
|
+
|
173
|
+
# Sets the frontend attribute to the value of the frontend parameter.
|
174
|
+
def initialize(frontend)
|
175
|
+
@frontend = frontend
|
176
|
+
end
|
177
|
+
|
178
|
+
# Factory method to create a new CheckoutNotification object from the REXML:Element object
|
179
|
+
#
|
180
|
+
# Raises NoMethodError and RuntimeError exceptions if the given element misses required
|
181
|
+
# elements.
|
182
|
+
#
|
183
|
+
# You have to pass in the Frontend class this notification belongs to.
|
184
|
+
def self.create_from_element(element, frontend)
|
185
|
+
result = NewOrderNotification.new(frontend)
|
186
|
+
|
187
|
+
result.timestamp = Time.parse(element.elements['timestamp'].text)
|
188
|
+
result.serial_number = element.elements['@serial-number'].value
|
189
|
+
result.google_order_number = element.elements['google-order-number'].text
|
190
|
+
result.buyer_billing_address = Address.create_from_element(element.elements['buyer-billing-address'])
|
191
|
+
result.buyer_shipping_address = Address.create_from_element(element.elements['buyer-shipping-address'])
|
192
|
+
result.buyer_id = element.elements['buyer-id'].text
|
193
|
+
result.marketing_preferences = MarketingPreferences.create_from_element(element.elements['buyer-marketing-preferences'])
|
194
|
+
result.financial_order_state = element.elements['financial-order-state'].text
|
195
|
+
result.fulfillment_order_state = element.elements['fulfillment-order-state'].text
|
196
|
+
result.order_adjustment = OrderAdjustment.create_from_element(element.elements['order-adjustment'])
|
197
|
+
result.shopping_cart = ShoppingCart.create_from_element(element.elements['shopping-cart'], result)
|
198
|
+
|
199
|
+
amount = element.elements['order-total'].text.to_s.gsub(/[^0-9]/, '').to_i rescue nil
|
200
|
+
currency = element.elements['order-total/@currency'].value rescue nil
|
201
|
+
result.order_total = Money.new(amount, currency)
|
202
|
+
|
203
|
+
return result
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# GoogleCheckout sends <order-change-notification> messages to the web service when the
|
208
|
+
# order's state changes. They will get parsed into OrderStateChangeNotification objects.
|
209
|
+
class OrderStateChangeNotification
|
210
|
+
# The frontend this notification belongs to.
|
211
|
+
attr_accessor :frontend
|
212
|
+
|
213
|
+
# The serial number of the notification (String).
|
214
|
+
attr_accessor :serial_number
|
215
|
+
|
216
|
+
# The order number in Google's database (String).
|
217
|
+
attr_accessor :google_order_number
|
218
|
+
|
219
|
+
# The previous financial state of the order (String, one of FinancialOrderState::*).
|
220
|
+
attr_accessor :previous_financial_order_state
|
221
|
+
|
222
|
+
# The new financial state of the order (String, one of FinancialOrderState::*).
|
223
|
+
attr_accessor :new_financial_order_state
|
224
|
+
|
225
|
+
# The previous fulfillment state of the order (String, one of FulfillmentOrderState::*).
|
226
|
+
attr_accessor :previous_fulfillment_order_state
|
227
|
+
|
228
|
+
# The new fulfillment state of the order (String, one of FulfillmentOrderState::*).
|
229
|
+
attr_accessor :new_fulfillment_order_state
|
230
|
+
|
231
|
+
# The reason for the change (String, can be nil).
|
232
|
+
attr_accessor :reason
|
233
|
+
|
234
|
+
# The timestamp of the notification. Also see #timestamp=
|
235
|
+
attr_accessor :timestamp
|
236
|
+
|
237
|
+
# Set the order's timestamp (Time). When the timestamp is set then the tax tables valid
|
238
|
+
# at the given point of time are set into the attribute tax tables from the frontend's
|
239
|
+
# tax_table_factory.
|
240
|
+
def timestamp=(time)
|
241
|
+
@timestamp = time
|
242
|
+
@tax_tables = frontend.tax_table_factory.effective_tax_tables_at(time)
|
243
|
+
end
|
244
|
+
|
245
|
+
# The tax tables for the items in the order notification.
|
246
|
+
attr_reader :tax_tables
|
247
|
+
|
248
|
+
# Sets the frontend attribute to the value of the frontend parameter.
|
249
|
+
def initialize(frontend)
|
250
|
+
@frontend = frontend
|
251
|
+
end
|
252
|
+
|
253
|
+
# Factory methdo that creates a new OrderStateChangeNotification from an REXML::Element instance.
|
254
|
+
# Use this to create instances of OrderStateChangeNotification.
|
255
|
+
#
|
256
|
+
# Raises NoMethodError and RuntimeError exceptions if the given element misses required
|
257
|
+
# elements.
|
258
|
+
def self.create_from_element(element, frontend)
|
259
|
+
result = OrderStateChangeNotification.new(frontend)
|
260
|
+
|
261
|
+
result.timestamp = Time.parse(element.elements['timestamp'].text)
|
262
|
+
|
263
|
+
result.serial_number = element.elements['@serial-number'].value
|
264
|
+
result.google_order_number = element.elements['google-order-number'].text
|
265
|
+
result.new_financial_order_state = element.elements['new-financial-order-state'].text
|
266
|
+
result.previous_financial_order_state = element.elements['previous-financial-order-state'].text
|
267
|
+
result.new_fulfillment_order_state = element.elements['new-fulfillment-order-state'].text
|
268
|
+
result.previous_fulfillment_order_state = element.elements['previous-fulfillment-order-state'].text
|
269
|
+
result.reason = element.elements['reason'].text rescue nil
|
270
|
+
|
271
|
+
return result
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Container for the valid financial order states as defined in the
|
276
|
+
# Google Checkout API.
|
277
|
+
module FinancialOrderState
|
278
|
+
REVIEWING = "REVIEWING".freeze
|
279
|
+
CHARGEABLE = "CHARGEABLE".freeze
|
280
|
+
CHARGING = "CHARGING".freeze
|
281
|
+
CHARGED = "CHARGED".freeze
|
282
|
+
PAYMENT_DECLINED = "PAYMENT_DECLINED".freeze
|
283
|
+
CANCELLED = "CANCELLED".freeze
|
284
|
+
CANCELLED_BY_GOOGLE = "CANCELLED_BY_GOOGLE".freeze
|
285
|
+
end
|
286
|
+
|
287
|
+
# Container for the valid fulfillment order states as defined in the
|
288
|
+
# Google Checkout API.
|
289
|
+
module FulfillmentOrderState
|
290
|
+
NEW = "NEW".freeze
|
291
|
+
PROCESSING = "PROCESSING".freeze
|
292
|
+
DELIVERED = "DELIVERED".freeze
|
293
|
+
WILL_NOT_DELIVER = "WILL_NOT_DELIVER".freeze
|
294
|
+
end
|
295
|
+
|
296
|
+
# Address instances are used in NewOrderNotification objects for the buyer's billing
|
297
|
+
# and buyer's shipping address.
|
298
|
+
class Address
|
299
|
+
# Contact name (String, optional).
|
300
|
+
attr_accessor :contact_name
|
301
|
+
|
302
|
+
# Second Address line (String).
|
303
|
+
attr_accessor :address1
|
304
|
+
|
305
|
+
# Second Address line (String optional).
|
306
|
+
attr_accessor :address2
|
307
|
+
|
308
|
+
# The buyer's city name (String).
|
309
|
+
attr_accessor :city
|
310
|
+
|
311
|
+
# The buyer's company name (String; optional).
|
312
|
+
attr_accessor :company_name
|
313
|
+
|
314
|
+
# The buyer's country code (String, 2 chars, ISO 3166).
|
315
|
+
attr_accessor :country_code
|
316
|
+
|
317
|
+
# The buyer's email address (String; optional).
|
318
|
+
attr_accessor :email
|
319
|
+
|
320
|
+
# The buyer's phone number (String; optional).
|
321
|
+
attr_accessor :fax
|
322
|
+
|
323
|
+
# The buyer's phone number (String; Optional, can be enforced in CheckoutCommand).)
|
324
|
+
attr_accessor :phone
|
325
|
+
|
326
|
+
# The buyers postal/zip code (String).
|
327
|
+
attr_accessor :postal_code
|
328
|
+
|
329
|
+
# The buyer's geographical region (String).
|
330
|
+
attr_accessor :region
|
331
|
+
|
332
|
+
# Creates a new Address from the given REXML::Element instance.
|
333
|
+
def self.create_from_element(element)
|
334
|
+
result = Address.new
|
335
|
+
|
336
|
+
result.address1 = element.elements['address1'].text
|
337
|
+
result.address2 = element.elements['address2'].text rescue nil
|
338
|
+
result.city = element.elements['city'].text
|
339
|
+
result.company_name = element.elements['company-name'].text rescue nil
|
340
|
+
result.contact_name = element.elements['contact-name'].text rescue nil
|
341
|
+
result.country_code = element.elements['country-code'].text
|
342
|
+
result.email = element.elements['email'].text rescue nil
|
343
|
+
result.fax = element.elements['fax'].text rescue nil
|
344
|
+
result.phone = element.elements['phone'].text rescue nil
|
345
|
+
result.postal_code = element.elements['postal-code'].text
|
346
|
+
result.region = element.elements['region'].text
|
347
|
+
|
348
|
+
return result
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# The marketing preferences of a customer.
|
353
|
+
class MarketingPreferences
|
354
|
+
# Boolean, true iff the customer wants to receive emails.
|
355
|
+
attr_accessor :email_allowed
|
356
|
+
|
357
|
+
# Creates a new MarketingPreferences object from a given REXML::Element instance.
|
358
|
+
def self.create_from_element(element)
|
359
|
+
result = MarketingPreferences.new
|
360
|
+
|
361
|
+
result.email_allowed = (element.elements['email-allowed'].text.downcase == 'true')
|
362
|
+
|
363
|
+
return result
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# MerchantCodes represent gift certificates or coupons that have been used in an order.
|
368
|
+
#
|
369
|
+
# Only used with Merchant Calculations.
|
370
|
+
class MerchantCode
|
371
|
+
GIFT_CERTIFICATE = "GIFT_CERTIFICATE".freeze
|
372
|
+
COUPON = "COUPON".freeze
|
373
|
+
|
374
|
+
# The type of the adjustment. Can be one of GIFT_CERTIFICATE and COUPON.
|
375
|
+
attr_accessor :type
|
376
|
+
|
377
|
+
# The adjustment's code (String).
|
378
|
+
attr_accessor :code
|
379
|
+
|
380
|
+
# The amount of money that has been calculated as the adjustment's worth (Money, optional).
|
381
|
+
attr_accessor :calculated_amount
|
382
|
+
|
383
|
+
# The amount of the adjustment that has been applied to the cart's total (Money).
|
384
|
+
attr_accessor :applied_amount
|
385
|
+
|
386
|
+
# The message associated with the direct adjustment (String, optional).
|
387
|
+
attr_accessor :message
|
388
|
+
|
389
|
+
# Creates the MerchantCode from the given REXML::Element instance. The Element's
|
390
|
+
# name must be "gift-certificate-adjustment" or "coupon-adjustment".
|
391
|
+
def self.create_from_element(element)
|
392
|
+
result = MerchantCode.new
|
393
|
+
|
394
|
+
result.type =
|
395
|
+
case element.name
|
396
|
+
when 'gift-certificate-adjustment' then
|
397
|
+
GIFT_CERTIFICATE
|
398
|
+
when 'coupon-adjustment' then
|
399
|
+
COUPON
|
400
|
+
else
|
401
|
+
raise ArgumentError, "Invalid tag name: #{element.name} in \n—-\n#{element.to_s}\n—-."
|
402
|
+
end
|
403
|
+
|
404
|
+
result.code = element.elements['code'].text
|
405
|
+
|
406
|
+
amount = element.elements['calculated-amount'].text.to_s.gsub(/[^0-9]/, '').to_i rescue nil
|
407
|
+
currency = element.elements['calculated-amount/@currency'].value rescue nil
|
408
|
+
result.calculated_amount = Money.new(amount, currency) unless amount.nil?
|
409
|
+
|
410
|
+
amount = element.elements['applied-amount'].text.to_s.gsub(/[^0-9]/, '').to_i
|
411
|
+
currency = element.elements['applied-amount/@currency'].value
|
412
|
+
result.applied_amount = Money.new(amount, currency)
|
413
|
+
|
414
|
+
result.message = element.elements['message'].text rescue nil
|
415
|
+
|
416
|
+
return result
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# ShippingAdjustments represent the chosen shipping method.
|
421
|
+
class ShippingAdjustment
|
422
|
+
MERCHANT_CALCULATED = "MERCHANT_CALCULATED".freeze
|
423
|
+
FLAT_RATE = "FLAT_RATE".freeze
|
424
|
+
PICKUP = "PICKUP".freeze
|
425
|
+
|
426
|
+
# The type of the shipping adjustment, one of MERCHANT_CALCULATED, FLAT_RATE
|
427
|
+
# PICKUP.
|
428
|
+
attr_accessor :type
|
429
|
+
|
430
|
+
# The name of the shipping adjustment.
|
431
|
+
attr_accessor :name
|
432
|
+
|
433
|
+
# The cost of the selected shipping (Money).
|
434
|
+
attr_accessor :cost
|
435
|
+
|
436
|
+
# Creates a new ShippingAdjustment object from a REXML::Element object.
|
437
|
+
#
|
438
|
+
# Can raise a RuntimeException if the given Element is invalid.
|
439
|
+
def self.create_from_element(element)
|
440
|
+
result = ShippingAdjustment.new
|
441
|
+
|
442
|
+
result.type =
|
443
|
+
case element.name
|
444
|
+
when 'flat-rate-shipping-adjustment' then
|
445
|
+
FLAT_RATE
|
446
|
+
when 'pickup-shipping-adjustment' then
|
447
|
+
PICKUP
|
448
|
+
when 'merchant-calculated-shipping-adjustment' then
|
449
|
+
MERCHANT_CALCULATED
|
450
|
+
else
|
451
|
+
raise "Unexpected shipping adjustment '#{element.name}'"
|
452
|
+
end
|
453
|
+
|
454
|
+
result.name = element.elements['shipping-name'].text
|
455
|
+
|
456
|
+
amount = element.elements['shipping-cost'].text.to_s.gsub(/[^0-9]/, '').to_i
|
457
|
+
currency = element.elements['shipping-cost/@currency'].value
|
458
|
+
result.cost = Money.new(amount, currency)
|
459
|
+
|
460
|
+
return result
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
# OrderAdjustment objects contain the adjustments (i.e. the entities in the cart that
|
465
|
+
# represent positive and negative amounts (at the moment Google Checkout support coupons,
|
466
|
+
# gift certificates and shipping)).
|
467
|
+
class OrderAdjustment
|
468
|
+
# The <adjustment-total> tag contains the total adjustment to an order total based
|
469
|
+
# on tax, shipping, gift certificates and coupon codes (optional).
|
470
|
+
attr_accessor :adjustment_total
|
471
|
+
|
472
|
+
# Boolean, true iff the merchant calculations have been successful (optional).
|
473
|
+
attr_accessor :merchant_calculation_successful
|
474
|
+
|
475
|
+
# Array of MerchantCode objects.
|
476
|
+
attr_accessor :merchant_codes
|
477
|
+
|
478
|
+
# The chosen ShippingAdjustment object for this order.
|
479
|
+
attr_accessor :shipping
|
480
|
+
|
481
|
+
# The total amount of tax that has been paid for this order (Money, optional).
|
482
|
+
attr_accessor :total_tax
|
483
|
+
|
484
|
+
# Creates a new OrderAdjustment from a given REXML::Element object.
|
485
|
+
def self.create_from_element(element)
|
486
|
+
result = OrderAdjustment.new
|
487
|
+
|
488
|
+
amount = element.elements['total-tax'].text.to_s.gsub(/[^0-9]/, '').to_i rescue nil
|
489
|
+
currency = element.elements['total-tax/@currency'].value rescue nil
|
490
|
+
result.total_tax = Money.new(amount, currency) unless amount.nil?
|
491
|
+
|
492
|
+
shipping_element = element.elements["shipping/*"]
|
493
|
+
result.shipping = ShippingAdjustment.create_from_element(shipping_element) unless shipping_element.nil?
|
494
|
+
|
495
|
+
result.merchant_codes = Array.new
|
496
|
+
element.elements.each(%q{merchant-codes/*}) { |elem| result.merchant_codes << MerchantCode.create_from_element(elem) }
|
497
|
+
|
498
|
+
result.merchant_calculation_successful = (element.elements['merchant-calculation-successful'].text.downcase == 'true') rescue nil
|
499
|
+
|
500
|
+
amount = element.elements['adjustment-total'].text.to_s.gsub(/[^0-9]/, '').to_i rescue nil
|
501
|
+
currency = element.elements['adjustment-total/@currency'].value rescue nil
|
502
|
+
result.adjustment_total = Money.new(amount, currency) unless amount.nil?
|
503
|
+
|
504
|
+
return result
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
# Class with static methods to parse the <merchant-private(-item)-data> tags.
|
509
|
+
class PrivateDataParser
|
510
|
+
# Returns a Hash with the representation of the structure in
|
511
|
+
# item/merchant-private-item-data.
|
512
|
+
def self.element_to_value(element)
|
513
|
+
# The return value; We will iterate over all children below. When we see a REXML::Element
|
514
|
+
# child then we set result to a Hash if it is nil. After the result is a Hash, we will
|
515
|
+
# ignore all REXML::Text children.
|
516
|
+
# Otherwise, result will be set to the REXML::Text node's value if it is not whitespace
|
517
|
+
# only.
|
518
|
+
result = nil
|
519
|
+
|
520
|
+
element.each_child do |child|
|
521
|
+
case child
|
522
|
+
when REXML::Element
|
523
|
+
result ||= Hash.new
|
524
|
+
child_value = self.element_to_value(child)
|
525
|
+
|
526
|
+
# <foo>bar</foo> becomes 'foo' => 'bar
|
527
|
+
# <foo>foo</foo><foo>bar</foo> becomes 'foo' => [ 'foo', 'bar' ]
|
528
|
+
if result[child.name].nil? then
|
529
|
+
result[child.name] = child_value
|
530
|
+
elsif result[child.name].kind_of?(Array) then
|
531
|
+
result[child.name] << child_value
|
532
|
+
else
|
533
|
+
tmp = result[child.name]
|
534
|
+
result[child.name] = [ tmp, child_value ]
|
535
|
+
end
|
536
|
+
when REXML::Text
|
537
|
+
next if result.kind_of?(Hash) # ignore text if we already found a tag
|
538
|
+
str = child.value.strip
|
539
|
+
result = str if str.length > 0
|
540
|
+
else
|
541
|
+
# ignore
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
return result
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|