Sunday, November 22, 2009

Sales Force Call Center Adapter

SalesForce Call Center Adapter.
Author :Bijumon Janardhanan.o
Contents
1. Introduction
2. Architecture
3. How it looks in the Application javascript:void(0)
4. DemoAdapter Class
5. ISalesforceCTIAdapter
6. CDemoAdapterBase
7. UIAction
8. SendUIRefreshEvent
9. FinalConstruct the place to initialize custom API
10. CDemoEventSink
11. CDemoUserInterface
12. Handling Call Center Hardware API
13. How to Add a Button in GUI and handling Button Message
14. How to Hide and Display Buttons
15. CWinsetContainer Class
16. Call Ringing Event
17. Search for Callers Accounts and Displaying Caller details in the CTIWindow





Introduction
This is an attempt to document salesforce adapter development for future reference for the maintenance of the newly developed adapter. The documentation available with salesforce can be supplemented by this document. It took me lot of trial and error to explore some features. My task was to integrate aspect Winset control with the salesforce adapter .
Salesforce call center adapter is a bridge integrates salesforce CRM with call center hardware. Callcenter Adapter allows the agents to receive calls from customers through a Callcenter hardware and manage it with the CRM interface. Salesforce ApexAPI helps to pop the account information. For an existing user Apex API brings up the account information, with the use of the information provided by the call center hardware. The information coming from the callcenter hardware may be just the callerID or a user entered account number. Popping up account details helps the agent to effectively handle the customer call.
Salesforce browser application instantiate Salesforce Adapter, which is implemented as a local COM server. Salesforce brower application expects Call center Adapter COM to notify call related events through a COM event. This COM event sends as a piece of XML to the browser interface which is then rendered by the browser to a neat user interface. There are a set of XML tags predefined for this communication. I assume that the browser uses a script to render it. This rendered GUI which appears the left side of the salesforce application. The communication between Salesforce Application and the Call center Adapter (Local COM server ) is bidirectional. Salesforce Browser application call methods in the COM server to pass events back to the COM server.

Architecture

CTI API(Call center API provided by vendor)
CTI Connector
(Call center Adapter)
SalesForce Aplication
Call Center Hardware



Salesforce Provides a sample implementation of the COM server (Which is called as CTI connector) which can be used as a template for building new Adapters . This workspace contain two projects one is named as CTIAdapter LIB and the other one is DemoAdapter. CTIAdapterLIb contains classes implementing basic functionalities. SecondProject contain the COM server implementation. I will start with DemoAdpter project workspace. As I stated above each element in the rendered call center GUI in the salesforce browser application represents a pre defined XML tag. Fig1 shows how the adapter appears in the browser.






How it looks in the Application





Fig 1.
Class Descriptions
DemoAdapter Class
This workspace consists the classes mainly derived from the base classes provided in CTIAdpaterLIB.
ISalesforceCTIAdapter
This is the COM interface implemented by the CTIConnector. It mainly contains one method and two events.
Events
HRESULT UIRefresh(BSTR pXMLUI); //Send to browser to refresh the UI
HRESULT UpdateTrayMenu(BSTR xmlMenu); //Send to update the tray menu. Rarely used.


Method
HRESULT UIAction([in] BSTR message);//This method is called by the browser script to indicate an action in the UI by the user. This includes the action in the adapter UI or in the browser application. Handling these messages originating from browser is the core of the CTI Connector development. Message handling function will be described later in this document.



CDemoAdapterBase
This class inherits from the CCTIAdapter base class and implements the actual COM interface ISalesforceCTIAdapter.
UIAction
This method method called by the browser to indicate events in the callcenter Adapter UI. This includes both Adapter user interface events and salesforce generated events like connect,logoff etc.
An XML fragment is passed to the function containing the event type and other parameters relevant to the action.




The function handling this messages (UIHandleMessage(std::wstring& message, PARAM_MAP& parameters)) are somewhat similar to WindowsMessage handle function. This function is implemented in CCTIUserInterface class which then over loaded in CDemoUserInterface. If you want to handle some message specially it is recommended to implement that in the CDemoUserInterface class.

SendUIRefreshEvent
This function generates the COM event to notify the browser to change the UI. This function is actually called by the main class (CDemoUserInterface) when there is a UI change is needed based on the events received from the underlying call center hardware or the driver . It send an XML fragment containing the elements to be displayed in the UI. Below given is a sample XML send by the adapter.


-
-


-
-


-
-














-









FinalConstruct the place to initialize custom API
This function is called by ATL when the COM server is instantiated. So this is the best place to do some startup related works. By default it is where the main worker class CDEmoUserInterface is created. In my work I used this function to create my hidden window which holds the activeX control for communicating with the Aspect Hardware.

m_pWinSetCon = new CWinsetContainer();
m_pWinSetCon->Create(NULL);
//m_pWinSetCon->ShowWindow(SW_SHOWNORMAL);
m_pWinSetCon->m_pDemoAdapterBase = this;
m_pWinSetCon->m_pUserInterface = (void *) m_pUI;
m_pUI->m_pWinsetContainer = m_pWinSetCon;
FinalRelease
This function is called when the COM server is destroyed.


CDemoEventSink:
Its usage is described in salesforce documentation. Other than the events alredy defined this class if of not that use for a developer.
CDemoUserInterface
This is the main worker class derived from CCTIUserInterface where we can embed our logic. We have to implement our specific functionalities in this class. But I broke the rule and sometimes I made my changes in the base class itself. I was mainly due to my time shortage and in the beginning I didn’t get a clear picture of the class structure.
Handling Call Center Hardware API
The purpose of CTIAdapter is to interface with call center hardware and provide a common platform for interfacing with salesforce web application. It is callcenter adapter developers responsibility to modify the demo implementation to encapsulate the API provided by the call center hardware vendor. The custom API provided by the harware vendor normally provide a set of methods and events. Vendors may be implemented the API as OCX or dlls. In case of OCX they may provide events for notifying call related events. If it is a DLL then we may have to depend on call back functions to receive events.
It is recommended implementing a wrapper calss to handle events and method. In my implementation I encapsulate the OCX provided by aspect in to class where I received events and wrote the methods to initialize. A wrapper class will allow you to handle state variables needed to mange API state.
In my implementation I used to call CDemoAdpter function directly to notify events received from the call center. In order to make voice calls and to log on to call center hardware I used wrapper class function.
Wrapper class can also process the data received from the call center hardware to match with what is expected by the CDemoUserInterface class. This way we can insulate the changes required for switching between different call center providers. In the below section I am explaining some details of this wrapper class implementation.

UIAction Handler in CDemoUserInterface



Above given chart shows the call stack of UIHandleMessage in CCTIUserInterface. UIParseIncomingMessage function parses the XML parametaers and places in the param collection and then passes the same to UIHandleMessage function.

UIHandle contains a big if then chain where it calls functions to process each message.
This is the function where you can start the debugging. To see the list of messages supported you can open the CTIConstants.H file.
If you want to see the messages send to the CTIAdapter place a break point in this function. That is the best way to determine the messages we want to. Below given table shows a partial list of messages.

KEY_ACCEPT_CONFERENCE Message send when user press accept button in the UI. If the Conference button is shown by the developer than it must be assigned to the button
KEY_ACCEPT_TRANSFER Message sends when user press accept transfer
KEY_CONNECT Sends to COM just after browser establishing the connection with the COM. At this point CTIAdpter COM can create call center API instance.
KEY_LOGIN Sends to COM on pressing login button. Parameter array contains the user entered login name and password. CTIAdpter can then send it to the Call center hardware through the API provided.
KEY_RELEASE When user presses end call this message is send to the Adapter
KEY_ANSWER Sends when user press answer button. CTI Adapter can now call appropriate API function to accept the cll.
KEY_HOLD Sends when user presses the HOL button
KEY_RETRIEVE Sends when user press retrive button to release a call from hold.


How to Add a Button in GUI
It is possible to add custom messages to handle custom UI elements.
In this implementation We have added a new button to verify the caller account. Below shown is how to add a button and handle button press
void CCTILine::AddDefaultButtons()
{
……………………………..
…………………………….
pButton = AddLongButton(BUTTON_ACCOUNTVERIFIED,KEY_ACCOUNTVERIFIED,COLOR_BEIGE,L"Account Verified");//L"/img/btn_conference.gif", L"Account Verified");
if(pButton)pButton->SetIconURL(L"/img/btn_conference.gif");

……………………
}
void CCTIUserInterface::UIHandleMessage(std::wstring& message, PARAM_MAP& parameters)
{
………………………………….
……………………………………..

else if (message==KEY_ACCOUNTVERIFIED )

{


AccoutVerified(parameters);

}
else if (message==KEY_COMPLETE_TRANSFER) {
//CallCompleteTransfer(parameters);
//BMJO trying above function to be called in the calsate event
//function in the derive class will handle this mesg
CallCompleteTransfer(parameters);

}


………
}

How to Hide and Display Buttons
To show buttons add it in listEnabledButtons. Those buttons whose ID is not listed in this list may not be shown.
if(Line!=1)
listEnabledButtons.push_back(BUTTON_OUTSIDELINE1);
if(Line!=2)
listEnabledButtons.push_back(BUTTON_OUTSIDELINE2);
listEnabledButtons.push_back(BUTTON_INSIDELINE);
listEnabledButtons.push_back(BUTTON_SUPERVISOR);
OnButtonEnablementChange(Line,listEnabledButtons,false);
UIRefresh();


CWinsetContainer Class
This class is specific to this implementation which I used to wrap the active control provided by Aspect to interface with call center hadware. This class implements a hidden window to receive the events from the Aspect WinsetControl.
Winset Control is an active X component provided by Aspect to communicate with call center hardware. This control allows to distribute calls landed in the aspect system to the agents PCs. Once you logged on to Aspect with the use of aspect winset control aspect system can reditrect calls to the PC based on the rules set in the Aspect system. I used a hidden window to host this control and to receive events from it.

This class keeps a pointer of CDemoUserInterface class . When ever it receives an event from the ASPECT hardware it calls the function in the CDemoUserInterface class to take appropriate functions. For example when there is a an incoming call it receives an event from the aspect hardware with CUTTHROUGH data(information passed by the call center hardware along with the call).After formatting the CUTTHOUGH data it fills that in parameter map and calls the CDemoUseInterafce functions to further handle the event. Function prototype used to handle this event is given below.


Call Ringing Event










Call tree to handle Ring Event



LRESULT ProcessCallRingEventFromWinset(BSTR CallInfo)
{
std::wstring strCallInfo = CCTIUtils::BSTRToString(CallInfo);


CDemoUserInterface *pUI = (CDemoUserInterface *)m_pUserInterface;

pUI->m_sCallTrack = GenerateCallTrackID(strCallInfo);
pUI->m_mapCustomAspectData[KEY_CALLTRK]=pUI->m_sCallTrack;
pUI->m_sAccNo = GenerateAccountNum(strCallInfo);
pUI->m_mapCustomAspectData[KEY_ACCNO]=pUI->m_sAccNo;
pUI->m_mapCustomAspectData[KEY_ZIP]=GenerateZip(strCallInfo);
pUI->m_mapCustomAspectData[KEY_CCT]=GenerateCCT(strCallInfo);
pUI->m_mapCustomAspectData[KEY_LANG]=GenerateLang(strCallInfo);
pUI->m_mapCustomAspectData[KEY_SEG]=GenerateCustSeg(strCallInfo);
pUI->m_mapCustomAspectData[KEY_DNIS]=GenerateDNIS(strCallInfo);
pUI->m_mapCustomAspectData[KEY_CALLTYPE]=GenerateCallType(strCallInfo);
pUI->m_mapCustomAspectData[L"ANINUM"]=m_sANI;
pUI->m_mapCustomAspectData[KEY_SITEID]=GenerateSiteID(strCallInfo);



PARAM_MAP mapInfoFields;



PARAM_MAP mapAttachedData;
//This field is used by the app-exchange class to prepare query
mapAttachedData[L"account.ICOMS_Account_Number__c"]=pUI->m_sAccNo ;

std::wstring sCallObjectId = pUI->CreateCallObjectId();

if(m_SearchType==0)
{
pUI->SetInfoFieldLabel(KEY_ACCNO,L"Account Number");
pUI->m_mapCustomAspectData[KEY_SEARCHTYPE]=L"AC";
mapInfoFields[KEY_ANI]=pUI->m_sCallTrack ;
}
else
{
pUI->SetInfoFieldLabel(KEY_ACCNO,L"ANI");
pUI->m_mapCustomAspectData[KEY_SEARCHTYPE]=L"AN";
mapInfoFields[KEY_ANI]=m_sANI;
}


mapInfoFields[KEY_CALLTRK]=pUI->m_mapCustomAspectData[KEY_CALLTRK];
mapInfoFields[KEY_ACCNO]=pUI->m_mapCustomAspectData[KEY_ACCNO];
mapInfoFields[KEY_ZIP]=pUI->m_mapCustomAspectData[KEY_ZIP];
mapInfoFields[KEY_CCT]=pUI->m_mapCustomAspectData[KEY_CCT];

mapInfoFields[KEY_LANG] = pUI->m_mapCustomAspectData[KEY_LANG];
mapInfoFields[KEY_DNIS]=pUI->m_mapCustomAspectData[KEY_DNIS];
mapInfoFields[KEY_SEG]=pUI->m_mapCustomAspectData[KEY_SEG];
mapInfoFields[KEY_CALLTYPE]=pUI->m_mapCustomAspectData[KEY_CALLTYPE];



int nLine = pUI->OnCallRinging(sCallObjectId,CALLTYPE_INBOUND,true,true,mapInfoFields,mapAttachedData);
//RCN requested to remove answer button. So calling
//answer directly. According to design I am expecting incoming call
//in line 1 only. It is not defined what will happen if a call comes in
//line 2.
pUI->OnAgentStateChange(std::wstring(AGENTSTATE_BUSY));
std::list listButtonsEnabled;


listButtonsEnabled.push_back(BUTTON_RELEASE);

pUI->OnButtonEnablementChange(nLine,listButtonsEnabled,false);
pUI->m_nWinsetLineToUse=0;//reseting it just in case
m_nDialOut=0;//reseting
return 0;
}


Similarly it calls end call and other related function appropriately to notify the call related status.

Search for Callers Accounts and Displaying Caller details in the CTIWindow
This is an important area where developers want to make changes. It is very important to show the details of the caller to the agent. Depending on the callcenter hardware and IVR prompts it may collect different information’s form the user. This can be used to search in the salesforce database to find out the user accounts and to display needed information in the CTI window. The default implementation contains a search based on ANI(caller ID).
In the frame work there is a special class and function to handle this search that is CCTIAppExchangeSearchThread::ThreadSearch()
After completing the search this function posts a message to the hidden window created in that class. In my implementation I used end of search to send Answer event. It was client requirement to auto answer with out user pressing the Answer buttion. I have no other event to depend on calling answer method.

LRESULT CALLBACK CCTIAppExchangeSearchThread::HiddenWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)

CCTIUserInterface calss calls search function on CallRinging function through AppExchange class. AppExchnage class is where APEX API COM interfaces are instatiated and used.

APEX API is the interface provided by sales force to access the salesforce internal data. Salesforce provides different methods to access these data. CTIDemoAdpter implementation uses the COM version of the Apex API
IQueryResultSet4Ptr

Conclusion
Though I want to write a comprehensive guide on CTIAdapter my time does not permit it. Also the technology is almost expired and there wont be much work based on this adapter I hope. The way I organized the contents is the way I remember it. My intention is just to record what ever I can so that it will be helpful to those who struggle with it. Also I hope this document will help me in future if there is some maintenance required in my own work. I am happy to help somebody who struggle with CTIAdapter development. Feel free to contact me at bmjo@ebirdonline.com