Tuesday, June 10, 2014

Notes about using the UPS Shipping API with PHP

I am working through integrating the UPS API on a client's system.  For my own benefit and the benefit of others, here are some notes:


1.  To test the Web services sample PHP code out of the box, the files in the Web Service "SCHEMA-WSDLs" directory need to be in the same directory as the sample php code.

2.  The WSDL and XSD files and the directory they and the sample code are in have to be writeable by the web user or you will get an error like can't import schema UPSSecurity.xsd.  I believe this is because the API needs to write the results to the output file in that directory.  In subsequent testing (see note 8), the directory didn't have to be writeable if I disable creating the output file.

3. My vanilla PHP install under Ubuntu Linux did have the SOAP library required to run the Web Services sample code.  The results of running it did appear in an XML file in the same directory, and cost, tracking number, and encoded label GIF were in those results.

4.  I looked at both the XML and the Web Service APIs.  They both looked similarly complicated.  I chose the Web Service for three reasons:  1) it seems to be favored by UPS, 2) its sample code came in the one-stage shipping style I wanted to use, while the XML sample seemed to have only two-stage shipping examples, and 3) I liked the PHP array method of assembling the shipment request that was shown in the sample code.

5.  I added a second package to the sample PHP code by changing the package definition from
        $shipment['Package'] = $package;
to
        $shipment['Package'][] = $package;

Then I simply defined another package and added it the same way.

6.  I was able to echo a tracking number to the browser by searching for trackingnumber in "Shipping Package WebServices Developers Guide.pdf", finding this reference--/ShipmentResponse/ShipmentResults/PackageResults/TrackingNumber--and using that hint to change this code in SoapShipClient.php:

echo "Response Status: " . $resp->Response->ResponseStatus->Description ."\n";
to
echo "Sample Info: " $resp->ShipmentResults->PackageResults[0]->TrackingNumber;

In the same way, I was able to echo the base64 encoding for the label to the browser by finding GraphicImage in "Shipping Package WebServices Developers Guide.pdf", finding this reference--/ShipmentResponse/ShipmentResults/PackageResults/ShippingLabel/GraphicImage--and using that hint to change this code in SoapShipClient.php:

echo "Response Status: " . $resp->Response->ResponseStatus->Description ."\n";
to
echo "Sample Info: " $resp->ShipmentResults->PackageResults[0]->ShippingLabel->GraphicImage;

7.  I was able to show the sample shipping label to the browser based on what I learned in step 6 using this line of code:
echo 'Sample Info: <img src="data:image/gif;base64, ' . $resp->ShipmentResults->PackageResults[0]->ShippingLabel->GraphicImage . '" />';

8.  I was able to make a label image using the sample code without having a writeable directory by commenting out the section that writes to a file:

echo 'Sample Info: <img src="data:image/gif;base64, ' . $resp->ShipmentResults->PackageResults[0]->ShippingLabel->GraphicImage . '" />';
//save soap request and response to file
//$fw = fopen($outputFileName , 'w');
//fwrite($fw , "Request: \n" . $client->__getLastRequest() . "\n");
//fwrite($fw , "Response: \n" . $client->__getLastResponse() . "\n");
//fclose($fw);

My PHP class

 Note: You should be able to just call the processShipment() method then any or several of the get methods and get some results.But you need to put your account details in the protected properties at the top first where I have asterisks.


/**
 * Haws Ups Shipping
 * Accesses the Ups Shipping Package Web Services (SOAP) API
 *
 * @category    Haws
 * @copyright   Copyright (c) 2014 Tom Haws
 * @author      Tom Haws
 */
class Haws_Ups_Shipping
{
    protected $access = "****************";
    protected $userid = "**********";
    protected $passwd = "********";
    protected $wsdl_path;
    protected $wsdl;
    protected $operation = "ProcessShipment";
    protected $endpointurl = 'https://wwwcie.ups.com/webservices/Ship';
    protected $shipment_details;
    protected $request;
    protected $response;

    public function __construct($shipment_details = null)
    {
        $this->wsdl_path = ROOT_DIR . '/library/Ups/Shipping/Package/Schema-WSDL/';
        $this->wsdl = $this->wsdl_path . "Ship.wsdl";
        $this->shipment_details = $shipment_details;
    }

    private function getRequest()
    {
        if ($this->request) {
        } else if (!$this->shipment_details) {
            $this->request = $this->getFakeRequest();
        } else {
            $this->request = $this->convertHawsShipmentToRequest($this->shipment_details);
        }
        //die(Zend_Debug::dump($this->request, '$this->request'));
        return $this->request;
    }

    /*
     * See UPS "Shipping Package WebServices Developers Guide.pdf" for reference.
     *
     * */
    private function convertHawsShipmentToRequest($shipment_details)
    {
        //create soap request from Haws shipment details
        $requestoption['RequestOption'] = 'nonvalidate';
        $request['Request'] = $requestoption;
        foreach ($shipment_details['packages'] as $sd_package) { // Unique $sd_package required because variable outlasts foreach.
            foreach ($sd_package['items'] as $item) {
                $shipment['Description'][] .= $item['title'];
            }
        }
        $shipment['Description'] = 'Merchandise';
        $shipper['Name'] = 'Tom Haws';
        $shipper['AttentionName'] = 'Buyer';
        $shipper['TaxIdentificationNumber'] = '**********';
        $shipper['ShipperNumber'] = '******';
        $address['AddressLine'] = '***********************';
        $address['City'] = '****';
        $address['StateProvinceCode'] = '**';
        $address['PostalCode'] = '*****';
        $address['CountryCode'] = '**';
        $shipper['Address'] = $address;
        $phone['Number'] = '************';
        $phone['Extension'] = '';
        $shipper['Phone'] = $phone;
        $shipment['Shipper'] = $shipper;

        $shipto['Name'] = $shipment_details['shipment']['to_name'];
        $shipto['AttentionName'] = $shipment_details['shipment']['to_name'];
        $addressTo['AddressLine'][] = $shipment_details['shipment']['to_address'];
        if ($shipment_details['shipment']['to_address2']) {
            $addressTo['AddressLine'][] = $shipment_details['shipment']['to_address2'];
        }
        if (count($addressTo['AddressLine']) === 1) {
            $addressTo['AddressLine'] = $addressTo['AddressLine'][0];
        }
        $addressTo['City'] = $shipment_details['shipment']['to_city'];
        $addressTo['StateProvinceCode'] = $shipment_details['shipment']['to_state'];
        $addressTo['PostalCode'] = $shipment_details['shipment']['to_zip'];
        $addressTo['CountryCode'] = $shipment_details['shipment']['to_country'];
        $phone2['Number'] = '11111111111';
        $shipto['Address'] = $addressTo;
        $shipto['Phone'] = $phone2;
        $shipment['ShipTo'] = $shipto;

        $shipfrom['Name'] = 'Tom Haws';
        $shipfrom['AttentionName'] = 'Buyer';
        $addressFrom['AddressLine'] = '******************';
        $addressFrom['City'] = '****';
        $addressFrom['StateProvinceCode'] = '**';
        $addressFrom['PostalCode'] = '*****';
        $addressFrom['CountryCode'] = '**';
        $phone3['Number'] = '************';
        $shipfrom['Address'] = $addressFrom;
        $shipfrom['Phone'] = $phone3;
        $shipment['ShipFrom'] = $shipfrom;

        $shipmentcharge['Type'] = '01';
        $billshipper['AccountNumber'] = '******';
        $shipmentcharge['BillShipper'] = $billshipper;
        $paymentinformation['ShipmentCharge'] = $shipmentcharge;
        $shipment['PaymentInformation'] = $paymentinformation;
       
        $service['Code'] = '03';
        $service['Description'] = 'Ground';
        $shipment['Service'] = $service;

        foreach ($shipment_details['packages'] as $sd_package) {
            $package['Description'] = $sd_package['name'];
            $packaging['Code'] = '02';
            $packaging['Description'] = 'Customer supplied box';
            $package['Packaging'] = $packaging;
            $unit['Code'] = 'IN';
            $unit['Description'] = 'Inches';
            $dimensions['UnitOfMeasurement'] = $unit;
            $dimensions['Length'] = strval(round($sd_package['length_inches'])); // Always rounded by UPS for label
            $dimensions['Width'] = strval(round($sd_package['width_inches']));
            $dimensions['Height'] = strval(round($sd_package['height_inches']));
            $package['Dimensions'] = $dimensions;
            $unit2['Code'] = 'LBS';
            $unit2['Description'] = 'Pounds';
            $packageweight['UnitOfMeasurement'] = $unit2;
            $packageweight['Weight'] = strval(round($sd_package['weight_pounds'], 3 - floor(log10($sd_package['weight_pounds'])))); // Max strlen 5
            $package['PackageWeight'] = $packageweight;
            $shipment['Package'][] = $package;
        }
        if (count($shipment['Package']) === 1) {
            $shipment['Package'] = $shipment['Package'][0];
        }

        $labelimageformat['Code'] = 'GIF';
        $labelimageformat['Description'] = 'GIF';
        $labelspecification['LabelImageFormat'] = $labelimageformat;
        $labelspecification['HTTPUserAgent'] = 'Mozilla/4.5';
        $shipment['LabelSpecification'] = $labelspecification;
        $request['Shipment'] = $shipment;
        return array($request);
    }

    private function getFakeRequest()
    {
        //create soap request
        $requestoption['RequestOption'] = 'nonvalidate';
        $request['Request'] = $requestoption;

        $shipment['Description'] = 'Ship WS test';
        $shipper['Name'] = 'Tom Haws';
        $shipper['AttentionName'] = 'Buyer';
        $shipper['TaxIdentificationNumber'] = '**********';
        $shipper['ShipperNumber'] = '******';
        $address['AddressLine'] = '**********************';
        $address['City'] = '****';
        $address['StateProvinceCode'] = '**';
        $address['PostalCode'] = '*****';
        $address['CountryCode'] = '**';
        $shipper['Address'] = $address;
        $phone['Number'] = '************';
        $phone['Extension'] = '';
        $shipper['Phone'] = $phone;
        $shipment['Shipper'] = $shipper;

        $shipto['Name'] = 'Tom Haws';
        $shipto['AttentionName'] = 'Tom Haws';
        $addressTo['AddressLine'] = '123 Main St';
        //$addressTo['AddressLine'][] = 'Apt B-2308';
        $addressTo['City'] = 'Roswell';
        $addressTo['StateProvinceCode'] = 'GA';
        $addressTo['PostalCode'] = '30076';
        $addressTo['CountryCode'] = 'US';
        $phone2['Number'] = '11111111111';
        $shipto['Address'] = $addressTo;
        $shipto['Phone'] = $phone2;
        $shipment['ShipTo'] = $shipto;

        $shipfrom['Name'] = 'Tom Haws';
        $shipfrom['AttentionName'] = 'Buyer';
        $addressFrom['AddressLine'] = '******************';
        $addressFrom['City'] = '****';
        $addressFrom['StateProvinceCode'] = '**';
        $addressFrom['PostalCode'] = '*****';
        $addressFrom['CountryCode'] = '**';
        $phone3['Number'] = '************';
        $shipfrom['Address'] = $addressFrom;
        $shipfrom['Phone'] = $phone3;
        $shipment['ShipFrom'] = $shipfrom;

        $shipmentcharge['Type'] = '01';
        $creditcard['Type'] = '06';
        $creditcard['Number'] = '4716995287640625';
        $creditcard['SecurityCode'] = '864';
        $creditcard['ExpirationDate'] = '12/2013';
        $creditCardAddress['AddressLine'] = '2010 warsaw road';
        $creditCardAddress['City'] = 'Roswell';
        $creditCardAddress['StateProvinceCode'] = 'GA';
        $creditCardAddress['PostalCode'] = '30076';
        $creditCardAddress['CountryCode'] = 'US';
        $creditcard['Address'] = $creditCardAddress;
        $billshipper['CreditCard'] = $creditcard;
        $shipmentcharge['BillShipper'] = $billshipper;
        $paymentinformation['ShipmentCharge'] = $shipmentcharge;
        $shipment['PaymentInformation'] = $paymentinformation;

        $service['Code'] = '03';
        $service['Description'] = 'Ground';
        $shipment['Service'] = $service;

        $package['Description'] = 'Box 1';
        $packaging['Code'] = '02';
        $packaging['Description'] = 'Zamillion';
        $package['Packaging'] = $packaging;
        $unit['Code'] = 'IN';
        $unit['Description'] = 'Inches';
        $dimensions['UnitOfMeasurement'] = $unit;
        $dimensions['Length'] = '7';
        $dimensions['Width'] = '5';
        $dimensions['Height'] = '2';
        $package['Dimensions'] = $dimensions;
        $unit2['Code'] = 'LBS';
        $unit2['Description'] = 'Pounds';
        $packageweight['UnitOfMeasurement'] = $unit2;
        $packageweight['Weight'] = '1';
        $package['PackageWeight'] = $packageweight;
        $shipment['Package'][] = $package;

        $package['Description'] = 'Box 2';
        $packaging['Code'] = '02';
        $packaging['Description'] = 'Bails';
        $package['Packaging'] = $packaging;
        $unit['Code'] = 'IN';
        $unit['Description'] = 'Inches';
        $dimensions['UnitOfMeasurement'] = $unit;
        $dimensions['Length'] = '7';
        $dimensions['Width'] = '5';
        $dimensions['Height'] = '2';
        $package['Dimensions'] = $dimensions;
        $unit2['Code'] = 'LBS';
        $unit2['Description'] = 'Pounds';
        $packageweight['UnitOfMeasurement'] = $unit2;
        $packageweight['Weight'] = '10';
        $package['PackageWeight'] = $packageweight;
        $shipment['Package'][] = $package;

        $labelimageformat['Code'] = 'GIF';
        $labelimageformat['Description'] = 'GIF';
        $labelspecification['LabelImageFormat'] = $labelimageformat;
        $labelspecification['HTTPUserAgent'] = 'Mozilla/4.5';
        $shipment['LabelSpecification'] = $labelspecification;
        $request['Shipment'] = $shipment;

        return array($request);
    }

    public function processShipment()
    {
        try {
            $mode = array
                (
                     'soap_version' => 'SOAP_1_1',  // use soap 1.1 client
                     'trace' => 1
                );

            // initialize soap client
            $client = new SoapClient($this->wsdl, $mode);

            //set endpoint url
            $client->__setLocation($this->endpointurl);


            //create soap header
            $usernameToken['Username'] = $this->userid;
            $usernameToken['Password'] = $this->passwd;
            $serviceAccessLicense['AccessLicenseNumber'] = $this->access;
            $upss['UsernameToken'] = $usernameToken;
            $upss['ServiceAccessToken'] = $serviceAccessLicense;

            $header = new SoapHeader('http://www.ups.com/XMLSchema/XOLTWS/UPSS/v1.0','UPSSecurity',$upss);
            $client->__setSoapHeaders($header);

            //die(var_export($this->request));
            //get response
            $this->response = $client->__soapCall('ProcessShipment', $this->getRequest());
            $this->saveResultsToDetails();
            //die(var_dump($client->__getLastResponse()));

        } catch(Exception $ex) {
            return ($ex);
        }
    }
   
    public function saveResultsToDetails() {
        $this->shipment_details['shipment']['cost'] = $this->response->ShipmentResults->ShipmentCharges->TotalCharges->MonetaryValue;
        $this->shipment_details['shipment']['tracking_number'] = $this->response->ShipmentResults->ShipmentIdentificationNumber;
        $result_packages = $this->getPackages();
        $n_packages = count($result_packages);
        for ($i = 0; $i < $n_packages; $i++) {
            $this->shipment_details['packages'][$i]['tracking_number'] = $result_packages[$i]->TrackingNumber;
        }
    }
   
    public function getShipmentDetails()
    {
        return $this->shipment_details;
    }

    public function getShipmentResults()
    {
        return $this->response->ShipmentResults;
    }

    public function getTotalCharges()
    {
        return $this->response->ShipmentResults->ShipmentCharges->TotalCharges->MonetaryValue;
    }

    public function getShipmentTrackingNumber()
    {
        return $this->response->ShipmentResults->ShipmentIdentificationNumber;
    }

    public function getPackageTrackingNumbers()
    {
        $packages = $this->getPackages();
        foreach ($packages as $package) {
            $tracking_numbers[] = $package->TrackingNumber;
        }
        return $tracking_numbers;
    }

    public function getPackages()
    {
        $packages = $this->response->ShipmentResults->PackageResults;
        if (!is_array($packages)) {
            $packages = array($packages);
        }
        return $packages;
    }

    public function getLabels()
    {
        $packages = $this->getPackages();
        //die(var_dump($packages));
        foreach ($packages as $package) {
            $labels[] = $package->ShippingLabel->GraphicImage;
        }
        return $labels;
    }
}
 

5 comments:

Phil Welch said...

When trying to submit multiple packages (step 5), I get this error: " [previous:Exception:private] => [faultstring] => An exception has been raised as a result of client data. [faultcode] => Client [faultcodens] => http://schemas.xmlsoap.org/soap/envelope/ [detail] => stdClass Object ( [Errors] => stdClass Object ( [ErrorDetail] => stdClass Object ( [Severity] => Hard [PrimaryErrorCode] => stdClass Object ( [Code] => 9120202 [Description] => Missing packaging code for package 2. ) ) "
I am still playing around with the test endpoint URL (https://wwwcie.ups.com/webservices/Ship) and can get it to work fine until I put in 2 packages - did you have any issues in test mode? All of the package details are identical (besides 'Description') for both of the packages. It works fine if I leave only the first "$shipment['Package'][] = $package;" and comment out the second one.

Thomas Gail Haws said...

Phil, I'm sorry I don't know what to say.

Pocchiolo Christophe said...

Dear Tom,

Thank you for sharing this information that I am looking for since fews days.

Could you please share your SoapShipClient.php file, because I got a credit card error, but I do not want use credit card, I have an account and I want to use it to paid my shipping.

I am looking for an example configuration with the minimum parameters to ship a box in express an charge my ups account.

Could you please help me to fix my problem ?
Thanks in advance.

Thomas Gail Haws said...

I am adding my class above in the article.

Pocchiolo Christophe said...

Thank you so much, I fixed my problem. You saved my day.

Best regards.