Link to home
Start Free TrialLog in
Avatar of tcszabo
tcszabo

asked on

How do I make a SOAP request to 3pl Central?

I am trying to produce code that will be a XML-SOAP web service client that sends orders from a website to a third party logistics company called 3pl Central (3plcentral.com). The script I have so far is attached. The main question is whether what I have written appears to be the appropriate method. I get an error that says:
// Error: HTTP/1.1 500 Internal Server Error Date: Mon, 16 Aug 2010 06:30:33 GMT Server:
// Microsoft-IIS/6.0 X-Powered-By: ASP.NET X-AspNet-Version: 2.0.50727 Cache-Control:
// private Content-Type: text/xml; charset=utf-8 Content-Length: 435 soap:ClientUnable
// to handle request without a valid action parameter. Please supply a valid soap
// action.

$key = xxxxxx; // deleted for privacy
$login = xxxxxx; // deleted for privacy
$password = xxxxxx; // deleted for privacy
$soap_host = 'app02.3plcentral.com';
$soap_url = 'ssl://app02.3plcentral.com';
$soap_uri = '/webserviceexternal/contracts.asmx?op=CreateOrders';
$soap_action = 'http://www.JOI.com/schemas/ViaSub.WMS/CreateOrders';
$soap_wsdl = 'xmlorders.wsdl';

// now talk to 3plc---------------------------------------------------------------------//

// Socket Info
$method   = 'POST';
$protocol = 'HTTP/1.1';
$c_type   = 'Content-Type: application/soap+xml; charset=utf-8';
$length   = strlen($xml_string);

// Headers
$header .= "$method $soap_uri $protocol\r\n";
$header .= "Host: $soap_host\r\n";
$header .= "$c_type\r\n";
$header .= "Content-Length: " . $length . "\r\n\r\n";

// Open Socket
$fp = fsockopen ($soap_url, 443, $errno, $errstr, 3600);

// Test Connection
if (!(get_resource_type($fp) == 'stream' )){ // HTTP ERROR
	die("fsockopen() FAILED \n\n ERRNO=$errno \n\n ERRSTR=$errstr \n\n");
}

// write header and request
if(!fwrite ($fp, $header . $xml_string)){ // WRITE ERROR
	die('fwrite failed'); // never a problem
}

// read response
$reply  = '';
while(!feof($fp)){
	$reply  .= fgets($fp);
}

Open in new window

$key = xxxxxx; // deleted for privacy
$login = xxxxxx; // deleted for privacy
$password = xxxxxx; // deleted for privacy
$soap_host = 'app02.3plcentral.com';
$soap_url = 'ssl://app02.3plcentral.com';
$soap_uri = '/webserviceexternal/contracts.asmx?op=CreateOrders';
$soap_action = 'http://www.JOI.com/schemas/ViaSub.WMS/CreateOrders';
$soap_wsdl = 'xmlorders.wsdl';

// now talk to 3plc---------------------------------------------------------------------//

// Socket Info
$method   = 'POST';
$protocol = 'HTTP/1.1';
$c_type   = 'Content-Type: application/soap+xml; charset=utf-8';
$length   = strlen($xml_string);

// Headers
$header .= "$method $soap_uri $protocol\r\n";
$header .= "Host: $soap_host\r\n";
$header .= "$c_type\r\n";
$header .= "Content-Length: " . $length . "\r\n\r\n";

// Open Socket
$fp = fsockopen ($soap_url, 443, $errno, $errstr, 3600);

// Test Connection
if (!(get_resource_type($fp) == 'stream' )){ // HTTP ERROR
	die("fsockopen() FAILED \n\n ERRNO=$errno \n\n ERRSTR=$errstr \n\n");
}

// write header and request
if(!fwrite ($fp, $header . $xml_string)){ // WRITE ERROR
	die('fwrite failed'); // never a problem
}

// read response
$reply  = '';
while(!feof($fp)){
	$reply  .= fgets($fp);
}

Open in new window

Avatar of Richard Quadling
Richard Quadling
Flag of United Kingdom of Great Britain and Northern Ireland image

Normally with a SOAP service, the server will be able to provide you with a WSDL file. This file describes (to a degree) how to use the service at such a level that client classes can be auto-created.wsdl2java, wsdl2php and wsdl (for .net) are the sort of tools you can use.If you can supply the URL to the WSDL files, I can create the client side classes you can use to talk to this service.You would use the classes in such a way to make the entire SOAP conversation invisible.A LOT easier than opening sockets and creating your own headers and XML body.As an example, I've attached an edited (removed real links) WSDL file and a slightly edited client side classes which was generated by wsdl2php (from http://sourceforge.net/projects/wsdl2php which I've amended and patched using RazorsEdgeUK's patches from the same site).Ignore the specifics of the WSDL file, but see how the content has been mapped to the client PHP script. Documentation, complex types, etc.
Authentication.wsdl.xml
Camelot-AuthenticationService.php
So, using the above content, the usage would be something like the snippet below.

As you can see, no headers(), no XML creation, etc. Just normal OOP calls.

The server side code was developed using Zend Framework and the WSDL file generated automatically.

So, all in all, only the server side code was actually written. All the middle layers are simple generated.


OK, so a LONG answer and a different tack for you to take, but hopefully one that will mean you can get the job done a LOT quicker.
<?php 
/** 
 * Camelot Web Services 
 * 
 * The Camelot Web Services are a collection of web services created to allow EDITED_DOMAIN Tyres business 
 * partners to communicate with us electronically via a standard SOAP interface. 
 * 
 * New services will be added as and when needed. 
 * 
 * @author    Richard Quadling <RQuadling@EDITED_DOMAIN.co.uk> 
 * @category  CamelotWebServices 
 * @copyright Copyright (c) 2005-2010 EDITED_DOMAIN
 * @package   Camelot_Authentication 
 * @version   1.0 
 */ 

/** 
 * AuthenticationExample 
 * 
 * This example shows how to use the Camelot_Authentication class in PHP. 
 * 
 * $Id: Client.php,v 1.0.49 2010-07-07T14:35:51+01:00 RichardQ $ 
 */ 

// Include the class definitions required by or returned by the Camelot Web Services. 
require_once './Camelot_Classes.php'; 

// Include the SOAP wrapper classes for the Camelot Web Services. 
require_once './Camelot_Services.php'; 

// Demonstration credentials. These credentials are encrypted and locked to this server. 
$Username = 'EDITED; 
$Password = 'EDITED; 

// Create a new Authentication instance. 
$Authenticator = new Camelot_AuthenticationService('http://services.EDITED_DOMAIN.co.uk/Authentication?wsdl'); 

// All SOAP Faults are converted to PHP exceptions, so use a try/catch mechanism to provide a level of fault tolerance. 
try 
    { 
    // Login : This will retrieve your unique token for all communications with this session. 
    $Login = $Authenticator->Login(); 

    // Authenticate : Having got your unique token, you need to authenticate yourself with the session. 
    $Authenticated = $Authenticator->Authenticate 
        ( 
        $Login, 
        $Username, 
        md5("$Password : {$Login->Token}") 
        ); 

    // At this point, you could now call any of the other Camelot servivces. 

Open in new window

Avatar of tcszabo
tcszabo

ASKER

Okay. The WSDL for this service is attached.
XML-Orders-WSDL.wsdl.txt
ASKER CERTIFIED SOLUTION
Avatar of Richard Quadling
Richard Quadling
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
OK. The issue is that the orders need to be filled in.

SOAP Services are a little hard to test as you always need fully compliant XML. So, for example, sending an empty order is not allowed. The details all need to be filled in.

I've added Tidy to the request/response, so you can see it tidied up.

The output is ...


Request Headers
---------------
POST /webserviceexternal/contracts.asmx HTTP/1.1
Host: app02.3plcentral.com
Connection: Keep-Alive
User-Agent: PHP
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://www.JOI.com/schemas/ViaSub.WMS/CreateOrders"
Content-Length: 602


Request
-------
<?xml version="1.0" encoding="utf-8"?>

 
   
     
      Richard
      Quadling
      0
   
   
     
       
         
         
       
       
     
     
       
     
   
   
 


Response Headers
----------------
HTTP/1.1 500 Internal Server Error
Date: Fri, 27 Aug 2010 09:07:52 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Length: 479

Response
--------
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <soap:Fault>
      soap:Client
      Server was unable to read request. ---> There is an error in XML document (2, 378). ---> The string '' is not a valid AllXsd value.
     
    </soap:Fault>
  </soap:Body>
</soap:Envelope>


Exception
---------
Server was unable to read request. ---> There is an error in XML document (2, 378). ---> The string '' is not a valid AllXsd value.



In the snippet, line 27 shows me starting to add the necessary elements to the order. You can see this in the output shown above.

You just have to fill in the entire order and all the elements that the order requires.

But it is all OOP and pretty simple stuff. Compared to reading a WSDL file and constructing XML accurately.
<?php
require_once 'ServiceExternal.php';


require_once 'ServiceExternal.php';

try
	{
	$service = new ServiceExternal
		(
                $wsdl = "XML-Orders-WSDL.wsdl.txt",
                $options = array
                        (
                        'encoding'  => 'ISO-8859-1',
                        'exception' => True,
                        'trace'     => True,
                        )
		);

	$login = new ExternalLoginData;
	$login->ThreePLKey = '';
	$login->Login = 'Richard';
	$login->Password = 'Quadling';
	$login->FacilityID = 0;

	$order1 = new Order();
	$order2 = new Order();
	$orders = array($order1, $order2);

	$order1->TransInfo = new TransactionInfo();

	// What format are the orders and the warnings supposed to be?
	$response = $service->CreateOrders($login, $orders, '');
        }
catch(Exception $e)
        {
        $o_Tidy = new tidy();
        $a_TidyConfig = array
        	(
		'indent'     => True,
		'input-xml'  => True,
		'output-xml' => True,
		'wrap'       => 200,
        	);

        echo 'Request Headers', PHP_EOL, '---------------', PHP_EOL, $service->__getLastRequestHeaders(), PHP_EOL;
        echo 'Request', PHP_EOL, '-------', PHP_EOL, $o_Tidy->repairString($service->__getLastRequest(), $a_TidyConfig), PHP_EOL;
        echo 'Response Headers', PHP_EOL, '----------------', PHP_EOL, $service->__getLastResponseHeaders(), PHP_EOL;
        echo 'Response', PHP_EOL, '--------', PHP_EOL, $o_Tidy->repairString($service->__getLastResponse(), $a_TidyConfig), PHP_EOL, PHP_EOL;
        echo 'Exception', PHP_EOL, '---------', PHP_EOL, $e->getMessage(), PHP_EOL;
        }

Open in new window

Avatar of tcszabo

ASKER

This seems to work really well. The only problem is now I don't seem to have the same control over the markup as if I were writing out in a string. So the XML is a little different. In the below snippet notice the '<ns1:Order>' instead of just '<Order>' as in the previous XML files. What is this about? Are these interchangeable? Can it be changed? (I still get error messages back from the service even after completing the solution.)
<ns1:Order>
        <ns1:TransInfo>
          <ns1:ReferenceNum>100000007</ns1:ReferenceNum>
          <ns1:EarliestShipDate>2010-07-21T20:30:16</ns1:EarliestShipDate>
          <ns1:ShipCancelDate>0001-01-01T00:00:00</ns1:ShipCancelDate>
        </ns1:TransInfo>
        <ns1:ShipTo>

Open in new window

Forget the XML. That really isn't of your concern. The DATA is your concern.

The namespace prefixing should be fine.

Because an XML file may use many different sources/structures, every element is tagged to the namespace to which it belongs. Quite fine.

The server should be able to handle this completely invisibly.

The error should change for each submission. The errors I saw prove that the server hasn't even passed the data to the handler yet as the XML file is badly formed. Some of the values MUST be set to appropriate types. This is why XML is cool (in my mind). I have to obey the rules to get any output. The WSDL file describes all the things you need.

Avatar of tcszabo

ASKER

This is exactly what I needed, thank you for the help!
I am alos having some trouble with this.

I copied your code and keep getting '503 Service Unavailabe'.
It has something to do with the creation of:

      $service = new ServiceExternal
      (
          $wsdl = "XML-Orders-WSDL.wsdl.txt",
        $options = array
        (
              'encoding'  => 'ISO-8859-1',
            'exception' => True,
            'trace'     => True,
        )
);

The pages that I have are? (See attached)

Any help would be super great.
#Main Page

<?php

$docRoot = getenv("DOCUMENT_ROOT");

include_once("$docRoot/cron/nusoap/lib/nusoap.php");

require_once $docRoot . '/cron/UpdateFunctions.php';


try
{
	$service = new ServiceExternal
	(
    	$wsdl = "XML-Orders-WSDL.wsdl.txt",
        $options = array
        (
        	'encoding'  => 'ISO-8859-1',
            'exception' => True,
            'trace'     => True,
        )
	);

	$login = new ExternalLoginData;
	$login->ThreePLKey = 'hidden';
	$login->Login = 'hidden';
	$login->Password = 'hidden';
	$login->FacilityID = 2;


	$order1->TransInfo = new TransactionInfo();


	#$response = $service->CreateOrders($login, $orders, '');
	$response = $service->ReportStockStatus($login);
}

catch(Exception $e)
{
	$o_Tidy = new tidy();
    $a_TidyConfig = array
    (
		'indent'     => True,
		'input-xml'  => True,
		'output-xml' => True,
		'wrap'       => 200,
    );

    echo 'Request Headers', PHP_EOL, '---------------', PHP_EOL, $service->__getLastRequestHeaders(), PHP_EOL;
    echo 'Request', PHP_EOL, '-------', PHP_EOL, $o_Tidy->repairString($service->__getLastRequest(), $a_TidyConfig), PHP_EOL;
    echo 'Response Headers', PHP_EOL, '----------------', PHP_EOL, $service->__getLastResponseHeaders(), PHP_EOL;
    echo 'Response', PHP_EOL, '--------', PHP_EOL, $o_Tidy->repairString($service->__getLastResponse(), $a_TidyConfig), PHP_EOL, PHP_EOL;
    echo 'Exception', PHP_EOL, '---------', PHP_EOL, $e->getMessage(), PHP_EOL;
}

?>

#Functions

<?php
class ExternalLoginData {
  public $ThreePLKey; // string
  public $Login; // string
  public $Password; // string
  public $FacilityID; // int
}

class Order {
  public $TransInfo; // TransactionInfo
  public $ShipTo; // ContactInfo
  public $ShippingInstructions; // ShippingInstructions
  public $ShipmentInfo; // ShipmentInfo
  public $Notes; // string
  public $PalletCount; // int
  public $OrderLineItems; // ArrayOfOrderLineItem
}

class TransactionInfo {
  public $ReferenceNum; // string
  public $EarliestShipDate; // dateTime
  public $ShipCancelDate; // dateTime
}

class ContactInfo {
  public $Name; // string
  public $CompanyName; // string
  public $Address; // Address
  public $PhoneNumber1; // string
  public $Fax; // string
  public $EmailAddress1; // string
  public $CustomerName; // string
  public $Vendor; // string
  public $Dept; // string
}

class Address {
  public $Address1; // string
  public $Address2; // string
  public $City; // string
  public $State; // string
  public $Zip; // string
  public $Country; // string
}

class ShippingInstructions {
  public $Carrier; // string
  public $Mode; // string
  public $BillingCode; // string
  public $Account; // string
}

class ShipmentInfo {
  public $NumUnits1; // decimal
  public $NumUnits1TypeID; // int
  public $NumUnits1TypeDesc; // string
  public $NumUnits2; // decimal
  public $NumUnits2TypeID; // int
  public $NumUnits2TypeDesc; // string
  public $TotalWeight; // decimal
  public $TotalVolume; // decimal
}

class OrderLineItem {
  public $SKU; // string
  public $Qty; // decimal
  public $Packed; // decimal
  public $CuFtPerCarton; // decimal
}

class UpdateOrder {
  public $WarehouseTransactionID; // int
  public $BillOfLading; // string
  public $TrackingNumber; // string
  public $ConfirmationDate; // dateTime
}

class UserLoginData {
  public $ThreePLID; // int
  public $Login; // string
  public $Password; // string
}

class Retailer {
  public $RetailerID; // int
  public $RetailerNumber; // string
  public $Description; // string
  public $CustomerID; // int
}

class FindOrderCriteria {
  public $CustomerID; // int
  public $FacilityID; // int
  public $RetailerID; // int
  public $MarkForListID; // int
  public $OrderFtpID; // int
  public $NoteContains; // string
  public $OverAlloc; // FindTriStateType
  public $Closed; // FindTriStateType
  public $ASNSent; // FindTriStateType
  public $RouteSent; // FindTriStateType
  public $BeginDate; // I18nDateTime
  public $EndDate; // I18nDateTime
  public $DateRangeType; // FindOrderDateRangeType
  public $BeginTrans; // int
  public $EndTrans; // int
  public $MasterBOLID; // string
}

class FindTriStateType {
  const Exclude = 'Exclude';
  const IncludeOnly = 'IncludeOnly';
  const Any = 'Any';
}

class I18nDateTime {
  public $_; // dateTime
}

class FindOrderDateRangeType {
  const None = 'None';
  const Create = 'Create';
  const Confirm = 'Confirm';
  const ASNSent = 'ASNSent';
  const Pickup = 'Pickup';
}


/**
 * ServiceExternal class
 * 
 * 
 * 
 * @author    {author}
 * @copyright {copyright}
 * @package   {package}
 */
class ServiceExternal extends SoapClient {

  private static $classmap = array(
                                    'ExternalLoginData' => 'ExternalLoginData',
                                    'Order' => 'Order',
                                    'TransactionInfo' => 'TransactionInfo',
                                    'ContactInfo' => 'ContactInfo',
                                    'Address' => 'Address',
                                    'ShippingInstructions' => 'ShippingInstructions',
                                    'ShipmentInfo' => 'ShipmentInfo',
                                    'OrderLineItem' => 'OrderLineItem',
                                    'UpdateOrder' => 'UpdateOrder',
                                    'UserLoginData' => 'UserLoginData',
                                    'Retailer' => 'Retailer',
                                    'FindOrderCriteria' => 'FindOrderCriteria',
                                    'FindTriStateType' => 'FindTriStateType',
                                    'I18nDateTime' => 'I18nDateTime',
                                    'FindOrderDateRangeType' => 'FindOrderDateRangeType',
                                   );


  public function ServiceExternal($wsdl = "XML-Orders-WSDL.wsdl.txt", $options = array()) {
    foreach(self::$classmap as $key => $value) {
      if(!isset($options['classmap'][$key])) {
        $options['classmap'][$key] = $value;
      }
    }
    parent::__construct($wsdl, $options);
  }



  /**
   * Create Orders
   *
   * @param ExternalLoginData $extLoginData
   * @param ArrayOfOrder $orders
   * @param string $warnings
   * @return list(int $CreateOrdersResult, string $warnings)
   */
  public function CreateOrders(ExternalLoginData $extLoginData, $orders, $warnings) {
    return $this->__soapCall('CreateOrders', array($extLoginData, $orders, $warnings),       array(
            'uri' => 'http://www.JOI.com/schemas/ViaSub.WMS/',
            'soapaction' => ''
           )
      );
  }

  /**
   * Update Orders
   *
   * @param ExternalLoginData $extLoginData
   * @param ArrayOfUpdateOrder $updateOrders
   * @param string $warnings
   * @return list(int $UpdateOrdersResult, string $warnings)
   */
  public function UpdateOrders(ExternalLoginData $extLoginData, $updateOrders, $warnings) {
    return $this->__soapCall('UpdateOrders', array($extLoginData, $updateOrders, $warnings),       array(
            'uri' => 'http://www.JOI.com/schemas/ViaSub.WMS/',
            'soapaction' => ''
           )
      );
  }

  /**
   * Report all Retailers for a customer
   *
   * @param UserLoginData $userLoginData
   * @return ArrayOfRetailer
   */
  public function ReportRetailers(UserLoginData $userLoginData) {
    return $this->__soapCall('ReportRetailers', array($userLoginData),       array(
            'uri' => 'http://www.JOI.com/schemas/ViaSub.WMS/',
            'soapaction' => ''
           )
      );
  }

  /**
   * Find Order records for given 'find' parameters
   *
   * @param UserLoginData $userLoginData
   * @param FindOrderCriteria $focr
   * @param int $limitCount
   * @return list(string $FindOrdersResult, int $totalOrders)
   */
  public function FindOrders(UserLoginData $userLoginData, FindOrderCriteria $focr, $limitCount) {
    return $this->__soapCall('FindOrders', array($userLoginData, $focr, $limitCount),       array(
            'uri' => 'http://www.JOI.com/schemas/ViaSub.WMS/',
            'soapaction' => ''
           )
      );
  }

  /**
   * Report stockstatus for a customer
   *
   * @param UserLoginData $userLoginData
   * @return string
   */
  public function ReportStockStatus(UserLoginData $userLoginData) {
    return $this->__soapCall('ReportStockStatus', array($userLoginData),       array(
            'uri' => 'http://www.JOI.com/schemas/ViaSub.WMS/',
            'soapaction' => ''
           )
      );
  }

}

?>

Open in new window

And the link to the temporray page is:
 http://flylowgear.reelmotioninc.com/cron/UpdateInventory.php

I am JUST trying to do the Stock Report.
@ragnew12345.

Ask a new question and I may get to see it.

If you make sure that the first zone you choose is SOAP, then that'll certainly help.
done it there as a new questions, thx