You can accept payments from Telegram users via Telegram Bots.
Note: This article is intended for MTProto API developers. If you're looking for a general overview of Telegram Payments, check out the Telegram blog and the bot API payment manual.
Telegram bots can accept payments for goods and services from users. For more info on how payments work, check out the Telegram Blog and the bot API payment manual.
This page will elaborate on the actions required to work with payments using the MTProto API.
A simplified version of the process is available only for bots using the bot API.
The first step for bots is enable payments as described here ».
Then, we work with payments as follows.
inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;
invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
---functions---
messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> schedule_date:flags.10?int = Updates;
The user contacts the bot and requests to purchase something.
The bot forms an inputMediaInvoice with an invoice constructor with a description of the goods or service, amount to be paid, as well as requested shipping info.
The provider
parameter of the inputMediaInvoice constructor is where you put the token value that you've obtained earlier via Botfather. It is possible for one merchant bot to use several different tokens for different users or different goods and services.
Use the messages.sendMedia method to send the invoice.
You can also attach an inline keyboard to the message using the reply_markup
field: if provided, the first button must be a keyboardButtonBuy button. Otherwise, an inline keyboard will be generated automatically, with a Pay 'total price'
keyboardButtonBuy as only button.
An invoice message with a pay button can only be sent to a private chat with the user. Groups and channels are not supported.
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
webDocument#1c570ed1 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
The user receives an updateNewMessage constructor from the bot, containing a messageMediaInvoice constructor with basic info about the product.
The message will also have a replyInlineMarkup keyboard attached to it. The the first button of the keyboard will always be a keyboardButtonBuy button.
invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;
paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;
paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
---functions---
payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm;
If the user clicks on the keyboardButtonBuy button, the client proceeds to call payments.getPaymentForm with the message ID of the invoice preview message to get the payment form.
The returned form will contain fields that should be passed to the payment provider along with the full invoice. The payment form also contains info about previously saved payment credentials and order information (name, phone number, email, shipping address & so on).
The full invoice contains info about the information required for the order, the price and the currency, and whether this is a test
order.
invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector<LabeledPrice> = Invoice;
postAddress#1e8caaeb street_line1:string street_line2:string city:string state:string country_iso2:string post_code:string = PostAddress;
paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;
payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = payments.ValidatedRequestedInfo;
---functions---
payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
If any data at all is requested by the invoice (name_requested
, phone_requested
, email_requested
, shipping_address_requested
), the user must call payments.validateRequestedInfo, providing the required data (as usual, msg_id
is the ID of the invoice message).
The user can choose to save order information for future use by setting the save
flag.
Data can be autofilled as described in autofill.
If no errors are found in the submitted info, the response of the method will contain an id
flag, to be used later to complete the payment.
If the flexible
flag of the invoice is set, calling the payments.validateRequestedInfo method will send a shipping query update to the bot, to which the bot will reply with the available shipping options for the specified address as described here ».
The return value in this case will also contain a shipping_options
field with the available shipping options.
If any errors are found in the submmitted data, a service notification will be sent to the user, with a description of the error from the bot.
payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo;
---functions---
payments.getSavedInfo#227d824b = payments.SavedInfo;
payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
The requested fields can be autofilled with the info provided in the saved_info
field of the payment form, or with the info fetched manually using payments.getSavedInfo.
Saved order information can also be cleared using payments.clearSavedInfo.
labeledPrice#cb296bf8 label:string amount:long = LabeledPrice;
shippingOption#b6213cdf id:string title:string prices:Vector<LabeledPrice> = ShippingOption;
updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;
---functions---
messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?string shipping_options:flags.1?Vector<ShippingOption> = Bool;
If a shipping address was requested and the bot included the parameter flexible
, when the user validates order information the Telegram API will send an updateBotShippingQuery to the bot.
The bot must respond using messages.setBotShippingResults either with a list of possible delivery options and the relevant delivery prices, or with an error (for example, if delivery to the specified address is not possible).
The returned shipping options or the shipping error will be returned to the user while validating order information.
inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
Typically, payment takes place by opening the url
in the specified payment form, which leads to a payment form on the website of the payment gateway.
Once the user finishes entering their payment credentials, a payment_form_submit
web event is generated by the payment gateway, containing data
and title
JSON fields.
The title
is used by the client app to represent the payment credentials (typically a censored version of credit card information).
The data
is used to generate an inputPaymentCredentials constructor.
Eventually, you can set the save
flag to save the credit card info for future use, only if 2FA is enabled.
Telegram does not have access to your card information. Credit card details will be handled only by the payment system.
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
Most telegram apps support working natively with the native APIs of some payment providers, without opening the website of the payment and receiving a JS event.
This is done using the JSON native_params
parameters field of the payments.paymentForm constructor, which contains an object, which can contain one or more of the following fields:
publishable_key
: Stripe API publishable keyapple_pay_merchant_id
: Apple Pay merchant IDandroid_pay_public_key
: Android Pay public keyandroid_pay_bgcolor
: Android Pay form background colorandroid_pay_inverse
: Whether to use the dark theme in the Android Pay formneed_country
: True, if the user country must be provided,need_zip
: True, if the user ZIP/postal code must be provided,need_cardholder_name
: True, if the cardholder name must be providedThe payment gateway to use is decided based on the value of the native_provider
field.
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
If the native_provider
field is set and equal to stripe
, the client can make use of the native Stripe token APIs with the publishable_key
from the native_params
to add a payment method to Stripe, and then use the token type
and id
to generate a JSON object:
{"type":"token.type", "id":"token.id"}"
The generated JSON object can then be passed to the data
field of the inputPaymentCredentials.
Eventually, you can set the save
flag to save the credit card info for future use, only if 2FA is enabled.
Telegram does not have access to your card information. Credit card details will be handled only by the payment system.
Example implementation: Telegram for Android.
inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
On iOS devices, Apple Pay can be used to generate payment data, which is then sent using the inputPaymentCredentialsApplePay constructor.
Example implementation: Telegram for iOS.
inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;
On Android devices, Google Pay can be used to generate payment data, which is then sent using the inputPaymentCredentialsAndroidPay constructor.
Example implementation: Telegram for Android.
inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector<User> = payments.PaymentForm;
account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword;
---functions---
account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;
To reuse saved payment methods, the saved_credentials
field of the payment form is used.
The title
of the paymentSavedCredentialsCard can be used to preview a censored version of credit card info.
The id
field is provided by the payment provider directly to the Telegram servers when saving the payment method, and identifies the payment method.
Full credit card info is not saved on Telegram Servers, and cannot be fetched by the user.
In order to use the saved payment method, 2FA must be enabled: the user must verify their identity by entering their 2FA password, which is then used as described in the SRP docs to generate SRP parameters which must be passed to account.getTmpPassword.
The generated temporary password can then be used to make payments using the saved credentials using the inputPaymentCredentialsSaved constructor.
id
field is the paymentSavedCredentialsCard id
.tmp_password
is the temporary payment password generated by the server, if the user provided a correct 2FA password.Example implementation: Telegram for Android.
inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials;
inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials;
inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials;
payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult;
payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult;
---functions---
payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult;
After verifying order information, the final step for the client is to call payments.sendPaymentForm, with the following parameters:
msg_id
is set to the ID of the invoice messagerequested_info_id
is set to the id
of the verified order information, if it was requestedshipping_option_id
is set to the selected delivery option, if shipping was requested.credentials
are the payment credentials generated by the payment provider, required to complete the order.Payment method info can also be saved to the Telegram Servers and reused, by setting the save
flag of inputPaymentCredentials when sending the form.
This is only possible on accounts with 2FA enabled.
The bot then replies to the received precheckout query, finally the user proceeds to checkout.
Please note that if the result of the method is a payments.paymentVerificationNeeded, before proceeding to checkout the payment provider requires the user to verify his identity by opening the provided url
and following instructions.
Once the user finishes working with the webpage, the client can proceed to checkout.
Eventual errors are returned in the form of RPC errors, with the description of the error by the bot contained in service updates.
paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string email:flags.2?string shipping_address:flags.3?PostAddress = PaymentRequestedInfo;
updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;
---functions---
messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool;
The user enters their payment information as described above and presses the final pay button. At this moment the Telegram API sends an updateBotPrecheckoutQuery constructor that contains all the available information about the order to the bot. The bot must reply using messages.setBotPrecheckoutResults within 10 seconds after receiving this update or the transaction is canceled.
The bot may return an error if it can‘t process the order for any reason. We highly recommend specifying a reason for failure to complete the order in human readable form (e.g. "Sorry, we’re all out of rubber ducks! Would you be interested in a steel bear instead?"). Telegram will display this reason to the user.
keyboardButtonBuy#afd93fbb text:string = KeyboardButton;
keyboardButtonRow#77608b83 buttons:Vector<KeyboardButton> = KeyboardButtonRow;
replyInlineMarkup#48a30254 rows:Vector<KeyboardButtonRow> = ReplyMarkup;
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector<RestrictionReason> = Message;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector<User> = payments.PaymentReceipt;
---functions---
payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt;
In case the bot confirms the order, Telegram requests the payment provider to complete the transaction. If the payment information was entered correctly and the payment goes through, the Telegram API will modify the invoice message and send a service message as described below. Once your bot receives this message, it should proceed with delivering the goods or services purchased by the user.
If all is OK, the user receives a payments.paymentResult in reply to the payments.sendPaymentForm query, containing info about the updated invoice message in the form of an updateEditMessage.
The invoice message will be updated as follows: the attached messageMediaInvoice will now have a receipt_msg_id
field.
Clients should treat invoice messages with a receipt_msg_id
field as receipt messages, locally modifying the label of the keyboardButtonBuy button to a localized version of the word Receipt
.
From this point, clicking on the Receipt
button should trigger a call to payments.getPaymentReceipt, providing the receipt_msg_id
to the msg_id
field, which will return info about the transaction.
The payment will also generate one service message of type messageActionPaymentSent or messageActionPaymentSentMe, replying to the invoice. For bots, the service message will be of type messageActionPaymentSentMe, for users it will be a messageActionPaymentSent.
messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction;
messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction;