Версия 1.10, добавлены дополнительные проверки входных данных и подробный лог ошибок. Так же добавлена документация и примеры.
parent
620304a3ea
commit
78049deffd
|
@ -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.
|
|
@ -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.
|
||||
Вы можете свободно использовать, изменять и продавать код при условии сохранения информации об авторских правах.
|
||||
|
||||
|
||||
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
|||
iQJJBAABCgAzFiEE6Gl+Lu92wC06YzJ3iIGyqCEJdvIFAmCBVw4VHHBhY2thZ2VzQHBnYWRtaW4ub3JnAAoJEIiBsqghCXbyDY8P/i33M99WWx0XGJDNtJMThse7ABCjocamwsVlBQ1IxbTqD26s3MviUzDN337XqwAWz6N6h5hPEzGYe9iI0QErIKsvsZpZJJrEJiLNamA1a
|
257
qiwi_module.py
257
qiwi_module.py
|
@ -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
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
requests~=2.23.0
|
|
@ -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)
|
Loading…
Reference in New Issue