Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,23 @@
# OS & Editors folders
.DS_Store
.cache
.directory
.project
.settings
.tmproj
.ropeproject
.pydevproject
nbproject
Thumbs.db
.idea
*.orig
.c9
.~c9_invoke*

# Python
__pycache__
*.pyc
*.DS_Store

# DBs
/*.db
/*.sqlite3
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include README.rst
include LICENSE
include AUTHORS
recursive-include django_braintree/templates *
3 changes: 3 additions & 0 deletions django_braintree/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-

default_app_config = 'django_braintree.apps.Config'
8 changes: 8 additions & 0 deletions django_braintree/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.apps import AppConfig


class Config(AppConfig):
name = 'django_braintree'
17 changes: 8 additions & 9 deletions django_braintree/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,16 @@ class UserCCDetailsForm(forms.Form):
)

__YEAR_CHOICES = (
(2010, '2010'),
(2011, '2011'),
(2012, '2012'),
(2013, '2013'),
(2014, '2014'),
(2015, '2015'),
(2016, '2016'),
(2017, '2017'),
(2018, '2018'),
(2019, '2019'),
(2020, '2020'),
(2021, '2021'),
(2022, '2022'),
(2023, '2023'),
(2024, '2024'),
(2025, '2025'),
)

name = forms.CharField(max_length=64, label='Name as on card')
Expand All @@ -55,7 +54,7 @@ def __init__(self, user, post_to_update=False, *args, **kwargs):
this form is meant for rendering to the user, hence initialize with braintree data (if any).
"""
self.__user = user
self.__user_vault = UserVault.objects.get_user_vault_instance_or_none(user)
self.__user_vault = UserVault.objects.for_user(user)

if not post_to_update and self.__user_vault and not args:
logging.debug('Looking up payment info for vault_id: %s' % self.__user_vault.vault_id)
Expand All @@ -72,7 +71,7 @@ def __init__(self, user, post_to_update=False, *args, **kwargs):
'zip_code': info.billing_address.postal_code,
}
super(UserCCDetailsForm, self).__init__(initial=initial, *args, **kwargs)
except Exception, e:
except Exception as e:
logging.error('Was not able to get customer from vault. %s' % e)
super(UserCCDetailsForm, self).__init__(initial = {'name': '%s %s' % (user.first_name, user.last_name)},
*args, **kwargs)
Expand Down Expand Up @@ -116,7 +115,7 @@ def save(self, prepend_vault_id=''):
response = Customer.find(self.__user_vault.vault_id)
cc_info = response.credit_cards[0]
return CreditCard.update(cc_info.token, params=cc_details_map)
except Exception, e:
except Exception as e:
logging.error('Was not able to get customer from vault. %s' % e)
self.__user_vault.delete() # delete the stale instance from our db

Expand Down
37 changes: 37 additions & 0 deletions django_braintree/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-05-25 08:03
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name='PaymentLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.DecimalField(decimal_places=2, max_digits=7)),
('timestamp', models.DateTimeField(auto_now=True)),
('transaction_id', models.CharField(max_length=128)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='UserVault',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('vault_id', models.CharField(max_length=64, unique=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
Empty file.
54 changes: 25 additions & 29 deletions django_braintree/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,39 @@
from decimal import Decimal

from django.db import models
from django.contrib.auth.models import User
from django.conf import settings
from django.utils.six import python_2_unicode_compatible, text_type

from braintree import Transaction


class UserVaultManager(models.Manager):
def get_user_vault_instance_or_none(self, user):
"""Returns a vault_id string or None"""
qset = self.filter(user=user)
if not qset:
def for_user(self, user):
""" Returns UserVault object for user or None"""
try:
return self.get(user=user)
except UserVault.DoesNotExists:
return None

if qset.count() > 1:
raise Exception('This app does not currently support multiple vault ids')

return qset.get()

def is_in_vault(self, user):
return True if self.filter(user=user) else False

def charge(self, user, vault_id=None):
"""If vault_id is not passed this will assume that there is only one instane of user and vault_id in the db."""
assert self.is_in_vault(user)
if vault_id:
user_vault = self.get(user=user, vault_id=vault_id)
else:
user_vault = self.get(user=user)
return True if self.filter(user=user).count() > 0 else False


@python_2_unicode_compatible
class UserVault(models.Model):
"""Keeping it open that one user can have multiple vault credentials, hence the FK to User and not a OneToOne."""
user = models.ForeignKey(User, unique=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL)
vault_id = models.CharField(max_length=64, unique=True)

objects = UserVaultManager()

def __unicode__(self):
return self.user.username
def __str__(self):
return text_type(self.user)

def charge(self, amount):
"""
Charges the users credit card, with he passed $amount, if they are in the vault. Returns the payment_log instance
or None (if charge fails etc.)
Charges the users credit card, with he passed $amount, if they are in the vault.
Returns the payment_log instance or None (if charge fails etc.)
"""
try:
result = Transaction.sale(
Expand All @@ -58,23 +49,28 @@ def charge(self, amount):

if result.is_success:
# create a payment log
payment_log = PaymentLog.objects.create(user=self.user, amount=amount, transaction_id=result.transaction.id)
payment_log = PaymentLog.objects.create(
user=self.user, amount=amount,
transaction_id=result.transaction.id
)
return payment_log
else:
raise Exception('Logical error in CC transaction')
except Exception:
raise ValueError('Logical error in CC transaction')
except ValueError:
logging.error('Failed to charge $%s to user: %s with vault_id: %s' % (amount, self.user, self.vault_id))
return None


@python_2_unicode_compatible
class PaymentLog(models.Model):
"""
Captures raw charges made to a users credit card. Extra info related to this payment should be a OneToOneField
referencing this model.
"""
user = models.ForeignKey(User)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
amount = models.DecimalField(max_digits=7, decimal_places=2)
timestamp = models.DateTimeField(auto_now=True)
transaction_id = models.CharField(max_length=128)

def __unicode__(self):
def __str__(self):
return '%s charged $%s - %s' % (self.user, self.amount, self.transaction_id)
5 changes: 3 additions & 2 deletions django_braintree/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

BAD_CC_ERROR_MSG = 'Oops! Doesn\'t seem like your Credit Card details are correct. Please re-check and try again.'


@ssl_required()
@login_required
def payments_billing(request, template='django_braintree/payments_billing.html'):
Expand All @@ -39,9 +40,9 @@ def payments_billing(request, template='django_braintree/payments_billing.html')
else:
if UserVault.objects.is_in_vault(request.user):
try:
response = Customer.find(UserVault.objects.get_user_vault_instance_or_none(request.user).vault_id)
response = Customer.find(UserVault.objects.for_user(request.user).vault_id)
d['current_cc_info'] = response.credit_cards[0]
except Exception, e:
except Exception as e:
logging.error('Unable to get vault information for user from braintree. %s' % e)
d['cc_form'] = UserCCDetailsForm(request.user)

Expand Down
14 changes: 6 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,24 @@

setup(
name='tivix-django-braintree',
version='0.1.2',
version='0.1.4',
author='Sumit Chachra',
author_email='chachra@tivix.com',
url='http://github.com/tivix/django-braintree',
description = 'An easy way to integrate with Braintree Payment Solutions from Django.',
description='An easy way to integrate with Braintree Payment Solutions from Django.',
long_description=long_description,
keywords = 'django braintree payment',
keywords='django braintree payment',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=[
'Django>=1.4.0',
'South>=0.7.2',
'braintree>=2.10.0',
'django-common>=0.1',
'django-common-helpers>=0.8.0',
'fudge==1.0.3'
],
#dependency_links=["git://github.com/Tivix/django-common.git@91e23cd5e0e8b420e8d4#egg=django_common-0.1"],
test_suite = 'django_braintree.tests',
include_package_data=True,
# cmdclass={},
test_suite='django_braintree.tests',
classifiers=[
'Framework :: Django',
'Intended Audience :: Developers',
Expand Down