parent
d4bc5b7621
commit
f09b5408ae
24 changed files with 847 additions and 27 deletions
@ -0,0 +1,30 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.db import migrations, models |
||||
from django.conf import settings |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
('customer', '0005_auto_20170629_1224'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Payment', |
||||
fields=[ |
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), |
||||
('order_amount', models.DecimalField(verbose_name='Сумма заказа', max_digits=15, decimal_places=2)), |
||||
('order_number', models.IntegerField(verbose_name='Номер заказа')), |
||||
('created', models.DateTimeField(verbose_name='Время создания', auto_now_add=True)), |
||||
('user', models.ForeignKey(verbose_name='Пользователь', related_name='payment_user', to=settings.AUTH_USER_MODEL)), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Платеж', |
||||
'verbose_name_plural': 'Платежи', |
||||
}, |
||||
), |
||||
] |
||||
@ -0,0 +1,19 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('customer', '0006_payment'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='payment', |
||||
name='status', |
||||
field=models.PositiveSmallIntegerField(verbose_name='Статус', default=0, choices=[(0, 'Ожидает оплаты'), (1, 'Оплачен'), (2, 'Отклонен')]), |
||||
), |
||||
] |
||||
@ -0,0 +1,30 @@ |
||||
import sys |
||||
|
||||
|
||||
VERSION = (1, 3, 3) |
||||
|
||||
|
||||
def get_version(): |
||||
return '.'.join(map(str, VERSION)) |
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2 |
||||
PY3 = sys.version_info[0] == 3 |
||||
|
||||
|
||||
def to_unicode(value): |
||||
try: |
||||
return unicode(value) |
||||
except NameError: |
||||
return str(value) |
||||
|
||||
|
||||
def python_2_unicode_compatible(klass): |
||||
if PY2: |
||||
if '__str__' not in klass.__dict__: |
||||
raise ValueError("@python_2_unicode_compatible cannot be applied " |
||||
"to %s because it doesn't define __str__()." % |
||||
klass.__name__) |
||||
klass.__unicode__ = klass.__str__ |
||||
klass.__str__ = lambda self: self.__unicode__().encode('utf-8') |
||||
return klass |
||||
@ -0,0 +1,9 @@ |
||||
# -*- encoding: utf-8 -*- |
||||
|
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from django.apps import AppConfig |
||||
|
||||
|
||||
class DBMailConfig(AppConfig): |
||||
name = 'robokassa' |
||||
verbose_name = _(u'Робокасса') |
||||
@ -0,0 +1,25 @@ |
||||
# coding: utf-8 |
||||
|
||||
from django.conf import settings |
||||
|
||||
# обязательные параметры - реквизиты магазина |
||||
LOGIN = settings.ROBOKASSA_LOGIN |
||||
PASSWORD1 = settings.ROBOKASSA_PASSWORD1 |
||||
PASSWORD2 = getattr(settings, 'ROBOKASSA_PASSWORD2', None) |
||||
|
||||
# использовать ли метод POST при приеме результатов |
||||
USE_POST = getattr(settings, 'ROBOKASSA_USE_POST', True) |
||||
|
||||
# требовать предварительного уведомления на ResultURL |
||||
STRICT_CHECK = getattr(settings, 'ROBOKASSA_STRICT_CHECK', True) |
||||
|
||||
# тестовый режим |
||||
TEST_MODE = getattr(settings, 'ROBOKASSA_TEST_MODE', False) |
||||
|
||||
# url, по которому будет идти отправка форм |
||||
FORM_TARGET = 'https://merchant.roboxchange.com/Index.aspx' |
||||
if TEST_MODE: |
||||
FORM_TARGET = 'https://auth.robokassa.ru/Merchant/Index.aspx' |
||||
|
||||
# список пользовательских параметров ("shp" к ним приписывать не нужно) |
||||
EXTRA_PARAMS = sorted(getattr(settings, 'ROBOKASSA_EXTRA_PARAMS', [])) |
||||
@ -0,0 +1,199 @@ |
||||
# coding: utf-8 |
||||
|
||||
from hashlib import md5 |
||||
from django import forms |
||||
|
||||
try: |
||||
from urllib import urlencode |
||||
except ImportError: |
||||
from urllib.parse import urlencode |
||||
|
||||
from robokassa.conf import LOGIN, PASSWORD1, PASSWORD2 |
||||
from robokassa.conf import STRICT_CHECK, FORM_TARGET, EXTRA_PARAMS, TEST_MODE |
||||
from robokassa.models import SuccessNotification |
||||
from robokassa import to_unicode, PY2 |
||||
|
||||
|
||||
class BaseRobokassaForm(forms.Form): |
||||
def __init__(self, *args, **kwargs): |
||||
self.password1 = kwargs.pop('password1', PASSWORD1) |
||||
self.password2 = kwargs.pop('password2', PASSWORD2) |
||||
self.login = kwargs.pop('login', LOGIN) |
||||
|
||||
super(BaseRobokassaForm, self).__init__(*args, **kwargs) |
||||
|
||||
# создаем дополнительные поля |
||||
for key in EXTRA_PARAMS: |
||||
self.fields['shp' + key] = forms.CharField(required=False) |
||||
if 'initial' in kwargs: |
||||
self.fields['shp' + key].initial = kwargs['initial'].get( |
||||
key, 'None') |
||||
|
||||
@staticmethod |
||||
def _append_extra_part(standard_part, value_func): |
||||
extra_part = ":".join( |
||||
["%s=%s" % ('shp' + key, value_func('shp' + key)) |
||||
for key in EXTRA_PARAMS]) |
||||
if extra_part: |
||||
return ':'.join([standard_part, extra_part]) |
||||
return standard_part |
||||
|
||||
def extra_params(self): |
||||
extra = {} |
||||
for param in EXTRA_PARAMS: |
||||
if ('shp' + param) in self.cleaned_data: |
||||
extra[param] = self.cleaned_data['shp' + param] |
||||
return extra |
||||
|
||||
def _get_signature(self): |
||||
if PY2: |
||||
return md5(self._get_signature_string()).hexdigest().upper() |
||||
return md5( |
||||
self._get_signature_string().encode("ascii")).hexdigest().upper() |
||||
|
||||
def _get_signature_string(self): |
||||
raise NotImplementedError |
||||
|
||||
|
||||
class RobokassaForm(BaseRobokassaForm): |
||||
# login магазина в обменном пункте |
||||
MrchLogin = forms.CharField(max_length=20) |
||||
|
||||
# требуемая к получению сумма |
||||
OutSum = forms.DecimalField( |
||||
min_value=0, max_digits=20, decimal_places=2, required=False) |
||||
|
||||
# номер счета в магазине (должен быть уникальным для магазина) |
||||
InvId = forms.IntegerField(min_value=0, required=False) |
||||
|
||||
# описание покупки |
||||
Desc = forms.CharField(max_length=100, required=False) |
||||
|
||||
# контрольная сумма MD5 |
||||
SignatureValue = forms.CharField(max_length=32) |
||||
|
||||
# предлагаемая валюта платежа |
||||
IncCurrLabel = forms.CharField(max_length=10, required=False) |
||||
|
||||
# e-mail пользователя |
||||
Email = forms.CharField(max_length=100, required=False) |
||||
|
||||
# язык общения с клиентом (en или ru) |
||||
Culture = forms.CharField(max_length=10, required=False) |
||||
if TEST_MODE: |
||||
IsTest = forms.IntegerField(min_value=1, required=False) |
||||
|
||||
# Параметр с URL'ом, на который форма должны быть отправлена. |
||||
# Может пригодиться для использования в шаблоне. |
||||
target = FORM_TARGET |
||||
|
||||
def __init__(self, *args, **kwargs): |
||||
super(RobokassaForm, self).__init__(*args, **kwargs) |
||||
|
||||
# скрытый виджет по умолчанию |
||||
for field in self.fields: |
||||
self.fields[field].widget = forms.HiddenInput() |
||||
|
||||
self.fields['MrchLogin'].initial = self.login |
||||
self.fields['SignatureValue'].initial = self._get_signature() |
||||
if TEST_MODE: |
||||
self.fields['IsTest'].initial = 1 |
||||
|
||||
def get_redirect_url(self): |
||||
""" |
||||
Получить URL с GET-параметрами, соответствующими значениям полей в |
||||
форме. Редирект на адрес, возвращаемый этим методом, эквивалентен |
||||
ручной отправке формы методом GET. |
||||
""" |
||||
|
||||
def _initial(key, fld): |
||||
val = self.initial.get(key, fld.initial) |
||||
if not val: |
||||
return val |
||||
return to_unicode(val).encode('1251') |
||||
|
||||
fields = [ |
||||
(name, _initial(name, field)) |
||||
for name, field in self.fields.items() |
||||
if _initial(name, field) |
||||
] |
||||
return self.target + '?' + urlencode(fields) |
||||
|
||||
def _get_signature_string(self): |
||||
def _val(name): |
||||
if name in self.initial: |
||||
value = self.initial[name] |
||||
else: |
||||
value = self.fields[name].initial |
||||
return '' if value is None else to_unicode(value) |
||||
|
||||
standard_part = ':'.join( |
||||
[_val('MrchLogin'), _val('OutSum'), _val('InvId'), self.password1]) |
||||
return self._append_extra_part(standard_part, _val) |
||||
|
||||
|
||||
class ResultURLForm(BaseRobokassaForm): |
||||
""" |
||||
Форма для приема результатов и проверки контрольной суммы. |
||||
""" |
||||
|
||||
OutSum = forms.CharField(max_length=15) |
||||
InvId = forms.IntegerField(min_value=0) |
||||
SignatureValue = forms.CharField(max_length=32) |
||||
|
||||
def clean(self): |
||||
try: |
||||
signature = self.cleaned_data['SignatureValue'].upper() |
||||
if signature != self._get_signature(): |
||||
raise forms.ValidationError(u'Ошибка в контрольной сумме') |
||||
except KeyError: |
||||
raise forms.ValidationError(u'Пришли не все необходимые параметры') |
||||
|
||||
return self.cleaned_data |
||||
|
||||
def _get_signature_string(self): |
||||
_val = lambda name: to_unicode(self.cleaned_data[name]) |
||||
standard_part = ':'.join( |
||||
[_val('OutSum'), _val('InvId'), self.password2]) |
||||
return self._append_extra_part(standard_part, _val) |
||||
|
||||
|
||||
class _RedirectPageForm(ResultURLForm): |
||||
""" |
||||
Форма для проверки контрольной суммы на странице Success. |
||||
""" |
||||
|
||||
Culture = forms.CharField(max_length=10) |
||||
|
||||
def _get_signature_string(self): |
||||
_val = lambda name: to_unicode(self.cleaned_data[name]) |
||||
standard_part = ':'.join( |
||||
[_val('OutSum'), _val('InvId'), self.password1]) |
||||
return self._append_extra_part(standard_part, _val) |
||||
|
||||
|
||||
class SuccessRedirectForm(_RedirectPageForm): |
||||
""" |
||||
Форма для обработки страницы Success с дополнительной защитой. Она |
||||
проверяет, что ROBOKASSA предварительно уведомила систему о платеже, |
||||
отправив запрос на ResultURL. |
||||
""" |
||||
|
||||
def clean(self): |
||||
data = super(SuccessRedirectForm, self).clean() |
||||
if STRICT_CHECK: |
||||
if not SuccessNotification.objects.filter(InvId=data['InvId']): |
||||
raise forms.ValidationError( |
||||
'От ROBOKASSA не было предварительного уведомления' |
||||
) |
||||
return data |
||||
|
||||
|
||||
class FailRedirectForm(BaseRobokassaForm): |
||||
""" |
||||
Форма приема результатов для перенаправления на страницу Fail. |
||||
""" |
||||
|
||||
OutSum = forms.CharField(max_length=15) |
||||
InvId = forms.IntegerField(min_value=0) |
||||
Culture = forms.CharField(max_length=10) |
||||
@ -0,0 +1,27 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.db import models, migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='SuccessNotification', |
||||
fields=[ |
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
||||
('InvId', models.IntegerField(verbose_name='\u041d\u043e\u043c\u0435\u0440 \u0437\u0430\u043a\u0430\u0437\u0430', db_index=True)), |
||||
('OutSum', models.CharField(max_length=15, verbose_name='\u0421\u0443\u043c\u043c\u0430')), |
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='\u0414\u0430\u0442\u0430 \u0438 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f')), |
||||
], |
||||
options={ |
||||
'verbose_name': '\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e\u0431 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u043c \u043f\u043b\u0430\u0442\u0435\u0436\u0435', |
||||
'verbose_name_plural': '\u0423\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e\u0431 \u0443\u0441\u043f\u0435\u0448\u043d\u044b\u0445 \u043f\u043b\u0430\u0442\u0435\u0436\u0430\u0445 (ROBOKASSA)', |
||||
}, |
||||
bases=(models.Model,), |
||||
), |
||||
] |
||||
@ -0,0 +1,22 @@ |
||||
# coding: utf-8 |
||||
|
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from robokassa import python_2_unicode_compatible |
||||
from django.db import models |
||||
|
||||
|
||||
@python_2_unicode_compatible |
||||
class SuccessNotification(models.Model): |
||||
InvId = models.IntegerField(_(u'Номер заказа'), db_index=True) |
||||
OutSum = models.CharField(_(u'Сумма'), max_length=15) |
||||
|
||||
created_at = models.DateTimeField( |
||||
_(u'Дата и время получения уведомления'), auto_now_add=True) |
||||
|
||||
class Meta: |
||||
verbose_name = _(u'Уведомление об успешном платеже') |
||||
verbose_name_plural = _( |
||||
u'Уведомления об успешных платежах (ROBOKASSA)') |
||||
|
||||
def __str__(self): |
||||
return u'#%d: %s (%s)' % (self.InvId, self.OutSum, self.created_at) |
||||
@ -0,0 +1,6 @@ |
||||
# coding: utf-8 |
||||
from django.dispatch import Signal |
||||
|
||||
result_received = Signal(providing_args=["InvId", "OutSum"]) |
||||
success_page_visited = Signal(providing_args=["InvId", "OutSum"]) |
||||
fail_page_visited = Signal(providing_args=["InvId", "OutSum"]) |
||||
@ -0,0 +1,214 @@ |
||||
# coding: utf-8 |
||||
|
||||
from unittest import TestCase |
||||
from django.test import TestCase as DjangoTestCase |
||||
from robokassa.forms import RobokassaForm, ResultURLForm |
||||
from robokassa.conf import LOGIN, PASSWORD1, PASSWORD2 |
||||
|
||||
|
||||
CUSTOM_LOGIN = 'test_login2' |
||||
CUSTOM_PASS1 = 'test_password21' |
||||
CUSTOM_PASS2 = 'test_password22' |
||||
|
||||
|
||||
class RobokassaFormTest(TestCase): |
||||
def setUp(self): |
||||
self.form = RobokassaForm( |
||||
initial={ |
||||
'OutSum': 100.00, |
||||
'InvId': 58, |
||||
'Desc': u'Холодильник "Бирюса"', |
||||
'Email': 'vasia@example.com' |
||||
}) |
||||
|
||||
def testSignature(self): |
||||
self.assertEqual( |
||||
self.form._get_signature_string(), |
||||
'%s:100.0:58:%s:shpparam1=None:shpparam2=None' % (LOGIN, PASSWORD1) |
||||
) |
||||
self.assertEqual(len(self.form.fields['SignatureValue'].initial), 32) |
||||
|
||||
def testSignatureMissingParams(self): |
||||
form = RobokassaForm(initial={'InvId': 5}) |
||||
self.assertEqual( |
||||
form._get_signature_string(), |
||||
'%s::5:%s:shpparam1=None:shpparam2=None' % (LOGIN, PASSWORD1) |
||||
) |
||||
|
||||
def testRedirectUrl(self): |
||||
url = "https://merchant.roboxchange.com/Index.aspx?MrchLogin=" \ |
||||
"test_login&OutSum=100.0&InvId=58&Desc=%D5%EE%EB%EE%E4%" \ |
||||
"E8%EB%FC%ED%E8%EA+%22%C1%E8%F0%FE%F1%E0%22&SignatureVa" \ |
||||
"lue=0EC23BE40003640B35EC07F6615FFB57&Email=vasia%40exa" \ |
||||
"mple.com&shpparam1=None&shpparam2=None" |
||||
self.assertEqual(self.form.get_redirect_url(), url) |
||||
|
||||
|
||||
class RobokassaFormExtraTest(TestCase): |
||||
def testExtra(self): |
||||
form = RobokassaForm( |
||||
initial={ |
||||
'InvId': 58, |
||||
'OutSum': 100, |
||||
'param1': 'value1', |
||||
'param2': 'value2' |
||||
}) |
||||
self.assertEqual( |
||||
form._get_signature_string(), |
||||
'%s:100:58:%s:shpparam1=value1:shpparam2=value2' % ( |
||||
LOGIN, PASSWORD1) |
||||
) |
||||
|
||||
|
||||
class ResultURLTest(DjangoTestCase): |
||||
def testFormExtra(self): |
||||
form = ResultURLForm({ |
||||
'OutSum': '100', |
||||
'InvId': '58', |
||||
'SignatureValue': 'B2111A06F6B7A1E090D38367BF7032D9', |
||||
'shpparam1': 'Vasia', |
||||
'shpparam2': 'None', |
||||
}) |
||||
self.assertTrue(form.is_valid()) |
||||
self.assertEqual( |
||||
form._get_signature_string(), |
||||
'100:58:%s:shpparam1=Vasia:shpparam2=None' % PASSWORD2) |
||||
self.assertEqual( |
||||
form.extra_params(), {'param1': 'Vasia', 'param2': 'None'} |
||||
) |
||||
|
||||
def testFormValid(self): |
||||
self.assertTrue(ResultURLForm({ |
||||
'OutSum': '100', |
||||
'InvId': '58', |
||||
'SignatureValue': '877D3BAF8381F70E56638C3BC82580C5', |
||||
'shpparam1': 'None', |
||||
'shpparam2': 'None', |
||||
}).is_valid()) |
||||
|
||||
self.assertFalse(ResultURLForm({ |
||||
'OutSum': '101', |
||||
'InvId': '58', |
||||
'SignatureValue': '877D3BAF8381F70E56638C3BC82580C5', |
||||
'shpparam1': 'None', |
||||
'shpparam2': 'None', |
||||
}).is_valid()) |
||||
|
||||
self.assertFalse(ResultURLForm({ |
||||
'OutSum': '100', |
||||
'InvId': '58', |
||||
'SignatureValue': '877D3BAF8381F70E56638C3BC82580C5', |
||||
'shpparam1': 'Vasia', |
||||
'shpparam2': 'None', |
||||
}).is_valid()) |
||||
|
||||
def testEmptyFormValid(self): |
||||
self.assertFalse(ResultURLForm().is_valid()) |
||||
|
||||
|
||||
class RobokassaCustomCredentialFormTest(TestCase): |
||||
def setUp(self): |
||||
self.form = RobokassaForm( |
||||
initial={ |
||||
'OutSum': 100.00, |
||||
'InvId': 58, |
||||
'Desc': u'Холодильник "Бирюса"', |
||||
'Email': 'vasia@example.com' |
||||
}, |
||||
login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2 |
||||
) |
||||
|
||||
def testSignature(self): |
||||
self.assertEqual( |
||||
self.form._get_signature_string(), |
||||
'%s:100.0:58:%s:shpparam1=None:shpparam2=None' % ( |
||||
CUSTOM_LOGIN, CUSTOM_PASS1) |
||||
) |
||||
self.assertEqual(len(self.form.fields['SignatureValue'].initial), 32) |
||||
|
||||
def testSignatureMissingParams(self): |
||||
form = RobokassaForm( |
||||
initial={'InvId': 5}, |
||||
login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2) |
||||
self.assertEqual( |
||||
form._get_signature_string(), |
||||
'%s::5:%s:shpparam1=None:shpparam2=None' % ( |
||||
CUSTOM_LOGIN, CUSTOM_PASS1) |
||||
) |
||||
|
||||
def testRedirectUrl(self): |
||||
url = "https://merchant.roboxchange.com/Index.aspx?MrchLogin=" \ |
||||
"test_login2&OutSum=100.0&InvId=58&Desc=%D5%EE%EB%EE%E4%E8%" \ |
||||
"EB%FC%ED%E8%EA+%22%C1%E8%F0%FE%F1%E0%22&SignatureValue=659" \ |
||||
"9E0D576E94D4E8616A40B16B8288F&Email=vasia%40example.com&sh" \ |
||||
"pparam1=None&shpparam2=None" |
||||
self.assertEqual(self.form.get_redirect_url(), url) |
||||
|
||||
|
||||
class RobokassaCustomCredentialFormExtraTest(TestCase): |
||||
def testExtra(self): |
||||
form = RobokassaForm( |
||||
initial={ |
||||
'InvId': 58, |
||||
'OutSum': 100, |
||||
'param1': 'value1', |
||||
'param2': 'value2' |
||||
}, |
||||
login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2 |
||||
) |
||||
self.assertEqual( |
||||
form._get_signature_string(), |
||||
'%s:100:58:%s:shpparam1=value1:shpparam2=value2' % ( |
||||
CUSTOM_LOGIN, CUSTOM_PASS1) |
||||
) |
||||
|
||||
|
||||
class RobokassaCustomCredentialResultURLTest(DjangoTestCase): |
||||
def testFormExtra(self): |
||||
form = ResultURLForm({ |
||||
'OutSum': '100', |
||||
'InvId': '58', |
||||
'SignatureValue': '8D63137C2AC89F7D5B7E6CD448DA64AF', |
||||
'shpparam1': 'Vasia', |
||||
'shpparam2': 'None', |
||||
}, login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2) |
||||
self.assertTrue(form.is_valid()) |
||||
self.assertEqual( |
||||
form._get_signature_string(), |
||||
'100:58:%s:shpparam1=Vasia:shpparam2=None' % CUSTOM_PASS2) |
||||
self.assertEqual( |
||||
form.extra_params(), {'param1': 'Vasia', 'param2': 'None'} |
||||
) |
||||
|
||||
def testFormValid(self): |
||||
self.assertTrue(ResultURLForm({ |
||||
'OutSum': '100', |
||||
'InvId': '58', |
||||
'SignatureValue': '3D7DE0A71282E50308ED401DC24BBD5B', |
||||
'shpparam1': 'None', |
||||
'shpparam2': 'None', |
||||
}, login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2 |
||||
).is_valid()) |
||||
|
||||
self.assertFalse(ResultURLForm({ |
||||
'OutSum': '101', |
||||
'InvId': '58', |
||||
'SignatureValue': '3D7DE0A71282E50308ED401DC24BBD5B', |
||||
'shpparam1': 'None', |
||||
'shpparam2': 'None', |
||||
}, login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2 |
||||
).is_valid()) |
||||
|
||||
self.assertFalse(ResultURLForm({ |
||||
'OutSum': '100', |
||||
'InvId': '58', |
||||
'SignatureValue': '3D7DE0A71282E50308ED401DC24BBD5B', |
||||
'shpparam1': 'Vasia', |
||||
'shpparam2': 'None', |
||||
}, login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2 |
||||
).is_valid()) |
||||
|
||||
def testEmptyFormValid(self): |
||||
self.assertFalse(ResultURLForm( |
||||
login=CUSTOM_LOGIN, password1=CUSTOM_PASS1, password2=CUSTOM_PASS2 |
||||
).is_valid()) |
||||
@ -0,0 +1,15 @@ |
||||
# coding: utf-8 |
||||
|
||||
try: |
||||
from django.conf.urls.defaults import url |
||||
except ImportError: |
||||
from django.conf.urls import url |
||||
|
||||
from . import views as v |
||||
|
||||
|
||||
urlpatterns = [ |
||||
url(r'^result/$', v.receive_result, name='robokassa_result'), |
||||
url(r'^success/$', v.success, name='robokassa_success'), |
||||
url(r'^fail/$', v.fail, name='robokassa_fail'), |
||||
] |
||||
@ -0,0 +1,99 @@ |
||||
# coding: utf-8 |
||||
|
||||
from django.http import HttpResponse |
||||
from django.template.response import TemplateResponse |
||||
from django.views.decorators.csrf import csrf_exempt |
||||
|
||||
from robokassa.conf import USE_POST |
||||
from robokassa.forms import ( |
||||
ResultURLForm, SuccessRedirectForm, FailRedirectForm) |
||||
from robokassa.models import SuccessNotification |
||||
from robokassa.signals import ( |
||||
result_received, success_page_visited, fail_page_visited) |
||||
|
||||
|
||||
@csrf_exempt |
||||
def receive_result(request, **credentials): |
||||
""" |
||||
Обработчик для ResultURL |
||||
""" |
||||
data = request.POST if USE_POST else request.GET |
||||
form = ResultURLForm(data, **credentials) |
||||
print(form.is_valid()) |
||||
if form.is_valid(): |
||||
inv_id = form.cleaned_data['InvId'] |
||||
out_sum = form.cleaned_data['OutSum'] |
||||
print('----------------------------------------------------') |
||||
print(form.cleaned_data['InvId'], form.cleaned_data['OutSum']) |
||||
# сохраняем данные об успешном уведомлении в базе, чтобы |
||||
# можно было выполнить дополнительную проверку на странице успешного |
||||
# заказа |
||||
notification = SuccessNotification.objects.create( |
||||
InvId=inv_id, OutSum=out_sum |
||||
) |
||||
|
||||
# дополнительные действия с заказом (например, смену его статуса) можно |
||||
# осуществить в обработчике сигнала robokassa.signals.result_received |
||||
result_received.send( |
||||
sender=notification, InvId=inv_id, OutSum=out_sum, |
||||
extra=form.extra_params()) |
||||
|
||||
return HttpResponse('OK%s' % inv_id) |
||||
return HttpResponse('error: bad signature') |
||||
|
||||
|
||||
@csrf_exempt |
||||
def success(request, template_name='robokassa/success.html', |
||||
extra_context=None, |
||||
error_template_name='robokassa/error.html', **credentials): |
||||
""" |
||||
Обработчик для SuccessURL. |
||||
""" |
||||
|
||||
data = request.POST if USE_POST else request.GET |
||||
form = SuccessRedirectForm(data, **credentials) |
||||
if form.is_valid(): |
||||
inv_id = form.cleaned_data['InvId'] |
||||
out_sum = form.cleaned_data['OutSum'] |
||||
|
||||
# в случае, когда не используется строгая проверка, действия с заказом |
||||
# можно осуществлять в обработчике сигнала |
||||
# robokassa.signals.success_page_visited |
||||
success_page_visited.send( |
||||
sender=form, InvId=inv_id, OutSum=out_sum, |
||||
extra=form.extra_params()) |
||||
|
||||
context = {'InvId': inv_id, 'OutSum': out_sum, 'form': form} |
||||
context.update(form.extra_params()) |
||||
context.update(extra_context or {}) |
||||
return TemplateResponse(request, template_name, context) |
||||
|
||||
return TemplateResponse(request, error_template_name, {'form': form}) |
||||
|
||||
|
||||
@csrf_exempt |
||||
def fail(request, template_name='robokassa/fail.html', extra_context=None, |
||||
error_template_name='robokassa/error.html', **credentials): |
||||
""" |
||||
Обработчик для FailURL. |
||||
""" |
||||
|
||||
data = request.POST if USE_POST else request.GET |
||||
form = FailRedirectForm(data, **credentials) |
||||
if form.is_valid(): |
||||
inv_id = form.cleaned_data['InvId'] |
||||
out_sum = form.cleaned_data['OutSum'] |
||||
|
||||
# дополнительные действия с заказом (например, смену его статуса для |
||||
# разблокировки товара на складе) можно осуществить в обработчике |
||||
# сигнала robokassa.signals.fail_page_visited |
||||
fail_page_visited.send( |
||||
sender=form, InvId=inv_id, OutSum=out_sum, |
||||
extra=form.extra_params()) |
||||
|
||||
context = {'InvId': inv_id, 'OutSum': out_sum, 'form': form} |
||||
context.update(form.extra_params()) |
||||
context.update(extra_context or {}) |
||||
return TemplateResponse(request, template_name, context) |
||||
|
||||
return TemplateResponse(request, error_template_name, {'form': form}) |
||||
@ -0,0 +1,6 @@ |
||||
{% extends 'base.html' %} |
||||
{% block content %} |
||||
Ошибка при оплате |
||||
{{ form.as_p }} |
||||
|
||||
{% endblock content %} |
||||
@ -0,0 +1,4 @@ |
||||
{% extends 'base.html' %} |
||||
{% block content %} |
||||
Неудачная оплата |
||||
{% endblock content %} |
||||
@ -0,0 +1,7 @@ |
||||
{% extends 'base.html' %} |
||||
{% block content %} |
||||
<form action="{{ form.target }}" method="GET"> |
||||
<p>{{ form.as_p }}</p> |
||||
<p><input type="submit" value="Оплатить"></p> |
||||
</form> |
||||
{% endblock %} |
||||
@ -0,0 +1,4 @@ |
||||
{% extends 'base.html' %} |
||||
{% block content %} |
||||
Оплата успешна |
||||
{% endblock content %} |
||||
Loading…
Reference in new issue