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