Build a WhatsApp Currency Conversion Bot with Twilio and Laravel

June 05, 2020
Written by
Dotun Jolaoso
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Build a WhatsApp Currency Conversion Bot with Twilio and Laravel

Over the years, we’ve seen a steady increase in the number of bots and chatbots available to us over different platforms. Brands make use of bots as an exciting way to engage customers, while also providing a way to automate what could normally be considered, monotonous conversations. With it’s over 1.5 billion users and growing, and familiar chat technology, WhatsApp presents a strong case for building a chatbot on it’s platform.

In this tutorial, we’ll be looking at how we can build a WhatsApp Currency Conversion Bot for converting multiple currencies using the Twilio API for WhatsApp.

Technical Requirements

You will need the following to complete this tutorial:

Setting up Laravel

There are different ways to set up a new Laravel project. You can do so via the Laravel installer or by using Composer. For the sake of this tutorial, we’ll be using Composer.

Run the following command in your terminal:

$ composer create-project --prefer-dist laravel/laravel twilio-currency-bot

This will set up a new Laravel project for us in the twilio-currency-bot directory.

Next, we’ll be needing the Twilio SDK Library to enable our bot to send back replies via WhatsApp using Twilio Markup Language (TwiML). From the terminal, run the following command:

$ composer require twilio/sdk

Currency Converter Client

We’ll be using the API From Currency Converter for performing the currency conversion. However, we’ll first need to get an API Key. Head over here to grab your API key. After you’ve entered your email address, an email will be sent to you containing your API key. Next, add the API Key you just received to your .env file.

CURRENCY_CONVERTER_API_KEY=xxxx

In the config directory, head over to the services.php file and add the following array to the file so that we can reference the environment variable we just defined:

 'currency_converter' => [
        'api_key' => env('CURRENCY_CONVERTER_API_KEY')
    ]

Next, we’ll need to create a wrapper around the Currency Converter API. In the app directory, create a CurrencyConverterClient.php file and add the following code to the file:

<?php

namespace App;

use Illuminate\Support\Facades\Http;


class CurrencyConverterClient
{
    protected $baseUrl = 'https://free.currconv.com/api/v7';

    protected $apiKey;

    public function __construct()
    {
        $this->apiKey = config('services.currency_converter.api_key');
    }

    public function getSupportedCurrencies()
    {
        $url = "{$this->baseUrl}/currencies?apiKey={$this->apiKey}";
        $response = Http::get($url);
        if ($response->ok()) {
            return $response->json();
        }
        return $response->throw();
    }

    public function convertCurrency($amount, $baseCurrency, $toCurrency)
    {
        $query = "{$baseCurrency}_{$toCurrency}";
        $url = "{$this->baseUrl}/convert?q={$query}&compact=ultra&apiKey={$this->apiKey}";
        $response = Http::get($url);
        if ($response->ok()) {
            $conversion = $response->json();
            $baseConversion = floatval($conversion[$query]);
            $total = $amount * $baseConversion;
            return number_format($total, 2);
        }
        return $response->throw();
    }
}
  • To make external HTTP calls to Currency Converter’s API, we’re using the HTTP Client that comes bundled with Laravel 7.
  • The getSupportedCurrencies() method is used for fetching a list of all the supported currencies in the world, along with their respective code. Shortly, we will be seeding a database with this data.
  • The convertCurrency() method does the actual conversion from one currency to another. The method accepts three arguments. The amount, the base currency code, and the currency code we would like to convert to.

Creating the Model and Migration

Our application is going to have just a single Model called Currency. Before we create the Model, let’s create the migration to generate the relative database tables.

$ php artisan make:migration create_currency_table

This will create a new migration file for us in the database/migrations directory at [TODAYSDATE]_create_currency_table.php. Next, edit the file with the following code:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateCurrencyTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('currencies', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('code');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('currencies');
    }
}

Next, run the following command to generate an equivalent Model for the migration.

$ php artisan make:model Currency

This will create a new Currency.php file in the app directory. Edit the file with the following code:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Currency extends Model
{
    public static function findByCurrencyCode(array $items)
    {
        return static::whereIn('code', $items)->get();
    }
}

Here, we’ve defined a static function called findByCurrencyCode() which is used to search the code column for a given array of currency code.

Next, update your .env file with your database credentials and then execute the following command to run our migration:

$ php artisan migrate

Seeding the Database

We’ll need to seed the currencies table we just created with a list of all the supported currencies along with their respective code in the world. To create a seeder, run the following command:

$ php artisan make:seeder CurrencySeeder

This will create a new CurrencySeeder.php file in the database/seeds directory. Edit the file with the following code:

<?php

use App\CurrencyConverterClient;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class CurrencySeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run(CurrencyConverterClient $client)
    {
        $currencies = $client->getSupportedCurrencies();

        foreach ($currencies as $currency => $key) {
            foreach ($key as $k) {
                DB::table('currencies')->insert([
                    'name' => $k['currencyName'],
                    'code' => $k['id']
                ]);
            }
        }
    }
}

The CurrencyConverterClient wrapper created earlier, obtains a list of all the supported currencies. Next, we loop through the result and insert the data we’re concerned with at this point, which is the name and code of each currency to their respective columns on the currencies table. Next, use the following command to seed the database:

$ php artisan db:seed --class=CurrencySeeder

Creating the Controller

Now that we have a list of all the currencies in the world stored in our database, we can move over to building the core functionality of our bot. From the terminal, run the following command to create a controller for us:

$ php artisan make:controller BotController

This will create a BotController.php file for us in the app/Http/Controllers directory. Add the following code to the file:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\CurrencyConverterClient;
use Twilio\TwiML\MessagingResponse;
use App\Currency;
use Exception;

class BotController extends Controller
{
    protected $client;

    public function __construct(CurrencyConverterClient $client)
    {
        $this->client = $client;
    }

    public function sendReplies(Request $request)
    {
        $response = new MessagingResponse();

        $body = $request->input('Body');

        $content = $this->determineMessageContent($body);

        $response->message($content);

        return $response;
    }

    private function determineMessageContent(string $content)
    {
        $formatContent = strtolower($content);

        if (strpos($formatContent, 'hello') !== false) {
            $message = "Welcome to the WhatsApp Bot for Currency Conversion \n";
            $message .= "Use the following format to chat with the bot \n";
            $message .= "Convert 5 USD to NGN \n";
            return $message;
        }

        if (strpos($formatContent, 'convert') !== false) {
            return $this->formatContentForConversion($formatContent);
        }

        return $this->formatContentForInvalidMessageFormat();
    }

    private function formatContentForConversion($formatContent)
    {
        $contentInArray = explode(" ", $formatContent);
        $itemsInArray = count($contentInArray);

        if ($itemsInArray < 5 || $itemsInArray > 5) {
            return $this->formatContentForInvalidMessageFormat();
        }

        return $this->performConversion($contentInArray);
    }

    private function formatContentForInvalidMessageFormat()
    {
        $message = "The Conversion Format is Invalid \n";
        $message .= "Please use the format \n";
        $message .= "Convert 5 USD to NGN";

        return $message;
    }

    private function performConversion(array $contentInArray)
    {
        $amount = $contentInArray[1];
        $baseCurrency = strtoupper($contentInArray[2]);
        $toCurrency = strtoupper($contentInArray[4]);

        if (!is_numeric($amount)) {
            return "Please provide a valid amount";
        }

        $items = $this->getCurrencyCode($baseCurrency, $toCurrency);

        if ($items->count() < 2) {
            return "Please enter a valid Currency Code";
        }

        try {
            $convertedAmount = $this->client->convertCurrency($amount, $baseCurrency, $toCurrency);
            return "{$amount} {$baseCurrency} is {$convertedAmount} {$toCurrency}";
        } catch (Exception $e) {
            return "We could not perform this conversion now, please bear with us";
        }
    }

    private function getCurrencyCode(string $baseCurrency, string $currency)
    {
        $items = [$baseCurrency, $currency];

        $currencyCode = Currency::findByCurrencyCode($items);

        return $currencyCode;
    }
}

This is quite a long edit, but step-by-step we will go over what’s happening in this file so you can better understand.

  • The sendReplies() method will be called whenever our bot receives a message from WhatsApp. Once we have the message and apply our logic to determine the response, we need to send back a reply. Twilio expects this reply to be in Twilio Markup Language (TwiML). This is an XML-based language but we’re not required to create XML directly. The MessagingResponse class from the Twilio SDK helps us to send the response in the right format. The response sent is dependent on the body of the message received.
  • The determineMessageContent() method determines the kind of response the user receives. If the content of the message sent to the bot contains the word “hello”, we send back a welcome message informing the user of how to make use of the bot. If the message contains the word “convert”, we’ll assume the user is trying to make a conversion and call the formatContentForConversion() method. Otherwise, if none of these conditions are met, a generic response format is sent back to the user informing them of the right messaging format to use with the bot.
  • The formatContentForConversion() method takes the message and splits it into an array using the PHP explode() method. In order for our bot to work as expected, the message format needs to be in the format: Convert 5 USD to NGN, where 5 is the amount to convert, USD is the currency code of the base currency, and NGN is the currency code the user is trying to convert to.
  • The peformConversion() method does the actual conversion. It accepts an array as it’s argument. Based on the agreed messaging format for our bot to work properly, it’s assumed that the items contained in the first index, second index, and fourth index of the array will be the amount, base currency code and the currency code the user is trying to convert to respectively. The getCurrencyCode() method is then called passing in both currency codes. This method further calls the static method findByCurrencyCode() on the Currency model to ensure that both codes are valid and are supported. Next, the convertCurrency() method is called on the CurrencyConverterClient class we created earlier to carry out the conversion.

Create Webhook Endpoint

Twilio makes use of Webhooks to notify us whenever our bot receives a message. We’ll be adding a Webhook URL to our Twilio account shortly. Before we do that, let’s add the webhook endpoint to our routes. Edit the routes/web.php file and add the following code:

Route::post('/currency', 'BotController@sendReplies');

Disable CSRF Verification

Because Twilio has no way to obtain a CSRF Token from our application, it’s important we disable CSRF verification for the endpoint we created earlier. The VerifyCsrfToken middleware is used for validating all tokens. Luckily, the middleware accepts an except array which accepts a list of endpoints to disable CSRF verification for.

Edit the app\Http\Middleware\VerifyCsrfToken.php file and add the route we created earlier to the except array.

protected $except = [
        '/currency'
];

Set Up Ngrok

We need a way to expose our application to the internet so that Twilio can send Webhook notifications to our application. Using ngrok, we can set up a public URL to enable access to our application over the internet.

To start our Laravel application, run the following command from the terminal:

$ php artisan serve

This will start a development server at a specific port on your local machine. Take note of the port as it will be printed to the terminal once the server starts running. Next, open up another terminal and run the following command:

$ ./ngrok http 8000

NOTE: Replace 8000 with the port your application is running on

This will display a UI in your terminal with your new Public URL and other information.

ngrok session

Take note of the first Forwarding URL, as this is what will be used to configure our Twilio Webhook.

Setting up WhatsApp Sandbox

Before you’re able to send and receive messages using the Twilio API for WhatsApp in production, you’ll need to enable your Twilio number for WhatsApp. Since this can take a while before your number is approved, we can use the Sandbox environment Twilio provides for WhatsApp.

To get started, head over to the WhatsApp section on your Twilio dashboard. You should see the sandbox phone number assigned to your account as below:

WhatsApp Sandbox

Right next to the phone number, you will also see a code that starts with “join” followed by two random words. To enable the WhatsApp sandbox, send a WhatsApp message with this code to the number assigned to your account. After a moment, you should receive a reply from Twilio confirming your mobile number is connected. Once this is done, you can start sending and receiving messages.

Next, head over to the “Sandbox” section under “WhatsApp Beta” and paste the Ngrok URL we noted earlier on the “WHEN A MESSAGE COMES IN” field. Don’t forget to append “/currency” at the end of the URL. Click “Save” at the bottom of the page.

Twilio Sandbox for WhatsApp

Testing

Since we are done coding all of the setup needed for Twilio Sandbox for WhatsApp, we can test the functionality of the bot. Here’s an example of a session I had with the chatbot:

Sample chat bot session

Conclusion

In this tutorial, we have created a simple WhatsApp chatbot for performing currency conversion. This was implemented using Laravel and the Twilio API for WhatsApp. However, it’s important to note that the chatbot isn’t production-ready. Further improvements can be made to improve the bot’s logic for determining the correct response to the user. The GitHub repository with the complete code for this project can be found here.

Dotun Jolaoso

Website: https://dotunj.dev/
Github: https://github.com/Dotunj
Twitter: https://twitter.com/Dotunj_