Skip to content

Commit

Permalink
Make the package compatible with various django versions
Browse files Browse the repository at this point in the history
  • Loading branch information
bogdal committed Oct 21, 2015
1 parent 62256b3 commit 2afa08e
Show file tree
Hide file tree
Showing 31 changed files with 229 additions and 193 deletions.
34 changes: 26 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
language: python
python:
- 2.7
- 3.3
- 3.4
sudo: false
install:
- python setup.py install
- pip install coverage
- pip install codecov
- pip install tox codecov
script:
- coverage run setup.py test
- tox
env:
- TOXENV=py27-django14
- TOXENV=py27-django18
- TOXENV=py27-django19
- TOXENV=py27-django_master
- TOXENV=py34-django14
- TOXENV=py34-django18
- TOXENV=py34-django19
- TOXENV=py34-django_master
- TOXENV=py35-django18
- TOXENV=py35-django19
- TOXENV=py35-django_master
matrix:
allow_failures:
- env: TOXENV=py27-django_master
- env: TOXENV=py34-django_master
- env: TOXENV=py35-django_master
after_success:
- codecov
addons:
apt:
sources:
- deadsnakes
packages:
- python3.5
149 changes: 0 additions & 149 deletions payments/__init__.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
from __future__ import unicode_literals
from collections import namedtuple
import re
try:
from urllib.parse import urljoin, urlencode
except ImportError:
from urllib import urlencode
from urlparse import urljoin

from django.conf import settings
from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
from django.db.models import get_model

PAYMENT_VARIANTS = {
'default': ('payments.dummy.DummyProvider', {})}

PAYMENT_HOST = getattr(settings, 'PAYMENT_HOST', None)
PAYMENT_USES_SSL = getattr(settings, 'PAYMENT_USES_SSL', False)

if not PAYMENT_HOST:
if not 'django.contrib.sites' in settings.INSTALLED_APPS:
raise ImproperlyConfigured('The PAYMENT_HOST setting without '
'the sites app must not be empty.')

PurchasedItem = namedtuple('PurchasedItem',
'name, quantity, price, currency, sku')


def get_base_url():
protocol = 'https' if PAYMENT_USES_SSL else 'http'
if not PAYMENT_HOST:
current_site = Site.objects.get_current()
domain = current_site.domain
return '%s://%s' % (protocol, domain)
return '%s://%s' % (protocol, PAYMENT_HOST)


class RedirectNeeded(Exception):
pass

Expand All @@ -46,120 +14,3 @@ class PaymentError(Exception):

class ExternalPostNeeded(Exception):
pass


class BasicProvider(object):
'''
This class defines the provider API. It should not be instantiated
directly. Use factory instead.
'''
_method = 'post'

def get_action(self, payment):
return self.get_return_url(payment)

def __init__(self, capture=True):
self._capture = capture

def get_hidden_fields(self, payment):
'''
Converts a payment into a dict containing transaction data. Use
get_form instead to get a form suitable for templates.
When implementing a new payment provider, overload this method to
transfer provider-specific data.
'''
raise NotImplementedError()

def get_form(self, payment, data=None):
'''
Converts *payment* into a form suitable for Django templates.
'''
from .forms import PaymentForm
return PaymentForm(self.get_hidden_fields(payment),
self.get_action(payment), self._method)

def process_data(self, payment, request):
'''
Process callback request from a payment provider.
'''
raise NotImplementedError()

def get_token_from_request(self, payment, request):
'''
Return payment token from provider request.
'''
raise NotImplementedError()

def get_return_url(self, payment, extra_data=None):
payment_link = payment.get_process_url()
url = urljoin(get_base_url(), payment_link)
if extra_data:
qs = urlencode(extra_data)
return url + '?' + qs
return url

def capture(self, payment, amount=None):
raise NotImplementedError()

def release(self, payment):
raise NotImplementedError()

def refund(self, payment, amount=None):
raise NotImplementedError()


PROVIDER_CACHE = {}


def provider_factory(variant):
'''
Return the provider instance based on variant
'''
variants = getattr(settings, 'PAYMENT_VARIANTS', PAYMENT_VARIANTS)
handler, config = variants.get(variant, (None, None))
if not handler:
raise ValueError('Payment variant does not exist: %s' %
(variant,))
if variant not in PROVIDER_CACHE:
module_path, class_name = handler.rsplit('.', 1)
module = __import__(
str(module_path), globals(), locals(), [str(class_name)])
class_ = getattr(module, class_name)
PROVIDER_CACHE[variant] = class_(**config)
return PROVIDER_CACHE[variant]


def get_payment_model():
'''
Return the Payment model that is active in this project
'''
try:
app_label, model_name = settings.PAYMENT_MODEL.split('.')
except (ValueError, AttributeError):
raise ImproperlyConfigured('PAYMENT_MODEL must be of the form '
'"app_label.model_name"')
payment_model = get_model(app_label, model_name)
if payment_model is None:
msg = (
'PAYMENT_MODEL refers to model "%s" that has not been installed' %
settings.PAYMENT_MODEL)
raise ImproperlyConfigured(msg)
return payment_model


CARD_TYPES = [
(r'^4[0-9]{12}(?:[0-9]{3})?$', 'visa', 'VISA'),
(r'^5[1-5][0-9]{14}$', 'mastercard', 'MasterCard'),
(r'^6(?:011|5[0-9]{2})[0-9]{12}$', 'discover', 'Discover'),
(r'^3[47][0-9]{13}$', 'amex', 'American Express'),
(r'^(?:(?:2131|1800|35\d{3})\d{11})$', 'jcb', 'JCB'),
(r'^(?:3(?:0[0-5]|[68][0-9])[0-9]{11})$', 'diners', 'Diners Club'),
(r'^(?:5[0678]\d\d|6304|6390|67\d\d)\d{8,15}$', 'maestro', 'Maestro')]


def get_credit_card_issuer(number):
for regexp, card_type, name in CARD_TYPES:
if re.match(regexp, number):
return card_type, name
return None, None
3 changes: 2 additions & 1 deletion payments/authorizenet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import requests

from .forms import PaymentForm
from .. import BasicProvider, RedirectNeeded
from .. import RedirectNeeded
from ..core import BasicProvider


class AuthorizeNetProvider(BasicProvider):
Expand Down
3 changes: 2 additions & 1 deletion payments/braintree/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from django.core.exceptions import ImproperlyConfigured

from .forms import BraintreePaymentForm
from .. import BasicProvider, RedirectNeeded
from .. import RedirectNeeded
from ..core import BasicProvider


class BraintreeProvider(BasicProvider):
Expand Down
2 changes: 1 addition & 1 deletion payments/coinbase/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from django.http import HttpResponse, HttpResponseForbidden
import requests

from .. import BasicProvider
from ..core import BasicProvider


class CoinbaseProvider(BasicProvider):
Expand Down
1 change: 0 additions & 1 deletion payments/coinbase/test_coinbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
from mock import MagicMock, patch

from . import CoinbaseProvider
from payments import RedirectNeeded

PAYMENT_TOKEN = '5a4dae68-2715-4b1e-8bb2-2c2dbe9255f6'
KEY = 'abc123'
Expand Down
152 changes: 152 additions & 0 deletions payments/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
from __future__ import unicode_literals
import re
try:
from urllib.parse import urljoin, urlencode
except ImportError:
from urllib import urlencode
from urlparse import urljoin
try:
from django.db.models import get_model
except ImportError:
from django.apps import apps
get_model = apps.get_model
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured

PAYMENT_VARIANTS = {
'default': ('payments.dummy.DummyProvider', {})}

PAYMENT_HOST = getattr(settings, 'PAYMENT_HOST', None)
PAYMENT_USES_SSL = getattr(settings, 'PAYMENT_USES_SSL', False)

if not PAYMENT_HOST:
if not 'django.contrib.sites' in settings.INSTALLED_APPS:
raise ImproperlyConfigured('The PAYMENT_HOST setting without '
'the sites app must not be empty.')


def get_base_url():
protocol = 'https' if PAYMENT_USES_SSL else 'http'
if not PAYMENT_HOST:
current_site = Site.objects.get_current()
domain = current_site.domain
return '%s://%s' % (protocol, domain)
return '%s://%s' % (protocol, PAYMENT_HOST)


class BasicProvider(object):
'''
This class defines the provider API. It should not be instantiated
directly. Use factory instead.
'''
_method = 'post'

def get_action(self, payment):
return self.get_return_url(payment)

def __init__(self, capture=True):
self._capture = capture

def get_hidden_fields(self, payment):
'''
Converts a payment into a dict containing transaction data. Use
get_form instead to get a form suitable for templates.
When implementing a new payment provider, overload this method to
transfer provider-specific data.
'''
raise NotImplementedError()

def get_form(self, payment, data=None):
'''
Converts *payment* into a form suitable for Django templates.
'''
from .forms import PaymentForm
return PaymentForm(self.get_hidden_fields(payment),
self.get_action(payment), self._method)

def process_data(self, payment, request):
'''
Process callback request from a payment provider.
'''
raise NotImplementedError()

def get_token_from_request(self, payment, request):
'''
Return payment token from provider request.
'''
raise NotImplementedError()

def get_return_url(self, payment, extra_data=None):
payment_link = payment.get_process_url()
url = urljoin(get_base_url(), payment_link)
if extra_data:
qs = urlencode(extra_data)
return url + '?' + qs
return url

def capture(self, payment, amount=None):
raise NotImplementedError()

def release(self, payment):
raise NotImplementedError()

def refund(self, payment, amount=None):
raise NotImplementedError()


PROVIDER_CACHE = {}


def provider_factory(variant):
'''
Return the provider instance based on variant
'''
variants = getattr(settings, 'PAYMENT_VARIANTS', PAYMENT_VARIANTS)
handler, config = variants.get(variant, (None, None))
if not handler:
raise ValueError('Payment variant does not exist: %s' %
(variant,))
if variant not in PROVIDER_CACHE:
module_path, class_name = handler.rsplit('.', 1)
module = __import__(
str(module_path), globals(), locals(), [str(class_name)])
class_ = getattr(module, class_name)
PROVIDER_CACHE[variant] = class_(**config)
return PROVIDER_CACHE[variant]


def get_payment_model():
'''
Return the Payment model that is active in this project
'''
try:
app_label, model_name = settings.PAYMENT_MODEL.split('.')
except (ValueError, AttributeError):
raise ImproperlyConfigured('PAYMENT_MODEL must be of the form '
'"app_label.model_name"')
payment_model = get_model(app_label, model_name)
if payment_model is None:
msg = (
'PAYMENT_MODEL refers to model "%s" that has not been installed' %
settings.PAYMENT_MODEL)
raise ImproperlyConfigured(msg)
return payment_model


CARD_TYPES = [
(r'^4[0-9]{12}(?:[0-9]{3})?$', 'visa', 'VISA'),
(r'^5[1-5][0-9]{14}$', 'mastercard', 'MasterCard'),
(r'^6(?:011|5[0-9]{2})[0-9]{12}$', 'discover', 'Discover'),
(r'^3[47][0-9]{13}$', 'amex', 'American Express'),
(r'^(?:(?:2131|1800|35\d{3})\d{11})$', 'jcb', 'JCB'),
(r'^(?:3(?:0[0-5]|[68][0-9])[0-9]{11})$', 'diners', 'Diners Club'),
(r'^(?:5[0678]\d\d|6304|6390|67\d\d)\d{8,15}$', 'maestro', 'Maestro')]


def get_credit_card_issuer(number):
for regexp, card_type, name in CARD_TYPES:
if re.match(regexp, number):
return card_type, name
return None, None
Loading

0 comments on commit 2afa08e

Please sign in to comment.