Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UPS Shipping Tutorial (PHP)

I finally managed to set up an UPS shipment XML request and I would like to share it with other people struggling with it. So here is a complete PHP object oriented valid XML according to newest documentation from 2017.

Im doing this because there are not many helpful information on the internet and I want to change that.

This code also solves many common errors about which you can find questions here on StackOverflow but mostly without answers. This is not a question, its a tutorial for those who will be doing UPS Shipments in the future.

I have managed to solve these common UPS errors with my code:

The XML document is not well formed -> This error was a bit tricky because I have checked my XML structure thousand times and I had it 100% the same as the documentation said BUT I had an OLD DOCUMENTATION so few things got changed so when I recreated the structure to the newest one the problem was solved.

The XML document is well formed but the document is not valid -> I really dont know what the hell was causing this error but changing the "sending logic" a little bit solved it (you can take a look at the end of XML structure how its done).

After I solved these two "biggest" errors UPS Requests started to work and the proper error messages were coming saying more details about whats wrong... for example "AccessLicenseNumber invalid" and so on

So the first thing you should do is to register on UPS Website and download newest documentation from there.

Note: This code sends first request called "ConfirmRequest" which will get a response "ConfirmResponse" and second request called "AcceptRequest" which results in "AcceptResponse" according to image below!

enter image description here

like image 603
Zdeněk Bednařík Avatar asked Apr 20 '17 08:04

Zdeněk Bednařík


1 Answers

So here is the complete script that will create your XML structures, send them to XML and gives you responses.

<?php
// ACCESS: You will get these after you register on UPS Website
$AccessLicenseNumber = 'xxxxxxx';
$UserID = 'xxxxxxx';
$Password = 'xxxxxxxxx';
// REQUEST *********************************
$CustomerContext = "something";
$RequestAction = "ShipConfirm";     // These values are contained in documentation 
$RequestOption = "nonvalidate";
.........
.........
......... 
And many others ...

And here is the XML Structure!! I have red the whole documentation to write down the necessary informations about every single element so you can easily debug your XML when you insert for example name longer then 30 characters

IMPORTANT: You may have noticed that I have only one header even though the documentation says that it wants TWO HEADERS, thats true. But I tried to send it only with one and it works so its not necessary to have two of them.

1. ShipmentConfirmRequest

$domtree = new DOMDocument('1.0');

// <AccessRequest>
$AccessRequest = $domtree->createElement("AccessRequest");
$AccessRequest->setAttribute("xml:lang", "en_US");
$domtree->appendChild($AccessRequest);
        // <AccessLicenseNumber>
    $AccessRequest->appendChild($domtree->createElement('AccessLicenseNumber', $AccessLicenseNumber));
        // <UserId>
    $AccessRequest->appendChild($domtree->createElement('UserId', $UserID));
        // <Password>
    $AccessRequest->appendChild($domtree->createElement('Password', $Password));
// </AccessRequest>

// <ShipmentConfirmRequest>
$ShipmentConfirmRequest = $domtree->createElement("ShipmentConfirmRequest");
$ShipmentConfirmRequest->setAttribute("xml:lang", "en_US");
$domtree->appendChild($ShipmentConfirmRequest);
        // <Request>
    $Request = $domtree->createElement("Request");
    $ShipmentConfirmRequest->appendChild($Request);
                // <TransactionReference>
        $TransactionReference = $domtree->createElement("TransactionReference");
        $Request->appendChild($TransactionReference);
                    // <CustomerContext>
                    $TransactionReference->appendChild($domtree->createElement('CustomerContext', $CustomerContext)); // Length: 1-512, Required: No
                // </TransactionReference>
                // <RequestAction>
                $Request->appendChild($domtree->createElement('RequestAction', $RequestAction)); // Length: 10, Required: Yes, Must be "ShipConfirm"
                // <RequestOption>
                $Request->appendChild($domtree->createElement('RequestOption', $RequestOption)); // Length: 1-256, Required: Yes, "validate" or "nonvalidate"
        // </Request>
        // <Shipment>
        $Shipment = $domtree->createElement("Shipment");
        $ShipmentConfirmRequest->appendChild($Shipment);
            // <Shipper>
            $Shipper = $domtree->createElement("Shipper");
            $Shipment->appendChild($Shipper);
                // <Name>
                $Shipper->appendChild($domtree->createElement('Name', $ShipperName)); // Length: 1-35, Required: Yes, Company Name
                // <AttentionName>
                $Shipper->appendChild($domtree->createElement('AttentionName', $ShipperAttentionName)); // Length: 1-35, Required: Cond, Required if destination is international
                // <PhoneNumber>
                $Shipper->appendChild($domtree->createElement('PhoneNumber', $ShipperPhoneNumber)); // Length: 1-15, Required: Cond
                // <ShipperNumber>
                $Shipper->appendChild($domtree->createElement('ShipperNumber', $ShipperNumber)); // Length: 6, Required: Yes
                // <Address>
                $Address = $domtree->createElement('Address');
                $Shipper->appendChild($Address);
                    // <AddressLine1>
                    $Address->appendChild($domtree->createElement('AddressLine1', $ShipperAddressLine)); // Length: 1-35, Required: Yes
                    // <City>
                    $Address->appendChild($domtree->createElement('City', $ShipperCity)); // Length: 1-30, Required: Yes
                    // <StateProvinceCode>
                    $Address->appendChild($domtree->createElement('StateProvinceCode', $ShipperStateProvinceCode)); // Length: 2-5, Required: Cond, Required if shipper is in the US or CA.
                    // <PostalCode>
                    $Address->appendChild($domtree->createElement('PostalCode', $ShipperPostalCode)); // Length: 1-10, Required: Cond, For all other countries, the postal code is optional
                    // <CountryCode>
                    $Address->appendChild($domtree->createElement('CountryCode', $ShipperCountryCode)); // Length: 2, Required: Yes
                // </Address>
            // </Shipper>
            // <ShipTo>
            $ShipTo = $domtree->createElement("ShipTo");
            $Shipment->appendChild($ShipTo);
                // <CompanyName>
                $ShipTo->appendChild($domtree->createElement('CompanyName', $ShipToCompanyName)); // Length: 1-35, Required: Yes
                // <AttentionName>
                $ShipTo->appendChild($domtree->createElement('AttentionName', $ShipToAttentionName)); // Length: 1-35, Required: Cond, for UPS Next Day Air Early service, and when ShipTo country is different than ShipFrom country.
                // <PhoneNumber>
                $ShipTo->appendChild($domtree->createElement('PhoneNumber', $ShipTo_phone_number)); // Length: 1-15, Required: Cond, Required for UPS Next Day Air Early service, and when Ship To country is different than the ShipFrom country.
                // <Address>
                $Address2 = $domtree->createElement('Address');
                $ShipTo->appendChild($Address2);
                    // <AddressLine1>
                    $Address2->appendChild($domtree->createElement('AddressLine1', $ShipToAddressLine)); // Length: 1-35, Required: Yes
                    // <City>
                    $Address2->appendChild($domtree->createElement('City', $ShipToCity)); // Length: 1-30, Required: Yes
                    // <StateProvinceCode>
                    $Address2->appendChild($domtree->createElement('StateProvinceCode', $ShipToStateProvinceCode)); // Length: 2-5, Required: Cond, Required if shipper is in the US or CA.
                    // <PostalCode>
                    $Address2->appendChild($domtree->createElement('PostalCode', $ShipToPostalCode)); // Length: 1-10, Required: Cond, For all other countries, the postal code is optional
                    // <CountryCode>
                    $Address2->appendChild($domtree->createElement('CountryCode', $ShipToCountryCode)); // Length: 2, Required: Yes
                // </Address>
            // </ShipTo>
            // <PaymentInformation>
            $PaymentInformation = $domtree->createElement("PaymentInformation");
            $Shipment->AppendChild($PaymentInformation);
                // <Prepaid>
                $Prepaid = $domtree->createElement("Prepaid");
                $PaymentInformation->appendChild($Prepaid);
                    // <BillShipper>
                    $BillShipper = $domtree->createElement("BillShipper");
                    $Prepaid->appendChild($BillShipper);
                        // <AccountNumber>
                        $BillShipper->appendChild($domtree->createElement('AccountNumber', $AccountNumber)); // Length: 6, Required: Cond, Based on PaymentInformation container, Must be the same UPS account number as the one provided in Shipper/ShipperNumber.
                    // </BillShipper>
                // </Prepaid>
            // </PaymentInformation>
            // <Service>
            $Service = $domtree->createElement("Service");
            $Shipment->appendChild($Service);
                // <Code>
                $Service->appendChild($domtree->createElement('Code', $ServiceCode)); // Length: 2, Required: Yes, 01 = Next Day Air 02 = 2nd Day Air ...
            // </Service>


Here is a for loop which creates as many <Package> elements as you want (You can remove this if you want to send only one PACKAGE)
            for ($i = 0; $i < sizeof($Pack_IDs); $i++) {
            // <Package>
            $Package = $domtree->createElement('Package');
            $Shipment->appendChild($Package);
                // <PackagingType>
                $PackagingType = $domtree->createElement('PackagingType');
                $Package->appendChild($PackagingType);
                    // <Code>
                    $PackagingType->appendChild($domtree->createElement('Code', $PackageTypeCode)); // Length: 2, Required: Yes, 01 = UPS Letter 02 = Customer Supplied Package ...
                // </PackagingType>
                // <Description>
                $Package->appendChild($domtree->createElement('Description', $Description)); // Length: 1-35, Required: Cond, Required for shipment with return service.
                // </Description>
                // <Dimensions>
                $Dimensions = $domtree->createElement('Dimensions'); // Required: Cond, Length + 2*(Width + Height) must be less than or equal to 130 IN or 330 CM.
                $Package->appendChild($Dimensions);
                    // <UnitOfMeasurement>
                    $UnitOfMeasurement = $domtree->createElement('UnitOfMeasurement');
                    $Dimensions->appendChild($UnitOfMeasurement);
                        // <Code>
                        $UnitOfMeasurement->appendChild($domtree->createElement('Code', $DimensionUnitOfMeasurementCode)); // Length: 2, Required: Yes*, Codes are: IN = Inches, CM = Centimeters, 00 = Metric Units Of Measurement, 01 = English Units of Measurement.
                    // </UnitOfMeasurement>
                    // <Length>
                    $Dimensions->appendChild($domtree->createElement('Length', $PackageLength)); // Length: 9, Required: Yes*, Valid values are 0 to 108 IN and 0 to 270 CM.
                    // <Width>
                    $Dimensions->appendChild($domtree->createElement('Width', $PackageWidth)); // Length: 9, Required: Yes*
                    // <Height>
                    $Dimensions->appendChild($domtree->createElement('Height', $PackageHeight)); // Length: 9, Required: Yes*
                // </Dimensions>
                // <PackageWeight>
                $PackageWeight = $domtree->createElement('PackageWeight');
                $Package->appendChild($PackageWeight);
                    // <UnitOfMeasurement>
                    $UnitOfMeasurement2 = $domtree->createElement('UnitOfMeasurement');
                    $PackageWeight->appendChild($UnitOfMeasurement2);
                        // <Code>
                        $UnitOfMeasurement2->appendChild($domtree->createElement('Code', $WeightUnitOfMeasurementCode)); // Length: 3, Required: Cond, LBS = Pounds KGS = Kilograms OZS = Ounces ...
                    // <Weight>
                    $PackageWeight->appendChild($domtree->createElement('Weight', $Pack_weights[$i])); // Length: 1-5, Required: Yes*, Weight accepted for letters/envelopes.
                    // </UnitOfMeasurement>
                // </PackageWeight>
            // </Package>
            }
        // </Shipment>
        // <LabelSpecification>
        $LabelSpecification = $domtree->createElement('LabelSpecification');
        $ShipmentConfirmRequest->appendChild($LabelSpecification);
            // <LabelPrintMethod>
            $LabelPrintMethod = $domtree->createElement('LabelPrintMethod');
            $LabelSpecification->appendChild($LabelPrintMethod);
                // <Code>
                $LabelPrintMethod->appendChild($domtree->createElement('Code', $LabelCode)); // Length: 4, Required: Yes*
            // </LabelPrintMethod>
            // <LabelImageFormat>
            $LabelImageFormat = $domtree->createElement('LabelImageFormat');
            $LabelSpecification->appendChild($LabelImageFormat);
                // <Code>
                $LabelImageFormat->appendChild($domtree->createElement('Code', $LabelImageCode)); // Length: 3, Required: Cond, Required if ShipmentConfirmRequest/LabelSpecification/LabelPrintMethod/Code = GIF. Valid values are GIF or PNG. Only GIF is supported on the remote server.
            // </LabelImageFormat>
        // </LabelSpecification>
// </ShipmentConfimRequest>

So now we have the XML Structure prepared and filled. I recommend you to print the XML structure and carefully compare it with the documentation few times to avoid many errors just because you appended something to wrong parent or whatever.

And here is the code you need, to send your XML and receive a request.

2. ShipmentConfirmResponse

$domtree->preserveWhiteSpace = true;
$domtree->formatOutput = true;
$xml_string = $domtree->saveXML();

// UPS Address
$url = 'https://wwwcie.ups.com/ups.app/xml/ShipConfirm'; // IMPORTANT: This is a testing URL address, dont be scared to send your request (Real URL is different -> documentation)
// SEND THE REQUEST
$stream_options = array(
    'http' => array(
       'method'  => 'POST',
       'header'  => 'Content-type: application/x-www-form-urlencoded',
       'content' => "$xml_string",
    ),
);
$context  = stream_context_create($stream_options);
$response = file_get_contents($url, null, $context); // Response XML structure 

Now you have the response XML structure stored in a $response variable so you can access any data you want from it

// Response handling
$ShipmentConfirmResponse = new SimpleXMLElement($response);
if ((string)$ShipmentConfirmResponse->Response->ResponseStatusCode == 1) { // If the response is "success" then continue with second request
    // If ShipmentCofirmRequest is successful, send ShipmentAcceptRequest
    $ShipmentDigest = $ShipmentConfirmResponse->ShipmentDigest;
    AcceptRequest($AccessLicenseNumber, $UserID, $Password, $CustomerContext, $ShipmentDigest, $ShipmentID, $connect); // After first successful request call a function which will send AcceptRequest

} else {
    echo $ShipmentConfirmResponse->Response->Error->ErrorDescription;
}

So here is a function for sending second request "AcceptRequest" Its the same code but with different XML structure.

3. ShipmentAcceptRequest

function AcceptRequest ($AccessLicenseNumber, $UserID, $Password, $CustomerContext, $ShipmentDigest) {
    $RequestAction = "ShipAccept";

    $domtree = new DOMDocument('1.0');

    // <AccessRequest>
    $AccessRequest = $domtree->createElement("AccessRequest");
    $domtree->appendChild($AccessRequest);
            // <AccessLicenseNumber>
        $AccessRequest->appendChild($domtree->createElement('AccessLicenseNumber', $AccessLicenseNumber));
            // <UserId>
        $AccessRequest->appendChild($domtree->createElement('UserId', $UserID));
            // <Password>
        $AccessRequest->appendChild($domtree->createElement('Password', $Password));
    // </AccessRequest>
    // <ShipmentAcceptRequest>
    $ShipmentAcceptRequest = $domtree->createElement("ShipmentAcceptRequest");
    $domtree->appendChild($ShipmentAcceptRequest);
            // <Request>
        $Request = $domtree->createElement("Request");
        $ShipmentAcceptRequest->appendChild($Request);
                    // <TransactionReference>
            $TransactionReference = $domtree->createElement("TransactionReference");
            $Request->appendChild($TransactionReference);
                        // <CustomerContext>
                        $TransactionReference->appendChild($domtree->createElement('CustomerContext', $CustomerContext));
                    // </TransactionReference>
                    // <RequestAction>
                    $Request->appendChild($domtree->createElement('RequestAction', $RequestAction));
            // </Request>
            // <ShipmentDigest>
            $ShipmentAcceptRequest->appendChild($domtree->createElement('ShipmentDigest', $ShipmentDigest));
    // </ShipmentAcceptRequest>

And again you send it with this code. Then you need to take a look inside a $response variable if it was successful or not and you can store the tracking ID and Shipping price and do whatever you want with it.

IMPORTANT: You will also get Base64 encoded label image

4. ShipmentAcceptResponse

    $domtree->preserveWhiteSpace = true;
    $domtree->formatOutput = true;
    $xml_string = $domtree->saveXML();
    $url = 'https://wwwcie.ups.com/ups.app/xml/ShipAccept'; // Again testing URL

    $stream_options = array(
        'http' => array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => "$xml_string",
    ),
);
    $context  = stream_context_create($stream_options);
    $response = file_get_contents($url, null, $context);

    $ShipmentAcceptResponse = new SimpleXMLElement($response);

    if ((string)$ShipmentAcceptResponse->Response->ResponseStatusCode == 1) {

        $Tracking_ID = $ShipmentAcceptResponse->ShipmentResults->PackageResults->TrackingNumber;
        $Price = $ShipmentAcceptResponse->ShipmentResults->ShipmentCharges->TransportationCharges->MonetaryValue;
        $ImageBase64 = $ShipmentAcceptResponse->ShipmentResults->PackageResults->LabelImage->GraphicImage;

    } else {
        echo $ShipmentAcceptResponse->Response->Error->ErrorDescription;
    }
}
like image 124
Zdeněk Bednařík Avatar answered Nov 02 '22 22:11

Zdeněk Bednařík