Netsuite Token Based Integration - WSO2 EI

Description 


This article will explain, how to integrate Netsuite using WSO2 EI (Version 6.5.0)



We are going to use Netsuite token-based authentication which supports OAuth 1.0 
Also, from the Netsuite end, we are using Restlet and suite-script to retrieve the data.
Restlet will expose as a service to call externally.

Follow the initial steps below to create the authentication credentials in Netsuite.

  • The best practice is to create a user role with the required permission to get the data (not using current role).
  • Add the user role to the user you are using to get the data.
  • Then get the token Id and the secret for that user with the created role.
  • Next, create an integration app and get the consumer key and secret.

In-order the acquire the data, we are using Netsuite Saved Search 

  • This saved search will contain the data we need to read from Netsuite.
Then we need to create the script (Javascript) to get the data from the Saved search we created.

Following is the script which I am using for this example

Suitescript



function getProductionIncome(){

var records = [];
var response = "";
var resultIndex = 0;
var resultStep = 1000;
var transactionSearch = nlapiLoadSearch(null,'customsearchcustom_account_search_sync_t');
var runTransactionSearch = transactionSearch.runSearch();
var transactionSearchResult = runTransactionSearch.getResults(resultIndex, resultIndex + resultStep);

var trnle=  transactionSearchResult.length;

while(transactionSearchResult.length > 0){
for ( var i = 0;  transactionSearchResult != null && i < transactionSearchResult.length; i++ ){
var record = new Object();
    var transactionResult= transactionSearchResult[i];
    var columns = transactionResult.getAllColumns();
    var transactionFullDate =  transactionResult.getValue(columns[1]);
      record.id = transactionFullDate;
      record.name = transactionResult.getValue(columns[3]);
    records.push(record);
}
resultIndex = resultIndex + resultStep;
   transactionSearchResult = runTransactionSearch.getResults(resultIndex, resultIndex + resultStep); 
}
return JSON.stringify(records);;
}

Deploy the above script and get the external url which need to be called from the following proxy service



Proxy service




<?xml version="1.0" encoding="UTF-8"?>
<proxy name="NetSuiteLTVDataSync" startOnLoad="true" transports="http https" xmlns="http://ws.apache.org/ns/synapse">
    <target>
        <inSequence>
            <property name="script_id" scope="default" type="STRING" value="xxxx"/>
            <property name="script_deployment_id" scope="default" type="STRING" value="xxxxx"/>
            <class name="com.wso2.netsuite.oauthentication.NetsuiteOauthentication"/>
            <log>
                <property expression="$ctx:OAuthVal" name="OAuthVal log: "/>
            </log>
            <header expression="$ctx:OAuthVal" name="Authorization" scope="transport"/>
            <header action="remove" name="Content-Type" scope="transport"/>
            <header name="Accept" scope="transport" value="application/json"/>
            <header name="Content_type" scope="transport" value="application/json"/>
            <call>
                <endpoint>
                    <http method="get" uri-template="https://rest.na1.netsuite.com/app/site/hosting/restlet.nl?script=xxx&amp;deploy=xxxx">
                        <timeout>
                            <duration>300000</duration>
                            <responseAction>fault</responseAction>
                        </timeout>
                        <suspendOnFailure>
                            <errorCodes>-1</errorCodes>
                            <initialDuration>0</initialDuration>
                            <progressionFactor>1.0</progressionFactor>
                            <maximumDuration>0</maximumDuration>
                        </suspendOnFailure>
                        <markForSuspension>
                            <errorCodes>-1</errorCodes>
                        </markForSuspension>
                    </http>
                </endpoint>
            </call>
            <property expression="$body/m0:text" name="JsonValue" scope="default" type="STRING" xmlns:m0="http://ws.apache.org/commons/ns/payload"/>
            <enrich>
                <source clone="true" property="JsonValue" type="property"/>
                <target type="body"/>
            </enrich>
            <property name="ContentType" scope="axis2" type="STRING" value="application/json"/>
            <property expression="//jsonElement" name="JsonValue" scope="default" type="STRING"/>
            <iterate expression="//jsonElement" sequential="true" xmlns:m0="http://services.samples">
                <target>
                    <sequence>
                        <log level="custom">
                            <property expression="//id" name="ID_JsonValue========="/>
                            <property expression="//name" name="Name_JsonValue========="/>
                        </log>
                        <payloadFactory media-type="xml">
                            <format>
                                <soapenv:Envelope xmlns:dat="http://ws.wso2.org/dataservice" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
                                    <soapenv:Header/>
                                    <soapenv:Body>
                                        <p:insert_Account_operation_batch_req xmlns:p="http://ws.wso2.org/dataservice">
                                           <insert_Account_operation xmlns="http://ws.wso2.org/dataservice">
                                        <idAccount xmlns:xs="http://ws.wso2.org/dataservice">$1</idAccount>
                                               <Name xmlns:xs="http://ws.wso2.org/dataservice">$2</Name>
                                                <Amount xmlns:xs="http://ws.wso2.org/dataservice">$3</Amount>
                                            </insert_Account_operation>
                                        </p:insert_Account_operation_batch_req>
                                    </soapenv:Body>
                                </soapenv:Envelope>
                            </format>
                            <args>
                                <arg evaluator="xml" expression="//id"/>
                                <arg evaluator="xml" expression="//name"/>
                                <arg evaluator="xml" expression="//id"/>
                            </args>
                        </payloadFactory>
                        <call>
                            <endpoint>
                                <address uri="http://localhost:8280/services/Account_DataService"/>
                            </endpoint>
                        </call>
                    </sequence>
                </target>
            </iterate>
        </inSequence>
        <outSequence/>
        <faultSequence/>
    </target>
</proxy>

Local Entry

This local entry values will be read from the class mediator after this.

<?xml version="1.0" encoding="UTF-8"?>
<localEntry key="NetsuiteConfig" xmlns="http://ws.apache.org/ns/synapse">
    <list>
        <baseurl>https://rest.na1.netsuite.com/app/site/hosting/restlet.nl</baseurl>
        <httpmethod>GET</httpmethod>
        <tokenid>xxxxxxxxxxxxxxxx</tokenid>
        <tokensecret> xxxxxxxxxxxxxxxx </tokensecret>
        <consumerkey> xxxxxxxxxxxxxxxx </consumerkey>
        <consumersecret> xxxxxxxxxxxxxxxx </consumersecret>
        <signaturemethod>HMAC-SHA1</signaturemethod>
        <oauthversion>1.0</oauthversion>
        <realm>xxxxxxxxxxxxxxxx </realm>
    </list>

</localEntry>


Class mediator (NetsuiteOauthentication.java)




package com.wso2.netsuite.oauthentication;


import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;


import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.xml.sax.InputSource;

import org.apache.synapse.MessageContext; 
import org.apache.synapse.mediators.AbstractMediator;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;


import org.apache.synapse.config.Entry;



public class NetsuiteOauthentication extends AbstractMediator { 
private static final String ALPHA_NUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static String OAuth ="null";
private String variableOAuth;
private String SCRIPT_DEPLOYMENT_ID;
private String SCRIPT_ID;
public String randomAlphaNumeric(int count) {
StringBuilder builder = new StringBuilder();
while (count-- != 0) {
int character = (int)(Math.random()*ALPHA_NUMERIC_STRING.length());
builder.append(ALPHA_NUMERIC_STRING.charAt(character));
}
return builder.toString();
}
private String computeSignature(String baseString, String keyString) throws GeneralSecurityException, UnsupportedEncodingException {

final String EMPTY_STRING = "";
final String CARRIAGE_RETURN = "\r\n";
final String UTF8 = "UTF-8";
String HMAC_SHA1 = "HmacSHA1";
SecretKeySpec key = new SecretKeySpec(keyString.getBytes(UTF8), HMAC_SHA1);
Mac mac = Mac.getInstance(HMAC_SHA1);
mac.init(key);
byte[] bytes = mac.doFinal(baseString.getBytes(UTF8));
String base= bytesToBase64String(bytes).replace(CARRIAGE_RETURN, EMPTY_STRING);
return URLEncoder.encode(base, "UTF-8");
}

private String bytesToBase64String(byte[] bytes) {
return Base64Encoder.getInstance().encode(bytes);
}

public boolean mediate(MessageContext context) { 
try {
String BASE_URL =  null;
String HTTP_METHOD =  null;
String TOKEN_ID = null;
String TOKEN_SECRET = null;
String CONSUMER_KEY = null;
String CONSUMER_SECRET = null;
String SIGNATURE_METHOD = null;
String OAUTH_NONCE = randomAlphaNumeric(20);
String TIME_STAMP = String.valueOf(System.currentTimeMillis() / 1000);
String OAUTH_VERSION = null;
setScript_deployment_id( (String) context.getProperty("script_deployment_id"));
setScript_id( (String) context.getProperty("script_id"));
SCRIPT_DEPLOYMENT_ID = getScript_deployment_id();
SCRIPT_ID = getScript_id();
String REALM= null;
String LOCAL_ENTRY_ID = "NetsuiteConfig";
String LOCAL_ENTRY_VALUE = null;
Document xmldoc = null;

//Class mediator reading local entry values.

Entry localEntryObj = (Entry) context.getConfiguration().getLocalRegistry().get(LOCAL_ENTRY_ID);
            if (localEntryObj != null) {
            LOCAL_ENTRY_VALUE = localEntryObj.getValue().toString();
            xmldoc = loadXMLFromString(LOCAL_ENTRY_VALUE);
            BASE_URL= xmldoc.getElementsByTagName("baseurl").item(0).getTextContent();
            HTTP_METHOD = xmldoc.getElementsByTagName("httpmethod").item(0).getTextContent();
            TOKEN_ID = xmldoc.getElementsByTagName("tokenid").item(0).getTextContent();
            TOKEN_SECRET = xmldoc.getElementsByTagName("tokensecret").item(0).getTextContent();
            CONSUMER_KEY = xmldoc.getElementsByTagName("consumerkey").item(0).getTextContent();
            CONSUMER_SECRET = xmldoc.getElementsByTagName("consumersecret").item(0).getTextContent();
            SIGNATURE_METHOD = xmldoc.getElementsByTagName("signaturemethod").item(0).getTextContent();
            OAUTH_VERSION = xmldoc.getElementsByTagName("oauthversion").item(0).getTextContent();
            REALM = xmldoc.getElementsByTagName("realm").item(0).getTextContent();
            }
            else{
            System.out.println("[ERROR] Cannot locate the local entry values...");
            }
           
String data = "";
data = data + "deploy=" + SCRIPT_DEPLOYMENT_ID + "&";
data = data + "oauth_consumer_key=" + CONSUMER_KEY + "&";
data = data + "oauth_nonce=" + OAUTH_NONCE + "&";
data = data + "oauth_signature_method=" + SIGNATURE_METHOD +"&";
data = data + "oauth_timestamp=" + TIME_STAMP + "&";
data = data + "oauth_token=" + TOKEN_ID + "&";
data = data + "oauth_version=" + OAUTH_VERSION + "&";
data = data + "script=" + SCRIPT_ID;
String encodedData = encode(data);
String completeData = HTTP_METHOD + "&" + encode(BASE_URL) + "&"+ encodedData;
String key ="";
key = encode(CONSUMER_SECRET) + "&" + encode(TOKEN_SECRET); 
String signature= computeSignature(completeData,key);
OAuth = "OAuth realm=\"" + REALM + "\",";
OAuth = OAuth + "oauth_consumer_key=\""+ CONSUMER_KEY + "\",";
OAuth = OAuth + "oauth_token=\"" + TOKEN_ID + "\",";
OAuth = OAuth + "oauth_signature_method=\"HMAC-SHA1\",";
OAuth = OAuth + "oauth_timestamp=\"" + TIME_STAMP + "\",";
OAuth = OAuth + "oauth_nonce=\"" + OAUTH_NONCE + "\",";
OAuth = OAuth + "oauth_version=\"" + "1.0" + "\",";
OAuth = OAuth + "oauth_signature=\"" + signature + "\"";
setVariableOAuth(OAuth);
context.setProperty("OAuthVal",OAuth);
return true;
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
public String getVariableOAuth() {
return variableOAuth;
}
public void setVariableOAuth(String variableOAuth) {
this.variableOAuth = variableOAuth;
}
public String getScript_deployment_id() {
return SCRIPT_DEPLOYMENT_ID;
}
public void setScript_deployment_id(String script_deployment_id) {
this.SCRIPT_DEPLOYMENT_ID = script_deployment_id;
}
public String getScript_id() {
return SCRIPT_ID;
}
public void setScript_id(String script_id) {
this.SCRIPT_ID = script_id;
}
/**
    * percentage encoding
    *
    * @return A encoded string
*/
private String encode(String value) {  
    String encoded = "";  
    try {  
      encoded = URLEncoder.encode(value, "UTF-8");  
    } catch (Exception e) {  
      e.printStackTrace();  
    }  
      String sb = "";  
    char focus;  
    for (int i = 0; i < encoded.length(); i++) {  
      focus = encoded.charAt(i);  
      if (focus == '*') {  
        sb += "%2A"; 
      } else if (focus == '+') {  
        sb += "%20";
      } else if (focus == '%' && i + 1 < encoded.length()  
          && encoded.charAt(i + 1) == '7' && encoded.charAt(i + 2) == 'E') {  
        sb += '~';
        i += 2;  
      } else {  
        sb += focus;
      }  
    }  
    return sb.toString();  
  }  
 
/**
    * load xml from string
    *
    * @return A encoded string
*/
public static Document loadXMLFromString(String xml) throws Exception
{
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
    InputSource is = new InputSource(new StringReader(xml));
    return builder.parse(is);
}
}

Class  Base64Encoder.java



package com.wso2.netsuite.oauthentication;

import java.io.UnsupportedEncodingException;
import java.util.Base64;

class Base64Encoder {

    private static Base64Encoder instance;

    public static Base64Encoder getInstance() {
        synchronized (Base64Encoder.class) {
            if (instance == null) {
                instance = new Base64Encoder();
            }
        }
        return instance;
    }
    
    public String encode(byte[] bytes) {
        try {
            return new String(Base64.getEncoder().encode(bytes), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("Can't perform base64 encoding", e);
        }
    }
}


Data Service


First, create the data source in EI (Make sure you add the relevant jar to the lib folder).
In the below service Netsuite_Sync is the name of my data source.


<data enableBatchRequests="true" name="Account_DataService" serviceNamespace="http://ws.wso2.org/dataservice" transports="http https">
   <config enableOData="false" id="default">
      <property name="carbon_datasource_name">Netsuite_Sync</property>
   </config>
   <query id="select_with_key_Account_query" useConfig="default">
      <sql>SELECT idAccount, Name, Amount FROM Account WHERE idAccount=?</sql>
      <result element="AccountCollection" rowName="Account">
         <element column="idAccount" name="idAccount" xsdType="xs:integer"/>
         <element column="Name" name="Name" xsdType="xs:string"/>
         <element column="Amount" name="Amount" xsdType="xs:string"/>
      </result>
      <param name="idAccount" ordinal="1" sqlType="INTEGER"/>
   </query>
   <query id="select_all_Account_query" useConfig="default">
      <sql>SELECT idAccount, Name, Amount FROM Account</sql>
      <result element="AccountCollection" rowName="Account">
         <element column="idAccount" name="idAccount" xsdType="xs:integer"/>
         <element column="Name" name="Name" xsdType="xs:string"/>
         <element column="Amount" name="Amount" xsdType="xs:string"/>
      </result>
   </query>
   <query id="insert_Account_query" useConfig="default">
      <sql>INSERT INTO Account(idAccount,Name,Amount) VALUES(?,?,?)</sql>
      <param name="idAccount" optional="false" ordinal="1" sqlType="STRING"/>
      <param name="Name" ordinal="2" sqlType="STRING"/>
      <param name="Amount" ordinal="3" sqlType="STRING"/>
   </query>
   <query id="update_Account_query" useConfig="default">
      <sql>UPDATE Account SET Name=?,Amount=? WHERE idAccount=?</sql>
      <param name="Name" ordinal="1" sqlType="STRING"/>
      <param name="Amount" ordinal="2" sqlType="STRING"/>
      <param name="idAccount" ordinal="3" sqlType="INTEGER"/>
   </query>
   <query id="delete_Account_query" useConfig="default">
      <sql>DELETE FROM Account WHERE idAccount=?</sql>
      <param name="idAccount" ordinal="1" sqlType="INTEGER"/>
   </query>
   <operation name="select_with_key_Account_operation">
      <call-query href="select_with_key_Account_query">
         <with-param name="idAccount" query-param="idAccount"/>
      </call-query>
   </operation>
   <operation name="select_all_Account_operation">
      <call-query href="select_all_Account_query"/>
   </operation>
   <operation name="update_Account_operation">
      <call-query href="update_Account_query">
         <with-param name="idAccount" query-param="idAccount"/>
         <with-param name="Amount" query-param="Amount"/>
         <with-param name="Name" query-param="Name"/>
      </call-query>
   </operation>
   <operation name="insert_Account_operation">
      <call-query href="insert_Account_query">
         <with-param name="idAccount" query-param="idAccount"/>
         <with-param name="Amount" query-param="Amount"/>
         <with-param name="Name" query-param="Name"/>
      </call-query>
   </operation>
   <operation name="delete_Account_operation">
      <call-query href="delete_Account_query">
         <with-param name="idAccount" query-param="idAccount"/>
      </call-query>
   </operation>

</data>


Comments

  1. Nice post content is impressive to read ,Thanks for proving some helpful information.Hope you will keep on sharing articles.
    This provides good insight. You might also be interested to know more about generating more leads and getting the right intelligence to engage prospects. Techno Data Group implements new lead gen ideas and strategies for generating more leads and targeting the right leads and accounts.

    TECHNO DATA GROUP

    ReplyDelete
  2. It will helpful for
    Cloud ERP solutions & netsuite implementation support companies employees.

    Thanks for sharing the article with us.
    people will learn and get Knowledge.

    Reach us
    netsuite implementation partners



    Few of my articles i fell helpful more :

    Top 5 Benefits of Using NetSuite

    NetSuite Industry Solutions

    NetSuite for small business

    NetSuite for retail industry

    ReplyDelete
  3. there are many advantages to running with a NetSuite San Francisco answer partner, which include:
    information, guide, customization and integration. For more details click NetSuite San Francisco Solution Partner

    ReplyDelete

Post a Comment

Popular posts from this blog

How to fix SoapUI freeze in Mac OS

Salesforce Auto generate renewal Opportunity with Line Items (i.e. Opportunity Products)

Salesforce Create multiple child records based on a number field in the parent using flow