Salesforce allows custom programming, including the functionality for communicating with the API that RPM offers.

In this example, we set up a scenario where you have a “Contract” in Salesforce that you want to sync with the “Term” process in RPM and the fields will be synchronized between them.

Required Software

Requirements

This integration does not require special accounts on either RPM or Salesforce. This document assumes you are comfortable coding in Salesforce, have a basic understanding of using a REST API and JSON, and a basic understanding of RPM.

  • Before you start, you should determine what you want to integrate down to the field level. The reason we don’t have a standard body of code you can just use without custom work is that everyone has a slightly different RPM and Salesforce setup. There are custom fields in each system and your Salesforce may also have other custom code or modules that affect the data structure and workflow.

Development

The RPM API will be referred to as the API and Salesforce will be SF.

Prepare RPM

You need to complete the following steps in RPM

  1. Obtain an API key. Go to Setup → API and use an existing key or make a new one.
    If you don’t have access to your RPM subscription then ask someone who does have access for assistance. RPM Software can not give out API keys.

  2. Obtain the URL to call the API.
    Example: https://secure.rpmtelco.com/rpm/api2.svc.

  3. Create the Term Process in RPM
    Use the following template.
  4. The name of each API call is added to that URL.
    For example: ProcForm – these will be covered below

Prepare Salesforce for HTTP requests:

  1. You need to enable the sending of outbound HTTP requests from your SF configuration. Add a “Remote Site” (Setup → Security Controls → Remote Site Setting) with the RPM domain name: Example: http://agentrpm.com)

Scenario

Like we said at the beginning, this example sets up a scenario where you have a “Contract” in SF that you want to sync with the “Term” process in RPM (in RPM we refer to each Term as a form), and the fields will be synchronized between them as follows:

Fields in Contract of SalesforceFields in Term Process of RPM
Account NameCustomer Name (a customer reference field)
Contract OwnerSold By (a custom list field)
End DateContract End Date (a custom date field)
Start DateContract Start Date (a custom date field)

The goal is that when a user looks at a Contract detail in SF, it will be updated with the latest information from Term in RPM, and when a user edits the Contract, it will update the Term in RPM.

When a user creates a Contract, it will create a Term in RPM and then save the Term ID to Contract for future synchronization.

Create the Term Process

Set up Field in process of Term is as shown in the following figure, you can use this template file.TermSetup

Create Classes

Create the following classes that match the naming and structure of the elements in the JSON strings we will use for communication between the RPM API and SF.

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Process {
    public String Process; // form name
    public Integer ProcessID;
    public Form Form;   
    public Process(String p, Integer id)
    {
        Process = p;
        ProcessID=id;
        Form = new Form();
    }
}
 
public class Form {
    public String FormNumber;
    public Integer FormID;
    public String Owner;
    public String Status;
    public String ApprovalResult;
    public Date Started;
    public Date Modified;
    public CustomField[] Fields;
    public String GetField(String Field)
    {
        for (CustomField f : Fields) 
        {
            if (f.Field == Field)
                return f.Value;  
        }
        return '';
    }
}
 
public class CustomField {
    public String Field;
    public String Value;
    public CustomField(String f, String v)
    {
        Field = f;
        Value = v;
    }
}
 
public  class Customer {
    public String CustomerName;
    public Integer CustomerID;
    public Customer(String name, Integer id)
    {
        CustomerName = name;
        CustomerID= id;
    }
}

Sending API Calls

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static String SendAPI ( String url, String body)
{
    string key = “xxxxxxxxxxxxxxxx”;
    // Initialize the http request
    HttpRequest req = new HttpRequest();
    Http http = new Http();
 
    // add the endpoint to the request
    req.setEndpoint(url);   
    req.setHeader('Content-Type','application/json; charset=utf-8');
    req.setMethod('POST');
    req.setBody(‘{\"Key\":\"' + key +  '\",'  + body  + ‘}’);
 
    // create the response object
    HTTPResponse resp = http.send(req);
 
    // service is returning a line feed so parse it out
    json = resp.getBody().replace('\n''');      
    return json;
}

First we instantiate the HTTP request, set the method to POST, and then we attach the JSON string which contains the properties and values that we need to send to the API (more on this below). After the API receives the HTTP request, it responds with a string in JSON format.

Methods

Create a Customer in RPM

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static Integer CreateSimpleCustomer(String name)
{
    // set the method
    // generate the url for the request
    String apiCall = 'CustomerAdd' ;
    String JSONRequest = '\"Customer\":{' +   '\"Name\":\"' + name + + '\"}';
    String json = APIRequest.Send(apiCall, JSONRequest);
    try
    {
        Customer customerResult = GetCustomer(json);
        return customerResult.CustomerID;
    }
    catch (Exception e)
    {
        system.debug('error=' + e);
        return 0;
    }
}
public static Customer GetCustomer(String json)
{
    json = json.substring('{"Result:{"'.Length()-1,json.Length()-1);
    json = json.Replace('Name','CustomerName');
    json = json.substring(0,json.IndexOf('"Aliases"')-1) + '}}';
    system.debug(json);
    try
    {
        Customer customerResult = (Customer)System.Json.deserialize(json,               Customer.CustomerResult.class);
        return customerResult;
    }
    catch (Exception e)
    {
        system.debug('ERROR=' + e);
        return null;
    }
}

We want to have the function in place so we can have RPM create the new customer if it does not yet exist in RPM. The API call is CustomerAdd.

Update Term

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public static void UpdateTerm(Contract curContract, Boolean isCreate)
{
    String apiCall;
    if (isCreate)
    {
        curContract = [select  AccountId, Name,OwnerID,EndDate, StartDate, Status,ContractNumber from Contract where id = :curContract.Id];
        apiCall= 'ProcFormAdd';
    }
    else
    {
        apiCall= 'ProcFormEdit';
    }
 
    // get contract owner info of Contract
    User contractOwner;
    if (curContract.OwnerId!=null)
    {
        contractOwner = [SELECT Name from User where id = :curContract.OwnerId];
    }
 
    // get account info of Contract
    Account account;
    if (curContract.AccountId!=null)
    {
        account = [SELECT Name from Account where id = :curContract.AccountId];
    }
 
    // Check if customer exists in RPM, if not create it
    if (account !=null && account.Name != '')
    {
        String name = account.Name;
        if (Customer.GetCustomerFromName(name) == null)
        {
            account.RPM_Customer_ID__c =  string.valueof(Customer.CreateSimpleCustomer(name));
        }
    }
 
    // Send API call to create/update the Term in RPM
    Process sendFormResult = new Process('Term',0);
 
    sendFormResult.Form = new Form();
    if (isCreate)
    {
        sendFormResult.Form.FormNumber =  (curContract.ContractNumber != null? curContract.ContractNumber:'') ;
    }
    else
    {
        sendFormResult.Form.FormID =  (curContract.RPM_Form_ID__c != null? Integer.valueof(curContract.RPM_Form_ID__c):0) ;
        sendFormResult.Form.FormNumber =  (curContract.RPM_Order_ID_Number__c != null? curContract.RPM_Order_ID_Number__c:'') ;
    }
 
    // Term owner and status
    sendFormResult.Form.Owner =  'Tai Huynh';
    sendFormResult.Form.Status = (curContract.Status != null ?  curContract.Status:'');
 
    // Set custom fields of Term
    sendFormResult.Form.Fields = new CustomField[]{
        new CustomField('Customer Name', (account !=null)? account.Name :''),
        new CustomField('Sold By', (contractOwner.Name !=null)? contractOwner.Name  :''),
        new CustomField('Contract Start Date',  string.valueof(curContract.StartDate) ),
        new CustomField('Contract End Date',   string.valueof(curContract.EndDate))
    };
 
    // add the endpoint to the request
    string  JSONRequest =System.JSON.serialize(sendFormResult);
    JSONRequest = JSONRequest.Replace('FormNumber','Number');
    JSONRequest = JSONRequest.substring(1,JSONRequest.Length()-1);
    String json = APIRequest.Send(apiCall, JSONRequest);
    try
    {
        Form form = GetForm(json);
        curContract.RPM_Form_ID__c = double.valueOf(form.FormID);
        update curContract;
    }
    catch (Exception e)
    {
        system.debug('error=' + e);
    }
}

Get RPM Term info

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static Form GetForm(Contract curContract) {
    // generate the url for the request
    String url = 'ProcForm';
    String body = '\"FormID\":' + curContract.RPM_Form_ID__c;
    String json =  APIRequest.Send(url,body);
    return GetForm(json);
}
public static Form GetForm(String json)
{
    json = json.substring('{"Result:{"'.Length()-1,json.Length()-1);
    json = json.Replace('Number','FormNumber');
    json = json.substring(0,json.IndexOf('"Worksheets"')-1) + '}}';
    system.debug(json);
    try
    {
        Process formResult = (Process)System.Json.deserialize(json, Process.class);
        return formResult.Form;
    }
    catch (Exception e)
    {
        system.debug('ERROR=' + e);
        return null;
    }
}

Update Contract With Term Data

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public static void UpdateContract(Contract contract)
{
  // update the curContract
  Form form = GetForm(contract);
  if (form != null )
  {
    if (form.GetField('Sold By') !='')
    {
      User owner = GetUser(contract.ID,form.GetField('Sold By'));
      if (owner !=null && owner.IsActive)
        contract.OwnerId = owner.Id; // clear
    }
    // update the Contract
    if (form.GetField('Customer Name') != '')
    {
      ID CustomerID = GetAccountID(contract.ID,form.GetField('Customer Name'));
      if (CustomerID !=null) \
      {
        contract.AccountId =CustomerID;
      }
    }
    else
    {
      contract.CustomerSignedId=null;
    }
    if (form.GetField('Contract Start Date') !='' )
    {
      contract.StartDate=date.valueOf(form.GetField('Contract Start Date’'));
    }
    // Form number
    contract.RPM_Order_ID_Number__c = form.FormNumber;
    try
    {
      update contract;
    }
    catch (Exception ex)
    {
      // handle error
    }
  }
}
public static Id GetAccountID(ID parentID,string name)
{
  try
  {
    Account account = [SELECT Name,Id from Account where name = :name];
    return account.Id;
  }
  catch(QueryException e)
  {
    Log.CreateNote(parentID,'Account ' + name + ' not found in SalesForce, update from RPM failed');
    return null;
  }
}

Class extension to extend the page controller

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public with sharing class ContractExtension {
  private final Contract contract;
  private final ApexPages.StandardController theController;
 
  public  ContractExtension(ApexPages.StandardController controller) {
      theController= controller;
     this.contract = (Contract)controller.getRecord();
  }
 
  public void Save()
  {
      update(contract);
  }
 
  public PageReference updateTerm()
  {
    try{
        Form.UpdateTerm(contract,false);
    catch(System.Exception ex){
        return null;
    }
      return theController.view().setRedirect(true);
  }
 
  public PageReference cancel() {
    PageReference contractPage = new ApexPages.StandardController(contract).view();
    contractPage.setRedirect(true);
    return contractPage;
  }
 
  // This method is executed when user is viewing at the contract, it will check if there is link between
  // Salesforce and RPM, if there is then update Salesforce record with data of RPM form, else it will try to // create one in RPM
  public void updateContract() {
    if (contract.RPM_Form_ID__c != null && contract.RPM_Form_ID__c!=0)
    {
        Form.UpdateContract(contract);
    }
    else
    {
      Form.UpdateTerm(contract,true);
    }
  }
}

Extend ContractExtension

Finally, we need to create a page controller to extend the above class so that we can override the page load for the view and edit pages on the contract page.

ContractView.page

Download this code snippet

1
2
3
4
5
6
7
8
9
<apex:page standardController="Contract" extensions="ContractExtension" showHeader="true" tabStyle="contract" action="{!updateContract}">
   <apex:form >
      <apex:inputhidden value="{!contract.RPM_Form_ID__c}" />
      <apex:inputhidden value="{!contract.OwnerId}" />
      <apex:inputhidden value="{!contract.AccountId}" />
      <apex:inputhidden value="{!contract.ContractNumber}" />
     <apex:detail relatedList="true" title="true"/>
    </apex:form>
</apex:page>

This page inherits from the default SF “Contract” controller and we extend it with our new ContractExtension. On load, it will call the method “updateContract” to get the form information from RPM and update the SF record.

ContractEdit.page

Similar to ContractView and we declare fields that we want to edit.

Download this code snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<apex:page standardController="Contract" extensions="ContractExtension" sidebar="true">
    <apex:sectionHeader title="Contract Edit" subtitle="{!contract.name}"/>
    <apex:form >
        <apex:pageBlock title="Contract Edit" id="thePageBlock" mode="edit">
            <apex:pageMessages />
            <apex:pageBlockButtons >
                <apex:actionFunction name="updateTerm" action="{!updateTerm}"></apex:actionFunction>
                <apex:commandButton value="Save" action="{!save}" oncomplete="executeWS()"/>
                <apex:commandButton value="Cancel" action="{!cancel}"/>
                <apex:inputhidden value="{!Contract.ContractNumber}" />
                <apex:inputhidden value="{!Contract.RPM_Form_ID__c}" />
                <apex:inputhidden value="{!Contract.OwnerId}" />
                <apex:inputhidden value="{!Contract.AccountId}" />
            </apex:pageBlockButtons>
            <apex:actionRegion >
                <apex:pageBlockSection title="Contract Information" columns="2">
                    <apex:outputLabel value="Contract Owner" for="contract__owner"/>
                        <apex:inputField value="{!contract.Owner.Name}" id="contract__owner"/>
                    </apex:pageBlockSectionItem>
                    <apex:pageBlockSectionItem />
                    <apex:pageBlockSectionItem />
                    <apex:pageBlockSectionItem >
                        <apex:outputLabel value="Company Name" for="contract__account__name"/>
                        <apex:inputField value="{!contract.Account.Name}" id="contract__account__name"/>
                    </apex:pageBlockSectionItem>
                    <apex:inputField value="{!contract.StartDate}" />
                    <apex:inputField value="{!contract.EndDate}" />
                    <apex:inputField value="{!contract.ContractTerm}" required="true"/>
                    <apex:inputField value="{!contract.Status}" required="true"/>
                    <apex:inputField value="{!contract.Does_order_replace_an_existing_order__c}" />
                    <apex:inputField value="{!contract.RPM_Form_ID__c}" />
                </apex:pageBlockSection>
                <apex:pageBlockSection title="Description Information" columns="1">
                    <apex:inputField value="{!contract.Description}" style="width: 360px; height: 100px"/>
                </apex:pageBlockSection>
            </apex:actionRegion>
        </apex:pageBlock>
    </apex:form>
</apex:page>

Override the default Salesforce page with the controller

Go to: Setup → Customize → Contracts → Buttons and Links. Set the “Edit” and “View” to Visualforce Pages that we just created above.

Snap7
This concludes the setup, you should be able to test the integration between RPM and Salesforce.