google4r-checkout 1.0.1 → 1.0.2

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