google4r-checkout-jn 1.1.jniziol

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 (79) hide show
  1. data/CHANGES +136 -0
  2. data/LICENSE +22 -0
  3. data/README.md +72 -0
  4. data/lib/google4r/checkout.rb +34 -0
  5. data/lib/google4r/checkout/commands.rb +665 -0
  6. data/lib/google4r/checkout/frontend.rb +222 -0
  7. data/lib/google4r/checkout/merchant_calculation.rb +323 -0
  8. data/lib/google4r/checkout/notifications.rb +774 -0
  9. data/lib/google4r/checkout/shared.rb +1386 -0
  10. data/lib/google4r/checkout/utils.rb +94 -0
  11. data/lib/google4r/checkout/xml_generation.rb +1023 -0
  12. data/test/frontend_configuration_example.rb +13 -0
  13. data/test/integration/checkout_command_test.rb +261 -0
  14. data/test/test_helper.rb +105 -0
  15. data/test/unit/add_merchant_order_number_command_test.rb +65 -0
  16. data/test/unit/add_tracking_data_command_test.rb +68 -0
  17. data/test/unit/address_test.rb +131 -0
  18. data/test/unit/anonymous_address_test.rb +75 -0
  19. data/test/unit/archive_order_command_test.rb +63 -0
  20. data/test/unit/area_test.rb +44 -0
  21. data/test/unit/authorization_amount_notification_test.rb +69 -0
  22. data/test/unit/authorize_order_command_test.rb +63 -0
  23. data/test/unit/backorder_items_command_test.rb +69 -0
  24. data/test/unit/callback_handler_test.rb +83 -0
  25. data/test/unit/cancel_items_command_test.rb +76 -0
  26. data/test/unit/cancel_order_command_test.rb +74 -0
  27. data/test/unit/carrier_calculated_shipping_test.rb +57 -0
  28. data/test/unit/charge_amount_notification_test.rb +72 -0
  29. data/test/unit/charge_and_ship_order_command_test.rb +69 -0
  30. data/test/unit/charge_fee_test.rb +53 -0
  31. data/test/unit/charge_order_command_test.rb +69 -0
  32. data/test/unit/chargeback_amount_notification_test.rb +69 -0
  33. data/test/unit/checkout_command_test.rb +149 -0
  34. data/test/unit/checkout_command_xml_generator_test.rb +216 -0
  35. data/test/unit/command_test.rb +116 -0
  36. data/test/unit/deliver_order_command_test.rb +65 -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 +133 -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 +173 -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 +203 -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 +109 -0
  55. data/test/unit/order_state_change_notification_test.rb +158 -0
  56. data/test/unit/parameterized_url_test.rb +57 -0
  57. data/test/unit/pickup_shipping_test.rb +70 -0
  58. data/test/unit/postal_area_test.rb +71 -0
  59. data/test/unit/private_data_parser_test.rb +68 -0
  60. data/test/unit/refund_amount_notification_test.rb +67 -0
  61. data/test/unit/refund_order_command_test.rb +79 -0
  62. data/test/unit/reset_items_shipping_information_command_test.rb +69 -0
  63. data/test/unit/return_items_command_test.rb +69 -0
  64. data/test/unit/risk_information_notification_test.rb +98 -0
  65. data/test/unit/send_buyer_message_command_test.rb +68 -0
  66. data/test/unit/ship_items_command_test.rb +81 -0
  67. data/test/unit/shipping_adjustment_test.rb +100 -0
  68. data/test/unit/shopping_cart_test.rb +146 -0
  69. data/test/unit/tax_rule_test.rb +70 -0
  70. data/test/unit/tax_table_test.rb +88 -0
  71. data/test/unit/tracking_data_test.rb +54 -0
  72. data/test/unit/unarchive_order_command_test.rb +63 -0
  73. data/test/unit/url_parameter_test.rb +55 -0
  74. data/test/unit/us_country_area_test.rb +76 -0
  75. data/test/unit/us_state_area_test.rb +70 -0
  76. data/test/unit/us_zip_area_test.rb +66 -0
  77. data/test/unit/world_area_test.rb +48 -0
  78. data/var/cacert.pem +7815 -0
  79. metadata +230 -0
@@ -0,0 +1,94 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/utils.rb
4
+ # Author: Tony Chan <api.htchan@gmail.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 by the command
28
+ # generating code.
29
+
30
+ require 'cgi'
31
+ require 'openssl'
32
+ require 'base64'
33
+
34
+ module Google4R #:nodoc:
35
+ module Checkout #:nodoc:
36
+ # HTML Signing
37
+ #
38
+ # Args:
39
+ # - params -- html form parameters
40
+ # - merchant_key -- Google Checkout merchant key
41
+ #
42
+ # Returns:
43
+ # - signature -- The base-64 encoded result of hashing the serialized
44
+ # parameters with the merchant key
45
+ #
46
+ # Example
47
+ # -------
48
+ # require 'google4r/checkout/utils'
49
+ #
50
+ # Google4R::Checkout.sign({:a=>'123', :b=>'456'}, 'merchantkey')
51
+ # => "5qBQYatFZk5BMS1hm5gSUS+9yrg="
52
+ #
53
+ def self.sign(params, merchant_key)
54
+ raise "params must be a Hash (e.g. {param1 => value1, param2 => value2, ...})" unless params.kind_of? Hash
55
+ raise "merchant_key must be a String" unless merchant_key.kind_of? String
56
+
57
+ # Remove unwanted parameters
58
+ params.delete_if do |key, value|
59
+ key = key.to_s
60
+ key == '_charset_' || key == 'analyticsdata' ||
61
+ key == 'urchindata' || key =~ /^(.+\.)*[xy]$/
62
+ end
63
+
64
+ # Strip away whitespaces and url-encode the values
65
+ params.each do |key, value|
66
+ params[key] = CGI::escape(value.to_s.strip)
67
+ end
68
+
69
+ # Sort parameters alphabetically by value and then key
70
+ params_arr = params.sort do |x, y|
71
+ if x[0] != y[0] then
72
+ x[0].to_s <=> y[0].to_s
73
+ else
74
+ x[1].to_s <=> y[1].to_s
75
+ end
76
+ end
77
+
78
+ # Create parameter string to be hashed
79
+ params_str = ''
80
+ params_arr.each do |x|
81
+ if params_str != '' then params_str += '&' end
82
+ params_str += x[0].to_s + '=' + x[1]
83
+ end
84
+
85
+ # Generate hashed signature
86
+ signature = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new,
87
+ merchant_key,
88
+ params_str)
89
+
90
+ # Encode the hash value in Base64 before returning it
91
+ return Base64.encode64(signature).chomp
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,1023 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/xml_generation.rb
4
+ # Authors: Manuel Holtgrewe <purestorm at ggnore dot net>
5
+ # Tony Chan <api.htchan at gmail dot com>
6
+ # Copyright: (c) 2007 by Manuel Holtgrewe
7
+ # License: MIT License as follows:
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to permit
14
+ # persons to whom the Software is furnished to do so, subject to the
15
+ # following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be included
18
+ # in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
26
+ # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #++
28
+ # This file contains the classes that allow to persist the object hierarchies
29
+ # that are created with the Google Checkout API to XML.
30
+
31
+ require 'stringio'
32
+ require 'rexml/document'
33
+ require 'time'
34
+
35
+ module Google4R #:nodoc:
36
+ module Checkout #:nodoc:
37
+
38
+ class XmlGenerator
39
+ def initialize()
40
+ raise 'Cannot instantiate an abstract class.'
41
+ end
42
+
43
+ # Base method to generate the XML for a particular command
44
+ def generate
45
+ @document = REXML::Document.new
46
+
47
+ declaration = REXML::XMLDecl.new
48
+ declaration.encoding = 'utf-8'
49
+ @document << declaration
50
+ end
51
+ end
52
+
53
+ # Abstract super class for all CommandXmlGenerators
54
+ # It should never be instantiated directly
55
+ class CommandXmlGenerator < XmlGenerator
56
+
57
+ # The list of command tag names
58
+ COMMAND_TO_TAG =
59
+ {
60
+ ChargeAndShipOrderCommand => "charge-and-ship-order",
61
+ ChargeOrderCommand => 'charge-order',
62
+ RefundOrderCommand => 'refund-order',
63
+ CancelOrderCommand => 'cancel-order',
64
+ AuthorizeOrderCommand => 'authorize-order',
65
+ ProcessOrderCommand => 'process-order',
66
+ AddMerchantOrderNumberCommand => 'add-merchant-order-number',
67
+ DeliverOrderCommand => 'deliver-order',
68
+ AddTrackingDataCommand => 'add-tracking-data',
69
+ SendBuyerMessageCommand => 'send-buyer-message',
70
+ ArchiveOrderCommand => 'archive-order',
71
+ UnarchiveOrderCommand => 'unarchive-order',
72
+ CreateOrderRecurrenceRequestCommand => 'create-order-recurrence-request',
73
+ ShipItemsCommand => 'ship-items',
74
+ BackorderItemsCommand => 'backorder-items',
75
+ CancelItemsCommand => 'cancel-items',
76
+ ReturnItemsCommand => 'return-items',
77
+ ResetItemsShippingInformationCommand => 'reset-items-shipping-information',
78
+ OrderReportCommand => 'order-list-request',
79
+ }
80
+
81
+ def initialize(command)
82
+ if COMMAND_TO_TAG.has_key?(command.class)
83
+ @command = command
84
+ else
85
+ raise 'Cannot instantiate an abstract class.'
86
+ end
87
+ end
88
+
89
+ # Base method to generate the XML for a particular command
90
+ def generate
91
+ super
92
+ self.process_command(@command)
93
+ io = StringIO.new
94
+ @document.write(io, -1)
95
+ return io.string
96
+ end
97
+
98
+ def tag_name_for_command(klass)
99
+ COMMAND_TO_TAG[klass]
100
+ end
101
+
102
+ protected
103
+
104
+ # Base method to generate root tag of a command
105
+ def process_command(command)
106
+ tag_name = self.tag_name_for_command(command.class)
107
+ root = @document.add_element(tag_name)
108
+ root.add_attribute('google-order-number', command.google_order_number)
109
+ root.add_attribute('xmlns', 'http://checkout.google.com/schema/2')
110
+ return root
111
+ end
112
+ end
113
+
114
+ # Use the CheckoutXmlGenerator to create an XML document from a CheckoutCommand
115
+ # object.
116
+ #
117
+ # Usage:
118
+ #
119
+ # checkout = CheckoutCommand.new
120
+ # # set up the CheckoutCommand
121
+ #
122
+ # generator = CheckoutCommandXmlGenerator.new(checkout)
123
+ # puts generator.generate # => "<xml? version=..."
124
+ # File.new('some.xml', 'w') { |f| f.write generator.generate }
125
+ #--
126
+ # TODO: Refactor the big, monolitic generator into smaller, easier testable ones. One for each major part of the resulting XML document. This will also reduce the overhead in generating other types of XML documents.
127
+ #++
128
+ class CheckoutCommandXmlGenerator < CommandXmlGenerator
129
+
130
+ def initialize(command)
131
+ @command = command
132
+ end
133
+
134
+ protected
135
+
136
+ def process_command(command)
137
+ root = @document.add_element("checkout-shopping-cart" , { 'xmlns' => 'http://checkout.google.com/schema/2' })
138
+
139
+ self.process_shopping_shopping_cart(root, command.shopping_cart)
140
+
141
+ # <merchant-checkout-flow-support>
142
+ flow_element = root.add_element('checkout-flow-support').add_element('merchant-checkout-flow-support')
143
+
144
+ # <tax-tables>
145
+ if command.tax_tables then
146
+ self.process_tax_tables(command.tax_tables, flow_element)
147
+ end
148
+
149
+ # <continue-shopping-url>
150
+ if not command.continue_shopping_url.nil? then
151
+ flow_element.add_element('continue-shopping-url').text = command.continue_shopping_url
152
+ end
153
+
154
+ # <edit-cart-url>
155
+ if not command.edit_cart_url.nil? then
156
+ flow_element.add_element('edit-cart-url').text = command.edit_cart_url
157
+ end
158
+
159
+ # <request-buyer-phone-number>
160
+ if not command.request_buyer_phone_number.nil? then
161
+ flow_element.add_element('request-buyer-phone-number').text =
162
+ if command.request_buyer_phone_number then
163
+ "true"
164
+ else
165
+ "false"
166
+ end
167
+ end
168
+
169
+ # <merchant-calculations>
170
+ if not command.merchant_calculations_url.nil? then
171
+ merchant_calculations = flow_element.add_element('merchant-calculations')
172
+ merchant_calculations.add_element('merchant-calculations-url').text =
173
+ command.merchant_calculations_url
174
+ if not command.accept_merchant_coupons.nil? then
175
+ merchant_calculations.add_element('accept-merchant-coupons').text =
176
+ command.accept_merchant_coupons.to_s
177
+ end
178
+ if not command.accept_gift_certificates.nil? then
179
+ merchant_calculations.add_element('accept-gift-certificates').text =
180
+ command.accept_gift_certificates.to_s
181
+ end
182
+ end
183
+
184
+ # <platform-id>
185
+ if not command.platform_id.nil? then
186
+ flow_element.add_element('platform-id').text = command.platform_id
187
+ end
188
+
189
+ # <shipping-methods>
190
+ shippings_element = flow_element.add_element('shipping-methods')
191
+ command.shipping_methods.each do |shipping_method|
192
+ self.process_shipping_method(shippings_element, shipping_method)
193
+ end
194
+
195
+ # <analytics-data>
196
+ unless command.analytics_data.nil? then
197
+ analytics_element = flow_element.add_element('analytics-data')
198
+ analytics_element.text = command.analytics_data
199
+ end
200
+
201
+ # <parameterized-urls>
202
+ if command.parameterized_urls then
203
+ parameterized_url_element = flow_element.add_element('parameterized-urls')
204
+ command.parameterized_urls.each do |parameterized_url|
205
+ self.process_parameterized_urls(parameterized_url, parameterized_url_element)
206
+ end
207
+ end
208
+ end
209
+
210
+ # adds the tax-tables to the parent xml element
211
+ # assumes that the first member of the tax_tables array is the default tax rule table
212
+ # and that all others are alternate tax rules
213
+ def process_tax_tables(tax_tables, parent)
214
+ tax_tables_element = parent.add_element('tax-tables')
215
+
216
+ # assumes that the first tax table is the default
217
+ default_table = tax_tables.first
218
+
219
+ if default_table.merchant_calculated
220
+ tax_tables_element.add_attribute('merchant-calculated', 'true')
221
+ else
222
+ tax_tables_element.add_attribute('merchant-calculated', 'false')
223
+ end
224
+
225
+ default_table_element = tax_tables_element.add_element('default-tax-table')
226
+ rules_element = default_table_element.add_element('tax-rules')
227
+
228
+ default_table.rules.each do |rule|
229
+ default_rule_element = rules_element.add_element('default-tax-rule')
230
+ default_rule_element.add_element('shipping-taxed').text=rule.shipping_taxed.to_s
231
+ default_rule_element.add_element('rate').text=rule.rate.to_s
232
+ self.process_area(default_rule_element.add_element('tax-area'), rule.area)
233
+ end
234
+
235
+ # populate alternate tax tables
236
+ alt_tables = tax_tables.last(tax_tables.length-1)
237
+ alt_tables_element = tax_tables_element.add_element('alternate-tax-tables')
238
+
239
+ alt_tables.each do |table|
240
+ table_element = alt_tables_element.add_element('alternate-tax-table')
241
+ table_element.add_attribute('name', table.name)
242
+ table_element.add_attribute('standalone', table.standalone.to_s)
243
+
244
+ rules_element = table_element.add_element('alternate-tax-rules')
245
+ table.rules.each do |rule|
246
+ alt_rule_element = rules_element.add_element('alternate-tax-rule')
247
+ alt_rule_element.add_element('rate').text=rule.rate.to_s
248
+
249
+ self.process_area(alt_rule_element.add_element('tax-area'), rule.area)
250
+ end
251
+ end
252
+ end
253
+
254
+
255
+ def process_shopping_shopping_cart(parent, shopping_cart)
256
+ cart_element = parent.add_element('shopping-cart')
257
+
258
+ # add <cart-expiration> tag to the cart if a time has been added to the cart
259
+ if not shopping_cart.expires_at.nil? then
260
+ cart_element.add_element('cart-expiration').add_element('good-until-date').text =
261
+ shopping_cart.expires_at.iso8601
262
+ end
263
+
264
+ # add <merchant-private-data> to the cart if any has been set
265
+ if not shopping_cart.private_data.nil? then
266
+ self.process_hash(cart_element.add_element('merchant-private-data'), shopping_cart.private_data)
267
+ end
268
+
269
+ # process the items in the cart
270
+ items_element = cart_element.add_element('items')
271
+ shopping_cart.items.each do |item|
272
+ self.process_item(items_element, item)
273
+ end
274
+ end
275
+
276
+ # Adds an <item> tag to the tag parent with the appropriate values.
277
+ def process_item(parent, item)
278
+ item_element = parent.add_element('item')
279
+
280
+ item_element.add_element('item-name').text = item.name
281
+ item_element.add_element('item-description').text = item.description
282
+
283
+ item_element.add_element('unit-price', { 'currency' => item.unit_price.currency.to_s }).text = item.unit_price.to_s
284
+ item_element.add_element('quantity').text = item.quantity.to_i
285
+
286
+ if not item.id.nil? then
287
+ item_element.add_element('merchant-item-id').text = item.id
288
+ end
289
+
290
+ if not item.weight.nil? then
291
+ item_element.add_element('item-weight',
292
+ { 'unit' => item.weight.unit,
293
+ 'value' => item.weight.value })
294
+ end
295
+
296
+ if not item.private_data.nil? then
297
+ self.process_hash(item_element.add_element('merchant-private-item-data'), item.private_data)
298
+ end
299
+
300
+ # The above was easy; now we need to get the appropriate tax table for this
301
+ # item. The Item class makes sure that the table exists.
302
+ if not item.tax_table.nil? then
303
+ item_element.add_element('tax-table-selector').text = item.tax_table.name
304
+ end
305
+
306
+ if not item.digital_content.nil? then
307
+ self.process_digital_content(item_element, item.digital_content)
308
+ end
309
+
310
+ if not (item.kind_of? Item::Subscription::RecurrentItem or item.subscription.nil?) then
311
+ self.process_subscription(item_element, item.subscription)
312
+ end
313
+ end
314
+
315
+ # Adds a <subscription> element to a parent (<item>) element
316
+ def process_subscription(parent, subscription)
317
+ subscription_element = parent.add_element('subscription')
318
+
319
+ if not subscription.no_charge_after.nil? then
320
+ subscription_element.attributes['no-charge-after'] = subscription.no_charge_after.xmlschema
321
+ end
322
+
323
+ if not subscription.period.nil? then
324
+ subscription_element.attributes['period'] = subscription.period.to_s
325
+ end
326
+
327
+ if not subscription.start_date.nil? then
328
+ subscription_element.attributes['start-date'] = subscription.start_date.xmlschema
329
+ end
330
+
331
+ if not subscription.type.nil? then
332
+ subscription_element.attributes['type'] = subscription.type.to_s
333
+ end
334
+
335
+ if subscription.payments.length > 0
336
+ payments_element = subscription_element.add_element('payments')
337
+
338
+ subscription.payments.each do |payment|
339
+ self.process_subscription_payment(payments_element, payment)
340
+ end
341
+ end
342
+
343
+ if subscription.recurrent_items.length > 0
344
+ # this is a little bit of a hack; we use the normal way of generating items
345
+ # for a shopping cart, and then rename the elements to 'recurrent-item'
346
+ # after the fact
347
+
348
+ subscription.recurrent_items.each do |item|
349
+ self.process_item(subscription_element, item)
350
+ end
351
+
352
+ subscription_element.elements.each('item') do |item_element|
353
+ item_element.name = 'recurrent-item'
354
+ end
355
+ end
356
+ end
357
+
358
+ # Adds a <subcription-payment> element to a parent (<payments>) element
359
+ def process_subscription_payment(parent, payment)
360
+ payment_element = parent.add_element('subscription-payment')
361
+
362
+ if not payment.times.nil? then
363
+ payment_element.attributes['times'] = payment.times.to_s
364
+ end
365
+
366
+ if not payment.maximum_charge.nil? then
367
+ payment_element.add_element('maximum-charge', { 'currency' => payment.maximum_charge.currency.to_s }).text = payment.maximum_charge.to_s
368
+ end
369
+ end
370
+
371
+ # Adds a <digital-content> element to a parent (<item>) element
372
+ def process_digital_content(parent, digital_content)
373
+ digital_content_element = parent.add_element('digital-content')
374
+
375
+ if not digital_content.description.nil? then
376
+ digital_content_element.add_element('description').text = digital_content.description.to_s
377
+ end
378
+
379
+ if not digital_content.email_delivery.nil? then
380
+ digital_content_element.add_element('email-delivery').text = digital_content.email_delivery.to_s
381
+ end
382
+
383
+ if not digital_content.key.nil? then
384
+ digital_content_element.add_element('key').text = digital_content.key.to_s
385
+ end
386
+
387
+ if not digital_content.url.nil? then
388
+ digital_content_element.add_element('url').text = digital_content.url.to_s
389
+ end
390
+
391
+ digital_content_element.add_element('display-disposition').text = digital_content.display_disposition.to_s
392
+ end
393
+
394
+ # Adds an item for the given shipping method.
395
+ def process_shipping_method(parent, shipping_method)
396
+ if shipping_method.kind_of? PickupShipping then
397
+ process_pickup(parent, shipping_method)
398
+ elsif shipping_method.kind_of? FlatRateShipping then
399
+ process_shipping('flat-rate-shipping', parent, shipping_method)
400
+ elsif shipping_method.kind_of? MerchantCalculatedShipping then
401
+ process_shipping('merchant-calculated-shipping', parent, shipping_method)
402
+ elsif shipping_method.kind_of? CarrierCalculatedShipping then
403
+ process_carrier_calculated_shipping('carrier-calculated-shipping', parent, shipping_method)
404
+ else
405
+ raise "Unknown ShippingMethod type of #{shipping_method.inspect}!"
406
+ end
407
+ end
408
+
409
+ def process_shipping(shipping_type, parent, shipping)
410
+ element = parent.add_element(shipping_type)
411
+ element.add_attribute('name', shipping.name)
412
+ element.add_element('price', { 'currency' => shipping.price.currency.to_s }).text = shipping.price.to_s
413
+
414
+ if shipping.shipping_restrictions_excluded_areas.length +
415
+ shipping.shipping_restrictions_allowed_areas.length > 0 then
416
+ shipping_restrictions_tag = element.add_element('shipping-restrictions')
417
+
418
+ allow_us_po_box = shipping_restrictions_tag.add_element('allow-us-po-box')
419
+ if shipping.shipping_restrictions_allow_us_po_box
420
+ allow_us_po_box.text = 'true'
421
+ else
422
+ allow_us_po_box.text = 'false'
423
+ end
424
+
425
+ if shipping.shipping_restrictions_allowed_areas.length > 0 then
426
+ allowed_tag = shipping_restrictions_tag.add_element('allowed-areas')
427
+
428
+ shipping.shipping_restrictions_allowed_areas.each do |area|
429
+ self.process_area(allowed_tag, area)
430
+ end
431
+ end
432
+
433
+ if shipping.shipping_restrictions_excluded_areas.length > 0 then
434
+ excluded_tag = shipping_restrictions_tag.add_element('excluded-areas')
435
+
436
+ shipping.shipping_restrictions_excluded_areas.each do |area|
437
+ self.process_area(excluded_tag, area)
438
+ end
439
+ end
440
+ end
441
+
442
+ if shipping.kind_of? MerchantCalculatedShipping then
443
+ if shipping.address_filters_excluded_areas.length +
444
+ shipping.address_filters_allowed_areas.length > 0 then
445
+ address_filters_tag = element.add_element('address-filters')
446
+
447
+ allow_us_po_box = address_filters_tag.add_element('allow-us-po-box')
448
+ if shipping.address_filters_allow_us_po_box
449
+ allow_us_po_box.text = 'true'
450
+ else
451
+ allow_us_po_box.text = 'false'
452
+ end
453
+
454
+ if shipping.address_filters_allowed_areas.length > 0 then
455
+ allowed_tag = address_filters_tag.add_element('allowed-areas')
456
+
457
+ shipping.address_filters_allowed_areas.each do |area|
458
+ self.process_area(allowed_tag, area)
459
+ end
460
+ end
461
+
462
+ if shipping.address_filters_excluded_areas.length > 0 then
463
+ excluded_tag = address_filters_tag.add_element('excluded-areas')
464
+
465
+ shipping.address_filters_excluded_areas.each do |area|
466
+ self.process_area(excluded_tag, area)
467
+ end
468
+ end
469
+ end
470
+ end
471
+ end
472
+
473
+ def process_pickup(parent, shipping)
474
+ element = parent.add_element('pickup')
475
+ element.add_attribute('name', shipping.name)
476
+ element.add_element('price', { 'currency' => shipping.price.currency.to_s }).text = shipping.price.to_s
477
+ end
478
+
479
+ def process_carrier_calculated_shipping(shipping_type, parent, shipping)
480
+ element = parent.add_element(shipping_type)
481
+ options_element = element.add_element('carrier-calculated-shipping-options')
482
+ packages_element = element.add_element('shipping-packages')
483
+ shipping.carrier_calculated_shipping_options.each do | option |
484
+ process_carrier_calculated_shipping_option(options_element, option)
485
+ end
486
+ shipping.shipping_packages.each do | package |
487
+ process_shipping_package(packages_element, package)
488
+ end
489
+ end
490
+
491
+ def process_carrier_calculated_shipping_option(parent, option)
492
+ element = parent.add_element('carrier-calculated-shipping-option')
493
+ element.add_element('price', { 'currency' => option.price.currency.to_s }).text = option.price.to_s
494
+ element.add_element('shipping-company').text = option.shipping_company
495
+ element.add_element('shipping-type').text = option.shipping_type
496
+ if not option.carrier_pickup.nil?
497
+ element.add_element('carrier-pickup').text = option.carrier_pickup
498
+ end
499
+ if not option.additional_fixed_charge.nil?
500
+ element.add_element('additional-fixed-charge',
501
+ { 'currency' => option.additional_fixed_charge.currency.to_s }).text =
502
+ option.additional_fixed_charge.to_s
503
+ end
504
+ if not option.additional_variable_charge_percent.nil?
505
+ element.add_element('additional-variable-charge-percent').text =
506
+ option.additional_variable_charge_percent.to_s
507
+ end
508
+ end
509
+
510
+ def process_shipping_package(parent, package)
511
+ element = parent.add_element('shipping-package')
512
+ ship_from = package.ship_from
513
+ ship_from_element = element.add_element('ship-from')
514
+ ship_from_element.add_attribute('id', ship_from.address_id)
515
+ ship_from_element.add_element('city').text = ship_from.city
516
+ ship_from_element.add_element('region').text = ship_from.region
517
+ ship_from_element.add_element('country-code').text = ship_from.country_code
518
+ ship_from_element.add_element('postal-code').text = ship_from.postal_code
519
+ if not package.delivery_address_category.nil?
520
+ element.add_element('delivery-address-category').text =
521
+ package.delivery_address_category
522
+ end
523
+ if not package.height.nil?
524
+ height_element = element.add_element('height')
525
+ height_element.add_attribute('unit', package.height.unit)
526
+ height_element.add_attribute('value', package.height.value.to_s)
527
+ end
528
+ if not package.length.nil?
529
+ length_element = element.add_element('length')
530
+ length_element.add_attribute('unit', package.length.unit)
531
+ length_element.add_attribute('value', package.length.value.to_s)
532
+ end
533
+ if not package.width.nil?
534
+ width_element = element.add_element('width')
535
+ width_element.add_attribute('unit', package.width.unit)
536
+ width_element.add_attribute('value', package.width.value.to_s)
537
+ end
538
+ end
539
+
540
+ # Adds an appropriate tag for the given Area subclass instance to the parent Element.
541
+ def process_area(parent, area)
542
+ if area.kind_of? UsZipArea then
543
+ parent.add_element('us-zip-area').add_element('zip-pattern').text = area.pattern
544
+ elsif area.kind_of? UsCountryArea then
545
+ parent.add_element('us-country-area', { 'country-area' => area.area })
546
+ elsif area.kind_of? UsStateArea then
547
+ parent.add_element('us-state-area').add_element('state').text = area.state
548
+ elsif area.kind_of? WorldArea then
549
+ parent.add_element('world-area')
550
+ elsif area.kind_of? PostalArea then
551
+ postal_area_element = parent.add_element('postal-area')
552
+ postal_area_element.add_element('country-code').text = area.country_code
553
+ if area.postal_code_pattern then
554
+ postal_area_element.add_element('postal-code-pattern').text = area.postal_code_pattern
555
+ end
556
+ else
557
+ raise "Area of unknown type: #{area.inspect}."
558
+ end
559
+ end
560
+
561
+ # Adda the paramterized URL nodes for 3rd party conversion tracking
562
+ # Adds a <paramertized-url> element to a parent (<parameterized-urls>) element
563
+ def process_parameterized_urls(parameterized_url, parent)
564
+ parameterized_url_node = parent.add_element('parameterized-url')
565
+ parameterized_url_node.add_attribute('url', parameterized_url.url)
566
+ parent_parameters_node = parameterized_url_node.add_element('parameters') if parameterized_url.url_parameters
567
+ parameterized_url.url_parameters.each do |parameter|
568
+ parameter_node = parent_parameters_node.add_element('url-parameter')
569
+ parameter_node.add_attribute('name', parameter.name)
570
+ parameter_node.add_attribute('type',parameter.parameter_type)
571
+ end
572
+ end
573
+
574
+ # Converts a Hash into an XML structure. The keys are converted to tag names. If
575
+ # the values are Hashs themselves then process_hash is called upon them. If the
576
+ # values are Arrays then a new element with the key's name will be created.
577
+ #
578
+ # If a value is an Array then this array will be flattened before it is processed.
579
+ # Thus, nested arrays are not allowed.
580
+ #
581
+ # === Example
582
+ #
583
+ # process_hash(parent, { 'foo' => { 'bar' => 'baz' } })
584
+ #
585
+ # # will produce a structure that is equivalent to.
586
+ #
587
+ # <foo>
588
+ # <bar>baz</bar>
589
+ # </foo>
590
+ #
591
+ #
592
+ # process_hash(parent, { 'foo' => [ { 'bar' => 'baz' }, "d'oh", 2 ] })
593
+ #
594
+ # # will produce a structure that is equivalent to.
595
+ #
596
+ # <foo>
597
+ # <bar>baz</bar>
598
+ # </foo>
599
+ # <foo>d&amp;</foo>
600
+ # <foo>2</foo>
601
+ def process_hash(parent, hash)
602
+ hash.each do |key, value|
603
+ if value.kind_of? Array then
604
+ value.flatten.each do |arr_entry|
605
+ if arr_entry.kind_of? Hash then
606
+ self.process_hash(parent.add_element(self.str2tag_name(key.to_s)), arr_entry)
607
+ else
608
+ parent.add_element(self.str2tag_name(key.to_s)).text = arr_entry.to_s
609
+ end
610
+ end
611
+ elsif value.kind_of? Hash then
612
+ process_hash(parent.add_element(self.str2tag_name(key.to_s)), value)
613
+ else
614
+ parent.add_element(self.str2tag_name(key.to_s)).text = value.to_s
615
+ end
616
+ end
617
+ end
618
+
619
+ # Converts a string to a valid XML tag name. Whitespace will be converted into a dash/minus
620
+ # sign, non alphanumeric characters that are neither "-" nor "_" nor ":" will be stripped.
621
+ def str2tag_name(str)
622
+ str.gsub(%r{\s}, '-').gsub(%r{[^a-zA-Z0-9\-\_:]}, '')
623
+ end
624
+ end
625
+
626
+ class ChargeOrderCommandXmlGenerator < CommandXmlGenerator
627
+
628
+ protected
629
+
630
+ def process_command(command)
631
+ root = super
632
+ process_money(root, command.amount) if command.amount
633
+ end
634
+
635
+ # add the amount element to the charge command
636
+ def process_money(parent, money)
637
+ amount_element = parent.add_element('amount')
638
+ amount_element.text = money.to_s
639
+ amount_element.add_attribute('currency', money.currency.to_s)
640
+ end
641
+ end
642
+
643
+ class ChargeAndShipOrderCommandXmlGenerator < CommandXmlGenerator
644
+
645
+ protected
646
+
647
+ def process_command(command)
648
+ root = super
649
+ process_money(root, command.amount) if command.amount
650
+ process_tracking_data(root, command.carrier, command.tracking_number)
651
+ root.add_element('send-email').text = command.send_email.to_s if command.send_email
652
+ end
653
+
654
+ def process_money(parent, money)
655
+ amount_element = parent.add_element('amount')
656
+ amount_element.text = money.to_s
657
+ amount_element.add_attribute('currency', money.currency.to_s)
658
+ end
659
+
660
+ def process_tracking_data(parent, carrier, tracking_number)
661
+ if carrier and tracking_number then
662
+ e1 = parent.add_element('tracking-data-list')
663
+ e2 = e1.add_element('tracking-data')
664
+ e2.add_element('carrier').text = carrier
665
+ e2.add_element('tracking-number').text = tracking_number
666
+ end
667
+ end
668
+ end
669
+
670
+ class RefundOrderCommandXmlGenerator < CommandXmlGenerator
671
+
672
+ protected
673
+
674
+ def process_command(command)
675
+ root = super
676
+ process_money(root, command.amount) if command.amount
677
+ process_comment(root, command.comment) if command.comment
678
+ process_reason(root, command.reason)
679
+ end
680
+
681
+ # add the amount element to the refund command
682
+ def process_money(parent, money)
683
+ amount_element = parent.add_element('amount')
684
+ amount_element.text = money.to_s
685
+ amount_element.add_attribute('currency', money.currency.to_s)
686
+ end
687
+
688
+ # add the comment element to the refund command
689
+ def process_comment(parent, comment)
690
+ comment_element = parent.add_element('comment')
691
+ comment_element.text = comment
692
+ end
693
+
694
+ # add the reason element to the refund command
695
+ def process_reason(parent, reason)
696
+ reason_element = parent.add_element('reason')
697
+ reason_element.text = reason
698
+ end
699
+ end
700
+
701
+ class CancelOrderCommandXmlGenerator < CommandXmlGenerator
702
+
703
+ protected
704
+
705
+ def process_command(command)
706
+ root = super
707
+ root.add_element('reason').text = command.reason
708
+
709
+ if command.comment then
710
+ root.add_element('comment').text = command.comment
711
+ end
712
+ end
713
+
714
+ end
715
+
716
+ class AuthorizeOrderCommandXmlGenerator < CommandXmlGenerator
717
+
718
+ protected
719
+
720
+ def process_command(command)
721
+ super
722
+ end
723
+ end
724
+
725
+ class ProcessOrderCommandXmlGenerator < CommandXmlGenerator
726
+
727
+ protected
728
+
729
+ def process_command(command)
730
+ super
731
+ end
732
+ end
733
+
734
+ class AddMerchantOrderNumberCommandXmlGenerator < CommandXmlGenerator
735
+
736
+ protected
737
+
738
+ def process_command(command)
739
+ root = super
740
+ process_merchant_order_number(root, command.merchant_order_number)
741
+ end
742
+
743
+ def process_merchant_order_number(parent, merchant_order_number)
744
+ merchant_order_number_element = parent.add_element('merchant-order-number')
745
+ merchant_order_number_element.text = merchant_order_number
746
+ end
747
+ end
748
+
749
+ class DeliverOrderCommandXmlGenerator < CommandXmlGenerator
750
+
751
+ protected
752
+
753
+ def process_command(command)
754
+ root = super
755
+ # Add tracking info
756
+ process_tracking_data(root, command.carrier, command.tracking_number)
757
+ root.add_element('send-email').text = command.send_email.to_s
758
+ end
759
+
760
+ def process_tracking_data(parent, carrier, tracking_number)
761
+ if carrier and tracking_number then
762
+ element = parent.add_element('tracking-data')
763
+ element.add_element('carrier').text = carrier
764
+ element.add_element('tracking-number').text = tracking_number
765
+ end
766
+ end
767
+ end
768
+
769
+ class AddTrackingDataCommandXmlGenerator < CommandXmlGenerator
770
+
771
+ protected
772
+
773
+ def process_command(command)
774
+ root = super
775
+ # Add tracking info
776
+ process_tracking_data(root, command.carrier, command.tracking_number)
777
+ end
778
+
779
+ def process_tracking_data(parent, carrier, tracking_number)
780
+ if carrier and tracking_number then
781
+ element = parent.add_element('tracking-data')
782
+ element.add_element('carrier').text = carrier
783
+ element.add_element('tracking-number').text = tracking_number
784
+ end
785
+ end
786
+ end
787
+
788
+ class SendBuyerMessageCommandXmlGenerator < CommandXmlGenerator
789
+
790
+ protected
791
+
792
+ def process_command(command)
793
+ root = super
794
+ root.add_element('message').text = command.message
795
+ if not command.send_email.nil? then
796
+ root.add_element('send-email').text = command.send_email.to_s
797
+ end
798
+ end
799
+ end
800
+
801
+ class ArchiveOrderCommandXmlGenerator < CommandXmlGenerator
802
+
803
+ protected
804
+
805
+ def process_command(command)
806
+ super
807
+ end
808
+ end
809
+
810
+ class UnarchiveOrderCommandXmlGenerator < CommandXmlGenerator
811
+
812
+ protected
813
+
814
+ def process_command(command)
815
+ super
816
+ end
817
+ end
818
+
819
+ class CreateOrderRecurrenceRequestCommandXmlGenerator < CheckoutCommandXmlGenerator
820
+
821
+ protected
822
+
823
+ def process_command(command)
824
+ root = @document.add_element("create-order-recurrence-request" , { 'xmlns' => 'http://checkout.google.com/schema/2' })
825
+
826
+ root.attributes['google-order-number'] = command.google_order_number
827
+
828
+ self.process_shopping_shopping_cart(root, command.shopping_cart)
829
+ end
830
+ end
831
+
832
+ class MerchantCalculationResultsXmlGenerator < XmlGenerator
833
+
834
+ def initialize(merchant_calculation_results)
835
+ @merchant_calculation_results = merchant_calculation_results
836
+ end
837
+
838
+ def generate()
839
+ super
840
+ process_results(@merchant_calculation_results.merchant_calculation_results)
841
+ io = StringIO.new
842
+ @document.write(io, -1)
843
+ return io.string
844
+ end
845
+
846
+ protected
847
+
848
+ def process_results(merchant_calculation_results)
849
+ root = @document.add_element("merchant-calculation-results" , { 'xmlns' => 'http://checkout.google.com/schema/2' })
850
+ results = root.add_element("results")
851
+ for merchant_calculation_result in merchant_calculation_results do
852
+ process_result(results, merchant_calculation_result)
853
+ end
854
+ end
855
+
856
+ def process_result(parent, merchant_calculation_result)
857
+ element = parent.add_element("result")
858
+ element.add_attribute("shipping-name", merchant_calculation_result.shipping_name)
859
+ element.add_attribute("address-id", merchant_calculation_result.address_id)
860
+ shipping_rate = element.add_element("shipping-rate")
861
+ shipping_rate.text = merchant_calculation_result.shipping_rate.to_s
862
+ shipping_rate.add_attribute("currency", merchant_calculation_result.shipping_rate.currency.to_s)
863
+ element.add_element("shippable").text = merchant_calculation_result.shippable.to_s
864
+ if (!merchant_calculation_result.total_tax.nil?)
865
+ total_tax = element.add_element("total-tax")
866
+ total_tax.text = merchant_calculation_result.total_tax.to_s
867
+ total_tax.add_attribute("currency", merchant_calculation_result.total_tax.currency.to_s)
868
+ end
869
+ process_code_results(element, merchant_calculation_result.merchant_code_results)
870
+ end
871
+
872
+ def process_code_results(parent, merchant_code_results)
873
+ element = parent.add_element("merchant-code-results")
874
+ for merchant_code_result in merchant_code_results do
875
+ process_merchant_code_result(element, merchant_code_result)
876
+ end
877
+ end
878
+
879
+ def process_merchant_code_result(parent, merchant_code_result)
880
+ if merchant_code_result.kind_of?(CouponResult)
881
+ element = parent.add_element("coupon-result")
882
+ elsif merchant_code_result.kind_of?(GiftCertificateResult)
883
+ element = parent.add_element("gift-certificate-result")
884
+ else
885
+ raise "Code of unknown type: #{merchant_code_result.inspect}."
886
+ end
887
+ element.add_element("valid").text = merchant_code_result.valid.to_s
888
+ element.add_element("code").text = merchant_code_result.code.to_s
889
+ calculated_amount = element.add_element("calculated-amount")
890
+ calculated_amount.text = merchant_code_result.calculated_amount.to_s
891
+ calculated_amount.add_attribute("currency", merchant_code_result.calculated_amount.currency.to_s)
892
+ element.add_element("message").text = merchant_code_result.message
893
+ end
894
+ end
895
+
896
+ class NotificationAcknowledgementXmlGenerator < XmlGenerator
897
+
898
+ def initialize(notification_acknowledgement)
899
+ @notification_acknowledgement = notification_acknowledgement
900
+ end
901
+
902
+ def generate
903
+ super
904
+ self.process_notification_acknowledgement(@notification_acknowledgement)
905
+ io = StringIO.new
906
+ @document.write(io, -1)
907
+ return io.string
908
+ end
909
+
910
+ def process_notification_acknowledgement(notification_acknowledgement)
911
+ root = @document.add_element('notification-acknowledgment')
912
+ root.add_attribute('xmlns', 'http://checkout.google.com/schema/2')
913
+ if not notification_acknowledgement.serial_number.nil?
914
+ root.add_attribute('serial-number', notification_acknowledgement.serial_number)
915
+ end
916
+ end
917
+ end
918
+
919
+ # Line-item shipping commands
920
+ class ItemsCommandXmlGenerator < CommandXmlGenerator
921
+ protected
922
+
923
+ def process_command(command)
924
+ root = super
925
+ process_item_info_arr(root, command.item_info_arr)
926
+ process_send_email(root, command.send_email)
927
+ return root
928
+ end
929
+
930
+ def process_item_info_arr(parent, item_info_arr)
931
+ element = parent.add_element('item-ids')
932
+ item_info_arr.each do |item_info|
933
+ item_id = element.add_element('item-id')
934
+ item_id.add_element('merchant-item-id').text =
935
+ item_info.merchant_item_id
936
+ end
937
+ end
938
+
939
+ def process_send_email(parent, send_email)
940
+ parent.add_element('send-email').text = send_email.to_s
941
+ end
942
+ end
943
+
944
+ class ShipItemsCommandXmlGenerator < ItemsCommandXmlGenerator
945
+ protected
946
+
947
+ def process_item_info_arr(parent, item_info_arr)
948
+ e1 = parent.add_element('item-shipping-information-list')
949
+ item_info_arr.each do |item_info|
950
+ e2 = e1.add_element('item-shipping-information')
951
+ item_id = e2.add_element('item-id')
952
+ item_id.add_element('merchant-item-id').text =
953
+ item_info.merchant_item_id
954
+ if !item_info.tracking_data_arr.nil?
955
+ e3 = e2.add_element('tracking-data-list')
956
+ item_info.tracking_data_arr.each do |tracking_data|
957
+ e4 = e3.add_element('tracking-data')
958
+ e4.add_element('carrier').text = tracking_data.carrier
959
+ e4.add_element('tracking-number').text =
960
+ tracking_data.tracking_number
961
+ end
962
+ end
963
+ end
964
+ end
965
+ end
966
+
967
+ class BackorderItemsCommandXmlGenerator < ItemsCommandXmlGenerator
968
+ end
969
+
970
+ class CancelItemsCommandXmlGenerator < ItemsCommandXmlGenerator
971
+ protected
972
+
973
+ def process_command(command)
974
+ root = super
975
+ root.add_element('reason').text = command.reason
976
+
977
+ if command.comment then
978
+ root.add_element('comment').text = command.comment
979
+ end
980
+ end
981
+ end
982
+
983
+ class ReturnItemsCommandXmlGenerator < ItemsCommandXmlGenerator
984
+ end
985
+
986
+ class ResetItemsShippingInformationCommandXmlGenerator < ItemsCommandXmlGenerator
987
+ end
988
+
989
+ class ReturnOrderReportCommandXmlGenerator < CommandXmlGenerator
990
+ def initialize(command)
991
+ @command = command
992
+ end
993
+
994
+ protected
995
+
996
+ def process_command(command)
997
+ root = super
998
+ # TODO - sanity check format ?
999
+ root.add_attribute('start-date', command.start_date.to_s)
1000
+ root.add_attribute('end-date', command.end_date.to_s)
1001
+ flow_element = root
1002
+
1003
+ # <financial-state>
1004
+ if command.financial_state then
1005
+ financial_state_element = flow_element.add_element('financial-state')
1006
+ financial_state_element.text = command.financial_state.to_s
1007
+ end
1008
+
1009
+ # <fulfillment-state>
1010
+ if command.fulfillment_state then
1011
+ fulfillment_state_element = flow_element.add_element('fulfillment-state')
1012
+ fulfillment_state_element.text = command.fulfillment_state.to_s
1013
+ end
1014
+
1015
+ # <date-time-zone>
1016
+ if command.date_time_zone then
1017
+ dtz_element = flow_element.add_element('date-time-zone')
1018
+ dtz_element.text = command.date_time_zone.to_s
1019
+ end
1020
+ end
1021
+ end
1022
+ end
1023
+ end