2021-05-17 14:52:39 +03:00
|
|
|
|
# Qiwi module v1.10
|
|
|
|
|
# 17/05/2021
|
|
|
|
|
# https://t.me/ssleg © 2020 – 2021
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
import logging
|
2021-05-17 14:52:39 +03:00
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import requests
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
headers = {
|
|
|
|
|
'accept': 'application/json',
|
2021-05-17 14:52:39 +03:00
|
|
|
|
'content-type': 'application/json'
|
2021-04-08 17:44:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
url = 'https://api.qiwi.com/partner/bill/v1/bills/'
|
|
|
|
|
|
2021-05-17 14:52:39 +03:00
|
|
|
|
error_flag = True
|
|
|
|
|
theme_code = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# не подлежит прямому вызову.
|
|
|
|
|
# проверяет, что модуль инициализирован.
|
|
|
|
|
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}
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
2021-05-17 14:52:39 +03:00
|
|
|
|
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
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
2021-05-17 14:52:39 +03:00
|
|
|
|
request_url = url + bill_id
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
try:
|
2021-05-17 14:52:39 +03:00
|
|
|
|
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 False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
else:
|
2021-05-17 14:52:39 +03:00
|
|
|
|
response_text = response.text
|
|
|
|
|
levent = 'Qiwi server error (create bill). code - ' + str(response_code) + ', response - ' + response_text
|
2021-04-08 17:44:37 +03:00
|
|
|
|
logging.error(levent)
|
2021-05-17 14:52:39 +03:00
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
levent = 'protocol error (create bill): ' + str(e)
|
|
|
|
|
logging.error(levent)
|
2021-05-17 14:52:39 +03:00
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
|
2021-05-17 14:52:39 +03:00
|
|
|
|
# проверка статуса счета,на входе его идентификатор (текст), на выходе один из 4х статусов, если успешно:
|
|
|
|
|
# 'WAITING' - cчет выставлен, ожидает оплаты.
|
|
|
|
|
# 'PAID' - cчет оплачен.
|
|
|
|
|
# 'REJECTED' - счет отменен.
|
|
|
|
|
# 'EXPIRED' - счет не оплачен и истек срок его действия.
|
2021-04-08 17:44:37 +03:00
|
|
|
|
# можно вызывать 1 раз в секунду и реже.
|
2021-05-17 14:52:39 +03:00
|
|
|
|
# если неуспешно - возвращает 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
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
try:
|
2021-05-17 14:52:39 +03:00
|
|
|
|
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')
|
2021-04-08 17:44:37 +03:00
|
|
|
|
return status.get('value')
|
2021-05-17 14:52:39 +03:00
|
|
|
|
elif response_code == 401:
|
|
|
|
|
levent = 'Qiwi autorization error. invalid secret key.'
|
|
|
|
|
logging.error(levent)
|
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
else:
|
2021-05-17 14:52:39 +03:00
|
|
|
|
response_text = response.text
|
|
|
|
|
levent = 'Qiwi server error (bill status). code - ' + str(response_code) + ', response - ' + response_text
|
2021-04-08 17:44:37 +03:00
|
|
|
|
logging.error(levent)
|
2021-05-17 14:52:39 +03:00
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
levent = 'protocol error (bill status): ' + str(e)
|
|
|
|
|
logging.error(levent)
|
2021-05-17 14:52:39 +03:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# отмена счета, на входе его идентификатор (текст).
|
|
|
|
|
# в случае успеха возвращает REJECTED, иначе False
|
|
|
|
|
def cancel_bill(bill_id):
|
|
|
|
|
if not init_check():
|
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
2021-05-17 14:52:39 +03:00
|
|
|
|
if type(bill_id) != str or bill_id == '':
|
|
|
|
|
levent = 'bill id is not a string or empty.'
|
|
|
|
|
logging.error(levent)
|
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
2021-05-17 14:52:39 +03:00
|
|
|
|
request_url = url + bill_id + '/reject'
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
try:
|
2021-05-17 14:52:39 +03:00
|
|
|
|
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')
|
2021-04-08 17:44:37 +03:00
|
|
|
|
return status.get('value')
|
2021-05-17 14:52:39 +03:00
|
|
|
|
elif response_code == 401:
|
|
|
|
|
levent = 'Qiwi autorization error. invalid secret key.'
|
|
|
|
|
logging.error(levent)
|
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
else:
|
2021-05-17 14:52:39 +03:00
|
|
|
|
response_text = response.text
|
|
|
|
|
levent = 'Qiwi server error (cancel bill). code - ' + str(response_code) + ', response - ' + response_text
|
2021-04-08 17:44:37 +03:00
|
|
|
|
logging.error(levent)
|
2021-05-17 14:52:39 +03:00
|
|
|
|
return False
|
2021-04-08 17:44:37 +03:00
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
levent = 'protocol error (cancel bill): ' + str(e)
|
|
|
|
|
logging.error(levent)
|
2021-05-17 14:52:39 +03:00
|
|
|
|
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
|