Building a Voice-Based Pizza Ordering Service with Twilio, OpenAI, and Google Maps
Building a Voice-Based Dominos Pizza Ordering Service with Twilio, OpenAI, and Google Maps
Introduction
In this project, we'll develop a phone service enabling users to place an order for Dominos pizza by calling us, using the menu of their nearest store to select items, and processing the payment with their bank card. Additionally, we'll utilize a Large Language Model (LLM) to interpret user requests into Dominos' official item codes.
Prerequisites
- An active Twilio account ( sign up for a free account if you don't have one).
- An OpenAI platform account.
- A Twilio phone number with phone call support.
- A Google Cloud account with a project and billing enabled for address processing.
Gathering Order Information
We'll utilize Twilio Studio to set up the user flow for when calls come in. This graphical tool provides a simple, intuitive way to orchestrate the interactions without dealing with TwiML codedirectly.
Begin by creating a new flow in Twilio Studio and clicking Create new flow. Then, name it "Dominos Flow" and select the Start from scratch option. After clicking Next, you’ll be brought to your newly created Studio Flow.
Step 1: Create the Entry Point
Start with a Gather Input on Call block named entrypoint. This block greets callers and prompts them to begin the ordering process. Configure it to say: Welcome to the Domino's Pizza ordering system. Press 1 to place an order.
Limit the input to one digit (number_of_digits: 1), set a timeout of 5 seconds, and ensure it stops gathering input after the user responds. On keypress, connect this block to the next block, split.
Step 2: Verify User Input
Add a Split Based On block named split. This block checks if the input from entrypoint equals "1" using the variable {{widgets.entrypoint.Digits}}
. If it matches, transition to the next block, get_name.
Step 3: Collect the User’s Name
Create another Gather Input on Call block named get_name. This block prompts the user with: Your name, please?
Configure it to stop gathering once the input is received. On speech, connect this block to the get_address block.
Step 4: Collect the Delivery Address
Add a Gather Input on Call block named get_address to collect the user's delivery details. Set the message to: What is your delivery address, including city, state, and ZIP code?Use the numbers_and_commands speech model for accurate address input. Connect this block to get_food on speech.
Step 5: Collect the Food Order
Create the final Gather Input on Call block named get_food. This block asks the caller: What do you want for today?
Ensure it gathers the input and transitions to the next step in your flow, such as get_price.
Final Connections
Connect the blocks in this order:
- entrypoint → split (on keypress)
- split → get_name (on match)
- get_name → get_address (on speech)
- get_address → get_food (on speech)
Calculating Order Price
Creating a Twilio Serverless Function
Before proceeding to the payment steps, validate the order and compute its price using the Dominos API.
We'll create a custom JavaScript function within Twilio Functions enabling us to leverage NPM packages for our custom logic. For now, set up a service with a user-friendly URL name, like "dominos", and create a corresponding function named "/price". This function will be accessible through a URL format like https://dominos-[random_numbers].twil.io/price
.
Validating the Delivery Address
The Dominos API requires precise address entries. However, users typically won't provide exact matches, and speech recognition can be imperfect, leading to potential errors.
To mitigate this, employ the Google Maps Places API to convert inexact addresses into the required format.
In your Google Cloud project console, locate "Places API" , enable it. Once enabled, you should be presented with an API key; ensure you copy this for the next step.
Head over to Twilio Functions Console, navigate to Environment Variables, and define a variable GOOGLE_MAPS_PLACES_API_KEY
, pasting in the copied key.
Next, in the Dependencies section, add the Google Maps package by copying the following into the Module box: @googlemaps/google-maps-services-js
. Leave the Version box blank and click Add to add the dependency..
Now, in your /price
function, replace everything inside the exports.handler
function with the following code to handle address conversion. Comments are provided for clarity.
Extracting the Order Metadata
Typically, a user will request something like "I want a medium-sized chicken BBQ pizza", which needs translating into a Dominos recognizable code like "CHICKEN10".
For this translation, use an LLM like GPT 4o Mini or any preferred LLM (e.g., Gemini, LLama, Claude).
Go to your OpenAI Platform page, obtain your API key, and add it to a new environment variable: OPENAI_API_KEY
. Then, install the OpenAImodule by navigating to the Dependencies section and entering openai
as Module and clicking Add.
Add the following code snippet below the existing code in module.exports
. This code will interpret user input into a compatible Dominos code:
The menu input is a string representation capturing menu items and their attributes, which is retrieved from the Dominos API.
Getting the Nearest Store
Upon acquiring a formatted delivery address, query the Dominos API to yield a list of proximate stores. Implement a simple algorithm to determine the closest outlet.
Then, install the Dominos module by navigating to the Dependencies section and entering dominos
as Module and clicking Add. Next, add this snippet within exports.handler
:
Note: Direct requires won't work due to CommonJS module limitations.
To change the dominos country settings, use:
Here's how to calculate the nearest store:
Fetching the Order Price
These utilities allow creating, validating, and pricing a Dominos order.
For a comprehensive process that constructs an order, include this code in the exports.handler
function:
This leverages the functions discussed to revalidate addresses and ascertain the nearest store ID, then proceeds to retrieve the store's menu.
Next, translate it for our LLM, only passing essential fields:
Finally, utilize this data to generate and validate a Dominos item object:
Utilize this function with parameters from the handler, utilizing stored event data:
Return the currency, price, order ID, store ID, and address. This prevents redundancy in recalculating this information, thus improving cost efficiency. Additionally, return the estimated wait time, which can be added as user feedback within your flow as an exercise.
Trying it Out
Save the function with Ctrl/CMD + S
and Deploy All
. Return to Twilio Studio.
Incorporating the function into your flow is straightforward. Add a Run Function block, selecting your dominos
service and the /price
function. Name this block get_price
. Then, connect the get_food block to the get_price block using the User Said Something transition.
Within the Function Parameters section of the get_price block, inject the details to the function: name, address, phone, and order, through parameters with this syntax:
Utilize a Say/Play block to notify the caller about their order cost:
Connect the block to the get_price block using the Success transition.
To manage payments within our app, implement PCI compliance for your Twilio account. Access Twilio's Voice Settings, click Enable PCI Mode, agree to Terms of Service, and save changes.
In Voice > Manage > Pay Connectors, install the Generic Pay Connector. Though specific connectors exist (e.g., for Stripe), the Generic Pay method enables direct transaction handling with Dominos.
Note that the Pay Connector facilitates DTMF keypad input for user financial details.
You are advised to use test cards such as 5555555555554444
for MasterCard, with any combination for expiry date, CVV, and Zip Code. A LIVE deployment presents advanced boundaries outside this walkthrough's scope, focusing on PCI security compliance and payment detail tokenization.
Creating a New Twilio Service Route
Establish a new function within the Twilio Dominos service titled /place-order
. This route must be Public to be accessed from our Flow. Therefore, in a production setting, you must ensure strict security practices to ensure that it can only be called from Twilio's webhooks, but that's outside the scope of this guide.
Since building for a production level setting is beyond this guide, a mock function will simulate token generation:
Implement order creation with subtle variations for handling segmented data components. Include this inside exports.handler
:
Now, create an order object utilizing event parameters (we'll later ensure setting up your flow to dispatch data into this function):
Returning a <Pay>
Response
Twilio forwards an HTTP POST request to your function, formatted like:
If it's a tokenize
request, respond with:
For charge
operations, we will return an object with the following schema: charge_id, parameters, error_code and error_message:
We can use the Twilio Response class to create such objects. Add this to your code:
Construct a payment card through request data:
Deliver relevant outputs:
Note: The order placement process is omitted, given we can't fully validate the payment using test card info alone and will result in a failed order due to incorrect data.
Save the function and click the Deploy All button to deploy your new Function.
Adding a Pay Connector
Copy your new /place-order
URL by clicking on the three dots next to your Function name beneath the Functions section and then clicking Copy URL..
Head to the installed modules page and find your connector here. Paste the endpoint into the ENDPOINT URL field on the Generic Pay connector page.
co
Return to Twilio Studio, refreshing the instance to surface the Capture Payments block. Integrate it into the flow, ensuring that the " Pay Connector" property is set to "Default" or matches the pay connector's " UNIQUE NAME," and set the charged amount using:
Approve card types like VISA and MASTERCARD (you may include additional preferences) and select YES for Request Postal Code?.
Inject the following parameters to correspond with the connector configuration:
Integrate Say blocks for both positive (success) and negative (failure) scenarios. Deploy the changes by hitting Publish.
Conclusion
Here's a demo of a phone call with the completed service: https://vimeo.com/1030106262?share=copy.
By following this walkthrough, you've established a comprehensive voice-based service for Dominos, bolstered by Twilio and interfaced with both AI-powered natural language processing and Dominos' own systems. This project showcases integrating robust APIs and cutting-edge AI to make interactions feel natural and intuitive, enhancing user satisfaction and achieving seamless experiences across communication channels.
Next Steps
- Collect and handle tips.
- Let users choose preferred stores.
- Implement "Press 2" to track orders.
- Use GPT to dynamically describe menu items.
- Utilize parsed wait times to inform users in natural units of time.
- Extend the system to manage multiple items like drinks and sides.
- Explicitly inform users of the store processing their order and seek confirmation.
- Handle errors such as incorrect orders, out-of-stock items, or unavailable nearby stores.
- Use GPT to interpret diverse responses, such as name and address, because users might say "my name is X" rather than just giving a name.
Enjoy your streamlined pizza ordering experience!
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.