active_shipping 1.0.0.pre1 → 1.0.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CONTRIBUTING.md +43 -30
- data/README.md +4 -5
- data/lib/active_shipping.rb +3 -0
- data/lib/active_shipping/carrier.rb +86 -16
- data/lib/active_shipping/carriers/canada_post.rb +70 -65
- data/lib/active_shipping/carriers/kunaki.rb +22 -31
- data/lib/active_shipping/carriers/ups.rb +224 -205
- data/lib/active_shipping/version.rb +1 -1
- metadata +29 -31
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 73732fbdf2ce8048403b8039027c688126ba5233
|
4
|
+
data.tar.gz: 6f16c0d09ecd5392008b5cd6f20a401a0631d8b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f007d4d627a81ba03b03fc638520e1576087fec52ac0989046720505c8eb550a07ff3501b7e43966624f53d8e0ee6e161d06752c8edeafd8106b91935ff2d42
|
7
|
+
data.tar.gz: e56fe6610709fbdd802a3e3bcd573ac3465d97ed3e79a879ea4708520fb43a9658725af24f13a20bd6de0920727db4bbcc5f769ca6a2c61655b106d9c6997874
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data.tar.gz.sig
CHANGED
Binary file
|
data/CONTRIBUTING.md
CHANGED
@@ -1,32 +1,45 @@
|
|
1
1
|
# Contributing to ActiveShipping
|
2
2
|
|
3
|
-
We welcome fixes and additions to this project.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
3
|
+
We welcome fixes and additions to this project. Fork this project, make your changes and submit a pull request!
|
4
|
+
|
5
|
+
### Code style
|
6
|
+
|
7
|
+
Please use clean, concise code that follows Ruby community standards. For example:
|
8
|
+
|
9
|
+
- Be consistent
|
10
|
+
- Don't use too much white space
|
11
|
+
- Use 2 space indent, no tabs.
|
12
|
+
- No spaces after (, [ and before ],)
|
13
|
+
- Nor too little
|
14
|
+
- Use spaces around operators and after commas, colons and semicolons
|
15
|
+
- Indent when as deep as case
|
16
|
+
- Write lucid code in lieu of adding comments
|
17
|
+
|
18
|
+
### Pull request guidelines
|
19
|
+
|
20
|
+
- Add unit tests, and remote tests to make sure we won't introduce regressions to your code later on.
|
21
|
+
- Make sure CI passes for all Ruby versions and dependency versions we support.
|
22
|
+
- XML handling: use `REXML` for parsing XML, and `builder` to generate it.
|
23
|
+
- JSON: use the JSON module that is included in Rubys standard ibrary
|
24
|
+
- HTTP: use `ActiveUtils`'s `PostsData`.
|
25
|
+
- Do not add new gem dependencies.
|
26
|
+
|
27
|
+
### Contributors
|
28
|
+
|
29
|
+
- James MacAulay (<http://jmacaulay.net>)
|
30
|
+
- Tobias Luetke (<http://blog.leetsoft.com>)
|
31
|
+
- Cody Fauser (<http://codyfauser.com>)
|
32
|
+
- Jimmy Baker (<http://jimmyville.com/>)
|
33
|
+
- William Lang (<http://williamlang.net/>)
|
34
|
+
- Cameron Fowler
|
35
|
+
- Christopher Saunders (<http://christophersaunders.ca>)
|
36
|
+
- Denis Odorcic
|
37
|
+
- Dennis O'Connor
|
38
|
+
- Dennis Theisen
|
39
|
+
- Edward Ocampo-Gooding
|
40
|
+
- Isaac Kearse
|
41
|
+
- John Duff
|
42
|
+
- Nigel Ramsay
|
43
|
+
- Philip Arndt
|
44
|
+
- Vikram Oberoi
|
45
|
+
- Willem van Bergen
|
data/README.md
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
[![Build Status](https://travis-ci.org/Shopify/active_shipping.png)](https://travis-ci.org/Shopify/active_shipping)
|
1
|
+
# ActiveShipping [![Build status](https://travis-ci.org/Shopify/active_shipping.svg?branch=master)](https://travis-ci.org/Shopify/active_shipping)
|
4
2
|
|
5
3
|
This library interfaces with the web services of various shipping carriers. The goal is to abstract the features that are most frequently used into a pleasant and consistent Ruby API.
|
6
4
|
|
7
5
|
- Finding shipping rates
|
6
|
+
- Registering shipments
|
8
7
|
- Tracking shipments
|
9
8
|
|
10
9
|
Active Shipping is currently being used and improved in a production environment for [Shopify][]. Development is being done by the Shopify integrations team (<integrations-team@shopify.com>). Discussion is welcome in the [Active Merchant Google Group][discuss].
|
@@ -100,7 +99,7 @@ Active Shipping is currently being used and improved in a production environment
|
|
100
99
|
|
101
100
|
After installing dependencies with `bundle install`, you can run the unit tests with `rake test:units` and the remote tests with `rake test:remote`. The unit tests mock out requests and responses so that everything runs locally, while the remote tests actually hit the carrier servers. For the remote tests, you'll need valid test credentials for any carriers' tests you want to run. The credentials should go in ~/.active_shipping/credentials.yml, and the format of that file can be seen in the included [credentials.yml](https://github.com/Shopify/active_shipping/blob/master/test/credentials.yml).
|
102
101
|
|
103
|
-
##
|
102
|
+
## Development
|
104
103
|
|
105
104
|
Yes, please! Take a look at the tests and the implementation of the Carrier class to see how the basics work. At some point soon there will be a carrier template generator along the lines of the gateway generator included in Active Merchant, but carrier.rb outlines most of what's necessary. The other main classes that would be good to familiarize yourself with are Location, Package, and Response.
|
106
105
|
|
@@ -116,7 +115,7 @@ To log requests and responses, just set the `logger` on your carrier class to so
|
|
116
115
|
|
117
116
|
(This logging functionality is provided by the [`PostsData` module](https://github.com/Shopify/active_utils/blob/master/lib/active_utils/posts_data.rb) in the `active_utils` dependency.)
|
118
117
|
|
119
|
-
After you've pushed your well-tested changes to your github fork, make a pull request and we'll take it from there!
|
118
|
+
After you've pushed your well-tested changes to your github fork, make a pull request and we'll take it from there! For more information, see CONTRIBUTING.md.
|
120
119
|
|
121
120
|
## Legal Mumbo Jumbo
|
122
121
|
|
data/lib/active_shipping.rb
CHANGED
@@ -27,6 +27,9 @@ require 'active_support/core_ext/enumerable'
|
|
27
27
|
require 'active_support/core_ext/class/attribute_accessors'
|
28
28
|
require 'active_utils'
|
29
29
|
|
30
|
+
require 'rexml/document'
|
31
|
+
require 'nokogiri'
|
32
|
+
|
30
33
|
require 'vendor/quantified/lib/quantified'
|
31
34
|
require 'vendor/xml_node/lib/xml_node'
|
32
35
|
|
@@ -1,7 +1,22 @@
|
|
1
1
|
module ActiveShipping
|
2
|
+
|
3
|
+
# Carrier is abstract the base class for all supported carriers.
|
4
|
+
#
|
5
|
+
# To implement support for a carrier, you should subclass this class and
|
6
|
+
# implement all the methods the carrier supports.
|
7
|
+
#
|
8
|
+
# @see #find_rates
|
9
|
+
# @see #create_shipment
|
10
|
+
# @see #find_tracking_info
|
11
|
+
#
|
12
|
+
# @!attribute test_mode
|
13
|
+
# Whether to interact with the carrier's sandbox environment.
|
14
|
+
# @return [Boolean]
|
15
|
+
#
|
16
|
+
# @!attribute last_request
|
17
|
+
# The last request performed against the carrier's API.
|
18
|
+
# @see #save_request
|
2
19
|
class Carrier
|
3
|
-
include ActiveUtils::RequiresParameters
|
4
|
-
include ActiveUtils::PostsData
|
5
20
|
include Quantified
|
6
21
|
|
7
22
|
attr_reader :last_request
|
@@ -9,6 +24,9 @@ module ActiveShipping
|
|
9
24
|
alias_method :test_mode?, :test_mode
|
10
25
|
|
11
26
|
# Credentials should be in options hash under keys :login, :password and/or :key.
|
27
|
+
# @param options [Hash] The details needed to connect to the carrier's API.
|
28
|
+
# @option options [Boolean] :test Set this to true to connect to the carrier's
|
29
|
+
# sandbox environment instead of the production environment.
|
12
30
|
def initialize(options = {})
|
13
31
|
requirements.each { |key| requires!(options, key) }
|
14
32
|
@options = options
|
@@ -16,22 +34,59 @@ module ActiveShipping
|
|
16
34
|
@test_mode = @options[:test]
|
17
35
|
end
|
18
36
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
#
|
37
|
+
# Asks the carrier for rate estimates for a given shipment.
|
38
|
+
#
|
39
|
+
# @note Override with whatever you need to get the rates from the carrier.
|
40
|
+
#
|
41
|
+
# @param origin [ActiveShipping::Location] Where the shipment will originate from.
|
42
|
+
# @param destination [ActiveShipping::Location] Where the package will go.
|
43
|
+
# @param packages [Array<ActiveShipping::Package>] The list of packages that will
|
44
|
+
# be in the shipment.
|
45
|
+
# @param options [Hash] Carrier-specific parameters.
|
46
|
+
# @return [ActiveShipping::RateResponse] The response from the carrier, which
|
47
|
+
# includes 0 or more rate estimates for different shipping products
|
25
48
|
def find_rates(origin, destination, packages, options = {})
|
49
|
+
raise NotImplementedError, "#find_rates is not supported by #{self.class.name}."
|
26
50
|
end
|
27
51
|
|
28
|
-
#
|
52
|
+
# Registers a new shipment with the carrier, to get a tracking number and
|
53
|
+
# potentially shipping labels
|
54
|
+
#
|
55
|
+
# @note Override with whatever you need to register a shipment, and obtain
|
56
|
+
# shipping labels if supported by the carrier.
|
57
|
+
#
|
58
|
+
# @param origin [ActiveShipping::Location] Where the shipment will originate from.
|
59
|
+
# @param destination [ActiveShipping::Location] Where the package will go.
|
60
|
+
# @param packages [Array<ActiveShipping::Package>] The list of packages that will
|
61
|
+
# be in the shipment.
|
62
|
+
# @param options [Hash] Carrier-specific parameters.
|
63
|
+
# @return [ActiveShipping::ShipmentResponse] The response from the carrier. This
|
64
|
+
# response should include a shipment identifier or tracking_number if successful,
|
65
|
+
# and potentially shipping labels.
|
29
66
|
def create_shipment(origin, destination, packages, options = {})
|
67
|
+
raise NotImplementedError, "#create_shipment is not supported by #{self.class.name}."
|
30
68
|
end
|
31
69
|
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
70
|
+
# Retrieves tracking information for a previous shipment
|
71
|
+
#
|
72
|
+
# @note Override with whatever you need to get a shipping label
|
73
|
+
#
|
74
|
+
# @param tracking_number [String] The unique identifier of the shipment to track.
|
75
|
+
# @param options [Hash] Carrier-specific parameters.
|
76
|
+
# @return [ActiveShipping::TrackingResponse] The response from the carrier. This
|
77
|
+
# response should a list of shipment tracking events if successful.
|
78
|
+
def find_tracking_info(tracking_number, options = {})
|
79
|
+
raise NotImplementedError, "#find_tracking_info is not supported by #{self.class.name}."
|
80
|
+
end
|
81
|
+
|
82
|
+
# Validate credentials with a call to the API.
|
83
|
+
#
|
84
|
+
# By default this just does a `find_rates` call with the orgin and destination both as
|
85
|
+
# the carrier's default_location. Override to provide alternate functionality, such as
|
86
|
+
# checking for `test_mode` to use test servers, etc.
|
87
|
+
#
|
88
|
+
# @return [Boolean] Should return `true` if the provided credentials proved to work,
|
89
|
+
# `false` otherswise.
|
35
90
|
def valid_credentials?
|
36
91
|
location = self.class.default_location
|
37
92
|
find_rates(location, location, Package.new(100, [5, 15, 30]), :test => test_mode)
|
@@ -41,17 +96,27 @@ module ActiveShipping
|
|
41
96
|
true
|
42
97
|
end
|
43
98
|
|
99
|
+
# The maximum weight the carrier will accept.
|
100
|
+
# @return [Quantified::Mass]
|
44
101
|
def maximum_weight
|
45
102
|
Mass.new(150, :pounds)
|
46
103
|
end
|
47
104
|
|
48
105
|
protected
|
49
106
|
|
50
|
-
|
51
|
-
|
107
|
+
include ActiveUtils::RequiresParameters
|
108
|
+
include ActiveUtils::PostsData
|
109
|
+
|
110
|
+
# Returns the keys that are required to be passed to the options hash
|
111
|
+
# @note Override to return required keys in options hash for initialize method.
|
112
|
+
# @return [Array<Symbol>]
|
113
|
+
def requirements
|
114
|
+
[]
|
52
115
|
end
|
53
116
|
|
54
|
-
#
|
117
|
+
# The default location to use for {#valid_credentials?}.
|
118
|
+
# @note Override for non-U.S.-based carriers.
|
119
|
+
# @return [ActiveShipping::Location]
|
55
120
|
def self.default_location
|
56
121
|
Location.new( :country => 'US',
|
57
122
|
:state => 'CA',
|
@@ -63,11 +128,16 @@ module ActiveShipping
|
|
63
128
|
:fax => '1-310-275-8159')
|
64
129
|
end
|
65
130
|
|
66
|
-
# Use after building the request to save for later inspection.
|
131
|
+
# Use after building the request to save for later inspection.
|
132
|
+
# @return [void]
|
67
133
|
def save_request(r)
|
68
134
|
@last_request = r
|
69
135
|
end
|
70
136
|
|
137
|
+
# Calculates a timestamp that corresponds a given number if business days in the future
|
138
|
+
#
|
139
|
+
# @param [Integer] The number of business days from now.
|
140
|
+
# @return [DateTime] A timestamp, the provided number of business days in the future.
|
71
141
|
def timestamp_from_business_day(days)
|
72
142
|
return unless days
|
73
143
|
date = DateTime.now.utc
|
@@ -23,7 +23,8 @@ module ActiveShipping
|
|
23
23
|
PostalOutlet = Struct.new(:sequence_no, :distance, :name, :business_name, :postal_address, :business_hours)
|
24
24
|
|
25
25
|
URL = "http://sellonline.canadapost.ca:30000"
|
26
|
-
|
26
|
+
DTD_NAME = 'eparcel'
|
27
|
+
DTD_URI = "http://sellonline.canadapost.ca/DevelopersResources/protocolV3/eParcel.dtd"
|
27
28
|
|
28
29
|
RESPONSE_CODES = {
|
29
30
|
'1' => "All calculation was done",
|
@@ -106,92 +107,96 @@ module ActiveShipping
|
|
106
107
|
|
107
108
|
private
|
108
109
|
|
110
|
+
def generate_xml(&block)
|
111
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
112
|
+
xml.doc.create_internal_subset(DTD_NAME, nil, DTD_URI)
|
113
|
+
yield(xml)
|
114
|
+
end
|
115
|
+
builder.to_xml
|
116
|
+
end
|
117
|
+
|
109
118
|
def build_rate_request(origin, destination, line_items = [], options = {})
|
110
|
-
line_items
|
111
|
-
origin
|
119
|
+
line_items = [line_items] unless line_items.is_a?(Array)
|
120
|
+
origin = origin.is_a?(Location) ? origin : Location.new(origin)
|
112
121
|
destination = destination.is_a?(Location) ? destination : Location.new(destination)
|
113
122
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
request << XmlNode.new('country', handle_non_iso_country_names(destination.country))
|
131
|
-
request << XmlNode.new('postalCode', destination.postal_code)
|
123
|
+
generate_xml do |xml|
|
124
|
+
xml.eparcel do
|
125
|
+
xml.language(@options[:french] ? 'fr' : 'en')
|
126
|
+
xml.ratesAndServicesRequest do
|
127
|
+
xml.merchantCPCID(@options[:login])
|
128
|
+
xml.fromPostalCode(origin.postal_code)
|
129
|
+
xml.turnAroundTime(options[:turn_around_time]) if options[:turn_around_time]
|
130
|
+
xml.itemsPrice(dollar_amount(line_items.map(&:value).compact.sum))
|
131
|
+
|
132
|
+
build_line_items(xml, line_items)
|
133
|
+
|
134
|
+
xml.city(destination.city)
|
135
|
+
xml.provOrState(destination.province)
|
136
|
+
xml.country(handle_non_iso_country_names(destination.country))
|
137
|
+
xml.postalCode(destination.postal_code)
|
138
|
+
end
|
132
139
|
end
|
133
140
|
end
|
134
|
-
|
135
|
-
DOCTYPE + xml_request.to_s
|
136
141
|
end
|
137
142
|
|
138
143
|
def parse_rate_response(response, origin, destination, options = {})
|
139
|
-
xml =
|
144
|
+
xml = Nokogiri.XML(response)
|
140
145
|
success = response_success?(xml)
|
141
146
|
message = response_message(xml)
|
142
147
|
|
143
148
|
rate_estimates = []
|
144
149
|
boxes = []
|
145
150
|
if success
|
146
|
-
xml.
|
147
|
-
service_name = (@options[:french] ? @@name_french : @@name) + " " + product.
|
148
|
-
service_code = product
|
151
|
+
xml.xpath('eparcel/ratesAndServicesResponse/product').each do |product|
|
152
|
+
service_name = (@options[:french] ? @@name_french : @@name) + " " + product.at('name').text
|
153
|
+
service_code = product['id']
|
149
154
|
|
150
155
|
rate_estimates << RateEstimate.new(origin, destination, @@name, service_name,
|
151
156
|
:service_code => service_code,
|
152
|
-
:total_price => product.
|
157
|
+
:total_price => product.at('rate').text,
|
153
158
|
:currency => 'CAD',
|
154
|
-
:shipping_date => product.
|
155
|
-
:delivery_range => [product.
|
159
|
+
:shipping_date => product.at('shippingDate').text,
|
160
|
+
:delivery_range => [product.at('deliveryDate').text] * 2
|
156
161
|
)
|
157
162
|
end
|
158
163
|
|
159
|
-
boxes = xml.
|
164
|
+
boxes = xml.xpath('eparcel/ratesAndServicesResponse/packing/box').map do |box|
|
160
165
|
b = Box.new
|
161
166
|
b.packedItems = []
|
162
|
-
b.name = box.
|
163
|
-
b.weight = box.
|
164
|
-
b.expediter_weight = box.
|
165
|
-
b.length = box.
|
166
|
-
b.width = box.
|
167
|
-
b.height = box.
|
168
|
-
b.packedItems = box.
|
167
|
+
b.name = box.at('name').text
|
168
|
+
b.weight = box.at('weight').text.to_f
|
169
|
+
b.expediter_weight = box.at('expediterWeight').text.to_f
|
170
|
+
b.length = box.at('length').text.to_f
|
171
|
+
b.width = box.at('width').text.to_f
|
172
|
+
b.height = box.at('height').text.to_f
|
173
|
+
b.packedItems = box.xpath('packedItem').map do |item|
|
169
174
|
p = PackedItem.new
|
170
|
-
p.quantity = item.
|
171
|
-
p.description = item.
|
175
|
+
p.quantity = item.at('quantity').text.to_i
|
176
|
+
p.description = item.at('description').text
|
172
177
|
p
|
173
178
|
end
|
174
179
|
b
|
175
180
|
end
|
176
181
|
|
177
|
-
postal_outlets = xml.
|
182
|
+
postal_outlets = xml.xpath('eparcel/ratesAndServicesResponse/nearestPostalOutlet').map do |outlet|
|
178
183
|
postal_outlet = PostalOutlet.new
|
179
|
-
postal_outlet.sequence_no = outlet.
|
180
|
-
postal_outlet.distance = outlet.
|
181
|
-
postal_outlet.name = outlet.
|
182
|
-
postal_outlet.business_name = outlet.
|
184
|
+
postal_outlet.sequence_no = outlet.at('postalOutletSequenceNo').text
|
185
|
+
postal_outlet.distance = outlet.at('distance').text
|
186
|
+
postal_outlet.name = outlet.at('outletName').text
|
187
|
+
postal_outlet.business_name = outlet.at('businessName').text
|
183
188
|
|
184
189
|
postal_outlet.postal_address = Location.new(
|
185
|
-
:address1 => outlet.
|
186
|
-
:postal_code => outlet.
|
187
|
-
:city => outlet.
|
188
|
-
:province => outlet.
|
190
|
+
:address1 => outlet.at('postalAddress/addressLine').text,
|
191
|
+
:postal_code => outlet.at('postalAddress/postal_code').text,
|
192
|
+
:city => outlet.at('postalAddress/municipality').text,
|
193
|
+
:province => outlet.at('postalAddress/province').text,
|
189
194
|
:country => 'Canada',
|
190
|
-
:phone_number => outlet.
|
195
|
+
:phone_number => outlet.at('phoneNumber').text
|
191
196
|
)
|
192
197
|
|
193
198
|
postal_outlet.business_hours = outlet.elements.collect('businessHours') do |hour|
|
194
|
-
{ :day_of_week => hour.
|
199
|
+
{ :day_of_week => hour.at('dayOfWeek').text, :time => hour.at('time').text }
|
195
200
|
end
|
196
201
|
|
197
202
|
postal_outlet
|
@@ -202,17 +207,17 @@ module ActiveShipping
|
|
202
207
|
end
|
203
208
|
|
204
209
|
def response_success?(xml)
|
205
|
-
return false unless xml.
|
210
|
+
return false unless xml.at('eparcel/error').nil?
|
206
211
|
|
207
|
-
value = xml.
|
212
|
+
value = xml.at('eparcel/ratesAndServicesResponse/statusCode').text
|
208
213
|
value == '1' || value == '2'
|
209
214
|
end
|
210
215
|
|
211
216
|
def response_message(xml)
|
212
217
|
if response_success?(xml)
|
213
|
-
xml.
|
218
|
+
xml.at('eparcel/ratesAndServicesResponse/statusMessage').text
|
214
219
|
else
|
215
|
-
xml.
|
220
|
+
xml.at('eparcel/error/statusMessage').text
|
216
221
|
end
|
217
222
|
end
|
218
223
|
|
@@ -225,17 +230,17 @@ module ActiveShipping
|
|
225
230
|
# <!-- - description (mandatory) -->
|
226
231
|
# <!-- - ready to ship (optional) -->
|
227
232
|
|
228
|
-
def build_line_items(line_items)
|
229
|
-
|
233
|
+
def build_line_items(xml, line_items)
|
234
|
+
xml.lineItems do
|
230
235
|
line_items.each do |line_item|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
236
|
+
xml.item do
|
237
|
+
xml.quantity(1)
|
238
|
+
xml.weight(line_item.kilograms)
|
239
|
+
xml.length(line_item.cm(:length).to_s)
|
240
|
+
xml.width(line_item.cm(:width).to_s)
|
241
|
+
xml.height(line_item.cm(:height).to_s)
|
242
|
+
xml.description(line_item.options[:description] || ' ')
|
243
|
+
xml.readyToShip(line_item.options[:ready_to_ship] || nil)
|
239
244
|
# By setting the 'readyToShip' tag to true, Sell Online will not pack this item in the boxes defined in the merchant profile.
|
240
245
|
end
|
241
246
|
end
|