Contents
In order to make the company-client interactions more effective and safe, companies often assign unique customer ID code to each client. This customer identification usually consists of a user ID (like a username) and a password (or in other words: PIN code). This way customer affairs can be identified and followed. Companies can ask this customer ID during personal administration or on their webpages in order to let the clients enter into their specific page and reach their own data and settings.
However, customer identification can be also possible through a phone call without any human intervention. The only one thing you need is an effective IVR menu system. (IVR is the acronym for Interactive Voice Response that is a technology allowing a PC to interact with humans through the use of voice and DTMF tones input via keypad.) It can be an effective part of any CRM (Customer Relationship Management) system. The following picture illustrates how my solution works. First your customer (in other words: the caller) dials your telephone number. The call will be accepted automatically by your IVR system. You can specify a greeting message that will be played without automatically. After this your customer (that is the caller) needs to enter his/her ID (a PIN code). After listening to the selectable menu items, your customer can access his/her specific data and settings.
The whole project is based on an IVR menu system. In this project my IVR has been written in C# by using XML code. XML coding facilitates the designing of the menu structure as it makes the system of menus and submenus more clear. I am going to present a very simple example on how to build a general telephone client gate system, but after some modifications, this project can be used to create a complete bank or telephone balance inquiry or any other system that needs user authentication.
For implementing the VoIP IVR functionality I have used Ozeki VoIP SIP SDK along with Microsoft Visual Studio. (Please note that for the Visual Studio at least .NET Framework 3.5 SP1 is needed or any newer version of it.) The default behaviour of the IVR has been implemented in C#, but I also used XML to design the menu structure.
A simple softphone application is also essentially needed in order to be able to accept the customers’ incoming calls. Concerning to the fact that my article focuses on the DTMF authentication and the implementation of the telephone client gate system, I would not like to describe the implementation of the softphone development. So my article assumes that you have a softphone. If not, you can build your own softphone easily by following the instructions of the video guide below:
In my project 7 classes have been used: Softphone.cs
, ICommand.cs
, Program.cs
, Menu.cs
, MultipleCommandHandler.cs
, SpeakCommand.cs
and PlayCommand.cs
.
For better understanding I have divided my project into two main parts:
- Start by implementing an IVR in C# using XML code
- Implementing DTMF authentication in C# using HTTP and PHP
Creating the Softphone.cs class
As a first step, you need to create the Softphone.cs
class, since a simple softphone application is essentially needed in order to be able to accept the customers’ incoming calls. To complete this step, please check the previously mentioned video tutorial. (If you have an existing softphone application, you can use that, of course.)
Creating the ICommand.cs class
Due to the ICommand
interface, the system will be able to handle all of the possible commands (text-to-speech, new menu level, etc.) that may be occured during the usage of the IVR menu system. As you can see on the following code snippet, this interface implements the Start()
and Cancel()
methods (they can start and cancel a command) and the Completed
event as well (to be able to check whether the commands are completed or not).
interface ICommand
{
void Start(ICall call);
void Cancel();
event EventHandler Completed;
}
Code example 1: Creating the ICommand.cs class
Creating the Program.cs class
The Program.cs
class is responsible for getting the SIP account details from the user to create a softphone, managing the incoming calls and processing all components of the XML code.
As it can be seen below, in order to implement the Program.cs
class, first you need to call the ShowHelp()
method within the Main()
method to provide a brief introduction related to the operation of the progrem for the user. To let the application get the necessary SIP account details from the user for creating tha softphone and the phone line, you need to create a softphone object from the Softphone class, then call the sipAccountInitialization()
method using the softphone parameter. Now you need to subscribe to the IncomingCall
event of the softphone to check whether the system has an incoming call.
static void Main(string[] args)
{
ShowHelp();
Softphone softphone = new Softphone();
sipAccountInitialization(softphone);
softphone.IncomigCall += softphone_IncomigCall;
Console.ReadLine();
}
Code example 2: The Main() method of the Program.cs
When an incoming event occurs, the softphone_IncomingCall()
method will be called (Code example 3). The following code snippet shows that you need to create a menu variable that will be equal to the return value of the Read_XML()
method.
- If it is not null, the
Start()
method of this menu will be called
- If it is null, the incoming call will be rejected
static void softphone_IncomigCall(object sender, Ozeki.VoIP.VoIPEventArgs<Ozeki.VoIP.IPhoneCall> e)
{
var menu = ReadXML();
if (menu != null)
menu.Start(e.Item);
else
e.Item.Reject();
}
Code example 3: The softphone_IncomingCall() method
Code example 4 demonstrates the ReadXML()
method that is a menu type method. It contains the XML code of the IVR. The ReadXML()
method starts with a menu tag so it will be the main menu. When the system accepts the call, a greeting message will be played for the caller (it can be seen below in the <init>
section). The following two commands will be used:
Speak
command: It allows the IVR to read out loud an optional text message (text-to-speech).
Play
command: It enables that the IVR will be able to play a prerecorded mp3 message.
The code snippet inserted below illustrates a simple menu example. If the caller presses 1, he/she will hear a short company information and a prerecorded mp3 file. If he/she presses 2, the system will inform the caller that the button 2 has been pressed.
Now let’s create a new Menu
object and call the MenuLevel()
method using the ivrXML
parameter that contains the xml code in string format, then return with the menu.
private static Menu ReadXML()
{
string ivrXml = @"<ivr>
<menu>
<init>
<speak>
Welcome to our Interactive Voice Menu System.
To get more information about our company and hear a sample mp3 song, please press button one.
By pressing button two, you can listen an inform message
</speak>
<play>../../test.mp3</play>
</init>
<keys>
<key pressed='1'>
<speak>
Our company is a well-known corporation. Etc.
</speak>
<play>../../test.mp3</play>
</key>
<key pressed='2'>
<speak>
You pressed button two. You did nothing.
</speak>
</key>
</keys>
</menu>
</ivr>";
var menu = new Menu();
MenuLevel(ivrXml, menu);
return menu;
}
Code example 4: The ReadXML() method
Code exampe 5 illustrates the implementation of the MenuLevel()
method. This method has two parameters:
- String type
ivrXML
(it contains the current menu section of the XML code)
- Menu type menu object
As it can be seen below, the whole process is in a try-catch block – if the system catches an unexpected exception, an error message will appear on the screen.
First of all you need to use the Parse()
method with the ivrXml
parameter to load an XElement
from your XML string. Thereafter select the menu element of the code. If the menuElement
variable is null, it means that there is not any menu tag in the XML code. In this case the XML code is inadequate, stop the running. If there was not any problem, select the init element of the menuElement. It is needed to get all the commands (speak, play) used in the init section, so you need to iterate them with foreach statement. In case of each command you need to call the AddInitCommand()
method of the menu class. To add the command to the command list use the SpeakCommand() parameter if the current command is speak; and use the PlayCommand()
parameter if the current command is play
.
After you have got all of the commands, you need to select the keys element of the menu. It contains the keys that can be pressed by the caller. The XML code is invalid if there is not any pressedKeyAttribute
. After this get the pressed key, store it in the pressedKey
integer variable, and add all the commands that belong to the current key (Code example 5).
private static void MenuLevel(string ivrXml, Menu menu)
{
try
{
var xelement = XElement.Parse(ivrXml);
var menuElement = xelement.Element("menu");
if (menuElement == null)
{
Console.WriteLine("Wrong XML code!");
return;
}
var menuInit = menuElement.Element("init");
foreach (var initElements in menuInit.Elements())
{
switch (initElements.Name.ToString())
{
case "play":
menu.AddInitCommand(new PlayCommand(initElements.Value.ToString()));
break;
case "speak":
menu.AddInitCommand(new SpeakCommand(initElements.Value.ToString()));
break;
}
}
var menuKeys = menuElement.Element("keys");
foreach (var key in menuKeys.Elements("key"))
{
var pressedKeyAttribute = key.Attribute("pressed");
if (pressedKeyAttribute == null)
{
Console.WriteLine("Invalid ivr xml, keypress has no value!");
return;
}
int pressedKey;
if (!Int32.TryParse(pressedKeyAttribute.Value, out pressedKey))
{
Console.WriteLine("You did not add any number!");
}
foreach (var element in key.Elements())
{
switch (element.Name.ToString())
{
case "play":
menu.AddKeypressCommand(pressedKey, new PlayCommand(element.Value.ToString()));
break;
case "speak":
menu.AddKeypressCommand(pressedKey, new SpeakCommand(element.Value.ToString()));
break;
default:
return;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Invalid ivr xml");
}
}
Code example 5: The MenuLevel() method
Creating the Menu.cs class
The Menu.cs
class is responsible for managing the menu sections (including the main menu and the sub-menu sections as well) of the IVR system. Therefore this class will manage the init
and keys
parts of the code.
First of all you need to implement all methods and events of the ICommand
interface and create the following objects (Code example 6):
Dictionary<int, MultipleCommandHandler> keys; MultipleCommandHandler init; ICall call; Timer greetingMessageTimer; MultipleCommandHandler handler;
Code example 6: Creating some objects for the implementation of the Menu.cs class
Code example 7 shows the consructor of the Main.cs
class. After creating a Menu
object, you need to set the keys
, init
and greetingMessageTimer
instances, then set the greetingMessageTimer
to auto reset. This way, the greeting message will be repreated automatically.
public Menu()
{
keys = new Dictionary<int, MultipleCommandHandler>();
init = new MultipleCommandHandler();
greetingMessageTimer = new Timer();
greetingMessageTimer.AutoReset = true;
}
Code example 7: The constructor of the Main.cs class
After starting the menu, you need to set the local call object with the parameter, subscribe to the necessary events (CallStateChanged
and DtmfReceived
events of the call; Elapsed
event of the greetingMessageTimer
). As it can be seen in Code example 8, you need to set the Interval of the greetingMessageTimer
, accept the call, start the greetingMessageTimer
and call the Start()
method of the init
in order to start the included commands.
public void Start(ICall call)
{
this.call = call;
Onsubscribe();
greetingMessageTimer.Interval = 20000;
call.Accept();
greetingMessageTimer.Start();
init.Start(call);
}
Code example 8: The Start() method of the Main.cs class
The call_DtmfReceived()
method is used to listen to the DTMF signals that informs you about the button pressed by the caller. This way you can find out which menu has been selected by the caller. When he caller pressed a button, it should be checked whether there is a command list with the key or not. If it is true, you need to unsubscribe from all events, and it is necessary to subscribe to the completed event of the handler and call the Start()
method of it with the call parameter (Code example 9).
void call_DtmfReceived(object sender, VoIPEventArgs<DtmfInfo> e)
{
if (keys.TryGetValue(e.Item.Signal.Signal, out handler))
{
Unsubscribe();
handler.Completed += handler_Completed;
handler.Start(call);
}
}
Code example 9: The call_DtmfReceived() method
Code example 10 demonstrates the following three methods:
Unsubscribe()
: It is used to cancel the init
and stop the greetingMessageTimer
, because there are commands for the pressed key. If the handler is not null, its Cancel()
method should be called and you need to unsubscribe from all events.
Onsubscribe()
: It is used to subscribe back to all events and start the greetingMessageTimer
.
handler_Completed()
: When the current handle is completed, call the Onsubscribe
method and start the greeting message of the menu.
public void Unsubscribe()
{
init.Cancel();
greetingMessageTimer.Stop();
if (handler != null)
handler.Cancel();
call.CallStateChanged -= call_CallStateChanged;
call.DtmfReceived -= call_DtmfReceived;
greetingMessageTimer.Elapsed -= greetingMessageTimer_Elapsed;
}
private void Onsubscribe()
{
Unsubscribe();
call.CallStateChanged += call_CallStateChanged;
call.DtmfReceived += call_DtmfReceived;
greetingMessageTimer.Elapsed += greetingMessageTimer_Elapsed;
greetingMessageTimer.Start();
}
void handler_Completed(object sender, EventArgs e)
{
Onsubscribe();
init.Start(call);
}
Code example 10: The Onsubscribe(), Unsubscribe() and handler_Completed() methods
In Code axample 11 you can see those methods that can be used to pass the commands from the Program.cs
to the MultipleCommandHandler.cs
(its implementation is described after the following code snippet).
public void AddInitCommand(ICommand command)
{
init.AddCommand(command);
}
public void AddKeypressCommand(int digit, ICommand command)
{
if (!keys.ContainsKey(digit))
keys[digit] = new MultipleCommandHandler();
keys[digit].AddCommand(command);
}
Code example 11: The AddInitCommand and AddKeyPressCommand
Creating the MultipleCommandHandler.cs class
If there are more than one commands belonging to the key pressed by the caller, the IVR system needs to start all commands with the same key. In this case the MultipleCommandHandler.cs
class can be used.
First of all you need to create the following objects. Thereafter you need to set the commandList
instance in the constructor of the class (Code example 12).
Queue<ICommand> commandQueue; List<ICommand> commandList; ICall call; ICommand currentCommand;
public MultipleCommandHandler()
{
commandList = new List<ICommand>();
}
Code example 12: The necessary objects and the constructor of the MultipleCommandHandler.cs
As you can see below, if you call the Start()
method, you need to set the call using the call parameter, set the commandQueue
object using the ICommand
elements, and it is also needed to call the StartNextCommand()
method.
public void Start(ICall call)
{
this.call = call;
commandQueue = new Queue<ICommand>(commandList);
StartNextCommand();
}
Code example 13: The Start() method
Code example 14 illustrates the StartNextCommand()
method that is used to check the commandQueue
. If the count is bigger than 0, the program gets the first command from the commandQueue
, subscribes to the completed event of the currentCommand
, cancel it and finally calls the Start()
method using the call parameter.
In the OnCompleted
method you need to invoke the Completed
event, and then start the next command – if the current command has finished – in the currentCommand_Complete
method.
void StartNextCommand()
{
if (commandQueue.Count > 0)
{
currentCommand = commandQueue.Dequeue();
currentCommand.Completed += currentCommand_Complete;
currentCommand.Cancel();
currentCommand.Start(call);
}
else
OnCompleted();
}
private void OnCompleted()
{
var Handler = Completed;
if (Handler != null)
Handler(this, EventArgs.Empty);
}
void currentCommand_Complete(object sender, EventArgs e)
{
StartNextCommand();
}
Code example 14: The StartNextCommand() method
By calling the Cancel()
method of the MultipleCommandHandler.cs
class, you can unsubscribe from the completed event of the currentCommand
and cancel that.
public void Cancel()
{
if (currentCommand != null)
{
currentCommand.Completed -= currentCommand_Complete;
currentCommand.Cancel();
}
}
Code example 15: The Cancel() method
Creating the SpeakCommand.cs class
The speak
command (that is one of the two specific command mentioned above) allows the IVR to read out loud a preferred text message (text-to-speech). Your IVR system will make this functionality possible by using the SpeakCommand.cs
class.
To be able to manage the speak command you need to implement the following objects. Thereafter you need to create an instance in the constructor by setting the local text variable using the text parameter (Code example 16).
ICall call;
PhoneCallAudioSender phoneCallAudioSender;
AudioHandler audioHandler;
MediaConnector mediaConnector;
string text;
bool isStarted;
public SpeakCommand(string text)
{
this.text = text;
}
Code example 16: The necessary objects and the constructor of the SpeakCommand.cs
As you can see below, in the Start()
method of this class, you need to set the objects and call the TextToSpeech()
method using the text to be able to convert the text into speech.
public void Start(Ozeki.VoIP.ICall call)
{
isStarted = true;
this.call = call;
phoneCallAudioSender = new PhoneCallAudioSender();
phoneCallAudioSender.AttachToCall(call);
mediaConnector = new MediaConnector();
TextToSpeech(text);
}
Code example 17: The Start() method of the SpeakCommand.cs class
The TextToSpeech()
method can be used to convert the provided text into speech. For this purpose, you need to create a TextToSpeech
pbject, then set the audioHandler
to be equal to this object. Thereafter you need to subscribe to the Stopped
event of the tts, connect the audioHandler
to the PhoneCallAudioHandler
and it is also needed to call the AddAndStartText
of the tts using text parameter (Code example 18).
private void TextToSpeech(string text)
{
var tts = new TextToSpeech();
audioHandler = tts;
tts.Stopped += tts_Stopped;
mediaConnector.Connect(audioHandler, phoneCallAudioSender);
tts.AddAndStartText(text);
}
Code example 18: The TextToSpeech() method of the SpeakCommand.cs class
As it can be seen in Code example 19, in the tts_Stopped()
method, you need to invoke the Completed
event of the SpeakCommand.cs
class to be able to find out when the command has stopped. To cancel the current Speak
command you need to call the Cancel
method of this class. After this, you need to check whether the audioHandler
is null or not. If that is not null, you need to disconnect the audioHandler
from the phoneCallAudioHandler
, and dispose the audioHandler
by using the Dispose()
method.
void tts_Stopped(object sender, EventArgs e)
{
if (!isStarted)
return;
isStarted = false;
var handler = Completed;
if (handler != null)
handler(this, EventArgs.Empty);
}
public void Cancel()
{
if (audioHandler != null)
{
mediaConnector.Disconnect(audioHandler, phoneCallAudioSender);
audioHandler.Dispose();
}
}
Code example 19: The tts_Stopped() and the Cancel() methods
Creating the PlayCommand.cs class
The play
command enables that the IVR will be able to play a prerecorded mp3 message. Your IVR system will make this functionality possible by using the PlayCommand.cs
class.
To be able to manage the play command you need to create an MP3ToSpeech()
method. For this purpose, you need to create an MP3StreamPlayback
object using the path parameter that defines the path of the file you want to be played. Thereafter it is also needed to set the audioHandler
to be equal to ths object. After this subscribe to the Stopped
event of the tts, connect the audioHandler
to the PhoneCallAudioHandler
and finally call the AddAndStartText
of the tts using the text parameter (Code example 20).
private void MP3ToSpeaker(string path)
{
DisposeMediaConnection();
mediaConnector = new MediaConnector();
var mp3Player = new MP3StreamPlayback(path);
audioHandler = mp3Player;
mp3Player.Stopped += mp3Player_Stopped;
mediaConnector.Connect(audioHandler, phoneCallAudioSender);
mp3Player.StartStreaming();
}
Code example 20: The MP3ToSpeaker() method of the PlayCommand.cs class
Now you have a one-level IVR menu system. But for DTMF authentication it is essentially needed to have a multi-level IVR (Code example 21):
<ivr>
<menu>
<init>
<speak>
Welcome to our Interactive Voice Menu System.
To get more information about our company and hear a sample mp3 song, please press button one.
By pressing button two, you can listen an inform message
</speak>
<play>../../test.mp3</play>
</init>
<keys>
<key pressed="1">
<speak>
Our company is a well-known corporation. Etc.
</speak>
<play>../../test.mp3</play>
</key>
<key pressed="2">
<speak>
You pressed button two. You did nothing.
</speak>
</key>
<key pressed="3">
<menu>
<init>
<speak>You reached the lower menu.</speak>
</init>
<keys>
<key pressed="1">
<speak>
You pressed button one at the lower menu level.
</speak>
</key>
</keys>
</menu>
</key>
</keys>
</menu>
</ivr>
Code example 21: Multi-level IVR
And finally you need to make some modifications in the MenuLevel()
method – in that part where you check which command belongs to the pressed key (since now the command can be a new menu as well). So let’s create a new Menu
object and add the menu command to the command list with the AddKeypressCommand
). Call recursively this MenuLevel()
method using the innerMenu
object and the section of the XML code belonging to the pressed key (Code example 22).
foreach (var element in key.Elements())
{
switch (element.Name.ToString())
{
case "play":
menu.AddKeypressCommand(pressedKey, new PlayCommand(element.Value.ToString()));
break;
case "speak":
menu.AddKeypressCommand(pressedKey, new SpeakCommand(element.Value.ToString()));
break;
case "menu":
Menu innerMenu = new Menu();
menu.AddKeypressCommand(pressedKey, innerMenu);
MenuLevel(key.ToString(), innerMenu);
break;
default:
return;
}
}
Code example 22: Modifications in the MenuLevel() method
Congratulations for your multi-level IVR system! Due to the XML coding, it is not needed to modify the source code if you would like to add some new menu points. It can be easily carried out by writing some new menu levels into the XML code.
Now you are ready to implement the DTMF authentication!
In this project I used HTTP requests for transmitting the DTMF signals (along with the User ID). The reason is quite simple: it is a platform-independent solution that can be received by using any programming language. This allows that the IVR can be integrated with any application.
To store the PIN codes belonging to the User IDs I used PHP in this project. (For this purpose any database can be also used of course.) PHP also allows you to check whether the provided User ID – PIN code combination is correct or not.
To be able to interpret the PIN codes appropriately, your application needs to be able to manage multiple DTMF signals provided one after the other as if they were coherent.
Creating the IVRFactory.cs class
The methods related to the IVR XML have been taken out from the Program.cs
class and they have been put into a new class. It is called IVRFactory.cs
. It contains the CreateIVR
method that is responsible for parsing the default IVR and the response IVR XML (Code example 23).
public static ICommand CreateIVR(string ivrXml, Menu menu = null)
{
if (menu == null)
{
var responseMenu = new Menu();
try
{
var xelement = XElement.Parse(ivrXml);
var menuElement = xelement.Element("menu");
if (menuElement != null)
{
InitElement(menuElement, responseMenu);
KeyElement(menuElement, responseMenu, false);
return responseMenu;
}
else
{
return PlaySpeakElement(xelement);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Invalid ivr xml");
}
}
else
{
try
{
var xelement = XElement.Parse(ivrXml);
var menuElement = xelement.Element("menu");
if (menuElement == null)
{
PlaySpeakElement(xelement, menu);
return menu;
}
ForwardToUrl(menuElement, menu);
InitElement(menuElement, menu);
KeyElement(menuElement, menu, true);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Invalid ivr xml");
}
return menu;
}
return null;
}
Code example 23: The CreateIVR method
If the CreateIVR
method does not receive any menu parameter, it will check whether the received XML includes a menu node, or not.
- If so, the
CreateIVR
method will call the Initelement()
and KeyElement()
methods for parsing. It returns with the new menu. (It will happen, if the provided PIN code is correct, and the caller needs to step into a new menu level.)
- If not, the
CreateIVR
method will call the PlaySpeakElement()
method. (It will happen, if the provided PIN code is not correct, and the caller should be informed about the error.)
If the CreateIVR
method receives a menu parameter during its calling, those methods will be called that are needed for the generation of the default IVR. (The CreateIVR
method will only receive a menu parameter, when it receives the burnt-in ivrXml for processing which includes the default menu.) In this case it is also needed to call the ForwardToUrl()
method that coverts the parameter of the forwardToUrl
into the ForwardToUrl
variable. This variable includes the URL where the HTTP request will be sent to (Code example 24).
static void ForwardToUrl(XElement element, Menu menu)
{
var forwardToUrl = element.Attribute("forwardToUrl");
if (forwardToUrl != null)
{
string[] strings = forwardToUrl.ToString().Split('"');
menu.ForwardToUrl = strings[1];
}
}
Code example 24: The ForwardToUrl method
Modifications in the Menu.cs class
Now take a look at the Menu.cs
class, because you need to make some modifications there. The CreateHttpRequest()
method can be found here. This method is responsible for sending the HTTP request and processing the response. As you can see below, the CreateHttpRequest()
method receives the URL as a parameter (it means the path where the requests need to be sent), the telephone number and the DTMF signals provided by the caller.
string CreateHttpRequest(string url, int dtmf, string phoneNumber)
{
WebRequest request = WebRequest.Create(url);
request.Method = "POST";
string postDtmf = dtmf.ToString();
string callerInfo = phoneNumber;
string postData = postDtmf + " " + callerInfo;
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
WebResponse response = request.GetResponse();
dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();
reader.Close();
dataStream.Close();
response.Close();
return responseFromServer;
}
Code example 25: The CreateHttpRequest() method
The CreateHttpRequest()
method described above creates a Webrequest object by using the provided URL. After this it will send the DTMF signals and the telephone number on a POST message as a byteArray. Thereafter it will also create an object for the response. It will save the data received as a response in a string by using the StreamReader. After closing the streams it will return this string.
As I mentioned above, it is important to be able to manage multiple DTMF signals provided one after the other as if they were coherent. If the program receives multiple DTMF signals one after the other, it will add them to a chain. This chain will be the PIN code itself. For this purpose the InitKeyPressTimeoutTimer()
method can be used. It is called in the constructor of the Menu.cs
class (Code example 26).
void InitKeypressTimeoutTimer()
{
keypressTimeoutTimer = new Timer(1000);
keypressTimeoutTimer.AutoReset = true;
keypressTimeoutTimer.Elapsed += KeypressTimeoutElapsed;
keypressTimeoutTimer.Start();
}
void KeypressTimeoutElapsed(object sender, ElapsedEventArgs e)
{
if (!dtmfPressed)
{
call_DtmfReceived(sender, dtmfChain);
dtmfChain = null;
}
dtmfPressed = false;
}
Code example 26: The InitKeyPressTimeoutTimer() method
If the caller presses the telephone buttons continuously in order to provide his/her PIN code (that is the DTMF signals are received continuously), the application will add the values of the DTMF to the chain. Code example 27 illustrates the DtmfReceived()
method that is used in case of each DtmfReceived
event.
void DtmfReceived(object sender, VoIPEventArgs<DtmfInfo> e)
{
dtmfPressed = true;
dtmfChain += DtmfNamedEventConverter.DtmfNamedEventsToString(e.Item.Signal.Signal);
}
Code example 27: The DtmfReceived() method
If there are not any further DTMF signal within a second, the KeypressTimeoutElapsed()
method will send the chaind by calling the call_DtmfReceived()
method (Code example 28):
void call_DtmfReceived(object sender, string dtmfChain)
{
if (dtmfChain != null)
{
int pressedKey;
if (!Int32.TryParse(dtmfChain, out pressedKey))
{
Console.WriteLine("You did not add a valid number!");
}
MultipleCommandHandler command;
if (keys.TryGetValue(pressedKey, out command))
{
StartCommand(command);
}
else
{
if (ForwardToUrl != null)
{
ResponseXml = CreateHttpRequest(ForwardToUrl, pressedKey, call.DialInfo.UserName);
var responseIvr = IVRFactory.CreateIVR(ResponseXml);
StartCommand(responseIvr);
}
else
{
Console.WriteLine("This is a not used option! Please try again!");
}
}
}
}
Code example 28: The call_DtmfReceived() method
Implementing the necessary PHP section
The following few code snippets illustrate the PHP part of my project. The PHP receives the HTTP request and comfirms whether the provided PIN code is valid or not. (If the entered PIN code is valid, the PHP will sent the IVR menu in a response. If not, the PHP will send an IVR that contains just a speak node in a response.)
As the User data are stored in a multidimensional array, the PHP will look up the necessary information after going through that array (Code example 29).
$userdb=Array
(
(0) => Array
(
('uid') => '1001',
('balance') => '23012312',
('pin') => '6544'
),
(1) => Array
(
('uid') => '1002',
('balance') => '11021021',
('pin') => '1234'
),
(2) => Array
(
('uid') => '1003',
('balance') => '1012',
('pin') => '7658'
)
);
Code example 29: User data in a multidimensional array
Code example 30 shows the file_get_contents(’php://input’)
function that is used to store the HTTP requests in a variable. Thereafter you can tokenize the HTTP request, so you can obtain the DTML values and the phone number.
$rawdata = file_get_contents('php://input');
$dtmf = strtok($rawdata, ' ');
$phoneNumber = strtok(' ');
Code example 30: The file_get_contents(’php://input’) function
As you can see below, first of all the searchForId()
function checks whether in which block the data belonging to the received telephone number can be found.
function searchForId($id, $array) {
foreach ($array as $key => $val) {
if ($val['uid'] === $id) {
return $key;
}
}
return null;
}
$id = searchForId($phoneNumber, $userdb);
Code example 31: The searchForId() function
And finally the array_walk_recursive()
function will check in that block whether the provided PIN code is valid or not (Code example 32):
array_walk_recursive($userdb[$id], function ($item, $key) {
global $dtmf;
global $money;
if($key == 'balance'){
$money = $item;
}
if($key == 'pin'){
if($item == $dtmf){
echo "<response>
<menu>
<init>
<speak>
Access Granted to your bank account. If you want to know your balance, please press one.
</speak>
</init>
<keys>
<key pressed='1'>
<speak>
You pressed button one. Your balance is $" . $money .
"</speak>
</key>
</keys>
</menu>
</response>";
}
else {
echo "<response>
<speak>
Access denied to your bank account.
</speak>
</response>";
}
}
});
Code example 32: The array_walk_recursive() function
To sum it up I have created an application that can be used to authenticate your customers through a phone call by using DTMF signals. This solution can be used effectively in different kind of CRM (Customer Relationship Management) systems, such as during banking/telephone balance inquiry or client gate entry. Due to the IVR system and DTMF signalling, your clients can access to their data automatically (after providing their unique PIN code) by using their touch-tone telephone key buttons. To make my project more understandable I have divided it into two main parts: in the first section I explained how to create an IVR in C# using XML code with the help of the background support of the necessary VoIP components; and in the second half I demonstrated the implemention of the DTMF authentication in C# using HTTP and PHP.