Saturday, January 6, 2018

XERO API Integration | XERO Public API Integration | XERO Connect Through Public API

1. Login to https://app.xero.com/Application to create an Public application
2. Now copy "Consumer Key" and "Consumer Secret", which will be needed to connect to XERO. The first step is to get a request token from using above "Consumber Key" and "Consumer Secret", then we will redirect user to XERO login screen, where user can authorize our application and redirect back to our main application with a code, we will then use the code to get access_token and refresh_token. We will store access_token and refresh_token for further use. Below scrip will help us to get Request_Token from XERO end.

<?php
session_start();
include_once "connector.php";

$callback_postfix = "xero-public-api-integration/callback.php?state=".uniqid();
$xero_connector = new XeroConnector(array(
    "oauth_callback" => "http://localhost:81/me/".$callback_postfix
));
$response = $xero_connector->execute(
    "GET", "https://api.xero.com/oauth/RequestToken"
);
if ($response["code"] == 200) {
    $parsed = $xero_connector->extract_params($response["body"]);
    $_SESSION["token"] = $parsed;
    XeroConnector::prettyPrint($parsed);
    $url = "https://api.xero.com/oauth/Authorize?oauth_token=".$parsed["oauth_token"];
    echo '<p>Authorize: <a href="'.$url.'">'.$url.'</a></p>';
}
XeroConnector::prettyPrint($response);
3. After redirected to XERO you will asked to login and then authorize application. (If you are already logged in, then ask for authorize the app)
After authorize you will redirected back you as Callback URL, with a oauth_token and oauth_verifier, you can then process those information to get access_token and refresh_token. Script and result is below respectively.

<?php
session_start();
include_once "connector.php";

XeroConnector::prettyPrint($_SESSION["token"]);
XeroConnector::prettyPrint($_REQUEST);

if (isset($_REQUEST ['oauth_verifier'])) {
    $configs = array(
        "access_token" => $_SESSION["token"]["oauth_token"],
        "access_token_secret" => $_SESSION["token"]["oauth_token_secret"]
    );
    $params = array(
        'oauth_verifier' => $_REQUEST ['oauth_verifier'],
        'oauth_token' => $_REQUEST ['oauth_token']
    );

    $xero_connector = new XeroConnector($params, $configs);
    $response = $xero_connector->execute("GET", "https://api.xero.com/oauth/AccessToken");
    if ($response["code"] == 200) {
        $parsed = $xero_connector->extract_params($response["body"]);
        $_SESSION["access_token"] = $parsed["oauth_token"];
        $_SESSION["oauth_token_secret"] = $parsed["oauth_token_secret"];
        XeroConnector::prettyPrint($parsed);
        echo '<p><a href="organization.php">View Organization Details</a></p>';
    }
    else {
        XeroConnector::prettyPrint($response);
    }
}
Response (Access token and Refresh token collected from XERO end)

Array
(
    [oauth_token] => N0UTBYDEZQFAOLPZ6JZDVJSOBQAJUS
    [oauth_token_secret] => KEOOMKNTNZYAQBNO4WYGCZ3SI33WYO
    [oauth_callback_confirmed] => true
)
Array
(
    [state] => 5a5051b091146
    [oauth_token] => N0UTBYDEZQFAOLPZ6JZDVJSOBQAJUS
    [oauth_verifier] => 7403017
    [org] => u-HyWGrAfasyv8hO$VlheW
)
Array
(
    [oauth_token] => NPTKFCAGC6KXXGU6RWOLIF6RDSUGGO
    [oauth_token_secret] => EWXBV0NWBGLBCIM0ZDSEKDUGOMCWC8
    [oauth_expires_in] => 1800
    [xero_org_muid] => u-HyWGrAfasyv8hO$VlheW
)
View Organization Details
Now you have access_token and refresh_token, below script will help to get organization details.
<?php
session_start();
include_once "connector.php";

$configs = array(
    "oauth_token" => $_SESSION["access_token"],
    "access_token_secret" => $_SESSION["oauth_token_secret"]
);
$params = array(
    "oauth_token" => $_SESSION["access_token"]
);

echo "<h3><a href='xero.php'>Start process again</a></h3>";

$xero_connector = new XeroConnector($params, $configs);
$response = $xero_connector->execute("GET", "https://api.xero.com/api.xro/2.0/Organisation");
if ($response["code"] == 200) {
    $parsed = json_decode($response["body"]);
    XeroConnector::prettyPrint($parsed);
}
XeroConnector::prettyPrint($response);
And organization details is as below:
stdClass Object
(
    [Id] => e8904090-....-46d1-....-829af831fb30
    [Status] => OK
    [ProviderName] => My Public App Rose
    [DateTimeUTC] => /Date(1515213767839)/
    [Organisations] => Array
        (
            [0] => stdClass Object
                (
                    [APIKey] => DZQNXBMG.............YFWIS0ZME
                    [Name] => Mine Test
                    [LegalName] => Mine Test
                    [PaysTax] => 1
                    [Version] => GLOBAL
                    [OrganisationType] => COMPANY
                    [BaseCurrency] => BDT
                    [CountryCode] => BD
                    [IsDemoCompany] => 
                    [OrganisationStatus] => ACTIVE
                    [FinancialYearEndDay] => 31
                    [FinancialYearEndMonth] => 12
                    [DefaultSalesTax] => Tax Exclusive
                    [DefaultPurchasesTax] => Tax Exclusive
                    [CreatedDateUTC] => /Date(1513332449000)/
                    [OrganisationEntityType] => COMPANY
                    [Timezone] => BANGLADESHSTANDARDTIME
                    [ShortCode] => !yXp1C
                    [OrganisationID] => 817ce4e3-....-4da6-....-8ad8fdfbeba2
                    [LineOfBusiness] => Marketing Software
                    [Addresses] => Array
                        (
                        )

                    [Phones] => Array
                        (
                        )

                    [ExternalLinks] => Array
                        (
                        )

                    [PaymentTerms] => stdClass Object
                        (
                        )

                )

        )

)
And finally, below is the Connector.php script:
<?php
class XeroConnector
{
    private $headers = array();
    private $parameters = array();
    private $configs = array(
        "consumer_key" => "8J3YWIAOE4XFH..........FPDB54R",
        "consumer_secret" => "7UGYHJZYITL...........B1FXCMTV",
        "core_version" => "2.0",
        "payroll_version" => "1.0",
        "file_version" => "1.0",
        "application_type" => "Public",
        "user_agent" => "Trial #3"
    );

    function __construct($parameters, $configs = array())
    {
        $this->parameters = $parameters;
        $this->configs = array_merge($this->configs, $configs);
    }

    function execute($method, $target, $post_body = "")
    {
        $this->post_body = $post_body;
        $this->method = strtoupper($method);
        $this->path = $target;
        $this->nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        $this->buildParameters();
        $this->sign();
        return $this->curlRequest($this->sign["signed_url"]);
    }

    private function sign()
    {
        $this->parameters["oauth_signature"] = $this->generateSignature();
        $this->sign = array(
            'parameters' => $this->parameters,
            'signature' => $this->escape($this->parameters["oauth_signature"]),
            'signed_url' => $this->path . '?' . $this->normalizeParameters('true'),
            'header' => $this->getHeaderString(),
            'sbs' => $this->sbs
        );
    }

    private function getHeaderString($args = array())
    {
        $result = 'OAuth ';

        foreach ($this->parameters as $pName => $pValue) {
            if (strpos($pName, 'oauth_') !== 0)
                continue;
            if (is_array($pValue)) {
                foreach ($pValue as $val) {
                    $result .= $pName . '="' . $this->escape($val) . '", ';
                }
            }
            else {
                $result .= $pName . '="' . $this->escape($pValue) . '", ';
            }
        }
        return preg_replace('/, $/', '', $result);
    }

    private function generateSignature()
    {
        switch ($this->parameters['oauth_signature_method']) {
            case 'RSA-SHA1':
                $private_key = openssl_pkey_get_private($this->readFile($this->configs['rsa_private_key']));
                $this->sbs = $this->escape($this->method) . "&" . $this->escape($this->path) . "&" . $this->escape($this->normalizeParameters());
                openssl_sign($this->sbs, $signature, $private_key);
                openssl_free_key($private_key);
                return base64_encode($signature);
            case 'PLAINTEXT':
                return urlencode($this->configs["consumer_secret"]);
            case 'HMAC-SHA1':
                $secret = $this->configs["consumer_secret"]."&";
                if (isset($this->configs["access_token_secret"])) {
                    $secret = $secret . $this->configs["access_token_secret"];
                }
                $this->sbs = $this->escape($this->method) . '&' . $this->escape($this->path) . '&' . $this->escape($this->normalizeParameters());
                return base64_encode(hash_hmac('sha1', $this->sbs, $secret, true));
            default:
                throw new Exception('Unknown signature method');
        }
    }

    private function normalizeParameters($filter = 'false')
    {
        $elements = array();
        ksort($this->parameters);
        foreach ($this->parameters as $paramName => $paramValue) {
            if ($paramName == 'xml') {
                if ($filter == "true")
                    continue;
            }
            if (preg_match('/\w+_secret/', $paramName))
                continue;
            if (is_array($paramValue)) {
                sort($paramValue);
                foreach ($paramValue as $element)
                    array_push($elements, $this->escape($paramName) . '=' . $this->escape($element));
                continue;
            }
            array_push($elements, $this->escape($paramName) . '=' . $this->escape($paramValue));

        }
        return join('&', $elements);
    }

    private function readFile($file_path)
    {
        $fp = fopen($file_path, "r");
        $file_contents = fread($fp, 8192);
        fclose($fp);
        return $file_contents;
    }

    private function escape($string)
    {
        if ($string === 0)
            return 0;
        if (empty($string))
            return '';
        if (is_array($string))
            throw new Exception('Array passed to escape()');

        $string = rawurlencode($string);
        $string = str_replace('+', '%20', $string);
        $string = str_replace('!', '%21', $string);
        $string = str_replace('*', '%2A', $string);
        $string = str_replace('\'', '%27', $string);
        $string = str_replace('(', '%28', $string);
        $string = str_replace(')', '%29', $string);
        return $string;
    }

    private function buildParameters()
    {
        $parameters = array();
        $parameters["oauth_nonce"] = $this->getNonce(5);
        $parameters["oauth_version"] = "1.0";
        $parameters["oauth_timestamp"] = time();
        $parameters["oauth_consumer_key"] = $this->configs["consumer_key"];
        $parameters["oauth_signature_method"] = "HMAC-SHA1";
        $this->parameters = array_merge($parameters, $this->parameters);
    }

    private function getNonce($length = 5)
    {
        $result = '';
        $cLength = strlen($this->nonce_chars);
        for ($i = 0; $i < $length; $i++) {
            $rnum = rand(0, $cLength);
            $result .= substr($this->nonce_chars, $rnum, 1);
        }
        return $result;
    }

    public static function prettyPrint($o)
    {
        echo "<pre>";
        print_r($o);
        echo "</pre>";
    }

    public function extract_params($body)
    {
        $kvs = explode ( '&', $body );
        $decoded = array ();
        foreach ( $kvs as $kv ) {
            $kv = explode ( '=', $kv, 2 );
            $kv [0] = $this->safe_decode ( $kv [0] );
            $kv [1] = $this->safe_decode ( $kv [1] );
            $decoded [$kv [0]] = $kv [1];
        }
        return $decoded;
    }

    private function safe_encode($data) {
        if (is_array ( $data )) {
            return array_map ( array (
                $this,
                'safe_encode'
            ), $data );
        }
        else if (is_scalar ( $data )) {
            return str_ireplace ( array (
                '+',
                '%7E'
            ), array (
                ' ',
                '~'
            ), rawurlencode ( $data ) );
        }
        else {
            return '';
        }
    }

    private function safe_decode($data)
    {
        if (is_array ( $data )) {
            return array_map ( array (
                $this,
                'safe_decode'
            ), $data );
        }
        else if (is_scalar ( $data )) {
            return rawurldecode ( $data );
        }
        else {
            return '';
        }
    }

    private function curlRequest($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_VERBOSE, true);

        $this->headers['Accept'] = 'application/json';
        switch ($this->method) {
            case "GET":
                $this->content_length = 0;
                break;
            case 'POST':
                $this->headers['Content-Length'] = strlen($this->post_body);
                curl_setopt($ch, CURLOPT_POST, TRUE);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $this->post_body);
                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
                break;
            case 'PUT' :
                $this->headers['Content-Length'] = strlen($this->post_body);
                $fh = tmpfile();
                fwrite($fh, $this->post_body);
                rewind($fh);
                curl_setopt($ch, CURLOPT_PUT, true);
                curl_setopt($ch, CURLOPT_INFILE, $fh);
                curl_setopt($ch, CURLOPT_INFILESIZE, $this->headers ['Content-Length']);
                $this->headers['Content-Type'] = 'application/x-www-form-urlencoded';
                break;
            default :
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->method);
        }

        if (count($this->headers) > 0) {
            $headers = array();
            foreach ($this->headers as $k => $v) {
                $headers [] = trim($k . ': ' . $v);
            }
            curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        }

        $response = curl_exec($ch);
        if ($response === false) {
            $response = 'Curl error: ' . curl_error($ch);
            $code = 1;
        }
        else {
            $code = curl_getinfo ($ch, CURLINFO_HTTP_CODE);
        }
        $info = curl_getinfo ($ch);

        curl_close($ch);

        return array(
            'body' => $response, 'code' => $code, 'info' => $info
        );
    }
}

No comments:

Post a Comment