Skip to content

Commit

Permalink
Merge c609883 into 665829c
Browse files Browse the repository at this point in the history
  • Loading branch information
treyhunner committed Jul 28, 2013
2 parents 665829c + c609883 commit 6d7e959
Show file tree
Hide file tree
Showing 16 changed files with 942 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
sample_project/local_settings.py
sample_project/dev.db
*.egg-info
*.egg
build/
dist/
.coverage
.tox
htmlcov
docs/_build/
10 changes: 8 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
language: python

python:
- "2.6"
- "2.7"
- 2.6
- 2.7

env:
- DJANGO=Django==1.4.2
- DJANGO=Django==1.5.0
- DJANGO=https://github.com/django/django/tarball/stable/1.6.x
- DJANGO=https://github.com/django/django/tarball/master

install:
Expand All @@ -17,4 +18,9 @@ script:
- coverage run -a setup.py test
- coverage report

matrix:
exclude:
- python: 2.6
env: DJANGO=https://github.com/django/django/tarball/master

after_success: coveralls
51 changes: 51 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Contributing
============

Below is a list of tips for submitting issues and pull requests. These are
suggestions and not requirements.

Submitting Issues
-----------------

Issues are often easier to reproduce/resolve when they have:

- A pull request with a failing test demonstrating the issue
- A code example that produces the issue consistently
- A traceback (when applicable)

Pull Requests
-------------

When creating a pull request, try to:

- Write tests if applicable
- Update the `README`_ file if needed
- Update the documentation if needed

.. _README: README.rst

Testing
-------

Please add tests for your code and ensure existing tests don't break. To run
the tests against your code::

python setup.py test

Please use tox to test the code against supported Python and Django versions.
First install tox::

pip install tox

To run tox and generate a coverage report (in ``htmlcov`` directory)::

make test

Generating documentation
------------------------

To regenerate the documentation use::

make docs

The generated documentation HTML files can be found in ``docs/_build/html``.
14 changes: 14 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
all: init test sphinx

init:
easy_install tox coverage Sphinx

test:
coverage erase
tox
coverage html

docs: sphinx

sphinx:
python setup.py build_sphinx
18 changes: 17 additions & 1 deletion authorizenet/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
"""Application-specific settings for django-authorizenet"""
"""
Application-specific settings for django-authorizenet
Available settings:
- AUTHNET_DEBUG: Set to ``True`` if using Authorize.NET test account
- AUTHNET_LOGIN_ID: Set to value of Authorize.NET login ID
- AUTHNET_TRANSACTION_KEY: Set to value of Authorize.NET transaction key
- AUTHNET_CUSTOMER_MODEL: Used to set customer model used for CIM customers
(defaults to Django user)
- AUTHNET_DELIM_CHAR: Used to set delimiter character for CIM requests
(defaults to "|")
- AUTHNET_FORCE_TEST_REQUEST
- AUTHNET_EMAIL_CUSTOMER
- AUTHNET_MD5_HASH
"""

from django.conf import settings as django_settings

Expand Down
21 changes: 20 additions & 1 deletion authorizenet/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ def create_from_list(self, items):


class Response(models.Model):

"""Transaction Response (See Section 4 of AIM Developer Guide)"""

response_code = models.CharField(max_length=2, choices=RESPONSE_CHOICES)
response_subcode = models.CharField(max_length=10)
response_reason_code = models.CharField(max_length=15)
Expand Down Expand Up @@ -193,6 +196,9 @@ def __unicode__(self):


class CIMResponse(models.Model):

"""Response for CIM API call (See Section 3 in CIM XML Guide)"""

result = models.CharField(max_length=8)
result_code = models.CharField(max_length=8,
choices=CIM_RESPONSE_CODE_CHOICES)
Expand All @@ -218,6 +224,7 @@ class CustomerProfile(models.Model):
profile_id = models.CharField(max_length=50)

def save(self, *args, **kwargs):
"""If creating new instance, create profile on Authorize.NET also"""
data = kwargs.pop('data', {})
sync = kwargs.pop('sync', True)
if not self.id and sync:
Expand All @@ -231,6 +238,7 @@ def delete(self):
super(CustomerProfile, self).delete()

def push_to_server(self, data):
"""Create customer profile for given ``customer`` on Authorize.NET"""
output = add_profile(self.customer.pk, data, data)
output['response'].raise_if_error()
self.profile_id = output['profile_id']
Expand Down Expand Up @@ -281,13 +289,22 @@ def __init__(self, *args, **kwargs):
return super(CustomerPaymentProfile, self).__init__(*args, **kwargs)

def save(self, *args, **kwargs):
"""Sync payment profile on Authorize.NET if sync kwarg is not False"""
if kwargs.pop('sync', True):
self.push_to_server()
self.card_code = None
self.card_number = "XXXX%s" % self.card_number[-4:]
super(CustomerPaymentProfile, self).save(*args, **kwargs)

def push_to_server(self):
"""
Use appropriate CIM API call to save payment profile to Authorize.NET
1. If customer has no profile yet, create one with this payment profile
2. If payment profile is not on Authorize.NET yet, create it there
3. If payment profile exists on Authorize.NET update it there
"""
if not self.customer_profile_id:
try:
self.customer_profile = CustomerProfile.objects.get(
Expand All @@ -301,13 +318,15 @@ def push_to_server(self):
self.raw_data,
self.raw_data,
)
response.raise_if_error()
elif self.customer_profile_id:
output = create_payment_profile(
self.customer_profile.profile_id,
self.raw_data,
self.raw_data,
)
response = output['response']
response.raise_if_error()
self.payment_profile_id = output['payment_profile_id']
else:
output = add_profile(
Expand All @@ -316,13 +335,13 @@ def push_to_server(self):
self.raw_data,
)
response = output['response']
response.raise_if_error()
self.customer_profile = CustomerProfile.objects.create(
customer=self.customer,
profile_id=output['profile_id'],
sync=False,
)
self.payment_profile_id = output['payment_profile_ids'][0]
response.raise_if_error()

@property
def raw_data(self):
Expand Down
10 changes: 10 additions & 0 deletions authorizenet/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ def validate_payment_form(self):


class PaymentProfileCreateView(CreateView):
"""
View for creating a CustomerPaymentProfile instance
CustomerProfile instance will be created automatically if needed.
"""

template_name = 'authorizenet/create_payment_profile.html'
form_class = CustomerPaymentForm

Expand All @@ -136,6 +142,10 @@ def get_form_kwargs(self):


class PaymentProfileUpdateView(UpdateView):
"""
View for modifying an existing CustomerPaymentProfile instance
"""

template_name = 'authorizenet/update_payment_profile.html'
form_class = CustomerPaymentForm

Expand Down
26 changes: 26 additions & 0 deletions docs.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Usage
=====

Installation
------------

Install from `PyPI`_:

.. code-block:: bash
$ pip install django-authorizenet
.. _PyPI: https://pypi.python.org/pypi/django-authorizenet/


Quickstart
----------

Add ``authorizenet`` to ``INSTALLED_APPS`` in your settings file:

.. code-block:: python
INSTALLED_APPS = (
...
'authorizenet',
)
Loading

0 comments on commit 6d7e959

Please sign in to comment.