https://www.endpointdev.com/blog/tags/shipping/2021-09-28T00:00:00+00:00End Point DevIntegrating the Estes Freight Shipping SOAP API as a Spree Shipping Calculatorhttps://www.endpointdev.com/blog/2021/09/estes-shipping-spree/2021-09-28T00:00:00+00:00Patrick Lewis
<p><img src="/blog/2021/09/estes-shipping-spree/20190617-084228-small.jpg" alt="Cargo ship on sea with dark clouds"></p>
<!-- photo by Jon Jensen -->
<p>One of our clients with a Spree-based e-commerce site was interested in providing automated shipping quotes to their customers using their freight carrier <a href="https://www.estes-express.com/">Estes</a>. After doing some research I found that Estes provided a variety of SOAP APIs and determined a method for extending Spree with custom shipping rate calculators. This presented an interesting challenge to me on several levels: most of my previous API integration experience was with <a href="https://en.wikipedia.org/wiki/Representational_state_transfer">REST</a>, not <a href="https://en.wikipedia.org/wiki/SOAP">SOAP</a> APIs, and I had not previously worked on custom shipping calculators for Spree. Fortunately, the Estes SOAP API documentation and some code examples of other Spree shipping calculators were all I needed to create a successful integration of the freight shipping API for this client.</p>
<h3 id="estes-api-documentation">Estes API Documentation</h3>
<p>The Estes <a href="https://www.estes-express.com/resources/digital-services/api/rate-quote-web-service-v4-0">Rate Quote Web Service</a> API is the one that I relied on for being able to generate shipping quotes based on a combination of source address, destination address, and package weight. I found the developer documentation to be thorough and helpful, and was able to create working client code to send a request and receive a response relatively quickly. Many optional fields can be provided when making requests but I found that I only needed to use a small subset of these, as shown in the example code below.</p>
<p>The one aspect of the API that tripped me up a bit was their use of <code>CN</code> as the country code for Canada; Spree and most other codebases I have encountered use the international standard <a href="https://www.iso.org/iso-3166-country-codes.html">ISO 3166 country codes</a> with <code>CA</code> for Canada, so I had to add a small workaround for that in my client code when requesting shipping quotes to Canadian addresses. Another limitation I encountered is that the API expected to receive only 5-digit US and 6-character Canadian postal codes, so I had to do a bit of manipulation in my shipping calculator to account for that.</p>
<h3 id="ruby-soap-client">Ruby SOAP Client</h3>
<p>I researched Ruby SOAP clients and soon found <a href="https://www.savonrb.com/">Savon</a>, the “Heavy metal SOAP client”, which proved to be very easy to integrate into my existing Rails/Spree application. I added the savon gem to my project’s Gemfile and I was quickly able to instantiate a Savon client and configure it using the WSDL provided by Estes. After that, most of the integration work involved crafting a valid XML payload for my request and then parsing the response.</p>
<h3 id="spree-shipping-calculators">Spree Shipping Calculators</h3>
<p>The final piece of the puzzle was implementing the Savon client into a Spree shipping calculator class. This process allowed me to retrieve details about the current user’s order and then return the calculated shipping estimates within the context of the Spree checkout pages. Looking at existing shipping calculator code helped set me on the right path here; in the end, it was just a matter of defining a new class that inherited from the base <code>Spree::ShippingCalculator</code> class and then defining the <code>#compute_package</code> method expected by Spree for returning the shipping cost of a given package.</p>
<h3 id="code-example">Code Example</h3>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><span style="color:#888"># app/models/spree/calculator/shipping/estes_calculator.rb</span>
<span style="color:#080;font-weight:bold">module</span> <span style="color:#b06;font-weight:bold">Spree::Calculator::Shipping</span>
<span style="color:#888"># Custom freight shipping rate API integration</span>
<span style="color:#080;font-weight:bold">class</span> <span style="color:#b06;font-weight:bold">EstesCalculator</span> < <span style="color:#036;font-weight:bold">Spree</span>::<span style="color:#036;font-weight:bold">ShippingCalculator</span>
<span style="color:#036;font-weight:bold">ESTES_API_URL</span> = <span style="color:#d20;background-color:#fff0f0">'https://www.estes-express.com/tools/rating/ratequote/v4.0/services/RateQuoteService?wsdl'</span>.freeze
<span style="color:#080;font-weight:bold">def</span> <span style="color:#b06;font-weight:bold">self</span>.<span style="color:#06b;font-weight:bold">description</span>
<span style="color:#d20;background-color:#fff0f0">'Estes Freight'</span>
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080;font-weight:bold">def</span> <span style="color:#06b;font-weight:bold">compute_package</span>(package)
country_code = package.order.ship_address.country.iso
<span style="color:#080;font-weight:bold">return</span> <span style="color:#00d;font-weight:bold">0</span> <span style="color:#080;font-weight:bold">unless</span> country_code.in?([<span style="color:#d20;background-color:#fff0f0">'US'</span>, <span style="color:#d20;background-color:#fff0f0">'CA'</span>, <span style="color:#d20;background-color:#fff0f0">'MX'</span>])
client = <span style="color:#036;font-weight:bold">Savon</span>.client(
<span style="color:#a60;background-color:#fff0f0">filters</span>: %i[user password account],
<span style="color:#a60;background-color:#fff0f0">log</span>: <span style="color:#080">true</span>,
<span style="color:#a60;background-color:#fff0f0">log_level</span>: <span style="color:#a60;background-color:#fff0f0">:debug</span>,
<span style="color:#a60;background-color:#fff0f0">logger</span>: <span style="color:#036;font-weight:bold">Logger</span>.new(<span style="color:#036;font-weight:bold">Rails</span>.root.join(<span style="color:#d20;background-color:#fff0f0">'log'</span>, <span style="color:#d20;background-color:#fff0f0">'savon.log'</span>)),
<span style="color:#a60;background-color:#fff0f0">pretty_print_xml</span>: <span style="color:#080">true</span>,
<span style="color:#a60;background-color:#fff0f0">wsdl</span>: <span style="color:#036;font-weight:bold">ESTES_API_URL</span>
)
country_code = <span style="color:#d20;background-color:#fff0f0">'CN'</span> <span style="color:#080;font-weight:bold">if</span> country_code == <span style="color:#d20;background-color:#fff0f0">'CA'</span> <span style="color:#888"># Estes uses CN for Canada</span>
postal_code = package.order.ship_address.zipcode
postal_code =
<span style="color:#080;font-weight:bold">if</span> country_code == <span style="color:#d20;background-color:#fff0f0">'CN'</span>
postal_code.delete(<span style="color:#d20;background-color:#fff0f0">' '</span>).first(<span style="color:#00d;font-weight:bold">6</span>)
<span style="color:#080;font-weight:bold">else</span>
postal_code.first(<span style="color:#00d;font-weight:bold">5</span>)
<span style="color:#080;font-weight:bold">end</span>
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-xml" data-lang="xml"> xml = <span style="color:#a61717;background-color:#e3d2d2"><<</span>~XML
<span style="color:#b06;font-weight:bold"><soapenv:Envelope</span>
<span style="color:#369">xmlns:soapenv=</span><span style="color:#d20;background-color:#fff0f0">"http://schemas.xmlsoap.org/soap/envelope/"</span>
<span style="color:#369">xmlns:rat=</span><span style="color:#d20;background-color:#fff0f0">"http://ws.estesexpress.com/ratequote"</span>
<span style="color:#369">xmlns:rat1=</span><span style="color:#d20;background-color:#fff0f0">"http://ws.estesexpress.com/schema/2019/01/ratequote"</span><span style="color:#b06;font-weight:bold">></span>
<span style="color:#b06;font-weight:bold"><soapenv:Header></span>
<span style="color:#b06;font-weight:bold"><rat:auth></span>
<span style="color:#b06;font-weight:bold"><rat:user></span>#{ENV['ESTES_USER']}<span style="color:#b06;font-weight:bold"></rat:user></span>
<span style="color:#b06;font-weight:bold"><rat:password></span>#{ENV['ESTES_PASSWORD']}<span style="color:#b06;font-weight:bold"></rat:password></span>
<span style="color:#b06;font-weight:bold"></rat:auth></span>
<span style="color:#b06;font-weight:bold"></soapenv:Header></span>
<span style="color:#b06;font-weight:bold"><soapenv:Body></span>
<span style="color:#b06;font-weight:bold"><rat1:rateRequest></span>
<span style="color:#b06;font-weight:bold"><rat1:requestID></span>#{package.order.number}<span style="color:#b06;font-weight:bold"></rat1:requestID></span>
<span style="color:#b06;font-weight:bold"><rat1:account></span>#{ENV['ESTES_ACCOUNT']}<span style="color:#b06;font-weight:bold"></rat1:account></span>
<span style="color:#b06;font-weight:bold"><rat1:originPoint></span>
<span style="color:#b06;font-weight:bold"><rat1:countryCode></span>US<span style="color:#b06;font-weight:bold"></rat1:countryCode></span>
<span style="color:#b06;font-weight:bold"><rat1:postalCode></span>10001<span style="color:#b06;font-weight:bold"></rat1:postalCode></span>
<span style="color:#b06;font-weight:bold"></rat1:originPoint></span>
<span style="color:#b06;font-weight:bold"><rat1:destinationPoint></span>
<span style="color:#b06;font-weight:bold"><rat1:countryCode></span>#{country_code}<span style="color:#b06;font-weight:bold"></rat1:countryCode></span>
<span style="color:#b06;font-weight:bold"><rat1:postalCode></span>#{postal_code}<span style="color:#b06;font-weight:bold"></rat1:postalCode></span>
<span style="color:#b06;font-weight:bold"></rat1:destinationPoint></span>
<span style="color:#b06;font-weight:bold"><rat1:payor></span>S<span style="color:#b06;font-weight:bold"></rat1:payor></span>
<span style="color:#b06;font-weight:bold"><rat1:terms></span>C<span style="color:#b06;font-weight:bold"></rat1:terms></span>
<span style="color:#b06;font-weight:bold"><rat1:baseCommodities></span>
<span style="color:#b06;font-weight:bold"><rat1:commodity></span>
<span style="color:#b06;font-weight:bold"><rat1:class></span>50<span style="color:#b06;font-weight:bold"></rat1:class></span>
<span style="color:#b06;font-weight:bold"><rat1:weight></span>#{package_weight(package)}<span style="color:#b06;font-weight:bold"></rat1:weight></span>
<span style="color:#b06;font-weight:bold"></rat1:commodity></span>
<span style="color:#b06;font-weight:bold"></rat1:baseCommodities></span>
<span style="color:#b06;font-weight:bold"></rat1:rateRequest></span>
<span style="color:#b06;font-weight:bold"></soapenv:Body></span>
<span style="color:#b06;font-weight:bold"></soapenv:Envelope></span>
XML
</code></pre></div><div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"> response = client.call(<span style="color:#a60;background-color:#fff0f0">:get_quote</span>, <span style="color:#a60;background-color:#fff0f0">xml</span>: xml)
quotes = response.body.dig(<span style="color:#a60;background-color:#fff0f0">:rate_quote</span>, <span style="color:#a60;background-color:#fff0f0">:quote_info</span>, <span style="color:#a60;background-color:#fff0f0">:quote</span>)
<span style="color:#080;font-weight:bold">if</span> quotes.is_a?(<span style="color:#038">Array</span>)
quotes.first.dig(<span style="color:#a60;background-color:#fff0f0">:pricing</span>, <span style="color:#a60;background-color:#fff0f0">:total_price</span>).to_f
<span style="color:#080;font-weight:bold">elsif</span> quotes.is_a?(<span style="color:#036;font-weight:bold">Hash</span>)
quotes.dig(<span style="color:#a60;background-color:#fff0f0">:pricing</span>, <span style="color:#a60;background-color:#fff0f0">:total_price</span>).to_f
<span style="color:#080;font-weight:bold">else</span>
<span style="color:#00d;font-weight:bold">0</span>
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080;font-weight:bold">rescue</span> <span style="color:#036;font-weight:bold">Savon</span>::<span style="color:#036;font-weight:bold">Error</span>
<span style="color:#888"># Record shipping rate as 0 if an API error is caught, 0 amount will indicate need to show user an error message on the shipping rate page</span>
<span style="color:#00d;font-weight:bold">0</span>
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080">private</span>
<span style="color:#080;font-weight:bold">def</span> <span style="color:#06b;font-weight:bold">package_weight</span>(package)
weight = package.contents.sum { |content| content.line_item.weight }
<span style="color:#080;font-weight:bold">if</span> weight < <span style="color:#00d;font-weight:bold">5</span> <span style="color:#888"># enforce minimum package weight</span>
<span style="color:#00d;font-weight:bold">5</span>
<span style="color:#080;font-weight:bold">else</span>
weight.round
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080;font-weight:bold">end</span>
<span style="color:#080;font-weight:bold">end</span>
</code></pre></div><h3 id="conclusion">Conclusion</h3>
<p>I was pleased that I was able to quickly build a custom shipping calculator in Spree that used the Estes Rate Quote API to provide accurate shipping estimates for large freight packages. The high quality documentation of the Estes API and the Savon SOAP Client gem made for a pleasant development experience, and the client was happy to gain the new functionality for their Spree store.</p>
Postal code pain and funhttps://www.endpointdev.com/blog/2017/05/postal-code-pain-and-fun/2017-05-10T00:00:00+00:00Jon Jensen
<p><img align="right" src="/blog/2017/05/postal-code-pain-and-fun/image-0.jpeg" style="margin: 1em" width="200"/>We do a lot of ecommerce development at End Point. You know the usual flow as a customer: Select products, add to the shopping cart, then check out. Checkout asks questions about the buyer, payment, and delivery, at least. Some online sales are for “soft goods”, downloadable items that don’t require a delivery address. Much of online sales are still for physical goods to be delivered to an address. For that, a postal code or zip code is usually required.</p>
<h3 id="no-postal-code">No postal code?</h3>
<p>I say <em>usually</em> because there are some countries that do not use postal codes at all. An ecommerce site that expects to ship products to buyers in one of those countries needs to allow for an empty postal code at checkout time. Otherwise, customers may leave thinking they aren’t welcome there. The more creative among them will make up something to put in there, such as “00000” or “99999” or “NONE”.</p>
<p>Someone has helpfully assembled and maintains a machine-readable (in Ruby, easily convertible to JSON or other formats) <a href="https://web.archive.org/web/20201128190754/https://gist.github.com/kennwilson/3902548">list of the countries that don’t require a postal code</a>. You may be surprised to see on the list such countries as Hong Kong, Ireland, Panama, Saudi Arabia, and South Africa. Some countries on the list actually do have postal codes but do not require them or commonly use them.</p>
<h3 id="do-you-really-need-the-customers-address">Do you really need the customer’s address?</h3>
<p><img align="right" src="/blog/2017/05/postal-code-pain-and-fun/image-1.jpeg" style="margin: 1em" width="200"/>When selling both downloadable and shipped products, it would be nice to not bother asking the customer for an address at all. Unfortunately even when there is no shipping address because there’s nothing to ship, the billing address is still needed if payment is made by credit card through a normal credit card payment gateway—as opposed to PayPal, Amazon Pay, Venmo, Bitcoin, or other alternative payment methods.</p>
<p>The credit card <a href="https://en.wikipedia.org/wiki/Address_Verification_System">Address Verification System (AVS)</a> allows merchants to ask a credit card issuing bank whether the mailing address provided matches the address on file for that credit card. Normally only two parts are checked: (1) the street address numeric part, for example, “123” if “123 Main St.” was provided; (2) the zip or postal code, normally only the first 5 digits for US zip codes, and often non-US postal code AVS doesn’t work at all with non-US banks.</p>
<p>Before sending the address to AVS, validating the <em>format</em> of postal codes is simple for many countries: 5 digits in the US (allowing an optional <em>-nnnn</em> for ZIP+4), and 4 or 5 digits in most others countries—see the Wikipedia <a href="https://en.wikipedia.org/wiki/List_of_postal_codes">List of postal codes</a> in various countries for a high-level view. Canada is slightly more complicated: 6 characters total, alternating a letter followed by a number, formally with a space in the middle, like K1A 0B1 as explained in Wikipedia’s <a href="https://en.wikipedia.org/wiki/Postal_codes_in_Canada#Components_of_a_postal_code">components of a Canadian postal code</a>.</p>
<p>So most countries’ postal codes can be validated in software with simple regular expressions, to catch typos such as transpositions and missing or extra characters.</p>
<h3 id="uk-postcodes">UK postcodes</h3>
<p><img align="right" src="/blog/2017/05/postal-code-pain-and-fun/image-2.jpeg" style="margin: 1em" width="200"/>The most complicated postal codes I have worked with is the United Kingdom’s, because they can be from 5 to 7 characters, with an unpredictable mix of letters and numbers, normally formatted with a space in the middle. The benefit they bring is that they encode a lot of detail about the address, and it’s possible to catch transposed character errors that would be missed in a purely numeric postal code. The Wikipedia article <a href="https://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom">Postcodes in the United Kingdom</a> has the gory details.</p>
<p>It is common to use a regular expression to validate UK postcodes in software, and many of these regexes are to some degree wrong. Most let through many invalid postcodes, and some disallow valid codes.</p>
<p>We recently had a client get a customer report of a valid UK postcode being rejected during checkout on their ecommerce site. The validation code was using a regex that is widely copied in software in the wild:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">[A-PR-UWYZ0-9][A-HK-Y0-9][AEHMNPRTVXY0-9]?[ABEHMNPRVWXY0-9]?[0-9][ABD-HJLN-UW-Z]{2}
</code></pre></div><p>(This example removes support for the odd exception GIR 0AA for simplicity’s sake.)</p>
<p>The customer’s valid postcode that doesn’t pass that test was W1F 0DP, in London, which the Royal Mail website confirms is valid. The problem is that the regex above doesn’t allow for F in the third position, as that was not valid at the time the regex was written.</p>
<p>This is one problem with being too strict in validations of this sort: The rules change over time, usually to allow things that once were not allowed. Reusable, maintained software libraries that specialize in UK postal codes can keep up, but there is always lag time between when updates are released and when they’re incorporated into production software. And copied or customized regexes will likely stay the way they are until someone runs into a problem.</p>
<p>The ecommerce site in question is running on the <a href="http://www.icdevgroup.org/">Interchange</a> ecommerce platform, which is based on Perl, so the most natural place to look for an updated validation routine is on CPAN, the Perl network of open source library code. There we find the nice module <a href="https://metacpan.org/pod/Geo::UK::Postcode">Geo::UK::Postcode</a> which has a more current validation routine and a nice interface. It also has a function to format a UK postcode in the canonical way, capitalized (easy) and with the space in the correct place (less easy).</p>
<p>It also presents us with a new decision: Should we use the basic “valid” test, or the “strict” one? This is where it gets a little trickier. The “valid” check uses a regex validation approach will still let through some invalid postcodes, because it doesn’t know what all the current valid delivery destinations are. This module has a “strict” check that uses a <a href="https://github.com/mjemmeson/Geo-UK-Postcode-Regex/blob/master/lib/Geo/UK/Postcode/Regex.pm#L664-L3652">comprehensive list of all the “outcode” data</a>—which as you can see if you look at that source code, is extensive.</p>
<p>The bulkiness of that list, and its short shelf life—the likelihood that it will become outdated and reject a future valid postcode—makes strict validation checks like this of questionable value for basic ecommerce needs. Often it is better to let a few invalid postcodes through now so that future valid ones will also be allowed.</p>
<p>The ecommerce site I mentioned also does in-browser validation via JavaScript before ever submitting the order to the server. Loading a huge list of valid outcodes would waste a lot of bandwidth and slow down checkout loading, especially on mobile devices. So a more lax regex check there is a good choice.</p>
<h3 id="when-christmas-comes">When Christmas comes</h3>
<p>There’s no Christmas gift of a single UK postal code validation solution for all needs, but there are some fun trivia notes in the Wikipedia page covering <a href="https://en.wikipedia.org/wiki/Postal_code#Non-geographic_codes">Non-geographic postal codes</a>:</p>
<blockquote>
<p>A fictional address is used by UK Royal Mail for letters to Santa Claus:</p>
<p>Santa’s Grotto<br>
Reindeerland XM4 5HQ</p>
<p>Previously, the postcode SAN TA1 was used.</p>
<p>In Finland the special postal code 99999 is for Korvatunturi, the place where Santa Claus (Joulupukki in Finnish) is said to live, although mail is delivered to the Santa Claus Village in Rovaniemi.</p>
<p>In Canada the amount of mail sent to Santa Claus increased every Christmas, up to the point that Canada Post decided to start an official Santa Claus letter-response program in 1983. Approximately one million letters come in to Santa Claus each Christmas, including from outside of Canada, and they are answered in the same languages in which they are written. Canada Post introduced a special address for mail to Santa Claus, complete with its own postal code:</p>
<p>SANTA CLAUS<br>
NORTH POLE H0H 0H0</p>
<p>In Belgium bpost sends a small present to children who have written a letter to Sinterklaas. They can use the non-geographic postal code 0612, which refers to the date Sinterklaas is celebrated (6 December), although a fictional town, street and house number are also used. In Dutch, the address is:</p>
<p>Sinterklaas<br>
Spanjestraat 1<br>
0612 Hemel</p>
<p>This translates as “1 Spain Street, 0612 Heaven”. In French, the street is called “Paradise Street”:</p>
<p>Saint-Nicolas<br>
Rue du Paradis 1<br>
0612 Ciel</p>
</blockquote>
<p>That UK postcode for Santa doesn’t validate in some of the regexes, but the simpler Finnish, Canadian, and Belgian ones do, so if you want to order something online for Santa, you may want to choose one of those countries for delivery. :)</p>
Spree Active Shipping Gem “We are unable to calculate shipping rates for the selected items.” Errorhttps://www.endpointdev.com/blog/2014/02/spree-active-shipping-gem-we-are-unable/2014-02-12T00:00:00+00:00Matt Galvin
<p>I was recently working on a <a href="http://spreecommerce.org/">Spree</a> site and setting up the <a href="https://github.com/spree/spree_active_shipping">Spree Active Shipping Gem</a> along with the UPS API. For those not familiar, the Spree Active Shipping Gem interacts with various shipping APIs like UPS, USPS, and FedEx. Due to the nature of Spree—where it does so much for you, and the interaction between the Active Shipping Gem and a shipping API also being “auto-magic”, it is often difficult to debug. As I was recently undertaking the task of setting this up I found a few “gotchas” that I hope, through this blog post, may be able to save others a lot of time.</p>
<p>I have found that there wasn’t a lot of instruction for setting up the Active Shipping Gem and a shipping carrier API like the UPS Shipping API. Ostensibly, there isn’t much to it—the Active Shipping Gem handles much of the interaction between the shipping API of choice and Spree.</p>
<p>First, you’re going to go the <a href="https://github.com/spree/spree_active_shipping">Spree Active Shipping Gem</a> GitHub repo and follow the instructions for installing the Active Shipping Gem. It is very straightforward, but do proceed in the order mentioned in the Spree Active Shipping Gem documentation as some steps depend on the successful completion of others.</p>
<p>Second, you’re going to go to the shipper of your choice, in this case UPS, and follow their directions for using their <a href="https://web.archive.org/web/20160101071458/http://www.ups.com/content/us/en/bussol/browse/cat/developer_kit.html">API</a>. I do recommend actually reading, or at least skimming, the pages and pages of documentation. Why? Because there are some important agreements explaining how the API is to be used (basically legal requirements for the UPS logo).</p>
<p>The Active Shipping Gem makes a call to the API, the API returns a hash of various shipping methods and prices based on the parameters you’ve sent it (such as shipment origin and destination), and then it automatically displays in the UI as an adjustment. How great is that?!</p>
<p>Well, it would be great if it all worked out exactly as planned. However, if you are running Spree 2-0-stable you may find yourself battling an unusual circumstance. Namely, Spree 2-0-stable will create your core/app/views/spree/checkout/edit.html.erb as</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><<span style="color:#2b2;background-color:#f0fff0">% content_for </span> <span style="color:#a60;background-color:#fff0f0">:head</span> <span style="color:#080;font-weight:bold">do</span> <span style="color:#d20;background-color:#fff0f0">%>
</span><span style="color:#d20;background-color:#fff0f0"> <%= javascript_include_tag '/states' %></span>
<<span style="color:#2b2;background-color:#f0fff0">% end </span> %>
</code></pre></div><p>This will provide the incorrect path. It is intended to hit the StatesController, so update it like so:</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-ruby" data-lang="ruby"><<span style="color:#2b2;background-color:#f0fff0">% content_for </span> <span style="color:#a60;background-color:#fff0f0">:head</span> <span style="color:#080;font-weight:bold">do</span> <span style="color:#d20;background-color:#fff0f0">%>
</span><span style="color:#d20;background-color:#fff0f0"> <%= javascript_include_tag states_url %></span>
<<span style="color:#2b2;background-color:#f0fff0">% end </span> %>
</code></pre></div><p>Now, once this correction has been made you may find that you are still having an error, “We are unable to calculate shipping rates for the selected items.”</p>
<p><a href="/blog/2014/02/spree-active-shipping-gem-we-are-unable/image-0.png" imageanchor="1"><img border="0" src="/blog/2014/02/spree-active-shipping-gem-we-are-unable/image-0.png"/></a></p>
<p>At this point Chrome Dev Tools will not show any errors. When I had this error, a number of Google searches returned results of the kind “make sure you have set up your shipping zones correctly and added shipping methods to these zones”. I verified this again and again in the console, as did many others who were equally perplexed by this message on <a href="http://stackoverflow.com/questions/18277367/spree-commerce-error-on-checkout-we-are-unable-to-ship-the-selected-items-to-y">StackOverflow</a> as well as in Google Groups, like <a href="https://groups.google.com/forum/#!msg/spree-user/aCJz5iNemfo/3v4uJ8hPBVsJ">here</a> and <a href="https://groups.google.com/forum/#!topic/spree-user/aCJz5iNemfo">here</a>. Some got this error when they added an item to the cart that had a count_on_hand of 0 and backorderable equal to false, like you can see here at <a href="https://github.com/spree/spree/issues/3521">Spree GitHub issues</a>. If a 0 count_on_hand is what is giving you this error, but you want a product to be backorderable, make sure to also check the “Propagate All Variants” in the Spree admin as seen below. This will loop through all of the product’s variants with a count_on_hand of 0, and allow them to be backorderable.</p>
<p><a href="/blog/2014/02/spree-active-shipping-gem-we-are-unable/image-1-big.png" imageanchor="1"><img border="0" src="/blog/2014/02/spree-active-shipping-gem-we-are-unable/image-1.png"/></a></p>
<p>After a long while of searching and wondering, is it the API? Is it the Active Shipping Gem? Is it a blacklisted zip code? I went through and changed one setting at a time in the Spree admin until finally arriving at the source of this error for me. Missing product weight. Because UPS needs the product weight in order to calculate shipping charges, make sure this is set.</p>
<p>The “We are unable to calculate shipping rates for the selected items” error message is misleading. If you encounter this error after correcting the javascript_include_tag, the cause is most likely a setting in the admin. Check for how insufficient inventory is handled, missing product weights, or incorrectly setup up or non-existent shipping zones & associated methods. I hope if this error message is what brought you here that this post has saved you some time.</p>
Integrating UPS Worldship - Pick and Packhttps://www.endpointdev.com/blog/2012/05/integrating-ups-worldship-pick-and-pack/2012-05-29T00:00:00+00:00Terry Grant
<h3 id="using-ups-worldship-to-automate-a-pick-and-pack-scenario">Using UPS WorldShip to automate a pick and pack scenario</h3>
<p>There are many options when selecting an application to handle your shipping needs. Typically you will be bound to one of the popular shipping services; UPS, FedEx, or USPS or a combination thereof. In my experience UPS Worldship offers a very robust shipping application that is dynamic enough to accommodate integration with just about any custom or out of the box ecommerce system.</p>
<p>UPS Worldship offers many automating features by allowing you to integrate in many different ways. The two main automated features consist of batch label printing and individual label printing. I would like to cover my favorite way of using UPS Worldship that allows you to import and export data seamlessly.</p>
<p>You should choose the solution that works best for you and your shipping procedure. In this blog post I would like to discuss a common warehouse scenario refereed to as <a href="https://en.wikipedia.org/wiki/Pick_and_pack">Pick And Pack</a>. The basic idea of this scenario is an order is selected for a warehouse personnel to fulfill, it is then picked, packed, and shipped. UPS Worldship allows you to do this in a very automated way with a bit of customization. This is a great solution for a small to medium sized business that wants to automate their shipping process and communicate tracking information with their customers.</p>
<h3 id="overall-breakdown-of-process">Overall Breakdown of Process</h3>
<p>There are a few steps involved in integrating your system with UPS Worldship. To get started I have listed the high-level breakdown of the process. I mention a few tables in this example, that I will explain in detail in the next section.</p>
<div class="separator" style="clear: both; text-align: center;"><a href="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-0-big.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"><img border="0" height="400" src="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-0.png" width="318"/></a></div>
<ol>
<li>An order is placed on your website and saved into your database. For the sake of this example this will be the ‘orders’ table</li>
<li>A warehouse worker can then print out a packing slip that contains a barcode</li>
<li>Worldship then grabs the order information from the ‘orders’ table and inserts the shipping information in the the mapped fields</li>
<li>The Worldship operator then presses ‘F7’ which will process the shipment with UPS, thus retrieving a tracking number</li>
<li>Worldship marks the order as shipped and inserts the tracking number into a place holder tracking table</li>
<li>Worldship prints an active shipping label for your customer’s order</li>
</ol>
<h3 id="setting-up-your-data-structure">Setting Up Your Data Structure</h3>
<p>In order to integrate Worldship seamlessly you will need to make a few database modifications. I have decided to use two tables ‘orders’ and ‘ups_order_tracking’. The ‘orders’ table represents a standard table that contains the shipping information for an order. The ‘ups_order_tracking’ table is used to hold an order number and a tracking number. The order number, of course, refers to the unique order number in the ‘orders’ table. Every system is different, but this is a simple way that worked for me in the past. You will most likely need to make a few modifications to suit the needs of your data model and environment. I have included an example that will show what you will need at the very least.</p>
<p><strong>‘orders’</strong></p>
<p>This is the bare information needed by Worldship in order to fill in the shipping information for a package. I have added two other columns ‘tracking’, and ‘tracking_sent’. The ‘tracking’ column will hold the tracking number for this order. The ‘tracking_sent’ is a boolean that will keep track of our tracking number emails discussed later in this post.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">Column | Type |
-------------------------+-----------------------+
id | integer |
order_status_id | integer |
ship_to_name | character varying |
ship_to_address | character varying |
ship_to_address2 | character varying |
ship_to_city | character varying |
ship_to_state_code | character varying |
ship_to_province | character varying |
ship_to_zip | character varying |
tracking | character varying |
tracking_sent | boolean |
</code></pre></div><p><strong>‘ups_order_tracking’</strong></p>
<p>This table acts as a temporary holding table for the tracking number for an individual order. I have found that it is much easier to have Worldship insert rows to a table and have a trigger copy the information to the ‘orders’ table (or something similar depending on your database), as opposed to updating a table. Since this is the case we smiply need to create a trigger that will updated the ‘orders’ table when a row is inserted into ‘ups_order_tracking’.</p>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">Column | Type |
--------------+-----------------------+
order_id | integer |
tracking | character varying |
</code></pre></div><h3 id="setting-up-triggers">Setting Up Triggers</h3>
<p>This article is written with Postgres used as the database. You will need to make the appropriate adjustments for your environment. The ‘ups_order_tracking’ table will need a simple trigger that is responsible for the following:</p>
<ul>
<li>Updating the ‘order.order_status_id’ column with a shipped flag (in this example 2 means it has been shipped)</li>
<li>Updating ‘order.tracking’ with the tracking number supplied by Worldship</li>
</ul>
<div class="highlight"><pre tabindex="0" style="background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-plain" data-lang="plain">BEGIN
:
: UPDATE order
: SET order_status_id = 2 WHERE id = NEW.order_id;
: UPDATE order
: SET tracking = NEW.tracking WHERE id = NEW.order_id;
: RETURN NEW;
END;
</code></pre></div><p>After you have configured your database you are now ready to setup and integrate Worldship.</p>
<h3 id="integrating-ups-worldship">Integrating UPS Worldship</h3>
<p>UPS Worldship offers many ways to import shipment data. Since this article is about automating a pick and pack scenario, I am only going to cover how to use the Connection Assistant to import and export data from your database.</p>
<p>You will need to install the appropriate ODBC driver and setup access to your database before starting this step. RazorSQL.com has a decent explanation to get you started: <a href="https://www.razorsql.com/docs/odbc_setup.html">ODBC Setup</a>. Once you have this setup and connecting to your database you can continue to the ‘Importing Data’ section below.</p>
<p><strong>Importing Data</strong></p>
<p>Follow these steps below and reference the starting on Page 10: <a href="https://www.ups.com/media/en/Importing_Shipment_Data.pdf">UPS Importing Shipment Instructions</a>.</p>
<p>Please note the following steps:</p>
<ul>
<li>Step 4: make sure you select ‘By Known ODBC Source’ and select your installed ODBC driver you setup previously</li>
</ul>
<div class="separator" style="clear: both; text-align: center;"><a href="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-1-big.png" imageanchor="1" style="clear:right; float:right; margin-left:1em; margin-bottom:1em"><img border="0" height="230" src="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-1.png" width="320"/></a></div>
<ul>
<li>
<p>Step 8 (part 1): you want to select the ‘orders’ table (or whatever your table is called) and map the appropriate shipment information to the Worldship fields on the right</p>
</li>
<li>
<p>Step 8 (part 2): When mapping your data from your orders table make sure you set the Reference ID field to the order number. This allows you to 1) Use the order number later when exporting your order data and 2) You can then search UPS by your tracking number OR your Reference ID which is also your order number (very convenient if a tracking number is lost!)</p>
</li>
<li>
<p>Step 12: If you have custom shipping options that is predetermined make sure you map these as seen in Step 12</p>
</li>
<li>
<p>Name your map something meaningful like ‘Shipment Import’</p>
</li>
<li>
<p>Step 20: Make sure you select your newly named import map under Keyed Import as this is how Worldship knows to use your ODBC driver and map to import your shipping data</p>
</li>
</ul>
<div class="separator" style="clear: both; text-align: center;"><a href="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-2-big.png" imageanchor="1" style=""><img border="0" height="261" src="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-2.png" width="320"/></a></div>
<p><strong>Exporting Data</strong></p>
<p>Follow these steps below and reference the starting on Page 1 <a href="https://www.ups.com/media/en/Exporting_Shipment_Data.pdf">UPS Exporting Shipment Data Instructions</a>.
Please note the following steps:</p>
<ul>
<li>
<p>Skip to Page 8: ‘Export Shipment Data using Connection Assistant’ since we want to automatically update our ‘ups_order_tracking’ table after a label is processed</p>
</li>
<li>
<p>Step 8: Make sure you map the tracking number and order number to the ‘ups_order_tracking’ table.</p>
</li>
<li>
<p>Name your map something meaningful like ‘Shipment Export’</p>
</li>
<li>
<p>Step 12: You can either configure Worldship to update your ‘ups_order_tracking’ table at the ‘End of the Day’ or ‘After processing Shipment’. I prefer to have Worldship update my ‘ups_order_tracking’ after each label is printed so the data is immediately available in the database.</p>
</li>
</ul>
<div class="separator" style="clear: both; text-align: center;"><a href="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-3-big.png" imageanchor="1" style=""><img border="0" height="261" src="/blog/2012/05/integrating-ups-worldship-pick-and-pack/image-3.png" width="320"/></a></div>
<h3 id="processing-an-order">Processing an Order</h3>
<p>Now that you have setup Worldship to interface with your database by creating maps you can use the ‘Keyed Import’ functionality to start processing packages. After you have selected your map under ‘Keyed Import’ you will see a small dialog box that is waiting for input.</p>
<p><strong>Scanner</strong></p>
<p>In my experience the fastest way to pull shipment data is by using a scanner that can scan the barcode on your packing slip. This barcode is the encoded order number that is referenced by your ‘orders’ table and used by the maps you created to retrieve the appropriate data. Most scanners can be configured to supply the key after a successful scan has occurred. NOTE: You must make sure the ‘Keyed Import’ box has focus and is waiting import. The basic process is as follows:</p>
<ul>
<li>An order is printed with a barcode (I was able to make use of <a href="https://www.barcodebakery.com/en">PHP Barcode Generator</a> to generate my barcodes on the packing slip). You will need to find something that suites your needs if you want to make use of barcodes.</li>
<li>User either scans an order or enters the order number in manually into the Keyed Import input.</li>
<li>Worldship then pulls the order data is pulled from the database and inserted into the proper Worldship fields.</li>
<li>The Worldship operator then presses the ‘F7’ key to process the shipment.</li>
<li>Worldship then inserts the order_id and tracking number into ups_order_tracking tabl.e</li>
<li>The ups_order_tracking table’s trigger is executed and updates the ‘orders’ table (or whatever is needed for your data model).</li>
<li>Worldship prints out the shipping label and runs the action you selected to run after a shipment is processed, or at the end of day.</li>
<li>Your order is now marked as shipped, updated with a tracking number, and you have a package ready to be picked up by UPS.</li>
</ul>
<p>This might be enough for your needs, but I like to send an email to the customer letting them know their order has shipped and giving them a UPS tracking number to track their package.</p>
<h3 id="email-the-customer-a-tracking-number">Email the Customer a Tracking Number</h3>
<p>UPS Worldship does offer a feature that will send an email with a tracking number after the label is printed. This might be enough for some people, but it does not offer anything in the way of customizing the communication. Most businesses prefer branded emails with custom information in all communication sent to their customers. As such, I integrated a small feature that sends a custom and branded email to customers. This email includes their tracking number with a link to the UPS tracking page along with a friendly message letting them know their order is on the way.</p>
<p>Remember the order.tracking_sent boolean mentioned earlier ? This is where that field will come in handy. I wrote a small Perl script that runs every few hours. The script queries the ‘orders’ table and looks for:</p>
<ul>
<li>
<p>order.order_status_id = 2 (The order has been set to shipped)</p>
</li>
<li>
<p>order.tracking_set IS NULL (A tracking email has not been sent)</p>
</li>
</ul>
<p>After it pulls a list of all of the orders that have been marked as shipped AND have not had a tracking email sent, it pulls the tracking number and fires off an email to the customer with the tracking number. The script then sets ‘order.tracking_sent’ to TRUE so the next time the script runs it does not resend the tracking number to the customer. This is of course a very custom feature specific to this database. I am sure you would want to customize this to your needs. I thought it was worth mentioning as customers really like confirmation that 1) Their order was placed and 2) Their order has shipped (with a means of tracking its progress).</p>
<h3 id="final-thoughts">Final Thoughts</h3>
<p>As mentioned initially UPS Worldship offers many ways of integrating into your environment and offers many customizations. UPS offers great support if you simply contact your UPS representative they can put you in touch with a developer that can answer any question you have. I believe that for a Pick and Pack scenario using a packing slip with a barcode, a scanner, and a properly configured Worldship application you can streamline a small to medium sized warehouse environment. Unfortunately there are not many affordable solutions for small to medium sized e-commerce businesses, but UPS Worldship does a great job trying to fill that need and automate your shipping and communication needs.</p>
USPS changes the Web Tools Rate Calculator APIhttps://www.endpointdev.com/blog/2007/05/usps-changes-web-tools-rate-calculator/2007-05-14T00:00:00+00:00Dan Collis-Puro
<p>End Point offers integration with online shipping APIs to provide “live lookups” of rates.</p>
<p>Advantages of “live lookups”:</p>
<ul>
<li>Current rates</li>
<li>Includes additional costs such as fuel surcharges</li>
<li>No manual maintenance of rate tables</li>
</ul>
<p>Disadvantages of “live lookups”:</p>
<ul>
<li>Dependent on the availability and performance of the rate service</li>
<li>Planning, programming and rolling out API changes</li>
</ul>
<p><strong>CH CH CH CH CHANGES!</strong></p>
<p>Speaking of changes, the USPS has changed shipping rates as of May 14, 2007 (non-tech-friendly details <a href="http://www.usps.com/ratecase/">here</a>). The changes include updates to rates, package attributes and shipping methods. These changes impact the XML-based Web Tools Rate Calculator, in some cases breaking lookups altogether. As of press time, the USPS hasn’t documented the changes to the API. Broken lookups appear to be confined mostly to international shipping.</p>
<p>Many of the changes represent a simplification and restructuring of international shipping methods, detailed <a href="http://www.usps.com/ratecase/simplified_international_rates.htm">here</a>. This tweaking of international shipping methods is definitely an improvement — there were too many confusing options before. Unfortunately, these tweaks aren’t backwards compatible — meaning nearly all old-style international API lookups are broken. The USPS still (as of press time) hasn’t documented technical changes to the API.</p>
<p><strong>TECHIE BACKGROUND - OR “HOW THINGS GOT BROKEN”</strong></p>
<p>When you send an international rate request, the USPS API returns a “response” XML packet that includes the available shipping methods, e.g. “EXPRESS MAIL INTERNATIONAL (EMS)”. Most of these shipping method names have changed in the response, leaving rate calculation code developed against the last released API in a broken state — you can’t match a requested rate to a specific response.</p>
<p>Fortunately, the USPS has provided an undocumented staging API for customer testing, so we’ve been able to deduce most of the changes to the shipping method names through a battery of tests. Unfortunately, until the USPS releases new documentation we’re left with an educated guess as to how to fix broken lookups.</p>
<p><strong>CONCLUSION</strong></p>
<p>The changes to international shipping methods are refreshing. However, the USPS release of an undocumented API into production for all customers has left developers guessing, especially since those changes aren’t backwards-compatible.</p>
<p><strong>SELECTED TIMELINE OF API CHANGES</strong></p>
<ul>
<li>March 21, 2007. USPS notifies users that the Rate Calculator API will be changing</li>
<li>April 26, 2007. USPS notifies users that they can use the staging API for testing … on April 19.</li>
<li>May 14, 2007. USPS switches to the still undocumented production API, breaking most international rate calculation lookups in the process.</li>
</ul>