google4r 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|