Just a heads up that this is a fairly long article and makes several assumptions about your application. Using the UPS API is fairly easy, but there are several variables that are required for the api to work successfully.
I’ll be making several assumptions here. First and foremost that you are looking to implement the UPS shipping and rating API for ecommerce. Second that you have a database set up with a table named ’shipping_tbl’ that includes column names ‘package_weight’, ‘package_width’, ‘package_length’, and ‘package_height’. And that these values are in some way related to the products whose dimensions and weight they represent. As well as other assumptions like how we store our cart data that will be further explained later.
Let’s start out with our HTML. This is the HTML that represents your cart, which is where I assume you are showing the shipping rate for whatever packages are being sold. We’ll pretend there’s a cart here. I’m, however, only going to show the code for the output of the shipping rate. So it is as follows.
The HTML (Output of shipping data)
<!---set our shipping rate---> <cfset VARIABLES.shippingrate = ""> <span class=”shippingrate”> <!---output our shipping rate to the page---> <cfoutput>#dollarformat(VARIABLES.shippingrate)#</cfoutput> </span>
Pretty simple. I’ve set the shipping rate into a var called VARIABLES.shippingrate. For now I’ve just set it to an empty string, so obviously it’s not going to work. Later, once we have looked at how to get and return our actual shipping rate from UPS we’ll set that value into our var. Then we output our shipping rate inside a set of span tags. These tags aren’t really necessary for this to work, but if your giving any type of special style to your rate it may be worth using. We output our shipping rate using Coldfusion’s dollarformat function. This will give it a dollar sign and two decimal places.
Before moving forward, let’s take a look at what it is that UPS expects to receive from us. First of all, UPS expects the data to be packaged in an XML document. Lucky for us Coldfusion makes working with XML very quick and easy.
UPS expects two parts to our XML document. The first part is pretty standard XML that sets the security and account validation data. This XML can be set inside of a .xml file and concatenated to the rest of the XML document when it is time to produce the document as a whole. So let’s look at our XML file.
XML Authentication (ups_authentication.xml)
<?xml version="1.0"?> <AccessRequest xml:lang="en-US"> <cfoutput> <AccessLicenseNumber>#xmlFormat(APPLICATION.accesskey)#</AccessLicenseNumber> <UserId>#xmlFormat(APPLICATION.id)#</UserId> <Password>#xmlFormat(APPLICATION.upspassword)#</Password> </cfoutput> </AccessRequest>
Despite the fact that this is an XML document we are using some Coldfusion code here. The reason for this is that in a component we will build in a moment this XML data is combined with more dynamically created XML. When that happens inside of the component Coldfusion will read our data correctly and replace our variables with the actual data. On this note, it is apparent that for security UPS is requiring our access key, user id, and our password before passing back any shipping rates to us. I’ve stored my values inside of my Application.cfc file in the APPLICATION scope. This is for security purposes.
The next step in our process is to write a Coldfusion component that will handle all of our interaction with UPS. We really need to do three things to get back a shipping rate from UPS. First we need to get the shipping data for the product we need to get a rate for, we need to store that data in XML format to be sent to UPS, and lastly, we need to send it to UPS. Once the data is sent, UPS will send back an XML document that contains all of our rate information.
Our component is broken down much like our goals to accomplish. First is the one and only ‘public’ function called ‘initializeShipping()’. This function manages the three remaining ‘private’ functions. These are ‘getShippingData()’, ‘createXml()’, and ’submitXml()’. Let’s start our by looking at our initializeShipping() function.
initializeShipping() (shipping.cfc)
<!--- ***************************** INITIALIZE SHIPPING ***************************** ---> <cffunction name="initializeShipping" displayname="Initialize Shipping" description="Manages the process of retrieving the shipping data" access="public" output="false" returntype="Any"> <!--- get the shipping data for each product in the user cart ---> <cfset VARIABLES.shippingData = getShippingData()> <!--- create the xml documents ---> <cfset VARIABLES.xmlData = createXml(VARIABLES.shippingData)> <!--- submit the xml we just created to UPS ---> <cfset VARIABLES.shippingRate = submitXml(VARIABLES.xmlData)> <!--- return our shipping data ---> <cfreturn VARIABLES.shippingRate> </cffunction>
We’re doing four things here. We first call our getShippingData method which returns back the data about the items being shipped. This is the dimensions and weight. We then send the shipping data to the createXml method where the xml is created and returned back. We then take our XML and send it to the submitXml method. Here our XML is sent to UPS where XML is returned back to us from UPS containing the specifics of our shipment. Lastly we return the xml data sent back from UPS to our calling page, which in our case is our HTML page.
So the clearly the first thing we need to get is some data about the packages that we need to ship. In our case our customer has placed several items in their cart and we need to tell them how much UPS will charge to ship the packages to their shipping destination. Let’s take a look at the getShippingData() method first.
getShippingData() (shipping.cfc)
<!--- ***************************** GET SHIPPING DATA FOR PRODUCTS ***************************** ---> <cffunction name="getShippingData" displayname="Get Shipping Data" description="Returns the weight and dimensions for each product in the user's cart" access="private" output="false" returntype="Struct"> <!--- let's set our product list into a variable to work with ---> <cfset VARIABLES.productList = SESSION.cart.itemList> <!--- create a struct to return our data in ---> <cfset VARIABLES.shippingData = structnew()> <!--- create a counter to maintain the index of our loop ---> <cfset VARIABLES.counter = 1> <!--- loop through our list setting each product and its values into the struct ---> <cfloop list="#VARIABLES.productList#" index="VARIABLES.i" delimiters=","> <!--- get the shipping data for the selected product ---> <cfquery name="VARIABLES.getShippingData" datasource="#APPLICATION.dataSource#"> SELECT package_weight, package_length, package_width, package_height FROM #APPLICATION.dbName#.shipping_tbl WHERE item_id = <cfqueryparam value="#VARIABLES.i#" cfsqltype="cf_sql_numeric"> </cfquery> <!--- SET OUR PACKAGE NUMBER TO 1 ---> <cfset VARIABLES.packageNum = 1> <!--- LOOP OVER OUR QUERY IN CASE A SINGLE PRODUCT SHIPS AS MULTIPLE BOXES ---> <cfloop query="VARIABLES.getShippingData"> <!--- add the values to our struct ---> <cfset "VARIABLES.shippingData.product#VARIABLES.counter#.package#VARIABLES.packageNum#.weight" = VARIABLES.getShippingData.package_weight> <cfset "VARIABLES.shippingData.product#VARIABLES.counter#.package#VARIABLES.packageNum#.height" = VARIABLES.getShippingData.package_height> <cfset "VARIABLES.shippingData.product#VARIABLES.counter#.package#VARIABLES.packageNum#.length" = VARIABLES.getShippingData.package_length> <cfset "VARIABLES.shippingData.product#VARIABLES.counter#.package#VARIABLES.packageNum#.width" = VARIABLES.getShippingData.package_width> <!--- INCREASE THE PACKAGE NUMBER BY 1 ---> <cfset VARIABLES.packageNum = VARIABLES.packageNum + 1> </cfloop> <cfset VARIABLES.counter = VARIABLES.counter + 1> </cfloop> <!--- return our struct ---> <cfreturn VARIABLES.shippingData> </cffunction>
Before diving too deep into this method, let’s think about what we’ll commonly need to ship items. We know that we may need to pass to UPS the dimensions and weight of multiple products depending upon how much the customer is purchasing. So if our customer is purchasing a T-Shirt and a lamp then we need to get the package dimensions for both the T-Shirt and the lamp. Another thing to keep in mind is that any one item may be shipped in multiple packages. If this is the case each of those packages needs to rated by UPS as a separate package. Let’s say for example that our customer is purchasing a T-Shirt and a desk. Our desk comes in two packages. One containing the legs and the other the top. So we will need to have UPS rate three packages – our T-Shirt and our two boxes containing our desk.
Lastly, I just want to mention again that you may need to modify this function to fit your cart. I’m assuming here that as your customer adds items to their cart the ids of those items are stored in a list in the SESSION scope called ‘SESSION.cart.itemList’. Also you may need to modify your query that retrieves the product data. My query assumes that there is a table called ’shipping_tbl’ with five columns. First is the ‘item_id’. This is the foreign key that relates our item data with our actual items. The next four columns contain shipping data being the weight, length, width, and height. With all that said let’s take a quick look at what’s happening in this function.
By the time we get to our verify cart page where we’re showing our customer how much it will cost to ship the items in their cart to the shipping address they specified we have stored the ids of all the items in their cart in the customer’s session under the variable SESSION.cart.itemList. So first we set that list of item ids into a var called ‘VARIABLES.productList’. This isn’t necessary, but it’s a little easier to remember and type out each time. Next we need to create a struct to store all of our data in to return to the initializeShipping() method. Lastly, we need to set a simple counter to keep up with our loops. Once we have these bits of data set we can start retrieving our data.
We need to get data for each product in our customer’s cart. As such, we need to loop over each of the item ids in our list. We do this by looping through our list of ids. For each item id we need to get our dimensions and weight. I’ve generically used here in my query APPLICATION.dataSource for my data source and APPLICATION.dbName for my schema name. You can either hardcode these into your query, which I wouldn’t recommend, or change the Application vars to whatever fits your application. We know that our query will always, or should always return at least one record. This will be the dimensions for our package that the item ships in. However, in the case of our desk our query will return two records. Once for each package that the desk ships in. Knowing, however, that we will always have at least one package we need to set a var equal to 1. This is our VARIABLES.packageNum = 1. To handle never really knowing how many packages our product is shipped in we create another loop. This one loops over the results of our query. This way if our item ships in one package we will do one loop, four packages four loops. Once inside this loop we’ll set our package data into our struct to be returned.
To set our data into our struct we’ll need to do so dynamically. I’ll review this quickly. If you’re interested in how to manage struct data dynamically with loops I have two other articles on just this topic. You can review how to set data dynamically into a loop with Creating a Struct by Looping, and review how to output struct data dynamically with a loop with Dynamically Displaying Struct Data with a Loop. We’re setting data into our struct using our counter number which represents the item we are dealing with, and our packageNum which represents the package we are dealing with for a given item. We are setting four values into our struct. One for each of the bits of data we need to return – weight, length, width, and height. Our dynamic set looks like <cfset “VARIABLES.shippingData.product#VARIABLES.counter#.package#VARIABLES.packageNum#.weight” = VARIABLES.getShippingData.package_weight>, but if we were to hard code the first item in our list it would essentially look like VARIABLES.shippingData.product1.package1.weight = 23. Quick synopsis is that we set which product we are setting a value for (product1), which package for that product we are setting a value for (package1), what type of value we are setting (weight), and what that value is (23). Lastly, we need to increase our packageNum inside this loop in case there are multiple packages for a single item.
Once all of our data has been attained and set into our struct, we can return our struct. Once back in our initializeShipping() method we can see that we pass our struct of item shipping data to the createXml() method. So let’s take a look at this method to see how we create the XML that we will eventually send to UPS to be rated.
createXml() (shipping.cfc)
<!--- ***************************** CREATE XML DOCUMENTS ***************************** ---> <cffunction name="createXml" displayname="Create XML Documents" description="Creates the XML needed to send to UPS" access="private" output="false" returntype="Any"> <!---pass in the shipping data we set into our struct in the previous method---> <cfargument name="shippingData" type="Struct" required="true"> <!--- get the number of items in the struct ---> <cfset VARIABLES.numberOfProducts = structcount(ARGUMENTS.shippingData)> <!--- write the dynamic xml based upon the items in the user's cart ---> <cfxml variable="VARIABLES.xmlShippingRequest" casesensitive="true"> <RatingServiceSelectionRequest> <Request> <RequestAction>Rate</RequestAction> </Request> <PickupType> <Code>01</Code> </PickupType> <Shipment> <Shipper> <Address> <PostalCode>02906</PostalCode> </Address> <countryCode>US</countryCode> </Shipper> <ShipTo> <Address> <cfoutput><PostalCode>#xmlFormat(SESSION.customer.shippingZip)#</PostalCode></cfoutput> <countryCode>US</countryCode> </Address> </ShipTo> <Service> <Code>03</Code> </Service> <cfoutput> <!--- loop through the number of packages we are shipping ---> <cfloop from="1" to="#VARIABLES.numberOfProducts#" index="VARIABLES.i"> <!--- GET THE PACKAGE COUNT ---> <cfset VARIABLES.product = "product" & VARIABLES.i> <cfset VARIABLES.productName = structfindkey(VARIABLES.shippingData, "#VARIABLES.product#", "all")> <cfset VARIABLES.packageCount = structcount(VARIABLES.productName[1].value)> <!--- ***************************** SET QUANTITY ***************************** ---> <cfset VARIABLES.quantity = listGetAt(SESSION.cart.quantityList, VARIABLES.i, ",")> <!--- ***************************** NOW DEAL WITH THE QUANTITY ***************************** ---> <!--- now loop for the number in the quantity ---> <cfloop from="1" to="#VARIABLES.quantity#" index="VARIABLES.j"> <!--- LOOP OVER OUR PACKAGES ---> <cfloop from="1" to="#VARIABLES.packageCount#" index="VARIABLES.k"> <!--- GET PACKAGE DATA ---> <cfset VARIABLES.package = "package" & VARIABLES.k> <cfset VARIABLES.productName = structfindkey(VARIABLES.shippingData[VARIABLES.product], "#VARIABLES.package#", "all")> <cfset VARIABLES.value = VARIABLES.productName[1].value> <!--- WRITE XML ---> <Package> <PackagingType> <Code>02</Code> <Description>Package</Description> </PackagingType> <Description>Rate Shopping</Description> <Dimensions> <Length>#xmlFormat(VARIABLES.value.length)#</Length> <Width>#xmlFormat(VARIABLES.value.width)#</Width> <Height>#xmlFormat(VARIABLES.value.height)#</Height> </Dimensions> <PackageWeight> <!--- output our weight as xml ---> <Weight>#xmlFormat(VARIABLES.value.weight)#</Weight> </PackageWeight> </Package> </cfloop> </cfloop> </cfloop> </cfoutput> <ShipmentServiceOptions/> </Shipment> </RatingServiceSelectionRequest> </cfxml> <!--- save our xml into a var ---> <cfsavecontent variable="VARIABLES.xmlShippingData"> <!--- include the authentication xml doc ---> <cfinclude template="ups_authentication.xml" > <!--- output the xml we created above ---> <cfoutput> #VARIABLES.xmlShippingRequest# </cfoutput> </cfsavecontent> <!--- return our xml ---> <cfreturn VARIABLES.xmlShippingData> </cffunction>
The bulk of this method consist of writing the XML dynamically according to the data we previously stored in our struct to create an XML document to send to UPS. However, before writing any XML we need to make sure we pass in the struct with our shipping data for the items in our customer’s cart, and we need to get the number of items we are getting shipping data for. The number of items can be gotten by getting the structCount of our general struct. Now we can dig into the XML.
I’m not going to go through each node of the XML here. The UPS XML API Developer’s Guide has a very nice overview of the XML required and the datatypes and requirements for each node. I’m just going to hit here the portions of the XML that I think need explanation. Let’s start with the very first tag – the <cfxml> tag. This tag tells the Coldfusion server that we are writing XML with Coldfusion. It also allows us to specify a var that all the XML we write between the opening and closing <cfxml> tags will be stored in. I’ve also set here an attribute to make sure that our XML is case-sensitive. A built-in Coldfusion method that you may not be familiar with that is used here that I would like to review is the xmlformat() method. This method takes a single value. So for example I have my customer’s zip code stored in the customer’s session as SESSION.customer.shippingZip. But when I place this value within the <PostalCode> XML node I use xmlformat(SESSION.customer.shippingZip). Using this method just ensures that the data being output dynamically is formatted for XML.
Let’s take a look now at the <Package> node of our XML. This is by far and away the most complicated XML to output in this document. The actual <Package> node begins on line 63, but the Coldfusion code begins on line 37. So I’ll begin my review on line 37. First we want to make sure all of our dynamic XML is wrapped with <cfoutput>. First we need to loop over each of the products in our customer’s cart. All of these items are stored in our struct that we passed in. If we remember at the top of this method we set the number of products in our struct to VARIABLES.numberOfProducts. So what we need to do now is loop from 1 to the number of products we have.
Once inside our loop we need to do two things. First we need to get the number of packages for our product. To do this I find the portion of the struct that holds the products and then count the number of packages returned for that one product. What I end up with is a var called VARIABLES.packageCount that holds the number of packages our product contains. Again for more information on how to get and output data dynamically from a struct click here.
Once we have the number of packages we need to deal with the quantity of the item we are creating XML for. In our case here we have our quantities set into a list that corresponds with our SESSION.cart.itemList, except our quantities are stored in the session scope at SESSION.cart.quantityList. So we can use the getListAt() method to get the quantity of the item we are looping over. The gist of this is that if we have two T-Shirts we need to get the T-Shirt package rated twice by UPS. To accomplish this we need a new loop that loops the number of times of our particular quantity. If we have two T-Shirts we run two loops.
Once inside our quantity loop we need to immediately create another loop that loops over the number of packages that comprise the item being looped over. To take this one step further, if we have two T-Shirts that comes in three packages (one for the left sleeve, one for the body, and one for the right sleeve. You have to sew it together yourself) we will need to loop over each T-Shirt twice, and in each of those loops loop three times, one for each package that the T-Shirt is shipped in.
Now that we have set all of our loops to account for our items, quantities, and packages per item we can set the actual package dimensions and weight into our XML <Package> node. To get these values we need to set reach into the right part of our struct and get the values. We set this into our var called VARIABLES.value. At this point we can get our dimensions and weight and pass them into our XML appropriately. So what we have created here is XML that will create itself depending upon the number of products in a user’s cart, their quantities, and the number of packages each of those products ship in. All in all, if we have 5 products, and one of them ships in two packages, we will have six <Package> XML nodes when the XML is finally output.
The last step now is to save the XML we just created along with our authentication XML that we wrote in a separate file earlier into a var that we can return to our initializeShipping() method. To do this we use the nifty <cfsavecontent> tag. We save our data into a var called VARIABLES.xmlShippingData. Inside of the <cfsavecontent> tag we include our authentication XML file and the XML we just created. Afterwards we can return our complete XML stored in our VARIABLES.xmlShippingData var.
Now we find ourselves once again in our initializeShipping() method with just one more method to run. Now that we have a complete XML document with all of our data written into it that UPS requires to return a rate all we need to do is send it to UPS. So let’s send our XML stored in our var VARIABLES.shippingRate to our submitXml() method. We’ll take a look at this method now.
submitXml() (shipping.cfm)
<!--- ***************************** SUBMIT XML ***************************** ---> <cffunction name="submitXml" displayname="Submit XML" description="Submits XML documents to UPS to get the shipping data" access="private" output="false" returntype="any"> <!---pass in our xml data to be sent to UPS---> <cfargument name="xmlData" type="string" required="true"> <cfoutput> <!--- post the data to the UPS server ---> <cfhttp url="#APPLICATION.upsServerUrl#" method="post" result="VARIABLES.shippingData"> <!--- pass in the xml data to be sent to UPS ---> <cfhttpparam type="xml" value="#ARGUMENTS.xmlData#"> </cfhttp> </cfoutput> <!--- parse the xml returned by UPS ---> <cfset VARIABLES.shippingDataParsed = xmlparse(VARIABLES.shippingData.filecontent)> <!--- return the parsed data ---> <cfreturn VARIABLES.shippingDataParsed> </cffunction>
Well this function is significantly shorter and simpler than the previous two functions. We should be able to run through this pretty quickly. First we need to pass in our XML that we created in our last function. Now we can post our XML to the UPS server where it will be parsed, rated, and an XML document will be returned in our result which I have set to VARIABLES.shippingData. We use the <cfhttp> tag, and the url to the UPS server. I’ve stored my URL in the APPLICATION scope, but the URL for the UPS sandbox environment is https://wwwcie.ups.com/ups.app/xml/Rate. To move this into production just remove the ‘cie’ from the wwwcie.ups… Once inside of our <cfhttp> tag we pass a parameter that is our XML to send to UPS. We use the <cfhttpparam> tag and pass in the type as XML and our value of our ARGUMENTS.xmlData which is the XML we passed in.
At this point UPS has received our XML and hopefully rated it and sent it back as an XML document that we can now parse and get our total cost of shipping. In order to get the returned XML into a format that we can work with we need to parse it. As usual Coldfusion comes equipped with a handy xmlparse() method that allows us to parse XML quickly. Once we’ve parsed our returned XML we can return it to our initializeShipping() method.
Back at our initializeShipping() method we can now return our parsed XML by returning VARIABLES.shippingRate.
Now let’s head back to our HTML to wrap this up. If you can even remember at this point we left our HTML looking like the following.
The HTML (Output of shipping data)
<!---set our shipping rate---> <cfset VARIABLES.shippingrate = ""> <span class=”shippingrate”> <!---output our shipping rate to the page---> <cfoutput>#dollarformat(VARIABLES.shippingrate)#</cfoutput> </span>
Let’s add a few things to it. First we will add the
The Complete HTML (Output of shipping data)
<!--- ***************************** SHIPPING ***************************** ---> <cfinvoke component="ups_shipping" method="initializeShipping" returnvariable="VARIABLES.packageData"> <!--- set our shipping price into a var ---> <cfset VARIABLES.shippingrate = VARIABLES.packageData["RatingServiceSelectionResponse"]["RatedShipment"]["TotalCharges"]["MonetaryValue"].xmlText> <span class=”shippingrate”> <!---output our shipping rate to the page---> <cfoutput>#dollarformat(VARIABLES.shippingrate)#</cfoutput> </span>
My suggestion is that you just use
That’s it. Hopefully this hasn’t been too painful. Any suggestions on how this can be simplified are greatly appreciated on my end since right now I’m much more anxious to wrap this up than you are.