How to End an Ebay Listing Automatically with a PHP Script

One programming project I completed included a custom backend order management system.  The client had a website where she sold items that were also listed on Ebay.   She had been manually removing the items when they sold by logging into her Ebay account and going to the Ebay End Your Listing Early Page (this can be found on the Ebay Site Map).  Of course, doing this manually has a few issues.  Not only is it time consuming, but it also runs the risk of an out-of-stock item selling.  For best ecommerce practices, store inventory should be updated immediately.  So automating this task can really save a lot of headaches!

This backend sales automation task is fairly simple.  It involves sending an XML document to Ebay and then parsing the XML response that they return.  It transfers the data to and from Ebay’s server using the php cURL library, which is very commonplace.  You can check to see if your server has cURL enabled by using

function_exists('curl_version')

If you’re working with a WordPress plugin, I suggest that you use the WordPress HTTP API instead, as it has alternative ways of completing the HTTP request if cURL doesn’t exist.

If you’re writing a program to simply handle one Ebay seller’s inventory, you can get the necessary authentication token by signing up for the Ebay Developer Program.  The only other piece of data you need is the Ebay Id, which in my case was stored in a custom MySQL Database table.

This is the PHP script that will remove the Ebay listing:



/**
 * This function ends an Ebay listing.  Returns true if successful.
 *
 * @param $ebay_id String Ebay Item Id
 * @param $auth_token String EbayAuthToken
 */
function remove_item_from_ebay( $ebay_id, $auth_token ) {
	$endpoint = "https://api.ebay.com/ws/api.dll";

	$xmlbody = '
<?xml version="1.0" encoding="utf-8"?>
<EndItemsRequest xmlns="urn:ebay:apis:eBLBaseComponents">
	<RequesterCredentials>
		<eBayAuthToken>' . $auth_token . '</eBayAuthToken>
	</RequesterCredentials>

	<!-- Call-specific Input Fields -->
	<EndItemRequestContainer>
		<EndingReason>NotAvailable</EndingReason>
		<ItemID>' . $ebay_id . '</ItemID>
		<MessageID>' . $ebay_id . '</MessageID>
	</EndItemRequestContainer>
	<!-- ... more EndItemRequestContainer nodes allowed here ... -->
	<!-- Standard Input Fields -->
	<ErrorLanguage>en_US</ErrorLanguage>
	<WarningLevel>High</WarningLevel>
</EndItemsRequest>
';
	
	$headers = array(
//Regulates versioning of the XML interface for the API
		'X-EBAY-API-COMPATIBILITY-LEVEL: 861',
//the name of the call we are requesting
		'X-EBAY-API-CALL-NAME: EndItems',
//SiteID must also be set in the Request's XML
//SiteID = 0  (US) - UK = 3, Canada = 2, Australia = 15, ....
//SiteID Indicates the eBay site to associate the call with
		'X-EBAY-API-SITEID: 0 ',
	);


//initialise a CURL session
	$connection = curl_init();
//set the server we are using (could be Sandbox or Production server)
	curl_setopt( $connection, CURLOPT_URL, $endpoint );

//stop CURL from verifying the peer's certificate
	curl_setopt( $connection, CURLOPT_SSL_VERIFYPEER, 0 );
	curl_setopt( $connection, CURLOPT_SSL_VERIFYHOST, 0 );

//set the headers using the array of headers
	curl_setopt( $connection, CURLOPT_HTTPHEADER, $headers );

//set method as POST
	curl_setopt( $connection, CURLOPT_POST, 1 );

//set the XML body of the request
	curl_setopt( $connection, CURLOPT_POSTFIELDS, $xmlbody );

//set it to return the transfer as a string from curl_exec
	curl_setopt( $connection, CURLOPT_RETURNTRANSFER, 1 );

	curl_setopt( $connection, CURLOPT_TIMEOUT, 5 );
//Send the Request
	$response = curl_exec( $connection );

//close the connection
	curl_close( $connection );

	$r = simplexml_load_string( $response );

	return $r->Ack == 'Success';
}

You can also grab this code from my Github account.

I may improve this code in the future so that it’s more informative.  I could have it throw an exception on failure, or I might create a class that allows the error to be retrieved.  The response from Ebay also contains the ebay ID (in CorrelationID XML Tag), so for further validation you could verify that the ID is what you expected.

When parsing this response, remember that the root element “becomes” the SimpleXML Object.  In this case, the root element is EndItemsRequest.

A common error is to try to refer to this root element:

$r->EndItemsRequest->Ack   // WRONG
$r->Ack // CORRECT

For quick reference, here’s what the response XML looks like:

    <?xml version="1.0" encoding="UTF-8"?>
<EndItemsResponse xmlns="urn:ebay:apis:eBLBaseComponents">
    <Timestamp>2015-12-16T15:36:29.791Z</Timestamp>
    <Ack>Success</Ack>
    <Version>949</Version>
    <Build>E949_UNI_API5_17774433_R1</Build>
    <EndItemResponseContainer>
        <EndTime>2015-12-16T15:36:29.000Z</EndTime>
        <CorrelationID>222253987187</CorrelationID>
    </EndItemResponseContainer>
</EndItemsResponse>

 

And, also for easy reference, here is an example of what an Ebay failure response looks like:

<EndItemsResponse xmlns="urn:ebay:apis:eBLBaseComponents">
    <Timestamp>2014-09-25T18:52:41.125Z</Timestamp>
    <Ack>Failure</Ack>
    <Errors>
        <ShortMessage>Errors in Input Data.</ShortMessage>
        <LongMessage>Errors in Input Data.Please try again.</LongMessage>
        <ErrorCode>400</ErrorCode>
        <SeverityCode>Error</SeverityCode>
        <ErrorClassification>RequestError</ErrorClassification>
    </Errors>
    <Version>891</Version>
    <Build>E891_UNI_API5_17049963_R1</Build>
    <EndItemResponseContainer>
        <CorrelationID>290345600169</CorrelationID>
        <Errors>
            <ShortMessage>The auction has been closed.</ShortMessage>
            <LongMessage>The auction has already been closed.</LongMessage>
            <ErrorCode>1047</ErrorCode>
            <SeverityCode>Error</SeverityCode>
            <ErrorClassification>RequestError</ErrorClassification>
        </Errors>
    </EndItemResponseContainer>
</EndItemsResponse>

 

Hopefully, this little bit of code will help someone else struggling with how to implement this task.   Since my client was getting payment by Paypal, I used it in a IPN script (Instant Payment Notification).  I also had my IPN script send email notifications with the order information.  Paypal only automatically sends payment notifications to one email address, so if you want to have email notifications sent to multiple address, implementing Paypal’s IPN is one solution.

Improving on This: From PHP Script to PHP Class

Although procedural-style code is common for the PHP programming language, object-oriented programming for the web with PHP is becoming more popular.  Regardless of whether you write OOP style code, creating classes is a good idea for organization.  You can put similar functions together.  Also, using classes really decreases the chance that you’ll run into a naming conflict.  You only have to make sure your function name is unique to the class and not to the whole codebase.

From an object oriented perspective, you want to name your classes after nouns.  So I named my class EbayConnection, although EbayConnector would also work, but naming your class GetEbayConnection would be incorrect.  getEbayConnection (with a lowercase g, by standard conventions) would be a function name, or, when in a class, a method name.   Methods and functions are pretty much the same thing.  It’s just that we only call them methods when they are in a class.  And Methods should usually contain verbs.  At least that’s that I learned — it makes reading code easier and it helps in understanding how the program works!

So, I wrote the class EbayConnection (Grab it on Github).  I expanded on the above function not only by refactoring it a bit, but I also added some error reporting so that the calling program can now get the last error message and last error code.

class EbayConnection {
	private $auth = null;
	private $error_msg = null;
	private $error_code = null;
	function __construct($auth) {
		$this->auth = $auth;
	}
	/**
	 * This function ends an Ebay listing.  Returns true if successful.
	 *
	 * @param $ebay_id String Ebay Item Id
	 */
	public function endItem($ebay_id) {
		$endpoint = "https://api.ebay.com/ws/api.dll";
		$xmlbody = '
		<?xml version="1.0" encoding="utf-8"?>
		<EndItemsRequest xmlns="urn:ebay:apis:eBLBaseComponents">
		<RequesterCredentials>
		<eBayAuthToken>' . $this->auth . '</eBayAuthToken>
		</RequesterCredentials>
		<!-- Call-specific Input Fields -->
		<EndItemRequestContainer>
		<EndingReason>NotAvailable</EndingReason>
		<ItemID>' . $ebay_id . '</ItemID>
		<MessageID>' . $ebay_id . '</MessageID>
		</EndItemRequestContainer>
		<!-- ... more EndItemRequestContainer nodes allowed here ... -->
		<!-- Standard Input Fields -->
		<ErrorLanguage>en_US</ErrorLanguage>
		<WarningLevel>High</WarningLevel>
		</EndItemsRequest>';
		$headers = array(
			//Regulates versioning of the XML interface for the API
			'X-EBAY-API-COMPATIBILITY-LEVEL: 861',
			//the name of the call we are requesting
			'X-EBAY-API-CALL-NAME: EndItems',
			//SiteID must also be set in the Request's XML
			//SiteID = 0  (US) - UK = 3, Canada = 2, Australia = 15, ....
			//SiteID Indicates the eBay site to associate the call with
			'X-EBAY-API-SITEID: 0 ',
			);
		//initialise a CURL session
		$connection = curl_init();
		//set the server we are using (could be Sandbox or Production server)
		curl_setopt( $connection, CURLOPT_URL, $endpoint );
		//stop CURL from verifying the peer's certificate
		curl_setopt( $connection, CURLOPT_SSL_VERIFYPEER, 0 );
		curl_setopt( $connection, CURLOPT_SSL_VERIFYHOST, 0 );
		//set the headers using the array of headers
		curl_setopt( $connection, CURLOPT_HTTPHEADER, $headers );
		//set method as POST
		curl_setopt( $connection, CURLOPT_POST, 1 );
		//set the XML body of the request
		curl_setopt( $connection, CURLOPT_POSTFIELDS, $xmlbody );
		//set it to return the transfer as a string from curl_exec
		curl_setopt( $connection, CURLOPT_RETURNTRANSFER, 1 );
		curl_setopt( $connection, CURLOPT_TIMEOUT, 5 );
		//Send the Request
		$response = curl_exec( $connection );
		//close the connection
		curl_close( $connection );
		$r = simplexml_load_string( $response );
		if($r->Ack == 'Success') {
			return true;
		} else {
			$this->recordError($r);
			return false;
		}
	}
	private function recordError($response) {
		if(isset($response->Errors->LongMessage)) {
			$this->error_msg = $response->Errors->LongMessage;
		} elseif(isset($response->Error->ShortMessage)) {
			$this->error_msg = $response->Errors->ShortMessage;
		}
		if(isset($response->Errors->ErrorCode)) {
			$this->error_code = $response->Errors->ErrorCode;
		}
	}
	/**
	 * @return null|String
	 */
	public function getErrorMsg() {
		return $this->error_msg;
	}
	/**
	 * @return null|String
	 */
	public function getErrorCode() {
		return $this->error_code;
	}
}