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.
// 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);
}
$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);
}
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.
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.
ASKER
Okay. The WSDL for this service is attached.
XML-Orders-WSDL.wsdl.txt
XML-Orders-WSDL.wsdl.txt
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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/contra cts.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.
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/contra
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;
}
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>
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.
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.
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.
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' => ''
)
);
}
}
?>
And the link to the temporray page is:
http://flylowgear.reelmotioninc.com/cron/UpdateInventory.php
I am JUST trying to do the Stock Report.
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.
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
Authentication.wsdl.xml
Camelot-AuthenticationService.php