Building a Telegram bot with PayPal payment and InlineKeyboardMarkup

A Telegram Bot with InlineKeyboardMarkup and a Paypal payment link.

Steve Dua
11 min readMar 24, 2023
Telegram Bot with InlineKeyboardMarkup
Telegram Bot with InlineKeyboardMarkup

Let’s discuss how to use he InlineKeyboardMarkup class from the python-telegram-bot package to make an inline keyboard in a Telegram chat.
With our keyboard, users have the option to pick a payment amount or enter one themselves.
Once that’s done, a payment link will be created.

We’ll go over the Python code necessary to create the keyboard and integrate it with the Paypal, with an explanation of each step in detail.

So let’s get started!

Here are the steps we’ll be following:

  • Create a PayPal sandbox App and retrieve your keys
  • Create a new bot on Telegram.
  • Let’s code.

The focus here is to dive into Telegram inline keyboards.

How to get a Paypal Client ID and Secret Key

If you’re a merchant and want to get a PayPal API key and secret in Sandbox mode, just follow these steps:

  • First, log in to your PayPal Developer account at https://developer.paypal.com/.
  • Next, head to the Dashboard and select the “My Account” tab.
  • Then click on the “Apps & Credentials” button.
  • Under the “REST API apps” section, find and click on the “Create App” button.
  • Give your app a name and select the Sandbox environment.
  • Once you’ve created the app, you’ll be taken to the app details page where you can find your Sandbox API key and secret.

One thing to keep in mind is that these keys are only for testing purposes, so don’t use them in production. To get your API keys for production, you’ll need to repeat these steps in your live PayPal account.

Setting up the Telegram API

If you want to get a Telegram API key, you have to create a Telegram bot.
Here are the step:

  • Open the Telegram app on your device or go to the Telegram website and log in.
  • Search for the “BotFather” bot and start a chat with it.
  • Type “/newbot” and follow the instructions to create a new bot. You will need to give your bot a name and a username.
  • When your bot is created, the BotFather will give you a token.
    This token serves as your Telegram API key.
  • Copy the API key and save it in a safe place.
Screenshot BodFather Telegram Bot
BodFather Telegram Bot

Note that the API key should be kept private. It allows anyone with access to it to send messages on behalf of your bot.

Installing the Paypal and Telegram Packages

The python-telegram-bot package provides an interface to the Telegram Bot API, which lets you communicate with Telegram bots.
To get started, you can use the following command in the terminal:

pip3 install python-telegram-bot

To use Paypal in our code, we can install the paypalrestsdk package by entering this command in the terminal:

pip3 install paypalrestsdk

Imports PayPal and Telegram

Alright, let’s talk about the import statements that we’ll be using.

To start with, you need to create a file and name it ‘paypaltelegrambot.py’. After that, we’ll be using the following import statements:

paypaltelegrambot.py file

import telegram
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler, ConversationHandler
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, ParseMode
import paypalrestsdk

# Define constants for PayPal configuration
PAYPAL_MODE = 'sandbox' # 'live' if using real PayPal account
PAYPAL_CLIENT_ID = "<<your_paypal_client_id>>"
PAYPAL_CLIENT_SECRET = "<your_paypal_client_secret>>"

# Initialize PayPal SDK
paypalrestsdk.configure({
'mode': PAYPAL_MODE,
'client_id': PAYPAL_CLIENT_ID,
'client_secret': PAYPAL_CLIENT_SECRET
})

# Define conversation states
PAYMENT_AMOUNT, PAYMENT_CONFIRMATION = range(2)

The code begins by importing the telegram module and several related submodules that are used to interact with the Telegram API and create the bot.

After that, it imports the paypalrestsdk module, which is a Python SDK for interacting with the PayPal API.

Then, it defines three constants that are used to configure the PayPal SDK: PAYPAL_MODE, which sets the environment to 'sandbox' or 'live' together with your PayPal client ID and PayPal client secret.

PAYMENT_AMOUNT and PAYMENT_CONFIRMATION are using the range() function to create a unique integer identifiers for each state.

Meet the trio: your Bot’s Best Friends

The Telegram Bot API library has three classes that work together to handle user interactions with a Telegram bot: CommandHandler, ConversationHandler, and CallbackQueryHandler.

CommandHandler processes commands sent by the user, which are triggered by a forward slash (/) followed by a command name. For example, the command “/start” is used to initiate a conversation with a bot. It can perform certain actions based on the received command.

ConversationHandler handles user input and generates bot responses, guiding the user through a specific conversation flow. ConversationHandler uses a state machine approach to manage conversation states and respond accordingly.

CallbackQueryHandler handles callbacks from inline keyboards, which are custom keyboards that appear within the chat interface. When a user interacts with an inline keyboard, CallbackQueryHandler handles the interaction and sends a callback query to the bot. The bot processes the query and generates an appropriate response.

Why you should use a ConversationHandler

One of the reasons ConversationHandler is so useful is because it uses a state machine approach to manage our conversation states.
That’s a fancy way of saying: it can keep track of where you are in our conversation and respond accordingly. This ensures that you always receive appropriate options and information based on our current conversation state.

Another advantage of ConversationHandler is that it ensures buttons created during our conversation are no longer active once we end the conversation. This is an important one for me because it prevents users from accidentally selecting buttons from a previous conversation.

Image generated on Midjourney. Prompt: a robot in paper with headset and microphone — ar 3:2
Image generated on Midjourney. Prompt: a robot in paper with headset and microphone — ar 3:2

The code

Okay, so we’re trying to set up 2 commands for our bot.

  • One is called /buy_manual_amount, where the user types in the amount they want to pay manually.
  • The other one is called /buy_button_amount, where the user can choose from 4 different amounts.

Either way, the user needs to confirm their choice.

def main():
"""Main function to start the bot and handle commands"""

# Create the Updater and pass it your bot's token.
updater = Updater("<<your_telegram_api_key>>")

# Get the dispatcher to register handlers
dispatcher = updater.dispatcher

# Define command handlers
buy_manual_amount_handler = CommandHandler('buy_button_amount', buy_button_amount)
buy_button_amount_handler = CommandHandler('buy_manual_amount', buy_manual_amount)

# Add conversation_handler for manual input
conversation_handler_manual = ConversationHandler(
entry_points=[buy_manual_amount_handler],
states={
PAYMENT_AMOUNT: [CallbackQueryHandler(ask_for_confirmation)],
PAYMENT_CONFIRMATION: [CallbackQueryHandler(payment_confirmation)]
},
fallbacks=[CommandHandler('cancel', cancel)]
)
dispatcher.add_handler(conversation_handler_manual)

# Add conversation_handler for button input
conversation_handler_buttons = ConversationHandler(
entry_points=[buy_button_amount_handler],
states={
PAYMENT_AMOUNT: [MessageHandler(Filters.text & ~Filters.command, ask_for_confirmation)],
PAYMENT_CONFIRMATION: [CallbackQueryHandler(payment_confirmation)]
},
fallbacks=[CommandHandler('cancel', cancel)]
)
dispatcher.add_handler(conversation_handler_buttons)

So we have a function called main which is responsible for starting our bot and handling commands. First, we create an Updater and give it our bot’s token. Then, we get the dispatcher to register our handlers.

To make the bot understand different types of user input, we use two commands. The first command is called conversation_handler_manual , which is for manual input. This means that the user types in the payment amount they want to pay. The second command is called conversation_handler_buttons, which is for button input. This means that the user can choose from four preset payment amounts.

Both commands have two states: PAYMENT_AMOUNT and PAYMENT_CONFIRMATION.
The first state, PAYMENT_AMOUNT, is where the bot waits for the user to input or select the payment amount.
The second state, PAYMENT_CONFIRMATION, is where the bot waits for the user to confirm the payment amount they have input or selected.

For the conversation_handler_manual command, we use the buy_button_amount_handler command to trigger it. This means that when the user clicks on the “buy” button, the bot will ask for the payment amount. The bot will confirm the payment amount using a CallbackQueryHandler. If the user wants to cancel the payment, they can use the “cancel” command.

For theconversation_handler_buttons command, we use the buy_manual_amount_handler command to trigger it. This means that the user can select one of the preset payment amounts using buttons. The bot will filter out any commands and ask for confirmation using a MessageHandler. If the user wants to cancel the payment, they can use the “cancel” command.

— — — — — — — — — — —
Janeway: “Seven, have you heard of the ConversationHandler library for chatbots?”
Seven: “Yes, Captain. It’s a powerful tool for managing dialogue flow.”
Janeway: “I was thinking more along the lines of using it for our morning coffee conversations.”
Seven: “I’m not sure how that would be useful, Captain.”
Janeway: “Well, we could use it to handle the flow of coffee orders and creamer preferences.”
Seven: “I see. So we could create a chatbot to handle our coffee runs.”
Janeway: “Exactly, Seven! Now that’s what I call a conversation worth handling."
— — — — — — — — — — —

So, there are two cool functions that help us handle different buying commands for products. Let me break them down for you.

# Define command handler for /buy command
def buy_button_amount(update, context):
# Ask user for payment amount
keyboard = [
[InlineKeyboardButton("$50", callback_data='40')],
[
InlineKeyboardButton("$40", callback_data='40'),
InlineKeyboardButton("$30", callback_data='30'),
],
[InlineKeyboardButton("$20", callback_data='20')],
[InlineKeyboardButton("cancel", callback_data='0')],
]
reply_markup = InlineKeyboardMarkup(keyboard)
context.bot.send_message(chat_id=update.effective_chat.id, text=f'How much do you want to pay?', reply_markup=reply_markup)

# Transition to ask_for_confirmation
return PAYMENT_AMOUNT

# Define command handler for /buy_manual_amount command
def buy_manual_amount(update, context):
# Ask user for payment amount
context.bot.send_message(chat_id=update.effective_chat.id, text='How much do you want to pay?')

# Transition to ask_for_confirmation
return PAYMENT_AMOUNT

First up, we have the buy_button_amount function that takes care of the /buy_button_amount command. This command gives the user some pre-defined options to select from when they’re paying. The function creates a cool custom keyboard with these options and sends it to the user, along with a message asking them to select one of the amounts. After the user selects an amount, the function moves to the PAYMENT_AMOUNT state, where it asks for confirmation.

The other function we have is called buy_manual_amount, which handles the /buy_manual_amount command. This command lets the user manually enter the amount they want to pay. The function sends a message to the user asking them to enter the amount they want to pay. After the user enters the amount, the function moves to the PAYMENT_AMOUNT state to ask for confirmation.

In both these functions, the PAYMENT_AMOUNT state is used to confirm the payment amount. We use the ask_for_confirmation callback function to ask the user to confirm the payment amount, and the payment_confirmation callback function to handle the user’s response.
Both these functions also use the cancel command to handle user cancellation requests, which are caught by the fallbacks parameter of the ConversationHandler class.

— — — — — — — — — — —
Janeway: “Seven, have you ever heard of an ‘InlineKeyboardMarkup’?”
Seven of Nine: “No, Captain. I am not familiar with that type of technology. What is its purpose?”
Janeway: “It’s a way to create interactive buttons in Telegram messages. You could say it’s the ‘key’ to a successful chat.”
— — — — — — — — — — —

Alright, let’s talk about this piece of code that defines a function called ask_for_confirmation.
This function is super useful because it confirms the payment amount with the user and takes us to the PAYMENT_CONFIRMATION state.


# Define message handler for ask_for_confirmation
def ask_for_confirmation(update, context):
# Store payment amount in user data
if update.message is None:
# Handle /buy_manual_amount response
context.user_data['payment_amount'] = update.callback_query.data
query = update.callback_query.data
else:
# Handle buy reponse
context.user_data['payment_amount'] = update.message.text
query = update.message.text

if query != "0":
# Ask user for confirmation
keyboard = [[InlineKeyboardButton("Yes", callback_data='yes'), InlineKeyboardButton("No", callback_data='no')]]
reply_markup = InlineKeyboardMarkup(keyboard)
context.bot.send_message(chat_id=update.effective_chat.id, text=f'You want to pay {query}?', reply_markup=reply_markup)
else:
# Payment is cancelled when amount is 0
context.bot.send_message(chat_id=update.effective_chat.id, text='Payment cancelled.')
# End conversation
return ConversationHandler.END


# Transition to payment_confirmation
return PAYMENT_CONFIRMATION

So, here’s how it works. First, the function checks if the user has entered the payment amount manually or selected it using a button. If the user entered the amount manually, the function retrieves the amount from the update.message.text attribute. But if the user selected it using a button, then the function retrieves the amount from the update.callback_query.data attribute.

Also, it checks if the payment amount is zero. If it is, then the function sends a message saying that the payment has been cancelled and ends the conversation. But, if the amount is not zero, the function sends a message to the user to confirm the payment amount. And, it does this using an inline keyboard with “Yes” and “No” buttons.

Finally, the function transitions to the PAYMENT_CONFIRMATION state and returns this state. So, this function is really helpful in making sure the user confirms their payment amount before moving to the next state.

— — — — — — — — — — —
“I’m a doctor, not a keyboardist!” — Doctor Bones
— — — — — — — — — — —

This piece of code defines a function named payment_confirmation, which handles the user’s response to the payment confirmation message. The function checks if the user said “yes” or “no” in response.

# Define callback query handler for payment confirmation
def payment_confirmation(update, context):
# Handle YES/NO response
query = update.callback_query
if query.data == 'yes':
# Create PayPal payment object
payment = paypalrestsdk.Payment({
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"transactions": [{
"amount": {
"total": context.user_data['payment_amount'],
"currency": "USD"
},
"description": "Payment for my bot"
}],
"redirect_urls": {
"return_url": "http://example.com/your_redirect_url",
"cancel_url": "http://example.com/your_cancel_url"
}
})

# Create PayPal payment and get approval URL
if payment.create():
for link in payment.links:
if link.rel == 'approval_url':
approval_url = link.href
context.user_data['paypal_payment_id'] = payment.id
break
else:
# Payment creation failed
context.bot.send_message(chat_id=update.effective_chat.id, text='Payment creation failed.')
return ConversationHandler.END
else:
# Payment creation failed
context.bot.send_message(chat_id=update.effective_chat.id, text='Payment creation failed.')
return ConversationHandler.END

# Send approval URL to user
context.bot.send_message(chat_id=update.effective_chat.id, text=f'Please click the following link to complete the payment:\n{approval_url}')

elif query.data == 'no':
context.bot.send_message(chat_id=update.effective_chat.id, text='Payment cancelled.')

# End conversation
return ConversationHandler.END

If the user said “yes,” the function uses the paypalrestsdklibrary to create a PayPal payment object. This object contains information about the payment, such as the amount, currency, and description, as well as the return and cancel URLs.
The function calls the create() method on the payment object to create the payment on PayPal’s server.
If the payment is created successfully, the function retrieves the approval URL from the payment object and sends it to the user using the context.bot.send_message() method.
The function also saves the Paypal payment ID in the context.user_data[‘paypal_payment_id’] variable for later use.

The return_url and cancel_url are parameters of the redirect_urls object in the PayPal payment creation request.
The return_url is the URL where the user will be redirected after completing the payment process, and typically leads to a "thank you" or confirmation page.
The cancel_url, on the other hand, is the URL where the user will be redirected if they cancel the payment process before completing it.
These URLs are simply placeholders and need to be replaced with the actual URLs where the user should be redirected after payment confirmation or cancellation.
The URLs can point to any valid website or page.
For example, if your return URL is http://example.com/your_redirect_url?paymentId=PAYID-XXXXXXXXXXXXXXXXX , you could retrieve the payment ID from this URL if you are the owner of the website and then use it to retrieve the payment details from PayPal’s API.
Keep in mind that our Python code already stored the Paypal payment ID in the context.user_data[‘paypal_payment_id’] variable

If the payment creation fails, the function sends a message to the user telling them that the payment creation has failed and ends the conversation using the ConversationHandler.END method.

If the user said “no,” the function sends a message to the user saying that the payment has been cancelled and ends the conversation using the ConversationHandler.END method.

That’s it!

Heroku

If you want to deploy your Telegram bot on Heroku, you’ll need to modify the code to use polling instead of webhooks to fetch new data.
But don’t worry, this is a simple task that can be accomplished by tweaking the code a little.
You can find a helpful guide on how to make these changes here.

Conclusion

So, I’ve got you covered with everything you need to know, from the code and import statements you’ll need, to getting the API keys for both Telegram and PayPal.
Plus, we’ve discussed the three classes from the Telegram Bot API library that manage user interactions with the bot.
Now you can easily create a bot with our advanced inline keyboard.

Thanks for reading!

References

Get the code on Github here:

Telegram Bot:
https://github.com/stevebelgium/telegramkeyboard

If you enjoyed this, please clap and follow me on Medium

--

--