This is the Fedex new API Calling method of getting rate and other infomation
The Job:
For many years, I have been downloading FedEx's rate
charts into FoxPro tables and using this to compute the freight cost for an
invoice in our accounting system. The accounting system is written in
FoxPro 6.0 and includes source code so this makes modifications very easy.
However, there is very little information on how to access FedEx's API from
FoxPro.
The project is to send to FedEx everything necessary to compute the rate.
Source location, destination, box count, box weights, box sizes, declared value,
COD and any other relevant information and get back either the list rate or my
contractual rate, all from inside my FoxPro program in real time.
This blog only covers one of the many API functions FedEx is making available. Getting the price is the one I wanted, You might want to get quotes on all available services for the information you pass to it. You can setup a shipment and print the labels, Get address validations. There is a list on the developer site. https://developer.fedex.com/api/en-us/home.html
Lets go through this. Create an account on the
developer site, then you need to create an organization and to this organization, you would need to add a project like getrate. When
you do this, you will need to add an API to the project. Choose Ship Rates and at the bottom, choose rates and transit times API. You
would also need to associate your FedEx account to this organization so that their rates get returned.
Once this is setup correctly, we can work on the code
Read every line carefully. There are places that you will need to replace your information and I have chosen options that you might
not want. and you might have information such as dangerous goods that I don't have to deal with.
And of course the other part is I have no idea where you are generating your information. Do you have box sizes and weights? I load an array in the calling program. I have my address but you need to feed it your address and the destination address. So you will need to be intimately familiar with this code. But this should get you most of the way.
***this version is to construct the json call using the new api
SET TALK OFF
SET SAFETY OFF
**this is for testing
***this version is to construct the json call using the new api
SET TALK OFF
SET SAFETY OFF
IF SYS(0)="YOUR COMPUTER NAME"
mstandalone=.F.
ELSE
mstandalone=.F.
ENDIF
malias=""
codpkg=.T.
STORE 0 TO minsur,codamt,mdas,fuel,mresdel
mresidential="false"
WAIT WINDOW "Contacting FedEx's servers for quote." NOWAIT
IF mstandalone
mservice="FEDEX_GROUND"
mcity="brooklyn"
mstate="NY"
mzip="11217"
mcountrycode="US"
mresidential="false"
rcod=.F.
mshipmentvalue=318
ELSE
malias=ALIAS()
rcod=mcod
IF mshipresid
mresidential="true"
ENDIF
DO CASE
CASE mservice="FEDEXGRND" AND mshipresid
mservice="GROUND_HOME_DELIVERY"
CASE mservice="FEDEXGRND"
mservice="FEDEX_GROUND"
CASE mservice="FEDEX HOME"
mservice="GROUND_HOME_DELIVERY"
mshipresid=.T.
CASE mservice="FEDEX PO "
codpkg=.F.
mservice="PRIORITY_OVERNIGHT"
CASE mservice="FEDEX SO "
codpkg=.F.
mservice="STANDARD_OVERNIGHT"
CASE mservice="FEDEX 2DAY "
codpkg=.F.
mservice="FEDEX_2_DAY"
CASE mservice="2DAYAM "
codpkg=.F.
mservice="FEDEX_2_DAY_AM"
CASE mservice="EXPRESS "
codpkg=.F.
mservice="FEDEX_EXPRESS_SAVER"
CASE mservice="FEDEX FO "
codpkg=.F.
mservice="FIRST_OVERNIGHT"
CASE mservice="FEDEXINTECO "
mservice="INTERNATIONAL_ECONOMY"
CASE mservice="FEDEXINTPR "
mservice="INTERNATIONAL_PRIORITY"
CASE mservice="FEDEXINTFO "
mservice="INTERNATIONAL_FIRST"
OTHERWISE
mservice="FEDEX_GROUND"
ENDCASE
mshipmentvalue=mdollar
ENDIF
raddress1=" "
raddress2=" "
rcity=mcity &&"dunstable"
rstate=mstate && "MA"
rzip=mzip &&"01827"
rcountry=mcountrycode
mactweight=0
**convert the MB aray into this array
IF mstandalone
DIME marray(2,4)
marray(1,1)=12 &&WEIGHT
marray(1,2)=12 &&LENGTH
marray(1,3)=12 &&WIDTH
marray(1,4)=12 &&DEPTH
marray(2,1)=12 &&WEIGHT
marray(2,2)=12 &&LENGTH
marray(2,3)=12 &&WIDTH
marray(2,4)=12 &&DEPTH
mactualweight=1
mboxvalue=mshipmentvalue/ALEN(marray,1)
ELSE
SET COMPATIBLE OFF
FOR a=1 TO ALEN(mb,1)
IF NOT EMPTY(mb(a,1))
DIME marray(a,4)
marray(a,1)=mb(a,3) &&WEIGHT
marray(a,2)=mb(a,8) &&LENGTH
marray(a,3)=mb(a,9) &&WIDTH
marray(a,4)=mb(a,10) &&DEPTH
ENDIF
mactweight=mactweight+mb(a,4)
mboxvalue=mshipmentvalue/ALEN(marray,1)
NEXT
SET COMPATIBLE ON
ENDIF
mtotalcnt=0
mtotalweight=0
FOR a=1 TO ALEN(marray,1)
mtotalcnt=mtotalcnt+1
mtotalweight=mtotalweight+marray(a,1)
ENDFOR
**you will be given the sandbox info. Later when you get it working, you can add the production under it which will supersede the
sandbox keys
**sandbox keys
mclientid = Enter your sandbox client ID here"
mclientsecret = "Enter your client secret here"
lcurl="https://apis-sandbox.fedex.com/oauth/token"
*production keys
mclientid = "Enter your production client ID here"
mclientsecret = "Enter your production clientsecret here"
lcurl="https://apis.fedex.com/oauth/token"
*if you have read my quickbooks blog, there is a need to choose a client company. Here there is not so we can skip
*that authorization portion.
**every time you do this, you will be handed a token which will be included in the API call
* Begin the OAuth2 Authorization code flow. This returns a URL that should be loaded in a browser.
*?"Start the flow."
************************oauth2 flow *******************
lcdata= ;
"grant_type=client_credentials" + ;
"&client_id=" + mclientid + ;
"&client_secret=" + mclientsecret
lohttp = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
lohttp.OPEN("POST", lcurl, .F.)
lohttp.setrequestheader("Content-type","application/x-www-form-urlencoded")
lohttp.setrequestheader("Accept","Application/json")
lohttp.SEND(lcdata)
IF lohttp.STATUS = 200
lcresponse = lohttp.responsetext
* ? lcResponse
ELSE
? "HTTP Error:"+lohttp.STATUS
? lohttp.responsetext
ENDIF
lojson = jsonparse(lohttp.responsetext)
lcaccesstoken = lojson.access_token
lctokentype = lojson.token_type
lnexpiresin = lojson.expires_in
lcscope = lojson.scope
********************end of oauth2 flow *********************
*it is that easy
*******************API call*********************************
RELEASE lohttp
**I have superseded this URL with the production endpoint
lcurl="https://apis-sandbox.fedex.com/rate/v1/rates/quotes"
lcurl="https://apis.fedex.com/rate/v1/rates/quotes"
*Sandbox account #
**lcdata=lcdata+' "accountNumber": { "value": "740561073" },'
lcdata='{'
**enter the account number instead of the 123456789
lcdata=lcdata+' "accountNumber": { "value": "123456789" },'
lcdata=lcdata+' "rateRequestControlParameters": {'
lcdata=lcdata+' "returnTransitTimes": false'
lcdata=lcdata+' },'
lcdata=lcdata+' "requestedShipment": {'
lcdata=lcdata+' "rateRequestType": ["LIST"],'
lcdata=lcdata+' "totalPackageCount": '+ALLT(STR(mtotalcnt,5))+','
lcdata=lcdata+' "serviceType": "'+mservice+'",'
lcdata=lcdata+' "shipper": { "address": { "postalCode": "type your zip code here", "countryCode": "US" } },'
lcdata=lcdata+' "recipient": { "address": { "postalCode": "'+rzip+'", "countryCode": "US","residential": '+mresidential+' } },'
lcdata=lcdata+' "pickupType": "USE_SCHEDULED_PICKUP",'
lcdata=lcdata+' "packagingType": "YOUR_PACKAGING",'
IF (rcod AND rcountry="US") AND codpkg
lcdata=lcdata+' "specialServicesRequested": {'
lcdata=lcdata+' "codDetail": {'
lcdata=lcdata+' "codCollectionAmount": { "amount": 500.00, "currency": "USD" },'
lcdata=lcdata+' "collectionType": "GUARANTEED_FUNDS" '
lcdata=lcdata+' }'
lcdata=lcdata+' },'
ENDIF
lcdata=lcdata+' "requestedPackageLineItems": ['
FOR a=1 TO ALEN(marray,1)
lcdata=lcdata+'{'
lcdata=lcdata+' "weight": { "units": "LB", "value": '+ALLTRIM(STR(marray(a,1),5,1))+' },'
lcdata=lcdata+' "dimensions": { "length": '+ALLTRIM(STR(marray(a,2),5,0))+', "width": '+ALLTRIM(STR(marray(a,3),5,0))+', "height":
'+ALLTRIM(STR(marray(a,4),5,0))+', "units": "IN" },'
lcdata=lcdata+' "declaredValue": { "amount": '+ALLT(STR(mboxvalue,8))+', "currency": "USD" }'
lcdata=lcdata+' },'
NEXT
lcdata=LEFT(lcdata,LEN(lcdata)-1)
lcdata=lcdata+' ]'
lcdata=lcdata+' }'
lcdata=lcdata+'}'
lohttp2 = CREATEOBJECT("WinHttp.WinHttpRequest.5.1")
lohttp2.OPEN("POST", lcurl, .F.)
lohttp2.setrequestheader("Content-Type","application/json")
lohttp2.setrequestheader("Authorization","Bearer "+ lcaccesstoken)
lohttp2.SEND(lcdata)
IF lohttp2.STATUS = 200
lcresponse = lohttp2.responsetext
* ? lcResponse
mresult="SUCCESS"
ELSE
_CLIPTEXT=lohttp2.responsetext
mresult="HTTP Error:"+ lohttp2.STATUS
myrate=0
listrate=0
mdimweight=0
** ? lohttp2.responsetext
**for diagnostics
RETURN mresult+SPACE(12)+STR(myrate,12,2)+STR(listrate,12,2)
ENDIF
**parse the resulting text file
* lcJson = your JSON string
lcjson = lcresponse
loscript = CREATEOBJECT("MSScriptControl.ScriptControl")
loscript.language = "JScript"
* Inject JSON into JS context
loscript.addcode([var data = ] + lcjson + [;])
***I found the best way was to write individual procedures to extract exactly what I needed from the JSON
***if all i wanted was the total charge, I could stop there but I may want to display all of the pieces somewhere so I get all of the pieces
myrate=0
DO gettotalcharge
listrate=0
DO getlistrate
mdimweight=0
DO getbillingweight
minsur=0
***insurance has been removed
DO getinsuredvalue
**at this point,
myrate=myrate-minsur
listrate=listrate-minsur
mfuel=0
DO getfuel
mresdel=0
DO getresident
mdas=0
DO getextend
mcodamt=0
DO getcodamt
IF NOT EMPTY(malias)
SELECT (malias)
ENDIF
WAIT CLEAR
**you may want to return just the total amount
RETURN mresult+STR(mdimweight,12,2)+STR(myrate,12,2)+STR(listrate,12,2)
FUNCTION gettotalcharge
* lcJson = FedEx JSON response string
* Get total net charge for the first shipment
myrate=0
myrate = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].totalNetCharge")
RETURN
FUNCTION getlistrate
* Count shipment details
lnshipmentcount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails.length")
lctotallistcharge = 0
lccurrency = "USD"
FOR lns = 0 TO lnshipmentcount - 1
lcratetype = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[" + STR(lns,2)+"].rateType")
IF lcratetype == "LIST"
lctotallistcharge = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[" + STR(lns,2)+"].totalNetCharge")
EXIT
ENDIF
ENDFOR
listrate=lctotallistcharge
RETURN
FUNCTION getbillingweight
* Get total billed weight
lcweightvalue = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].shipmentRateDetail.totalBillingWeight.value")
lcweightunits = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].shipmentRateDetail.totalBillingWeight.units")
mdimweight=lcweightvalue
RETURN
FUNCTION getinsuredvalue
* Only one shipment
lnpkgcount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages.length")
* Prepare array: package#, insured value amount, currency
DIMENSION ainsured[lnPkgCount,3]
FOR lnp = 0 TO lnpkgcount - 1
lnsurchargecount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges.length")
lcinsamount = 0
lccurrency = "USD"
FOR lnj = 0 TO lnsurchargecount - 1
lctype = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges["
+ STR(lnj,2)+"].type")
IF lctype == "INSURED_VALUE"
lcinsamount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.surcharges[" + STR(lnj,2)+"].amount")
lccurrency = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.currency")
EXIT && Found Insured Value
ENDIF
ENDFOR
ainsured[lnP+1,1] = lnp+1
ainsured[lnP+1,2] = lcinsamount
ainsured[lnP+1,3] = lccurrency
ENDFOR
FOR lni = 1 TO lnpkgcount
minsur= minsur + ainsured[lnI, 2]
ENDFOR
RETURN
FUNCTION getfuel
* Count packages in the first service
* Only one shipment
lnpkgcount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages.length")
* Prepare array: package#, fuel surcharge amount, currency
DIMENSION afuel[lnPkgCount,3]
FOR lnp = 0 TO lnpkgcount - 1
lnsurchargecount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges.length")
lcfuelamount = 0
lccurrency = "USD"
FOR lnj = 0 TO lnsurchargecount - 1
lctype = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges["
+ STR(lnj,2)+"].type")
IF lctype == "FUEL"
lcfuelamount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.surcharges[" + STR(lnj,2)+"].amount")
lccurrency = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.currency")
EXIT && Found Fuel Surcharge
ENDIF
ENDFOR
afuel[lnP+1,1] = lnp+1
afuel[lnP+1,2] = lcfuelamount
afuel[lnP+1,3] = lccurrency
ENDFOR
mfuel=0
FOR lni = 1 TO lnpkgcount
mfuel= mfuel + afuel[lnI, 2]
ENDFOR
RETURN
FUNCTION getresident
* Only one shipment
lnpkgcount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages.length")
* Prepare array: package#, residential surcharge amount, currency
DIMENSION ares[lnPkgCount,3]
FOR lnp = 0 TO lnpkgcount - 1
lnsurchargecount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges.length")
lcresamount = 0
lccurrency = "USD"
FOR lnj = 0 TO lnsurchargecount - 1
lctype = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges["
+ STR(lnj,2)+"].type")
IF lctype == "RESIDENTIAL_DELIVERY" OR lctype == "RESIDENTIAL"
lcresamount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.surcharges[" + STR(lnj,2)+"].amount")
lccurrency = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.currency")
EXIT && Found residential surcharge
ENDIF
ENDFOR
ares[lnP+1,1] = lnp+1
ares[lnP+1,2] = lcresamount
ares[lnP+1,3] = lccurrency
ENDFOR
mresdel=0
FOR lni = 1 TO lnpkgcount
mresdel= mresdel + ares[lnI, 2]
ENDFOR
RETURN
FUNCTION getextend
* Only one shipment
lnpkgcount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages.length")
* Prepare array: package#, extended area surcharge amount, currency
DIMENSION aextarea[lnPkgCount,3]
FOR lnp = 0 TO lnpkgcount - 1
lnsurchargecount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages["+ STR(lnp,2)+"].packageRateDetail.surcharges.length")
lcextamount = 0
lccurrency = "USD"
FOR lnj = 0 TO lnsurchargecount - 1
lctype = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges["
+ STR(lnj,2)+"].type")
IF lctype == "OUT_OF_AREA" OR lctype == "EXTENDED_AREA"
lcextamount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.surcharges[" + STR(lnj,2)+"].amount")
lccurrency = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.currency")
EXIT && Found Extended Area surcharge
ENDIF
ENDFOR
aextarea[lnP+1,1] = lnp+1
aextarea[lnP+1,2] = lcextamount
aextarea[lnP+1,3] = lccurrency
ENDFOR
mdas=0
FOR lni = 1 TO lnpkgcount
mdas= mdas + aextarea[lnI, 2]
ENDFOR
RETURN
FUNCTION getcodamt
* Only one shipment
lnpkgcount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages.length")
* Prepare array: package#, COD amount, currency
DIMENSION acod[lnPkgCount,3]
FOR lnp = 0 TO lnpkgcount - 1
lnsurchargecount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges.length")
lccodamount = 0
lccurrency = "USD"
FOR lnj = 0 TO lnsurchargecount - 1
lctype = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" + STR(lnp,2)+"].packageRateDetail.surcharges["
+ STR(lnj,2)+"].type")
IF lctype == "COD"
lccodamount = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2)+"].packageRateDetail.surcharges[" + STR(lnj,2)+"].amount")
lccurrency = loscript.EVAL("data.output.rateReplyDetails[0].ratedShipmentDetails[0].ratedPackages[" +
STR(lnp,2) + "].packageRateDetail.currency")
EXIT && Found COD surcharge
ENDIF
ENDFOR
acod[lnP+1,1] = lnp+1
acod[lnP+1,2] = lccodamount
acod[lnP+1,3] = lccurrency
ENDFOR
mcodamt=0
FOR lni = 1 TO lnpkgcount
mcodamt= mcodamt + acod[lnI, 2]
ENDFOR
RETURN
FUNCTION jsonparse(tcjson)
LOCAL losc
losc = CREATEOBJECT("ScriptControl")
losc.language = "JScript"
* Wrap JSON so JScript evaluates it as an object
losc.addcode("function parse(){ return " + tcjson + "; }")
RETURN losc.RUN("parse")
ENDFUNC