Часть 3. TMA на KMP. Как платить через Telegram Mini Apps

c0207e3f1d43e2a871eec84b6eb7c516.png

Эта заключительная статья смешивает в себе как часть на Kotlin, самого Web приложения, и на Python, собственно реализация оплаты. Для лучшего понимания рекомендуется прочитать предыдущие части.

Навигация по циклу статей:

Часть 1. Пишем веб-приложение кликер на Kotlin
Часть 2. Пишем кликер для Telegram на Kotlin
Часть 2.5. Аутентификация пользователя с DRF
Часть 3. Добавляем оплату через Telegram Mini Apps на Kotlin — текущая статья

Раскрытые темы в цикле

  • Web приложение на Kotlin — часть 1

  • Интеграция приложения с Telegram Mini Apps — часть 2

  • Работа с элементами интерфейса TMA приложения. Тема,  MainButton,  BackButton — часть 2

  • Поделиться ссылкой на приложение через Telegram. Передача данных через ссылку — часть 2

  • Аутентификации через TMA приложение — часть 2 и 2.5

  • Telegram Payments API– часть 3

Подключение Telegram Payments API

Telegram Payments API самостоятельно не принимает платежи, он лишь может получить данные о доставке и пользователе и отправить их в бот вместе со статусом оплаты.

Для работы с платежами потребуется подключить один из провайдеров:

  1. Возвращаемся в BotFather к нашему боту и настраиваем его Payments >> Paymaster >> Connect Paymaster Test.

  2. Выбираем любой интересующий вас провайдер. Для проверки рекомедуем «Paymaster: тест»

  3. Нас перебрасывает в бот «Paymaster: тест», который подключит наш бот к своей системе.

  4. Теперь возвращаемся к BotFather, который отправляет нам токен.

Можно выбрать любой другой провайдер, однако именно Paymaster прост для тестового запуска, поскольку не требует никакой сторонней регистрации.

Теперь мы можем принимать оплату через наш бот, осталось только его настроить на это. Архитектура оплаты из TMA приложения проста:

  1. Приложение отправляет на сервер запрос на сервер.

  2. Сервер генерирует ссылку для оплаты и отправляет её TMA приложение

  3. Клиент открывает экран оплаты webApp.openInvoice(url)

  4. После нажатия «Оплатить» в бот посылается событие pre_checkout_query, на которое он должен в течение 10 секунд ответить

  5. Если бот разрешает оплату, она проходит и в бот приходит новое событие об успешной оплате successful_payment

Если вы используете другой провайдер, то этапы могут измениться

Отправляем запрос на оплату

Добавим в наш кликер премиум аккаунты, чтобы лучше кликалось. Создаём кнопку, по нажатию на которую будет отправляться запрос на сервер на генерацию ссылки и открываться меню для оплаты после получения ответа. После оплаты будем показывать сообщение со статусом оплаты.

@Composable
fun PayButton() {
    val coroutineScope = rememberCoroutineScope()
    Button(
        attrs = {
            classes(ClickerStyles.PayButton)
            onClick {
                coroutineScope.launch {
                    val link = TapClient.fetchUpgradeLink()
                    webApp.openInvoice(link) {
                        when (it.statusTyped) {
                            InvoiceStatus.Cancelled -> webApp.showAlert(message = "Оплата отменена")
                            InvoiceStatus.Failed -> webApp.showAlert(message = "Ошибка оплаты")
                            InvoiceStatus.Paid -> webApp.showAlert(message = "Оплата принята")
                            else -> {}
                        }
                    }
                }
            }
        }
    ) {
        Text("Upgrade")
    }
}

Настройка сервера на генерацию ссылки

Теперь вернёмся к нашему серверу. Именно здесь проходит основная работа по оплате.

Теперь можно установить библиотеку для работы с Telegram Bot API и добавить в настройки наш токен для провайдера оплаты.

pip install pyTelegramBotAPI

Почему именно эта библиотека, а не IOGram или python-telegram-bot? Ответ: она может работать в синхронных функциях. По хорошему пора переходить на async, но пока DRF поддерживает (хотя есть и ADRF), используем что имеем.

Поскольку мы используем ngrok, у нас уже есть https ссылка, следовательно нам доступна возможность использовать WebHooks.

Для начала создадим новое приложение для нашего проекта, ненужные файлы можно удалить.

python manage.py startapp bot

Изображение выглядит как текст, снимок экрана, Шрифт, дизайн Автоматически созданное описание

Изображение выглядит как текст, снимок экрана, Шрифт, дизайн Автоматически созданное описание

Далее в bot.py создадим сам объект бота, который будут использовать остальные части приложения.

import telebot
from django.conf import settings

bot = telebot.TeleBot(settings.TELEGRAM_API_TOKEN, parse_mode="HTML")

Теперь мы можем импортировать экземпляр нашего бота из Django приложения и с его помощью создать ссылку на оплату через bot.create_invoice_link

class UpgradeAPIView(APIView):
    authentication_classes = [TMAAuthentication]
    permission_classes = [IsAuthenticated]

    def post(self, request: Request):
        if request.user.is_premium:
            raise Exception() # Обработать логику, если пользователь уже имеет премиум
        return Response(
            data={
                'invoice_link': bot.create_invoice_link(
                    title='Оплата премиум статуса',
                    description='С премиумом тапается лучше! Проложи дорогу к криптоинвестициям',
                    payload=f'user_{request.user.id}',
                    provider_token=settings.TELEGRAM_PAYMENTS_PROVIDER_TOKEN,
                    currency='RUB',
                    prices=[LabeledPrice(label='Премиум статус', amount=5000)],
                    need_name=True,
                    need_email=True,
                    need_phone_number=False,
                    need_shipping_address=False,
                )
            }
        )

Теперь по нажатию на кнопку «Upgrade» открывается меню оплаты

Для справки: поскольку используется тестовый провайдер, деньги списываться не будет. Можно использовать тестовые данные карты.
Номер карты: 4242 4242 4242 4242
Дата истечения: любая будущая
CVV: любой

Изображение выглядит как текст, снимок экрана, мультимедиа, программное обеспечение Автоматически созданное описание

Изображение выглядит как текст, снимок экрана, мультимедиа, программное обеспечение Автоматически созданное описание

Подробнее о возможностях настройки заказа в документации библиотеки и Telegram.

Обработка подтверждения оплаты.Обработка подтверждения оплаты.

Однако сейчас оплата проходить не может, нужно её подтвердить в течение 10 секунд уже через бот. На протяжении всего цикла мы ни разу не запустили бота, именно в этот момент мы первый раз и его и запустим.

Создадим обработчик post запросов, который будет направлять содержимое запросов в bot

# многое не обработно, не используйте код в таком виде
@require_POST
@csrf_exempt
def tg_webhook(request):
    update = telebot.types.Update.de_json(request.body.decode('utf-8'))
    bot.process_new_updates([update])
    return HttpResponse(status=200)

Осталось подключить этот запрос по какому либо пути в urls.py

urlpatterns = [
    path('tg-webhook/', views.tg_webhook)
]

Установим WebHook на старте нашего приложения в apps.py

class BotConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'bot'

    def ready(self):
        if 'runserver' not in sys.argv:
            return True
        from bot.bot import bot
        bot.set_webhook(settings.WEBHOOK_URL) # WEBHOOK_URL берётся из конфига Django.

Добавим обработчик pre_checout_query в bot.py , который будет проверять, не куплен ли премиум у аккаунта, для которого совершается оплата. get_user_id_from_payload получает user_id, который мы передавали через payload в момент создания ссылки

# ...
# don’t use in real code, it is example
@bot.pre_checkout_query_handler(lambda query: True)
def pre_checkout_query_handler(query: PreCheckoutQuery):
    id = get_user_id_from_payload(query.invoice_payload)
    if id is None:
        return bot.answer_pre_checkout_query(pre_checkout_query_id=int(query.id), ok=False)
    user = TelegramUser.objects.get(id=id)
    if user.is_premium:
        return bot.answer_pre_checkout_query(pre_checkout_query_id=int(query.id), ok=False,
                                             error_message='User already has premium')
    return bot.answer_pre_checkout_query(pre_checkout_query_id=int(query.id), ok=True)
# ...

Всё готово, теперь оплата будет проходить или завершаться с ошибкой, если статус уже куплен. Осталось только обработать успешную оплату.

После успешной оплаты бот получает другое событие. Это сообщение типа successful_payment. Мы можем обрабатывать такие сообщения и присваивать пользователю премиум статус.

@bot.message_handler(content_types=['successful_payment'])
def successful_payment_handler(message: Message):
    id = get_user_id_from_payload(message.successful_payment.invoice_payload)
    if id is None:
        return
    user = TelegramUser.objects.get(id=id)
    user.is_premium = True
    user.save()

Итоги

Это завершающая статья из цикла Telegram Mini Apps на Kotlin. В нём мы разобрали все этапы разработки TMA кликера. Однако стоит предупредить, что это лишь код для примера. В реальном приложении нужно использовать более сложную логику как в клиентском приложении, так и в серверной части.

TMA — это способ расширить возможности существующего веб-приложения. Интеграция в нём позволяет получить доступ к большому количеству функций, которые не доступны обычному веб-приложению.

Это не весь список доступного API, однако то, что будет чаще всего использоваться в нашем цикле представлено, и если бизнес поймёт важность приложений на TMA, то это направление начнёт очень сильно развиваться, хотя пока выбран не самый удобный способ взаимодействия — через бот.

© Habrahabr.ru