Интеграция оплаты Юкасса в telegramm для самозанятых

от автора

Привет тем кто хочет опробовать себя в качестве бизнесмена! Недавно в голову пришла идея, получить некоторый опыт предпринимательства. В качестве продукта выступает доступ к некоторой цифровой услуге, а контроль за оплатой этой услуги ложиться на плечи телеграмм бота. В ходе поисков системы оплаты была найдена Юкасса, одна из немногих систем (если вообще не единственная), которая работает с самозанятыми.

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

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

Интеграция для самозанятых

Реализовать оплату услуг самозанятого можно через сайт юкассы. Я это реализовал следующим образом: клиент запрашивает в боте услугу, в ответ ему приходит ссылка на оплату, клиент переходит и оплачивает товар, после чего его перенаправляет обратно в бот. Обработка же платежа работает следующим образом: как только создается ссылка на оплату, бот запрашивает статус платежа, до тех пор пока статус является «pending». Как только статус меняется на «succeeded» бот выполняет действие (отправляет товар/оказывает услугу и тд).

Для реализации такой схемы необходим модуль yookassa откуда мы возьмем классы Configuration и Payment. Далее необходимо заполнить два поля класса Configuration: Configuration.account_id и Configuration.secret_key , в первый записываем id магазина, во второй api ключ магазина.

import json from yookassa import Configuration,Payment import config  Configuration.account_id = config.SHOP_ID Configuration.secret_key = config.SHOP_API_TOKEN

Далее с помощью метода Payment.create , необходимо создать объект платежа. При создании этот метод сам отправит данные в юкассу.

import json from yookassa import Configuration,Payment import config  Configuration.account_id = config.SHOP_ID Configuration.secret_key = config.SHOP_API_TOKEN  payment = Payment.create({     "amount": {         "value": сумма платежа,         "currency": "RUB"     },     "payment_method_data": {         "type": "bank_card"     },     "confirmation": {         "type": "redirect",         "return_url": "Ссылка, куда перенаправить после совершения платежа"     },     "capture": True,     "description": description })

Платеж создан, теперь необходимо получить id операции (понадобиться позже) и ссылку на оплату, которую мы и отправим пользователю. Для этого воспользуемся методом json(), который запросит данные по платежу на сервере юкассы и вернет их в формате json. Для удобства преобразуем json в словарь python :

import json from yookassa import Configuration,Payment import config  Configuration.account_id = config.SHOP_ID Configuration.secret_key = config.SHOP_API_TOKEN  payment = Payment.create({     "amount": {         "value": сумма платежа,         "currency": "RUB"     },     "payment_method_data": {         "type": "bank_card"     },     "confirmation": {         "type": "redirect",         "return_url": "Ссылка, куда перенаправить после совершения платежа"     },     "capture": True,     "description": description })         payment_data = json.loads(payment.json())   payment_id = payment_data['id']   payment_url = (payment_data['confirmation'])['confirmation_url']

Теперь можно отправить payment_url пользователю, по которому он сможет оплатить товар. Однако мы пока не знаем оплатил пользователь товар или нет. Для получения статуса платежа реализуем метод Payment.find_one(payment_id)).json(), которые найдет платеж по указанному id (который мы получили на прошлом шаге) и пришлет его статус в формате json. Далее мы будем опрашивать этот метод до тех пор пока статус платежа не измениться с pending на успешный / не успешный.

import json from yookassa import Configuration,Payment import config import time  Configuration.account_id = config.SHOP_ID Configuration.secret_key = config.SHOP_API_TOKEN  payment = Payment.create({     "amount": {         "value": сумма платежа,         "currency": "RUB"     },     "payment_method_data": {         "type": "bank_card"     },     "confirmation": {         "type": "redirect",         "return_url": "Ссылка, куда перенаправить после совершения платежа"     },     "capture": True,     "description": description })         payment_data = json.loads(payment.json())   payment_id = payment_data['id']   payment_url = (payment_data['confirmation'])['confirmation_url']      payment = json.loads((Payment.find_one(payment_id)).json())   while payment['status'] == 'pending': payment = json.loads((Payment.find_one(payment_id)).json()) time.sleep(время между опросами)   

Теперь при успешной оплате или таймауте операции (что-то около 15 минут), мы выйдем из цикла, однако здесь существует огромная проблема, с тем, что если вызывать эти методы из бота, бот будет заблокирован на весь период оплаты. Чтобы избежать подобного поведения будем использовать асинхронный sleep. А также реализуем логику оплаты в виде двух функций: создания и проверки статуса платежа

import json from yookassa import Configuration,Payment import config import asyncio  Configuration.account_id = config.SHOP_ID Configuration.secret_key = config.SHOP_API_TOKEN  def payment(value,description): payment = Payment.create({     "amount": {         "value": value,         "currency": "RUB"     },     "payment_method_data": {         "type": "bank_card"     },     "confirmation": {         "type": "redirect",         "return_url": "урл редиректа"     },     "capture": True,     "description": description })  return json.loads(payment.json())  async def check_payment(payment_id): payment = json.loads((Payment.find_one(payment_id)).json()) while payment['status'] == 'pending': payment = json.loads((Payment.find_one(payment_id)).json()) await asyncio.sleep(3)  if payment['status']=='succeeded': print("SUCCSESS RETURN") print(payment) return True else: print("BAD RETURN") print(payment) return False

Теперь посмотрим как это может выглядеть в боте (aiogram)

import json from aiogram import Bot, Dispatcher, executor, types, filters from yookassa import Configuration,Payment import payment import config   API_TOKEN = config.API_TOKEN BASE=config.BASE REFBASE=config.REFBASE CURRENT_ENDPOINT=config.CURRENT_ENDPOINT   #--- buttons --- btnbuy = types.KeyboardButton("купить") btnprologue = types.KeyboardButton("продлить") btnstatus = types.KeyboardButton("статус") btnsinfo = types.KeyboardButton("инфо")  mainmenu=types.ReplyKeyboardMarkup(resize_keyboard = True).add(btnbuy,btnprologue,btnstatus,btnsinfo)  # Initialize bot and dispatcher bot = Bot(token=API_TOKEN) dp = Dispatcher(bot)  @dp.message_handler(commands=['start']) async def send_welcome(message: types.Message): await message.answer("Hello", reply_markup = mainmenu)   @dp.message_handler(commands=['info']) async def info(message: types.Message): await message.answer("info", disable_web_page_preview=True)  @dp.message_handler(commands=['buy']) async def BuyVPNforMonth(message: types.Message): await message.answer(msg.buy_message) payment_deatils = payment.payment(100,'Купить товар1') await message.answer( (payment_deatils['confirmation'])['confirmation_url'] ) if await payment.check_payment(payment_deatils['id']):     message.answer("платеж")  else: message.answer("платеж не прошел")  @dp.message_handler() async def menu_message(message: types.Message): if message.text == "купить": await BuyVPNforMonth(message) if message.text == "инфо": await info(message)  if __name__ == '__main__':     executor.start_polling(dp, skip_updates=True)

Пример работы бота, с интеграцией оплаты, можно посмотреть здесь


ссылка на оригинал статьи https://habr.com/ru/post/673646/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *