Showing posts with label Xero. Show all posts
Showing posts with label Xero. Show all posts

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
        );
    }
}

Saturday, May 6, 2017

Create Contact in Xero Via API using PHP

The very first step you make a connection between your application and Xero API. To do so you need to create and application in Xero and establish a connection. Follow the below link to see how PHP communicates with Xero Application:

http://pritomkumar.blogspot.com/2017/05/connect-to-xero-private-application.html

And below is full PHP script which will create Contacts in Xero:


<?php
define('BASE_PATH', dirname(__FILE__));
$configs = array(
    "consumer_key" => "OVLVISLORWW72YRDYAV3KI........",
    "consumer_secret" => "HRTPKPBSSBNN4TA32BKW.......",
    "core_version" => "2.0",
    "payroll_version" => "1.0",
    "file_version" => "1.0",
    "rsa_private_key" => BASE_PATH . "/XeroCerts/privatekey.pem",
    "application_type" => "Private",
    "oauth_callback" => "oob",
    "user_agent" => "Trial #3"
);

$parameters = array();

createContact($configs, $parameters);

function createContact($configs, $parameters)
{
    $post = array(
        "Contacts" => array(
            "Contact" => array(
                "Name" => "Xero Contact 1"
            ),
            "Contact 2" => array(
                "Name" => "Xero Contact 2"
            )
        )
    );
    $xero_connector = new XeroConnector($configs, $parameters);
    $contact_data = $xero_connector->execute("POST", "Contact", $post);
    XeroConnector::prettyPrint($contact_data);
}

class XeroConnector
{
    private $configs = null;
    private $headers = array();
    private $parameters = array();
    private $nonce_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

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

    function execute($method, $target, $post_body = "")
    {
        if (is_array($post_body)) {
            $this->post_body = $this->arrayToXml($post_body);
        } else {
            $this->post_body = $post_body;
        }
        $this->method = strtoupper($method);
        $this->path = "https://api.xero.com/api.xro/2.0/$target";
        $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 curlRequest($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_USERAGENT, "Test APP");
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        $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/xml';
                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);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        curl_close($ch);

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

    private function safe_encode_xml_entity($data)
    {
        return str_ireplace(
            array("<",    ">",    "'",      "\"",    "&"),
            array("&lt;", "&gt;", "&apos;", "&quot", "&amp;"),
            $data
        );
    }

    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':
                $this->sbs = $this->escape($this->method) . '&' . $this->escape($this->path) . '&' . $this->escape($this->normalizeParameters());
                return base64_encode(hash_hmac('sha1', $this->sbs, $this->configs["consumer_secret"], true));
            default:
                throw new OAuthSimpleException('Unknown signature method for OAuthSimple');
        }
    }

    private function normalizeParameters($filter = 'false')
    {
        $elements = array();
        ksort($this->parameters);
        foreach ($this->parameters as $k => $v) {
            if ($k == 'xml') {
                if ($filter == "true")
                    continue;
            }
            if (preg_match('/\w+_secret/', $k))
                continue;
            if (is_array($v)) {
                sort($v);
                foreach ($v as $element)
                    array_push($elements, $this->escape($k) . '=' . $this->escape($element));
                continue;
            }
            array_push($elements, $this->escape($k) . '=' . $this->escape($v));
        }
        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 arrayToXml($data, $tab = 0)
    {
        $xml = "\r\n";
        foreach ($data as $k => $v)
        {
            $sp = "";
            for ($i = 0; $i < $tab; $i++)
            {
                $sp .= "\t";
            }
            if (strpos($k, " ") !== false) {
                $k = substr($k, 0, strpos($k, " "));
            }
            if (is_array($v)) {
                $tab2 = $tab + 1;
                $v = $this->arrayToXml($v, $tab2);
                $xml .= "$sp<$k>$v". "$sp</$k>\r\n";
            } else {
                $v = $this->safe_encode_xml_entity($v);
                $xml .= "$sp<$k>$v</$k>\r\n";
            }
        }
        return $xml;
    }

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

        $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()
    {
        $this->parameters["oauth_nonce"] = $this->getNonce(20);
        $this->parameters["oauth_token"] = $this->configs["consumer_key"];
        $this->parameters["oauth_version"] = "1.0";
        $this->parameters["oauth_timestamp"] = time();
        $this->parameters["oauth_consumer_key"] = $this->configs["consumer_key"];
        $this->parameters["oauth_signature_method"] = "RSA-SHA1";
    }

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

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




Below is a screen-shot of my Xero portal:




Output will be as follows:


Array
(
    [code] => 200
    [body] => stdClass Object
        (
            [Id] => e614f8ea-07b1-4b69-88b0-c174ffb062d5
            [Status] => OK
            [ProviderName] => Trial #2
            [DateTimeUTC] => /Date(1494045918570)/
            [Contacts] => Array
                (
                    [0] => stdClass Object
                        (
                            [ContactID] => 7de72cb7-7d6f-13f8-9e76-ea191269553a
                            [ContactStatus] => ACTIVE
                            [Name] => Xero Contact 1
                            [EmailAddress] => 
                            [BankAccountDetails] => 
                            [Addresses] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [AddressType] => STREET
                                            [City] => 
                                            [Region] => 
                                            [PostalCode] => 
                                            [Country] => 
                                        )

                                    [1] => stdClass Object
                                        (
                                            [AddressType] => POBOX
                                            [City] => 
                                            [Region] => 
                                            [PostalCode] => 
                                            [Country] => 
                                        )

                                )

                            [Phones] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [PhoneType] => DEFAULT
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                    [1] => stdClass Object
                                        (
                                            [PhoneType] => DDI
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                    [2] => stdClass Object
                                        (
                                            [PhoneType] => FAX
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                    [3] => stdClass Object
                                        (
                                            [PhoneType] => MOBILE
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                )

                            [UpdatedDateUTC] => /Date(1494044711587+0000)/
                            [ContactGroups] => Array
                                (
                                )

                            [IsSupplier] => 
                            [IsCustomer] => 
                            [SalesTrackingCategories] => Array
                                (
                                )

                            [PurchasesTrackingCategories] => Array
                                (
                                )

                            [ContactPersons] => Array
                                (
                                )

                            [HasValidationErrors] => 
                        )

                    [1] => stdClass Object
                        (
                            [ContactID] => 8eb00757-74f3-4302-99a8-3453dd4d456c
                            [ContactStatus] => ACTIVE
                            [Name] => Xero Contact 2
                            [EmailAddress] => 
                            [BankAccountDetails] => 
                            [Addresses] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [AddressType] => STREET
                                            [City] => 
                                            [Region] => 
                                            [PostalCode] => 
                                            [Country] => 
                                        )

                                    [1] => stdClass Object
                                        (
                                            [AddressType] => POBOX
                                            [City] => 
                                            [Region] => 
                                            [PostalCode] => 
                                            [Country] => 
                                        )

                                )

                            [Phones] => Array
                                (
                                    [0] => stdClass Object
                                        (
                                            [PhoneType] => DEFAULT
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                    [1] => stdClass Object
                                        (
                                            [PhoneType] => DDI
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                    [2] => stdClass Object
                                        (
                                            [PhoneType] => FAX
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                    [3] => stdClass Object
                                        (
                                            [PhoneType] => MOBILE
                                            [PhoneNumber] => 
                                            [PhoneAreaCode] => 
                                            [PhoneCountryCode] => 
                                        )

                                )

                            [UpdatedDateUTC] => /Date(1494044711620+0000)/
                            [ContactGroups] => Array
                                (
                                )

                            [IsSupplier] => 
                            [IsCustomer] => 
                            [SalesTrackingCategories] => Array
                                (
                                )

                            [PurchasesTrackingCategories] => Array
                                (
                                )

                            [ContactPersons] => Array
                                (
                                )

                            [HasValidationErrors] => 
                        )

                )

        )

)

Friday, May 5, 2017

Connect to Xero Private Application Using Php Application

The link below is for Xero dashboard:
https://my.xero.com

Very first you need to create an application in Xero, follow the link below to create application:
https://app.xero.com/Application/

Click on "Add Application" and fill up required fields. In this step you will need public/private key pair. To generate public/pair key follow the link. You need to upload "publickey.cer" in xero application form and other file named "privatekey.perm" need to place in a folder named "XeroCerts" in your application directory.




After click save you will forward to next page, from where you can get Consumer Key & Consumer Secret which will need to connect to your organization via this application.





You are done with create application in Xero.

Next step to write some PHP script to connect to Xero.
Below is a small PHP script which help you to connect to Xero.


<?php
define('BASE_PATH', dirname(__FILE__));
$configs = array(
    "consumer_key" => "QM6QQSESK2KVGTRUGY1........",
    "consumer_secret" => "LHAHIPRIIP6ZZASIHPP........",
    "core_version" => "2.0",
    "payroll_version" => "1.0",
    "file_version" => "1.0",
    "rsa_private_key" => BASE_PATH . "/XeroCerts/privatekey.pem",
    "rsa_public_key" => BASE_PATH . "/XeroCerts/publickey.cer",
    "application_type" => "Private",
    "oauth_callback" => "oob",
    "user_agent" => "Trial #3"
);

$parameters = array();

$xero_connector = new XeroConnector($configs, $parameters);
$organization_data = $xero_connector->execute("GET", "Organisation");
XeroConnector::prettyPrint($organization_data);

class XeroConnector
{
    private $configs = null;
    private $headers = array();
    private $parameters = array();

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

    function execute($method, $target, $post_body = "")
    {
        $this->post_body = $post_body;
        $this->method = strtoupper($method);
        $this->path = "https://api.xero.com/api.xro/2.0/$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 curlRequest($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        $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);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        curl_close($ch);

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

    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':
                $this->sbs = $this->escape($this->method) . '&' . $this->escape($this->path) . '&' . $this->escape($this->normalizeParameters());
                return base64_encode(hash_hmac('sha1', $this->sbs, $this->configs["consumer_secret"], true));
            default:
                throw new OAuthSimpleException('Unknown signature method for OAuthSimple');
        }
    }

    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 OAuthSimpleException('Array passed to _oauthEscape');

        $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()
    {
        $this->parameters["oauth_nonce"] = $this->getNonce(5);
        $this->parameters["oauth_token"] = $this->configs["consumer_key"];
        $this->parameters["oauth_version"] = "1.0";
        $this->parameters["oauth_timestamp"] = time();
        $this->parameters["oauth_consumer_key"] = $this->configs["consumer_key"];
        $this->parameters["oauth_signature_method"] = "RSA-SHA1";
    }

    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>";
    }
}


And output will be like this on execution the above script:


Array
(
    [body] => stdClass Object
        (
            [Id] => 0d1fe562-8621-4bbb-b249-6a5ea051f308
            [Status] => OK
            [ProviderName] => Trial #3
            [DateTimeUTC] => /Date(1494001316337)/
            [Organisations] => Array
                (
                    [0] => stdClass Object
                        (
                            [APIKey] => W2N0UZYHPLFSPABKS2SS42AF5GDKMD
                            [Name] => Trial #3
                            [LegalName] => Trial #3
                            [PaysTax] => 1
                            [Version] => AU
                            [OrganisationType] => COMPANY
                            [BaseCurrency] => AUD
                            [CountryCode] => AU
                            [IsDemoCompany] => 
                            [OrganisationStatus] => ACTIVE
                            [FinancialYearEndDay] => 30
                            [FinancialYearEndMonth] => 6
                            [SalesTaxBasis] => ACCRUALS
                            [SalesTaxPeriod] => QUARTERLY1
                            [DefaultSalesTax] => Tax Exclusive
                            [DefaultPurchasesTax] => Tax Inclusive
                            [CreatedDateUTC] => /Date(1493991850000)/
                            [OrganisationEntityType] => COMPANY
                            [Timezone] => AUSEASTERNSTANDARDTIME
                            [ShortCode] => !GS3w5
                            [OrganisationID] => c653628f-a901-4836-8de1-6b00d23664fe
                            [LineOfBusiness] => Software Development & Consulting
                            [Addresses] => Array
                                (
                                )

                            [Phones] => Array
                                (
                                )

                            [ExternalLinks] => Array
                                (
                                )

                            [PaymentTerms] => stdClass Object
                                (
                                )

                        )

                )

        )

    [code] => 200
)