Oh my god. It's full of code!

Salesforce and Angel.com IVR

Salesforce is one of the largest (if not the largest) CRM provider. Angel.com is one of the largest interactive voice response unit providers. It’s likely at some point you are going to want to get a phone system to work with Salesforce, and thankfully it is fairly easy to do. Say for example you wanted to make a simple application that let a person check their Salesforce case status from the phone. It might work something like this…

1) Person calls in
2) IVR prompts person for their case number
3) IVR sends case number to Salesforce
4) Salesforce responds to the IVR with the case status and responsible agent.
5) IVR reads back the information to the person
6) End the call

(I know you’d want to add more features, but we’ll keep it simple for now). The part you are probably wondering about is how steps 3 and 4 work. We’ll lucky you I’m going to take you step by step through on how to do this.

Step 1: Build the Angel.com IVR site
First, your going to need a site which people can call into. Log in and create a new voice site. You’ll want to create 4 pages. First page should be some kind of welcome message that tells the person the service this site will offer. 2nd page will be a question page where they are prompted to enter their case number. 3rd page will be a transaction page that sends the case number to Salesforce, gets a response back and reads the results. The 4th page will be a message page that thanks them and disconnects.
Case Managment site in Angel

A the 2nd and 3rd page are the really important ones The 2nd page asks the person for their case number and stores it in variable. You can call that variable whatever you like, I just called it caseNumber. Salesforce case numbers are 8 digits, so made sure that a person has to enter 8 digits before moving on. Like so..

Get Case Info Page

The setup for page 20

The 3rd page is the transaction page responsible for sending the information to Salesforce. It will send the caseNumber with either the Get or Post method (use Get). I always include at least 2 other variables when I make transaction page calls, that I call nextPage and failPage. NextPage is where the XML should point the IVR to go if everything worked as expected. FailPage is where the IVR should go it the code encounters some kind of problem or unexpected result.This would normally bounce the person to customer support or something. For this example I am also including a retryPage, so if a case is not found the person can enter another case number if they want. Anyway, after that we tell it to expect the results as Angel XML (Angel’s XML data format, go figure).

Transaction Page

The page that sends info from Angel to SF

That is all we have to do on the Angel side for now, lets get the Salesforce part set up. This will consist of few steps.
1) Build the Salesforce site to house the required page.
2) Create the Class that actually gets and formats the data
3) Create the visual force page that will be called by the IVR and gets info from the above class
4) Write testing class and deploy.

So go ahead and log into your Salesforce org. Create a new Salesforce site. I called it IVR. This is my site for all kinds of IVR pages and classes. My setup looks like this.

Salesforce IVR Site

The setup for the Salesforce IVR site. Pretty simple.

Next we need to create a class that will take a case number, get the status and description and return it in some Angel XML. So fire eclipse (or the web based IDE if that floats your boat) and create a new class. I’m terrible at organizing code (never really been on a real dev team so never had anyone teach me) so I’m just going to call it getCaseInfoForAngel.

public class getCaseInfoForAngel 
//The default here is "public class with sharing" but that doesn't work. Remove the "with sharing"
{
    //Create a public variable to store the XML we will return to Angel. 
    public string AngelXML {get;set;}
    
    //Create the method that gets the info
    public void getInfoByCaseNum() 
    {
        //Take any variables passed in GET or POST and make them available in the params structure
        Map<string,string> params = ApexPages.currentPage().getParameters();
        
        //Variable to hold the text that will be read by the IVR to the user
        String promptMessage;
        
        //Variable that holds the mappings of the buttons a user can press to where that button will take them (ex press 1 to re-enter info)
        String xmlLinks = '';
        
        //a list of "structures" (a custom data type that contains name and value pairs) that can returned to Angel.
        //We won't need to pass any variables back to Angel for this example, but we'll just keep there here for future reference.
        List<Struct> angelContacts = new List<Struct>();

        List<Case> records = new List<Case>();
        
        try
        {        
            records = [Select Id, Status, Description
                                  from case
                                  where caseNumber = :params.get('caseNumber')];
               
               //If we found a case number
               if (!records.isEmpty()) 
            {    
                //Set the prompt message to read
                promptMessage = 'Your case status is '+records[0].Status+'. '+records[0].Description+'.';
                xmlLinks += '<LINK dtmf="1" returnValue="1" destination="'+params.get('nextPage')+'" />';
            }
            else
            {
                promptMessage = 'We were unable to locate any information for a case with that number. Press one to enter your case number again or two to disconnect.';
                xmlLinks += '<LINK dtmf="1" returnValue="1" destination="'+params.get('retryPage')+'" />';
                xmlLinks += '<LINK dtmf="2" returnValue="2" destination="'+params.get('nextPage')+'" />';
            }
                
        }
        catch(Exception e)
        {
                promptMessage = 'Sorry but we have encountered and error while getting your case information. Press 1 to enter your case number again or two to disconnect.';
                xmlLinks += '<LINK dtmf="1" returnValue="1" destination="'+params.get('retryPage')+'" />';
                xmlLinks += '<LINK dtmf="2" returnValue="2" destination="'+params.get('nextPage')+'" />';            
        }    
        
        //Create an Angel XML return object              
        createAngelXML A = new createAngelXML(); 
        
        //Set the AngelXML to the results of the call to the createAngelXML method.
        AngelXML = A.createXML('caseInfo',promptMessage,xmlLinks,params.get('failPage'),angelContacts);
    }
    public String getResult() 
    {
        //Return the results
        return AngelXML;    
    }
}

You’ll notice you are missing two things, the struct class, and the createAngelXML class. These are both supporting classes I wrote. Again, they are piss poor, not very flexible but they do the job and are easy to understand. I’m sure a real programmer can make these suck less, but they work for now.

Struct.cls

global class Struct {

    public String name;
    public String value;

    public Struct (String name, String value)
    {
         this.name = name;
         this.value = value;
    }
}

CreateAngelXML.cls

global class createAngelXML 
{
    public String createXML(String variableName, 
                          String textMessage, 
                          String linkMessage, 
                          String failPage,
                          List<Struct> AngelVariables)
    {

        String extraVariables = '';
        
        if(!AngelVariables.isEmpty())
        {
            extraVariables = '<VARIABLES>';
            for (Struct s: AngelVariables) 
            {
                extraVariables += '<VAR name="'+s.name+'" value="'+s.value+'" />';
            }
            extraVariables += '</VARIABLES>';
        }
        String responseText = '<ANGELXML>'+
                '<QUESTION var="'+variableName+'">'+
                    '<PLAY>'+
                        '<PROMPT type="text">'+
                            textMessage+
                        '</PROMPT>'+
                    '</PLAY>'+
                    '<RESPONSE>'+
                        '<KEYWORD>'+
                            linkMessage+
                        '</KEYWORD>'+
                    '</RESPONSE>'+
                    '<ERROR_STRATEGY type="nomatch" reprompt="true">'+
                        '<PROMPT type="text"> Sorry I did not get that. </PROMPT>'+
                        '<PROMPT type="text"> I still did not get that. </PROMPT>'+
                        '<PROMPT type="text"> Since I am having so much trouble; please hold while I transfer you to a customer representative who can better serve you. </PROMPT>'+
                        '<GOTO destination="'+failPage+'" />'+
                    '</ERROR_STRATEGY>'+
                    '<ERROR_STRATEGY type="noinput" reprompt="true">'+
                        '<PROMPT type="text"> Sorry I did not get that. </PROMPT>'+
                        '<PROMPT type="text"> I still did not get that. </PROMPT>'+
                        '<PROMPT type="text"> Since I am having so much trouble; please hold while I transfer you to a customer representative who can better serve you. </PROMPT>'+
                        '<GOTO destination="'+failPage+'" />'+
                    '</ERROR_STRATEGY>'+
                '</QUESTION>    '+  
                    extraVariables+
            '</ANGELXML>';
            
            return responseText;
    
    }
    
}

So with those, your getCaseInfoForAngel class should have everything it needs. It will take a case number, get the details and print them back in Angel’s expected format. Now we just need the visual force page to actually invoke that class. So create a new visualForce page. Call it whatever you want, I’m calling mine getCaseInfo It’s code will look like this.

<apex:page controller="getCaseInfoForAngel"  action="{!getInfoByCaseNum}"
contentType="application/x-JavaScript; charset=utf-8" showHeader="false" standardStylesheets="false" sidebar="false">
{!result}
</apex:page>

Alright, getting close, the hard stuff is done. Now we need to write the testing code, deploy it, adjust the site security, feed angel the new data about our site, and we see if it runs.

I’m not a big fan of testing classes, for me the best test is just to see if my stupid code runs, so I try go get by on as a little work as possible. For this class that means we need to create a case so we can get a case number. Which means we need a contact. Which means we need an account (You can see why test classes bother me, sigh). Anywho, here is some pre packaged testing code. It includes both a success case, and a failure case. Should get pretty close to 75% coverage I think.

       //create a new date object so we can easily insert it
        DateTime dT = System.now();
        Date myDate = date.newinstance(dT.year(), dT.month(), dT.day());

        //make an account so we can attach a contact to it
        Account TestAccount = new Account(name='My Test Account', Tax_ID__c='99-9999999');    
               
        //Now make a contact, so we can attach him as a respondent a few times
        date birthdate = date.parse('03/21/1988');
        Contact ContactGuy1 = new Contact(Firstname='Frank', 
                                            Lastname='Jones', 
                                            AccountID=TestAccount.ID, 
                                            of_PSA__c=0, 
                                            of_No_shows__c =0, 
                                            of_Cancellations__c =0, 
                                            of_Disqualified__c =0, 
                                            of_Participations__c =0, 
                                            MailingCity='coon rapids', 
                                            MailingCountry = 'us', 
                                            MailingState='MN', 
                                            MailingStreet='11021 bittersweet street', 
                                            MailingPostalCode='55433',
                                            phone = '7632344306',
                                            birthdate = birthdate);

        insert ContactGuy1;

        //Try to create the same case twice. Second one should error.
        Case case1 = new case(contact = ContactGuy1, status = 'Open', Origin = 'Web', subject='This is a test case. Eat face');
        insert case1;

        //***************** TEST getCaseInfo Class ********************************
        
        //Working test
        try
        {
            Case testCase = [select CaseNumber from case where id = :case1.id];
            PageReference getCaseInfo = Page.getCaseInfo;
            getCaseInfo.getParameters().put('caseNumber',testCase.CaseNumber);

            Test.setCurrentPageReference(getCaseInfo);
            getCaseInfoForAngel getInfoByCaseNumCtrl = new getCaseInfoForAngel ();
            getInfoByCaseNumCtrl.getInfoByCaseNum();
            myRunResult = getInfoByCaseNumCtrl.getResult(); 
        }
        catch(Exception e)
        {
            
        }
        
        //failure test    
        try
        {
            PageReference getCaseInfo = Page.getCaseInfo;
            getCaseInfo.getParameters().put('caseNumber','000000');

            Test.setCurrentPageReference(getCaseInfo);
            getCaseInfoForAngel getInfoByCaseNumCtrl = new getCaseInfoForAngel ();
            getInfoByCaseNumCtrl.getInfoByCaseNum();
            myRunResult = getInfoByCaseNumCtrl.getResult(); 
        }
        catch(Exception e)
        {
            
        }

So now go ahead and get your deployment ready. Select struct.cls, CreateAngelXML.cls, getCaseInfoForAngel.cls, your testing class, and your getCaseInfo visual force page and push them into prod (if you want, you can test in your sandbox, doesn’t matter really). Hopefully it should all deploy. If not post in the comments and I’ll try to help if I can.

Now we need to include all that stuff in our IVR Salesforce site. So go to your site configs, click public access settings. Modify the profile to have access to case info, and contact info just to be safe (since cases are related to contacts, not having contact info might cause issues, I dunno). Head to the enabled Apex classes section, and include struct.cls, createAngelXML.cls, and getCaseInfoForAngel.cls. Go to the enabled Apex Pages, and include getCaseInfo. If any of these things are not available in the lists, you might have to ajust their individual security settings. For example, my VisualForce page wasn’t available for me to select at first. So go to develop->pages->getCaseInfo->Security. Then I just enable it for all profiles to make life easy. I’m not sure which profile is that list is the one that does it, probably whatever profile your current user account has. EX

Setting VisualForce Page Permissions

f you are not able to select a needed page or class in Public Access Settings area, you may need to adjust the resources security like so

Okay, so now all classes and pages should be enabled for your site. You should now be able to access the getCaseInfo page. It would be something like
http://YourSalesForceSite.force.com/IVR/getCaseInfo
With any luck visitng that page should print out some AngelXML. If you get a Salesforce security message, you probably forgot a setting somewhere. It also shows that message if your code has an error, but my code never errors :P. The sample code I posted did work for me, so I’m pretty sure it does run. If you try it without giving it any case number it should print out the message “We were unable to locate any information for a case with that number”. You can give it a case number by adding a ?caseNumber=xxxxxxxx at the end to see what it does. It should now print our your case details.

So now with our functioning page, we need to hook it up to Angel. Go back to your transaction page in Angel. For the URL you can now enter the URL of your page. You can plug some test values in the boxes and see what happens. You should see it return some happy XML like so.

Happy XML Results!

Huzzah it worked!

And there you have it, you’ve built your first simple Salesforce and Angel.com integrated app. The beginnings of a customer service platform that lives totally in the cloud. Feel free to post code upgrades, questions, comments, whatever. I hope this helped some people out there, as it took me a long time to figure out, and almost as long to write about 😛

3 responses

  1. You are not right. I am assured. I suggest it to discuss.

    August 12, 2010 at 7:38 am

  2. Wow, this post is pleasant, my sister is analyzing these kinds
    of things, therefore I am going to inform her.

    September 17, 2013 at 3:10 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s