We are writing a multi-platform bot for transferring money from card to card using Microsoft Bot Framework V1

  • Tutorial
During the Microsoft Build 2016 conference, the Microsoft Bot Framework (session with Build 2016: video ) was announced . Using it, you can create a bot (in C # or Node.js), which you can then connect to various channels / applications: SMS, Skype, Telegram, Slack, etc. We write the bot using the Bot Builder SDK from Microsoft, and the Bot Connector takes care of all the problems with third-party APIs (see image). It sounds beautiful, let's try to create a simple bot that could transfer money from card to card (we will take the transfer logic from Alfa Bank - a test bench, API description: Alfa Bank ), having experienced all the delights of a product in the alpha version.

Disclaimer: at the time of writing, Microsoft released a new version of the framework, so wait for the second series: we will migrate the bot from v1 to V3.



Preparing a development environment


For successful bot development we will need:

  1. Visual studio 2015
  2. Microsoft Account to login at dev.botframework.com
  3. URL with the deployed code of our bot. This URL must be publicly accessible.
  4. Telegram / Skype / etc developer accounts to be able to add communication channels (for each application its own tricks and settings).

Now download the project template for the Bot Framework: aka.ms/bf-bc-vstemplate . To make the new project type available in Visual Studio 2015, copy the downloaded archive to the folder “% USERPROFILE% \ Documents \ Visual Studio 2015 \ Templates \ ProjectTemplates \ Visual C #". Now we are ready to create the simplest echo bot.

First bot


Let's open Visual Studio 2015, we have a new type of project:



The created project is a Web API project with one controller - MessagesController , which in turn has only one available Post method :

MessagesController
[BotAuthentication]
public class MessagesController : ApiController
{
    /// 
    /// POST: api/Messages
    /// Receive a message from a user and reply to it
    /// 
    public async Task Post([FromBody]Message message)
    {
        if (message.Type == "Message")
        {
            // calculate something for us to return
            int length = (message.Text ?? string.Empty).Length;
            // return our reply to the user
            return message.CreateReplyMessage($"You sent {length} characters");
        }
        else
        {
            return HandleSystemMessage(message);
        }
    }
    private Message HandleSystemMessage(Message message)
    {
        if (message.Type == "Ping")
        {
            Message reply = message.CreateReplyMessage();
            reply.Type = "Ping";
            return reply;
        }
        else if (message.Type == "DeleteUserData")
        {
            // Implement user deletion here
            // If we handle user deletion, return a real message
        }
        else if (message.Type == "BotAddedToConversation")
        {
        }
        else if (message.Type == "BotRemovedFromConversation")
        {
        }
        else if (message.Type == "UserAddedToConversation")
        {
        }
        else if (message.Type == "UserRemovedFromConversation")
        {
        }
        else if (message.Type == "EndOfConversation")
        {
        }
        return null;
    }
}



This method accepts a single parameter of type Message , which represents not only the message sent to our bot, but also an event, for example, the addition of a new user to the chat or the end of the conversation. To find out exactly what the message object is, you need to check its Type property , which is done in the controller. If this is a normal message from the user (message.Type == “Message”), we can read the message itself, process it and reply using the CreateReplyMessage method . A simple bot is ready, now we will try to launch it and check the operability. Microsoft provides a convenient utility Bot Framework Emulator ( download for v1), which allows you to conveniently run and debug bots on the local machine. Run our EchoBot project , in the browser this page will appear at localhost : 3978 /


Now run the installed Bot Framework Emulator, which knows that it is worth looking for our running bot on port 3978:


We will send a message to the bot, we will receive an answer. As you can see, everything works. Now consider the creation of a bot that, based on user input, could transfer money from card to card.

Bot to transfer money from card to card


In order to transfer money from card to card, we need information about these cards and the amount of transfer. To facilitate the task of writing standard scripts using the Bot Framework, Microsoft created support for the two most common options for interacting with the bot: Dialogs and FormFlow. In our case, FormFlow is suitable, because all the work of the bot can be represented as filling out a form with data, and then processing it. Dialogs allows you to work with simpler scenarios, for example, a notification script when a given event occurs (it can be useful for monitoring servers). Let's start creating a bot by adding a class, which will be the form that the user needs to fill out. This class should be marked as [Serializable], to annotate properties, use attributes from the Microsoft.Bot.Builder.FormFlow namespace :

CardToCardTransfer
[Serializable]
public class CardToCardTransfer
{
	[Prompt("Номер карты отправителя:")]
	[Describe("Номер карты, с которой Вы хотите перевести деньги")]
	public string SourceCardNumber;
	[Prompt("Номер карты получателя:")]
	[Describe("Номер карты, на которую Вы хотите перевести деньги")]
	public string DestinationCardNumber;
	[Prompt("VALID THRU (месяц):")]
	[Describe("VALID THRU (месяц)")]
	public Month ValidThruMonth;
	[Prompt("VALID THRU (год):")]
	[Describe("VALID THRU (год)")]
	[Numeric(2016, 2050)]
	public int ValidThruYear;
	[Prompt("CVV:")]
	[Describe("CVV (три цифры на обороте карточки)")]
	public string CVV;
	[Prompt("Сумма перевода (руб):")]
	[Describe("Сумма перевода (руб)")]
	public int Amount;
	[Prompt("Комиссия (руб):")]
	[Describe("Комиссия (руб)")]
	public double Fee;
}


In order for the Bot Framework to use a class in FormFlow, all open fields or properties must belong to one of the following types:

  • Integral Types: sbyte, byte, short, ushort, int, uint, long, ulong
  • Floating-point numeric types: float, double
  • Lines
  • Datetime
  • Transfers
  • List of Enumerations

The Prompt attribute is responsible for what text will be shown as a hint for filling the field, Describe - how the field will be called for the user. Now, using the FormBuilder class , we need to tell the Bot Framework that we want to use the CardToCardTransfer class as the form for the dialog. Create a new CardToCardFormBuilder class:
CardToCardFormBuilder
 public static class CardToCardFormBuilder
{
	public static IForm MakeForm()
	{
		FormBuilder _order = new FormBuilder(); 
		return _order
			.Message("Добро пожаловать в сервис перевода денег с карты на карту!")
			.Field(nameof(CardToCardTransfer.SourceCardNumber))
			.Field(nameof(CardToCardTransfer.ValidThruMonth))
			.Field(nameof(CardToCardTransfer.ValidThruYear))
			.Field(nameof(CardToCardTransfer.DestinationCardNumber), null, validateCard)
			.Field(nameof(CardToCardTransfer.CVV))
			.Field(nameof(CardToCardTransfer.Amount))                
			.OnCompletionAsync(async (context, cardTocardTransfer) =>
			{                    
				Debug.WriteLine("{0}", cardTocardTransfer);
			})
			.Build();
	}
}

We create an instance of the FormBuilder class, indicating that we are using CardToCardTransfer as a form. Now using the method call chain, we do the following

  1. The Message method sets the greeting message.
  2. The Field method sets the fields, the value of which the user will have to enter, the order is important.
  3. The OnCompletionAsync method allows you to set the delegate that will be called when the user completes all the fields.
  4. The Build method does the main work - returns an object that implements IForm.

Everything is quite simple, but now we want to add a simple validation of the entered values ​​and the calculation of the commission. To calculate the commission, we will use the fact that we have the AlfabankService class that implements all interaction with the banking API. To validate the card number, we will create the CardValidator class to indicate the delegate used to validate the field; we must pass it to the Field method with the third parameter. The commission calculation also has to be done in the validation method, because in version 1 the Bot Framework did not provide other mechanisms for this.
CardToCardFormBuilder with validation and commission calculation
public static class CardToCardFormBuilder
{
	public static IForm MakeForm()
	{
		FormBuilder _order = new FormBuilder();
		ValidateAsyncDelegate validateCard =
			async (state, value) =>
			{
				var cardNumber = value as string;
				string errorMessage;
				ValidateResult result = new ValidateResult();
				result.IsValid = CardValidator.IsCardValid(cardNumber, out errorMessage);
				result.Feedback = errorMessage;
				return result;
			};
		return _order
			.Message("Добро пожаловать в сервис перевода денег с карты на карту!")
			.Field(nameof(CardToCardTransfer.SourceCardNumber), null, validateCard)
			.Field(nameof(CardToCardTransfer.Fee), state => false)
			.Field(nameof(CardToCardTransfer.ValidThruMonth))
			.Field(nameof(CardToCardTransfer.ValidThruYear))
			.Field(nameof(CardToCardTransfer.DestinationCardNumber), null, validateCard)
			.Field(nameof(CardToCardTransfer.CVV))
			.Field(nameof(CardToCardTransfer.Amount), null,
			async (state, value) =>
			{
				int amount = int.Parse(value.ToString());
				var alfabankService = new AlfabankService();                    
				string auth = await alfabankService.AuthorizePartner();
				state.Fee = (double) await alfabankService.GetCommission(auth, state.SourceCardNumber, state.DestinationCardNumber, amount);                   
				ValidateResult result = new ValidateResult();
				result.IsValid = true;
				return result;
			})
			.Confirm("Вы хотите перевести {Amount} рублей с карты {SourceCardNumber} на карту {DestinationCardNumber}? Комиссия составит {Fee} рублей. (y/n)")
			.OnCompletionAsync(async (context, cardTocardTransfer) =>
			{                    
				Debug.WriteLine("{0}", cardTocardTransfer);
			})
			.Build();
	}
}


The last step left is to integrate CardToCardFormBuilder into the controller. To do this, we need a method that returns an IDialogto pass it in turn as the second parameter to the Conversation.SendAsync method .
MessagesController
[BotAuthentication]
public class MessagesController : ApiController
{
	internal static IDialog MakeRoot()
	{
		return Chain.From(() => FormDialog.FromForm(CardToCardFormBuilder.MakeForm))
			.Do(async (context, order) =>
			{
				try
				{
					var completed = await order;
					var alfaService = new AlfabankService();
					string expDate = completed.ValidThruYear.ToString() + ((int)completed.ValidThruMonth).ToString("D2");
					string confirmationUrl = await alfaService.TransferMoney(completed.SourceCardNumber, expDate, completed.CVV, completed.DestinationCardNumber, completed.Amount);
					await context.PostAsync($"Осталось только подтвердить платеж. Перейдите по адресу {confirmationUrl}");
				}
				catch (FormCanceledException e)
				{
					string reply;
					if (e.InnerException == null)
					{
						reply = $"Вы прервали операцию, попробуем позже!";
					}
					else
					{
						reply = "Извините, произошла ошибка. Попробуйте позже.";
					}
					await context.PostAsync(reply);
				}
			});
	}
	/// 
	/// POST: api/Messages
	/// Receive a message from a user and reply to it
	/// 
	public async Task Post([FromBody]Message message)
	{
		if (message.Type == "Message")
		{
			return await Conversation.SendAsync(message, MakeRoot);
		}
		else
		{
			return HandleSystemMessage(message);
		}
	}

The actual binding takes place in the code Chain.From (() => FormDialog.FromForm (CardToCardFormBuilder.MakeForm)), and then we pass to the Do method a method that waits for the completion of the request and processes it, while also being responsible for handling errors. Now we can start the bot and test its work in the emulator:



We can make sure that the bot works as expected, now we need to make friends with the Bot Connector.

Register the bot in the Bot Connector


First, we need to upload our bot to some public URL, for example, in Azure (a free subscription is suitable): https://alfacard2cardbot.azurewebsites.net . Now go to dev.botframework.com using your Microsoft account. In the upper menu, select "Register a Bot", enter all the required fields: name, description, Messaging endpoint - the same public URL, etc.



Do not forget to update our web.config by adding the AppId and AppSecret generated by us at this step. We will end up with these changes. Now our bot has appeared in the “My Bots” menu, we can make sure that the Bot Connector interacts correctly with the bot using the “Test connection to your bot” window in the lower left. Now it remains to add interaction with Telegram, for this, in the right column, select “Add another channel” - “Telegram” - “Add”, this window will open, in which it is described in steps how to add the Telegram bot:



Source code, testing, conclusion


Telegram bot can be written @ AlfaCard2CardBot , money will not be transferred, test environment. The code can be found on GitHub: https://github.com/StanislavUshakov/AlfaCardToCardBot .
In the next series, we will migrate the bot to version 3!

Also popular now: