Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

trying to test UPS "Quantum" interface - The XML document is not well formed

We are trying to test United Parcel Service (UPS) "Quantum" interface test get order status information. We got the tracking number API working but having trouble with the QVEvents one.

UPS "Outbound subscription account" is set up, active and linked to my account numbers. User access Quantum View Data view is checked off in User Setup screen. I have, incidently, gotten Tracking API working with similar code, so think that I can rule out username or password problems. Is there anything operationally different with these two APIs? (e.g: SSL requirements, HTTP Header settings?)

Here is code for tracking API ("Tack by Waybill") which DOES works for me:

 <?php
  //  UPS Tracker API - track specfic Waybill
  //  DEV server
  $access      = '99999999399999999';
  $userid      = '9999999';
  $passwd      = '999999999999';
  $endpointUrl = 'https://www.ups.com/ups.app/xml/Track';
  $outFileName = './XOLTResult.xml'; 


  // Note: you need at least a UPS DEV account to test this
  $data ="<?xml version=\"1.0\"?><AccessRequest xml:lang='en-US'>
    <AccessLicenseNumber>$access</AccessLicenseNumber>
    <UserId>$userid</UserId>
    <Password>$passwd</Password>
    </AccessRequest>
    <?xml version=\"1.0\"?>
    <TrackRequest>
        <Request>
            <TransactionReference>
                <CustomerContext>
                    <InternalKey>hello</InternalKey>
                </CustomerContext>
                <XpciVersion>1.0</XpciVersion>
            </TransactionReference>
            <RequestAction>Track</RequestAction>
        </Request>
        <TrackingNumber>9999999999999999</TrackingNumber>
    </TrackRequest>";

    $ch = curl_init("https://www.ups.com/ups.app/xml/Track");
    curl_setopt($ch, CURLOPT_HEADER, 1);
    curl_setopt($ch,CURLOPT_POST,1);
    curl_setopt($ch,CURLOPT_TIMEOUT, 60);
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
    curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
    curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
    $result=curl_exec ($ch);
    $data = strstr($result, '<?');
    $xml=simplexml_load_string($data);
    echo "<pre>";
    print_r($xml);

And here is Quantum API code that gives error message...

<?php
 //  UPS Quantum API ("Show list of recent tracking information")
 //  DEV server
 $access      = '99999999399999999';
 $userid      = '9999999';
 $passwd      = '999999999999';
 $endpointUrl = 'https://wwwcie.ups.com/ups.app/xml/QVEvents';      // URL for testing Quantum
 $outFileName = './XOLTResult.xml'; 


try
{

$data ="<?xml version=\"1.0\"?>
        <AccessRequest xml:lang=\"en-US\">
        <AccessLicenseNumber>$access</AccessLicenseNumber>
        <UserId>$userid</UserId>
        <Password>$passwd</Password>
        </AccessRequest>
        <?xml version=\"1.0\"?>    
        <QuantumViewRequest xml:lang=\"en-US\">
            <Request>
                <TransactionReference>
                    <CustomerContext>Test XML</CustomerContext>
                    <XpciVersion>1.0007</XpciVersion>
                 </TransactionReference>
                 <RequestAction>QVEvents</RequestAction>
                 <IntegrationIndicator></IntegrationIndicator>
            </Request> 
        </QuantumViewRequest>";

  $postData = array
    (
      'content' =>  $data
    );


        $ch = curl_init();

        curl_setopt($ch, CURLOPT_HEADER, 1);
        curl_setopt($ch, CURLOPT_POST,1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);      
        curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
        curl_setopt($ch, CURLOPT_URL,$endpointUrl);
        curl_setopt($ch, CURLOPT_VERBOSE, 1 );
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded'));     
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);                // disable SSL verification if not installed
        //curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);             
        curl_setopt($ch, CURLOPT_SSLVERSION, 3);                        // use Secure Socket v3 SSL3
        curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'SSLv3');             
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);        
        curl_setopt($ch,CURLOPT_POSTFIELDS,$postData);


        if( ! $result = curl_exec($ch))
        {
            trigger_error(curl_error($ch));
        } 


        echo $result;

        $data = strstr($result, '<?');
        $xml=simplexml_load_string($data);


        echo "<pre>";
        print_r($xml);

}
catch(Exception $ex)
{
   echo ($ex . "!");
}

curl_close($ch);        

This is XML actually sent to UPS... [Note the double xml header is what they ask for and it works in all their other APIs, so don't blame me]

<?xml version="1.0"?>
<AccessRequest xml:lang="en-US">
<AccessLicenseNumber>999</AccessLicenseNumber>
<UserId>999</UserId>
<Password>999</Password>
</AccessRequest>
<?xml version="1.0"?>    
<QuantumViewRequest xml:lang="en-US">
    <Request>
        <TransactionReference>
            <CustomerContext>Test XML</CustomerContext>
            <XpciVersion>1.0007</XpciVersion>
         </TransactionReference>
         <RequestAction>QVEvents</RequestAction>
         <IntegrationIndicator></IntegrationIndicator>
    </Request> 
</QuantumViewRequest>

Error message look like this:

HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Date: Fri, 25 Jul 2014 22:50:57 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Pragma: no-cache
Transfer-Encoding: chunked
Content-Type: application/xml

<QuantumViewResponse><Response><TransactionReference><XpciVersion>1.0</XpciVersion>
</TransactionReference><ResponseStatusCode>0</ResponseStatusCode>
<ResponseStatusDescription>Failure</ResponseStatusDescription><Error>
<ErrorSeverity>Hard</ErrorSeverity><ErrorCode>10001</ErrorCode>
<ErrorDescription>The XML document is not well formed</ErrorDescription></Error>
 </Response></QuantumViewResponse><pre>

In am not using UPS SCA_SDO library because it would not install on my PC. Maybe I should look at it again, but my programmer assured me it was not necessary and not relevant to this problem. The double stacked XML looks suspicious, but manual says this is the way UPS wants it. I guess UPS runs a pre-processor on the request.

like image 240
Mustapha George Avatar asked Nov 01 '22 19:11

Mustapha George


2 Answers

According to the July 2014 Quantum View Developers Guide, the IntegrationIndicator is not used.

IntegrationIndicator

Remove that from your Request node and it should function as you expect:

<Request>
    <TransactionReference>
        <CustomerContext>Test XML</CustomerContext>
        <XpciVersion>1.0007</XpciVersion>
     </TransactionReference>
     <RequestAction>QVEvents</RequestAction>
</Request> 
like image 179
Andy Avatar answered Nov 10 '22 04:11

Andy


The Problem

I'm not fluent with the UPS Api in specific, but the error message hints a not well-formed XML document:

Error Code 10001: The XML document is not well formed

According to the Quantum View Package - XML Developers Guide you're using the wrong encoding and content-type of the post-data in your HTTP request which breaks the XML which makes it not well formed any longer. This causes the error code 10001.

Instead, use the right content-type of the HTTP request body (you're using multipart/form-data which is wrong, the correct content-type is application/x-www-form-urlencoded) with your HTTP POST request to the UPS XML API endpoint and also encode the XML data correct as well for which you should use an XML library to create the XML (not your concrete issue, but when you know that the XML is correct and the creation is not error prone as it is in your string concatenation, this helps to exclude a tons of error cases for the same error code).

How to fix?

To fix the concrete issue of the wrong content-type in your code, replace the following lines:

$postData = array
(
    'content' =>  $data
);

with

$postData = $data;

This ensures that the curl library will use "application/x-www-form-urlencoded" as Content-Type instead of "multipart/form-data". You then get the correct error message that the license number is invalid:

Error Code 250003: Invalid Access License number

Alternative Code-Example

As alternative, here is another little example that shows how this can be done with standard PHP HTTP wrappers and the SimpleXML library for the XML handling as well as nicer output for the response with DOMDocument:

/*
 * UPS API XML PHP Example (Open Source)
 */

# Every UPS XML API request needs an AccessRequest XML payload first
$accessRequest                      = new SimpleXMLElement("<AccessRequest xml:lang='en-US'/>");
$accessRequest->AccessLicenseNumber = '99999999399999999';
$accessRequest->UserId              = '9999999';
$accessRequest->Password            = '999999999999';

# Exemplary testing QuantumViewRequest
$url     = 'https://wwwcie.ups.com/ups.app/xml/QVEvents';
$request = new SimpleXMLElement('<QuantumViewRequest xml:lang="en-US"/>');

# UPS XML API requires two concatenated XML documents send via a HTTP POST
# request with a HTTP request body and the content-type set to 
# application/x-www-form-urlencoded
$options = array(
    'http' => array(
        'header'  => "Content-type: application/x-www-form-urlencoded",
        'method'  => 'POST',
        'content' => $accessRequest->asXML() . $request->asXML(),
    ),
);
$context = stream_context_create($options);
$result  = file_get_contents($url, false, $context);

# output the response nicely formatted
$dom = new DOMDocument();
$dom->loadXML($result);
$dom->preserveWhiteSpace = false;
$dom->formatOutput       = true;
echo $dom->saveXML();

This example is using the data you've provided in your example, and instead of the 10001 (that your code produces) it gives the much more precise error information about the real known error in your data sample:

Error Code 250003: Invalid Access License number

<?xml version="1.0"?>
<QuantumViewResponse>
  <Response>
    <TransactionReference/>
    <ResponseStatusCode>0</ResponseStatusCode>
    <ResponseStatusDescription>Failure</ResponseStatusDescription>
    <Error>
      <ErrorSeverity>Hard</ErrorSeverity>
      <ErrorCode>250003</ErrorCode>
      <ErrorDescription>Invalid Access License number</ErrorDescription>
    </Error>
  </Response>
</QuantumViewResponse>

In which I have good faith that this is the correct error message.

For the brevity of the example, I've left the error handling for file_get_contents and DOMDocument::loadXML() out.

like image 27
hakre Avatar answered Nov 10 '22 05:11

hakre