django-getpaid is a multi-broker payment processor for Django
The full documentation is at https://django-getpaid.readthedocs.io.
Install django-getpaid:
pip install django-getpaid
Add it to your INSTALLED_APPS:
INSTALLED_APPS = (
...
'getpaid.apps.GetpaidConfig',
...
)
Add django-getpaid's URL patterns:
from getpaid import urls as getpaid_urls
urlpatterns = [
...
url(r'^', include(getpaid_urls)),
...
]
- multiple payment brokers support - allows using simultaneously many payments methods at the same time,
- multiple payments currency support (getpaid will automatically filter available backends list accordingly to the payment currency),
- integration flexibility - makes minimal assumption on 3rd party code - requires only an existence of any single django model representing an order,
- proper architecture design - all backends which requires fetching payment confirmation are enforced to use asynchronous celery tasks.
In alphabetical order:
Don't see the payment backend you need? Writing your own backend is very simple. Pull requests are welcome.
With few simple steps you will easily integrate your project with django-getpaid. This module is shipped with very well documented django-getpaid test project which can be found with module source code. Please refer to this code for implementation details.
Required
First of all you need a model that will represent an order in you application. It does not matter how complicated the model is or what fields does it provide, be it single item order, or multiple items order. Let's take an example from test project:
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 Payment = getpaid.register_to_payment(Order, unique=False, related_name='payments')
and add the following line to your settings:
GETPAID_ORDER_MODEL = 'my_super_app.Order'
First of all, class name is not important at all. You register a model with register_to_payment
method.
You can add some kwargs that are basically used for ForeignKey
kwargs. In this example whe allow creating multiple payments for one order, and naming One-To-Many relation.
There are two important things on that model. In fact two methods are required to be present in order class.
The first one is __unicode__
method as this will be used in few places as a fallback for generating
order description. The second one is get_absolute_url
method which should return an URL of order object.
It is used again as a fallback for some final redirections after payment success of failure (if you do not provide otherwise).
The second important thing is, that it actually doesn't matter if you store total in database or just sum it up from some items. You will see why, in further sections.
Required
Your application - after some custom workflow - just created an order object. That's fine. We now want to get paid for that order. So lets take a look on a view for creating a payment for an order:
from django.views.generic.detail import DetailView from getpaid.forms import PaymentMethodForm from example.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
Here we get a PaymentMethodForm
object, that is parametrised with currency type.
This is an important thing, because this form will display you only payments method that are suitable
for a given order currency.
PaymentMethodForm
provides two fields: HiddenInput with order_id and ChoiceField with backend name. This is how you use it in template:
<form action="{% url 'getpaid:new-payment' currency=object.currency %}" method="post"> {% csrf_token %} {{ payment_form.as_p }} <input type="submit" value="Continue"> </form>
Action URL of form should point on named link getpaid:new-payment that requires currency code argument. This form will redirect client from order view directly to page of payment broker.
Required
Because the idea of whole module is that it should be loosely coupled, there is this convention that it does
not require any structure of your order model. But still it needs to know some transaction details of your order.
Django signals are used for that. django-getpaid, while generating gateway redirect url, will emit
a getpaid.signals.new_payment_query
signal. Here is the signal declaration:
new_payment_query = Signal(providing_args=['order', 'payment']) new_payment_query.__doc__ = """ Sent to ask for filling Payment object with additional data: payment.amount: total amount of an order payment.currency: amount currency This data cannot be filled by ``getpaid`` because it is Order structure agnostic. After filling values just return. Saving is done outside signal. """
Your code should have some signal listeners, that will fill payment object with required information:
from getpaid import signals def new_payment_query_listener(sender, order=None, payment=None, **kwargs): """ Here we fill only two obligatory fields of payment, and leave signal handler """ payment.amount = order.total payment.currency = order.currency signals.new_payment_query.connect(new_payment_query_listener)
So this is a little piece of logic that you need to provide to map your order to payment object. As you can see you can do all fancy stuff here to get order total value and currency code.
Note
If you don't know where to put your listeners code, we recommend to put it in listeners.py
file
and then add a line import listeners
to the end of you models.py
file. Both files
(listeners.py
and models.py
) should be placed in on of your app (possibly an app related to order model).
Required
Signals are also used to inform you that some particular payment just change status. In this case you will
use getpaid.signals.payment_status_changed
signal which is defined as:
payment_status_changed = Signal(providing_args=['old_status', 'new_status']) payment_status_changed.__doc__ = """Sent when Payment status changes."""
example code that handles status change:
from getpaid import signals def payment_status_changed_listener(sender, instance, old_status, new_status, **kwargs): """ Here we will actually do something, when payment is accepted. E.g. lets change an order status. """ if old_status != 'paid' and new_status == 'paid': # Ensures that we process order only one instance.order.status = 'P' instance.order.save() signals.payment_status_changed.connect(payment_status_changed_listener)
For example: when payment changes status to 'paid', it means that the necessary amount was verified
by your payment broker. You can now access payment.order
object and do some stuff here.
Optional
For some reasons you may want to make some additiona checks before a new
Payment is created or add some extra validation before the user is redirected
to gateway url. You can handle this with
getpaid.signals.order_additional_validation
signal defined as:
order_additional_validation = Signal(providing_args=['request', 'order', 'backend']) order_additional_validation.__doc__ = """ A hook for additional validation of an order. Sent after PaymentMethodForm is submitted but before Payment is created and before user is redirected to payment gateway. """
It may also (e.g. for KPI benchmarking) be important for you to how many
and which payments were made.
You can handle getpaid.signals.new_payment
signal defined as:
new_payment = Signal(providing_args=['order', 'payment']) new_payment.__doc__ = """Sent after creating new payment."""
Note
This method will enable you to make on-line KPI processing. For batch processing you can as well just query the database for Payment model.
Required
Please be sure to read carefully Backends section for information on how to configure particular backends. They will probably not work out of the box without providing some account keys or other credentials.
Does the code actually work?
source <YOURVIRTUALENV>/bin/activate (myenv) $ pip install tox (myenv) $ tox
This project has nothing in common with getpaid plone project. It is mostly based on mamona project. This app was written because there was not a single reliable or simple to use payment processor dedicated to django. You can refer to other payment modules which does not meet our needs: Satchmo, python-payflowpro, django-authorizenet, mamona, django-paypal, django-payme.
Proudly sponsored by SUNSCRAPERS
Tools used in rendering this package: