google4r-checkout 1.0.1 → 1.0.2

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  =google4r-checkout Changelog
2
2
 
3
+ == 1.0.2 (2007-08-18)
4
+
5
+ * Moved Address from notifications.rb to shared.rb
6
+ * Added AnonymousAddress class, now Address is extended from it
7
+ * Added merchant calcuations callback and result support
8
+ * Added unit tests for merchant calculation callback and result
9
+ * Added merchant calculations tag support in Shopping Cart XML
10
+ * Updated checkout_command_test.rb and checkout_command_xml_generator_test.rb after for testing merchant calculations tag support
11
+
3
12
  == 1.0.1 (2007-08-16)
4
13
 
5
14
  * Fixed text.to_s.gsub(/[^0-9]/, '') to (...text.to_f*100).to_i so dollar amount is now handled correctly
@@ -31,3 +31,4 @@ require 'google4r/checkout/commands'
31
31
  require 'google4r/checkout/notifications'
32
32
  require 'google4r/checkout/xml_generation'
33
33
  require 'google4r/checkout/frontend'
34
+ require 'google4r/checkout/merchant_calculation'
@@ -208,6 +208,18 @@ module Google4R #:nodoc:
208
208
  # A boolean flag; true iff the customer HAS to provide his phone number (optional).
209
209
  attr_accessor :request_buyer_phone_number
210
210
 
211
+ # The URL of the merchant calculation callback (optional).
212
+ attr_accessor :merchant_calculations_url
213
+
214
+ # A boolean flag to indicate whether merchant coupon is supported or not (optional).
215
+ attr_accessor :accept_merchant_coupons
216
+
217
+ # A boolean flag to indicate whether gift certificate is supported or not (optional).
218
+ attr_accessor :accept_gift_certificates
219
+
220
+ # A Google Checkout merchant ID that identifies the eCommerce provider.
221
+ attr_accessor :platform_id
222
+
211
223
  # Generates the XML for this CheckoutCommand.
212
224
  def to_xml
213
225
  CheckoutCommandXmlGenerator.new(self).generate
@@ -237,7 +249,7 @@ module Google4R #:nodoc:
237
249
  #
238
250
  # Raises a ArgumentError if the parameter clazz is invalid.
239
251
  def create_shipping_method(clazz, &block)
240
- if not [ PickupShipping, FlatRateShipping ].include?(clazz) then
252
+ if not [ PickupShipping, FlatRateShipping, MerchantCalculatedShipping ].include?(clazz) then
241
253
  raise ArgumentError, "Unknown shipping method: #{clazz.inspect}."
242
254
  end
243
255
 
@@ -29,8 +29,8 @@
29
29
 
30
30
  module Google4R #:nodoc:
31
31
  module Checkout #:nodoc:
32
- # The Frontend class is the factory that is to be used to create the Command (and later
33
- # NotificationHandler) objects.
32
+ # The Frontend class is the factory that is to be used to create the Command,
33
+ # NotificationHandler and CallbackHandler objects.
34
34
  #
35
35
  # === Example
36
36
  #
@@ -91,6 +91,18 @@ module Google4R #:nodoc:
91
91
 
92
92
  @configuration = configuration.dup.freeze
93
93
  end
94
+
95
+ # Factory method that creates a new NotificationHandler object. Use this method to
96
+ # create your NotificationHandler instances.
97
+ def create_notification_handler
98
+ return NotificationHandler.new(self)
99
+ end
100
+
101
+ # Factory method that creates a new CallbackHandler object. Use this method to
102
+ # create your CallbackHandler instances.
103
+ def create_callback_handler
104
+ return CallbackHandler.new(self)
105
+ end
94
106
 
95
107
  # Factory method to create a new DeliverOrderCommand object. Use this method to create
96
108
  # your DeliverOrderCommand instances.
@@ -109,12 +121,6 @@ module Google4R #:nodoc:
109
121
  def create_checkout_command
110
122
  return CheckoutCommand.new(self)
111
123
  end
112
-
113
- # Factory method that creates a new NotificationHandler object. Use this method to
114
- # create your NotificationHandler instances.
115
- def create_notification_handler
116
- return NotificationHandler.new(self)
117
- end
118
124
 
119
125
  # Factory method to create a new CancelOrderCommand object. Use this method to create
120
126
  # your CancelOrderCommand instances.
@@ -0,0 +1,317 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/merchant_calculation.rb
4
+ # Author: Tony Chan <api dot htchan at gmail dot com>
5
+ # Copyright: (c) 2007 by Tony Chan
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 callback
28
+ # handling code.
29
+
30
+ require 'rexml/document'
31
+
32
+ module Google4R #:nodoc:
33
+ module Checkout #:nodoc:
34
+ # Thrown by Callback on unimplemented and unknown callbacks from Google.
35
+ class UnknownCallbackType < Exception
36
+ end
37
+
38
+ # This class expects the message sent by Google. It parses the XMl document and returns
39
+ # the appropriate callback. If the callback sent by Google is invalid then a
40
+ # UnknownCallbackType is raised that you should catch and then send a 404 to Google
41
+ # to indicate that the callback handler has not been implemented yet.
42
+ #
43
+ # See http://code.google.com/apis/checkout/developer/index.html#merchant_calculations_api for
44
+ # details.
45
+ #
46
+ # Note that you must protect the HTTPS request to the piece of code using a
47
+ # CallbackHandler by HTTP Auth Basic. If you are using Ruby On Rails then you can
48
+ # use the great "simple_http_auth" plugin you can find here:
49
+ # http://blog.codahale.com/2006/05/11/basic-http-authentication-with-rails-simple_http_auth/
50
+ #
51
+ # === Usage Example
52
+ #
53
+ # When you use a Rails controller to handle the calbacks by Google then your action to handle
54
+ # the callbacks could use a CallbackHandler as follows:
55
+ #
56
+ # def google_checkout_api
57
+ # frontend = Google4R::Checkout::Frontend.new(FRONTEND_CONFIGURATION)
58
+ # frontend.tax_table_factory = TaxTableFactory.new
59
+ # handler = frontend.create_callback_handler
60
+ #
61
+ # begin
62
+ # callback = handler.handle(request.raw_post) # raw_post contains the XML
63
+ # rescue Google4R::Checkout::UnknownCallbackType => e
64
+ # # This can happen if Google adds new commands and Google4R has not been
65
+ # # upgraded yet. It is not fatal.
66
+ # render :text => 'ignoring unknown callback type', :status => 200
67
+ # return
68
+ # end
69
+ #
70
+ # # ...
71
+ # end
72
+ class CallbackHandler
73
+ # The Frontend object that created this CallbackHandler
74
+ attr_accessor :frontend
75
+
76
+ # Create a new CallbackHandler and assign value of the parameter frontend to
77
+ # the frontend attribute.
78
+ def initialize(frontend)
79
+ @frontend = frontend
80
+ end
81
+
82
+ # Parses the given xml_str and returns the appropriate *Callback class. At the
83
+ # moment, only MerchantCalculationCallback objects can be returned.
84
+ def handle(xml_str)
85
+ root = REXML::Document.new(xml_str).root
86
+
87
+ case root.name
88
+ when 'merchant-calculation-callback' then
89
+ MerchantCalculationCallback.create_from_element(root, frontend)
90
+ else
91
+ raise UnknownCallbackType, "Unknown callback type: #{root.name}"
92
+ end
93
+ end
94
+ end
95
+
96
+ # Google Checkout send a <merchant-calculation-callback> message to you when any of the
97
+ # events occur:
98
+ # The customer signs in to Google Checkout.
99
+ # The customer enters a new shipping address on the Place Order page.
100
+ # The customer enters a coupon or gift certificate code.
101
+ #
102
+ # The message will be parsed into a MerchantCalculationCallback instance.
103
+ #
104
+ class MerchantCalculationCallback
105
+ # The frontend this callback belongs to.
106
+ attr_accessor :frontend
107
+
108
+ # The order's shopping cart (ShoppingCart)
109
+ attr_accessor :shopping_cart
110
+
111
+ # The buyer's language
112
+ attr_accessor :buyer_language
113
+
114
+ # An array of AnonymousAddress objects of this callback.
115
+ attr_reader :anonymous_addresses
116
+
117
+ # This indicates whether the merchant needs to calculate taxes for the order.
118
+ attr_accessor :tax
119
+
120
+ # An array of shipping method names
121
+ attr_reader :shipping_methods
122
+
123
+ # An array of merchant codes
124
+ attr_reader :merchant_code_strings
125
+
126
+ # Sets the frontend attribute to the value of the frontend parameter.
127
+ def initialize(frontend)
128
+ @frontend = frontend
129
+ @anonymous_addresses = Array.new
130
+ @shipping_methods = Array.new
131
+ @merchant_code_strings = Array.new
132
+ end
133
+
134
+ # Factory method to create a new MerchantCalculationCallback object from
135
+ # the REXML:Element object
136
+ #
137
+ # Raises NoMethodError and RuntimeError exceptions if the given element misses required
138
+ # elements.
139
+ #
140
+ # You have to pass in the Frontend class this callback belongs to.
141
+ def self.create_from_element(element, frontend)
142
+ result = MerchantCalculationCallback.new(frontend)
143
+
144
+ result.shopping_cart = ShoppingCart.create_from_element(element.elements['shopping-cart'], result)
145
+ result.buyer_language = element.elements['buyer-language'].text
146
+ element.elements.each('calculate/addresses/anonymous-address') do |address_element|
147
+ result.anonymous_addresses << AnonymousAddress.create_from_element(address_element)
148
+ end
149
+ result.tax = element.elements['calculate/tax'].text
150
+ element.elements.each('calculate/shipping/method') do |shipping_method_element|
151
+ result.shipping_methods << shipping_method_element.attributes['name']
152
+ end
153
+ element.elements.each('calculate/merchant-code-strings/merchant-code-string') do |merchant_code_string_element|
154
+ result.merchant_code_strings << merchant_code_string_element.attributes['code']
155
+ end
156
+
157
+ return result
158
+ end
159
+ end
160
+
161
+ # This class represents a merchant-calculation-results XML
162
+ #
163
+ # === Usage Sample
164
+ #
165
+ # results = MerchantCalculationResults.new
166
+ # coupon_result = CouponResult.new(true, 'FirstVisitCoupon', Money.new(500, 'USD'), 'Congratulations! You saved $5.00 on your first visit!')
167
+ # gift_certificate_result = GiftCertificateResult.new(true, 'GiftCert012345', Money.new(1000, 'USD'), 'You used your Gift Certificate!')
168
+ #
169
+ # results.create_merchant_calculation_result do |result|
170
+ # result.shipping_name = 'SuperShip'
171
+ # result.address_id = '739030698069958'
172
+ # result.shipping_rate = Money.new(703, 'USD')
173
+ # result.shippable = true
174
+ # result.total_tax = Money.new(1467, 'USD')
175
+ # result.create_merchant_code_result(@coupon_result)
176
+ # result.create_merchant_code_result(@gift_certificate_result)
177
+ # end
178
+ #
179
+ # results.create_merchant_calculation_result do |result|
180
+ # result.shipping_name = 'UPS Ground'
181
+ # result.address_id = '739030698069958'
182
+ # result.shipping_rate = Money.new(556, 'USD')
183
+ # result.shippable = true
184
+ # result.total_tax = Money.new(1467, 'USD')
185
+ # result.create_merchant_code_result(@coupon_result)
186
+ # result.create_merchant_code_result(@gift_certificate_result)
187
+ # end
188
+ #
189
+ # results.to_xml # To create the XML to return to Google
190
+ #
191
+ class MerchantCalculationResults
192
+ # An array of merchant calcuation results
193
+ attr_reader :merchant_calculation_results
194
+
195
+ def initialize()
196
+ @merchant_calculation_results = Array.new
197
+ end
198
+
199
+ # This method takes a MerchantCalculationResult object and add it to the
200
+ # merchant_calculation_results array. If the object is not provided, it will
201
+ # instantiate one and add it to the array. An optional code block can be
202
+ # supplied to set the attributes of the new MerchantCalculationResult object.
203
+ #
204
+ # Raises RuntimeError exceptions if the given object is not of type
205
+ # MerchantCalculationResult.
206
+ def create_merchant_calculation_result(result=nil, &block)
207
+ if result.nil?
208
+ result = MerchantCalculationResult.new
209
+ # Pass the newly generated item to the given block to set its attributes.
210
+ yield(result) if block_given?
211
+ else
212
+ raise "Not a MerchantCalculationResult!" unless result.kind_of?(MerchantCalculationResult)
213
+ end
214
+ @merchant_calculation_results << result
215
+ end
216
+
217
+ def to_xml()
218
+ return MerchantCalculationResultsXmlGenerator.new(self).generate()
219
+ end
220
+ end
221
+
222
+ # The class represnts a merchant-calculation-result in the merchant-calculation-results XML
223
+ class MerchantCalculationResult
224
+ # The shipping name (string)
225
+ attr_accessor :shipping_name
226
+
227
+ # The address id (string)
228
+ attr_accessor :address_id
229
+
230
+ # The shipping rate (Money)
231
+ attr_accessor :shipping_rate
232
+
233
+ # Is it this applicable to this order (boolean)
234
+ attr_accessor :shippable
235
+
236
+ # The total tax (Money)
237
+ attr_accessor :total_tax
238
+
239
+ # An array of merchant code results
240
+ attr_reader :merchant_code_results
241
+
242
+ def initialize(shipping_name='', address_id='', shipping_rate=nil, shippable=false, total_tax=nil)
243
+ @shipping_name = shipping_name
244
+ @address_id = address_id
245
+ @shipping_rate = shipping_rate
246
+ @shippable = shippable
247
+ @total_tax = total_tax
248
+ @merchant_code_results = Array.new
249
+ end
250
+
251
+ # This method takes either a CouponResult or GiftCertificateResult object and
252
+ # add it to the merchant_code_results array. If the Class object of either
253
+ # the two types is provided, it will create an instance from the Class object.
254
+ # An optional code block can be supplied to set the attributes of the new
255
+ # object.
256
+ #
257
+ # Raises RuntimeError exceptions if there is no argument or a wrong class
258
+ # type is provided.
259
+ def create_merchant_code_result(result=nil, &block)
260
+ if !result.nil?
261
+ if [ CouponResult, GiftCertificateResult ].include?(result) # is a Class object
262
+ result = result.new
263
+ else
264
+ raise "Invalid Merchant Code Result class type: #{result.class}!" unless
265
+ (result.kind_of?(CouponResult) || result.kind_of?(GiftCertificateResult))
266
+ end
267
+ else
268
+ raise "You must either provide a MerchantCodeResult Class type or a CoupleResult or GiftCertificateResult instance."
269
+ end
270
+ @merchant_code_results << result
271
+
272
+ # Pass the newly generated item to the given block to set its attributes.
273
+ yield(result) if block_given?
274
+
275
+ end
276
+ end
277
+
278
+ # Base class for merchant code (coupon and gift certificate)
279
+ class CodeResult
280
+ # Is this valid (boolean)
281
+ attr_accessor :valid
282
+
283
+ # The code (string)
284
+ attr_accessor :code
285
+
286
+ # The calculated amount (Money)
287
+ attr_accessor :calculated_amount
288
+
289
+ # The message (string)
290
+ attr_accessor :message
291
+
292
+ def initialize()
293
+ raise "Do not use the abstract class Google::Checkout::CodeReslt"
294
+ end
295
+ end
296
+
297
+ # This class represents a coupon-result in the merchant-calculation-results XML
298
+ class CouponResult < CodeResult
299
+ def initialize(valid=false, code='', calculated_amount=nil, message='')
300
+ @valid = valid
301
+ @code = code
302
+ @calculated_amount = calculated_amount
303
+ @message = message
304
+ end
305
+ end
306
+
307
+ # This class represents a gift-certificate-result in the merchant-calculation-results XML
308
+ class GiftCertificateResult < CodeResult
309
+ def initialize(valid=false, code='', calculated_amount=nil, message='')
310
+ @valid = valid
311
+ @code = code
312
+ @calculated_amount = calculated_amount
313
+ @message = message
314
+ end
315
+ end
316
+ end
317
+ end
@@ -583,62 +583,6 @@ module Google4R #:nodoc:
583
583
  DELIVERED = "DELIVERED".freeze
584
584
  WILL_NOT_DELIVER = "WILL_NOT_DELIVER".freeze
585
585
  end
586
-
587
- # Address instances are used in NewOrderNotification objects for the buyer's billing
588
- # and buyer's shipping address.
589
- class Address
590
- # Contact name (String, optional).
591
- attr_accessor :contact_name
592
-
593
- # Second Address line (String).
594
- attr_accessor :address1
595
-
596
- # Second Address line (String optional).
597
- attr_accessor :address2
598
-
599
- # The buyer's city name (String).
600
- attr_accessor :city
601
-
602
- # The buyer's company name (String; optional).
603
- attr_accessor :company_name
604
-
605
- # The buyer's country code (String, 2 chars, ISO 3166).
606
- attr_accessor :country_code
607
-
608
- # The buyer's email address (String; optional).
609
- attr_accessor :email
610
-
611
- # The buyer's phone number (String; optional).
612
- attr_accessor :fax
613
-
614
- # The buyer's phone number (String; Optional, can be enforced in CheckoutCommand).)
615
- attr_accessor :phone
616
-
617
- # The buyers postal/zip code (String).
618
- attr_accessor :postal_code
619
-
620
- # The buyer's geographical region (String).
621
- attr_accessor :region
622
-
623
- # Creates a new Address from the given REXML::Element instance.
624
- def self.create_from_element(element)
625
- result = Address.new
626
-
627
- result.address1 = element.elements['address1'].text
628
- result.address2 = element.elements['address2'].text rescue nil
629
- result.city = element.elements['city'].text
630
- result.company_name = element.elements['company-name'].text rescue nil
631
- result.contact_name = element.elements['contact-name'].text rescue nil
632
- result.country_code = element.elements['country-code'].text
633
- result.email = element.elements['email'].text rescue nil
634
- result.fax = element.elements['fax'].text rescue nil
635
- result.phone = element.elements['phone'].text rescue nil
636
- result.postal_code = element.elements['postal-code'].text
637
- result.region = element.elements['region'].text
638
-
639
- return result
640
- end
641
- end
642
586
 
643
587
  # The marketing preferences of a customer.
644
588
  class MarketingPreferences