django-getpaid
Multi-broker payment processing for django
Problem to be solved
What's already there?
Satchmo, python-payflowpro, django-authorizenet, mamona, django-paypal, django-payme and other...
None meet my requirements
starting new project django-getpaid
(heavily based on mamona)
Status of project
Code & Docs
PyPi
$ pip install django-getpaid
GitHub
$ pip install git+https://github.com/cypreess/django-getpaid.git
Documentation
https://django-getpaid.readthedocs.org/
Integrations steps
1. INSTALLED_APPS
Very straightforward:
INSTALLED_APPS += ('getpaid', )
2. GETPAID_BACKENDS
An iterable of backends that should be enabled:
Payment backend is identified by fully qualified Python import path, so it can be imported from any module. Backends are also django-apps that need to installed:
GETPAID_BACKENDS = ('getpaid.backends.dummy',� 'getpaid.backends.payu', )
INSTALLED_APPS += GETPAID_BACKENDS
3. GETPAID_BACKENDS_SETTINGS
A dict with keys being backends paths with dicts of configuration values for a given backend. E.g.
Strictly dependent on backend. Refer to docs.
GETPAID_BACKENDS_SETTINGS = {� 'getpaid.backends.payu' : {� 'pos_id' : 123456,� 'key1' : 'xxxxxxxxxxxxx',� 'key2' : 'xxxxxxxxxxxxx',� 'pos_auth_key': 'xxxxxxxxx',� 'signing' : True, # optional� },�}
4. urls.py
getpaid will also automatically discover and register all urls.py from enabled backends
url(r'', include('getpaid.urls')),
5. Order model
from django.core.urlresolvers import reverse�from django.db import models�import getpaid�class Order(models.Model):� name = models.CharField(max_length=100)� total = models.DecimalField(decimal_places=2, max_digits=8, default=0)� currency = models.CharField(max_length=3, default='EUR')� status = models.CharField(max_length=1, blank=True, default='W', choices=(('W', 'Waiting for payment'), ('P', 'Payment complete')))� def get_absolute_url(self):� return reverse('order_detail', kwargs={'pk': self.pk})� def __unicode__(self):� return self.name��getpaid.register_to_payment(Order, unique=False, related_name='payments')
6. Listeners (1/2)
Getpaid will send a query signal:
An example listener for it:
new_payment_query = Signal(providing_args=['order', 'payment'])
from getpaid.signals import new_payment_query
from django.dispatch import receiver
�@receiver(new_payment_query)
def new_payment_query_listener(sender, order=None,
payment=None, **kwargs):� payment.amount = order.total� payment.currency = order.currency��
6. Listeners (2/2)
You should also do something on successful payment. At least change order status?
Example listener:
��
payment_status_changed = Signal(providing_args=['old_status', 'new_status'])
from getpaid.signals import payment_status_changed
from django.dispatch import receiver
@receiver(payment_status_changed)�def payment_status_changed_listener(sender, instance, old_status,
new_status, **kwargs):� if old_status != 'paid' and new_status == 'paid':� # Ensures that we process order only one� instance.order.status = 'P'� instance.order.save()
7. Payment form (1/2)
Finally we can display a form to make payment.
Your example Order view code:
Form will filter a list of available payment backend to those supporting given currency.
from django.views.generic.detail import DetailView�from getpaid.forms import PaymentMethodForm�from getpaid_test_project.orders.models import Order��class OrderView(DetailView):� model=Order�� def get_context_data(self, **kwargs):� context = super(OrderView, self).get_context_data(**kwargs)� context['payment_form'] = PaymentMethodForm(self.object.currency, initial={'order': self.object})� return context
7. Payment form (2/2)
and django template boilerplate:
<form action="{% url getpaid-new-payment currency=object.currency %}" method="post">� {% csrf_token %}� {{ payment_form.as_p }}� <input type="submit" value="Continue">�</form>
Highlights of backend design
Example: PayU backend
$. /manage.py payu_configuration�Login to PayU configuration page and setup following links:�� * Success URL: http://example.com/getpaid.backends.payu/success/%orderId%/� https://example.com/getpaid.backends.payu/success/%orderId%/�� * Failure URL: http://example.com/getpaid.backends.payu/failure/%orderId%/� https://example.com/getpaid.backends.payu/failure/%orderId%/�� * Online URL: http://example.com/getpaid.backends.payu/online/� https://example.com/getpaid.backends.payu/online/��To change domain name please edit Sites settings. Don't forget to setup your web server to accept https connection in order to use secure links.��Request signing is ON� * Please be sure that you enabled signing payments in PayU configuration page.
Payment flow diagram
OrderView
(template with PaymentForm)
getpaid�NewPaymentView
PaymentProcessor�.get_gateway_url()
Payment broker�system
PaymentProcessor�SuccessView / FailureView
getpaid�FallbackView
Order
.get_absolute_url()
PaymentProcessor�OnlineView�(custom)
PaymentFactory model
Payment statuses
Other statuses are not really important from the perspective of an order processing
South migrations? How
Each Payment model class (and DB table) is custom for each application, therefore no common migrations can be added to django-getpaid.
But south is great and already has what we need! Use following settings variable:
Now you can make migrations by your own and store them with your project.
SOUTH_MIGRATION_MODULES = {� 'getpaid' : 'yourproject.migrations.getpaid',� 'payu' : 'yourproject.migrations.getpaid_payu',�}
Tricky things #1
class SetRemoteAddrFromForwardedFor(object):� def process_request(self, request):� try:� real_ip = request.META['HTTP_X_FORWARDED_FOR']� except KeyError:� pass� else:� # HTTP_X_FORWARDED_FOR can be a comma-separated list of IPs.� # Take just the first one.� real_ip = real_ip.split(",")[0]� request.META['REMOTE_ADDR'] = real_ip
Tricky things #2
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Thank you
Contributors are welcome via github fork+pull request
https://github.com/cypreess/django-getpaid
Krzysztof Dorosz
github: https://github.com/cypreess
twitter: @krzysztofdorosz
linkedin: http://www.linkedin.com/in/krzysztofdorosz
e-mail: cypreess@gmail.com