diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/paybox_api/__init__.py b/paybox_api/__init__.py new file mode 100644 index 0000000..dba8bc6 --- /dev/null +++ b/paybox_api/__init__.py @@ -0,0 +1,124 @@ +import hashlib +import random +import string + +from functools import wraps +from urllib.parse import parse_qs + +from django.conf import settings + +from requests import Request + + +class PayboxAPI: + """ + 100 'Некорректная подпись запроса *' + 101 'Неверный номер магазина'' + 110 'Отсутствует или не действует контракт с магазином' + 120 'Запрошенное действие отключено в настройках магазина'' + 200 'Не хватает или некорректный параметр запроса' + 340 'Транзакция не найдена' + 350 'Транзакция заблокирована' + 360 'Транзакция просрочена' + 365 'Срок жизни рекуррентного профиля истек' + 400 'Платеж отменен покупателем или платежной системой' + 420 'Платеж отменен по причине превышения лимита' + 465 'Ошибка связи с платежной системой' + 466 'Истек SSL сертификат' + 470 'Ошибка на стороне платежной системы' + 475 'Общий сбой платежной системы' + 490 'Отмена платежа невозможна' + 600 'Общая ошибка' + 700 'Ошибка в данных введенных покупателем' + 701 'Некорректный номер телефона' + 711 'Номер телефона неприемлем для выбранной ПС' + 850 'Ни одна из платежных систем не готова принять запрос' + 1000 'Внутренняя ошибка сервиса (может не повториться при повторном обращении)' + """ + def __init__(self, *args, **kwargs): + self.PG_URL = kwargs.pop('pg_url', None) or getattr(settings, 'PG_URL', '') + self.PG_SECRET = kwargs.pop('pg_secret', None) or getattr(settings, 'PG_SECRET', '') + self.PG_MERCHANT_ID = kwargs.pop('pg_merchant_id', None) or getattr(settings, 'PG_MERCHANT_ID', '') + self.PG_TESTING_MODE = kwargs.pop('pg_testing', None) or getattr(settings, 'PG_TESTING_MODE', '') + self.PG_RESULT_URL = kwargs.pop('pg_result_url', None) or getattr(settings, 'PG_RESULT_URL', '') + self.PG_SUCCESS_URL = kwargs.pop('pg_success_url', None) or getattr(settings, 'PG_SUCCESS_URL', '') + self.PG_CHECK_URL = kwargs.pop('pg_check_url', None) or getattr(settings, 'PG_CHECK_URL', '') + self.PG_REFUND_URL = kwargs.pop('pg_refund_url', None) or getattr(settings, 'PG_REFUND_URL', '') + self.PG_CAPTURE_URL = kwargs.pop('pg_capture_url', None) or getattr(settings, 'PG_CAPTURE_URL', '') + self.PG_FAILURE_URL = kwargs.pop('pg_failure_url', None) or getattr(settings, 'PG_FAILURE_URL', '') + + @staticmethod + def parse_body(body): + body = body.decode('utf-8') + body = parse_qs(body) + for k, v in body.items(): + body[k] = v[0] + return body + + def get_sig(self, mydict, url='payment.php'): + new_list = list() + for key in sorted(mydict.keys()): + new_list.append(str(mydict[key])) + res = url + ';' + ';'.join(new_list) + ';' + self.PG_SECRET + hashed = hashlib.md5(res.encode('utf-8')).hexdigest() + return hashed + + @staticmethod + def verify_sig(request, url=''): + if request.method == 'GET': + sig = request.GET.get('pg_sig') + my_dict = request.GET.dict() + else: + my_dict = PayboxAPI.parse_body(request.body) + sig = my_dict.pop('pg_sig', None) + if sig != PayboxAPI().get_sig(my_dict, url=url): + return False + return True + + @staticmethod + def verify_paybox(): + def decorator(func): + @wraps(func) + def inner(request, *args, **kwargs): + if not PayboxAPI.verify_sig(request): + raise Exception(detail='Неправильная подпись') + response = func(request, *args, **kwargs) + return response + return inner + return decorator + + @staticmethod + def generate_salt(length=8): + chars = string.ascii_letters + string.digits + rnd = random.SystemRandom() + return ''.join(rnd.choice(chars) for _ in range(length)) + + def get_url(self, order_id, amount, description, salt, email=None, phone=None): + params = { + 'pg_order_id': order_id, + 'pg_merchant_id': self.PG_MERCHANT_ID, + 'pg_amount': amount, + 'pg_currency': 'KZT', + 'pg_description': description, + 'pg_salt': salt, + 'pg_testing_mode': self.PG_TESTING_MODE, + 'pg_request_method': 'POST', + 'pg_success_url_method': 'POST', + 'pg_failure_url_method': 'POST', + 'pg_result_url': self.PG_RESULT_URL, + 'pg_success_url': self.PG_SUCCESS_URL, + 'pg_check_url': self.PG_CHECK_URL, + 'pg_refund_url': self.PG_REFUND_URL, + 'pg_capture_url': self.PG_CAPTURE_URL, + 'pg_failure_url': self.PG_FAILURE_URL, + } + + if email: + params['pg_user_contact_email'] = email + + if phone: + params['pg_user_phone'] = phone + + params['pg_sig'] = self.get_sig(params) + + return Request('GET', self.PG_URL, params=params).prepare().url diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8f50dab --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +django>=1.11 +requests>=2.14 +coverage +codecov +flake8 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d548fee --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from setuptools import ( + find_packages, + setup, +) + +setup( + name='django-paybox-api', + version='0.0.1', + author='Aiba', + description='Django paybox api', + url='https://github.com/aibaq/django-paybox-api', + packages=find_packages(exclude=('*tests*',)), + install_requires=[ + 'django>=1.11', + 'requests>=2.14', + ], +) diff --git a/test_app/__init__.py b/test_app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_app/__init__.pyc b/test_app/__init__.pyc new file mode 100644 index 0000000..1a28544 Binary files /dev/null and b/test_app/__init__.pyc differ diff --git a/test_app/settings.py b/test_app/settings.py new file mode 100644 index 0000000..f8ad072 --- /dev/null +++ b/test_app/settings.py @@ -0,0 +1,29 @@ +import os + + +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SECRET_KEY = '*nrox99_o1^@n!@mb00=#iflh&%n^$^!oxy7c8+=1j1e5r4c%*' +DEBUG = True +USE_TZ = True + +ROOT_URLCONF = 'test_app.urls' +STATIC_URL = '/static/' + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'catracking' +] + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +ALLOWED_HOSTS = ['*'] diff --git a/test_app/settings.pyc b/test_app/settings.pyc new file mode 100644 index 0000000..cd79dfa Binary files /dev/null and b/test_app/settings.pyc differ diff --git a/test_app/tests/__init__.py b/test_app/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test_app/tests/__pycache__/__init__.cpython-37.pyc b/test_app/tests/__pycache__/__init__.cpython-37.pyc new file mode 100644 index 0000000..4f168d9 Binary files /dev/null and b/test_app/tests/__pycache__/__init__.cpython-37.pyc differ diff --git a/test_app/tests/__pycache__/test_utils.cpython-37.pyc b/test_app/tests/__pycache__/test_utils.cpython-37.pyc new file mode 100644 index 0000000..e83505b Binary files /dev/null and b/test_app/tests/__pycache__/test_utils.cpython-37.pyc differ diff --git a/test_app/tests/test_utils.py b/test_app/tests/test_utils.py new file mode 100644 index 0000000..a5ff165 --- /dev/null +++ b/test_app/tests/test_utils.py @@ -0,0 +1,18 @@ + +from django.test import ( + RequestFactory, + TestCase, +) + +from test_app.utils import get_pagetype + + +class GetPagetypeTest(TestCase): + + def test_get_pagetype_home_no(self): + request = RequestFactory().get('/') + self.assertEqual(get_pagetype(request), 'Home') + + def test_get_pagetype_home_yes(self): + request = RequestFactory().get('/abc/') + self.assertEqual(get_pagetype(request), 'Not-Home') diff --git a/test_app/urls.py b/test_app/urls.py new file mode 100644 index 0000000..ede2ec9 --- /dev/null +++ b/test_app/urls.py @@ -0,0 +1,6 @@ +from django.conf.urls import url +from django.contrib import admin + +urlpatterns = [ + url(r'^admin/', admin.site.urls), +] diff --git a/test_app/utils.py b/test_app/utils.py new file mode 100644 index 0000000..b16144e --- /dev/null +++ b/test_app/utils.py @@ -0,0 +1,3 @@ + +def get_pagetype(request): + return 'Home' if request.path == '/' else 'Not-Home'