Mikhail Korobov avatar Mikhail Korobov committed 253715f

Initial import

Comments (0)

Files changed (12)

+syntax: glob
+
+#IDE files
+.settings/*
+.project
+.pydevproject
+.cache/*
+nbproject/*
+.buildpath
+build.properties
+
+#temp files
+*.pyc
+*.pyo
+*.orig
+*~
+
+#misc files
+pip-log.txt
+
+#os files
+.DS_Store
+Thumbs.db
+
+#doc files
+docs/_build/doctrees/
+
+#setup files
+build/
+dist/
+.build/
+MANIFEST
+Copyright (c) 2010 Mikhail Korobov
+
+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.
+include *.txt
+include *.rst
+recursive-include docs *.txt
+==============
+django-netcash
+==============
+
+A pluggable Django application for integrating netcash.co.za payment system.
+
+Install
+=======
+
+    $ pip install django-netcash
+
+or ::
+
+    $ easy_install django-netcash
+
+or ::
+
+    $ hg clone http://bitbucket.org/kmike/django-netcash/
+    $ cd django-netcash
+    $ python setup.py install
+
+
+Then add 'netcash' to INSTALLED_APPS and execute ::
+
+    $ python manage.py syncdb
+
+or (if South is in use) ::
+
+    $ python manage.py migrate
+
+
+Settings
+========
+
+Specify your credentials in settings.py:
+
+* NETCASH_USERNAME
+* NETCASH_PASSWORD
+* NETCASH_PIN
+* NETCASH_TERMINAL_NUMBER
+
+Usage
+=====
+
+Payment form
+------------
+
+netcash.forms.NetcashForm can be used to construct the html form. It is
+a helper form for html output and it shouldn't perform any validation.
+
+Example::
+
+    # views.py
+
+    from django.shortcuts import get_object_or_404
+    from django.views.generic.simple import direct_to_template
+    from django.contrib.auth.decorators import login_required
+
+    from netcash.forms import NetcashForm
+
+    @login_required
+    def pay_with_netcash(request, order_id)
+
+        # Order model have to be defined by user, it is not a part
+        # of django-netcash
+        order = get_object_or_404(Order, pk = order_id)
+
+        form = NetcashForm(initial={
+
+            # required params:
+            'p3': 'description of the goods',
+            'p4': order.total,
+
+            # optional params:
+
+            # 'p10': '/cancel/button/url',
+            # 'Budget': 'Y',  # will display the budget option in the Gateway popup
+            # 'm_4': 'extra param 1',
+            # 'm_5': 'extra param 2',
+            # 'm_6': 'extra param 3',
+            # 'm_9': order.user.email # cardholder email address
+        })
+
+        return direct_to_template(request, 'pay_with_netcash.html', {'form': form})
+
+
+The template::
+
+    {% extends 'base.html' %}
+
+    {% block content %}
+        <form action="{{ form.target }}" method="POST">
+            <p>{{ form.as_p }}</p>
+            <p><input type="submit" value="Pay by Credit Card"></p>
+        </form>
+    {% endblock %}
+
+The form output will be a number of <input type='hidden'> tags.
+
+NetcashForm has a 'target' attribute with Netcash URL.
+
+Please note that it's up to you to provide Order model with any fields you want.
+Order handling should be performed in signal handlers.
+
+
+Получение результатов платежей
+------------------------------
+В Robokassa есть несколько методов определения результата платежа:
+
+1. При переходе на страницы Success и Fail гарантируется, что платеж
+   соответственно прошел и не прошел
+
+2. При успешном или неудачном платеже Robokassa отправляет POST или GET запрос
+   на Result URL.
+
+3. Можно запрашивать статус платежа через XML-сервис.
+
+В django-robokassa на данный момент поддерживаются методы 1 и 2 и их совмещение
+(дополнительная проверка, что при переходе на Success URL уже было уведомление
+на Result URL при использовании опции ROBOKASSA_STRICT_CHECK = True).
+Обработчики подключаются через urls.py, рендерят соответствующие
+шаблоны и шлют сигналы в зависимости от успешности платежа.
+
+
+Signals
+-------
+Обработку смены статусов покупок следует осуществлять в обработчиках сигналов.
+
+* robokassa.signals.result_received - шлется при получении уведомления от
+  Robokassa. Получение этого сигнала означает, что оплата была успешной.
+  В качестве sender передается экземпляр модели SuccessNotification, у
+  которой есть атрибуты InvId и OutSum.
+
+* robokassa.signals.success_page_visited - шлется при переходе пользователя
+  на страницу успешной оплаты. Этот сигнал следует использовать вместо
+  result_received, если не используется строгая проверка
+  (ROBOKASSA_STRICT_CHECK=False)
+
+* robokassa.signals.fail_page_visited - шлется при переходе пользователя
+  на страницу ошибки оплаты. Получение этого сигнала означает, что оплата
+  не была произведена. В обработчике следует осуществлять разблокирвку товара
+  на складе и т.д.
+
+Все сигналы получают параметры InvId (номер заказа), OutSum (сумма оплаты) и
+extra (словарь с дополнительными параметрами, описанными в
+ROBOKASSA_EXTRA_PARAMS).
+
+Пример::
+
+    from robokassa.signals import result_received
+    from my_app.models import Order
+
+    def payment_received(sender, **kwargs):
+        order = Order.objects.get(id=kwargs['InvId'])
+        order.status = 'paid'
+        order.paid_sum = kwargs['OutSum']
+        order.extra_param = kwargs['extra']['my_param']
+        order.save()
+
+    result_received.connect(payment_received)
+
+
+
+urls.py
+-------
+
+In order to get Data URL, Success URL and Fail URL up and running,
+include netcash.urls in your urls.py::
+
+    urlpatterns = patterns('',
+        #...
+        url(r'^netcash/', include('netcash.urls')),
+        #...
+    )
+
+Адреса, которые нужно указывать в панели robokassa, в этом случае будут иметь вид
+
+* Result URL: ``http://yoursite.ru/robokassa/result/``
+* Success URL: ``http://yoursite.ru/robokassa/success/``
+* Fail URL: ``http://yoursite.ru/robokassa/fail/``
+
+
+Templates
+---------
+
+* ``robokassa/success.html`` - показывается в случае успешной оплаты. В
+  контексте есть переменная form типа ``SuccessRedirectForm``, InvId
+  и OutSum с параметрами заказа, а также все дополнительные параметры, описанные
+  в ROBOKASSA_EXTRA_PARAMS.
+
+* ``robokassa/fail.html`` - показывается в случае неуспешной оплаты. В
+  контексте есть переменная form типа ``FailRedirectForm``, InvId
+  и OutSum с параметрами заказа, а также все дополнительные параметры, описанные
+  в ROBOKASSA_EXTRA_PARAMS.
+
+* ``robokassa/error.html`` - показывается при ошибочном запросе к странице
+  "успех" или "неудача" (например, при ошибке в контрольной сумме). В контексте
+  есть переменная form класса ``FailRedirectForm`` или ``SuccessRedirectForm``.

Empty file added.

+from django.conf import settings
+
+NETCASH_TARGET_URL = 'https://gateway.netcash.co.za/vvonline/ccnetcash.asp'
+
+NETCASH_USERNAME = getattr(settings, 'NETCASH_USERNAME', 'testuser')
+NETCASH_PASSWORD = getattr(settings, 'NETCASH_PASSWORD', 'testpass')
+NETCASH_PIN = getattr(settings, 'NETCASH_PIN', '654321')
+NETCASH_TERMINAL_NUMBER = getattr(settings, 'NETCASH_TERMINAL_NUMBER', '0291')
+
+# NETCASH_TEST_MODE = getattr(settings, 'NETCASH_TEST_MODE', False)
+from django import forms
+
+from netcash.conf import *
+from netcash.models import NetcashOrder
+
+
+class HiddenForm(forms.Form):
+    """ A form with all fields hidden """
+    def __init__(self, *args, **kwargs):
+        super(HiddenForm, self).__init__(*args, **kwargs)
+        for field in self.fields:
+            self.fields[field].widget = forms.HiddenInput()
+
+
+class NetcashForm(HiddenForm):
+    """ NetCash helper form.
+    It is not for validating data.
+    It can be used to output html. """
+
+    target = NETCASH_TARGET_URL
+
+    # these params are handled automatically
+    m_1 = forms.CharField(max_length = 50, initial = NETCASH_USERNAME)
+    m_2 = forms.CharField(max_length = 50, initial = NETCASH_PASSWORD)
+    m_3 = forms.CharField(max_length = 50, initial = NETCASH_PIN)
+    p1 = forms.CharField(max_length = 50, initial = NETCASH_TERMINAL_NUMBER)
+    p2 = forms.CharField(max_length = 25)
+
+    # The description of the goods sent for payment
+    p3 = forms.CharField(max_length = 50)
+
+    # Transactional Amount that is to be settled to the Card
+    p4 = forms.DecimalField(min_value=0, max_decimal_places=2, max_digits=8)
+
+    # Cancel Button URL. This is the URL the client will be directed
+    # to when at anytime the client clicks the cancel button.
+    p10 = forms.URLField(max_length=255, verify_exists=False, required=False)
+
+    # 'Y' will display the budget option in the Gateway popup
+    # 'N' will not display the budget option in the Gateway popup
+    Budget = forms.ChoiceField(choices=('Y', 'N'), initial='N')
+
+    # Extra fields that can contain any data that you require
+    # back from the gateway once the settlement has been done
+    m_4 = forms.CharField(max_length=50, required=False)
+    m_5 = forms.CharField(max_length=50, required=False)
+    m_6 = forms.CharField(max_length=50, required=False)
+
+    # Card holders email address should you want an email sent to the cardholder
+    m_9 = forms.CharField(max_length=100, required=False)
+
+    # Any text sent in this parameter is returned to the Accept and Reject
+    # return URL’s. This is usually used with basket products like
+    # OSCommerce and VirtueMart
+    m_10 = forms.CharField(max_length=100, required=False)
+
+    def __init__(self, *args, **kwargs):
+        super(NetcashForm, self).__init__(*args, **kwargs)
+
+        # new order reference number is issued each time form is instantiated
+        self.order = NetcashOrder.objects.create()
+        self.fields['p2'].initial = self.order.pk
+
+
+class NetcashResultForm(forms.ModelForm):
+    class Meta:
+        model = NetcashOrder
+        exclude = ['created_at']
+
+
+class DataHandlerForm(NetcashResultForm):
+
+    # This fields are named differently on data page for some reason
+    m_4 = forms.CharField(max_length=50, required=False)
+    m_5 = forms.CharField(max_length=50, required=False)
+    m_6 = forms.CharField(max_length=50, required=False)
+
+    def save(self, *args, **kwargs):
+        self.cleaned_data['Extra1'] = self.cleaned_data['m_4']
+        self.cleaned_data['Extra2'] = self.cleaned_data['m_5']
+        self.cleaned_data['Extra3'] = self.cleaned_data['m_6']
+        super(DataHandlerForm, self).save(*args, **kwargs)

netcash/models.py

+#coding: utf-8
+
+from datetime import datetime
+from django.db.models import Model, AutoField, NullBooleanField, IPAddressField, \
+                             CharField, DateTimeField, DecimalField
+
+class NetcashOrder(Model):
+
+    # field names are not pep-08 in order to match Netcash API docs
+
+    Reference = AutoField(primary_key=True,
+                          help_text='This is the unique reference that have '
+                          'been sent to Netcash in the original request')
+
+    TransactionAccepted = NullBooleanField(default=None)
+    CardHolderIpAddr = IPAddressField(u'Cardholder ip address', null=True, blank=True)
+    Amount = DecimalField(min_value=0, max_decimal_places=2, max_digits=12, null=True, blank=True)
+
+    Reason = CharField(max_length=255, null=True, blank=True,
+                       help_text='If the transaction failed this is the reason '
+                       'returned for the failure')
+
+    RETC = CharField(max_length=25, null=True, blank=True,
+                     help_text = "This is a code returned by the system for "
+                    "this transaction. Should you need to make an "
+                    "enquiry you will use this number to reference "
+                    "this transaction")
+
+    Extra1 = CharField(null=True, blank=True, max_length = 50)
+    Extra2 = CharField(null=True, blank=True, max_length = 50)
+    Extra3 = CharField(null=True, blank=True, max_length = 50)
+
+    created_at = DateTimeField(default=datetime.now)
+
+    def __unicode__(self):
+        return u'NetCash #%s (%s)' % (self.pk, self.created_at)
+
+    class Meta:
+        verbose_name = 'NetCash order'
+        verbose_name_plural = 'NetCash order'

netcash/signals.py

+#coding: utf-8
+from django.dispatch import Signal
+
+accept = Signal(providing_args=['order'])
+reject = Signal(providing_args=['order'])
+data = Signal(providing_args=['order'])
+#coding: utf-8
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('netcash.views',
+    url(
+          r'^data/$',
+          'data_handler',
+          name='netcash_data'
+    ),
+    url(
+          r'^accept/$',
+          'accept_handler',
+          name='netcash_accept'
+    ),
+    url(
+          r'^reject/$',
+          'reject_handler',
+          name='netcash_reject'
+    ),
+)
+#coding: utf-8
+from django.http import HttpResponse
+from django.views.generic.simple import direct_to_template
+from django.shortcuts import get_object_or_404
+
+try:
+    from django.views.decorators.csrf import csrf_exempt
+except ImportError: # django < 1.2
+    from django.contrib.csrf.middleware import csrf_exempt
+
+from netcash.forms import DataHandlerForm, NetcashResultForm
+from netcash.models import NetcashOrder
+from netcash import signals
+
+@csrf_exempt
+def data_handler(request):
+    """ Data URL handler. """
+    reference = request.POST.get('Reference', False)
+    order = get_object_or_404(NetcashOrder, pk=reference)
+    form = DataHandlerForm(request.POST, instance = order)
+    if form.is_valid():
+        order = form.save()
+        signals.data.send(sender = data_handler, order=order)
+        return HttpResponse('OK')
+    return HttpResponse('error')
+
+
+@csrf_exempt
+def accept_handler(request, template_name='netcash/accept.html',
+       extra_context=None, error_template_name = 'netcash/error.html'):
+    """ Accept URL handler """
+
+    reference = request.POST.get('Reference', False)
+    order = get_object_or_404(NetcashOrder, pk=reference)
+    form = NetcashResultForm(request.POST, instance = order)
+
+    if form.is_valid():
+        order = form.save()
+        signals.accept.send(sender = data_handler, order=order)
+
+        context = {'order': order, 'form': form}
+        context.update(extra_context or {})
+        return direct_to_template(request, template_name, extra_context=context)
+
+    return direct_to_template(request, error_template_name,
+                              extra_context={'form': form})
+
+
+@csrf_exempt
+def reject_handler(request, template_name='netcash/reject.html',
+       extra_context=None, error_template_name = 'netcash/error.html'):
+    """ Reject URL handler """
+
+    reference = request.POST.get('Reference', False)
+    order = get_object_or_404(NetcashOrder, pk=reference)
+    form = NetcashResultForm(request.POST, instance = order)
+
+    if form.is_valid():
+        order = form.save()
+        signals.reject.send(sender = data_handler, order=order)
+
+        context = {'order': order, 'form': form}
+        context.update(extra_context or {})
+        return direct_to_template(request, template_name, extra_context=context)
+
+    return direct_to_template(request, error_template_name,
+                              extra_context={'form': form})
+#!/usr/bin/env python
+from distutils.core import setup
+
+setup(
+    name='django-netcash',
+    version='0.1',
+    author='Mikhail Korobov',
+    author_email='kmike84@gmail.com',
+
+    packages=['netcash', 'netcash.migrations'],
+
+    url='http://bitbucket.org/kmike/django-netcash/',
+    download_url = 'http://bitbucket.org/kmike/django-netcash/get/tip.gz',
+    license = 'MIT license',
+    description = 'A pluggable Django application for integrating netcash.co.za payment system.',
+    long_description = open('README.rst').read().decode('utf8'),
+
+    classifiers=(
+        'Development Status :: 3 - Alpha',
+        'Environment :: Web Environment',
+        'Framework :: Django',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: MIT License',
+        'Programming Language :: Python',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+    ),
+)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.