3

I'm trying to use PHP and SoapClient to utilize the UPS Ratings web service. I found a nice tool called WSDLInterpreter to create a starting point library for creating the service requests, but regardless what I try I keep getting the same (non-descriptive) error:

EXCEPTION=SoapFault::__set_state(array(
   'message' => 'An exception has been raised as a result of client data.',
   'string' => '',
   'code' => 0,

Um ok, what the hell does this mean?

Unlike some of the other web service tools I have implemented, the UPS Soap wants a security block put into the header. I tried doing raw associative array data but I wasn't sure 100% if I was injecting the header part correctly. Using the WSDLInterpreter, it pulls out a RateService class with a ProcessRate method that excepts it's own (instance) datastructure for the RateRequest and UPSSecurity portions, but all of the above generates that same error.

Here's a sample of the code that I'm using that calls classes defined by the interpreter:


require_once "Shipping/UPS/RateService.php"; // WSDLInterpreter class library

class Query
{

    // constants removed for stackoverflow that define (proprietary) security items
    private $_upss;
    private $_shpr;

    // use Interpreter code's RateRequest class to send with ProcessRate
    public function soapRateRequest(RateRequest $req, UPSSecurity $upss = null)
    {
        $res = false;
        if (!isset($upss)) {
            $upss = $this->__getUPSS();
        }
        echo "SECURITY:\n" . var_export($upss, 1) . "\n";
        $upsrs = new RateService(self::UPS_WSDL);
        try {
            $res = $upsrs->ProcessRate($req, $upss);
        } catch (SoapFault $exception) {
            echo 'EXCEPTION=' . var_export($exception, 1) . "\n";
        }
        return $res;
    }

    // create a soap data structure to send to web service from shipment
    public function getRequestSoap(Shipment $shpmnt)
    {
        $qs = new RateRequest();
        // pickup information
        $qs->PickupType->Code = '01';
        $qs->Shipment->Shipper = $this->__getAcctInfo();
        // Ship To address
        $qs->Shipment->ShipTo->Address->AddressLine = $this->__getAddressArray($shpmnt->destAddress->address1, $shpmnt->destAddress->address2);
        $qs->Shipment->ShipTo->Address->City = $shpmnt->destAddress->city;
        $qs->Shipment->ShipTo->Address->StateProvinceCode = $shpmnt->destAddress->state;
        $qs->Shipment->ShipTo->Address->PostalCode = $shpmnt->destAddress->zip;
        $qs->Shipment->ShipTo->Address->CountryCode = $shpmnt->destAddress->country;
        $qs->Shipment->ShipTo->Name = $shpmnt->destAddress->name;
        // Ship From address
        $qs->Shipment->ShipFrom->Address->AddressLine = $this->__getAddressArray($shpmnt->origAddress->address1, $shpmnt->origAddress->address2);
        $qs->Shipment->ShipFrom->Address->City = $shpmnt->origAddress->city;
        $qs->Shipment->ShipFrom->Address->StateProvinceCode = $shpmnt->origAddress->state;
        $qs->Shipment->ShipFrom->Address->PostalCode = $shpmnt->origAddress->zip;
        $qs->Shipment->ShipFrom->Address->CountryCode = $shpmnt->origAddress->country;
        $qs->Shipment->ShipFrom->Name = $shpmnt->origAddress->name;
        // Service type
        // TODO cycle through available services
        $qs->Shipment->Service->Code = "03";
        $qs->Shipment->Service->Description = "UPS Ground";
        // Package information
        $pkg = new PackageType();
        $pkg->PackagingType->Code = "02";
        $pkg->PackagingType->Description = "Package/customer supplied";
        //   dimensions
        $pkg->Dimensions->UnitOfMeasurement->Code = $shpmnt->dimensions->dimensionsUnit;
        $pkg->Dimensions->Length = $shpmnt->dimensions->length;
        $pkg->Dimensions->Width = $shpmnt->dimensions->width;
        $pkg->Dimensions->Height = $shpmnt->dimensions->height;

        $pkg->PackageServiceOptions->DeclaredValue->CurrencyCode = "USD";
        $pkg->PackageServiceOptions->DeclaredValue->MonetaryValue = $shpmnt->dimensions->value;

        $pkg->PackageServiceOptions->DeclaredValue->CurrencyCode = "USD";
        $pkg->PackageWeight->UnitOfMeasurement = $this->__getWeightUnit($shpmnt->dimensions->weightUnit);
        $pkg->PackageWeight->Weight = $shpmnt->dimensions->weight;
        $qs->Shipment->Package = $pkg;
        $qs->CustomerClassification->Code = 123456;
        $qs->CustomerClassification->Description = "test_rate_request";
        return $qs;
    }

    // fill out and return a UPSSecurity data structure
    private function __getUPSS()
    {
        if (!isset($this->_upss)) {
            $unmt = new UsernameToken();
            $unmt->Username = self::UPSS_USERNAME;
            $unmt->Password = self::UPSS_PASSWORD;
            $sat = new ServiceAccessToken();
            $sat->AccessLicenseNumber = self::UPSS_ACCESS_LICENSE_NUMBER;
            $upss = new UPSSecurity();
            $upss->UsernameToken = $unmt;
            $upss->ServiceAccessToken = $sat;
            $this->_upss = $upss;
        }
        return $this->_upss;
    }

    // get our shipper/account info (some items blanked for stackoverflow)
    private function __getAcctInfo()
    {
        if (!isset($this->_shpr)) {
            $shpr = new ShipperType();
            $shpr->Address->AddressLine = array(
                "CONTACT",
                "STREET ADDRESS"
            );
            $shpr->Address->City = "CITY";
            $shpr->Address->StateProvinceCode = "MI";
            $shpr->Address->PostalCode = "ZIPCODE";
            $shpr->Address->CountryCode = "US";

            $shpr = new ShipperType();
            $shpr->Name = "COMPANY NAME";
            $shpr->ShipperNumber = self::UPS_ACCOUNT_NUMBER;
            $shpr->Address = $addr;

            $this->_shpr = $shpr;
        }

        return $this->_shpr;
    }

    private function __getAddressArray($adr1, $adr2 = null)
    {
        if (isset($adr2) && $adr2 !== '') {
            return array($adr1, $adr2);
        } else {
            return array($adr1);
        }
    }

}

It doesn't even seem to be getting to the point of sending anything over the Soap so I am assuming it is dying as a result of something not matching the WSDL info. (keep in mind, I've tried sending just a properly seeded array of key/value details to a manually created SoapClient using the same WSDL file with the same error resulting) It would just be nice to get an error to let me know what about the 'client data' is a problem. This PHP soap implementation isn't impressing me!

Paweł Tomkiel
  • 1,974
  • 2
  • 21
  • 39
Scott
  • 7,983
  • 2
  • 26
  • 41

1 Answers1

2

I know this answer is probably way too late, but I'll provide some feedback anyway. In order to make a custom SOAP Header you'll have to override the SoapHeader class.

/*
 * Auth Class to extend SOAP Header for WSSE Security
 * Usage:
 * $header = new upsAuthHeader($user, $password);
 * $client = new SoapClient('{...}', array("trace" => 1, "exception" => 0));
 * $client->__setSoapHeaders(array($header));
 */

class upsAuthHeader extends SoapHeader
{
...
    function __construct($user, $password)
    {

        // Using SoapVar to set the attributes has proven nearly impossible; no WSDL.
        // It might be accomplished with a combined SoapVar and stdClass() approach.

        // Security Header - as a combined XSD_ANYXML SoapVar
        // This method is much easier to define all of the custom SoapVars with attributes.
        $security = '<ns2:Security xmlns:ns2="'.$this->wsse.'">'. // soapenv:mustUnderstand="1" 
                        '<ns2:UsernameToken ns3:Id="UsernameToken-49" xmlns:ns3="'.$this->wsu.'">'.
                            '<ns2:Username>'.$user.'</ns2:Username>'.
                            '<ns2:Password Type="'.$this->password_type.'">'.htmlspecialchars($password).'</ns2:Password>'.
                        '</ns2:UsernameToken>'.
                    '</ns2:Security>';
        $security_sv = new SoapVar($security, XSD_ANYXML);
        parent::__construct($this->wsse, 'Security', $security_sv, false);

    }

}

Then call the upsAuthHeader() before the soap call.

$client = new SoapClient($this->your_ups_wsdl,
    array('trace' => true,
        'exceptions' => true,
        'soap_version' => SOAP_1_1
    )
);
// Auth Header - Security Header
$header = new upsAuthHeader($user, $password);
// Set Header
$client->__setSoapHeaders(array($header));
jjwdesign
  • 3,272
  • 8
  • 41
  • 66
  • thanks, still appreciated. I did a work around but if and when I have to go back to that again, I'll definitely refer back here. – Scott Dec 18 '12 at 22:08