diff --git a/.travis.yml b/.travis.yml index 0e63505..df230e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ language: python python: - "2.7" + - "3.3" + - "3.4" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: pip install -r requirements-test.txt diff --git a/HISTORY.rst b/HISTORY.rst index 9494aac..e2b286e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -2,6 +2,12 @@ History ------- +0.3.5 (2015-11-11) +++++++++++++++++++ +- Support Django 1.8 +- Support Py33 and Py34 +- Return proper IPN response + 0.3.4 (2015-08-12) ++++++++++++++++++ - Restructure flow to better support IPN processing diff --git a/django_pesapal/__init__.py b/django_pesapal/__init__.py index bfeb9e7..40ed83d 100644 --- a/django_pesapal/__init__.py +++ b/django_pesapal/__init__.py @@ -1 +1 @@ -__version__ = '0.3.4' +__version__ = '0.3.5' diff --git a/django_pesapal/templates/django_pesapal/post_payment.html b/django_pesapal/templates/django_pesapal/post_payment.html index 0de639d..47efd1c 100644 --- a/django_pesapal/templates/django_pesapal/post_payment.html +++ b/django_pesapal/templates/django_pesapal/post_payment.html @@ -18,6 +18,7 @@

{% trans "Redirect Page" %}

{{ message }} {% trans "Finish Ordering" %} + {% trans "Check Status" %} diff --git a/django_pesapal/tests.py b/django_pesapal/tests.py new file mode 100644 index 0000000..97a580c --- /dev/null +++ b/django_pesapal/tests.py @@ -0,0 +1,7 @@ + +from django.test import TestCase + + +class UserModelTestCase(TestCase): + + pass diff --git a/django_pesapal/urls.py b/django_pesapal/urls.py index 7d8a6c7..d5a2fd5 100644 --- a/django_pesapal/urls.py +++ b/django_pesapal/urls.py @@ -1,6 +1,10 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + from django.conf.urls import patterns, url -import views +from . import views urlpatterns = patterns( '', diff --git a/django_pesapal/views.py b/django_pesapal/views.py index 73dba0a..267c975 100644 --- a/django_pesapal/views.py +++ b/django_pesapal/views.py @@ -1,8 +1,15 @@ +# -*- coding: utf-8 -*- + +from __future__ import absolute_import + +import logging +import oauth2 as oauth +import requests from django.contrib.sites.models import Site from django.core.urlresolvers import reverse from django.db.models.loading import get_model -from django.http import HttpResponse +from django.http import HttpResponse, QueryDict from django.shortcuts import get_object_or_404 from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ @@ -10,12 +17,8 @@ from xml.etree import cElementTree as ET -import conf as settings +from . import conf as settings -import logging -import oauth2 as oauth -import requests -import urllib DEFAULT_TYPE = "MERCHANT" Transaction = get_model(settings.PESAPAL_TRANSACTION_MODEL) @@ -148,12 +151,14 @@ def get_payment_status(self, **kwargs): class PaymentResponseMixin(object): def build_url_params(self): - url_params = '?' + urllib.urlencode( + url_params = QueryDict(mutable=True) + url_params.update( { 'pesapal_merchant_reference': self.transaction.merchant_reference, 'pesapal_transaction_tracking_id': self.transaction.pesapal_transaction } ) + url_params = '?' + url_params.urlencode() return url_params def get_payment_status_url(self): @@ -196,6 +201,7 @@ def get_context_data(self, **kwargs): ctx = super(TransactionCompletedView, self).get_context_data(**kwargs) ctx['transaction_completed_url'] = self.get_order_completion_url() + ctx['transaction_status_url'] = self.get_payment_status_url() ctx['payment_status'] = self.transaction.payment_status if self.transaction.payment_status == Transaction.PENDING: @@ -265,18 +271,30 @@ class TransactionStatusView(UpdatePaymentStatusMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): params = self.get_params() - self.process_payment_status() # redirect back to Transaction completed view url = reverse('transaction_completed') - url += '?' + urllib.urlencode(params) + + query_dict = QueryDict(mutable=True) + query_dict.update(params) + url += '?' + query_dict.urlencode() return url class IPNCallbackView(UpdatePaymentStatusMixin, PaymentResponseMixin, View): + def build_ipn_response(self): + params = self.get_params() + params['pesapal_notification_type'] = self.request.GET.get('pesapal_notification_type') + + query_dict = QueryDict(mutable=True) + query_dict.update(params) + response = query_dict.urlencode() + return HttpResponse(response) + def get(self, request, *args, **kwargs): self.process_payment_status() - return HttpResponse('OK') + response = self.build_ipn_response() + return response diff --git a/docs/usage.rst b/docs/usage.rst index 3e879f5..9aa1711 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -2,40 +2,55 @@ Usage ====== -Take a look at the sandbox app for a quick overview on how to use `django-pesapal`. +Install django-pesapal:: -To use django-pesapal in a project:: + pip install django-pesapal + +Then use it in a project:: + + import django_pesapal + +#. Add `django_pesapal` to your `INSTALLED_APPS` setting like this:: + + INSTALLED_APPS = ( + ... + 'django_pesapal', + ) + +#. Include the `django_pesapal` URLconf in your project urls.py like this:: + + url(r'^payments/', include('django_pesapal.urls')), + +#. You can set your own return url by adding this to `settings.py`:: + + PESAPAL_TRANSACTION_DEFAULT_REDIRECT_URL = 'app_name:url_name' # this needs to be a reversible + +#. Run `python manage.py migrate` to create the models. + +#. Create a method that receives payment details and returns the pesapal iframe url:: - from django.views.generic import TemplateView from django_pesapal.views import PaymentRequestMixin - from django_pesapal.models import Transaction - class PaymentView(TemplateView, PaymentRequestMixin): - """ - This view returns secure payment form from pesapal - """ - template_name = 'testapp/payment.html' + class PaymentView(PaymentRequestMixin): - def get_context_data(self, **kwargs): - ctx = super(PaymentView, self).get_context_data(**kwargs) + def get_pesapal_payment_iframe(self): + ''' + Authenticates with pesapal to get the payment iframe src + ''' order_info = { 'first_name': 'Some', 'last_name': 'User', 'amount': 100, 'description': 'Payment for X', - 'reference': 2, - 'email': 'pesapal@example.com' + 'reference': 2, # some object id + 'email': 'user@example.com', } - # get_payment_url returns the URL of the secure payment form from pesapal - # You can use this URL in an iframe - ctx['pesapal_url'] = self.get_payment_url(**order_info) - return ctx - + iframe_src_url = self.get_payment_url(**order_info) + return iframe_src_url -Once processing is complete the user will be redirected to the intermediate processing where -they can update check the status of the payment +#. Once payment has been processed, you will be redirected to an intermediate screen where the user can finish ordering. Clicking the "Finish Ordering" button will check the payment status to ensure that the payment was successful and then redirects the user to `PESAPAL_TRANSACTION_DEFAULT_REDIRECT_URL`. **NOTE:** You can override the intermediate (`post_payment.html`) processing template if you need to have a customized look. diff --git a/requirements.txt b/requirements.txt index d1599b2..813ba08 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -Django>=1.7,<=1.7.8 -wheel==0.23.0 -oauth2==1.5.211 -requests==2.1 +Django>=1.7 +wheel==0.26.0 +oauth2==1.9.0.post1 +requests[security]>=2.8.1 # Custom requirements git+https://github.com/dcramer/django-uuidfield.git@9bd27e9#egg=django-uuidfield diff --git a/runtests.py b/runtests.py index ad7416c..0bf8930 100644 --- a/runtests.py +++ b/runtests.py @@ -42,10 +42,10 @@ def run_tests(*test_args): if not test_args: - test_args = ['tests'] + test_args = ['django_pesapal.tests'] # Run tests - test_runner = NoseTestSuiteRunner(verbosity=1) + test_runner = NoseTestSuiteRunner() failures = test_runner.run_tests(test_args) diff --git a/sandbox/sandbox/settings.py b/sandbox/sandbox/settings.py index c251004..5ff5130 100644 --- a/sandbox/sandbox/settings.py +++ b/sandbox/sandbox/settings.py @@ -119,6 +119,10 @@ PESAPAL_OAUTH_CALLBACK_URL ='transaction_completed' PESAPAL_OAUTH_SIGNATURE_METHOD ='SignatureMethod_HMAC_SHA1' PESAPAL_TRANSACTION_DEFAULT_REDIRECT_URL = 'payment' +PESAPAL_DEMO = True +PESAPAL_OAUTH_CALLBACK_URL = 'transaction_completed' +PESAPAL_OAUTH_SIGNATURE_METHOD = 'SignatureMethod_HMAC_SHA1' +PESAPAL_TRANSACTION_DEFAULT_REDIRECT_URL = 'transaction_completed' PESAPAL_TRANSACTION_FAILED_REDIRECT_URL = '' PESAPAL_ITEM_DESCRIPTION = False PESAPAL_TRANSACTION_MODEL = 'testapp.Transaction' @@ -128,4 +132,4 @@ try: from local_config import * except ImportError: - pass \ No newline at end of file + pass diff --git a/setup.py b/setup.py index 1621941..93edeff 100644 --- a/setup.py +++ b/setup.py @@ -34,10 +34,10 @@ packages=find_packages(exclude=['sandbox*']), package_data={'django_pesapal': ['templates/django_pesapal/*.html']}, install_requires=[ - 'Django>=1.7,<=1.7.8', - 'oauth2==1.5.211', + 'Django>=1.7', + 'oauth2==1.9.0.post1', 'django-uuidfield<=0.6.0', - 'requests==2.1', + 'requests[security]==2.8.1', ], dependency_links=[ "https://github.com/dcramer/django-uuidfield/archive/9bd27e9.zip#egg=django-uuidfield-0.6.0", @@ -55,7 +55,9 @@ 'Natural Language :: English', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], -) \ No newline at end of file +) diff --git a/tox.ini b/tox.ini index f9d0793..fa9abfa 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py33 +envlist = py27, py33, py34 [testenv] setenv =