Self Service Using Keypad Input
In this how-to guide, we will guide you through implementing a basic self-service IVR application with FreeClimb that utilizes user DTMF (e.g. keypad) inputs. This system will perform the following actions:
- Receive an incoming call via a FreeClimb application
- Get user DTMF (e.g. keypad) input
- Redirect a user to the appropriate endpoint
- Return information to the user based on previous input
You're ready for this how-to guide if you have:
Followed the IVR sample app set-up instructions
Step 1: Create the express server
The server will provide endpoints where user input can be captured. Create an index.js
file in your project directory, import the needed dependencies, and create/configure the Express application:
require('dotenv-safe').config()
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Starting server on port ${port}`)
})
module.exports = { app }
Step 2: Handle an incoming call
The first step in our call routing application will be to handle incoming calls. To do this, add the following to index.js
:
const {
createConfiguration,
DefaultApi,
PerclScript,
Say,
Pause,
Redirect,
Hangup
} = require('@freeclimb/sdk')
const host = process.env.HOST
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const freeclimb = new DefaultApi(createConfiguration({ accountId, apiKey }))
app.post('/incomingCall', (req, res) => {
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: 'Welcome to the Node self service IVR.' }),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/mainMenuPrompt` })
]
}).build()
)
})
We'll also use index.js
to contain the routes for handling call end and transfer. To create these add the following to index.js
after /incomingCall
but before app.listen
:
app.post('/transfer', (req, res) => {
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: 'there are no operators available at this time' }),
new Redirect({ actionUrl: `${host}/endCall` })
]
}).build()
)
})
app.post('/endCall', (req, res) => {
res.status(200).json(
new PerclScript({
commands: [
new Say({
text:
'Thank you for calling the Node self service IVR , have a nice day!'
}),
new Hangup({})
]
}).build()
)
})
Step 3: Collect digits via a main menu
Next we'll create a main menu for collecting DTMF input from the user and routing their call appropriately. To do this create a new file in your project directory called mainMenu.js
and add the following:
require('dotenv-safe').config()
const express = require('express')
const {
createConfiguration,
DefaultApi,
PerclScript,
Say,
Pause,
Redirect,
GetDigits
} = require('@freeclimb/sdk')
const host = process.env.HOST
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const freeclimb = new DefaultApi(createConfiguration({ accountId, apiKey }))
router = express.Router()
let mainMenuErrCount = 0
router.post('/mainMenuPrompt', (req, res) => {
res.status(200).json(
new PerclScript({
commands: [
new GetDigits({
actionUrl: `${host}/mainMenu`,
prompts: [
new Say({
text:
'Press 1 for existing orders, 2 for new orders, or 0 to speak to an operator'
})
],
maxDigits: 1,
minDigits: 1,
flushBuffer: true
})
]
}).build()
)
})
router.post('/mainMenu', (req, res) => {
const getDigitsResponse = req.body
const digits = getDigitsResponse.digits
const menuOpts = new Map([
[
'1',
{
script: 'Redirecting your call to existing orders.',
redirect: `${host}/accountNumberPrompt`
}
],
[
'2',
{
script: 'Redirecting your call to new orders.',
redirect: `${host}/transfer`
}
],
[
'0',
{ script: 'Redirecting you to an operator', redirect: `${host}/transfer` }
]
])
if ((!digits || !menuOpts.get(digits)) && mainMenuErrCount < 3) {
mainMenuErrCount++
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: 'Error, please try again' }),
new Redirect({ actionUrl: `${host}/mainMenuPrompt` })
]
}).build()
)
} else if (mainMenuErrCount >= 3) {
mainMenuErrCount = 0
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: 'Max retry limit reached' }),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/endCall` })
]
}).build()
)
} else {
mainMenuErrCount = 0
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: menuOpts.get(digits).script }),
new Redirect({ actionUrl: menuOpts.get(digits).redirect })
]
}).build()
)
}
})
module.exports = router
To make these routes available from our /incomingCall
endpoint defined in index.js
, add the following content to your index.js
file before the /incomingCall
endpoint:
const mainMenuRoutes = require('./mainMenu')
app.use('/', mainMenuRoutes)
Step 4: Get an account number
Next we'll create routes to ask the user for an account number and redirect according to their input. To do this, create a new file in your project directory called accountNumberEntry.js
and add the following:
require('dotenv-safe').config()
const express = require('express')
const {
createConfiguration,
DefaultApi,
PerclScript,
Say,
Pause,
Redirect,
GetDigits
} = require('@freeclimb/sdk')
const host = process.env.HOST
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const freeclimb = new DefaultApi(createConfiguration({ accountId, apiKey }))
router = express.Router()
let acctMenuErrCount = 0
router.post('/accountNumberPrompt', (req, res) => {
res.status(200).json(
new PerclScript({
commands: [
new GetDigits({
actionUrl: `${host}/accountNumber`,
prompts: [new Say({ text: 'Please Enter your account number' })],
maxDigits: 6,
minDigits: 1,
flushBuffer: true
})
]
}).build()
)
})
router.post('/accountNumber', (req, res) => {
const getDigitsResponse = req.body
const digits = getDigitsResponse.digits
if ((!digits || digits.length < 6) && acctMenuErrCount < 2) {
acctMenuErrCount++
res.status(200).json(
new PerclScript({
commands: [
new Say({
text:
'Error, please enter your six digit account number or press 0 to speak with an operator'
}),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/accountNumberPrompt` })
]
}).build()
)
} else if (acctMenuErrCount >= 2) {
acctMenuErrCount = 0
res.status(200).json(
new PerclScript({
commands: [
new Say({
text:
'Max retry limit reached, please wait while we connect you to an operator'
}),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/transfer` })
]
}).build()
)
} else {
acctMenuErrCount = 0
res.status(200).json(
new PerclScript({
commands: [
new Redirect({
actionUrl: `${host}/confirmAccountNumberPrompt?acct=${digits}`
})
]
}).build()
)
}
})
module.exports = router
Then, make the routes available by adding the following to your index.js
file before the /incomingCall
endpoint:
const accountNumberEntryRoutes = require('./accountNumberEntry')
app.use('/', accountNumberEntryRoutes)
Step 5: Confirm the account number
Next we'll create routes to :
- read the input from the user back to them and
- ask the to confirm their previous entry
To do this, create a file in your project directory called accountNumberConfirmation.js
and add the following:
require('dotenv-safe').config()
const express = require('express')
const {
createConfiguration,
DefaultApi,
PerclScript,
Say,
Pause,
Redirect,
GetDigits
} = require('@freeclimb/sdk')
const host = process.env.HOST
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const freeclimb = new DefaultApi(createConfiguration({ accountId, apiKey }))
router = express.Router()
let confirmNumberErrCount = 0
let retries = 0
router.post('/confirmAccountNumberPrompt', (req, res) => {
res.status(200).json(
new PerclScript({
commands: [
new GetDigits({
actionUrl: `${host}/confirmAccountNumber?acct=${req.param('acct')}`,
prompts: [
new Say({
text: `You entered ${req.param(
'acct'
)} is that correct? Press 1 to confirm your account number or 2 to try again`
})
],
maxDigits: 1,
minDigits: 1,
flushBuffer: true
})
]
}).build()
)
})
router.post('/confirmAccountNumber', (req, res) => {
const getDigitsResponse = req.body
const digits = getDigitsResponse.digits
const menuOpts = new Map([
[
'1',
{
script: 'proceeding to account number lookup.',
redirect: `${host}/accountLookup?acct=${req.param('acct')}`
}
],
[
'2',
{
script: 'Ok',
redirect: `${host}/accountNumberPrompt`
}
],
[
'0',
{ script: 'Redirecting you to an operator', redirect: `${host}/transfer` }
]
])
if ((!digits || !menuOpts.get(digits)) && confirmNumberErrCount < 3) {
confirmNumberErrCount++
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: 'Error' }),
new Redirect({
actionUrl: `${host}/confirmAccountNumberPrompt?acct=${req.param(
'acct'
)}`
})
]
}).build()
)
} else if (confirmNumberErrCount >= 3 || retries >= 2) {
confirmNumberErrCount = 0
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: 'Please wait while we connect you to an operator' }),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/transfer` })
]
}).build()
)
} else {
confirmNumberErrCount = 0
if (digits === '2') {
retries++ // retries tracked separately from input errors
} else if (digits === '1') {
retries = 0
}
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: menuOpts.get(digits).script }),
new Redirect({ actionUrl: menuOpts.get(digits).redirect })
]
}).build()
)
}
})
module.exports = router
Then, make the routes available by adding the following to your index.js
file before the /incomingCall
endpoint:
const accountNumberConfirmationRoutes = require('./accountNumberConfirmation')
app.use('/', accountNumberConfirmationRoutes)
Step 6: Create an accounts file for testing
In order to simulate having an accounts service that returns accounts with several different statuses, we'll create a new file for testing purposes. To do this, create a new file called accounts.js
in your main directory and add the following:
const accounts = new Map([
[
'111222',
{
open: true,
frequentBuyer: true,
name: 'John Smith',
mostRecentOrderDate: 'March 30th 2020'
}
],
[
'222333',
{
open: true,
frequentBuyer: false,
name: 'Jane Smith',
mostRecentOrderDate: 'March 30th 2020'
}
],
[
'333444',
{
open: false,
frequentBuyer: true,
name: 'Sam Smith',
mostRecentOrderDate: 'March 30th 2020'
}
]
])
module.exports = accounts
Step 7: Lookup the account number
Next we'll create a route to take the account number entered and confirmed by the user in steps 4 and 5 and search for it in the data structure we created in step 6. We'll also direct the call based on the status of the account.
To do this create a new file in your project directory called accountLookup.js
and add the following content:
require('dotenv-safe').config()
const express = require('express')
const {
createConfiguration,
DefaultApi,
PerclScript,
Say,
Pause,
Redirect,
GetDigits
} = require('@freeclimb/sdk')
const accounts = require('./accounts')
const host = process.env.HOST
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const freeclimb = new DefaultApi(createConfiguration({ accountId, apiKey }))
router = express.Router()
let retries = 0
router.post('/accountLookup', (req, res) => {
if (!accounts.get(req.param('acct')) && retries < 2) {
retries++
res.status(200).json(
new PerclScript({
commands: [
new Say({ text: 'Sorry, we couldnt find that account number.' }),
new Redirect({ actionUrl: `${host}/accountNumberPrompt` })
]
}).build()
)
} else if (retries >= 2) {
retries = 0
res.status(200).json(
new PerclScript({
commands: [
new Say({
text:
'Max retry limit reached, please wait while we connect you to an operator'
}),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/transfer` })
]
}).build()
)
} else {
res.status(200).json(
new PerclScript({
commands: [
new Redirect({
actionUrl: `${host}/accountRead?acct=${req.param('acct')}`
})
]
}).build()
)
}
})
module.exports = router
Then, make the routes available by adding the following to your index.js
file before the /incomingCall
endpoint:
const accountLookupRoutes = require('./accountLookup')
app.use('/', accountLookupRoutes)
Step 8: Read account information back to caller
Finally, we'll add the route for reading information about the user's account back to the caller. To do this, create a new file called accountRead.js
and add the following:
require('dotenv-safe').config()
const express = require('express')
const {
createConfiguration,
DefaultApi,
PerclScript,
Say,
Pause,
Redirect
} = require('@freeclimb/sdk')
const accounts = require('./accounts')
const host = process.env.HOST
const accountId = process.env.ACCOUNT_ID
const apiKey = process.env.API_KEY
const freeclimb = new DefaultApi(createConfiguration({ accountId, apiKey }))
router = express.Router()
router.post('/accountRead', (req, res) => {
account = accounts.get(req.param('acct'))
if (account.open) {
if (account.frequentBuyer) {
res.status(200).json(
new PerclScript({
commands: [
new Say({
text:
'Welcome back platinum member, please wait while we connect you with a customer service representative.'
}),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/transfer` })
]
}).build()
)
} else {
res.status(200).json(
new PerclScript({
commands: [
new Say({
text: `Welcome back ${account.name}, I've found your most recent order from ${account.mostRecentOrderDate}, please hold while I connect you with a customer service representative. `
}),
new Pause({ length: 100 }),
new Redirect({ actionUrl: `${host}/transfer` })
]
}).build()
)
}
} else {
res.status(200).json(
new PerclScript({
commands: [
new Say({
text:
'This account appears to be closed please wait while we transfer you to an operator for asistance'
}),
new Redirect({ actionUrl: `${host}/transfer` })
]
}).build()
)
}
})
module.exports = router
Then, make the routes available by adding the following to your index.js
file before the /incomingCall
endpoint:
const accountReadRoutes = require('./accountRead')
app.use('/', accountReadRoutes)
Step 9: Run your app
To hear your IVR DTMF self service app in action, run the following command at the command line:
yarn start
Once you do this, call the FreeClimb number associated with your application. From there you should be able to experience your self service application.
Congrats! You can now build your own custom IVR DTMF self service application. 🥳🥳
Or, to add voice-enabled capabilities, continue with the our Self Service Using Text-to-Speech how-to guide.
For a more advanced IVR how-to guide, try out our Pay by Phone with Voice Calling sample app.
Updated 2 months ago