Версия 1.10, добавлены дополнительные проверки входных данных и подробный лог ошибок. Так же добавлена документация и примеры.

master
ssleg 2021-05-17 14:52:39 +03:00
parent 620304a3ea
commit 78049deffd
7 changed files with 343 additions and 50 deletions

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
The MIT License
Copyright ©2020-2021 https://t.me/ssleg
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

29
README.MD Normal file
View File

@ -0,0 +1,29 @@
###### Модуль для работы с API Qiwi-кошелька.
Предназначен для использования в python проектах, в том числе в **telegram-ботах**.
Работает с Qiwi-кошельками **физических** лиц.
Позволяет выставлять счета клиентам и контролировать их оплату.
Содержит три функции:
`create_bill` - создание счета.
`bill_status` - проверка оплаты.
`cancel_bill` - отмена счета.
Поддерживается только метод **поллинга** (опроса) сервера.
Примеры использования модуля смотрите в файлах **sample.py** (простой) и **adv_sample.py** (расширенный).
Перед использованием необходимо ваш **секретный ключ** записать в файл qiwi_key.txt.
Он должен всегда быть в той же папке, в которую вы положите сам модуль.
Начать прием переводов на свой кошелек вы [https://p2p.qiwi.com][можете здесь].
Полная документация по API Qiwi [https://developer.qiwi.com/ru/p2p-payments/#API][лежит здесь].
Благодарности, вопросы и замечания складывать в комментариях к [https://t.me/ssleg/321][этому посту].
Лицензия на код и документацию MIT.
Вы можете свободно использовать, изменять и продавать код при условии сохранения информации об авторских правах.

54
adv_sample.py Normal file
View File

@ -0,0 +1,54 @@
# Qiwi module advanced usage example v1.00
# 17/05/2021
# https://t.me/ssleg © 2021
import logging
import qiwi_module
# настройка логфлайла test,log, туда будут записываться все ошибки и предупреждения.
lfile = logging.FileHandler('test.log', 'a', 'utf-8')
lfile.setFormatter(logging.Formatter('%(levelname)s %(module)-13s [%(asctime)s] %(message)s'))
# noinspection PyArgumentList
logging.basicConfig(level=logging.INFO, handlers=[lfile])
# простой вариант использования смотрите в файле sample.py
# если у вас настроен свой внешний вид формы платежа, необходимо передать код темы модулю.
# это делается один раз, при его инициализации.
# сам код и настройки формы находятся на странице https://qiwi.com/p2p-admin/transfers/link
theme_code = 'Ivanov-XX-vvv-k_'
# перед любым использованием необходима однократная инициализация модуля.
qiwi_module.init(theme_code)
# создание счета на 1 рубль. При успехе получаете url с формой оплаты для клиента.
# при неуспехе возвращается False с подробной записью в лог.
# идентификаторы счетов придумываете и сохраняете вы сами, они должны быть уникальными всегда.
bill_id = 'bill_2021_00000002'
# по умолчанию счет действителен 15 минут, но вы можете поставить свое время, например сутки и 1 минуту.
valid_hours = 24
valid_minutes = 1
# есть так же поле для комментария, его видит клиент в форме оплаты. например, туда можно записать детали заказа
comment = 'Винт с левой резьбой для Сидорова.'
invoice_url = qiwi_module.create_bill(1.00, bill_id, comment, valid_hours, valid_minutes)
print(invoice_url)
# проверка статуса оплаты.
# возвращает одно из четырех возможных значений, если успешно или False и запись в лог.
# 'WAITING' - cчет выставлен, ожидает оплаты.
# 'PAID' - cчет оплачен.
# 'REJECTED' - счет отменен с вашей стороны.
# 'EXPIRED' - счет не оплачен и истек срок его действия.
# можно вызывать ежесекундно или реже.
pay_status = qiwi_module.bill_status(bill_id)
print(pay_status)
# отмена счета, если вам это необходимо.
# возврашает 'REJECTED' если успешно, иначе False и запись в лог.
bill_status = qiwi_module.cancel_bill(bill_id)
print(bill_status)

1
qiwi_key.txt Normal file
View File

@ -0,0 +1 @@
iQJJBAABCgAzFiEE6Gl+Lu92wC06YzJ3iIGyqCEJdvIFAmCBVw4VHHBhY2thZ2VzQHBnYWRtaW4ub3JnAAoJEIiBsqghCXbyDY8P/i33M99WWx0XGJDNtJMThse7ABCjocamwsVlBQ1IxbTqD26s3MviUzDN337XqwAWz6N6h5hPEzGYe9iI0QErIKsvsZpZJJrEJiLNamA1a

View File

@ -1,89 +1,246 @@
# Qiwi module v1.00
# 21/08/2020
# https://t.me/ssleg © 2020
# Qiwi module v1.10
# 17/05/2021
# https://t.me/ssleg © 2020 2021
import logging
from datetime import datetime, timedelta
from pathlib import Path
import requests
import logging
headers = {
'accept': 'application/json',
'content-type': 'application/json',
'Authorization': 'Bearer xxxxxx' # ваш секретный ключ из личного кабинета
'content-type': 'application/json'
}
url = 'https://api.qiwi.com/partner/bill/v1/bills/'
error_flag = True
theme_code = None
# создание платежа. на входе; сумма (число), номер заказа (текст, идет в комментарий к платежу)
# номер счета (текст, любой уникальный счетчик) и str(datetime) формата 2020-08-21 10:34:22
# на выходе возвращает URL формы оплаты для клиента.
def create_bill(summa, order_num, bill_num, exp_datetime):
am = {'currency': 'RUB', 'value': '{:.2f}'.format(summa)}
exp = exp_datetime.replace(' ', 'T') + '+03:00'
# персональная форма платежа, может не использоваться
# cust = {'themeCode': 'ваш код формы из личного кабинета'}
rdata = {'amount': am, 'expirationDateTime': exp, 'comment': order_num} # , 'customFields': cust}
rurl = url + bill_num
# не подлежит прямому вызову.
# проверяет, что модуль инициализирован.
def init_check():
if error_flag:
levent = 'module is not initialized.'
logging.error(levent)
return False
return True
# не подлежит прямому вызову.
# проверяет корректность суммы счета.
def zero_check(amount):
if type(amount) == int or type(amount) == float:
if amount <= 0:
levent = 'bill amount is zero or negative.'
logging.error(levent)
return False
return True
else:
levent = 'bill amount is not a number. positive int or float values possible.'
logging.error(levent)
return False
# не подлежит прямому вызову.
# возврашает строку со временем действия счета в формате сервера Qiwi.
def get_valid_to_time(hours, mins):
if type(hours) == int and type(mins) == int:
if hours >= 0 and mins > 0:
now = datetime.now()
delta = timedelta(hours=hours, minutes=mins)
valid_to = now + delta
valid_to_string = str(valid_to)[0:19]
rezult_string = valid_to_string.replace(' ', 'T') + '+03:00'
return rezult_string
else:
levent = 'hours or minutes must be above zero.'
logging.error(levent)
return False
else:
levent = 'hours or minutes is not integer.'
logging.error(levent)
return False
# создание счета на оплату. при успехе возвращает url с формой оплаты для клиента.
# при неуспехе - False.
# примеры использования смотрите в sample.py и adv_sample.py
def create_bill(bill_amount, bill_id, comment_string=None, valid_hours=0, valid_mins=15):
if not init_check():
return False
if not zero_check(bill_amount):
return False
if type(bill_amount) == float:
full_penny = bill_amount * 100
fract = full_penny - int(full_penny)
if fract != 0:
levent = 'bill amount must have integer number of penny (kopeek).'
logging.error(levent)
return False
if type(bill_id) != str or bill_id == '':
levent = 'bill id is not a string or empty.'
logging.error(levent)
return False
valid_to = get_valid_to_time(valid_hours, valid_mins)
if not valid_to:
return False
amount = {'currency': 'RUB', 'value': '{:.2f}'.format(bill_amount)}
request_data = {'amount': amount, 'expirationDateTime': valid_to}
if comment_string is not None:
if type(comment_string) == str and comment_string != '':
request_data['comment'] = comment_string
else:
levent = 'comment is not a string or empty.'
logging.warning(levent)
if theme_code is not None:
custom = {'themeCode': theme_code}
request_data['customFields'] = custom
request_url = url + bill_id
try:
response = requests.put(rurl, json=rdata, headers=headers, timeout=5)
cod = response.status_code
res = response.json()
if cod == 200:
return res.get('payUrl')
else:
levent = 'qiwi server error (create bill). code - ' + str(cod) + ', response - ' + str(res)
response = requests.put(request_url, json=request_data, headers=headers, timeout=5)
response_code = response.status_code
if response_code == 200:
response_dict = response.json()
return response_dict.get('payUrl')
elif response_code == 401:
levent = 'Qiwi autorization error. invalid secret key.'
logging.error(levent)
return 'error'
return False
else:
response_text = response.text
levent = 'Qiwi server error (create bill). code - ' + str(response_code) + ', response - ' + response_text
logging.error(levent)
return False
except Exception as e:
levent = 'protocol error (create bill): ' + str(e)
logging.error(levent)
return 'error'
return False
# проверка статуса платежа,на входе его номер (текст), на выходе статус из документации.
# проверка статуса счета,на входе его идентификатор (текст), на выходе один из 4х статусов, если успешно:
# 'WAITING' - cчет выставлен, ожидает оплаты.
# 'PAID' - cчет оплачен.
# 'REJECTED' - счет отменен.
# 'EXPIRED' - счет не оплачен и истек срок его действия.
# можно вызывать 1 раз в секунду и реже.
def bill_status(bill_num):
rurl = url + bill_num
# если неуспешно - возвращает False.
def bill_status(bill_id):
if not init_check():
return False
if type(bill_id) != str or bill_id == '':
levent = 'bill id is not a string or empty.'
logging.error(levent)
return False
request_url = url + bill_id
try:
response = requests.get(rurl, headers=headers, timeout=5)
cod = response.status_code
res = response.json()
if cod == 200:
status = res.get('status')
response = requests.get(request_url, headers=headers, timeout=5)
response_code = response.status_code
if response_code == 200:
response_dict = response.json()
status = response_dict.get('status')
return status.get('value')
else:
levent = 'qiwi server error (bill status). code - ' + str(cod) + ', response - ' + str(res)
elif response_code == 401:
levent = 'Qiwi autorization error. invalid secret key.'
logging.error(levent)
return 'error'
return False
else:
response_text = response.text
levent = 'Qiwi server error (bill status). code - ' + str(response_code) + ', response - ' + response_text
logging.error(levent)
return False
except Exception as e:
levent = 'protocol error (bill status): ' + str(e)
logging.error(levent)
return 'error'
return False
# отмена счета, на входе его номер (текст).
# в случае успеха возвращает REJECTED
def cancel_bill(bill_num):
rurl = url + bill_num + '/reject'
# отмена счета, на входе его идентификатор (текст).
# в случае успеха возвращает REJECTED, иначе False
def cancel_bill(bill_id):
if not init_check():
return False
if type(bill_id) != str or bill_id == '':
levent = 'bill id is not a string or empty.'
logging.error(levent)
return False
request_url = url + bill_id + '/reject'
try:
response = requests.post(rurl, headers=headers, timeout=5)
cod = response.status_code
res = response.json()
if cod == 200:
status = res.get('status')
response = requests.post(request_url, headers=headers, timeout=5)
response_code = response.status_code
if response_code == 200:
response_dict = response.json()
status = response_dict.get('status')
return status.get('value')
else:
levent = 'qiwi server error (cancel bill). code - ' + str(cod) + ', response - ' + str(res)
elif response_code == 401:
levent = 'Qiwi autorization error. invalid secret key.'
logging.error(levent)
return 'error'
return False
else:
response_text = response.text
levent = 'Qiwi server error (cancel bill). code - ' + str(response_code) + ', response - ' + response_text
logging.error(levent)
return False
except Exception as e:
levent = 'protocol error (cancel bill): ' + str(e)
logging.error(levent)
return 'error'
return False
# инициализация модуля - загрузка секретного ключа из файла и настройка пользовательской темы для формы оплаты.
def init(theme=None):
global error_flag
global theme_code
if theme is not None:
if type(theme) == str and theme != '':
theme_code = theme
else:
levent = 'custom theme code is not a string or empty. theme not used.'
logging.warning(levent)
key_path = Path('qiwi_key.txt')
if not key_path.exists():
levent = 'Qiwi key file not found, module work is not possible. write secret key to file qiwi_key.txt'
logging.error(levent)
return False
file = open('qiwi_key.txt')
qiwi_key = file.readline()
file.close()
if qiwi_key.find('\n') > -1:
qiwi_key = qiwi_key[0:len(qiwi_key) - 1]
if not 180 < len(qiwi_key) < 230:
levent = 'Qiwi key not found in file, module work is not possible. write secret key to file qiwi_key.txt'
logging.error(levent)
return False
headers['Authorization'] = 'Bearer ' + qiwi_key
levent = 'Qiwi key loaded, init completed.'
logging.info(levent)
error_flag = False
return True

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
requests~=2.23.0

42
sample.py Normal file
View File

@ -0,0 +1,42 @@
# Qiwi module usage example v1.00
# 17/05/2021
# https://t.me/ssleg © 2021
import logging
import qiwi_module
# настройка логфлайла test,log, туда будут записываться все ошибки и предупреждения.
lfile = logging.FileHandler('test.log', 'a', 'utf-8')
lfile.setFormatter(logging.Formatter('%(levelname)s %(module)-13s [%(asctime)s] %(message)s'))
# noinspection PyArgumentList
logging.basicConfig(level=logging.INFO, handlers=[lfile])
# перед любым использованием необходима однократная инициализация модуля.
qiwi_module.init()
# создание счета на 1 рубль. При успехе получаете url с формой оплаты для клиента.
# при неуспехе возвращается False с подробной записью в лог.
# идентификаторы счетов придумываете и сохраняете вы сами, они должны быть уникальными всегда.
bill_id = 'bill_2021_00000001'
# по умолчанию счет действителен 15 минут.
# продвинутые варианты использования смотрите в файле adv_sample.py
invoice_url = qiwi_module.create_bill(1.00, bill_id)
print(invoice_url)
# проверка статуса оплаты.
# возвращает одно из четырех возможных значений, если успешно или False и запись в лог.
# 'WAITING' - cчет выставлен, ожидает оплаты.
# 'PAID' - cчет оплачен.
# 'REJECTED' - счет отменен с вашей стороны.
# 'EXPIRED' - счет не оплачен и истек срок его действия.
# можно вызывать ежесекундно или реже.
pay_status = qiwi_module.bill_status(bill_id)
print(pay_status)
# отмена счета, если вам это необходимо.
# возврашает 'REJECTED' если успешно, иначе False и запись в лог.
bill_status = qiwi_module.cancel_bill(bill_id)
print(bill_status)