archerfinley-google4r-checkout-1.0.5 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/CHANGES +93 -0
  2. data/LICENSE +22 -0
  3. data/README +58 -0
  4. data/Rakefile +13 -0
  5. data/VERSION +1 -0
  6. data/google4r-checkout-1.0.5-1.0.5.gem +0 -0
  7. data/google4r-checkout-1.0.5.gemspec +174 -0
  8. data/lib/google4r/checkout.rb +34 -0
  9. data/lib/google4r/checkout/commands.rb +567 -0
  10. data/lib/google4r/checkout/frontend.rb +210 -0
  11. data/lib/google4r/checkout/merchant_calculation.rb +321 -0
  12. data/lib/google4r/checkout/notifications.rb +708 -0
  13. data/lib/google4r/checkout/shared.rb +1086 -0
  14. data/lib/google4r/checkout/utils.rb +94 -0
  15. data/lib/google4r/checkout/xml_generation.rb +880 -0
  16. data/test/integration/checkout_command_test.rb +174 -0
  17. data/test/unit/add_merchant_order_number_command_test.rb +70 -0
  18. data/test/unit/add_tracking_data_command_test.rb +75 -0
  19. data/test/unit/address_test.rb +131 -0
  20. data/test/unit/anonymous_address_test.rb +75 -0
  21. data/test/unit/archive_order_command_test.rb +66 -0
  22. data/test/unit/area_test.rb +44 -0
  23. data/test/unit/authorization_amount_notification_test.rb +69 -0
  24. data/test/unit/authorize_order_command_test.rb +66 -0
  25. data/test/unit/backorder_items_command_test.rb +83 -0
  26. data/test/unit/callback_handler_test.rb +83 -0
  27. data/test/unit/cancel_items_command_test.rb +89 -0
  28. data/test/unit/cancel_order_command_test.rb +83 -0
  29. data/test/unit/carrier_calculated_shipping_test.rb +57 -0
  30. data/test/unit/charge_amount_notification_test.rb +64 -0
  31. data/test/unit/charge_order_command_test.rb +77 -0
  32. data/test/unit/chargeback_amount_notification_test.rb +65 -0
  33. data/test/unit/checkout_command_test.rb +125 -0
  34. data/test/unit/checkout_command_xml_generator_test.rb +218 -0
  35. data/test/unit/command_test.rb +116 -0
  36. data/test/unit/deliver_order_command_test.rb +70 -0
  37. data/test/unit/delivery_method_test.rb +42 -0
  38. data/test/unit/digital_content_test.rb +105 -0
  39. data/test/unit/flat_rate_shipping_test.rb +132 -0
  40. data/test/unit/frontend_test.rb +144 -0
  41. data/test/unit/item_info_test.rb +69 -0
  42. data/test/unit/item_test.rb +171 -0
  43. data/test/unit/marketing_preferences_test.rb +65 -0
  44. data/test/unit/merchant_calculated_shipping_test.rb +172 -0
  45. data/test/unit/merchant_calculation_callback_test.rb +137 -0
  46. data/test/unit/merchant_calculation_result_test.rb +78 -0
  47. data/test/unit/merchant_calculation_results_test.rb +178 -0
  48. data/test/unit/merchant_code_result_test.rb +51 -0
  49. data/test/unit/merchant_code_test.rb +122 -0
  50. data/test/unit/new_order_notification_test.rb +115 -0
  51. data/test/unit/notification_acknowledgement_test.rb +67 -0
  52. data/test/unit/notification_handler_test.rb +113 -0
  53. data/test/unit/order_adjustment_test.rb +119 -0
  54. data/test/unit/order_report_command_test.rb +111 -0
  55. data/test/unit/order_state_change_notification_test.rb +158 -0
  56. data/test/unit/pickup_shipping_test.rb +70 -0
  57. data/test/unit/postal_area_test.rb +71 -0
  58. data/test/unit/private_data_parser_test.rb +68 -0
  59. data/test/unit/refund_amount_notification_test.rb +65 -0
  60. data/test/unit/refund_order_command_test.rb +86 -0
  61. data/test/unit/reset_items_shipping_information_command_test.rb +83 -0
  62. data/test/unit/return_items_command_test.rb +83 -0
  63. data/test/unit/risk_information_notification_test.rb +98 -0
  64. data/test/unit/send_buyer_message_command_test.rb +73 -0
  65. data/test/unit/ship_items_command_test.rb +101 -0
  66. data/test/unit/shipping_adjustment_test.rb +100 -0
  67. data/test/unit/shopping_cart_test.rb +146 -0
  68. data/test/unit/tax_rule_test.rb +70 -0
  69. data/test/unit/tax_table_test.rb +82 -0
  70. data/test/unit/tracking_data_test.rb +54 -0
  71. data/test/unit/unarchive_order_command_test.rb +66 -0
  72. data/test/unit/us_country_area_test.rb +76 -0
  73. data/test/unit/us_state_area_test.rb +70 -0
  74. data/test/unit/us_zip_area_test.rb +66 -0
  75. data/test/unit/world_area_test.rb +48 -0
  76. data/var/cacert.pem +7815 -0
  77. metadata +189 -0
@@ -0,0 +1,1086 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/shared.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 shared by the notification
28
+ # handling and parsing as well as the command generating code.
29
+
30
+ #--
31
+ # TODO: Make the optional attributes return defaults that make sense, i.e. Money.new(0)?
32
+ #++
33
+ module Google4R #:nodoc:
34
+ module Checkout #:nodoc:
35
+ # This exception is thrown by Command#send_to_google_checkout when an error occured.
36
+ class GoogleCheckoutError < Exception
37
+ # The serial number of the error returned by Google.
38
+ attr_reader :serial_number
39
+
40
+ # The HTTP response code of Google's response.
41
+ attr_reader :response_code
42
+
43
+ # The parameter is a hash with the entries :serial_number, :message and :response_code.
44
+ # The attributes serial_number, message and response_code are set to the values in the
45
+ # Hash.
46
+ def initialize(hash)
47
+ @response_code = hash[:response_code]
48
+ @message = hash[:message]
49
+ @serial_number = hash[:serial_number]
50
+ end
51
+
52
+ # Returns a human readable representation of the Exception with the message, HTTP
53
+ # response code and serial number as returned by Google checkout.
54
+ def to_s
55
+ "GoogleCheckoutError: message = '#{@message}', response code = '#{@response_code}', serial number = '#{@serial_number}'."
56
+ end
57
+ end
58
+
59
+ # ShoppingCart instances are containers for Item instances. You can add
60
+ # Items to the class using #create_item (see the documentation of this
61
+ # method for an example).
62
+ class ShoppingCart
63
+ # The owner of this cart. At the moment, this always is the CheckoutCartCommand.
64
+ attr_reader :owner
65
+
66
+ # The items in the cart. Do not modify this array directly but use
67
+ # #create_item to add items.
68
+ attr_reader :items
69
+
70
+ # You can set the <cart-expiration> time with this property. If left
71
+ # unset then the tag will not be generated and the cart will never
72
+ # expire.
73
+ attr_accessor :expires_at
74
+
75
+ # You can set almost arbitrary data into the cart using this method.
76
+ #
77
+ # The data will be converted to XML in the following way: The keys are converted
78
+ # to tag names (whitespace becomes "-", all chars not matching /[a-zA-Z0-9\-_])/
79
+ # will be removed.
80
+ #
81
+ # If a value is an array then the key for this value will be used as the tag
82
+ # name for each of the arrays's entries.
83
+ #
84
+ # Arrays will be flattened before it is processed.
85
+ #
86
+ # === Example
87
+ #
88
+ # cart.private_data = { 'foo' => { 'bar' => 'baz' } })
89
+ #
90
+ # # will produce the following XML
91
+ #
92
+ # <foo>
93
+ # <bar>baz</bar>
94
+ # </foo>
95
+ #
96
+ #
97
+ # cart.private_data = { 'foo' => [ { 'bar' => 'baz' }, "d'oh", 2 ] }
98
+ #
99
+ # # will produce the following XML
100
+ #
101
+ # <foo>
102
+ # <bar>baz</bar>
103
+ # </foo>
104
+ # <foo>d&amp;</foo>
105
+ # <foo>2</foo>
106
+ attr_reader :private_data
107
+
108
+ # Sets the value of the private_data attribute.
109
+ def private_data=(value)
110
+ raise "The given value #{value.inspect} is not a Hash!" unless value.kind_of?(Hash)
111
+ @private_data = value
112
+ end
113
+
114
+ # Initialize a new ShoppingCart with an empty Array for the items.
115
+ def initialize(owner)
116
+ @owner = owner
117
+ @items = Array.new
118
+ end
119
+
120
+ # Use this method to add a new item to the cart. If you use a block with
121
+ # this method then the block will be given the new item. The new item
122
+ # will be returned in any case.
123
+ #
124
+ # Passing a block is the preferred way of using this method.
125
+ #
126
+ # === Example
127
+ #
128
+ # # Using a block (preferred).
129
+ # cart = ShoppingCart.new
130
+ #
131
+ # cart.create_item do |item|
132
+ # item.name = "Dry Food Pack"
133
+ # item.description = "A pack of highly nutritious..."
134
+ # item.unit_price = Money.new(3500, "USD") # $35.00
135
+ # item.quantity = 1
136
+ # end
137
+ #
138
+ # # Not using a block.
139
+ # cart = ShoppingCart.new
140
+ #
141
+ # item = cart.create_item
142
+ # item.name = "Dry Food Pack"
143
+ # item.description = "A pack of highly nutritious..."
144
+ # item.unit_price = Money.new(3500, "USD") # $35.00
145
+ # item.quantity = 1
146
+ def create_item(&block)
147
+ item = Item.new(self)
148
+ @items << item
149
+
150
+ # Pass the newly generated item to the given block to set its attributes.
151
+ yield(item) if block_given?
152
+
153
+ return item
154
+ end
155
+
156
+ # Creates a new ShoppingCart object from a REXML::Element object.
157
+ def self.create_from_element(element, owner)
158
+ result = ShoppingCart.new(owner)
159
+
160
+ text = element.elements['cart-expiration/good-until-date'].text rescue nil
161
+ result.expires_at = Time.parse(text) unless text.nil?
162
+
163
+ data_element = element.elements['merchant-private-data']
164
+ value = PrivateDataParser.element_to_value(data_element) unless data_element.nil?
165
+
166
+ result.private_data = value unless value.nil?
167
+
168
+ element.elements.each('items/item') do |item_element|
169
+ result.items << Item.create_from_element(item_element, result)
170
+ end
171
+
172
+ return result
173
+ end
174
+ end
175
+
176
+ # An Item object represents a line of goods in the shopping cart/receipt.
177
+ #
178
+ # You should never initialize them directly but use ShoppingCart#create_item instead.
179
+ #
180
+ # Note that you have to create/set the tax tables for the owner of the cart in which
181
+ # the item is before you can set the tax_table attribute.
182
+ class Item
183
+ # The cart that this item belongs to.
184
+ attr_reader :shopping_cart
185
+
186
+ # The name of the cart item (string, required).
187
+ attr_accessor :name
188
+
189
+ # The description of the cart item (string, required).
190
+ attr_accessor :description
191
+
192
+ # The price for one unit of the given good (Money instance, required).
193
+ attr_reader :unit_price
194
+
195
+ # Sets the price for one unit of goods described by this item. money must respond to
196
+ # :cents and :currency as the Money class does.
197
+ def unit_price=(money)
198
+ if not (money.respond_to?(:cents) and money.respond_to?(:currency)) then
199
+ raise "Invalid price - does not respond to :cents and :currency - #{money.inspect}."
200
+ end
201
+
202
+ @unit_price = money
203
+ end
204
+
205
+ # The weigth of the cart item (Weight, required when carrier calculated
206
+ # shipping is used)
207
+ attr_reader :weight
208
+
209
+ # Sets the weight of this item
210
+ def weight=(weight)
211
+ raise "Invalid object type for weight" unless weight.kind_of? Weight
212
+ @weight = weight
213
+ end
214
+
215
+
216
+ # Number of units that this item represents (integer, required).
217
+ attr_accessor :quantity
218
+
219
+ # Optional string value that is used to store the item's id (defined by the merchant)
220
+ # in the cart. Serialized to <merchant-item-id> in XML. Displayed by Google Checkout.
221
+ attr_accessor :id
222
+
223
+ # Optional hash value that is used to store the item's id (defined by the merchant)
224
+ # in the cart. Serialized to <merchant-private-item-data> in XML. Not displayed by
225
+ # Google Checkout.
226
+ #
227
+ # Must be a Hash. See ShoppingCart#private_data on how the serialization to XML is
228
+ # done.
229
+ attr_reader :private_data
230
+
231
+ # Sets the private data for this item.
232
+ def private_data=(value)
233
+ raise "The given value #{value.inspect} is not a Hash!" unless value.kind_of?(Hash)
234
+ @private_data = value
235
+ end
236
+
237
+ # The tax table to use for this item. Optional.
238
+ attr_reader :tax_table
239
+
240
+ # Sets the tax table to use for this item. When you set this attribute using this
241
+ # method then the used table must already be added to the cart. Otherwise, a
242
+ # RuntimeError will be raised.
243
+ def tax_table=(table)
244
+ raise "The table #{table.inspect} is not in the item's cart yet!" unless shopping_cart.owner.tax_tables.include?(table)
245
+
246
+ @tax_table = table
247
+ end
248
+
249
+ # DigitalContent information for this item. Optional.
250
+ attr_reader :digital_content
251
+
252
+ def create_digital_content(digital_content=nil, &block)
253
+
254
+ if @digital_content.nil?
255
+ if digital_content.nil?
256
+ @digital_content = DigitalContent.new
257
+ else
258
+ @digital_content = digital_content
259
+ end
260
+ end
261
+
262
+ if block_given?
263
+ yield @digital_content
264
+ end
265
+
266
+ return @digital_content
267
+ end
268
+
269
+ # Create a new Item in the given Cart. You should not instantize this class directly
270
+ # but use Cart#create_item instead.
271
+ def initialize(shopping_cart)
272
+ @shopping_cart = shopping_cart
273
+ end
274
+
275
+ # Creates a new Item object from a REXML::Element object.
276
+ def self.create_from_element(element, shopping_cart)
277
+ result = Item.new(shopping_cart)
278
+
279
+ result.name = element.elements['item-name'].text
280
+ result.description = element.elements['item-description'].text
281
+ result.quantity = element.elements['quantity'].text.to_i
282
+ result.id = element.elements['merchant-item-id'].text rescue nil
283
+
284
+ weight_element = element.elements['item-weight']
285
+ if not weight_element.nil?
286
+ result.weight = Weight.create_from_element(weight_element)
287
+ end
288
+
289
+ data_element = element.elements['merchant-private-item-data']
290
+ if not data_element.nil? then
291
+ value = PrivateDataParser.element_to_value(data_element)
292
+ result.private_data = value unless value.nil?
293
+ end
294
+
295
+ table_selector = element.elements['tax-table-selector'].text rescue nil
296
+ if not table_selector.nil? then
297
+ result.tax_table = shopping_cart.owner.tax_tables.find {|table| table.name == table_selector }
298
+ end
299
+
300
+ unit_price = (element.elements['unit-price'].text.to_f * 100).to_i
301
+ unit_price_currency = element.elements['unit-price'].attributes['currency']
302
+ result.unit_price = Money.new(unit_price, unit_price_currency)
303
+
304
+ digital_content_element = element.elements['digital-content']
305
+ if not digital_content_element.nil?
306
+ result.create_digital_content(DigitalContent.create_from_element(digital_content_element))
307
+ end
308
+
309
+ return result
310
+ end
311
+
312
+ # A DigitalContent item represents the information relating to online delivery of digital items
313
+ #
314
+ # You should never initialize it directly but use Item#digital_content instead
315
+ #
316
+ # See http://code.google.com/apis/checkout/developer/Google_Checkout_Digital_Delivery.html
317
+ # for information on Google Checkout's idea of digital content.
318
+ #
319
+ # item.digital_content do |dc|
320
+ # dc.optimistic!
321
+ # dc.description = %{Here's some information on how to get your content}
322
+ # end
323
+ class DigitalContent
324
+
325
+ # Constants for display-disposition
326
+ OPTIMISTIC = 'OPTIMISTIC'
327
+ PESSIMISTIC = 'PESSIMISTIC'
328
+
329
+ # A description of how the user should access the digital content
330
+ # after completing the order (string, required for description-based
331
+ # delivery, otherwise optional)
332
+ attr_accessor :description
333
+
334
+ # Either 'OPTIMISTIC' or 'PESSIMISTIC'. If OPTIMISTIC, then Google
335
+ # will display instructions for accessing the digital content as soon
336
+ #as the buyer confirms the order. Optional, but default is PESSIMISTIC
337
+ attr_reader :display_disposition
338
+
339
+ def display_disposition=(disposition)
340
+ raise "display_disposition can only be set to PESSIMISTIC or OPTIMISTIC" unless disposition == OPTIMISTIC || disposition == PESSIMISTIC
341
+ @display_disposition = disposition
342
+ end
343
+
344
+ # A boolean identifying whether email delivery is used for this item.
345
+ attr_accessor :email_delivery
346
+
347
+ # A key required by the user to access this digital content after completing the order (string, optional)
348
+ attr_accessor :key
349
+
350
+ # A URL required by the user to access this digital content after completing the order (string, optional)
351
+ attr_accessor :url
352
+
353
+ def initialize
354
+ @display_disposition = PESSIMISTIC
355
+ end
356
+
357
+ # Creates a new DigitalContent object from a REXML::Element object
358
+ def self.create_from_element(element)
359
+ result = DigitalContent.new
360
+ result.description = element.elements['description'].text rescue nil
361
+ result.display_disposition = element.elements['display-disposition'].text rescue nil
362
+ result.email_delivery = element.elements['email-delivery'].text rescue nil # TODO need to convert to boolean?
363
+ result.key = element.elements['key'].text rescue nil
364
+ result.url = element.elements['url'].text rescue nil
365
+ return result
366
+ end
367
+ end
368
+ end
369
+
370
+ # A TaxTable is an ordered array of TaxRule objects. You should create the TaxRule
371
+ # instances using #create_rule
372
+ #
373
+ # You must set up a tax table factory and should only create tax tables from within
374
+ # its temporal factory method as described in the class documentation of Frontend.
375
+ #
376
+ # Each tax table must have one or more tax rules.
377
+ #
378
+ # === Example
379
+ #
380
+ # include Google4R::Checkout
381
+ #
382
+ # tax_free_table = TaxTable.new(false)
383
+ # tax_free_table.name = "default table"
384
+ # tax_free_table.create_rule do |rule|
385
+ # rule.area = UsCountryArea.new(UsCountryArea::ALL)
386
+ # rule.rate = 0.0
387
+ # end
388
+ class TaxTable
389
+ # The name of this tax table (string, required).
390
+ attr_accessor :name
391
+
392
+ # An Array of the TaxRule objects that this TaxTable contains. Use #create_rule do
393
+ # add to this Array but do not change it directly.
394
+ attr_reader :rules
395
+
396
+ # Boolean, true iff the table's standalone attribute is to be set to "true".
397
+ attr_reader :standalone
398
+
399
+ def initialize(standalone)
400
+ @rules = Array.new
401
+
402
+ @standalone = standalone
403
+ end
404
+
405
+ # Use this method to add a new TaxRule to the table. If you use a block with
406
+ # this method then the block will called with the newly created rule for the
407
+ # parameter. The method will return the new rule in any case.
408
+ def create_rule(&block)
409
+ rule = TaxRule.new(self)
410
+ @rules << rule
411
+
412
+ # Pass the newly generated rule to the given block to set its attributes.
413
+ yield(rule) if block_given?
414
+
415
+ return rule
416
+ end
417
+ end
418
+
419
+ # A TaxRule specifies which taxes to apply in which area. Have a look at the "Google
420
+ # Checkout documentation" [http://code.google.com/apis/checkout/developer/index.html#specifying_tax_info]
421
+ # for more information.
422
+ class TaxRule
423
+ # The table this rule belongs to.
424
+ attr_reader :table
425
+
426
+ # The tax rate for this rule (double, required).
427
+ attr_accessor :rate
428
+
429
+ # The area where this tax rule applies (Area subclass instance, required). Serialized
430
+ # to <tax-area> in XML.
431
+ attr_accessor :area
432
+
433
+ # If shipping should be taxed with this tax rule (boolean, defaults to false)
434
+ attr_accessor :shipping_taxed
435
+
436
+ # Creates a new TaxRule in the given TaxTable. Do no call this method yourself
437
+ # but use TaxTable#create_rule instead!
438
+ def initialize(table)
439
+ @table = table
440
+ @shipping_taxed = false
441
+ end
442
+ end
443
+
444
+ # Abstract class for areas that are used to specify a tax area. Do not use this class
445
+ # but only its subclasses.
446
+ class Area
447
+ # Mark this class as abstract by throwing a RuntimeError on initialization.
448
+ def initialize #:nodoc:
449
+ raise "Do not use the abstract class Google::Checkout::Area!"
450
+ end
451
+ end
452
+
453
+ # Instances of UsZipArea represent areas specified by US ZIPs and ZIP patterns.
454
+ class UsZipArea < Area
455
+ # The pattern for this ZIP area.
456
+ attr_accessor :pattern
457
+
458
+ # You can optionally initialize the Area with its value.
459
+ def initialize(pattern=nil)
460
+ self.pattern = pattern unless pattern.nil?
461
+ end
462
+ end
463
+
464
+ # Instances of WorldArea represent a tax area that applies globally.
465
+ class WorldArea < Area
466
+ def initialize
467
+ end
468
+ end
469
+
470
+ # Instances of PostalArea represent a geographical region somewhere in the world.
471
+ class PostalArea < Area
472
+
473
+ # String; The two-letter ISO 3166 country code.
474
+ attr_accessor :country_code
475
+
476
+ # String; Postal code or a range of postal codes for a specific country. To specify a
477
+ # range of postal codes, use an asterisk as a wildcard operator. For example,
478
+ # you can provide a postal_code_pattern value of "SW*" to indicate that a shipping
479
+ # option is available or a tax rule applies in any postal code beginning with the
480
+ # characters SW.
481
+ #
482
+ # === Example
483
+ #
484
+ # area = PostalArea.new('DE')
485
+ # area.postal_code_pattern = '10*'
486
+ attr_accessor :postal_code_pattern
487
+
488
+ # === Parameters
489
+ #
490
+ # country_code should be a two-letter ISO 3166 country code
491
+ # postal_code_pattern should be a full or partial postcode string, using * as a wildcard
492
+ def initialize(country_code=nil, postal_code_pattern=nil)
493
+ @country_code = country_code
494
+ @postal_code_pattern = postal_code_pattern
495
+ end
496
+ end
497
+
498
+ # Instances of UsStateArea represent states in the US.
499
+ class UsStateArea < Area
500
+ # The two-letter code of the US state.
501
+ attr_reader :state
502
+
503
+ # You can optionally initialize the Area with its value.
504
+ def initialize(state=nil)
505
+ @state = state unless state.nil?
506
+ end
507
+
508
+ # Writer for the state attribute. value must match /^[A-Z]{2,2}$/.
509
+ def state=(value)
510
+ raise "Invalid US state: #{value}" unless value =~ /^[A-Z]{2,2}$/
511
+ @state = value
512
+ end
513
+ end
514
+
515
+ # Instances of UsCountryArea identify a region within the US.
516
+ class UsCountryArea < Area
517
+ CONTINENTAL_48 = "CONTINENTAL_48".freeze
518
+ FULL_50_STATES = "FULL_50_STATES".freeze
519
+ ALL = "ALL".freeze
520
+
521
+ # The area that is specified with this UsCountryArea (required). Can be
522
+ # one of UsCountryArea::CONTINENTAL_48, UsCountryArea::FULL_50_STATES
523
+ # and UsCountryArea::ALL.
524
+ # See the Google Checkout API for information on these values.
525
+ attr_reader :area
526
+
527
+ # You can optionally initialize the Area with its value.
528
+ def initialize(area=nil)
529
+ self.area = area unless area.nil?
530
+ end
531
+
532
+ # Writer for the area attribute. value must be one of CONTINENTAL_48,
533
+ # FULL_50_STATES and ALL
534
+ def area=(value)
535
+ raise "Invalid area :#{value}!" unless [ CONTINENTAL_48, FULL_50_STATES, ALL ].include?(value)
536
+ @area = value
537
+ end
538
+ end
539
+
540
+ # Abstract class for delivery methods
541
+ class DeliveryMethod
542
+ # The name of the shipping method (string, required).
543
+ attr_accessor :name
544
+
545
+ # The price of the shipping method (Money instance, required).
546
+ attr_reader :price
547
+
548
+ # Sets the cost for this shipping method. money must respond to :cents and :currency
549
+ # as Money objects would.
550
+ def price=(money)
551
+ if not (money.respond_to?(:cents) and money.respond_to?(:currency)) then
552
+ raise "Invalid cost - does not respond to :cents and :currency - #{money.inspect}."
553
+ end
554
+
555
+ @price = money
556
+ end
557
+
558
+ # Mark this class as abstract by throwing a RuntimeError on initialization.
559
+ def initialize
560
+ raise "Do not use the abstract class Google::Checkout::ShippingMethod!"
561
+ end
562
+ end
563
+
564
+ # Abstract class for shipping methods. Do not use this class directly but only
565
+ # one of its subclasses.
566
+ class ShippingMethod < DeliveryMethod
567
+ # An Array of allowed areas for shipping-restrictions of this shipping instance. Use
568
+ # #create_allowed_area to add to this area but do not change it directly.
569
+ attr_reader :shipping_restrictions_allowed_areas
570
+
571
+ # An Array of excluded areas for shipping-restrictions of this shipping instance. Use
572
+ # #create_excluded_area to add to this area but do not change it directly.
573
+ attr_reader :shipping_restrictions_excluded_areas
574
+
575
+ def initialize
576
+ @shipping_restrictions_allowed_areas = Array.new
577
+ @shipping_restrictions_excluded_areas = Array.new
578
+ end
579
+
580
+ # This method create a new instance of subclass of Area and put it
581
+ # in the array determined by the two symbols provided. The valid
582
+ # symbols for the first two parameters are:
583
+ #
584
+ # type : :shipping_restrictions, :address_filters
585
+ # areas : :allowed_areas, :excluded_areas
586
+ #
587
+ # The third parameter clazz is used to specify the type of
588
+ # Area you want to create. It can be one
589
+ # of { PostalArea, UsCountryArea, UsStateArea, UsZipArea, WorldArea }.
590
+ #
591
+ # Raises a RuntimeError if the parameter clazz is invalid.
592
+ #
593
+ # If you passed a block (preferred) then the block is called
594
+ # with the Area as the only parameter.
595
+ #
596
+ # === Example
597
+ #
598
+ # method = MerchantCalculatedShipping.new
599
+ # method.create_area(:shipping_restrictions, :allowed_areas, UsCountryArea) do |area|
600
+ # area.area = UsCountryArea::ALL
601
+ # end
602
+ def create_area(type, areas, clazz, &block)
603
+ areas_array_name = "@#{type.to_s + '_' + areas.to_s}"
604
+ areas = instance_variable_get(areas_array_name)
605
+ raise "Undefined instance variable: #{areas_array_name}" unless areas.nil? == false
606
+ raise "Invalid Area class: #{clazz}!" unless [ PostalArea, UsCountryArea, UsStateArea, UsZipArea, WorldArea ].include?(clazz)
607
+ area = clazz.new
608
+ areas << area
609
+
610
+ yield(area) if block_given?
611
+
612
+ return area
613
+ end
614
+
615
+ # Creates a new Area, adds it to the internal list of allowed areas for shipping
616
+ # restrictions. If you passed a block (preferred) then the block is called
617
+ # with the Area as the only parameter.
618
+ #
619
+ # The area to be created depends on the given parameter clazz. It can be one
620
+ # of { PostalArea, UsCountryArea, UsStateArea, UsZipArea, WorldArea }.
621
+ #
622
+ # Raises a RuntimeError if the parameter clazz is invalid.
623
+ #
624
+ # === Example
625
+ #
626
+ # method = FlatRateShipping.new
627
+ # method.create_allowed_area(UsCountryArea) do |area|
628
+ # area.area = UsCountryArea::ALL
629
+ # end
630
+ def create_allowed_area(clazz, &block)
631
+ return create_area(:shipping_restrictions, :allowed_areas, clazz, &block)
632
+ end
633
+
634
+ # Creates a new Area, adds it to the internal list of excluded areas for shipping
635
+ # restrictions. If you passed a block (preferred) then the block is called
636
+ # with the Area as the only parameter. The created area is returned in any case.
637
+ #
638
+ # The area to be created depends on the given parameter clazz. It can be one
639
+ # of { PostalArea, UsCountryArea, UsStateArea, UsZipArea, WorldArea }.
640
+ #
641
+ # Raises a RuntimeError if the parameter clazz is invalid.
642
+ #
643
+ # === Example
644
+ #
645
+ # method = FlatRateShipping.new
646
+ # method.create_excluded_area(UsCountryArea) do |area|
647
+ # area.area = UsCountryArea::ALL
648
+ # end
649
+ def create_excluded_area(clazz, &block)
650
+ return create_area(:shipping_restrictions, :excluded_areas, clazz, &block)
651
+ end
652
+
653
+ alias :create_shipping_restrictions_allowed_area :create_allowed_area
654
+ alias :create_shipping_restrictions_excluded_area :create_excluded_area
655
+ end
656
+
657
+ # A class that represents the "pickup" shipping method.
658
+ class PickupShipping < DeliveryMethod
659
+ def initialize
660
+ end
661
+ end
662
+
663
+ # A class that represents the "flat_rate" shipping method.
664
+ class FlatRateShipping < ShippingMethod
665
+ def initialize
666
+ super
667
+ end
668
+ end
669
+
670
+ # A class that represents the "merchant-calculated" shipping method
671
+ class MerchantCalculatedShipping < ShippingMethod
672
+
673
+ # An Array of allowed areas for address-filters of this shipping instance. Use
674
+ # #create_allowed_area to add to this area but do not change it directly.
675
+ attr_reader :address_filters_allowed_areas
676
+
677
+ # An Array of excluded areas for address-filters of this shipping instance. Use
678
+ # #create_excluded_area to add to this area but do not change it directly.
679
+ attr_reader :address_filters_excluded_areas
680
+
681
+ def initialize
682
+ super
683
+ @address_filters_allowed_areas = Array.new
684
+ @address_filters_excluded_areas = Array.new
685
+ end
686
+
687
+ # Creates a new Area, adds it to the internal list of allowed areas for
688
+ # address filters. If you passed a block (preferred) then the block is
689
+ # called with the Area as the only parameter.
690
+ #
691
+ # The area to be created depends on the given parameter clazz. It can be one
692
+ # of { PostalArea, UsCountryArea, UsStateArea, UsZipArea, WorldArea }.
693
+ #
694
+ # Raises a RuntimeError if the parameter clazz is invalid.
695
+ #
696
+ # === Example
697
+ #
698
+ # method = FlatRateShipping.new
699
+ # method.create_allowed_area(UsCountryArea) do |area|
700
+ # area.area = UsCountryArea::ALL
701
+ # end
702
+ def create_address_filters_allowed_area(clazz, &block)
703
+ return create_area(:address_filters, :allowed_areas, clazz, &block)
704
+ end
705
+
706
+ # Creates a new Area, adds it to the internal list of excluded areas for
707
+ # address filters. If you passed a block (preferred) then the block is
708
+ # called with the Area as the only parameter.
709
+ #
710
+ # The area to be created depends on the given parameter clazz. It can be one
711
+ # of { PostalArea, UsCountryArea, UsStateArea, UsZipArea, WorldArea }.
712
+ #
713
+ # Raises a RuntimeError if the parameter clazz is invalid.
714
+ #
715
+ # === Example
716
+ #
717
+ # method = FlatRateShipping.new
718
+ # method.create_allowed_area(UsCountryArea) do |area|
719
+ # area.area = UsCountryArea::ALL
720
+ # end
721
+ def create_address_filters_allowed_area(clazz, &block)
722
+ return create_area(:address_filters, :allowed_areas, clazz, &block)
723
+ end
724
+ end
725
+
726
+ # A class that represents the "merchant-calculated" shipping method
727
+ class CarrierCalculatedShipping
728
+ # This encapsulates information about all of the shipping methods
729
+ # for which Google Checkout should obtain shipping costs.
730
+ attr_reader :carrier_calculated_shipping_options
731
+
732
+ # This encapsulates information about all of the packages that will be
733
+ # shipped to the buyer. At this time, merchants may only specify
734
+ # one package per order.
735
+ attr_reader :shipping_packages
736
+
737
+ def initialize()
738
+ @carrier_calculated_shipping_options = Array.new
739
+ @shipping_packages = Array.new
740
+ end
741
+
742
+ def create_carrier_calculated_shipping_option(&block)
743
+ option = CarrierCalculatedShippingOption.new(self)
744
+ @carrier_calculated_shipping_options << option
745
+
746
+ # Pass the newly generated rule to the given block to set its attributes.
747
+ yield(option) if block_given?
748
+
749
+ return option
750
+ end
751
+
752
+ def create_shipping_package(&block)
753
+ package = ShippingPackage.new(self)
754
+ @shipping_packages << package
755
+
756
+ # Pass the newly generated rule to the given block to set its attributes.
757
+ yield(package) if block_given?
758
+
759
+ return package
760
+ end
761
+
762
+ # Creates a new CarrierCalculatedShipping from the given
763
+ # REXML::Element instance.
764
+ # For testing only.
765
+ def create_from_element(element)
766
+ result = CarrierCalculatedShipping.new
767
+ element.elements.each('carrier-calculated-shipping-options/carrier-calculated-shipping-option') do |shipping_option_element|
768
+ result.carrier_calculated_shipping_options << CarrierCalculatedShippingOption.create_from_element(self, shipping_option_element)
769
+ end
770
+ element.elements.each('shipping-packages/shipping-package') do |shipping_package_element|
771
+ result.shipping_packages << ShippingPackage.create_from_element(self, shipping_package_element)
772
+ end
773
+ end
774
+
775
+ class CarrierCalculatedShippingOption < DeliveryMethod
776
+ # Constants for shipping company
777
+ FEDEX = 'FedEx'
778
+ UPS = 'UPS'
779
+ USPS = 'USPS'
780
+
781
+ # Constants for carrier pickup
782
+ DROP_OFF = 'DROP_OFF'
783
+ REGULAR_PICKUP = 'REGULAR_PICKUP'
784
+ SPECIAL_PICKUP = 'SPECIAL_PICKUP'
785
+
786
+ # The CarrierCalculatedShipping instance that this option belongs to.
787
+ attr_reader :carrier_calculated_shipping
788
+
789
+ # The name of the company that will ship the order.
790
+ # The only valid values for this tag are FedEx, UPS and USPS.
791
+ # (String, required)
792
+ alias :shipping_company :name
793
+ alias :shipping_company= :name=
794
+
795
+ # The shipping option that is being offered to the buyer
796
+ attr_accessor :shipping_type
797
+
798
+ # This specifies how the package will be transferred from the merchant
799
+ # to the shipper. Valid values for this tag are REGULAR_PICKUP,
800
+ # SPECIAL_PICKUP and DROP_OFF. The default value for this tag is DROP_OFF.
801
+ # (optional)
802
+ attr_accessor :carrier_pickup
803
+
804
+ # The fixed charge that will be added to the total cost of an order
805
+ # if the buyer selects the associated shipping option
806
+ # (Money, optional)
807
+ attr_accessor :additional_fixed_charge
808
+
809
+ # The percentage amount by which a carrier-calculated shipping rate
810
+ # will be adjusted. The tag's value may be positive or negative.
811
+ # (Float, optional)
812
+ attr_accessor :additional_variable_charge_percent
813
+
814
+ def initialize(carrier_calculated_shipping)
815
+ @carrier_calculated_shipping = carrier_calculated_shipping
816
+ #@carrier_pickup = DROP_OFF
817
+ end
818
+
819
+ # Creates a new CarrierCalculatedShippingOption from the given
820
+ # REXML::Element instance.
821
+ # For testing only.
822
+ def self.create_from_element(this_shipping, element)
823
+ result = CarrierCalculatedShippingOption.new(this_shipping)
824
+ result.shipping_company = element.elements['shipping-company'].text
825
+ price = (element.elements['price'].text.to_f * 100).to_i
826
+ price_currency = element.elements['price'].attributes['currency']
827
+ result.price = Money.new(price, price_currency)
828
+ result.shipping_type = element.elements['shipping-type']
829
+ result.carrier_pickup = element.elements['carrier-pickup'] rescue nil
830
+ result.additional_fixed_charge =
831
+ element.elements['additional-fixed-charge'] rescue nil
832
+ result.additional_variable_charge_percent =
833
+ element.elements['additional-variable-charge-percent'] rescue nil
834
+ end
835
+ end
836
+
837
+ class ShippingPackage
838
+ # Constants for delivery address category
839
+ RESIDENTIAL = 'RESIDENTIAL'
840
+ COMMERCIAL = 'COMMERCIAL'
841
+
842
+ # The CarrierCalculatedShipping instance that this package belongs to.
843
+ attr_reader :carrier_calculated_shipping
844
+
845
+ # This contains information about the location from which an order
846
+ # will be shipped. (AnonymousAddress)
847
+ attr_accessor :ship_from
848
+
849
+ # This indicates whether the shipping method should be applied to
850
+ # a residential or a commercial address. Valid values for this tag
851
+ # are RESIDENTIAL and COMMERCIAL. (String, optional)
852
+ attr_accessor :delivery_address_category
853
+
854
+ # This contains information about the height of the package being
855
+ # shipped to the customer. (Google::Checktou::Dimension, optional)
856
+ attr_accessor :height
857
+
858
+ # This contains information about the length of the package being
859
+ # shipped to the customer. (Google::Checktou::Dimension, optional)
860
+ attr_accessor :length
861
+
862
+ # This contains information about the width of the package being
863
+ # shipped to the customer. (Google::Checktou::Dimension, optional)
864
+ attr_accessor :width
865
+
866
+ def initialize(carrier_calculated_shipping)
867
+ @carrier_calculated_shipping = carrier_calculated_shipping
868
+ end
869
+
870
+ # Creates a new ShippingPackage from the given REXML::Element instance.
871
+ # For testing only.
872
+ def self.create_from_element(this_shipping, element)
873
+ result = ShippingPackage.new(this_shipping)
874
+ result.ship_from = ShipFromAddress.create_from_element(element.elements['ship-from'])
875
+ result.delivery_address_category = element.elements['delivery-address-category'].text rescue nil
876
+ result.height = element.elements['height'].text rescue nil
877
+ result.length = element.elements['length'].text rescue nil
878
+ result.width = element.elements['width'].text rescue nil
879
+ return result
880
+ end
881
+ end
882
+ end
883
+
884
+ # This is a base class for defining the unit of weight and dimension
885
+ class Unit
886
+ # This specifies the unit of measurement that corresponds to a shipping
887
+ # package's length, width or height. The only valid value for
888
+ # this attribute is IN.
889
+ attr_accessor :unit
890
+
891
+ # This specifies the numeric value of a unit of measurement
892
+ # corresponding to an item or a shipping package. (float)
893
+ attr_accessor :value
894
+
895
+ def initialize
896
+ raise "Google::Checkout::Unit is an abstract class!"
897
+ end
898
+
899
+ # Creates a new Unit from the given REXML::Element instance.
900
+ def self.create_from_element(element)
901
+ result = self.new(element.attributes['value'].to_f)
902
+ return result
903
+ end
904
+ end
905
+
906
+ # This defines package dimension
907
+ class Dimension < Unit
908
+
909
+ # Constants for unit
910
+ INCH = 'IN'
911
+
912
+ def initialize(value, unit=INCH)
913
+ @unit = unit
914
+ @value = value.to_f
915
+ end
916
+ end
917
+
918
+ # This defines item weight
919
+ class Weight < Unit
920
+ # Constants for unit
921
+ LB = 'LB'
922
+
923
+ def initialize(value, unit=LB)
924
+ @unit = unit
925
+ @value = value.to_f
926
+ end
927
+ end
928
+
929
+ # This address is used in merchant calculation callback
930
+ class AnonymousAddress
931
+
932
+ # The address ID (String)
933
+ attr_accessor :address_id
934
+
935
+ # The buyer's city name (String).
936
+ attr_accessor :city
937
+
938
+ # The buyers postal/zip code (String).
939
+ attr_accessor :postal_code
940
+
941
+ # The buyer's geographical region (String).
942
+ attr_accessor :region
943
+
944
+ # The buyer's country code (String, 2 chars, ISO 3166).
945
+ attr_accessor :country_code
946
+
947
+ # Creates a new AnonymousAddress from the given REXML::Element instance.
948
+ def self.create_from_element(element)
949
+ result = AnonymousAddress.new
950
+
951
+ result.address_id = element.attributes['id']
952
+ result.city = element.elements['city'].text
953
+ result.country_code = element.elements['country-code'].text
954
+ result.postal_code = element.elements['postal-code'].text
955
+ result.region = element.elements['region'].text
956
+ return result
957
+ end
958
+ end
959
+
960
+ # Address instances are used in NewOrderNotification objects for the buyer's billing
961
+ # and buyer's shipping address.
962
+ class Address < AnonymousAddress
963
+ # Contact name (String, optional).
964
+ attr_accessor :contact_name
965
+
966
+ # Second Address line (String).
967
+ attr_accessor :address1
968
+
969
+ # Second Address line (String optional).
970
+ attr_accessor :address2
971
+
972
+ # The buyer's city name (String).
973
+ # attr_accessor :city
974
+ # Now inherit from AnonymousAddress
975
+
976
+ # The buyer's company name (String; optional).
977
+ attr_accessor :company_name
978
+
979
+ # The buyer's country code (String, 2 chars, ISO 3166).
980
+ # attr_accessor :country_code
981
+ # Now inherit from AnonymousAddress
982
+
983
+ # The buyer's email address (String; optional).
984
+ attr_accessor :email
985
+
986
+ # The buyer's phone number (String; optional).
987
+ attr_accessor :fax
988
+
989
+ # The buyer's phone number (String; Optional, can be enforced in CheckoutCommand).)
990
+ attr_accessor :phone
991
+
992
+ # The buyers postal/zip code (String).
993
+ # attr_accessor :postal_code
994
+ # Now inherit from AnonymousAddress
995
+
996
+
997
+ # The buyer's geographical region (String).
998
+ # attr_accessor :region
999
+ # Now inherit from AnonymousAddress
1000
+
1001
+ # Creates a new Address from the given REXML::Element instance.
1002
+ def self.create_from_element(element)
1003
+ result = Address.new
1004
+
1005
+ result.address1 = element.elements['address1'].text
1006
+ result.address2 = element.elements['address2'].text rescue nil
1007
+ result.city = element.elements['city'].text
1008
+ result.company_name = element.elements['company-name'].text rescue nil
1009
+ result.contact_name = element.elements['contact-name'].text rescue nil
1010
+ result.country_code = element.elements['country-code'].text
1011
+ result.email = element.elements['email'].text rescue nil
1012
+ result.fax = element.elements['fax'].text rescue nil
1013
+ result.phone = element.elements['phone'].text rescue nil
1014
+ result.postal_code = element.elements['postal-code'].text
1015
+ result.region = element.elements['region'].text
1016
+
1017
+ return result
1018
+ end
1019
+ end
1020
+
1021
+ # ItemInfo instances are used in Line-item shipping commands
1022
+ class ItemInfo
1023
+ # The merchant item id (String)
1024
+ attr_reader :merchant_item_id
1025
+
1026
+ # An array of tracking data for this item
1027
+ attr_reader :tracking_data_arr
1028
+
1029
+ def initialize(merchant_item_id)
1030
+ @merchant_item_id = merchant_item_id
1031
+ @tracking_data_arr = Array.new
1032
+ end
1033
+
1034
+ def create_tracking_data(carrier, tracking_number)
1035
+ tracking_data = TrackingData.new(carrier, tracking_number)
1036
+ @tracking_data_arr << tracking_data
1037
+ return tracking_data
1038
+ end
1039
+ end
1040
+
1041
+ # TrackingData instances are used in Line-item shipping commands
1042
+ class TrackingData
1043
+ # The name of the company responsible for shipping the item. Valid values
1044
+ # for this tag are DHL, FedEx, UPS, USPS and Other.
1045
+ attr_reader :carrier
1046
+
1047
+ # The shipper's tracking number that is associated with an order
1048
+ attr_reader :tracking_number
1049
+
1050
+ def initialize(carrier, tracking_number)
1051
+ @carrier = carrier.to_s
1052
+ @tracking_number = tracking_number.to_s
1053
+ end
1054
+ end
1055
+
1056
+ # financial_state
1057
+ # REVIEWING - Google Checkout is reviewing the order.
1058
+ # CHARGEABLE - The order is ready to be charged.
1059
+ # CHARGING - The order is being charged; you may not refund or cancel an order until is the charge is completed.
1060
+ # CHARGED - The order has been successfully charged; if the order was only partially charged, the buyer's account page will reflect the partial charge.
1061
+ # PAYMENT_DECLINED - The charge attempt failed.
1062
+ # CANCELLED - The seller canceled the order; an order's financial state cannot be changed after the order is canceled.
1063
+ # CANCELLED_BY_GOOGLE - Google canceled the order. Google may cancel orders due to a failed charge without a replacement credit card being provided within a set period of time or due to a failed risk check. If Google cancels an order, you will be notified of the reason the order was canceled in the <reason> tag of an <order-state-change-notification>.
1064
+ class FinancialState
1065
+ REVIEWING = 'REVIEWING'
1066
+ CHARGEABLE = 'CHARGEABLE'
1067
+ CHARGING = 'CHARGING'
1068
+ CHARGED = 'CHARGED'
1069
+ PAYMENT_DECLINED = 'PAYMENT_DECLINED'
1070
+ CANCELLED = 'CANCELLED'
1071
+ CANCELLED_BY_GOOGLE = 'CANCELLED_BY_GOOGLE'
1072
+ end
1073
+
1074
+ # fulfillment_state
1075
+ # NEW - The order has been received but not prepared for shipping.
1076
+ # PROCESSING - The order is being prepared for shipping.
1077
+ # DELIVERED - The seller has shipped the order.
1078
+ # WILL_NOT_DELIVER - The seller will not ship the order; this status is used for canceled orders.
1079
+ class FulfillmentState
1080
+ NEW = 'NEW'
1081
+ PROCESSING = 'PROCESSING'
1082
+ DELIVERED = 'DELIVERED'
1083
+ WILL_NOT_DELIVER = 'WILL_NOT_DELIVER'
1084
+ end
1085
+ end
1086
+ end