Saturday, December 1, 2007

How the heck do you use the FedEx Rate web service?


UPDATE The original code sample uses version 2 of the WSDL.
  • September 24, 2008: Updated to include a rough example for WSDL version 4

  • October 19, 2009: Updated to include a rough example for WSDL version 7


  • The code samples work as of those dates. But no guarantees Fedex will not make further changes. Also, given the complexity of this webservice, consider looking into the old fashioned SOAP/CFHTTP method. See CFFedexRates for a base example. Be sure to check the open Issues List as well. There may be newer versions available (as attachments).


    I admit it. I do not get web services. Creating the web service equivalent of a "Hello World" project on my computer was one thing. Trying to figure out how to consume an external web service is a completely different story.

    Next stop: Web Service Hell


    So I took the plunge and decided to try and figure out how to consume the FedEx Rate Web Service. On what feels like day 212 I finally got it working, somewhat ;)

    Three things you will need for your journey..


    1. My first step was to register for a developer account

    2. Armed with my new account information, I downloaded the "Rate Web Service" WDSL. Looked at it and thought "Gee ...that is great. Now what do I do with it?"

    3. So I googled a bit and figured out I could put the wdsl file in a web folder and then try and call it as a webservice from a test .cfm page. On my computer I placed it in this directory

    [directory] c:\cfusionmx7\wwwroot\fedex\RateService_v2.wsdl
    [url path] http://127.0.0.1:8501/fedex/RateService_v2.wsdl
    


    Abandon hope, all ye who enter here


    Before getting started, I checked the code samples section. There were no ColdFusion samples .. of course. So I downloaded the java code samples instead. Let me just say.. thank god. If not for the java samples I would still be struggling with my favorite error message ..and muttering it in my sleep.


    Web service operation "getRate" with parameters {PAYMENT={{PAYMENTTYPE={SENDER}}}, .... could not be found.


    Paradiso


    After much trial and error my test page is finally working. I ended up using createObject() to create the webservice, because of errors when using cfinvoke. I suspect it might be due to the optional parameters. Anyway, the code is rough, but its a start. Sorry in advance for the terrible code formatting. Figuring out the blog formatting is next on my list (..sure, sure ;)

    As always comments/suggestions/corrections are welcome.

    WSDL Version 2 Code
    <!--- setting used later on in the code to set shipment details --->
    <cfset PassRateRequestPackageSummary = true />
    
    <!--- java objects used for paramters later on in the code --->
    <cfset objPosInteger = createObject("java", "org.apache.axis.types.PositiveInteger") />
    <cfset objNonNegInteger = createObject("java", "org.apache.axis.types.NonNegativeInteger") />
    
    <!---
    Populate a structure with all rate request settings
    --->
    <cfset data = structNew() />
    <cfset data.ClientDetail.AccountNumber="My Test Account" />
    <cfset data.ClientDetail.MeterNumber="My Test Meter" />
    <cfset data.TransactionDetail.CustomerTransactionId = "Rate Request v2" />
    <cfset data.WebAuthenticationDetail.UserCredential.key ="My Developer Test Key" />
    <cfset data.WebAuthenticationDetail.UserCredential.password ="My Developer Test Password" />
    
    <cfset data.Version.serviceId = "crs" />
    <cfset data.Version.major = "2" />
    <cfset data.Version.intermediate =  "0" />
    <cfset data.Version.minor =  "0" />
    <cfset data.Origin.CountryCode= "US" />
    <!--- web service complained if this was not an array --->
    <cfset streetArray = arrayNew(1) />
    <cfset arrayAppend(streetArray, "Address Line 1") />
    <cfset data.Origin.StreetLines = streetArray />
    <cfset data.Origin.City = "City Name" />
    <cfset data.Origin.StateOrProvinceCode = "TN" />
    <cfset data.Origin.PostalCode = "38115" />
    <cfset data.Origin.UrbanizationCode = "" />
    <cfset data.Origin.Residential = "false" />
    
    <cfset data.Destination = structNew() />
    <cfset data.Destination.CountryCode= "US" />
    <!--- web service complained if this was not an array --->
    <cfset streetArray = arrayNew(1) />
    <cfset arrayAppend(streetArray, "Address Line 1") />
    <cfset data.Destination.StreetLines= streetArray />
    <cfset data.Destination.City= "City Name" />
    <cfset data.Destination.StateOrProvinceCode= "QC" />
    <cfset data.Destination.PostalCode= "H1E1A1" />
    <cfset data.Destination.CountryCode= "CA" />
    
    <cfset data.DropoffType = "REGULAR_PICKUP" />
    <cfset data.ServiceType = "INTERNATIONAL_PRIORITY" />
    <cfset data.PackagingType = "YOUR_PACKAGING" />
    
    <cfset data.Payment.PaymentType = "SENDER" />
    <cfset data.ShipDate = "2007-11-30" />
    <cfset RateRequestType = structNew() />
    <cfset RateRequestType.VALUE = "ACCOUNT" />
    <!--- web service complained if this was not an array --->
    <cfset rateRequestArray = ArrayNew(1) />
    <cfset arrayAppend(rateRequestArray, "ACCOUNT") />
    <cfset data.RateRequestTypes = rateRequestArray />
    
    
    <!--- Not passing one of these choices will result in "Schema Validation Failure", as one of these objects is required. --->
    <cfif PassRateRequestPackageSummary >
    <cfset data.RateRequestPackageSummary.TotalInsuredValue.Amount = "100">
    <cfset data.RateRequestPackageSummary.TotalInsuredValue.Currency = "USD">
    <cfset data.RateRequestPackageSummary.TotalWeight.Value = "20.0">
    <cfset data.RateRequestPackageSummary.TotalWeight.Units = "LB">
    <!--- could not get this to work without using java objects --->
    <cfset data.RateRequestPackageSummary.PieceCount = objPosInteger.init(1) />
    <!--- web service complained if this was not an array --->
    <cfset specialServiceArray = ArrayNew(1)>
    <cfset arrayAppend(specialServiceArray, "APPOINTMENT_DELIVERY")>
    <cfset data.RateRequestPackageSummary.SpecialServicesRequested.SpecialServiceTypes = specialServiceArray>
    <cfelse>
    <cfset data.PackageCount = objNonNegInteger.init(1) >
    <!--- web service complained if this was not an array --->
    <cfset packagesArray = ArrayNew(1)>
    <cfset newPackage = structNew()>
    <cfset newPackage.Weight.Value = "10.00">
    <cfset newPackage.Weight.Units = "LB">
    <cfset newPackage.InsuredValue.Amount = "10.00">
    <cfset newPackage.InsuredValue.Currency = "USD">
    <!--- could not get this to work without using java objects --->
    <cfset newPackage.Dimensions.Length = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Width = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Height = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Units = "IN">
    <cfset arrayAppend(packagesArray, newPackage)>
    <cfset data.Packages = packagesArray>
    </cfif>
    
    <!---
    Invoke the webservice
    
    Notes, Using cfinvoke did not work. I think
    it may be related to the optional parameters
    --->
    <cfscript>
    ws = createObject("webservice", "http://127.0.0.1:8501/FedEx/RateService_v2.wsdl");
    rateReply = ws.getRate(data);
    </cfscript>
    
    <!---
    Process the response
    --->
    <cfprocessingdirective suppressWhiteSpace="true" />
    <cfoutput>
    <h1>Response: #rateReply.getHighestSeverity().toString()#</h1>
    
    <cfif rateReply.getHighestSeverity().toString() is "SUCCESS">
    <!--- This is weight, and charge per package --->
    <cfset shipment = rateReply.getRatedShipmentDetails() />
    <cfloop    from="1" to="#arrayLen(shipment)#" index="i">
    <cfset shipmentDetails =  shipment[i].getShipmentRateDetail()>
    <cfset packages = shipment[i].getRatedPackages() />
    <cfloop from="1" to="#arrayLen(packages)#" index="j">
    <cfset packageDetail = packages[j].getPackageRateDetail() />
    <cfset surcharge = packageDetail.getSurcharges() />
    
    <!--- display package info --->
    Package: #i#<br />
    Billing weight: #packageDetail.getBillingWeight().getValue()#
    #packageDetail.getBillingWeight().getUnits().toString()#<br />
    Base charge:     #packageDetail.getBaseCharge().getAmount()#
    #packageDetail.getBaseCharge().getCurrency()#<br />
    
    <!--- display surcharges --->
    <cfloop from="1" to="#ArrayLen(surcharge)#" index="k">
    #surcharge[k].getSurchargeType().toString()#  <b>surcharge</b>
    #surcharge[k].getAmount().getAmount()#
    #surcharge[k].getAmount().getCurrency()#<br />
    </cfloop>
    <hr>
    
    <!--- display net charges --->
    Net charge:     #packageDetail.getNetCharge().getAmount()#
    #packageDetail.getNetCharge().getCurrency()#
    <hr>
    
    <!--- display totals. these values may be null ? --->
    <cfset TotalBillingWeight = shipmentDetails.getTotalBillingWeight() />
    <cfset TotalSurcharges = shipmentDetails.getTotalSurcharges() />
    <cfset TotalNetCharge = shipmentDetails.getTotalNetCharge() />
    <cfif IsDefined("totalBillingWeight")>
    Total billing weight:     #TotalBillingWeight.getValue()#
    #TotalBillingWeight.getUnits().toString()#<br />
    </cfif>
    <cfif IsDefined("TotalSurcharges")>
    Total surcharge: #TotalSurcharges.getAmount()# #TotalSurcharges.getCurrency()#<br />
    </cfif>
    <cfif IsDefined("TotalNetCharge")>
    Total net charge: #TotalNetCharge.getAmount()# #TotalNetCharge.getCurrency()#<br />
    </cfif>
    <hr>
    </cfloop>
    </cfloop>
    <cfelse>
    <cfset Notifications  = rateReply.getNotifications() />
    RateReply Notifications:  #Notifications[1].getMessage()#
    </cfif>
    </cfoutput>
    </cfprocessingdirective>
    


    WSDL Version 4 Code (Rough)
    <!--- java objects used for paramters later on in the code --->
    <cfset objPosInteger = createObject("java", "org.apache.axis.types.PositiveInteger") />
    <cfset objNonNegInteger = createObject("java", "org.apache.axis.types.NonNegativeInteger") />
    
    <!---
    Populate a structure with all rate request settings
    --->
    <cfset data = structNew() />
    
    <!---- set to true to get the rates for different service types --->
    <cfset getAllRatesFlag = false>
    
    <!--- credential information --->
    <cfset data.ClientDetail.AccountNumber = "My Test Account" />
    <cfset data.ClientDetail.MeterNumber = "My Test Meter" />
    <cfset data.TransactionDetail.CustomerTransactionId = "java sample - Rate Request" />
    <cfset data.WebAuthenticationDetail.UserCredential.key ="My Developer Test Key" />
    <cfset data.WebAuthenticationDetail.UserCredential.password ="My Developer Test Password" />
    
    <!--- webservice version --->
    <cfset data.Version.serviceId = "crs" />
    <cfset data.Version.major = "4" />
    <cfset data.Version.intermediate =  "0" />
    <cfset data.Version.minor =  "0" />
    
    <!--- shipment characteristics --->
    <cfset data.requestedShipment.DropoffType = "REGULAR_PICKUP" />
    <cfset data.requestedShipment.ShipTimestamp = now()>
    <cfif NOT getAllRatesFlag>
    <cfset data.requestedShipment.ServiceType = "INTERNATIONAL_PRIORITY">
    <cfset data.requestedShipment.PackagingType = "YOUR_PACKAGING">
    </cfif>
    
    <!--- shipper information --->
    <cfset streetArray = arrayNew(1) />
    <cfset arrayAppend(streetArray, "Address Line 1") />
    <cfset shipper.address.StreetLines = streetArray />
    <cfset shipper.address.City = "City Name" />
    <cfset shipper.address.StateOrProvinceCode = "TN" />
    <cfset shipper.address.PostalCode = "38115" />
    <cfset shipper.address.CountryCode = "US" />
    <cfset data.requestedShipment.shipper = shipper>
    
    <!--- receiver information --->
    <cfset streetArray = arrayNew(1) />
    <cfset arrayAppend(streetArray, "Address Line 1") />
    <cfset recipient.address.StreetLines= streetArray />
    <cfset recipient.address.City= "City Name" />
    <cfset recipient.address.StateOrProvinceCode= "QC" />
    <cfset recipient.address.PostalCode= "H1E1A1" />
    <cfset recipient.address.CountryCode= "CA" />
    <cfset data.requestedShipment.recipient = recipient>
    
    
    <cfset Payment.PaymentType = "SENDER">
    <cfset data.requestedShipment.shippingChargesPayment = Payment>
    
    <!--- package information --->
    <cfset packagesArray = ArrayNew(1)>
    <cfset newPackage = structNew()>
    <cfset newPackage.Weight.Value = "15.00">
    <cfset newPackage.Weight.Units = "LB">
    <cfset newPackage.InsuredValue.Amount = "10.00">
    <cfset newPackage.InsuredValue.Currency = "USD">
    
    <!--- package dimensions --->
    <!--- could not get this to work without using java objects --->
    <cfset newPackage.Dimensions.Length = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Width = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Height = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Units = "IN">
    <cfset arrayAppend(packagesArray, newPackage)>
    
    <cfset data.requestedShipment.RequestedPackages = packagesArray>
    <cfset data.requestedShipment.packageCount = objNonNegInteger.init("1")>
    <cfset data.requestedShipment.packageDetail = "INDIVIDUAL_PACKAGES">
    <cfset RateRequestTypeArray = arrayNew(1)>
    <cfset arrayAppend(RateRequestTypeArray, "ACCOUNT")>
    <cfset data.requestedShipment.RateRequestTypes = RateRequestTypeArray>
    
    <!---
    Invoke the webservice
    
    Notes, Using cfinvoke did not work. I think
    it may be related to the optional parameters
    --->
    <cfscript>
    ws = createObject("webservice", "http://127.0.0.1:8501/FedEx/RateService_v4.wsdl");
    rateReply = ws.getRates(data);
    </cfscript>
    
    <!---
    Process the response
    --->
    <cfoutput>
    <h1>Response: Highest Severity = #rateReply.getHighestSeverity().toString()#</h1>
    <cfset notifications = getNotifications(rateReply.getNotifications())>
    <cfdump var="#notifications#" label="Notifications">
    
    <cfif isResponseOK(rateReply.getHighestSeverity().toString())>
    <cfset rrds = rateReply.getRateReplyDetails() />
    
    <cfloop from="1" to="#arrayLen(rrds)#" index="i">
    <cfset rrd =  rrds[i]>
    
    Service type = #rrd.getServiceType()#<br>
    Packaging type = #rrd.getPackagingType()#<br>
    
    <cfset rsds = rrd.getRatedShipmentDetails()>
    <cfloop from="1" to="#arrayLen(rsds)#" index="j">
    <cfset rsd = rsds[j] />
    <cfset srd = rsd.getShipmentRateDetail() />
    
    RatedShipmentDetail #j#<br>
    <!--- using functions because these values may be null --->
    Rate type = #getValue( srd.getRateType() )#<br>
    Total Billing weight = #getWeightValue( srd.getTotalBillingWeight() )# <br>
    Total surcharges = #getMoneyValue( srd.getTotalSurcharges() )# <br>
    Total net charge #getMoneyValue( srd.getTotalNetCharge() )#<br>
    RatedPackageDetails:<hr>
    
    <cfset rpds = rsd.getRatedPackages()>
    <cfif not IsDefined("rpds")>
    <cfset rpds = arrayNew(1)>
    </cfif>
    
    <cfloop from="1" to="#arrayLen(rpds)#" index="k">
    RatedPackageDetail #k#<br>
    <cfset rpd = rpds[k]>
    <cfset prd = rpd.getPackageRateDetail()>
    <cfif IsDefined("prd")>
    Billing weight = #getWeightValue( prd.getBillingWeight() )# <br>
    Base charge = #getMoneyValue( prd.getBaseCharge() )# <br>
    <cfset surcharges = prd.getSurcharges()>
    <cfif not IsDefined("surcharges")>
    <cfset surcharges = arrayNew(1)>
    </cfif> 
    <cfloop from="1" to="#arrayLen(surcharges)#" index=",">
    #surcharge.getDescription()# surcharge = #getMoneyValue( surcharge.getAmount() )#<br>
    </cfloop>
    </cfif>
    </cfloop>
    </cfloop>
    </cfloop>
    </cfif>
    </cfoutput>
    
    <cffunction name="getMoneyValue" returntype="string">
    <cfargument name="obj" type="any" required="false">
    <cfif structKeyExists(arguments, "obj")>
    <cfreturn arguments.obj.getAmount().toString() &" "& arguments.obj.getCurrency().toString() />
    </cfif>
    
    <cfreturn "" />
    </cffunction>
    
    <cffunction name="getWeightValue" returntype="string">
    <cfargument name="obj" type="any" required="false">
    <cfif structKeyExists(arguments, "obj")>
    <cfreturn arguments.obj.getValue().toString() &" "& arguments.obj.getUnits().toString() />
    </cfif>
    
    <cfreturn "" />
    </cffunction>
    
    <cffunction name="getValue" returntype="string">
    <cfargument name="obj" type="any" required="false">
    <cfargument name="defaultValue" type="string" required="false" default="">
    <cfif structKeyExists(arguments, "obj")>
    <cfreturn arguments.obj.toString()/>
    <cfelse>
    <cfreturn arguments.defaultValue/>
    </cfif>
    </cffunction>
    
    <cffunction name="getNotifications" returntype="array" hint="Converts the notifications object into an array of structures">
    <cfargument name="notifications" type="array" required="false">
    
    <cfset var Local = structNew()>
    <cfset Local.results = arrayNew(1)>
    
    <cfif structKeyExists(arguments, "notifications") and arrayLen(arguments.notifications)>
    <cfloop from="1" to="#arrayLen(arguments.notifications)#" index="Local.x">
    <cfset Local.n = arguments.notifications[Local.x]>
    <cfif structKeyExists(Local, "n")>
    <cfset Local.ns = Local.n.getSeverity()>
    
    <cfset Local.note = structNew()>
    <cfset Local.note.severity = "">
    <cfif NOT structKeyExists(Local, "ns")>
    <cfset Local.severity = Local.ns.getValue()>
    </cfif>
    <cfset Local.note.code = Local.n.getCode()>
    <cfset Local.note.message = Local.n.getMessage()>
    <cfset Local.note.source = Local.n.getSource()>
    <cfset arrayAppend( Local.results, Local.note) >
    </cfif>
    </cfloop>
    </cfif>
    <cfreturn Local.results>
    </cffunction>
    
    <cffunction name="isResponseOK" returntype="boolean" hint="Returns true if the severity type is WARNIGN,NOTE or SUCCESS">
    <cfargument name="notificationSeverityType" required="false">
    <cfset var isOK = false>
    <cfif structKeyExists(arguments, "notificationSeverityType")>
    <cfswitch expression="#arguments.notificationSeverityType#">
    <cfcase value="WARNING,NOTE,SUCCESS">
    <cfset isOK = true>
    </cfcase>
    <cfdefaultcase>
    <cfset isOK = false>
    </cfdefaultcase>
    </cfswitch>
    </cfif>
    
    <cfreturn isOK>
    </cffunction>
    


    WSDL Version 7 Code (Rough)
    <!--- java objects used for paramters later on in the code --->
    <cfset objPosInteger = createObject("java", "org.apache.axis.types.PositiveInteger") />
    <cfset objNonNegInteger = createObject("java", "org.apache.axis.types.NonNegativeInteger") />
    
    <!---
    Populate a structure with all rate request settings
    --->
    <cfset data = structNew() />
    
    <!---- set to true to get the rates for different service types --->
    <cfset getAllRatesFlag = false>
    
    <!--- credential information --->
    <cfset data.ClientDetail.AccountNumber= "My Test Account" />
    <cfset data.ClientDetail.MeterNumber= "My Meter Number" />
    <cfset data.TransactionDetail.CustomerTransactionId = "Example - Rate Request" />
    <cfset data.WebAuthenticationDetail.UserCredential.key = "My Developer Test Key" />
    <cfset data.WebAuthenticationDetail.UserCredential.password = "My Developer Test Password" />
    
    <!--- webservice version --->
    <cfset data.Version.serviceId = "crs" />
    <cfset data.Version.major = "7" />
    <cfset data.Version.intermediate =  "0" />
    <cfset data.Version.minor =  "0" />
    
    <!--- shipment characteristics --->
    <cfset data.requestedShipment.DropoffType = "REGULAR_PICKUP" />
    <cfset data.requestedShipment.ShipTimestamp = now()>
    <cfif NOT getAllRatesFlag>
    <cfset data.requestedShipment.ServiceType = "INTERNATIONAL_PRIORITY">
    <cfset data.requestedShipment.PackagingType = "YOUR_PACKAGING">
    </cfif>
    
    <!--- shipper information --->
    <cfset streetArray = arrayNew(1) />
    <cfset arrayAppend(streetArray, "Address Line 1") />
    <cfset shipper.address.StreetLines = streetArray />
    <cfset shipper.address.City = "City Name" />
    <cfset shipper.address.StateOrProvinceCode = "TN" />
    <cfset shipper.address.PostalCode = "38115" />
    <cfset shipper.address.CountryCode = "US" />
    <cfset data.requestedShipment.shipper = shipper>
    
    <!--- receiver information --->
    <cfset streetArray = arrayNew(1) />
    <cfset arrayAppend(streetArray, "Address Line 1") />
    <cfset recipient.address.StreetLines= streetArray />
    <cfset recipient.address.City= "City Name" />
    <cfset recipient.address.StateOrProvinceCode= "QC" />
    <cfset recipient.address.PostalCode= "H1E1A1" />
    <cfset recipient.address.CountryCode= "CA" />
    <cfset data.requestedShipment.recipient = recipient>
    
    
    <cfset Payment.PaymentType = "SENDER">
    <cfset data.requestedShipment.shippingChargesPayment = Payment>
    
    <!--- package information --->
    <cfset packagesArray = ArrayNew(1)>
    <cfset newPackage = structNew()>
    <cfset newPackage.Weight.Value = "15.00">
    <cfset newPackage.Weight.Units = "LB">
    <cfset newPackage.InsuredValue.Amount = "10.00">
    <cfset newPackage.InsuredValue.Currency = "USD">
    
    <!--- package dimensions --->
    <!--- could not get this to work without using java objects --->
    <cfset newPackage.Dimensions.Length = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Width = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Height = objNonNegInteger.init(1)>
    <cfset newPackage.Dimensions.Units = "IN">
    <cfset arrayAppend(packagesArray, newPackage)>
    
    <cfset data.requestedShipment.RequestedPackageLineItems = packagesArray>
    <cfset data.requestedShipment.packageCount = objNonNegInteger.init("1")>
    <cfset data.requestedShipment.packageDetail = "INDIVIDUAL_PACKAGES">
    <cfset RateRequestTypeArray = arrayNew(1)>
    <cfset arrayAppend(RateRequestTypeArray, "ACCOUNT")>
    <cfset data.requestedShipment.RateRequestTypes = RateRequestTypeArray>
    
    <!---
    Invoke the webservice
    
    Notes, Using cfinvoke did not work. I think
    it may be related to the optional parameters
    --->
    <cfscript>
    ws = createObject("webservice", "http://127.0.0.1:8501/FedEx/RateService_v7.wsdl");
    rateReply = ws.getRates(data);
    </cfscript>
    
    <!---
    Process the response
    --->
    <cfoutput>
    <h1>Response: Highest Severity = #rateReply.getHighestSeverity().toString()#</h1>
    <cfset notifications = getNotifications(rateReply.getNotifications())>
    <cfdump var="#notifications#" label="Notifications">
    
    <cfif isResponseOK(rateReply.getHighestSeverity().toString())>
    <cfset rrds = rateReply.getRateReplyDetails() />
    
    <cfloop from="1" to="#arrayLen(rrds)#" index="i">
    <cfset rrd =  rrds[i]>
    
    Service type = #rrd.getServiceType()#<br>
    Packaging type = #rrd.getPackagingType()#<br>
    
    <cfset rsds = rrd.getRatedShipmentDetails()>
    <cfloop from="1" to="#arrayLen(rsds)#" index="j">
    <cfset rsd = rsds[j] />
    <cfset srd = rsd.getShipmentRateDetail() />
    
    RatedShipmentDetail #j#<br>
    <!--- using functions because these values may be null --->
    Rate type = #getValue( srd.getRateType() )#<br>
    Total Billing weight = #getWeightValue( srd.getTotalBillingWeight() )# <br>
    Total surcharges = #getMoneyValue( srd.getTotalSurcharges() )# <br>
    Total net charge #getMoneyValue( srd.getTotalNetCharge() )#<br>
    RatedPackageDetails:<hr>
    
    <cfset rpds = rsd.getRatedPackages()>
    <cfif not IsDefined("rpds")>
    <cfset rpds = arrayNew(1)>
    </cfif>
    
    <cfloop from="1" to="#arrayLen(rpds)#" index="k">
    RatedPackageDetail #k#<br>
    <cfset rpd = rpds[k]>
    <cfset prd = rpd.getPackageRateDetail()>
    <cfif IsDefined("prd")>
    Billing weight = #getWeightValue( prd.getBillingWeight() )# <br>
    Base charge = #getMoneyValue( prd.getBaseCharge() )# <br>
    <cfset surcharges = prd.getSurcharges()>
    <cfif not IsDefined("surcharges")>
    <cfset surcharges = arrayNew(1)>
    </cfif> 
    <cfloop from="1" to="#arrayLen(surcharges)#" index=",">
    #surcharge.getDescription()# surcharge = #getMoneyValue( surcharge.getAmount() )#<br>
    </cfloop>
    </cfif>
    </cfloop>
    </cfloop>
    </cfloop>
    </cfif>
    </cfoutput>
    
    <cffunction name="getMoneyValue" returntype="string">
    <cfargument name="obj" type="any" required="false">
    <cfif structKeyExists(arguments, "obj")>
    <cfreturn arguments.obj.getAmount().toString() &" "& arguments.obj.getCurrency().toString() />
    </cfif>
    
    <cfreturn "" />
    </cffunction>
    
    <cffunction name="getWeightValue" returntype="string">
    <cfargument name="obj" type="any" required="false">
    <cfif structKeyExists(arguments, "obj")>
    <cfreturn arguments.obj.getValue().toString() &" "& arguments.obj.getUnits().toString() />
    </cfif>
    
    <cfreturn "" />
    </cffunction>
    
    <cffunction name="getValue" returntype="string">
    <cfargument name="obj" type="any" required="false">
    <cfargument name="defaultValue" type="string" required="false" default="">
    <cfif structKeyExists(arguments, "obj")>
    <cfreturn arguments.obj.toString()/>
    <cfelse>
    <cfreturn arguments.defaultValue/>
    </cfif>
    </cffunction>
    

    37 comments:

    Gay February 22, 2008 at 9:26 PM  

    Please help....
    I signed up for a test developer account with FedEx and got a Developer test key, account number and meter number....I now put it into the code and I see that it needs something called "password". What the hell is the password? Is it my FedEx account password? If so, that doesn't work....what else could the password be? Is there any way I can get the password?

    Just for your info I work on PHP

    Thanks for any light you can shed

    Best Regards
    Gayatri.

    cfSearching February 23, 2008 at 5:22 AM  

    Gayatri,

    Yes, I thought the same thing. They give you four (4) pieces of information in two (2) separate steps. Probably for security reasons. Though there is some duplication, you just need to combine the information from both steps.

    Step 1 - When you register for the developer account online, the confirmation displays 3 items:

    1. Developer Test Key: (UserCredential.key)
    2. Test Account: (ClientDetail.AccountNumber)
    3. Test Meter: (ClientDetail.MeterNumber)

    Step 2 - When the registration is complete they email you a confirmation with 3 items:

    1. Test Security Code: (UserCredential.password)
    2. Test account number: (ClientDetail.AccountNumber)
    3. Test meter number: (ClientDetail.MeterNumber)

    nick March 17, 2008 at 7:51 PM  

    Does anyone have any idea why the JAVA example provided by FedEx does not work?

    java.lang.NullPointerException
    at com.mrn.unitTests.RatingWebServiceClient.main(RatingWebServiceClient.java:140)

    The result is successful, but the:
    RatedPackageDetail[] rpd = rsd[i].getRatedPackages();
    is null

    I wish the example would work out of the box.

    cfSearching March 18, 2008 at 6:23 PM  

    @nick,

    Did you get it working yet? It was a while ago, but I know I successfully ran the java example in Eclipse. I do not remember an error, but I may still have the code lying around somewhere.

    Albert March 20, 2008 at 2:43 PM  

    I wanted to thank you for the post since it has saved me some time integrating with FedEx’s web services. I have made a few changes I wanted to post to help others out.

    FedEx will charge extra if dimensional weight is over a certain amount of cubic feet. In your example, you set the length, width, and height to 1. For anyone who has the dimensional data to pass to FedEx, this is how to get fedex to take your dimensions.

    Add back in the opening and closing tags since I had to submit without them.

    cfset newPackage.Dimensions.Length = objNonNegInteger.init(evaluate(JavaCast("int", maxDimensions.length)))

    cfset newPackage.Dimensions.Width = objNonNegInteger.init(evaluate(JavaCast("int", maxDimensions.width))) /

    cfset newPackage.Dimensions.Height = objNonNegInteger.init(evaluate(JavaCast("int", maxDimensions.height))) /

    Enjoy,
    Al

    cfSearching March 21, 2008 at 7:19 AM  

    @Albert,

    Yes, it took me a while to figure out how to pass some of the arguments. So I thought I would post my results. I am glad it could help in some way.

    Thanks for follow up. Just curious, is the evaluate() necessary?

    Albert March 21, 2008 at 8:43 AM  

    Unfortunately, the evaluate() is necessary. It won't work without it. The javacast isn't necessary if you use the coldfusion int() function.

    Thanks for the blog! I added it to my favorites. I wish there were more coldfusion blogs out there posting solutions to problems

    cfSearching March 21, 2008 at 9:35 AM  

    Interesting. I will have to test that one out.

    You may be right about javacast being unnecessary here. I prefer to use it religiously, whether it is needed or not. That way I never have to worry about conversion problems.. (and believe me I have had them ;)

    I am glad you like the blog. I am not an avid blog reader, but there are some good ones out there. You can find two good listings of cf/adobe product blogs here:

    http://fullasagoog.com/index.cfm
    http://weblogs.macromedia.com/

    Truthfully, some of them are over my head ;) So I try and write about some of the more basic problems I have run into. I figure if I get stuck on something, then someone else out there probably will too. I might as well blog about it and hopefully help someone out ;)

    Ranga March 24, 2008 at 10:11 AM  

    I am still getting the "Web service operation "getRate" with parameters {PAYMENT={{PAYMENTTYPE={SENDER}}}, .... could not be found." error.

    This is what I did before executing the cfm file.

    Downloaded RateService_v3.wsdl and RateService_v3.xsd. Created it as a webservice using the ColdFusion Administrator.

    As a next step copied the code and tried to execute it, of course after modifying the webservice URL, but it returned me the same error as you had mentioned.

    I am not sure if I am missing something here?

    cfSearching March 24, 2008 at 10:54 AM  

    @Ranga,

    Well, the original CF example was developed using the previous version, RateService_v2.wsdl. So that may make a difference.

    I did not create a webservice in the ColdFusion Administrator. I just placed the RateService_v2.wsdl file in the same directory as my cfm script. The webservice was created automatically when I ran the script.

    Ranga March 24, 2008 at 11:45 AM  

    Oh, v2. It was my mistake to have to run the script against v3. I will try to accommodate the script for v3 and see if it is working. If it does, I'll post the script.

    Meanwhile, do you have a RateService_v2.wsdl that you can post it here?

    Thanks!
    Ranga

    cfSearching March 24, 2008 at 12:22 PM  

    @Ranga,

    Sorry, I do not have access to a copy of it right now. If the fedex site does not provide previous versions, you might also try googling RateService_v2.wsdl. There may be a web accessible copy out there.

    cfSearching April 6, 2008 at 9:55 AM  

    Has anyone gotten the v3 examples to work, either with JAVA or CF? If you have can you post the corrections you made to the code?

    The web services were updated a few months ago (to version 3) and I suspect the code examples were not updated correctly. I tried v3 JAVA examples, and like @nick said, they do not work. There appear to be several errors in the code.

    Jordan June 23, 2008 at 1:29 PM  

    After much sweat and tears I've finally got a custom tag that works with the new (v3) rate services. It's open source and fairly well documented if I do say so myself. I'll be adding to it as time goes on. Hopefully someone will find it useful.

    FedEx custom tag from CorpDirect Agents

    Rafael,  August 8, 2008 at 10:32 PM  

    Thank Goodness I found some CF related work with FedEx. I was afraid I'd have to start sacrificing kittens in order to appease the FedEx gods.

    cfSearching August 11, 2008 at 1:41 PM  

    @Rafael,

    LOL. I thinking Jordan may have had to sacrifice larger animals to finally sate the Fedex devils ;-)

    cfSearching August 11, 2008 at 1:43 PM  

    Darn fat fingers!:

    "I AM thinking Jordan may have had to sacrifice larger animals to finally sate the Fedex devils ;-)"

    Emanuel Costa,  September 15, 2008 at 11:40 AM  

    Thanks a lot to everyone here for the clarifications about implementing Fedex API with Cold Fusion. Anyone was able to use the Version 4 of the WSDL file?! I was able to use the V2 listed here. I also could test using the PHP5 and Java examples they have at Fedex Developer site. But no luck using version 3 (it gives me an authentication error). Any help is very welcome!

    cfSearching September 24, 2008 at 10:57 AM  

    @Emanuel Costa,

    No I have not used Version 4. Though I did try the java examples which _do_ work. Unlike the ones for version 3 ;)

    I put up a quick and dirty CF example for version 4 above. Just in case it helps anyone get started.

    Dominic O'Connor,  November 26, 2008 at 9:59 AM  

    I had to make some tweaks for the new v5, but this blog post was a lifesaver. Thanks.

    Chad July 23, 2009 at 9:18 AM  

    One thing i noticed is you have to be careful that if the account gets a discount from fedex the function getTotalNetFedExCharge() returns the discounted amount.

    You have to get the effective net discount and add it to the Net FedEx charge.

    At least i have not found a way to get the Standard List Rate straight out of the returned object.

    Uly October 16, 2009 at 8:00 AM  

    Hi,

    Thank you for the example you provided. It's the only one I can find on the net.

    Unfortunately, it's not working for me with v7. Same problem that Ranga above had: "Web service operation "getRate" with parameters {PAYMENT={{PAYMENTTYPE={SENDER}}}, .... could not be found." error.

    I have never worked with SOAP before, so my question is: how do I see what functions are available in the web service, and more importantly how do I tell what input arguments are required?

    Thanks!

    Uly

    Jordan October 16, 2009 at 11:02 AM  

    Uly,

    That error message means you've sent in the wrong set of parameters for that function. How do you know which ones to send in? Ah ha ha.

    Theoretically you can read the WSDL file, look at the .java version, and piece it together. You have to pay very close attention to what fields are requires and exactly what type they are. But CF will never tell you [variable X is missing]. You either get it entirely right or entirely wrong. Making this tag work with v3 almost cost me my sanity.

    Hmm, that's no help to you, is it? Um. Try UPS?

    cfSearching October 16, 2009 at 11:04 AM  

    @Uly,

    A quick way to view the methods is to create an instance of the webservice. Then do a cfdump

    ie
    <cfset ws = createObject("webservice", "http://127.0.0.1:8501/FedEx/RateService_v2.wsdl")>
    <cfdump var="#ws#">

    There may be easier methods, but IIRC I used the fedex java API and the .wsdl file to figure out the input arguments, data types, etcetera. I had never used a wdsl before this. But once I got used to reading the xml, it was pretty easy to understand. (I will update the entry and post an example, when I get a chance).



    BTW: I have seen a few CF/fedex projects out there. Jordan posted a link to one of them above and here is another one I found via a quick google search:

    http://code.google.com/p/cffedexrates/

    -Leigh

    cfSearching October 16, 2009 at 11:12 AM  

    @Jordan,

    But CF will never tell you [variable X is missing]. You either get it entirely right or entirely wrong. Making this tag work with v3 almost cost me my sanity.

    Truer words were never spoken. The whole endeavor was challenging. Plus v3 was pretty bad IMO. Even the java examples did not work at first.

    -Leigh

    Uly October 16, 2009 at 1:18 PM  

    Jordan:
    At this point, I am really considering switching to UPS. I figured there are more sample/ready made codes available out there in CF.

    We've been using Fedex Ship Manager Direct for the last few years. It's working out well, but they told us that they are discontinuing it in favour of the web service. From my point of view as a novice, I am not quite sure what the big deal about web service is. The FSM Direct looks just like the non-soap version of the web service (ie. do a direct http with a pre-filled xml)

    cfsearching:

    Thanks for the advice and the link. I checked it out but it only has the getrate function. I'd need to implement the other functions as well. I did try to go through the cfdump variable, but the length of it makes my head spin.

    I really wish CF gives more specific error messages. Is this weakness a CF thing or true with other languages?

    Thanks again,
    Uly

    cfSearching October 16, 2009 at 4:04 PM  

    @Uly,

    Yes, since you are only dumping a instance of the webservice, only its methods are shown. In this case: getRate(). For other object types, you would have to a) create an instance or b) look at the api or the java stubs. IMO, viewing the wsdl is easier.

    I really wish CF gives more specific error messages. Is this weakness a CF thing or true with other languages?

    You are not alone there. The issue of having to get the data types just right is simply the nature of working with webservices. But having said that, IMO the error messages could be more informative. That is often the price you pay when working with what is essentially a simplified wrapper of a more complex interface. Things work swimmingly in most cases, but once you pass a certain level of complexity it can turn into a nightmare. Consuming webservices in CF is certainly a lot easier than in some other languages. But that simplicity sometimes comes at the cost of clear debugging information. Unfortunately, that is a common problem in any language.

    Have you considered trying to submit the information via a regular cfhttp post? Given the complexity of the FedEx webservices and how frequently they seem to update them, that approach might be simpler.

    http://russ.michaels.me.uk/index.cfm/2007/5/18/ColdFusion-Web-Services-and-SOAP

    -Leigh

    cfSearching October 18, 2009 at 9:19 PM  

    @Uly & @Jordan,

    It seems one change in version 7 that broke the example was the property requestedShipment.RequestedPackages was replaced with requestedShipment.RequestedPackageLineItems. Though there may be other changes. I have updated the entry with a rough example for version 7.

    Also, given all the nesting in this particular webservice I would consider going the SOAP/cfhttp route, like in the CFFedexRates project. It is easier to debug. Plus the version 7 files even include a working SOAP sample.

    HTH
    -Leigh

    Uly October 20, 2009 at 12:38 PM  

    Hi Leigh,

    Thank you so much for your suggestion. I tried doing direct cfhttp with a pre-formatted xml, and while I am still occasionally having trouble figuring out which input data are required, and I am making progress.

    I absolutely agree that doing it this way is much easier than doing the web service. It's too hard to figure out all the nesting complex types, and there is no debugging help.

    Thanks again!

    Uly

    Russ S. October 20, 2009 at 3:00 PM  

    Thank you very much!

    I was able to rewrite this code using implicit structs and arrays in CFScript so that the data is much easier to read.

    I'm glad you made this post, it has saved me hours of time!

    cfSearching October 21, 2009 at 9:33 AM  

    @Uly,

    Glad to hear you are making progress at last!

    @Russ S,

    You are welcome. It is always good hear code was helpful :) I totally agree about implicit notation being more readable. Though given the date of this entry, I probably avoided it for MX7 compatibility (Yes, that entry is that old ;-)

    -Leigh

    deepa December 2, 2009 at 2:45 PM  

    I am trying to use the callTag fedex webservices and keep getting

    Cannot perform web service invocation processTag.

    java.lang.NumberFormatException: Invalid positiveInteger: 0

    cfSearching December 2, 2009 at 3:09 PM  

    @Deepa,

    The PositiveInteger class does not accept zeros. As the name implies, the value has to be positive (ie greater than zero) ;). NonNegativeInteger does accept zeros.

    -Leigh

    deepa December 2, 2009 at 3:15 PM  

    I am not using the positive integer class.
    I have used almost similar code as your's for rate services and that works but the ship service does not.

    Here's my code.
    http://www.houseoffusion.com/groups/cf-talk/thread.cfm/threadid:60544

    cfSearching December 2, 2009 at 3:23 PM  

    @deepa,

    ColdFusion / Axis is using the PositiveInteger internally for one of the values you are passing. Can you post a link to the wsdl on your houseoffusion post?

    -Leigh

    deepa December 2, 2009 at 3:33 PM  

    it's the shipService_v7.wsdl and I have downloaded it to my local system from the fedex site.

    Uly December 10, 2009 at 10:04 AM  

    Hi,

    Does anyone have any experience printing shipping labels from fedex using cfprint to zebra direct thermal printers?

    I set up my shipRequest to output labels in pdf and PAPER_4X6 format. I then send this output to my zebra direct thermal printer using cfprint. However, the resulting label printout is shrunk eventhough the I am using 4x6 labels.

    I am guessing that this is because while the shipping label that fedex returns is 4x6, the pdf itself is letter size. So the zebra printer resizes this into 4x6, resulting in a shrunken label.

    Anyone has any idea what to do to print shipping labels directly to zebra printer?

    Thanks,
    Uly

      © Blogger templates The Professional Template by Ourblogtemplates.com 2008

    Header image adapted from atomicjeep