WhiteApfel / pyQiwiP2P

Simple wrapper for comfortable work with the API of payment system for individuals QiwiP2P
Mozilla Public License 2.0
54 stars 12 forks source link
api api-wrapper asyncio payments qiwi qiwip2p

⚠️ Важное уведомление [2023.07.25]

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

pyQiwiP2P

CodeFactor

Поддержка прекращена

Есть более качественный инструмент, полностью покрывающий функции pyQiwiP2P: glQiwiApi

Возможно, здесь когда-нибудь будет гайд по миграции на него, но всё в руках сообщества

О библиотеке

Штучка для удобной работы с кивишной апишкой платежей

Есть типа документация, но в ней есть и косячки, поэтому, если найдёте таковой, обязательно сообщите мне. Буду искренне рад. Правда. Спасибо.

Миграция с первой версии:

  1. Свойство Bill.actual было удалено из-за PEP8
  2. QiwiNotify по умолчанию выполняет только функцию по первому подошедшему хендлеру

⚠️ Важное уведомление [2023.04.05]

⛔️ Способ из этого уведомления (временно) не работает [2023.07.22]

Выпуск ключей для приёма P2P платежей через сайт официально закрыт, но метод, заявленный в документации QIWI, продолжает работать, хоть и с несколько изменённой авторизацией.

Инструкция:

  1. Переходим на https://qiwi.com/p2p-admin
  2. Авторизуемся в свой Qiwi аккаунт
  3. Открываем консоль браузера. Как?
  4. Вставляем код из первого блока и нажимаем enter
  5. Вставляем код из второго блока и нажимаем enter
  6. Если прошло успешно:
    • появится строка "Private Key: "
    • копируем ключ, и используем по назначению
  7. Если что-то пошло не так:
    • смириться, что накосячили
    • меня не тревожить
    • мне в личку не писать
    • кусаюсь и кидаю в чс
function createKeys(name, notification_url) {
    let p2pApiData = JSON.parse(localStorage.getItem("p2p-admin-checkout-oauth-token-head"))
    let token = btoa(p2pApiData["client_id"] + ":" + p2pApiData["access_token"]).replaceAll("=", "")

    let body = {
        keysPairName: name
    }

    if (notification_url) {
        body.serverNotificationsUrl = notification_url
    }

    fetch('https://edge.qiwi.com/widgets-api/api/p2p/protected/keys/create', {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'Authorization': "TokenHeadV2 " + token
        },
        body: JSON.stringify(body),
        credentials: 'include',
    })
    .then(response => {
        console.log(response.status);
        return response.json();
    })
    .then(data => {
        console.log(data);
        console.log("Private Key: "+ data.result.secretKey)
    })
    .catch(error => console.error(error));
}
createKeys("Tokens by WhiteApfel")
// or
createKeys("Tokens by WhiteApfel", "https://qiwi.example.com/any")

⚠️ Важное уведомление [2021.06.13]

С июня Qiwi начала блокировать кошельки, если пользователь открыл страницу оплаты "напрямую", тем самым не передав заголовок referer.

Это случается при открытии ссылки:

Для обхода всех проблем, кроме последней (она не решается), к объекту Bill был добавлен атрибут Bill.alt_url, который предоставляет ссылку для перенаправления на страницу оплаты через специальную страницу-прокладу, добавляющую этот самый referer.

Страница предоставлена мною, но её можно поднять на своём сервере с помощью docker-контейнера.


А как пользоваться

Что есть?

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

Пример использования:

from pyqiwip2p import QiwiP2P
from pyqiwip2p.p2p_types import QiwiCustomer, QiwiDatetime, PaymentMethods

QIWI_PRIV_KEY = "abCdef...xYz"

p2p = QiwiP2P(auth_key=QIWI_PRIV_KEY)

# Выставим счет на сумму 228 рублей который будет работать 45 минут
new_bill = p2p.bill(bill_id=212332030, amount=228, lifetime=45)

print(new_bill.bill_id, new_bill.pay_url)

# Проверим статус выставленного счета
print(p2p.check(bill_id=new_bill.bill_id).status)

# Потеряли ссылку на оплату счета? Не проблема!
print(p2p.check(bill_id=245532).pay_url)

# Клиент отменил заказ? Тогда и счет надо закрыть
p2p.reject(bill_id=new_bill.bill_id)

# Если планируете выставлять счета с одинаковой суммой,
# можно воспользоваться параметром default_amount
p2p = QiwiP2P(auth_key=QIWI_PRIV_KEY, default_amount=148)

# Теперь, если не указывать в методе p2p.bill() значение суммы заказа,
# будет применяться указанная базовая сумма
new_bill = p2p.bill(bill_id=6627358)

# А ещё можно не указывать bill_id, тогда значение сгенерируется автоматически.
# Его можно будет посмотреть в объекте ответа Bill
# В комбинации со стандартным значением суммы будет вот так
new_bill = p2p.bill()
print(new_bill.bill_id, new_bill.pay_url)

# Чтобы запретить приём платежей через какой-то метод оплаты, например, карты,
# необходимо передать paySourcesFilter в fields. Туда же можно передать themeCode

fields = {
    "paySourcesFilter": "qw,card",
    "themeCode": "MalchikGay",
}
p2p.bill(fields=fields)

# Либо же воспользоваться удобными полями

p2p.bill(pay_sources=[PaymentMethods.qiwi, PaymentMethods.card])
p2p.bill(pay_sources=[PaymentMethods.qiwi], theme_code="MalchikGay")

А асинхронно могёте?

Могём. Причём примерно так же.

from pyqiwip2p import AioQiwiP2P
from pyqiwip2p.p2p_types import QiwiCustomer, QiwiDatetime, PaymentMethods

QIWI_PRIV_KEY = "abCdef...xYz"

p2p = AioQiwiP2P(auth_key=QIWI_PRIV_KEY)

# Если планируете выставлять счета с одинаковой суммой,
# можно воспользоваться параметром default_amount
p2p = AioQiwiP2P(auth_key=QIWI_PRIV_KEY, default_amount=148)

async def main():
    async with p2p:
        # Выставим счет на сумму 228 рублей который будет работать 45 минут
        new_bill = await p2p.bill(bill_id=212332030, amount=228, lifetime=45)

        print(new_bill.bill_id, new_bill.pay_url)

        # Проверим статус выставленного счета
        print((await p2p.check(bill_id=new_bill.bill_id)).status)

        # Потеряли ссылку на оплату счета? Не проблема!
        print((await p2p.check(bill_id=245532)).pay_url)

        # Клиент отменил заказ? Тогда и счет надо закрыть
        await p2p.reject(bill_id=new_bill.bill_id)

        # Если не указывать в методе p2p.bill() значение суммы заказа,
        # будет применяться указанная базовая сумма, которую вы установили
        new_bill = await p2p.bill(bill_id=6627358)

        # А ещё можно не указывать bill_id, тогда значение сгенерируется автоматически.
        # Его можно будет посмотреть в объекте ответа Bill
        # В комбинации со стандартным значением суммы будет вот так
        new_bill = await p2p.bill()
        print(new_bill.bill_id, new_bill.pay_url)

        # Чтобы запретить приём платежей через какой-то метод оплаты, например, карты,
        # необходимо передать paySourcesFilter в fields. Туда же можно передать themeCode

        fields = {
            "paySourcesFilter": "qw,card",
            "themeCode": "MalchikGay",
        }
        await p2p.bill(fields=fields)

        # Либо же воспользоваться удобными полями

        await p2p.bill(pay_sources=[PaymentMethods.qiwi, PaymentMethods.card])
        await p2p.bill(pay_sources=[PaymentMethods.qiwi], theme_code="MalchikGay")

p2p.run(main())
# Аналог
# asyncio.run(main())

И всё?

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

Внимание! Для работы будет необходим проксирующий сервер (например, Nginx) с доверенным SSL-сертификатом. Да. Подробнее про требования к проксирующему серверу в документации Qiwi

А эта шайтан-машина нужна для захендлирования функций на события. Она не готова самостоятельно контактировать с внешним миром. Пожалей её.

Запросы сервер по умолчанию принимает на 8099 порту, его можно изменить, и только на /qiwi_notify - изменить нельзя.

from pyqiwip2p.notify import QiwiNotify
from pyqiwip2p.p2p_types import Bill

QIWI_PRIV_KEY = "abCdef...xYz"

qiwi_notify = QiwiNotify(QIWI_PRIV_KEY)

#
# Хэндлер принимает в себя аргументом функцию,
# в которую передаст объект счёта - Bill
#

# Добавим хэндлер, который будет печатать billID для всех счетов
@qiwi_notify.handler(lambda bill: True)
def print_bill(bill: Bill):
    print(bill.bill_id)

# Создадим хэндлер, который будет печатать сумму оплаченных счетов
@qiwi_notify.handler(lambda bill: bill.status == "PAID")
def print_bill(bill: Bill):
    print(bill.amount)

# Теперь запустим сервер на 12345'ом порту
qiwi_notify.start(port=12345)

И асинхронный сервер, наверное, у вас есть?

Да есть. Причём хендлить функции можно и асинхронные, и синхронные.

from pyqiwip2p import QiwiP2P, AioQiwiP2P
from pyqiwip2p.p2p_types import Bill
from pyqiwip2p.notify import AioQiwiNotify
import asyncio

QIWI_PRIV_KEY = "abCdef...xYz"

qiwi_notify = AioQiwiNotify(QIWI_PRIV_KEY)
p2p = AioQiwiP2P(auth_key=QIWI_PRIV_KEY)

@qiwi_notify.handler(lambda bill: bill.status == "EXPIRED")
async def on_expired(bill: Bill):
    new_bill = await p2p.bill(amount=bill.amount, comment=bill.comment)
    print(new_bill.pay_url)

@qiwi_notify.handler(lambda bill: True)
def on_all(bill: Bill):
    print(bill.status)

async def main():
    p = asyncio.get_event_loop()
    server = p.create_task(qiwi_notify.a_start(port=12345))
    await server

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

Настройка проксирующего Nginx

Для порта 12345 (как в примерах выше) будет:

server {
    listen 443;
    server_name qiwinotify.domain.com;
    ssl_certificate      cert.crt;
    ssl_certificate_key  pkey.key;
    location /superSecretQiwiURI {
        proxy_pass http://0.0.0.0:12345/qiwi_notify;
    }
}

В таком случае при генерации ключей API на https://qiwi.com/p2p-admin/transfers/api нужно будет указать https://qiwinotify.domain.com/superSecretQiwiURI в качестве URL для уведомлений

P.S. за неприходящие от Qiwi запросы ответственность не несу, как и за приходящие, кстати, тоже. Если запроса от Qiwi не было, то пишите им в поддержку @qiwi_api_help_bot