Recently I had to take a step back to address some issues with the development of the eBay Integration plug-in for Zen Cart.
The main reason to reconsider how some things have been implemented so far was due to eBay requiring the use of the oAuth protocol to call some API methods. A secondary reason was to restructure the code to be more maintainable and extensible.
Implementing the oAuth protocol was challenging because the documentation is confusing and there is a lack of PHP examples to follow. But, I finally got it working.
Here is the class I developed that the code uses to get an access token and refresh it when necessary.
<?php
// -----
// eBay Integration plugin for Zen Cart
//
// Copyright (c) 2021, Roger Smith (sales@jonrocket.com)
//
// -----
// oAuth Methods
//
require_once DIR_FS_CATALOG . 'cron/ebayincludes/utilities/ebayutils.php';
function ebayGetAccessToken()
{
$token = ebayGetConfiguration('ACCESS_TOKEN');
if ($token != '')
{
$expires = DateTime::createFromFormat('Y-m-d H:i:s', ebayGetConfiguration('ACCESS_TOKEN_EXPIRES'));
$now = new DateTime();
$diff = ebayDateIntervalToSeconds(date_diff($expires, $now));
if ($diff < 120 || ($now > $expires))
{
ebayRefreshAccessToken();
$token = ebayGetConfiguration('ACCESS_TOKEN');
}
}
else
{
ebayLog(EBAY_LOG_ERROR_NO_ACCESS_TOKEN, true);
}
return $token;
}
function ebayRefreshAccessToken()
{
$refreshToken = ebayGetConfiguration('REFRESH_TOKEN');
if ($refreshToken != '')
{
$expires = DateTime::createFromFormat('Y-m-d H:i:s', ebayGetConfiguration('REFRESH_TOKEN_EXPIRES'));
$now = new DateTime();
$diff = ebayDateIntervalToSeconds(date_diff($expires, $now));
if ($diff < 60)
{
ebayLog(EBAY_LOG_ERROR_REFRESH_TOKEN_EXPIRED, true);
return;
}
else if ($diff < (30 * 24 * 60 * 60))
{
$lastWarning = ebayGetConfiguration('LAST_REFRESH_EXPIRES_WARNING');
if ($lastWarning == '' || (time() - intval($lastWarning)) > (24 * 60 * 60))
{
ebayLog(EBAY_LOG_ERROR_REFRESH_TOKEN_EXPIRING, false);
ebaySetConfiguration('LAST_REFRESH_EXPIRES_WARNING', strval(time()));
}
}
}
else
{
ebayLog(EBAY_LOG_ERROR_NO_REFRESH_TOKEN, true);
}
$now = new DateTimeImmutable();
$scope = implode(' ', array(
'https://api.ebay.com/oauth/api_scope/sell.inventory',
'https://api.ebay.com/oauth/api_scope/sell.account',
'https://api.ebay.com/oauth/api_scope/sell.account.readonly',
'https://api.ebay.com/oauth/api_scope/sell.fulfillment'
));
$appID = ebayGetConfiguration('PROD_APPID');
$certID = ebayGetConfiguration('PROD_CERTID');
$ruName = ebayGetConfiguration('PROD_RU_NAME');
$url = "https://api.ebay.com/identity/v1/oauth2/token";
$authorization = base64_encode($appID . ':' . $certID);
$curlHandle = curl_init($url);
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array(
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Basic ' . $authorization
));
$postFields = http_build_query(array(
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken,
'scope' => $scope
));
curl_setopt($curlHandle, CURLHEADER_SEPARATE, true);
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curlHandle, CURLOPT_POST, 1);
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $postFields);
$response = curl_exec($curlHandle);
curl_close($curlHandle);
$json = json_decode($response, true);
if ($json != null)
{
@$error = $json['error'];
if ($error != '')
{
ebayLog(EBAY_LOG_ERROR_REFRESH_TOKEN . ' (' . $error . ' - ' . $json['error_description'] . ')', true);
return;
}
$accessToken = $json['access_token'];
ebaySetConfiguration('ACCESS_TOKEN', $accessToken);
$accessTokenExpiresIn = $json['expires_in'];
$expires = new DateInterval('PT' . $accessTokenExpiresIn . 'S');
$accessTokenExpires = $now->add($expires);
ebaySetConfiguration('ACCESS_TOKEN_EXPIRES', $accessTokenExpires->format('Y-m-d H:i:s'));
}
}
function ebaySetAccessToken($code)
{
$now = new DateTimeImmutable();
$appID = ebayGetConfiguration('PROD_APPID');
$certID = ebayGetConfiguration('PROD_CERTID');
$ruName = ebayGetConfiguration('PROD_RU_NAME');
$url = "https://api.ebay.com/identity/v1/oauth2/token";
$authorization = base64_encode($appID . ':' . $certID);
$curlHandle = curl_init($url);
curl_setopt($curlHandle, CURLOPT_HTTPHEADER, array(
'Content-Type: application/x-www-form-urlencoded',
'Authorization: Basic ' . $authorization
));
$postFields = http_build_query(array(
'grant_type' => 'authorization_code',
'code' => urldecode($code),
'redirect_uri' => $ruName
));
curl_setopt($curlHandle, CURLHEADER_SEPARATE, true);
curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlHandle, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curlHandle, CURLOPT_POST, 1);
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $postFields);
$response = curl_exec($curlHandle);
curl_close($curlHandle);
$json = json_decode($response, true);
if ($json != null)
{
@$error = $json['error'];
if ($error != '')
{
echo '<p><b>Error:</b> ' . $error . ' - ' . $json['error_description'] . '</p>';
return;
}
$accessToken = $json['access_token'];
ebaySetConfiguration('ACCESS_TOKEN', $accessToken);
$accessTokenExpiresIn = $json['expires_in'];
$expires = new DateInterval('PT' . $accessTokenExpiresIn . 'S');
$accessTokenExpires = $now->add($expires);
ebaySetConfiguration('ACCESS_TOKEN_EXPIRES', $accessTokenExpires->format('Y-m-d H:i:s'));
$refreshToken = $json['refresh_token'];
ebaySetConfiguration('REFRESH_TOKEN', $refreshToken);
$refreshTokenExpiresIn = $json['refresh_token_expires_in'];
$expires = new DateInterval('PT' . $refreshTokenExpiresIn . 'S');
$refreshTokenExpires = $now->add($expires);
ebaySetConfiguration('REFRESH_TOKEN_EXPIRES', $refreshTokenExpires->format('Y-m-d H:i:s'));
}
}
Code language: HTML, XML (xml)
The ebayGetAccessToken method returns the current access token unless it has expired or is about to expire. If it has expired or is about to expire, the refresh token is used to obtain a new access token.
The initial access token and refresh token are retrieved by the ebaySetAccessToken method. A special code returned by eBay when the user logs into eBay and authorizes the plug-in to access the user’s eBay info is passed to the method. The code is used to call an eBay method to retrieve the tokens.
I had been putting most of the code for the admin interface in a single source file. This was quickly becoming unwieldly. So, I recently took some time to clean it up by moving some methods to a utility file and encapsulating things the GUI tabs into individual classes in separate files.