At first you have to create an Private Application in XERO. If you didn't yet you can follow the below link: Connect to Xero Private Application Using Php Application |
You already notified that there are two combinations of public/private key file. Public key need to create application and Private key need to sign data before send to XERO. Below is a sample Java script which will connect to XERO private application. |
package com.pkm; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.BufferedReader; import java.io.FileReader; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.KeyFactory; import java.security.Signature; import java.security.interfaces.RSAPrivateKey; import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; /** * Created by pritom on 2/01/2018. */ public class XeroPrivateApiUtils { private static final String NONCE = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static final String ORGANIZATION_URL = "https://api.xero.com/api.xro/2.0/Organisation"; private String sbs; private String path; private String method; private Map<Object, Object> configs = new HashMap<>(); private Map<Object, Object> params = new HashMap<>(); private Map<Object, Object> sign = new HashMap<>(); private String KEY_FILE = null; private String CONSUMER_KEY = null; private String CONSUMER_SECRET = null; public static void main(String[] args) throws Exception { XeroPrivateApiUtils xero = new XeroPrivateApiUtils(); HttpJavaClient.Response response = xero.execute("GET", XeroPrivateApiUtils.ORGANIZATION_URL, new HashMap(), new HashMap()); System.out.println(response.toString()); } private XeroPrivateApiUtils() { this.KEY_FILE = "private.key"; this.CONSUMER_KEY = "IKAEAI8BA..........1MAOR9XBEHU"; this.CONSUMER_SECRET = "STXPEDM..........0FWMPLIDGB6DS"; putToMap(this.configs, "consumer_key", CONSUMER_KEY); putToMap(this.configs, "consumer_secret", CONSUMER_SECRET); putToMap(this.configs, "core_version", "2.0"); putToMap(this.configs, "payroll_version", "1.0"); putToMap(this.configs, "file_version", "1.0"); putToMap(this.configs, "application_type", "Private"); } HttpJavaClient.Response execute( String method, String path, Map<Object, Object> configs, Map<Object, Object> params ) throws Exception { this.method = method; this.path = path; this.params = params; putToMap(this.configs, configs); this.buildParameters(); this.sign(); Map<Object, Object> headers = new HashMap<>(); putToMap(headers, "Accept", "application/json"); if (this.method.equalsIgnoreCase("get")) { return HttpJavaClient.doGet((String) this.sign.get("signed_url"), headers); } return null; } private void sign() throws Exception { putToMap(this.params, "oauth_signature", this.generateSignature()); putToMap(this.sign, "parameters", this.params); putToMap(this.sign, "signature", escape((String) this.params.get("oauth_signature"))); putToMap(this.sign, "signed_url", this.path + "?" + normalizeParameters(true)); putToMap(this.sign, "sbs", this.sbs); } private String generateSignature() throws Exception { switch ((String) this.params.get("oauth_signature_method")) { case "RSA-SHA1": this.sbs = escape(this.method.toUpperCase()); this.sbs += "&" + escape(this.path); this.sbs += "&" + escape(this.normalizeParameters(false)); RSAPrivateKey privateKey = getPrivateKey(KEY_FILE); Signature sign = Signature.getInstance("SHA1withRSA"); sign.initSign(privateKey); sign.update(this.sbs.getBytes("UTF-8")); return encodeBase64(sign.sign()); case "HMAC-SHA1": String secret = this.configs.get("consumer_secret") + "&"; if (this.configs.get("access_token_secret") != null) { secret = secret + this.configs.get("access_token_secret"); } this.sbs = escape(this.method.toUpperCase()); this.sbs += "&" + escape(this.path); this.sbs += "&" + escape(this.normalizeParameters(false)); return hmacSha(secret, this.sbs); default: throw new Exception("Undefined signature method"); } } private static String hmacSha(String key, String value) throws Exception { SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(keySpec); byte[] result = mac.doFinal(value.getBytes()); return encodeBase64(result); } public static String encodeBase64(String o) { BASE64Encoder base64Encoder = new BASE64Encoder(); return base64Encoder.encodeBuffer(o.getBytes()).replace("\r\n", "").replace("\n", ""); } private static String encodeBase64(byte[] o) { BASE64Encoder base64Encoder = new BASE64Encoder(); return base64Encoder.encodeBuffer(o).replace("\r\n", "").replace("\n", ""); } private static RSAPrivateKey getPrivateKey(String fileName) throws Exception { byte[] encoded = decodeBase64(getKey(fileName)); KeyFactory kf = KeyFactory.getInstance("RSA"); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded); return (RSAPrivateKey) kf.generatePrivate(keySpec); } private static byte[] decodeBase64(String o) throws Exception { BASE64Decoder decoder = new BASE64Decoder(); return decoder.decodeBuffer(o); } private String normalizeParameters(Boolean filter) throws Exception { String normalized = ""; for (Object o : this.params.entrySet()) { Map.Entry me2 = (Map.Entry) o; String key = me2.getKey().toString(); Object value = me2.getValue(); Boolean allow = !(key.equalsIgnoreCase("xml") && filter) && !key.endsWith("_secret"); if (allow) { normalized += escape(key) + "=" + escape(value.toString()) + "&"; } } return normalized.length() > 0 ? normalized.substring(0, normalized.length() - 1) : normalized; } private static String escape(String context) throws Exception { context = rawUrlEncode(context); context = stringReplace("+", "%20", context); context = stringReplace("!", "%21", context); context = stringReplace("*", "%2A", context); context = stringReplace("\'", "%27", context); context = stringReplace("(", "%28", context); context = stringReplace(")", "%29", context); return context; } private static String rawUrlEncode(String context) throws Exception { return URLEncoder.encode(context, "UTF-8"); } private static String rawUrlDecode(String context) throws Exception { return URLDecoder.decode(context, "UTF-8"); } private static String stringReplace(String search, String fill, String context) { return context.replace(search, fill); } private void buildParameters() { putToMap(this.params, "oauth_nonce", shuffle(5)); putToMap(this.params, "oauth_token", CONSUMER_KEY); putToMap(this.params, "oauth_version", "1.0"); putToMap(this.params, "oauth_timestamp", "" + ((System.currentTimeMillis()) / 1000)); putToMap(this.params, "oauth_consumer_key", CONSUMER_KEY); putToMap(this.params, "oauth_signature_method", "RSA-SHA1"); this.params = new TreeMap(this.params); //Sorted map by key } private static String shuffle(Integer length) { List<Character> characters = new ArrayList<Character>(); for(char c : NONCE.toCharArray()){ characters.add(c); } StringBuilder output = new StringBuilder(length); while(length != 0) { int randPicker = (int) (Math.random() * characters.size()); output.append(characters.get(randPicker)); length --; } return output.toString(); } private static String getKey(String filename) throws Exception { String key = ""; BufferedReader br = new BufferedReader(new FileReader(filename)); String line; while ((line = br.readLine()) != null) { if (!line.startsWith("-")) { key += line + "\n"; } } br.close(); return key; } private static void putToMap(Map<Object, Object> map, Map fromMap) { for (Object key : fromMap.keySet().toArray()) { putToMap(map, key.toString(), fromMap.get(key)); } } private static void putToMap(Map<Object, Object> map, Object key, Object value) { map.put(key, value); } private static void println(Object o) { System.out.println("" + o); } private static void exception() throws Exception { throw new Exception(); } } |
You can download a sample Private Key File from this link |
And output? Obviously as below: |
{ "Id": "966aed9c-....-4d86-....-c59a6b34eb2c", "Status": "OK", "ProviderName": "Dev Private", "DateTimeUTC": "\/Date(1515683933534)\/", "Organisations": [ { "APIKey": "WXP9HDM..........ILMSZHZIUSIA5", "Name": "Dev Organisation", "LegalName": "Dev Organisation", "PaysTax": true, "Version": "AU", "OrganisationType": "COMPANY", "BaseCurrency": "AUD", "CountryCode": "AU", "IsDemoCompany": false, "OrganisationStatus": "ACTIVE", "FinancialYearEndDay": 30, "FinancialYearEndMonth": 6, "SalesTaxBasis": "ACCRUALS", "SalesTaxPeriod": "QUARTERLY1", "DefaultSalesTax": "Tax Exclusive", "DefaultPurchasesTax": "Tax Inclusive", "CreatedDateUTC": "\/Date(1514794572000)\/", "OrganisationEntityType": "COMPANY", "Timezone": "AUSEASTERNSTANDARDTIME", "ShortCode": "!wnfkR", "OrganisationID": "91de7ce4-....-48e8-....-74573ab8b4e0", "LineOfBusiness": "tests xero integration", "Addresses": [], "Phones": [], "ExternalLinks": [], "PaymentTerms": {} } ] } |
Thursday, January 11, 2018
XERO Private Application | XERO Connect Using Private Application Java | Java to Connect to XERO Private Application
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 ); } } |
Friday, January 5, 2018
Regex Domain Name Validation | Regex URL Validation
Domain Name Validation (Regex-101 Link)
^((https?):\/\/)?([a-zA-Z0-9_][-_a-zA-Z0-9]{0,62}\.)+([a-zA-Z0-9]{1,20})$
URL Validation (Regex-101 Link)
Subscribe to:
Posts (Atom)