Skip to content
Browse files

Merge pull request #26 from mirumee/features/braintree

Add Braintree payment gateway
  • Loading branch information...
2 parents 752d243 + d20f8d9 commit d22374b14e01f0df3cf46905b69bcebd3272a913 @patrys patrys committed Nov 8, 2013
Showing with 135 additions and 10 deletions.
  1. +20 −0 doc/modules.rst
  2. +40 −0 payments/braintree/__init__.py
  3. +61 −0 payments/braintree/forms.py
  4. +10 −0 payments/forms.py
  5. +2 −9 payments/paypal/forms.py
  6. +2 −1 setup.py
View
20 doc/modules.rst
@@ -36,6 +36,26 @@ Example::
'endpoint': 'https://test.authorize.net/gateway/transact.dll'})}
+Braintree
+-------------
+
+.. class:: payments.braintree.BraintreeProvider(merchant_id, public_key, private_key)
+
+ This backend implements payments using `Braintree <https://www.braintreepayments.com/>`_.
+
+ :param merchant_id: Merchant ID assigned by Braintree
+ :param public_key: Public key assigned by Braintree
+ :param private_key: Private key assigned by Braintree
+
+Example::
+
+ # use sandbox
+ PAYMENT_VARIANTS = {
+ 'braintree': ('payments.braintree.BraintreeProvider', {
+ 'merchant_id': '112233445566',
+ 'public_key': '1234567890abcdef',
+ 'private_key': 'abcdef123456'})}
+
Dotpay
------
View
40 payments/braintree/__init__.py
@@ -0,0 +1,40 @@
+from django.shortcuts import redirect
+import braintree
+
+from .forms import BraintreePaymentForm
+from .. import BasicProvider, RedirectNeeded
+
+
+class BraintreeProvider(BasicProvider):
+
+ def __init__(self, *args, **kwargs):
+ self.merchant_id = kwargs.pop('merchant_id')
+ self.public_key = kwargs.pop('public_key')
+ self.private_key = kwargs.pop('private_key')
+
+ braintree.Configuration.configure(braintree.Environment.Sandbox,
+ merchant_id=self.merchant_id,
+ public_key=self.public_key,
+ private_key=self.private_key)
+
+ super(BraintreeProvider, self).__init__(*args, **kwargs)
+
+ def get_form(self, data=None):
+ kwargs = {
+ 'data': data,
+ 'payment': self.payment,
+ 'provider': self,
+ 'action': '',
+ }
+ form = BraintreePaymentForm(**kwargs)
+ if form.is_valid():
+ form.save()
+ raise RedirectNeeded(self.payment.get_success_url())
+ else:
+ self.payment.change_status('input')
+ return form
+
+ def process_data(self, request):
+ if self.payment.status == 'confirmed':
+ return redirect(self.payment.get_success_url())
+ return redirect(self.payment.get_failure_url())
View
61 payments/braintree/forms.py
@@ -0,0 +1,61 @@
+import braintree
+
+from ..forms import CreditCardPaymentFormWithName
+
+
+class BraintreePaymentForm(CreditCardPaymentFormWithName):
+
+ transaction_id = None
+
+ def clean(self):
+ data = self.cleaned_data
+
+ if not self.errors and not self.payment.transaction_id:
+ result = braintree.Transaction.sale({
+ 'amount': str(self.payment.total),
+ 'billing': self.get_billing_data(),
+ 'credit_card': self.get_credit_card_clean_data(),
+ 'customer': self.get_customer_data(),
+ 'options': {
+ 'submit_for_settlement': True
+ },
+ 'order_id': self.payment.description
+ })
+
+ if result.is_success:
+ self.transaction_id = result.transaction.id
+ else:
+ self._errors['__all__'] = self.error_class([result.message])
+ self.payment.change_status('error')
+
+ return data
+
+ def get_credit_card_clean_data(self):
+ if self.cleaned_data:
+ return {
+ 'number': self.cleaned_data.get('number'),
+ 'cvv': self.cleaned_data.get('cvv2'),
+ 'cardholder_name': self.cleaned_data.get('name'),
+ 'expiration_month': self.cleaned_data.get('expiration').month,
+ 'expiration_year': self.cleaned_data.get('expiration').year}
+
+ def get_billing_data(self):
+ return {
+ 'first_name': self.payment.billing_first_name,
+ 'last_name': self.payment.billing_last_name,
+ 'street_address': self.payment.billing_address_1,
+ 'extended_address': self.payment.billing_address_2,
+ 'locality': self.payment.billing_city,
+ 'region': self.payment.billing_country_area,
+ 'postal_code': self.payment.billing_postcode,
+ 'country_code_alpha2': self.payment.billing_country_code}
+
+ def get_customer_data(self):
+ return {
+ 'first_name': self.payment.billing_first_name,
+ 'last_name': self.payment.billing_last_name}
+
+ def save(self):
+ braintree.Transaction.submit_for_settlement(self.transaction_id)
+ self.payment.transaction_id = self.transaction_id
+ self.payment.change_status('confirmed')
View
10 payments/forms.py
@@ -47,3 +47,13 @@ def __init__(self, *args, **kwargs):
hidden_inputs=False, *args, **kwargs)
if hasattr(self, 'VALID_TYPES'):
self.fields['number'].valid_types = self.VALID_TYPES
+
+
+class CreditCardPaymentFormWithName(CreditCardPaymentForm):
+
+ name = forms.CharField(label=_('Name on Credit Card'), max_length=128)
+
+ def __init__(self, *args, **kwargs):
+ super(CreditCardPaymentFormWithName, self).__init__(*args, **kwargs)
+ self.fields.keyOrder.remove('name')
+ self.fields.keyOrder.insert(0, 'name')
View
11 payments/paypal/forms.py
@@ -1,21 +1,14 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
-from ..forms import CreditCardPaymentForm
+from ..forms import CreditCardPaymentFormWithName
from .. import get_credit_card_issuer
-class PaymentForm(CreditCardPaymentForm):
+class PaymentForm(CreditCardPaymentFormWithName):
VALID_TYPES = ['visa', 'mastercard', 'discover', 'amex']
- name = forms.CharField(label=_('Name on Credit Card'), max_length=128)
-
- def __init__(self, *args, **kwargs):
- super(PaymentForm, self).__init__(*args, **kwargs)
- self.fields.keyOrder.remove('name')
- self.fields.keyOrder.insert(0, 'name')
-
def clean(self):
cleaned_data = super(PaymentForm, self).clean()
View
3 setup.py
@@ -26,8 +26,9 @@
'Topic :: Software Development :: Libraries :: Application Frameworks',
'Topic :: Software Development :: Libraries :: Python Modules'],
install_requires=[
+ 'braintree>=2.24.1',
'Django>=1.5',
- 'pycrypto',
+ 'pycrypto>=2.6',
'PyJWT',
'requests>=1.2.0',
'stripe>=1.9.8'],

0 comments on commit d22374b

Please sign in to comment.
Something went wrong with that request. Please try again.