vizjerai-google-checkout 0.0.5

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.
Files changed (67) hide show
  1. data/History.txt +14 -0
  2. data/MIT-LICENSE.txt +23 -0
  3. data/README.txt +143 -0
  4. data/Rakefile +34 -0
  5. data/VERSION.yml +4 -0
  6. data/examples/google_notifications_controller.rb +159 -0
  7. data/lib/duck_punches/hpricot.rb +24 -0
  8. data/lib/google-checkout/cart.rb +304 -0
  9. data/lib/google-checkout/command.rb +255 -0
  10. data/lib/google-checkout/geography/area.rb +11 -0
  11. data/lib/google-checkout/geography/postal.rb +26 -0
  12. data/lib/google-checkout/geography/us_country.rb +24 -0
  13. data/lib/google-checkout/geography/us_state.rb +22 -0
  14. data/lib/google-checkout/geography/us_zip.rb +22 -0
  15. data/lib/google-checkout/geography/world.rb +12 -0
  16. data/lib/google-checkout/geography.rb +7 -0
  17. data/lib/google-checkout/merchant_calculation.rb +30 -0
  18. data/lib/google-checkout/notification.rb +352 -0
  19. data/lib/google-checkout/shipping/filters.rb +32 -0
  20. data/lib/google-checkout/shipping/flat_rate.rb +26 -0
  21. data/lib/google-checkout/shipping/merchant_calculated.rb +29 -0
  22. data/lib/google-checkout/shipping/method.rb +11 -0
  23. data/lib/google-checkout/shipping/pickup.rb +22 -0
  24. data/lib/google-checkout/shipping/restrictions.rb +32 -0
  25. data/lib/google-checkout/shipping.rb +8 -0
  26. data/lib/google-checkout.rb +65 -0
  27. data/spec/fixtures/google/checkout-shopping-cart.xml +22 -0
  28. data/spec/fixtures/google/commands/add-merchant-order-number.xml +5 -0
  29. data/spec/fixtures/google/commands/add-tracking-data.xml +8 -0
  30. data/spec/fixtures/google/commands/archive-order.xml +3 -0
  31. data/spec/fixtures/google/commands/authorize-order.xml +2 -0
  32. data/spec/fixtures/google/commands/cancel-order.xml +5 -0
  33. data/spec/fixtures/google/commands/charge-order.xml +4 -0
  34. data/spec/fixtures/google/commands/deliver-order.xml +9 -0
  35. data/spec/fixtures/google/commands/process-order.xml +2 -0
  36. data/spec/fixtures/google/commands/refund-order.xml +6 -0
  37. data/spec/fixtures/google/commands/send-buyer-message.xml +7 -0
  38. data/spec/fixtures/google/commands/unarchive-order.xml +2 -0
  39. data/spec/fixtures/google/merchant_calculations/shipping.xml +40 -0
  40. data/spec/fixtures/google/notifications/authorization-amount-notification.xml +10 -0
  41. data/spec/fixtures/google/notifications/charge-amount-notification.xml +8 -0
  42. data/spec/fixtures/google/notifications/chargeback-amount-notification.xml +8 -0
  43. data/spec/fixtures/google/notifications/new-order-notification.xml +94 -0
  44. data/spec/fixtures/google/notifications/order-state-change-notification.xml +11 -0
  45. data/spec/fixtures/google/notifications/refund-amount-notification.xml +8 -0
  46. data/spec/fixtures/google/notifications/risk-information-notification.xml +23 -0
  47. data/spec/fixtures/google/responses/checkout-redirect.xml +5 -0
  48. data/spec/fixtures/google/responses/error-charged.xml +5 -0
  49. data/spec/fixtures/google/responses/error.xml +5 -0
  50. data/spec/fixtures/google/responses/request-received.xml +3 -0
  51. data/spec/google-checkout/cart_spec.rb +110 -0
  52. data/spec/google-checkout/command_spec.rb +216 -0
  53. data/spec/google-checkout/geography/postal_spec.rb +26 -0
  54. data/spec/google-checkout/geography/us_country_spec.rb +26 -0
  55. data/spec/google-checkout/geography/us_state_spec.rb +11 -0
  56. data/spec/google-checkout/geography/us_zip_spec.rb +11 -0
  57. data/spec/google-checkout/geography/world_spec.rb +12 -0
  58. data/spec/google-checkout/merchant_calculation_spec.rb +17 -0
  59. data/spec/google-checkout/notification_spec.rb +253 -0
  60. data/spec/google-checkout/response_spec.rb +49 -0
  61. data/spec/google-checkout/shipping/flat_rate_spec.rb +46 -0
  62. data/spec/google-checkout/shipping/merchant_calculated_spec.rb +70 -0
  63. data/spec/google-checkout/shipping/pickup_spec.rb +22 -0
  64. data/spec/google-checkout_spec.rb +15 -0
  65. data/spec/spec_helper.rb +47 -0
  66. data/support/cacert.pem +7815 -0
  67. metadata +145 -0
@@ -0,0 +1,304 @@
1
+
2
+ module GoogleCheckout
3
+
4
+ # These are the only sizes allowed by Google. These shouldn't be needed
5
+ # by most people; just specify the :size and :buy_or_checkout options to
6
+ # Cart#checkout_button and the sizes are filled in automatically.
7
+ ButtonSizes = {
8
+ :checkout => {
9
+ :small => { :w => 160, :h => 43 },
10
+ :medium => { :w => 168, :h => 44 },
11
+ :large => { :w => 180, :h => 46 },
12
+ },
13
+
14
+ :buy_now => {
15
+ :small => { :w => 117, :h => 48 },
16
+ :medium => { :w => 121, :h => 44 },
17
+ :large => { :w => 121, :h => 44 },
18
+ },
19
+ }
20
+
21
+ ##
22
+ # This class represents a cart for Google Checkout. After initializing it
23
+ # with a +merchant_id+ and +merchant_key+, you add items via add_item,
24
+ # and can then get xml via to_xml, or html code for a form that
25
+ # provides a checkout button via checkout_button.
26
+ #
27
+ # Example:
28
+ #
29
+ # item = {
30
+ # :name => 'A Quarter',
31
+ # :description => 'One shiny quarter.',
32
+ # :price => 0.25
33
+ # }
34
+ # @cart = GoogleCheckout::Cart.new(merchant_id, merchant_key, item)
35
+ # @cart.add_item(:name => "Pancakes",
36
+ # :description => "Flapjacks by mail."
37
+ # :price => 0.50,
38
+ # :quantity => 10,
39
+ # "merchant-item-id" => '2938292839')
40
+ #
41
+ # Then in your view:
42
+ #
43
+ # Checkout here! <%= @cart.checkout_button %>
44
+ #
45
+ # This object is also useful for getting back a url to the image for a Google
46
+ # Checkout button. You can use this image in forms that submit back to your own
47
+ # server for further processing via Google Checkout's level 2 XML API.
48
+
49
+ class Cart < Command
50
+
51
+ include GoogleCheckout
52
+
53
+ SANDBOX_CHECKOUT_URL = "https://sandbox.google.com/checkout/cws/v2/Merchant/%s/checkout"
54
+ PRODUCTION_CHECKOUT_URL = "https://checkout.google.com/cws/v2/Merchant/%s/checkout"
55
+
56
+ ##
57
+ # You can provide extra data that will be sent to Google and returned with
58
+ # the NewOrderNotification.
59
+ #
60
+ # This should be a Hash and will be turned into XML with proper escapes.
61
+ #
62
+ # Beware using symbols as values. They may be set as sub-keys instead of values,
63
+ # so use a String or other datatype.
64
+
65
+ attr_accessor :merchant_private_data
66
+
67
+ attr_accessor :edit_cart_url
68
+ attr_accessor :continue_shopping_url
69
+
70
+ attr_accessor :merchant_calculations_url
71
+
72
+ attr_accessor :shipping_methods
73
+
74
+ # The default options for drawing in the button that are filled in when
75
+ # checkout_button or button_url is called.
76
+ DefaultButtonOpts = {
77
+ :size => :medium,
78
+ :style => 'white',
79
+ :variant => 'text',
80
+ :loc => 'en_US',
81
+ :buy_or_checkout => nil,
82
+ }
83
+
84
+ # You need to supply, as strings, the +merchant_id+ and +merchant_key+
85
+ # used to identify your store to Google. You may optionally supply one
86
+ # or more items to put inside the cart.
87
+ def initialize(merchant_id, merchant_key, *items)
88
+ super(merchant_id, merchant_key)
89
+ @contents = []
90
+ @merchant_private_data = {}
91
+ @shipping_methods = []
92
+ items.each { |i| add_item i }
93
+ end
94
+
95
+ def empty?
96
+ @contents.empty?
97
+ end
98
+
99
+ # Number of items in the cart.
100
+ def size
101
+ @contents.size
102
+ end
103
+
104
+ def submit_domain
105
+ (GoogleCheckout.production? ? 'checkout' : 'sandbox') + ".google.com"
106
+ end
107
+
108
+ ##
109
+ # The Google Checkout form submission url.
110
+
111
+ def submit_url
112
+ GoogleCheckout.sandbox? ? (SANDBOX_CHECKOUT_URL % @merchant_id) : (PRODUCTION_CHECKOUT_URL % @merchant_id)
113
+ end
114
+
115
+ # This method puts items in the cart.
116
+ # +item+ may be a hash, or have a method named +to_google_product+ that
117
+ # returns a hash with the required values.
118
+ # * name
119
+ # * description (a brief description as it will appear on the bill)
120
+ # * price
121
+ # You may fill in some optional values as well:
122
+ # * quantity (defaults to 1)
123
+ # * currency (defaults to 'USD')
124
+ def add_item(item)
125
+ @xml = nil
126
+ if item.respond_to? :to_google_product
127
+ item = item.to_google_product
128
+ end
129
+
130
+ # We need to check that the necessary keys are in the hash,
131
+ # Otherwise the error will happen in the middle of to_xml,
132
+ # and the bug will be harder to track.
133
+ missing_keys = [ :name, :description, :price ].select { |key|
134
+ !item.include? key
135
+ }
136
+
137
+ unless missing_keys.empty?
138
+ raise ArgumentError,
139
+ "Required keys missing: #{missing_keys.inspect}"
140
+ end
141
+
142
+ @contents << { :quantity => 1, :currency => 'USD' }.merge(item)
143
+ item
144
+ end
145
+
146
+ # This is the important method; it generatest the XML call.
147
+ # It's fairly lengthy, but trivial. It follows the docs at
148
+ # http://code.google.com/apis/checkout/developer/index.html#checkout_api
149
+ #
150
+ # It returns the raw XML string, not encoded.
151
+ def to_xml
152
+ raise RuntimeError, "Empty cart" if self.empty?
153
+
154
+ xml = Builder::XmlMarkup.new
155
+ xml.instruct!
156
+ @xml = xml.tag!('checkout-shopping-cart', :xmlns => "http://checkout.google.com/schema/2") {
157
+ xml.tag!("shopping-cart") {
158
+ xml.items {
159
+ @contents.each { |item|
160
+ xml.item {
161
+ if item.key?(:item_id)
162
+ xml.tag!('merchant-item-id', item[:item_id])
163
+ end
164
+ xml.tag!('item-name') {
165
+ xml.text! item[:name].to_s
166
+ }
167
+ xml.tag!('item-description') {
168
+ xml.text! item[:description].to_s
169
+ }
170
+ xml.tag!('unit-price', :currency => (item[:currency] || 'USD')) {
171
+ xml.text! item[:price].to_s
172
+ }
173
+ xml.quantity {
174
+ xml.text! item[:quantity].to_s
175
+ }
176
+ xml.tag!('merchant-private-item-data') {
177
+ xml << item[:merchant_private_item_data]
178
+ } if item.key?(:merchant_private_item_data)
179
+ }
180
+ }
181
+ }
182
+ unless @merchant_private_data.empty?
183
+ xml.tag!("merchant-private-data") {
184
+ @merchant_private_data.each do |key, value|
185
+ xml.tag!(key, value)
186
+ end
187
+ }
188
+ end
189
+ }
190
+ xml.tag!('checkout-flow-support') {
191
+ xml.tag!('merchant-checkout-flow-support') {
192
+ xml.tag!('edit-cart-url', @edit_cart_url) if @edit_cart_url
193
+ xml.tag!('continue-shopping-url', @continue_shopping_url) if @continue_shopping_url
194
+ xml.tag!("request-buyer-phone-number", false)
195
+ xml.tag!('merchant-calculations') {
196
+ xml.tag!('merchant-calculations-url') {
197
+ xml.text! @merchant_calculations_url
198
+ }
199
+ } if @merchant_calculations_url
200
+
201
+ # TODO tax-tables
202
+ xml.tag!("tax-tables") {
203
+ xml.tag!("default-tax-table") {
204
+ xml.tag!("tax-rules") {
205
+ xml.tag!("default-tax-rule") {
206
+ xml.tag!("shipping-taxed", false)
207
+ xml.tag!("rate", "0.00")
208
+ xml.tag!("tax-area") {
209
+ xml.tag!("world-area")
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ xml.tag!('shipping-methods') {
217
+ @shipping_methods.each do |shipping_method|
218
+ xml << shipping_method.to_xml
219
+ end
220
+ }
221
+ }
222
+ }
223
+ }
224
+ @xml.dup
225
+ end
226
+
227
+ # Returns the signature for the cart XML.
228
+ def signature
229
+ @xml or to_xml
230
+ HMAC::SHA1.digest(@merchant_key, @xml)
231
+ end
232
+
233
+ # Returns HTML for a checkout form for buying all the items in the
234
+ # cart.
235
+ def checkout_button(button_opts = {})
236
+ @xml or to_xml
237
+ burl = button_url(button_opts)
238
+ html = Builder::XmlMarkup.new(:indent => 2)
239
+ html.form({
240
+ :action => submit_url,
241
+ :style => 'border: 0;',
242
+ :id => 'BB_BuyButtonForm',
243
+ :method => 'post',
244
+ :name => 'BB_BuyButtonForm'
245
+ }) do
246
+ html.input({
247
+ :name => 'cart',
248
+ :type => 'hidden',
249
+ :value => Base64.encode64(@xml).gsub("\n", '')
250
+ })
251
+ html.input({
252
+ :name => 'signature',
253
+ :type => 'hidden',
254
+ :value => Base64.encode64(signature).gsub("\n", '')
255
+ })
256
+ html.input({
257
+ :alt => 'Google Checkout',
258
+ :style => "width: auto;",
259
+ :src => button_url(button_opts),
260
+ :type => 'image'
261
+ })
262
+ end
263
+ end
264
+
265
+ # Given a set of options for the button, button_url returns the URL
266
+ # for the button image.
267
+ # The options are the same as those specified on
268
+ # http://checkout.google.com/seller/checkout_buttons.html , with a
269
+ # couple of extra options for convenience. Rather than specifying the
270
+ # width and height manually, you may specify :size to be one of :small,
271
+ # :medium, or :large, and that you may set :buy_or_checkout to :buy_now
272
+ # or :checkout to get a 'Buy Now' button versus a 'Checkout' button. If
273
+ # you don't specify :buy_or_checkout, the Cart will try to guess based
274
+ # on if the cart has more than one item in it. Whatever you don't pass
275
+ # will be filled in with the defaults from DefaultButtonOpts.
276
+ #
277
+ # http://checkout.google.com/buttons/checkout.gif
278
+ # http://sandbox.google.com/checkout/buttons/checkout.gif
279
+
280
+ def button_url(opts = {})
281
+ opts = DefaultButtonOpts.merge opts
282
+ opts[:buy_or_checkout] ||= @contents.size > 1 ? :checkout : :buy_now
283
+ opts.merge! ButtonSizes[opts[:buy_or_checkout]][opts[:size]]
284
+ bname = opts[:buy_or_checkout] == :buy_now ? 'buy.gif' : 'checkout.gif'
285
+ opts.delete :size
286
+ opts.delete :buy_or_checkout
287
+ opts[:merchant_id] = @merchant_id
288
+
289
+ path = opts.map { |k,v| "#{k}=#{v}" }.join('&')
290
+
291
+ # HACK Sandbox graphics are in the checkout subdirectory
292
+ subdir = ""
293
+ if GoogleCheckout.sandbox? && bname == "checkout.gif"
294
+ subdir = "checkout/"
295
+ end
296
+
297
+ protocol = opts[:https] ? 'https' : 'http'
298
+
299
+ # TODO Use /checkout/buttons/checkout.gif if in sandbox.
300
+ "#{protocol}://#{submit_domain}/#{ subdir }buttons/#{bname}?#{path}"
301
+ end
302
+ end
303
+
304
+ end
@@ -0,0 +1,255 @@
1
+
2
+ # TODO
3
+ #
4
+ # * Use standard ssl certs
5
+
6
+ module GoogleCheckout
7
+
8
+ ##
9
+ # Abstract class for commands.
10
+ #
11
+ # https://sandbox.google.com/checkout/cws/v2/Merchant/1234567890/request
12
+ # https://checkout.google.com/cws/v2/Merchant/1234567890/request
13
+
14
+ class Command
15
+
16
+ attr_accessor :merchant_id, :merchant_key, :currency
17
+
18
+ SANDBOX_REQUEST_URL = "https://sandbox.google.com/checkout/cws/v2/Merchant/%s/request"
19
+ PRODUCTION_REQUEST_URL = "https://checkout.google.com/cws/v2/Merchant/%s/request"
20
+
21
+ def initialize(merchant_id, merchant_key)
22
+ @merchant_id = merchant_id
23
+ @merchant_key = merchant_key
24
+
25
+ @currency = "USD"
26
+ end
27
+
28
+ ##
29
+ # Returns the appropriate sandbox or production url for posting API requests.
30
+
31
+ def url
32
+ GoogleCheckout.sandbox? ? (SANDBOX_REQUEST_URL % @merchant_id) : (PRODUCTION_REQUEST_URL % @merchant_id)
33
+ end
34
+
35
+ ##
36
+ # Sends the Command's XML to GoogleCheckout via HTTPS with Basic Auth.
37
+ #
38
+ # Returns a GoogleCheckout::RequestReceived or a GoogleCheckout::Error object.
39
+
40
+ def post
41
+ # Create HTTP(S) POST command and set up Basic Authentication.
42
+ uri = URI.parse(url)
43
+
44
+ request = Net::HTTP::Post.new(uri.path)
45
+ request.basic_auth(@merchant_id, @merchant_key)
46
+
47
+ # Set up the HTTP connection object and the SSL layer.
48
+ https = Net::HTTP.new(uri.host, uri.port)
49
+ https.use_ssl = true
50
+ https.cert_store = self.class.x509_store
51
+ https.verify_mode = OpenSSL::SSL::VERIFY_PEER
52
+ https.verify_depth = 5
53
+
54
+ # Send the request to Google.
55
+ response = https.request(request, self.to_xml)
56
+
57
+ # NOTE Because Notification.parse() is used, the content of objects
58
+ # will be correctly parsed no matter what the HTTP response code
59
+ # is from the server.
60
+ case response
61
+ when Net::HTTPSuccess, Net::HTTPClientError
62
+ notification = Notification.parse(response.body)
63
+ if notification.error? && !notification.message.eql?('You cannot charge an order that is already completely charged')
64
+ raise APIError, "#{notification.message} [in #{GoogleCheckout.production? ? 'production' : 'sandbox' }]"
65
+ end
66
+ return notification
67
+ when Net::HTTPRedirection, Net::HTTPServerError, Net::HTTPInformation
68
+ raise "Unexpected response code (#{response.class}): #{response.code} - #{response.message}"
69
+ else
70
+ raise "Unknown response code: #{response.code} - #{response.message}"
71
+ end
72
+ end
73
+
74
+ ##
75
+ # Class method to return the OpenSSL::X509::Store instance for the
76
+ # CA certificates.
77
+ #
78
+ # NOTE May not be thread-safe.
79
+
80
+ def self.x509_store
81
+ return @@x509_store if defined?(@@x509_store)
82
+
83
+ cacert_path = File.expand_path(File.dirname(__FILE__) + '/../../support/cacert.pem')
84
+
85
+ @@x509_store = OpenSSL::X509::Store.new
86
+ @@x509_store.add_file(cacert_path)
87
+
88
+ return @@x509_store
89
+ end
90
+
91
+ end
92
+
93
+ ##
94
+ # Abstract class for all commands associated with an existing order.
95
+
96
+ class OrderCommand < Command
97
+
98
+ attr_accessor :google_order_number, :amount
99
+
100
+ ##
101
+ # Make a new object. Last argument is the Google's order number as received
102
+ # in the NewOrderNotification.
103
+
104
+ def initialize(merchant_id, merchant_key, google_order_number)
105
+ # TODO raise "Not an order number!" unless google_order_number.is_a? String
106
+ super(merchant_id, merchant_key)
107
+ @google_order_number = google_order_number
108
+ @amount = 0.00
109
+ end
110
+
111
+ end
112
+
113
+ ##
114
+ # Create a new ChargeOrder object, set the +amount+, then
115
+ # +post+ it.
116
+
117
+ class ChargeOrder < OrderCommand
118
+
119
+ def to_xml
120
+ raise "Charge amount must be greater than 0!" unless @amount.to_f > 0.0
121
+
122
+ xml = Builder::XmlMarkup.new
123
+ xml.instruct!
124
+ @xml = xml.tag!('charge-order', {
125
+ :xmlns => "http://checkout.google.com/schema/2",
126
+ "google-order-number" => @google_order_number
127
+ }) do
128
+ xml.tag!("amount", @amount, {:currency => @currency})
129
+ end
130
+ @xml
131
+ end
132
+
133
+ end
134
+
135
+ ##
136
+ # Tells Google that the order has shipped.
137
+
138
+ class DeliverOrder < OrderCommand
139
+
140
+ def to_xml
141
+
142
+ xml = Builder::XmlMarkup.new
143
+ xml.instruct!
144
+ @xml = xml.tag!('deliver-order', {
145
+ :xmlns => "http://checkout.google.com/schema/2",
146
+ "google-order-number" => @google_order_number
147
+ }) do
148
+ xml.tag!("send-email", false)
149
+ end
150
+ @xml
151
+ end
152
+
153
+ end
154
+
155
+ ##
156
+ # Tells Google tracking data for the order.
157
+
158
+ class AddTrackingData < OrderCommand
159
+
160
+ def initialize(merchant_id, merchant_key, google_order_number, carrier, tracking_number)
161
+ raise "Unknown carrier. Valid values are DHL, FedEx, UPS, USPS, and Other." unless ["DHL","FedEx","UPS","USPS","Other"].include? carrier
162
+
163
+ @carrier = carrier
164
+ @tracking_number = tracking_number
165
+ super(merchant_id, merchant_key, google_order_number)
166
+ end
167
+
168
+ def to_xml
169
+ xml = Builder::XmlMarkup.new
170
+ xml.instruct!
171
+ @xml = xml.tag!('add-tracking-data', {
172
+ :xmlns => "http://checkout.google.com/schema/2",
173
+ "google-order-number" => @google_order_number
174
+ }) do
175
+ xml.tag!('tracking-data') do
176
+ xml.tag!('carrier', @carrier)
177
+ xml.tag!('tracking-number', @tracking_number)
178
+ end
179
+ end
180
+ @xml
181
+ end
182
+
183
+ end
184
+
185
+ ##
186
+ # Tells Google to refund the order.
187
+
188
+ class RefundOrder < OrderCommand
189
+
190
+ attr_accessor :reason, :comment
191
+
192
+ def initialize(merchant_id, merchant_key, google_order_number)
193
+ super(merchant_id, merchant_key, google_order_number)
194
+ @reason = ''
195
+ @comment = ''
196
+ end
197
+
198
+ def to_xml
199
+ raise "Refund amount must be greater than 0!" unless @amount.to_f > 0.0
200
+ raise "Reason must be longer than 0 characters!" unless @reason.length > 0
201
+ raise "Reason cannot be greater than 140 characters!" if @reason.length > 140
202
+ raise "Comment cannot be greater than 140 characters!" if @comment.length > 140
203
+
204
+ xml = Builder::XmlMarkup.new
205
+ xml.instruct!
206
+ @xml = xml.tag!('refund-order', {
207
+ :xmlns => "http://checkout.google.com/schema/2",
208
+ "google-order-number" => @google_order_number
209
+ }) do
210
+ xml.tag!("amount", @amount, {:currency => @currency})
211
+ xml.tag!("reason", @reason)
212
+ xml.tag!("comment", @comment) unless @comment.blank?
213
+ end
214
+ @xml
215
+ end
216
+
217
+ end
218
+
219
+ ##
220
+ # Send a message to the buyer associated with an order.
221
+ #
222
+ # Google will actually send the message to their email address.
223
+
224
+ class SendBuyerMessage < OrderCommand
225
+
226
+ ##
227
+ # Make a new message to send.
228
+ #
229
+ # The last argument is the actual message.
230
+ #
231
+ # Call +post+ on the resulting object to submit it to Google for sending.
232
+
233
+ def initialize(merchant_id, merchant_key, google_order_number, message)
234
+ # TODO Raise meaninful error if message is longer than 255 characters
235
+ raise "Google won't send anything longer than 255 characters! Sorry!" if message.length > 255
236
+ @message = message
237
+ super(merchant_id, merchant_key, google_order_number)
238
+ end
239
+
240
+ def to_xml # :nodoc:
241
+ xml = Builder::XmlMarkup.new
242
+ xml.instruct!
243
+ @xml = xml.tag!('send-buyer-message', {
244
+ :xmlns => "http://checkout.google.com/schema/2",
245
+ "google-order-number" => @google_order_number
246
+ }) do
247
+ xml.tag!("message", @message)
248
+ xml.tag!("send-email", true)
249
+ end
250
+ @xml
251
+ end
252
+
253
+ end
254
+
255
+ end
@@ -0,0 +1,11 @@
1
+ module GoogleCheckout
2
+ module Geography
3
+ class Area
4
+
5
+ def to_xml
6
+ raise NotImplementedError
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module GoogleCheckout
2
+ module Geography
3
+ class Postal < Area
4
+
5
+ attr_accessor :country, :postal_pattern
6
+
7
+ def initialize(country, postal_pattern=nil)
8
+ @country = country
9
+ @postal_pattern = postal_pattern
10
+ end
11
+
12
+ def to_xml
13
+ xml = Builder::XmlMarkup.new
14
+ xml.tag!('postal-area') do
15
+ xml.tag!('country-code') do
16
+ xml.text! @country
17
+ end
18
+ xml.tag!('postal-code-pattern') do
19
+ xml.text! @postal_pattern
20
+ end if @postal_pattern
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module GoogleCheckout
2
+ module Geography
3
+ class UsCountry < Area
4
+
5
+ VALID_REGIONS = [:continental_48, :full_50_states, :all]
6
+
7
+ attr_accessor :region
8
+
9
+ def initialize(region)
10
+ self.region = region
11
+ end
12
+
13
+ def to_xml
14
+ unless VALID_REGIONS.include?(@region.to_sym)
15
+ raise ArgumentError,
16
+ ":#{@region.to_s} is not a valid region. You may use :continental_48, :full_50_states or :all"
17
+ end
18
+ xml = Builder::XmlMarkup.new
19
+ xml.tag!('us-country-area', 'country-area' => @region.to_s.upcase)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ module GoogleCheckout
2
+ module Geography
3
+ class UsState < Area
4
+
5
+ attr_accessor :state
6
+
7
+ def initialize(state)
8
+ @state = state
9
+ end
10
+
11
+ def to_xml
12
+ xml = Builder::XmlMarkup.new
13
+ xml.tag!('us-state-area') do
14
+ xml.tag!('state') do
15
+ xml.text! @state
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module GoogleCheckout
2
+ module Geography
3
+ class UsZip < Area
4
+
5
+ attr_accessor :zip_pattern
6
+
7
+ def initialize(zip_pattern)
8
+ @zip_pattern = zip_pattern
9
+ end
10
+
11
+ def to_xml
12
+ xml = Builder::XmlMarkup.new
13
+ xml.tag!('us-zip-area') do
14
+ xml.tag!('zip-pattern') do
15
+ xml.text! @zip_pattern
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,12 @@
1
+ module GoogleCheckout
2
+ module Geography
3
+ class World < Area
4
+
5
+ def to_xml
6
+ xml = Builder::XmlMarkup.new
7
+ xml.tag!('world-area')
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ require 'google-checkout/geography/area'
2
+
3
+ require 'google-checkout/geography/world'
4
+ require 'google-checkout/geography/postal'
5
+ require 'google-checkout/geography/us_country'
6
+ require 'google-checkout/geography/us_state'
7
+ require 'google-checkout/geography/us_zip'