Необходимость добавить возможность оплаты чего-либо в своём проекте всплывает достаточно часто, при этом возня с ИП, банковскими договорами и прочей бюрократией мало кого привлекает, особенно если масштабы проекта сопоставимы с небольшим telegram-ботом или чем-то подобным. На помощь приходят такие сервисы как QIWI, ЮMoney и другие (не рекламирую, просто нахожу удобным для себя).

Подход прочитать документацию API такого сервиса, написать небольшой модуль и использовать в своих проектах - лучший путь, но начинающие программисты зачастую находят это нудным, сложным и ищут простое готовое решение. Так и я решил когда-то и не нашёл, а теперь вместо переписывания одного модуля по 100 раз решил собрать небольшую библиотеку для быстрой интеграции платежей QIWI.

Установка

В терминале пишем команду и ожидаем конца загрузки:

pip install PyEasyQiwi
Исходный код GitHub
Пакет PyPi

Создание платежа

Для начала импортируем класс для создания соединения с QIWI аккаунтом:

from PyEasyQiwi import QiwiConnection

Конструктор класса принимает один аргумент - ваш секретный API ключ, для начала нам понадобится аккаунт QIWI со статусом "Основной", так как сервис запрещает проводить анонимные платежи. Авторизуемся на сайте:

Далее в верхней панели переходим по вкладкам: Ещё > Приём переводов

Нажимаем "Начать приём платежей" и в верхней панели переходим в создание API ключей

Настраиваем новую пару ключей, дав ей любое название:

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

Теперь, когда мы получили API ключ, вернёмся к коду.

Создадим подключение указав наш секретный ключ:

api_key = "your_api_key"
conn = QiwiConnection(api_key)

Теперь мы можем использовать экземпляр класса для управления приёмом платежей, начнём с создания счёта на оплату для нашего пользователя, используем метод conn.create_bill()

pay_url, bill_id, response = conn.create_bill(value=1000.00, description="User1")
print(pay_url)

>>> https://oplata.qiwi.com/form/?...

pay_url - строка содержащая ссылку для оплаты (для отправки клиенту или редиректа)

bill_id - строка содержащие полное наименование счёта на оплату, состоит из текста указанного в conn.create_bill(..., description="User1") и полной даты создания платежа. (для идентификации каждого отдельно взятого платежа, фактически его ID в вашей базе данных)

response - словарь содержащий более подробную информацию о деталях созданного платежа (в некоторых случаях полезно, например для создания подробных логов)

Теперь перейдём по ссылке в pay_url и увидим страницу с формой для оплаты любым удобным из доступных методов:

Так же все операции проведённые через API отобразятся в сервисе:

Конечно, наш метод позволяет более гибко настроить то, что пользователь увидит на данной странице, разберём по порядку все доступные опции:

conn.create_bill(value, currency, delay, description, theme_code, comment, qiwi_pay, card_pay)

  1. value - Обязательный аргумент, сумма для оплаты. Например 100.59. Допустимые значения 1-1000000 (QIWI ограничивает транзакции на сумму более 1000000 рублей).

  2. currency - Валюта для проведения платежа. По умолчанию RUB, доступны варианты "RUB" и "KZT".

  3. delay - Срок действия платежа в минутах. По умолчанию 15 минут. Устанавливает максимальный временной промежуток для оплаты пользователем, после чего закрывает возможность оплаты.

  4. description - Описание платежа, по умолчанию - unknown. Используется для более точной идентификации каждого платежа. Разумно использовать ID пользователя из вашей базы данных.

  5. theme_code - Уникальный код оформления страницы платежа. По умолчанию - пустое значение. Подробнее - далее в статье.

  6. comment - Комментарий который увидит пользователь при оплате. По умолчанию - пустая строка.

  7. qiwi_pay - Возможность оплаты с кошелька QIWI для пользователя. По умолчанию True.

  8. card_pay - Возможность оплаты банковской картой для пользователя. По умолчанию True.

Более полный пример создания платежа

Для начала разберёмся с параметром theme_code:

Этот параметр позволяет задать своё (на сколько это возможно) оформление страницы оплаты, и сервис QIWI предлагает небольшой инструмент для настройки кода персонализации: перейдя по ссылке настраиваем всё по своему желанию и сохраняем наш код, позже передадим его в качестве аргумента.

Теперь когда у нас есть всё необходимое попробуем создать новый счёт на оплату, используя всё тот же метод conn.create_bill():

value = 3010.11
currency = "RUB"
delay = 30
description = "test_user_ID"
theme_code = "my_code"
comment = "Привет Хабр!"
qiwi_pay = False
card_pay = True


pay_url, bill_id, response = conn.create_bill(value, currency, delay, description, theme_code, comment, qiwi_pay, card_pay)
print("Ссылка для оплаты: ", pay_url)

>>> https://oplata.qiwi.com/form/?..............

Проверяем, мы отключили оплату с кошелька QIWI, установили код персонализации и добавили комментарий:

Проверка статуса платежа

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

В качестве примера возьмём счёт на оплату созданный выше, как вы помните метод conn.create_bill() вернул нам bill_id , он нам и понадобится для отслеживания статуса.

status, response = conn.check_bill(bill_id)
print(status)

>>> WAITING 

status - строка содержащая статус платежа: WAITING - счёт ожидает оплаты, PAID - счёт ожидает оплаты, REJECTED - счёт отклонён, EXPIRED - срок оплаты просрочен (Используется для проверки статуса оплаты)

response - словарь с более подробной информацией о счёте

Теперь у нас есть точное понимание, в каком состоянии находится выставленный счёт, как использовать это в своих проектах - решать вам, простейший пример использования:

status, response = conn.check_bill(bill_id)

if status == "PAID":
  # Пользователь оплатил
else:
  # Пользователь не оплатил

Отмена выставленных платежей

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

Для этого нам всё так же понадобится ID счёта, в нашем случае bill_id и новый метод conn.remove_bill()

conn.remove_bill(bill_id)

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

Так же при проверке статуса платежа будет понятно, что он уже недействителен:

status, response = conn.check_bill(bill_id)
print(status)

>>> REJECTED 

Оплаченный или просроченный счёт закрывать не требуется:

Для ленивых

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

Код
from PyEasyQiwi import QiwiConnection

# Создаём подключение
api_key = "your_api_key"
conn = QiwiConnection(api_key)

# Создаём счёт на оплату
value = 500.01
currency = "RUB"
delay = 30
description = "test_user_ID"
theme_code = "my_code"
comment = "PyEasyQiwi"
qiwi_pay = False
card_pay = True


pay_url, bill_id, response = conn.create_bill(value, currency, delay, description, theme_code, comment, qiwi_pay, card_pay)
print("Ссылка для оплаты: ", pay_url)


# Проверяем статус платежа
status, response = conn.check_bill(bill_id)
print("Текущий статус платежа:", status)

# Закрываем счёт на оплату
conn.remove_bill(bill_id)
print("Счёт закрыт!")

Заключение

На этом всё, буду рад если найдёте это полезным для себя. Адекватная критика всегда приветствуется, это мой первый опыт написания статей, так что не судите строго, старался для людей! Ссылки на документацию ниже:

Документация

Комментарии (9)


  1. photocritic
    09.01.2023 22:50
    +1

    Небеса тебя послали ко мне с этим!


    1. Kur_up Автор
      09.01.2023 22:50
      +1

      Буду рад помочь, если есть вопросы!


  1. igor6130
    10.01.2023 00:14
    +1

    Пара соображений по коду.

    Часто встречаются строки типа

    if ...:
        pass
    else:
        raise ...

    Если срабатывание на основное условие никак не обрабатывается, то можно выкинуть его вообще, написав от обратного
    if not ...:
        raise ...

    Также вы никак не обрабатываете возможные проблемы при запросах к API. Что будете делать, если при
    response = session.put(create_url, data=parameters)
    
    удаленный сервер упадет или вернет что-то что нельзя спарсить как JSON?

    И еще сомнительно использовать round() при формировании тела запроса. Получается, что программа решает за человека, какую сумму выставить, корректируя ее на свое усмотрение. Если есть необходимость ограничить данные двумя знаками после запятой, на это следует сделать проверку. И в случае несоблюдения условия, сообщать об этом пользователю (разработчику) и завершать работу. Так вижу.

    Все if-конструкции в support_module.py можно заменить на
    values = datetime.datetime.utcnow()
    datetime_link = f":{values:%y-%m-%d-%H-%M-%S}-UTC"


    1. Kur_up Автор
      10.01.2023 12:12

      Спасибо за замечание!

      if ...:
          pass
      else:
          raise ...

      Касательно этого - сказал бы лишь, что читаемость кода для меня так выше и лучше, не кричу что кто-то прав, а кто-то нет. Но делал для себя в основном, условно на коленке))))

      В остальном вы безусловно правы, приму во внимание и поправлю!

      Спасибо!


  1. igor6130
    10.01.2023 00:57
    +1

    Вот вам новая, более лучшая версия support_module.py. Не благодарите. :)

    from datetime import datetime, timedelta, timezone
    
    
    def create_datetime_link_and_expiration(delay):
        values = datetime.utcnow().replace(microsecond=0, tzinfo=timezone.utc)
        datetime_link = f":{values:%Y-%m-%d-%H-%M-%S}-UTC"
        datetime_expiration = values + timedelta(minutes=delay)
        datetime_expiration = datetime_expiration.isoformat()
        return datetime_link, datetime_expiration


  1. fedorov0av
    10.01.2023 15:15
    +1

    Может кому-то пригодится...
    Есть уже хорошо задокументированная библиотека.
    https://github.com/GLEF1X/glQiwiApi
    Она активно развивается. Есть чатик в телеграмме, где можно пообщаться с разработчиком.
    Также она полностью асинхронна, что будет полезно, если вы используете в своих проектах какие нибудь асинхронные фреймворки, к примеру aiogram.

    Прошу обратить внимание еще на то, что следуя из документации по P2P платежам Qiwi, в заголовке запроса параметр referer не должен быть пустой, иначе повышается вероятность блокировки Вашего кошелька.
    А с переходов по прямой ссылке типа "https://oplata.qiwi.com/form/?..............":

    • из мессенджера

    • из смс

    • из письма

    • из адресной строки

    • из браузера с повышенным режимом приватности или расширениями для приватного просмотра

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


  1. Tigro11
    10.01.2023 15:34

    Крайне не рекомендую использовать киви. Это мошенники. Технология отъема денег проста. Вы заведете аккаунт на киви, подтвердите его через госуслуги и станете идентифицированным. Казалось бы все окей и можно пользоваться. Но при первом же переводе вам кошелек заблокируют. И сумма не важна. Блочат даже при получении 1000 рублей. Дальше у вас потребуют фото с паспортом, договор с сотовой на номер указанный в киви и экономическое обоснование получения тысячи рублей. Если что то не понравится киви то кошелек останется заблокированным, закрыть его нельзя и вывести деньги тоже. Через год начнут снимать деньги за неактивность и кошелек обнулят. Решить проблему невозможно. В чате операторы просто футболят вас на ботов. Учитывайте это


    1. Kur_up Автор
      10.01.2023 15:42

      Честно говоря не сталкивался с блокировкой из-за сумм близких к 1000, я вполне спокойно проводил там операции и всё было нормально, сталкивался с блокировкой счёта с большой суммой, но это уже другая история которая в итоге закончилась положительно. Посыл автора этого комментария, что хранить там деньги идея плоха, вполне справедлив. Поэтому для микро-проектов, не более. Ну и в конце концов этот материал кому-то поможет сделать своё лучше или понять как делать не надо))))


  1. mrkaban
    11.01.2023 10:14

    Я только что понял, что для меня легче воспринимаются подобные инструкции из кода с комментариями, нежели из инструкции step-by-step.