├── .coveragerc ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── authorizenet ├── __init__.py ├── admin.py ├── cim.py ├── conf.py ├── creditcard.py ├── exceptions.py ├── fields.py ├── forms.py ├── helpers.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto__add_cimresponse.py │ ├── 0003_missing_response_fields.py │ ├── 0004_auto__add_field_response_created__add_field_cimresponse_created__chg_f.py │ ├── 0005_auto__add_customerpaymentprofile__add_customerprofile__chg_field_cimre.py │ └── __init__.py ├── models.py ├── signals.py ├── templates │ └── admin │ │ └── authorizenet │ │ ├── change_form.html │ │ └── customerprofile │ │ └── change_form.html ├── urls.py ├── utils.py └── views.py ├── docs.rst ├── docs ├── Makefile ├── cim.rst ├── conf.py ├── index.rst ├── make.bat └── usage.rst ├── runtests.py ├── sample_project ├── __init__.py ├── local_settings.py.example ├── manage.py ├── media │ └── contentx │ │ ├── IframeCommunicator.html │ │ ├── closeButton1.png │ │ ├── closeButton1a.png │ │ ├── closeButton1h.png │ │ ├── empty.html │ │ ├── manage.css │ │ ├── manageIELTE6.css │ │ ├── popup.js │ │ ├── powered_simple.png │ │ ├── shadow1-bottom.png │ │ ├── shadow1-bottomLeft.png │ │ ├── shadow1-bottomRight.png │ │ ├── shadow1-left.png │ │ ├── shadow1-right.png │ │ ├── shadow1-top.png │ │ ├── shadow1-topLeft.png │ │ └── shadow1-topRight.png ├── requirements.txt ├── samplestore │ ├── __init__.py │ ├── admin.py │ ├── fixtures │ │ └── initial_data.json │ ├── models.py │ ├── urls.py │ └── views.py ├── settings.py ├── templates │ ├── authorizenet │ │ ├── aim_payment.html │ │ ├── aim_success.html │ │ └── sim_payment.html │ ├── base.html │ ├── registration │ │ └── login.html │ └── samplestore │ │ ├── capture.html │ │ ├── capture_index.html │ │ ├── commit_to_buy.html │ │ ├── edit_cim_profile.html │ │ ├── items.html │ │ └── make_payment.html └── urls.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── models.py ├── templates │ └── authorizenet │ │ ├── create_payment_profile.html │ │ └── update_payment_profile.html ├── tests │ ├── __init__.py │ ├── cim.py │ ├── mocks.py │ ├── models.py │ ├── test_data.py │ ├── utils.py │ └── views.py ├── urls.py └── views.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = authorizenet/* 3 | omit = tests/* 4 | branch = 1 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | sample_project/local_settings.py 3 | sample_project/dev.db 4 | *.egg-info 5 | *.egg 6 | build/ 7 | dist/ 8 | .coverage 9 | .tox 10 | htmlcov 11 | docs/_build/ 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 2.6 5 | - 2.7 6 | 7 | env: 8 | - DJANGO=Django==1.4.2 9 | - DJANGO=Django==1.5.0 10 | - DJANGO=https://github.com/django/django/tarball/stable/1.6.x 11 | - DJANGO=https://github.com/django/django/tarball/master 12 | 13 | install: 14 | - pip install --use-mirrors $DJANGO 15 | - pip install --use-mirrors coverage coveralls 16 | 17 | script: 18 | - coverage run -a setup.py test 19 | - coverage report 20 | 21 | matrix: 22 | exclude: 23 | - python: 2.6 24 | env: DJANGO=https://github.com/django/django/tarball/master 25 | 26 | after_success: coveralls 27 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | https://github.com/zen4ever/django-authorizenet/contributors 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | Below is a list of tips for submitting issues and pull requests. These are 5 | suggestions and not requirements. 6 | 7 | Submitting Issues 8 | ----------------- 9 | 10 | Issues are often easier to reproduce/resolve when they have: 11 | 12 | - A pull request with a failing test demonstrating the issue 13 | - A code example that produces the issue consistently 14 | - A traceback (when applicable) 15 | 16 | Pull Requests 17 | ------------- 18 | 19 | When creating a pull request, try to: 20 | 21 | - Write tests if applicable 22 | - Update the `README`_ file if needed 23 | - Update the documentation if needed 24 | 25 | .. _README: README.rst 26 | 27 | Testing 28 | ------- 29 | 30 | Please add tests for your code and ensure existing tests don't break. To run 31 | the tests against your code:: 32 | 33 | python setup.py test 34 | 35 | Please use tox to test the code against supported Python and Django versions. 36 | First install tox:: 37 | 38 | pip install tox 39 | 40 | To run tox and generate a coverage report (in ``htmlcov`` directory):: 41 | 42 | make test 43 | 44 | Generating documentation 45 | ------------------------ 46 | 47 | To regenerate the documentation use:: 48 | 49 | make docs 50 | 51 | The generated documentation HTML files can be found in ``docs/_build/html``. 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Andrii Kurinnyi 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include setup.py README.rst MANIFEST.in LICENSE 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: init test sphinx 2 | 3 | init: 4 | easy_install tox coverage Sphinx 5 | 6 | test: 7 | coverage erase 8 | tox 9 | coverage html 10 | 11 | docs: sphinx 12 | 13 | sphinx: 14 | python setup.py build_sphinx 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | django-authorizenet 3 | =================== 4 | 5 | .. image:: https://secure.travis-ci.org/zen4ever/django-authorizenet.png?branch=master 6 | :target: http://travis-ci.org/zen4ever/django-authorizenet 7 | :alt: Test Status 8 | .. image:: https://coveralls.io/repos/zen4ever/django-authorizenet/badge.png?branch=master 9 | :target: https://coveralls.io/r/zen4ever/django-authorizenet 10 | :alt: Coverage Status 11 | 12 | Django integration with Authorize.NET payment gateway. 13 | Includes SIM and AIM implementations. 14 | 15 | Usage example: 16 | https://github.com/zen4ever/django-authorizenet/tree/master/sample_project 17 | 18 | Authors: https://github.com/zen4ever/django-authorizenet/contributors 19 | -------------------------------------------------------------------------------- /authorizenet/__init__.py: -------------------------------------------------------------------------------- 1 | AUTHNET_POST_URL = "https://secure.authorize.net/gateway/transact.dll" 2 | AUTHNET_TEST_POST_URL = "https://test.authorize.net/gateway/transact.dll" 3 | AUTHNET_TEST_CIM_URL = "https://apitest.authorize.net/xml/v1/request.api" 4 | AUTHNET_CIM_URL = "https://api.authorize.net/xml/v1/request.api" 5 | -------------------------------------------------------------------------------- /authorizenet/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.core.urlresolvers import reverse 3 | from django.utils.safestring import mark_safe 4 | from authorizenet.models import (Response, CIMResponse, CustomerProfile, 5 | CustomerPaymentProfile) 6 | from authorizenet.forms import CustomerPaymentForm, CustomerPaymentAdminForm 7 | from relatives.utils import object_edit_link 8 | 9 | 10 | class ResponseAdmin(admin.ModelAdmin): 11 | list_display = ['response_code', 12 | 'response_reason_text', 13 | 'auth_code', 14 | 'trans_id'] 15 | 16 | readonly_fields = ['response_code', 17 | 'response_subcode', 18 | 'response_reason_code', 19 | 'response_reason_text', 20 | 'auth_code', 21 | 'avs_code', 22 | 'trans_id', 23 | 'invoice_num', 24 | 'description', 25 | 'amount', 26 | 'method', 27 | 'type', 28 | 'cust_id', 29 | 'first_name', 30 | 'last_name', 31 | 'company', 32 | 'address', 33 | 'city', 34 | 'state', 35 | 'zip', 36 | 'country', 37 | 'phone', 38 | 'fax', 39 | 'email', 40 | 'ship_to_first_name', 41 | 'ship_to_last_name', 42 | 'ship_to_company', 43 | 'ship_to_address', 44 | 'ship_to_city', 45 | 'ship_to_state', 46 | 'ship_to_zip', 47 | 'ship_to_country', 48 | 'tax', 49 | 'duty', 50 | 'freight', 51 | 'tax_exempt', 52 | 'po_num', 53 | 'MD5_Hash', 54 | 'cvv2_resp_code', 55 | 'cavv_response', 56 | 'test_request', 57 | 'card_type', 58 | 'account_number', 59 | 'created'] 60 | 61 | admin.site.register(Response, ResponseAdmin) 62 | 63 | 64 | class CIMResponseAdmin(admin.ModelAdmin): 65 | list_display = ['result_code', 66 | 'result'] 67 | 68 | readonly_fields = ['result', 69 | 'result_code', 70 | 'result_text', 71 | 'response_link', 72 | 'created'] 73 | 74 | exclude = ['transaction_response'] 75 | 76 | def response_link(self, obj): 77 | change_url = reverse('admin:authorizenet_response_change', 78 | args=(obj.transaction_response.id,)) 79 | return mark_safe('%s' % (change_url, 80 | obj.transaction_response)) 81 | response_link.short_description = 'transaction response' 82 | 83 | admin.site.register(CIMResponse, CIMResponseAdmin) 84 | 85 | 86 | class CustomerPaymentProfileInline(admin.TabularInline): 87 | model = CustomerPaymentProfile 88 | form = CustomerPaymentForm 89 | fields = [object_edit_link("Edit"), 'first_name', 'last_name', 90 | 'card_number', 'expiration_date'] 91 | readonly_fields = fields 92 | extra = 0 93 | max_num = 0 94 | can_delete = False 95 | 96 | 97 | class CustomerProfileAdmin(admin.ModelAdmin): 98 | list_display = ['profile_id', 'customer'] 99 | readonly_fields = ['profile_id', 'customer'] 100 | inlines = [CustomerPaymentProfileInline] 101 | 102 | def get_readonly_fields(self, request, obj=None): 103 | return self.readonly_fields if obj is not None else ['profile_id'] 104 | 105 | admin.site.register(CustomerProfile, CustomerProfileAdmin) 106 | 107 | 108 | class CustomerPaymentProfileAdmin(admin.ModelAdmin): 109 | list_display = ['payment_profile_id', 'customer_profile', 'customer'] 110 | readonly_fields = ['payment_profile_id', 'customer', 'customer_profile'] 111 | form = CustomerPaymentAdminForm 112 | 113 | def get_readonly_fields(self, request, obj=None): 114 | return self.readonly_fields if obj is not None else [] 115 | 116 | admin.site.register(CustomerPaymentProfile, CustomerPaymentProfileAdmin) 117 | -------------------------------------------------------------------------------- /authorizenet/conf.py: -------------------------------------------------------------------------------- 1 | """ 2 | Application-specific settings for django-authorizenet 3 | 4 | Available settings: 5 | 6 | - AUTHNET_DEBUG: Set to ``True`` if using Authorize.NET test account 7 | - AUTHNET_LOGIN_ID: Set to value of Authorize.NET login ID 8 | - AUTHNET_TRANSACTION_KEY: Set to value of Authorize.NET transaction key 9 | - AUTHNET_CUSTOMER_MODEL: Used to set customer model used for CIM customers 10 | (defaults to Django user) 11 | - AUTHNET_DELIM_CHAR: Used to set delimiter character for CIM requests 12 | (defaults to "|") 13 | - AUTHNET_FORCE_TEST_REQUEST 14 | - AUTHNET_EMAIL_CUSTOMER 15 | - AUTHNET_MD5_HASH 16 | 17 | """ 18 | 19 | from django.conf import settings as django_settings 20 | 21 | 22 | class Settings(object): 23 | 24 | """ 25 | Retrieves django.conf settings, using defaults from Default subclass 26 | 27 | All usable settings are specified in settings attribute. Use an 28 | ``AUTHNET_`` prefix when specifying settings in django.conf. 29 | """ 30 | 31 | prefix = 'AUTHNET_' 32 | settings = set(('DEBUG', 'LOGIN_ID', 'TRANSACTION_KEY', 'CUSTOMER_MODEL', 33 | 'DELIM_CHAR', 'FORCE_TEST_REQUEST', 'EMAIL_CUSTOMER', 34 | 'MD5_HASH')) 35 | 36 | class Default: 37 | CUSTOMER_MODEL = getattr( 38 | django_settings, 'AUTH_USER_MODEL', "auth.User") 39 | DELIM_CHAR = "|" 40 | FORCE_TEST_REQUEST = False 41 | EMAIL_CUSTOMER = None 42 | MD5_HASH = "" 43 | 44 | def __init__(self): 45 | self.defaults = Settings.Default() 46 | 47 | def __getattr__(self, name): 48 | if name not in self.settings: 49 | raise AttributeError("Setting %s not understood" % name) 50 | try: 51 | return getattr(django_settings, self.prefix + name) 52 | except AttributeError: 53 | return getattr(self.defaults, name) 54 | 55 | settings = Settings() 56 | -------------------------------------------------------------------------------- /authorizenet/creditcard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # from http://github.com/johnboxall/django-paypal 4 | import re 5 | from string import digits, split as L 6 | 7 | # Adapted from: 8 | # http://www.djangosnippets.org/snippets/764/ 9 | # http://www.satchmoproject.com/ 10 | # http://tinyurl.com/shoppify-credit-cards 11 | 12 | # Well known card regular expressions. 13 | CARDS = { 14 | 'Visa': re.compile(r"^4\d{12}(\d{3})?$"), 15 | 'Mastercard': re.compile(r"(5[1-5]\d{4}|677189)\d{10}$"), 16 | 'Dinersclub': re.compile(r"^3(0[0-5]|[68]\d)\d{11}"), 17 | 'Amex': re.compile("^3[47]\d{13}$"), 18 | 'Discover': re.compile("^(6011|65\d{2})\d{12}$"), 19 | } 20 | 21 | # Well known test numbers 22 | TEST_NUMBERS = L("378282246310005 371449635398431 378734493671000" 23 | "30569309025904 38520000023237 6011111111111117" 24 | "6011000990139424 555555555554444 5105105105105100" 25 | "4111111111111111 4012888888881881 4222222222222") 26 | 27 | 28 | def verify_credit_card(number, allow_test=False): 29 | """Returns the card type for given card number or None if invalid.""" 30 | return CreditCard(number).verify(allow_test) 31 | 32 | 33 | class CreditCard(object): 34 | def __init__(self, number): 35 | self.number = number 36 | 37 | def is_number(self): 38 | """Returns True if there is at least one digit in number.""" 39 | if isinstance(self.number, basestring): 40 | self.number = "".join([c for c in self.number if c in digits]) 41 | return self.number.isdigit() 42 | return False 43 | 44 | def is_mod10(self): 45 | """Returns True if number is valid according to mod10.""" 46 | double = 0 47 | total = 0 48 | for i in range(len(self.number) - 1, -1, -1): 49 | for c in str((double + 1) * int(self.number[i])): 50 | total = total + int(c) 51 | double = (double + 1) % 2 52 | return (total % 10) == 0 53 | 54 | def is_test(self): 55 | """Returns True if number is a test card number.""" 56 | return self.number in TEST_NUMBERS 57 | 58 | def get_type(self): 59 | """Return the type if it matches one of the cards.""" 60 | for card, pattern in CARDS.iteritems(): 61 | if pattern.match(self.number): 62 | return card 63 | return None 64 | 65 | def verify(self, allow_test): 66 | """Returns the card type if valid else None.""" 67 | if self.is_number() and \ 68 | (not self.is_test() or allow_test) \ 69 | and self.is_mod10(): 70 | return self.get_type() 71 | return None 72 | -------------------------------------------------------------------------------- /authorizenet/exceptions.py: -------------------------------------------------------------------------------- 1 | class BillingError(Exception): 2 | """Error due to Authorize.NET request""" 3 | -------------------------------------------------------------------------------- /authorizenet/fields.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from datetime import date 4 | from calendar import monthrange 5 | 6 | from django import forms 7 | from django.utils.translation import ugettext as _ 8 | 9 | from authorizenet.conf import settings 10 | from authorizenet.creditcard import verify_credit_card 11 | 12 | 13 | class CreditCardField(forms.CharField): 14 | """ 15 | Form field for checking out a credit card. 16 | """ 17 | def __init__(self, *args, **kwargs): 18 | kwargs.setdefault('max_length', 20) 19 | super(CreditCardField, self).__init__(*args, **kwargs) 20 | 21 | def clean(self, value): 22 | """ 23 | Raises a ValidationError if the card is not valid 24 | and stashes card type. 25 | """ 26 | self.card_type = verify_credit_card(value, allow_test=settings.DEBUG) 27 | if self.card_type is None: 28 | raise forms.ValidationError("Invalid credit card number.") 29 | return value 30 | 31 | 32 | # Credit Card Expiry Fields from: 33 | # http://www.djangosnippets.org/snippets/907/ 34 | class CreditCardExpiryWidget(forms.MultiWidget): 35 | """MultiWidget for representing credit card expiry date.""" 36 | def decompress(self, value): 37 | if value: 38 | return [value.month, value.year] 39 | else: 40 | return [None, None] 41 | 42 | def format_output(self, rendered_widgets): 43 | html = u' / '.join(rendered_widgets) 44 | return u'%s' % html 45 | 46 | 47 | class CreditCardExpiryField(forms.MultiValueField): 48 | EXP_MONTH = [(x, "%02d" % x) for x in xrange(1, 13)] 49 | EXP_YEAR = [(x, x) for x in xrange(date.today().year, 50 | date.today().year + 15)] 51 | 52 | default_error_messages = { 53 | 'invalid_month': u'Enter a valid month.', 54 | 'invalid_year': u'Enter a valid year.', 55 | } 56 | 57 | def __init__(self, *args, **kwargs): 58 | errors = self.default_error_messages.copy() 59 | if 'error_messages' in kwargs: 60 | errors.update(kwargs['error_messages']) 61 | 62 | fields = ( 63 | forms.ChoiceField( 64 | choices=self.EXP_MONTH, 65 | error_messages={'invalid': errors['invalid_month']}), 66 | forms.ChoiceField( 67 | choices=self.EXP_YEAR, 68 | error_messages={'invalid': errors['invalid_year']}), 69 | ) 70 | 71 | super(CreditCardExpiryField, self).__init__(fields, *args, **kwargs) 72 | self.widget = CreditCardExpiryWidget(widgets=[fields[0].widget, 73 | fields[1].widget]) 74 | 75 | def clean(self, value): 76 | exp = super(CreditCardExpiryField, self).clean(value) 77 | if date.today() > exp: 78 | raise forms.ValidationError( 79 | "The expiration date you entered is in the past.") 80 | return exp 81 | 82 | def compress(self, data_list): 83 | if data_list: 84 | if data_list[1] in forms.fields.EMPTY_VALUES: 85 | error = self.error_messages['invalid_year'] 86 | raise forms.ValidationError(error) 87 | if data_list[0] in forms.fields.EMPTY_VALUES: 88 | error = self.error_messages['invalid_month'] 89 | raise forms.ValidationError(error) 90 | year = int(data_list[1]) 91 | month = int(data_list[0]) 92 | # find last day of the month 93 | day = monthrange(year, month)[1] 94 | return date(year, month, day) 95 | return None 96 | 97 | 98 | class CreditCardCVV2Field(forms.CharField): 99 | def __init__(self, *args, **kwargs): 100 | kwargs.setdefault('min_length', 3) 101 | kwargs.setdefault('max_length', 4) 102 | super(CreditCardCVV2Field, self).__init__(*args, **kwargs) 103 | 104 | 105 | # Country Field from: 106 | # http://www.djangosnippets.org/snippets/494/ 107 | # http://xml.coverpages.org/country3166.html 108 | COUNTRIES = ( 109 | ('US', _('United States of America')), 110 | ('CA', _('Canada')), 111 | ('AD', _('Andorra')), 112 | ('AE', _('United Arab Emirates')), 113 | ('AF', _('Afghanistan')), 114 | ('AG', _('Antigua & Barbuda')), 115 | ('AI', _('Anguilla')), 116 | ('AL', _('Albania')), 117 | ('AM', _('Armenia')), 118 | ('AN', _('Netherlands Antilles')), 119 | ('AO', _('Angola')), 120 | ('AQ', _('Antarctica')), 121 | ('AR', _('Argentina')), 122 | ('AS', _('American Samoa')), 123 | ('AT', _('Austria')), 124 | ('AU', _('Australia')), 125 | ('AW', _('Aruba')), 126 | ('AZ', _('Azerbaijan')), 127 | ('BA', _('Bosnia and Herzegovina')), 128 | ('BB', _('Barbados')), 129 | ('BD', _('Bangladesh')), 130 | ('BE', _('Belgium')), 131 | ('BF', _('Burkina Faso')), 132 | ('BG', _('Bulgaria')), 133 | ('BH', _('Bahrain')), 134 | ('BI', _('Burundi')), 135 | ('BJ', _('Benin')), 136 | ('BM', _('Bermuda')), 137 | ('BN', _('Brunei Darussalam')), 138 | ('BO', _('Bolivia')), 139 | ('BR', _('Brazil')), 140 | ('BS', _('Bahama')), 141 | ('BT', _('Bhutan')), 142 | ('BV', _('Bouvet Island')), 143 | ('BW', _('Botswana')), 144 | ('BY', _('Belarus')), 145 | ('BZ', _('Belize')), 146 | ('CC', _('Cocos (Keeling) Islands')), 147 | ('CF', _('Central African Republic')), 148 | ('CG', _('Congo')), 149 | ('CH', _('Switzerland')), 150 | ('CI', _('Ivory Coast')), 151 | ('CK', _('Cook Iislands')), 152 | ('CL', _('Chile')), 153 | ('CM', _('Cameroon')), 154 | ('CN', _('China')), 155 | ('CO', _('Colombia')), 156 | ('CR', _('Costa Rica')), 157 | ('CU', _('Cuba')), 158 | ('CV', _('Cape Verde')), 159 | ('CX', _('Christmas Island')), 160 | ('CY', _('Cyprus')), 161 | ('CZ', _('Czech Republic')), 162 | ('DE', _('Germany')), 163 | ('DJ', _('Djibouti')), 164 | ('DK', _('Denmark')), 165 | ('DM', _('Dominica')), 166 | ('DO', _('Dominican Republic')), 167 | ('DZ', _('Algeria')), 168 | ('EC', _('Ecuador')), 169 | ('EE', _('Estonia')), 170 | ('EG', _('Egypt')), 171 | ('EH', _('Western Sahara')), 172 | ('ER', _('Eritrea')), 173 | ('ES', _('Spain')), 174 | ('ET', _('Ethiopia')), 175 | ('FI', _('Finland')), 176 | ('FJ', _('Fiji')), 177 | ('FK', _('Falkland Islands (Malvinas)')), 178 | ('FM', _('Micronesia')), 179 | ('FO', _('Faroe Islands')), 180 | ('FR', _('France')), 181 | ('FX', _('France, Metropolitan')), 182 | ('GA', _('Gabon')), 183 | ('GB', _('United Kingdom (Great Britain)')), 184 | ('GD', _('Grenada')), 185 | ('GE', _('Georgia')), 186 | ('GF', _('French Guiana')), 187 | ('GH', _('Ghana')), 188 | ('GI', _('Gibraltar')), 189 | ('GL', _('Greenland')), 190 | ('GM', _('Gambia')), 191 | ('GN', _('Guinea')), 192 | ('GP', _('Guadeloupe')), 193 | ('GQ', _('Equatorial Guinea')), 194 | ('GR', _('Greece')), 195 | ('GS', _('South Georgia and the South Sandwich Islands')), 196 | ('GT', _('Guatemala')), 197 | ('GU', _('Guam')), 198 | ('GW', _('Guinea-Bissau')), 199 | ('GY', _('Guyana')), 200 | ('HK', _('Hong Kong')), 201 | ('HM', _('Heard & McDonald Islands')), 202 | ('HN', _('Honduras')), 203 | ('HR', _('Croatia')), 204 | ('HT', _('Haiti')), 205 | ('HU', _('Hungary')), 206 | ('ID', _('Indonesia')), 207 | ('IE', _('Ireland')), 208 | ('IL', _('Israel')), 209 | ('IN', _('India')), 210 | ('IO', _('British Indian Ocean Territory')), 211 | ('IQ', _('Iraq')), 212 | ('IR', _('Islamic Republic of Iran')), 213 | ('IS', _('Iceland')), 214 | ('IT', _('Italy')), 215 | ('JM', _('Jamaica')), 216 | ('JO', _('Jordan')), 217 | ('JP', _('Japan')), 218 | ('KE', _('Kenya')), 219 | ('KG', _('Kyrgyzstan')), 220 | ('KH', _('Cambodia')), 221 | ('KI', _('Kiribati')), 222 | ('KM', _('Comoros')), 223 | ('KN', _('St. Kitts and Nevis')), 224 | ('KP', _('Korea, Democratic People\'s Republic of')), 225 | ('KR', _('Korea, Republic of')), 226 | ('KW', _('Kuwait')), 227 | ('KY', _('Cayman Islands')), 228 | ('KZ', _('Kazakhstan')), 229 | ('LA', _('Lao People\'s Democratic Republic')), 230 | ('LB', _('Lebanon')), 231 | ('LC', _('Saint Lucia')), 232 | ('LI', _('Liechtenstein')), 233 | ('LK', _('Sri Lanka')), 234 | ('LR', _('Liberia')), 235 | ('LS', _('Lesotho')), 236 | ('LT', _('Lithuania')), 237 | ('LU', _('Luxembourg')), 238 | ('LV', _('Latvia')), 239 | ('LY', _('Libyan Arab Jamahiriya')), 240 | ('MA', _('Morocco')), 241 | ('MC', _('Monaco')), 242 | ('MD', _('Moldova, Republic of')), 243 | ('MG', _('Madagascar')), 244 | ('MH', _('Marshall Islands')), 245 | ('ML', _('Mali')), 246 | ('MN', _('Mongolia')), 247 | ('MM', _('Myanmar')), 248 | ('MO', _('Macau')), 249 | ('MP', _('Northern Mariana Islands')), 250 | ('MQ', _('Martinique')), 251 | ('MR', _('Mauritania')), 252 | ('MS', _('Monserrat')), 253 | ('MT', _('Malta')), 254 | ('MU', _('Mauritius')), 255 | ('MV', _('Maldives')), 256 | ('MW', _('Malawi')), 257 | ('MX', _('Mexico')), 258 | ('MY', _('Malaysia')), 259 | ('MZ', _('Mozambique')), 260 | ('NA', _('Namibia')), 261 | ('NC', _('New Caledonia')), 262 | ('NE', _('Niger')), 263 | ('NF', _('Norfolk Island')), 264 | ('NG', _('Nigeria')), 265 | ('NI', _('Nicaragua')), 266 | ('NL', _('Netherlands')), 267 | ('NO', _('Norway')), 268 | ('NP', _('Nepal')), 269 | ('NR', _('Nauru')), 270 | ('NU', _('Niue')), 271 | ('NZ', _('New Zealand')), 272 | ('OM', _('Oman')), 273 | ('PA', _('Panama')), 274 | ('PE', _('Peru')), 275 | ('PF', _('French Polynesia')), 276 | ('PG', _('Papua New Guinea')), 277 | ('PH', _('Philippines')), 278 | ('PK', _('Pakistan')), 279 | ('PL', _('Poland')), 280 | ('PM', _('St. Pierre & Miquelon')), 281 | ('PN', _('Pitcairn')), 282 | ('PR', _('Puerto Rico')), 283 | ('PT', _('Portugal')), 284 | ('PW', _('Palau')), 285 | ('PY', _('Paraguay')), 286 | ('QA', _('Qatar')), 287 | ('RE', _('Reunion')), 288 | ('RO', _('Romania')), 289 | ('RU', _('Russian Federation')), 290 | ('RW', _('Rwanda')), 291 | ('SA', _('Saudi Arabia')), 292 | ('SB', _('Solomon Islands')), 293 | ('SC', _('Seychelles')), 294 | ('SD', _('Sudan')), 295 | ('SE', _('Sweden')), 296 | ('SG', _('Singapore')), 297 | ('SH', _('St. Helena')), 298 | ('SI', _('Slovenia')), 299 | ('SJ', _('Svalbard & Jan Mayen Islands')), 300 | ('SK', _('Slovakia')), 301 | ('SL', _('Sierra Leone')), 302 | ('SM', _('San Marino')), 303 | ('SN', _('Senegal')), 304 | ('SO', _('Somalia')), 305 | ('SR', _('Suriname')), 306 | ('ST', _('Sao Tome & Principe')), 307 | ('SV', _('El Salvador')), 308 | ('SY', _('Syrian Arab Republic')), 309 | ('SZ', _('Swaziland')), 310 | ('TC', _('Turks & Caicos Islands')), 311 | ('TD', _('Chad')), 312 | ('TF', _('French Southern Territories')), 313 | ('TG', _('Togo')), 314 | ('TH', _('Thailand')), 315 | ('TJ', _('Tajikistan')), 316 | ('TK', _('Tokelau')), 317 | ('TM', _('Turkmenistan')), 318 | ('TN', _('Tunisia')), 319 | ('TO', _('Tonga')), 320 | ('TP', _('East Timor')), 321 | ('TR', _('Turkey')), 322 | ('TT', _('Trinidad & Tobago')), 323 | ('TV', _('Tuvalu')), 324 | ('TW', _('Taiwan, Province of China')), 325 | ('TZ', _('Tanzania, United Republic of')), 326 | ('UA', _('Ukraine')), 327 | ('UG', _('Uganda')), 328 | ('UM', _('United States Minor Outlying Islands')), 329 | ('UY', _('Uruguay')), 330 | ('UZ', _('Uzbekistan')), 331 | ('VA', _('Vatican City State (Holy See)')), 332 | ('VC', _('St. Vincent & the Grenadines')), 333 | ('VE', _('Venezuela')), 334 | ('VG', _('British Virgin Islands')), 335 | ('VI', _('United States Virgin Islands')), 336 | ('VN', _('Viet Nam')), 337 | ('VU', _('Vanuatu')), 338 | ('WF', _('Wallis & Futuna Islands')), 339 | ('WS', _('Samoa')), 340 | ('YE', _('Yemen')), 341 | ('YT', _('Mayotte')), 342 | ('YU', _('Yugoslavia')), 343 | ('ZA', _('South Africa')), 344 | ('ZM', _('Zambia')), 345 | ('ZR', _('Zaire')), 346 | ('ZW', _('Zimbabwe')), 347 | ('ZZ', _('Unknown or unspecified country')), 348 | ) 349 | 350 | 351 | class CountryField(forms.ChoiceField): 352 | def __init__(self, *args, **kwargs): 353 | kwargs.setdefault('choices', COUNTRIES) 354 | super(CountryField, self).__init__(*args, **kwargs) 355 | -------------------------------------------------------------------------------- /authorizenet/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from authorizenet.conf import settings 3 | from authorizenet.fields import CreditCardField, CreditCardExpiryField, \ 4 | CreditCardCVV2Field, CountryField 5 | from authorizenet.models import CustomerPaymentProfile 6 | 7 | 8 | class SIMPaymentForm(forms.Form): 9 | x_login = forms.CharField(max_length=20, 10 | required=True, 11 | widget=forms.HiddenInput, 12 | initial=settings.LOGIN_ID) 13 | x_type = forms.CharField(max_length=20, 14 | widget=forms.HiddenInput, 15 | initial="AUTH_CAPTURE") 16 | x_amount = forms.DecimalField(max_digits=15, 17 | decimal_places=2, 18 | widget=forms.HiddenInput) 19 | x_show_form = forms.CharField(max_length=20, 20 | widget=forms.HiddenInput, 21 | initial="PAYMENT_FORM") 22 | x_method = forms.CharField(max_length=10, 23 | widget=forms.HiddenInput, 24 | initial="CC") 25 | x_fp_sequence = forms.CharField(max_length=10, 26 | widget=forms.HiddenInput, 27 | initial="CC") 28 | x_version = forms.CharField(max_length=10, 29 | widget=forms.HiddenInput, 30 | initial="3.1") 31 | x_relay_response = forms.CharField(max_length=8, 32 | widget=forms.HiddenInput, 33 | initial="TRUE") 34 | x_fp_timestamp = forms.CharField(max_length=55, 35 | widget=forms.HiddenInput) 36 | x_relay_url = forms.CharField(max_length=55, 37 | widget=forms.HiddenInput) 38 | x_fp_hash = forms.CharField(max_length=55, 39 | widget=forms.HiddenInput) 40 | x_invoice_num = forms.CharField(max_length=55, 41 | required=False, 42 | widget=forms.HiddenInput) 43 | x_description = forms.CharField(max_length=255, 44 | required=False, 45 | widget=forms.HiddenInput) 46 | 47 | 48 | class SIMBillingForm(forms.Form): 49 | x_first_name = forms.CharField(max_length=50, widget=forms.HiddenInput) 50 | x_last_name = forms.CharField(max_length=50, widget=forms.HiddenInput) 51 | x_company = forms.CharField(max_length=50, widget=forms.HiddenInput) 52 | x_address = forms.CharField(max_length=60, widget=forms.HiddenInput) 53 | x_city = forms.CharField(max_length=40, widget=forms.HiddenInput) 54 | x_state = forms.CharField(max_length=40, widget=forms.HiddenInput) 55 | x_zip = forms.CharField(max_length=20, widget=forms.HiddenInput) 56 | x_country = forms.CharField(max_length=60, widget=forms.HiddenInput) 57 | x_phone = forms.CharField(max_length=25, widget=forms.HiddenInput) 58 | x_fax = forms.CharField(max_length=25, widget=forms.HiddenInput) 59 | x_email = forms.CharField(max_length=255, widget=forms.HiddenInput) 60 | x_cust_id = forms.CharField(max_length=20, widget=forms.HiddenInput) 61 | 62 | 63 | class BillingAddressForm(forms.Form): 64 | first_name = forms.CharField(50, label="First Name") 65 | last_name = forms.CharField(50, label="Last Name") 66 | company = forms.CharField(50, label="Company", required=False) 67 | address = forms.CharField(60, label="Street Address") 68 | city = forms.CharField(40, label="City") 69 | state = forms.CharField(40, label="State") 70 | country = CountryField(label="Country", initial="US") 71 | zip = forms.CharField(20, label="Postal / Zip Code") 72 | 73 | class ShippingAddressForm(forms.Form): 74 | ship_to_first_name = forms.CharField(50, label="First Name") 75 | ship_to_last_name = forms.CharField(50, label="Last Name") 76 | ship_to_company = forms.CharField(50, label="Company", required=False) 77 | ship_to_address = forms.CharField(60, label="Street Address") 78 | ship_to_city = forms.CharField(40, label="City") 79 | ship_to_state = forms.CharField(label="State") 80 | ship_to_zip = forms.CharField(20, label="Postal / Zip Code") 81 | ship_to_country = CountryField(label="Country", initial="US") 82 | 83 | class AIMPaymentForm(forms.Form): 84 | card_num = CreditCardField(label="Credit Card Number") 85 | exp_date = CreditCardExpiryField(label="Expiration Date") 86 | card_code = CreditCardCVV2Field(label="Card Security Code") 87 | 88 | 89 | class CIMPaymentForm(forms.Form): 90 | card_number = CreditCardField(label="Credit Card Number") 91 | expiration_date = CreditCardExpiryField(label="Expiration Date") 92 | card_code = CreditCardCVV2Field(label="Card Security Code") 93 | 94 | 95 | class CustomerPaymentForm(forms.ModelForm): 96 | 97 | """Base customer payment form without shipping address""" 98 | 99 | country = CountryField(label="Country", initial="US") 100 | card_number = CreditCardField(label="Credit Card Number") 101 | expiration_date = CreditCardExpiryField(label="Expiration Date") 102 | card_code = CreditCardCVV2Field(label="Card Security Code") 103 | 104 | def __init__(self, *args, **kwargs): 105 | self.customer = kwargs.pop('customer', None) 106 | return super(CustomerPaymentForm, self).__init__(*args, **kwargs) 107 | 108 | def save(self, commit=True): 109 | instance = super(CustomerPaymentForm, self).save(commit=False) 110 | if self.customer: 111 | instance.customer = self.customer 112 | instance.card_code = self.cleaned_data.get('card_code') 113 | if commit: 114 | instance.save() 115 | return instance 116 | 117 | class Meta: 118 | model = CustomerPaymentProfile 119 | fields = ('first_name', 'last_name', 'company', 'address', 'city', 120 | 'state', 'country', 'zip', 'card_number', 121 | 'expiration_date', 'card_code') 122 | 123 | 124 | class CustomerPaymentAdminForm(CustomerPaymentForm): 125 | class Meta(CustomerPaymentForm.Meta): 126 | fields = ('customer',) + CustomerPaymentForm.Meta.fields 127 | 128 | 129 | class HostedCIMProfileForm(forms.Form): 130 | token = forms.CharField(widget=forms.HiddenInput) 131 | def __init__(self, token, *args, **kwargs): 132 | super(HostedCIMProfileForm, self).__init__(*args, **kwargs) 133 | self.fields['token'].initial = token 134 | if settings.DEBUG: 135 | self.action = "https://test.authorize.net/profile/manage" 136 | else: 137 | self.action = "https://secure.authorize.net/profile/manage" 138 | 139 | 140 | 141 | def get_test_exp_date(): 142 | from datetime import date, timedelta 143 | test_date = date.today() + timedelta(days=365) 144 | return test_date.strftime('%m%y') 145 | -------------------------------------------------------------------------------- /authorizenet/helpers.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import requests 4 | 5 | from authorizenet.conf import settings 6 | from authorizenet import AUTHNET_POST_URL, AUTHNET_TEST_POST_URL 7 | 8 | 9 | class AIMPaymentHelper(object): 10 | def __init__(self, defaults): 11 | self.defaults = defaults 12 | if settings.DEBUG: 13 | self.endpoint = AUTHNET_TEST_POST_URL 14 | else: 15 | self.endpoint = AUTHNET_POST_URL 16 | 17 | def get_response(self, data): 18 | final_data = dict(self.defaults) 19 | final_data.update(data) 20 | c = final_data['x_delim_char'] 21 | # Escape delimiter characters in request fields 22 | for k, v in final_data.items(): 23 | if k != 'x_delim_char': 24 | final_data[k] = unicode(v).replace(c, "\\%s" % c) 25 | response = requests.post(self.endpoint, data=final_data) 26 | # Split response by delimiter, 27 | # unescaping delimiter characters in fields 28 | response_list = re.split("(? 11 | Add payment profile
12 | {% endif %} 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /authorizenet/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url 2 | 3 | urlpatterns = patterns('authorizenet.views', 4 | url(r'^sim/payment/$', 'sim_payment', name="authnet_sim_payment"), 5 | ) 6 | -------------------------------------------------------------------------------- /authorizenet/utils.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | 3 | from django.core.exceptions import ImproperlyConfigured 4 | 5 | from authorizenet.conf import settings 6 | from authorizenet.helpers import AIMPaymentHelper 7 | from authorizenet.models import Response 8 | from authorizenet.signals import payment_was_successful, payment_was_flagged 9 | 10 | 11 | def get_fingerprint(x_fp_sequence, x_fp_timestamp, x_amount): 12 | msg = '^'.join([settings.LOGIN_ID, 13 | x_fp_sequence, 14 | x_fp_timestamp, 15 | x_amount 16 | ]) + '^' 17 | 18 | return hmac.new(settings.TRANSACTION_KEY, msg).hexdigest() 19 | 20 | 21 | def extract_form_data(form_data): 22 | return dict(map(lambda x: ('x_' + x[0], x[1]), 23 | form_data.items())) 24 | 25 | AIM_DEFAULT_DICT = { 26 | 'x_login': settings.LOGIN_ID, 27 | 'x_tran_key': settings.TRANSACTION_KEY, 28 | 'x_delim_data': "TRUE", 29 | 'x_delim_char': settings.DELIM_CHAR, 30 | 'x_relay_response': "FALSE", 31 | 'x_type': "AUTH_CAPTURE", 32 | 'x_method': "CC" 33 | } 34 | 35 | 36 | def create_response(data): 37 | helper = AIMPaymentHelper(defaults=AIM_DEFAULT_DICT) 38 | response_list = helper.get_response(data) 39 | response = Response.objects.create_from_list(response_list) 40 | if response.is_approved: 41 | payment_was_successful.send(sender=response) 42 | else: 43 | payment_was_flagged.send(sender=response) 44 | return response 45 | 46 | 47 | def process_payment(form_data, extra_data): 48 | data = extract_form_data(form_data) 49 | data.update(extract_form_data(extra_data)) 50 | data['x_exp_date'] = data['x_exp_date'].strftime('%m%y') 51 | if settings.FORCE_TEST_REQUEST: 52 | data['x_test_request'] = 'TRUE' 53 | if settings.EMAIL_CUSTOMER is not None: 54 | data['x_email_customer'] = settings.EMAIL_CUSTOMER 55 | return create_response(data) 56 | 57 | 58 | def combine_form_data(*args): 59 | data = {} 60 | for form in args: 61 | data.update(form.cleaned_data) 62 | return data 63 | 64 | 65 | def capture_transaction(response, extra_data=None): 66 | if response.type.lower() != 'auth_only': 67 | raise ImproperlyConfigured( 68 | "You can capture only transactions with AUTH_ONLY type") 69 | if extra_data is None: 70 | extra_data = {} 71 | data = dict(extra_data) 72 | data['x_trans_id'] = response.trans_id 73 | #if user already specified x_amount, don't override it with response value 74 | if not data.get('x_amount', None): 75 | data['x_amount'] = response.amount 76 | data['x_type'] = 'PRIOR_AUTH_CAPTURE' 77 | if settings.FORCE_TEST_REQUEST: 78 | data['x_test_request'] = 'TRUE' 79 | return create_response(data) 80 | -------------------------------------------------------------------------------- /authorizenet/views.py: -------------------------------------------------------------------------------- 1 | try: 2 | import hashlib 3 | except ImportError: 4 | import md5 as hashlib 5 | 6 | from authorizenet.conf import settings 7 | from django.shortcuts import render 8 | from django.views.decorators.csrf import csrf_exempt 9 | from django.views.generic.edit import CreateView, UpdateView 10 | 11 | from authorizenet.forms import AIMPaymentForm, BillingAddressForm, CustomerPaymentForm 12 | from authorizenet.models import CustomerProfile, CustomerPaymentProfile 13 | from authorizenet.models import Response 14 | from authorizenet.signals import payment_was_successful, payment_was_flagged 15 | from authorizenet.utils import process_payment, combine_form_data 16 | 17 | 18 | @csrf_exempt 19 | def sim_payment(request): 20 | response = Response.objects.create_from_dict(request.POST) 21 | MD5_HASH = settings.MD5_HASH 22 | hash_is_valid = True 23 | 24 | #if MD5-Hash value is provided, use it to validate response 25 | if MD5_HASH: 26 | hash_is_valid = False 27 | hash_value = hashlib.md5(''.join([MD5_HASH, 28 | settings.LOGIN_ID, 29 | response.trans_id, 30 | response.amount])).hexdigest() 31 | 32 | hash_is_valid = hash_value.upper() == response.MD5_Hash 33 | 34 | if response.is_approved and hash_is_valid: 35 | payment_was_successful.send(sender=response) 36 | else: 37 | payment_was_flagged.send(sender=response) 38 | 39 | return render(request, 'authorizenet/sim_payment.html') 40 | 41 | 42 | class AIMPayment(object): 43 | """ 44 | Class to handle credit card payments to Authorize.NET 45 | """ 46 | 47 | processing_error = ("There was an error processing your payment. " 48 | "Check your information and try again.") 49 | form_error = "Please correct the errors below and try again." 50 | 51 | def __init__(self, 52 | extra_data={}, 53 | payment_form_class=AIMPaymentForm, 54 | context={}, 55 | billing_form_class=BillingAddressForm, 56 | shipping_form_class=None, 57 | payment_template="authorizenet/aim_payment.html", 58 | success_template='authorizenet/aim_success.html', 59 | initial_data={}): 60 | self.extra_data = extra_data 61 | self.payment_form_class = payment_form_class 62 | self.payment_template = payment_template 63 | self.success_template = success_template 64 | self.context = context 65 | self.initial_data = initial_data 66 | self.billing_form_class = billing_form_class 67 | self.shipping_form_class = shipping_form_class 68 | 69 | def __call__(self, request): 70 | self.request = request 71 | if request.method == "GET": 72 | return self.render_payment_form() 73 | else: 74 | return self.validate_payment_form() 75 | 76 | def render_payment_form(self): 77 | self.context['payment_form'] = self.payment_form_class( 78 | initial=self.initial_data) 79 | self.context['billing_form'] = self.billing_form_class( 80 | initial=self.initial_data) 81 | if self.shipping_form_class: 82 | self.context['shipping_form'] = self.shipping_form_class( 83 | initial=self.initial_data) 84 | return render( 85 | self.request, 86 | self.payment_template, 87 | self.context 88 | ) 89 | 90 | def validate_payment_form(self): 91 | payment_form = self.payment_form_class(self.request.POST) 92 | billing_form = self.billing_form_class(self.request.POST) 93 | 94 | if self.shipping_form_class: 95 | shipping_form = self.shipping_form_class(self.request.POST) 96 | 97 | #if shipping for exists also validate it 98 | if payment_form.is_valid() and billing_form.is_valid() and (not self.shipping_form_class or shipping_form.is_valid()): 99 | 100 | if not self.shipping_form_class: 101 | args = payment_form, billing_form 102 | else: 103 | args = payment_form, billing_form, shipping_form 104 | 105 | form_data = combine_form_data(*args) 106 | response = process_payment(form_data, self.extra_data) 107 | self.context['response'] = response 108 | if response.is_approved: 109 | return render( 110 | self.request, 111 | self.success_template, 112 | self.context 113 | ) 114 | else: 115 | self.context['errors'] = self.processing_error 116 | self.context['payment_form'] = payment_form 117 | self.context['billing_form'] = billing_form 118 | if self.shipping_form_class: 119 | self.context['shipping_form'] = shipping_form 120 | self.context.setdefault('errors', self.form_error) 121 | return render( 122 | self.request, 123 | self.payment_template, 124 | self.context 125 | ) 126 | 127 | 128 | class PaymentProfileCreateView(CreateView): 129 | """ 130 | View for creating a CustomerPaymentProfile instance 131 | 132 | CustomerProfile instance will be created automatically if needed. 133 | """ 134 | 135 | template_name = 'authorizenet/create_payment_profile.html' 136 | form_class = CustomerPaymentForm 137 | 138 | def get_form_kwargs(self): 139 | kwargs = super(PaymentProfileCreateView, self).get_form_kwargs() 140 | kwargs['customer'] = self.request.user 141 | return kwargs 142 | 143 | 144 | class PaymentProfileUpdateView(UpdateView): 145 | """ 146 | View for modifying an existing CustomerPaymentProfile instance 147 | """ 148 | 149 | template_name = 'authorizenet/update_payment_profile.html' 150 | form_class = CustomerPaymentForm 151 | 152 | def get_form_kwargs(self): 153 | kwargs = super(PaymentProfileUpdateView, self).get_form_kwargs() 154 | kwargs['customer'] = self.request.user 155 | return kwargs 156 | -------------------------------------------------------------------------------- /docs.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | Installation 5 | ------------ 6 | 7 | Install from `PyPI`_: 8 | 9 | .. code-block:: bash 10 | 11 | $ pip install django-authorizenet 12 | 13 | .. _PyPI: https://pypi.python.org/pypi/django-authorizenet/ 14 | 15 | 16 | Quickstart 17 | ---------- 18 | 19 | Add ``authorizenet`` to ``INSTALLED_APPS`` in your settings file: 20 | 21 | .. code-block:: python 22 | 23 | INSTALLED_APPS = ( 24 | ... 25 | 'authorizenet', 26 | ) 27 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-authorizenet.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-authorizenet.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-authorizenet" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-authorizenet" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/cim.rst: -------------------------------------------------------------------------------- 1 | CIM Usage 2 | ========= 3 | 4 | The easiest way to use the CIM support is to use the ``CustomerProfile`` and 5 | ``CustomerPaymentProfile`` models provided by the ``authorizenet`` app. These 6 | models map the ORM CRUD operations to Authorize.NET calls, making it easy to 7 | keep your local and remote data in sync. 8 | 9 | Customer profiles contain a one-to-one field ``customer`` which links to the 10 | Django user model by default. This foreign key target may be customized in the 11 | ``CUSTOMER_MODEL`` setting in your settings module. 12 | 13 | Using built-in models 14 | --------------------- 15 | 16 | CustomerPaymentProfile Model 17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | 19 | When the ``save()`` method is called on a ``CustomerPaymentProfile`` instance, 20 | the payment profile is created or update on Authorize.NET and saved to the 21 | database. A ``CustomerProfile`` will also be created if the specified 22 | ``customer`` doesn't have one yet. 23 | 24 | When the ``delete()`` method is called on a ``CustomerPaymentProfile`` 25 | instance, the payment profile is deleted on Authorize.NET and deleted from the 26 | database. 27 | 28 | Payment Profile Form 29 | ~~~~~~~~~~~~~~~~~~~~ 30 | 31 | The ``CustomerPaymentForm`` available in ``authorizenet.forms`` allows a 32 | ``CustomerPaymentProfile`` to be easily created or updated for a given 33 | ``customer``. This form is just a model form for the 34 | ``CustomerPaymentProfile`` model. 35 | 36 | Generic Views 37 | ~~~~~~~~~~~~~ 38 | 39 | The ``PaymentProfileCreateView`` and ``PaymentProfileUpdateView`` allow 40 | ``CustomerPaymentProfile`` instances can be created and updated with ease. 41 | The ``customer`` argument sent to ``CustomerPaymentForm`` defaults to the 42 | currently authenticated user. 43 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-authorizenet documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Jun 10 13:41:57 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'django-authorizenet' 44 | copyright = u'2013, Andrii Kurinnyi' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '2.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '2.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | # If true, keep warnings as "system message" paragraphs in the built documents. 90 | #keep_warnings = False 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | #html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | #html_use_smartypants = True 135 | 136 | # Custom sidebar templates, maps document names to template names. 137 | #html_sidebars = {} 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | #html_domain_indices = True 145 | 146 | # If false, no index is generated. 147 | #html_use_index = True 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | #html_show_sourcelink = True 154 | 155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 156 | #html_show_sphinx = True 157 | 158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 159 | #html_show_copyright = True 160 | 161 | # If true, an OpenSearch description file will be output, and all pages will 162 | # contain a tag referring to it. The value of this option must be the 163 | # base URL from which the finished HTML is served. 164 | #html_use_opensearch = '' 165 | 166 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 167 | #html_file_suffix = None 168 | 169 | # Output file base name for HTML help builder. 170 | htmlhelp_basename = 'django-authorizenetdoc' 171 | 172 | 173 | # -- Options for LaTeX output -------------------------------------------------- 174 | 175 | latex_elements = { 176 | # The paper size ('letterpaper' or 'a4paper'). 177 | #'papersize': 'letterpaper', 178 | 179 | # The font size ('10pt', '11pt' or '12pt'). 180 | #'pointsize': '10pt', 181 | 182 | # Additional stuff for the LaTeX preamble. 183 | #'preamble': '', 184 | } 185 | 186 | # Grouping the document tree into LaTeX files. List of tuples 187 | # (source start file, target name, title, author, documentclass [howto/manual]). 188 | latex_documents = [ 189 | ('index', 'django-authorizenet.tex', u'django-authorizenet Documentation', 190 | u'Andrii Kurinnyi', 'manual'), 191 | ] 192 | 193 | # The name of an image file (relative to this directory) to place at the top of 194 | # the title page. 195 | #latex_logo = None 196 | 197 | # For "manual" documents, if this is true, then toplevel headings are parts, 198 | # not chapters. 199 | #latex_use_parts = False 200 | 201 | # If true, show page references after internal links. 202 | #latex_show_pagerefs = False 203 | 204 | # If true, show URL addresses after external links. 205 | #latex_show_urls = False 206 | 207 | # Documents to append as an appendix to all manuals. 208 | #latex_appendices = [] 209 | 210 | # If false, no module index is generated. 211 | #latex_domain_indices = True 212 | 213 | 214 | # -- Options for manual page output -------------------------------------------- 215 | 216 | # One entry per manual page. List of tuples 217 | # (source start file, name, description, authors, manual section). 218 | man_pages = [ 219 | ('index', 'django-authorizenet', u'django-authorizenet Documentation', 220 | [u'Andrii Kurinnyi'], 1) 221 | ] 222 | 223 | # If true, show URL addresses after external links. 224 | #man_show_urls = False 225 | 226 | 227 | # -- Options for Texinfo output ------------------------------------------------ 228 | 229 | # Grouping the document tree into Texinfo files. List of tuples 230 | # (source start file, target name, title, author, 231 | # dir menu entry, description, category) 232 | texinfo_documents = [ 233 | ('index', 'django-authorizenet', u'django-authorizenet Documentation', 234 | u'Andrii Kurinnyi', 'django-authorizenet', 'One line description of project.', 235 | 'Miscellaneous'), 236 | ] 237 | 238 | # Documents to append as an appendix to all manuals. 239 | #texinfo_appendices = [] 240 | 241 | # If false, no module index is generated. 242 | #texinfo_domain_indices = True 243 | 244 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 245 | #texinfo_show_urls = 'footnote' 246 | 247 | # If true, do not generate a @detailmenu in the "Top" node's menu. 248 | #texinfo_no_detailmenu = False 249 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-authorizenet documentation master file, created by 2 | sphinx-quickstart on Mon Jun 10 13:41:57 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to django-authorizenet's documentation! 7 | =============================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | usage 15 | cim 16 | 17 | 18 | 19 | Indices and tables 20 | ================== 21 | 22 | * :ref:`genindex` 23 | * :ref:`modindex` 24 | * :ref:`search` 25 | 26 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-authorizenet.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-authorizenet.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | Installation 5 | ------------ 6 | 7 | Install from `PyPI`_: 8 | 9 | .. code-block:: bash 10 | 11 | $ pip install django-authorizenet 12 | 13 | .. _PyPI: https://pypi.python.org/pypi/django-authorizenet/ 14 | 15 | 16 | Quickstart 17 | ---------- 18 | 19 | Add ``authorizenet`` to ``INSTALLED_APPS`` in your settings file: 20 | 21 | .. code-block:: python 22 | 23 | INSTALLED_APPS = ( 24 | ... 25 | 'authorizenet', 26 | ) 27 | 28 | The following settings are required: 29 | 30 | .. code-block:: python 31 | 32 | AUTHNET_DEBUG = True 33 | 34 | AUTHNET_LOGIN_ID = "yOuRl0g1nID" 35 | 36 | AUTHNET_TRANSACTION_KEY = "Tr4n5aCti0nK3y" 37 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from os.path import abspath, dirname 3 | 4 | from django.conf import settings 5 | 6 | sys.path.insert(0, abspath(dirname(__file__))) 7 | 8 | if not settings.configured: 9 | settings.configure( 10 | AUTHNET_DEBUG=False, 11 | AUTHNET_LOGIN_ID="loginid", 12 | AUTHNET_TRANSACTION_KEY="key", 13 | INSTALLED_APPS=( 14 | 'django.contrib.contenttypes', 15 | 'django.contrib.auth', 16 | 'django.contrib.sessions', 17 | 'tests', 18 | 'authorizenet', 19 | ), 20 | ROOT_URLCONF='tests.urls', 21 | STATIC_URL='/static/', 22 | DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3'}}, 23 | ) 24 | 25 | 26 | def runtests(): 27 | from django.test.simple import DjangoTestSuiteRunner 28 | failures = DjangoTestSuiteRunner(failfast=False).run_tests(['tests']) 29 | sys.exit(failures) 30 | -------------------------------------------------------------------------------- /sample_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/__init__.py -------------------------------------------------------------------------------- /sample_project/local_settings.py.example: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | 3 | AUTHNET_DEBUG = DEBUG 4 | 5 | if AUTHNET_DEBUG: 6 | AUTHNET_LOGIN_ID = "your login id" 7 | AUTHNET_TRANSACTION_KEY = "your transaction key" 8 | 9 | """ 10 | For your reference, you can use the following test credit card numbers when testing your connection. 11 | The expiration date must be set to the present date or later: 12 | - American Express Test Card: 370000000000002 13 | - Discover Test Card: 6011000000000012 14 | - Visa Test Card: 4007000000027 15 | - Second Visa Test Card: 4012888818888 16 | - JCB: 3088000000000017 17 | - Diners Club/ Carte Blanche: 38000000000006 18 | """ -------------------------------------------------------------------------------- /sample_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /sample_project/media/contentx/IframeCommunicator.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | IFrame Communicator 5 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /sample_project/media/contentx/closeButton1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/closeButton1.png -------------------------------------------------------------------------------- /sample_project/media/contentx/closeButton1a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/closeButton1a.png -------------------------------------------------------------------------------- /sample_project/media/contentx/closeButton1h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/closeButton1h.png -------------------------------------------------------------------------------- /sample_project/media/contentx/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | empty 4 | 5 | 6 | -------------------------------------------------------------------------------- /sample_project/media/contentx/manage.css: -------------------------------------------------------------------------------- 1 | 2 | #divAuthorizeNetPopupScreen { position:fixed; left:0px; top:0px; width:100%; height:100%; z-index:1; background-color:#808080; opacity:0.5; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=50)'; filter:alpha(opacity=50); } 3 | #divAuthorizeNetPopup { position:absolute; left:50%; top:50%; margin-left:-200px; margin-top: -200px; z-index:2; overflow:visible; } 4 | .AuthorizeNetShadow { 5 | height: 16px; 6 | width: 16px; 7 | position: absolute; 8 | } 9 | .AuthorizeNetShadowT { top: -16px; left: 0px; background-image: url('shadow1-top.png'); background-repeat: repeat-x; width: 100%; } 10 | .AuthorizeNetShadowR { top: 0px; right: -16px; background-image: url('shadow1-right.png'); background-repeat: repeat-y; height: 100%; } 11 | .AuthorizeNetShadowB { bottom: -16px; left: 0px; background-image: url('shadow1-bottom.png'); background-repeat: repeat-x; width: 100%; } 12 | .AuthorizeNetShadowL { top: 0px; left: -16px; background-image: url('shadow1-left.png'); background-repeat: repeat-y; height: 100%; } 13 | .AuthorizeNetShadowTR { top: -16px; right: -16px; background-image: url('shadow1-topRight.png'); background-repeat: no-repeat; } 14 | .AuthorizeNetShadowBR { bottom: -16px; right: -16px; background-image: url('shadow1-bottomRight.png'); background-repeat: no-repeat; } 15 | .AuthorizeNetShadowBL { bottom: -16px; left: -16px; background-image: url('shadow1-bottomLeft.png'); background-repeat: no-repeat; } 16 | .AuthorizeNetShadowTL { top: -16px; left: -16px; background-image: url('shadow1-topLeft.png'); background-repeat: no-repeat; } 17 | 18 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupOuter { background-color:#dddddd; border-width:1px; border-style:solid; border-color: #a0a0a0 #909090 #909090 #a0a0a0; padding:4px; } 19 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupTop { height:23px; } 20 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupClose { position:absolute; right:7px; top:7px; } 21 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupClose a { 22 | background-image: url('closeButton1.png'); 23 | background-repeat: no-repeat; 24 | height: 16px; 25 | width: 16px; 26 | display: inline-block; 27 | } 28 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupClose a:hover { 29 | background-image: url('closeButton1h.png'); 30 | } 31 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupClose a:active { 32 | background-image: url('closeButton1a.png'); 33 | } 34 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupInner { background-color:#ffffff; border-width:2px; border-style:solid; border-color: #cfcfcf #ebebeb #ebebeb #cfcfcf; } 35 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupBottom { height:30px; } 36 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupLogo { position:absolute; right:9px; bottom:4px; width:200px; height:25px; background-image:url('powered_simple.png'); } 37 | -------------------------------------------------------------------------------- /sample_project/media/contentx/manageIELTE6.css: -------------------------------------------------------------------------------- 1 | body { height:100%; } 2 | #divAuthorizeNetPopupScreen { position:absolute; } 3 | .AuthorizeNetShadow { display:none; } 4 | .AuthorizeNetPopupGrayFrameTheme .AuthorizeNetPopupLogo { background-image:none; filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='powered_simple.png', sizingMethod='scale'); } 5 | -------------------------------------------------------------------------------- /sample_project/media/contentx/popup.js: -------------------------------------------------------------------------------- 1 | /* Usage: 2 | 3 | 4 | 5 | 26 | */ 27 | 28 | (function () { 29 | if (!window.AuthorizeNetPopup) window.AuthorizeNetPopup = {}; 30 | if (!AuthorizeNetPopup.options) AuthorizeNetPopup.options = { 31 | onPopupClosed: null 32 | ,eCheckEnabled: false 33 | ,skipZIndexCheck: false 34 | ,useTestEnvironment: false 35 | }; 36 | AuthorizeNetPopup.closePopup = function() { 37 | document.getElementById("divAuthorizeNetPopupScreen").style.display = "none"; 38 | document.getElementById("divAuthorizeNetPopup").style.display = "none"; 39 | document.getElementById("iframeAuthorizeNet").src = "contentx/empty.html"; 40 | if (AuthorizeNetPopup.options.onPopupClosed) AuthorizeNetPopup.options.onPopupClosed(); 41 | }; 42 | AuthorizeNetPopup.openManagePopup = function() { 43 | openSpecificPopup({action:"manage"}); 44 | }; 45 | AuthorizeNetPopup.openAddPaymentPopup = function() { 46 | openSpecificPopup({action:"addPayment", paymentProfileId:"new"}); 47 | }; 48 | AuthorizeNetPopup.openEditPaymentPopup = function(paymentProfileId) { 49 | openSpecificPopup({action:"editPayment", paymentProfileId:paymentProfileId}); 50 | }; 51 | AuthorizeNetPopup.openAddShippingPopup = function() { 52 | openSpecificPopup({action:"addShipping", shippingAddressId:"new"}); 53 | }; 54 | AuthorizeNetPopup.openEditShippingPopup = function(shippingAddressId) { 55 | openSpecificPopup({action:"editShipping", shippingAddressId:shippingAddressId}); 56 | }; 57 | AuthorizeNetPopup.onReceiveCommunication = function (querystr) { 58 | var params = parseQueryString(querystr); 59 | switch(params["action"]) { 60 | case "successfulSave": 61 | AuthorizeNetPopup.closePopup(); 62 | break; 63 | case "cancel": 64 | AuthorizeNetPopup.closePopup(); 65 | break; 66 | case "resizeWindow": 67 | var w = parseInt(params["width"]); 68 | var h = parseInt(params["height"]); 69 | var ifrm = document.getElementById("iframeAuthorizeNet"); 70 | ifrm.style.width = w.toString() + "px"; 71 | ifrm.style.height = h.toString() + "px"; 72 | centerPopup(); 73 | adjustPopupScreen(); 74 | break; 75 | } 76 | }; 77 | function openSpecificPopup(opt) { 78 | var popup = document.getElementById("divAuthorizeNetPopup"); 79 | var popupScreen = document.getElementById("divAuthorizeNetPopupScreen"); 80 | var ifrm = document.getElementById("iframeAuthorizeNet"); 81 | var form = document.forms["formAuthorizeNetPopup"]; 82 | 83 | switch (opt.action) { 84 | case "addPayment": 85 | ifrm.style.width = "435px"; 86 | ifrm.style.height = AuthorizeNetPopup.options.eCheckEnabled ? "508px" : "479px"; 87 | break; 88 | case "editPayment": 89 | ifrm.style.width = "435px"; 90 | ifrm.style.height = "479px"; 91 | break; 92 | case "addShipping": 93 | ifrm.style.width = "385px"; 94 | ifrm.style.height = "359px"; 95 | break; 96 | case "editShipping": 97 | ifrm.style.width = "385px"; 98 | ifrm.style.height = "359px"; 99 | break; 100 | case "manage": 101 | ifrm.style.width = "442px"; 102 | ifrm.style.height = "578px"; 103 | break; 104 | } 105 | 106 | if (!AuthorizeNetPopup.options.skipZIndexCheck) { 107 | var zIndexHightest = getHightestZIndex(); 108 | popup.style.zIndex = zIndexHightest + 2; 109 | popupScreen.style.zIndex = zIndexHightest + 1; 110 | } 111 | 112 | if (AuthorizeNetPopup.options.useTestEnvironment) { 113 | form.action = "https://test.authorize.net/profile/" + opt.action; 114 | } else { 115 | form.action = "https://secure.authorize.net/profile/" + opt.action; 116 | } 117 | if (form.elements["PaymentProfileId"]) form.elements["PaymentProfileId"].value = opt.paymentProfileId ? opt.paymentProfileId : ""; 118 | if (form.elements["ShippingAddressId"]) form.elements["ShippingAddressId"].value = opt.shippingAddressId ? opt.shippingAddressId : ""; 119 | form.submit(); 120 | 121 | popup.style.display = ""; 122 | popupScreen.style.display = ""; 123 | centerPopup(); 124 | adjustPopupScreen(); 125 | }; 126 | function centerPopup() { 127 | var d = document.getElementById("divAuthorizeNetPopup"); 128 | d.style.left = "50%"; 129 | d.style.top = "50%"; 130 | var left = -Math.floor(d.clientWidth / 2); 131 | var top = -Math.floor(d.clientHeight / 2); 132 | d.style.marginLeft = left.toString() + "px"; 133 | d.style.marginTop = top.toString() + "px"; 134 | if (d.offsetLeft < 16) { 135 | d.style.left = "16px"; 136 | d.style.marginLeft = "0px"; 137 | } 138 | if (d.offsetTop < 16) { 139 | d.style.top = "16px"; 140 | d.style.marginTop = "0px"; 141 | } 142 | } 143 | function adjustPopupScreen() { // IE6 fix 144 | var popupScreen = document.getElementById("divAuthorizeNetPopupScreen"); 145 | if (popupScreen.currentStyle && popupScreen.currentStyle.position == "absolute") { 146 | if (popupScreen.clientHeight < document.documentElement.scrollHeight) { 147 | popupScreen.style.height = document.documentElement.scrollHeight.toString() + "px"; 148 | } 149 | if (popupScreen.clientWidth < document.documentElement.scrollWidth) { 150 | popupScreen.style.width = document.documentElement.scrollWidth.toString() + "px"; 151 | } 152 | } 153 | } 154 | function getHightestZIndex() { 155 | var max = 0; 156 | var z = 0; 157 | var a = document.getElementsByTagName('*'); 158 | for (var i = 0; i < a.length; i++) { 159 | z = 0; 160 | if (a[i].currentStyle) { 161 | var style = a[i].currentStyle; 162 | if (style.display != "none") { 163 | z = parseFloat(style.zIndex); 164 | } 165 | } else if (window.getComputedStyle) { 166 | var style = window.getComputedStyle(a[i], null); 167 | if (style.getPropertyValue("display") != "none") { 168 | z = parseFloat(style.getPropertyValue("z-index")); 169 | } 170 | } 171 | if (!isNaN(z) && z > max) max = z; 172 | } 173 | return Math.ceil(max); 174 | } 175 | function parseQueryString(str) { 176 | var vars = []; 177 | var arr = str.split('&'); 178 | var pair; 179 | for (var i = 0; i < arr.length; i++) { 180 | pair = arr[i].split('='); 181 | vars.push(pair[0]); 182 | vars[pair[0]] = unescape(pair[1]); 183 | } 184 | return vars; 185 | } 186 | } ()); 187 | -------------------------------------------------------------------------------- /sample_project/media/contentx/powered_simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/powered_simple.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-bottom.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-bottomLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-bottomLeft.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-bottomRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-bottomRight.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-left.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-right.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-top.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-topLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-topLeft.png -------------------------------------------------------------------------------- /sample_project/media/contentx/shadow1-topRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/media/contentx/shadow1-topRight.png -------------------------------------------------------------------------------- /sample_project/requirements.txt: -------------------------------------------------------------------------------- 1 | django>=1.5 2 | ../ 3 | -------------------------------------------------------------------------------- /sample_project/samplestore/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/sample_project/samplestore/__init__.py -------------------------------------------------------------------------------- /sample_project/samplestore/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from samplestore.models import Invoice, Item, Customer, Address 3 | 4 | admin.site.register(Invoice) 5 | admin.site.register(Item) 6 | admin.site.register(Customer) 7 | admin.site.register(Address) 8 | -------------------------------------------------------------------------------- /sample_project/samplestore/fixtures/initial_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "samplestore.customer", 5 | "fields": { 6 | "user": 1, 7 | "shipping_same_as_billing": true 8 | } 9 | }, 10 | { 11 | "pk": 1, 12 | "model": "samplestore.item", 13 | "fields": { 14 | "price": "300", 15 | "title": "TV" 16 | } 17 | }, 18 | { 19 | "pk": 2, 20 | "model": "samplestore.item", 21 | "fields": { 22 | "price": "5", 23 | "title": "T-Shirt" 24 | } 25 | }, 26 | { 27 | "pk": 1, 28 | "model": "samplestore.invoice", 29 | "fields": { 30 | "customer": 1, 31 | "item": 1 32 | } 33 | }, 34 | { 35 | "pk": 2, 36 | "model": "samplestore.invoice", 37 | "fields": { 38 | "customer": 1, 39 | "item": 1 40 | } 41 | } 42 | ] -------------------------------------------------------------------------------- /sample_project/samplestore/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.signals import post_save 3 | 4 | from django.contrib.auth.models import User 5 | from django.contrib.localflavor.us.models import PhoneNumberField, USStateField 6 | 7 | from authorizenet.signals import payment_was_successful, payment_was_flagged 8 | 9 | 10 | ADDRESS_CHOICES = ( 11 | ('billing', 'Billing'), 12 | ('shipping', 'Shipping'), 13 | ) 14 | 15 | 16 | class Customer(models.Model): 17 | user = models.ForeignKey(User) 18 | shipping_same_as_billing = models.BooleanField(default=True) 19 | cim_profile_id = models.CharField(max_length=10) 20 | 21 | def __unicode__(self): 22 | return self.user.username 23 | 24 | 25 | class Address(models.Model): 26 | type = models.CharField(max_length=10, choices=ADDRESS_CHOICES) 27 | customer = models.ForeignKey(Customer) 28 | first_name = models.CharField(max_length=50) 29 | last_name = models.CharField(max_length=50) 30 | company = models.CharField(max_length=50, blank=True) 31 | address = models.CharField(max_length=60) 32 | city = models.CharField(max_length=40) 33 | state = USStateField() 34 | zip_code = models.CharField(max_length=20) 35 | phone = PhoneNumberField(blank=True) 36 | fax = PhoneNumberField(blank=True) 37 | 38 | def __unicode__(self): 39 | return self.customer.user.username 40 | 41 | 42 | class Item(models.Model): 43 | title = models.CharField(max_length=55) 44 | price = models.DecimalField(max_digits=8, decimal_places=2) 45 | 46 | def __unicode__(self): 47 | return self.title 48 | 49 | 50 | class Invoice(models.Model): 51 | customer = models.ForeignKey(Customer) 52 | item = models.ForeignKey(Item) 53 | 54 | def __unicode__(self): 55 | return u"" % (self.id, self.customer.user.username) 56 | 57 | 58 | def create_customer_profile(sender, instance=None, **kwargs): 59 | if instance is None: 60 | return 61 | profile, created = Customer.objects.get_or_create(user=instance) 62 | 63 | 64 | post_save.connect(create_customer_profile, sender=User) 65 | 66 | 67 | def successfull_payment(sender, **kwargs): 68 | response = sender 69 | # do something with the response 70 | 71 | 72 | def flagged_payment(sender, **kwargs): 73 | response = sender 74 | # do something with the response 75 | 76 | 77 | payment_was_successful.connect(successfull_payment) 78 | payment_was_flagged.connect(flagged_payment) 79 | -------------------------------------------------------------------------------- /sample_project/samplestore/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url 2 | 3 | 4 | urlpatterns = patterns('samplestore.views', 5 | url(r'^commit/(\d+)/$', 'commit_to_buy', name="samplestore_commit_to_buy"), 6 | url(r'^make_payment/(\d+)/$', 'make_payment', 7 | name="samplestore_make_payment"), 8 | url(r'^$', 'items', name="samplestore_items"), 9 | url(r'^capture/(\d+)/$', 'capture', name="samplestore_capture"), 10 | url(r'^capture/index/$', 'capture_index', 11 | name="samplestore_capture_index"), 12 | url(r'^create_invoice/(\d+)/$', 'create_invoice', 13 | name="samplestore_create_invoice"), 14 | url(r'^create_invoice/(\d+)/auth/$', 'create_invoice', 15 | {'auth_only': True}, name="samplestore_create_invoice_auth"), 16 | url(r'^make_direct_payment/(\d+)/$', 'make_direct_payment', 17 | name="samplestore_make_direct_payment"), 18 | url(r'^make_direct_payment/(\d+)/auth/$', 'make_direct_payment', 19 | {'auth_only': True}, name="samplestore_make_direct_payment_auth"), 20 | 21 | url(r'^edit_cim_profile/$', 'edit_cim_profile', 22 | name='edit_cim_profile'), 23 | ) 24 | -------------------------------------------------------------------------------- /sample_project/samplestore/views.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from django.conf import settings 4 | from django.core.urlresolvers import reverse 5 | from django.http import HttpResponseRedirect, Http404 6 | from django.shortcuts import render_to_response, get_object_or_404 7 | from django.template import RequestContext 8 | 9 | from django.contrib.sites.models import Site 10 | from django.contrib.auth.decorators import login_required 11 | 12 | from authorizenet import AUTHNET_POST_URL, AUTHNET_TEST_POST_URL 13 | from authorizenet.forms import SIMPaymentForm, SIMBillingForm, ShippingAddressForm 14 | from authorizenet.models import Response 15 | from authorizenet.views import AIMPayment 16 | from authorizenet.utils import get_fingerprint, capture_transaction 17 | 18 | from samplestore.models import Invoice, Item, Address 19 | 20 | 21 | def items(request): 22 | return render_to_response('samplestore/items.html', 23 | {'items': Item.objects.all()}, 24 | context_instance=RequestContext(request)) 25 | 26 | 27 | @login_required 28 | def commit_to_buy(request, item_id): 29 | item = get_object_or_404(Item, id=item_id) 30 | if request.POST: 31 | if "yes" in request.POST: 32 | invoice = Invoice.objects.create( 33 | customer=request.user.get_profile(), 34 | item=item) 35 | return HttpResponseRedirect(reverse('samplestore_make_payment', 36 | args=[invoice.id])) 37 | else: 38 | return HttpResponseRedirect(reverse('samplestore_items')) 39 | return render_to_response('samplestore/commit_to_buy.html', 40 | {'item': item}, 41 | context_instance=RequestContext(request)) 42 | 43 | 44 | @login_required 45 | def make_payment(request, invoice_id): 46 | domain = Site.objects.get_current().domain 47 | invoice = get_object_or_404(Invoice, id=invoice_id) 48 | if invoice.customer.user != request.user: 49 | raise Http404 50 | params = { 51 | 'x_amount': "%.2f" % invoice.item.price, 52 | 'x_fp_sequence': invoice_id, 53 | 'x_invoice_num': invoice_id, 54 | 'x_description': invoice.item.title, 55 | 'x_fp_timestamp': str(int(time.time())), 56 | 'x_relay_url': "http://" + domain + reverse("authnet_sim_payment"), 57 | } 58 | 59 | try: 60 | ba = invoice.customer.address_set.get(type='billing') 61 | billing_params = {'x_first_name': ba.first_name, 62 | 'x_last_name': ba.last_name, 63 | 'x_company': ba.company, 64 | 'x_address': ba.address, 65 | 'x_city': ba.city, 66 | 'x_state': ba.state, 67 | 'x_zip': ba.zip_code, 68 | 'x_country': "United States", 69 | 'x_phone': ba.phone, 70 | 'x_fax': ba.fax, 71 | 'x_email': request.user.email, 72 | 'x_cust_id': invoice.customer.id} 73 | billing_form = SIMBillingForm(initial=billing_params) 74 | except Address.DoesNotExist: 75 | billing_form = None 76 | 77 | params['x_fp_hash'] = get_fingerprint(invoice_id, 78 | params['x_fp_timestamp'], 79 | params['x_amount']) 80 | form = SIMPaymentForm(initial=params) 81 | if settings.DEBUG: 82 | post_url = AUTHNET_TEST_POST_URL 83 | else: 84 | post_url = AUTHNET_POST_URL 85 | return render_to_response('samplestore/make_payment.html', 86 | {'form': form, 87 | 'billing_form': billing_form, 88 | 'post_url': post_url}, 89 | context_instance=RequestContext(request)) 90 | 91 | 92 | @login_required 93 | def create_invoice(request, item_id, auth_only=False): 94 | item = get_object_or_404(Item, id=item_id) 95 | invoice = Invoice.objects.create(item=item, 96 | customer=request.user.get_profile()) 97 | if auth_only: 98 | final_url = reverse('samplestore_make_direct_payment_auth', 99 | args=[invoice.id]) 100 | else: 101 | final_url = reverse('samplestore_make_direct_payment', 102 | args=[invoice.id]) 103 | return HttpResponseRedirect(final_url) 104 | 105 | 106 | @login_required 107 | def make_direct_payment(request, invoice_id, auth_only=False): 108 | invoice = get_object_or_404(Invoice, id=invoice_id) 109 | if invoice.customer.user != request.user: 110 | raise Http404 111 | try: 112 | ba = invoice.customer.address_set.get(type='billing') 113 | initial_data = {'first_name': ba.first_name, 114 | 'last_name': ba.last_name, 115 | 'company': ba.company, 116 | 'address': ba.address, 117 | 'city': ba.city, 118 | 'state': ba.state, 119 | 'zip': ba.zip_code} 120 | extra_data = {'phone': ba.phone, 121 | 'fax': ba.fax, 122 | 'email': request.user.email, 123 | 'cust_id': invoice.customer.id} 124 | except Address.DoesNotExist: 125 | initial_data = {} 126 | extra_data = {} 127 | if auth_only: 128 | extra_data['type'] = 'AUTH_ONLY' 129 | extra_data['amount'] = "%.2f" % invoice.item.price 130 | extra_data['invoice_num'] = invoice.id 131 | extra_data['description'] = invoice.item.title 132 | pp = AIMPayment( 133 | extra_data=extra_data, 134 | context={'item': invoice.item}, 135 | initial_data=initial_data, 136 | shipping_form_class=ShippingAddressForm 137 | ) 138 | return pp(request) 139 | 140 | 141 | @login_required 142 | def capture_index(request): 143 | responses = Response.objects.filter(type='auth_only') 144 | if request.user.is_staff: 145 | return render_to_response('samplestore/capture_index.html', 146 | {'responses': responses}, 147 | context_instance=RequestContext(request)) 148 | raise Http404 149 | 150 | 151 | @login_required 152 | def capture(request, id): 153 | response = get_object_or_404(Response, id=id, type='auth_only') 154 | if Response.objects.filter(trans_id=response.trans_id, 155 | type='prior_auth_capture').count() > 0: 156 | raise Http404 157 | if request.user.is_staff: 158 | new_response = capture_transaction(response) 159 | return render_to_response('samplestore/capture.html', 160 | {'response': response, 161 | 'new_response': new_response}, 162 | context_instance=RequestContext(request)) 163 | raise Http404 164 | 165 | 166 | from authorizenet.cim import GetHostedProfilePageRequest, CreateProfileRequest, \ 167 | get_profile 168 | from authorizenet.forms import HostedCIMProfileForm 169 | 170 | @login_required 171 | def edit_cim_profile(request): 172 | customer = request.user.get_profile() 173 | if not customer.cim_profile_id: 174 | # Create a new empty profile 175 | helper = CreateProfileRequest(request.user.id) 176 | resp = helper.get_response() 177 | if resp.success: 178 | customer.cim_profile_id = helper.profile_id 179 | customer.save() 180 | else: 181 | # since this is a sample app, we'll just raise an exception 182 | raise Exception("Error making Authorize.NET request: %s" % resp.result_text) 183 | 184 | # Get the token for displaying the hosted CIM form 185 | settings = { 186 | # Pass these when integrating the form as a redirect: 187 | #'hostedProfileReturnUrl': 'http://localhost:8000/edit_cim_profile', 188 | #'hostedProfileReturnUrlText': 'Back to the django-authorizenet sample app', 189 | 190 | # Pass 'false' for iframes, and 'true' for redirects 191 | #'hostedProfilePageBorderVisible', 192 | 193 | # Pass this for iframes for automatic resizing 194 | #'hostedProfileIFrameCommunicatorUrl' 195 | 196 | # Optional: 197 | 'hostedProfileHeadingBgColor': '#092E20' 198 | } 199 | helper = GetHostedProfilePageRequest(customer.cim_profile_id, **settings) 200 | resp = helper.get_response() 201 | if not resp.success: 202 | raise Exception("Error making Authorize.NET request: %s" % resp.result_text) 203 | 204 | form = HostedCIMProfileForm(helper.token) 205 | 206 | # Optional - retrieve the current payment profile information for display 207 | response, payment_profiles, shipping_profiles = get_profile(customer.cim_profile_id) 208 | 209 | return render_to_response('samplestore/edit_cim_profile.html', 210 | {'form': form, 211 | 'customer': customer, 212 | 'payment_profiles': payment_profiles, 213 | 'shipping_profiles': shipping_profiles}, 214 | context_instance=RequestContext(request)) 215 | 216 | -------------------------------------------------------------------------------- /sample_project/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for authnetsite project. 2 | 3 | import os.path 4 | 5 | PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) 6 | 7 | try: 8 | from local_settings import DEBUG 9 | except ImportError: 10 | DEBUG = True 11 | TEMPLATE_DEBUG = DEBUG 12 | 13 | ADMINS = ( 14 | # ('Your name', 'email@example.com'), 15 | ) 16 | 17 | AUTH_PROFILE_MODULE = 'samplestore.Customer' 18 | 19 | MANAGERS = ADMINS 20 | 21 | DATABASES = { 22 | 'default': { 23 | 'ENGINE':'django.db.backends.sqlite3', # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 24 | 'NAME':'dev.db', # Or path to database file if using sqlite3. 25 | 'USER':'', # Not used with sqlite3. 26 | 'PASSWORD':'', # Not used with sqlite3. 27 | 'HOST':'', # Set to empty string for localhost. Not used with sqlite3. 28 | 'PORT':'' # Set to empty string for default. Not used with sqlite3. 29 | } 30 | } 31 | 32 | # Local time zone for this installation. Choices can be found here: 33 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 34 | # although not all choices may be available on all operating systems. 35 | # If running in a Windows environment this must be set to the same as your 36 | # system time zone. 37 | TIME_ZONE = 'America/Los_Angeles' 38 | 39 | LOGIN_REDIRECT_URL = '/store/' 40 | 41 | # Language code for this installation. All choices can be found here: 42 | # http://www.i18nguy.com/unicode/language-identifiers.html 43 | LANGUAGE_CODE = 'en-us' 44 | 45 | SITE_ID = 1 46 | 47 | # If you set this to False, Django will make some optimizations so as not 48 | # to load the internationalization machinery. 49 | USE_I18N = False 50 | 51 | # Absolute path to the directory that holds media. 52 | # Example: "/home/media/media.lawrence.com/" 53 | MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') 54 | 55 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 56 | # trailing slash if there is a path component (optional in other cases). 57 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 58 | MEDIA_URL = '/media/' 59 | 60 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 61 | # trailing slash. 62 | # Examples: "http://foo.com/media/", "/media/". 63 | STATIC_URL = '/static/' 64 | 65 | # Make this unique, and don't share it with anybody. 66 | SECRET_KEY = '=21(@m#)-$5r(cc110zpy$v4od_45r!k1nz!uq@v$w17&!i8=%' 67 | 68 | # List of callables that know how to import templates from various sources. 69 | TEMPLATE_LOADERS = ( 70 | 'django.template.loaders.filesystem.Loader', 71 | 'django.template.loaders.app_directories.Loader', 72 | # 'django.template.loaders.eggs.load_template_source', 73 | ) 74 | 75 | TEMPLATE_CONTEXT_PROCESSORS = ( 76 | "django.contrib.auth.context_processors.auth", 77 | "django.core.context_processors.debug", 78 | "django.core.context_processors.request", 79 | "django.core.context_processors.media", 80 | "django.core.context_processors.csrf" 81 | ) 82 | 83 | MIDDLEWARE_CLASSES = ( 84 | 'django.middleware.common.CommonMiddleware', 85 | 'django.contrib.sessions.middleware.SessionMiddleware', 86 | 'django.contrib.messages.middleware.MessageMiddleware', 87 | 'django.middleware.csrf.CsrfViewMiddleware', 88 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 89 | 'django.contrib.redirects.middleware.RedirectFallbackMiddleware', 90 | ) 91 | 92 | ROOT_URLCONF = 'urls' 93 | 94 | TEMPLATE_DIRS = ( 95 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 96 | # Always use forward slashes, even on Windows. 97 | # Don't forget to use absolute paths, not relative paths. 98 | os.path.join(PROJECT_ROOT, 'templates'), 99 | ) 100 | 101 | INSTALLED_APPS = ( 102 | 'django.contrib.auth', 103 | 'django.contrib.contenttypes', 104 | 'django.contrib.sessions', 105 | 'django.contrib.sites', 106 | 'django.contrib.admin', 107 | 'django.contrib.redirects', 108 | 'django.contrib.staticfiles', 109 | 'authorizenet', 110 | 'samplestore', 111 | ) 112 | 113 | try: 114 | from local_settings import * 115 | except ImportError: 116 | pass 117 | -------------------------------------------------------------------------------- /sample_project/templates/authorizenet/aim_payment.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 |

{{ item.title }}

5 | {{ item.price }}

6 |

{{ errors }}

7 |
{% csrf_token %} 8 |

Billing address

9 | {{ billing_form.as_p }} 10 |

Shipping address

11 | {{ shipping_form.as_p }} 12 |

Payment info

13 | {{ payment_form.as_p }} 14 | 15 |
16 | {% endblock %} 17 | -------------------------------------------------------------------------------- /sample_project/templates/authorizenet/aim_success.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 |

Response: {{ response.response_reason_text }}

5 |

Transaction ID: {{ response.trans_id }}

6 |

Description: {{ response.description }}

7 |

Amount: {{ response.amount }}

8 |

Continue Shopping

9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /sample_project/templates/authorizenet/sim_payment.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 | {{ request.POST }} 5 |
    6 |
  • Response: {{ request.POST.x_response_reason_text }}
  • 7 |
  • Response code: {{ request.POST.x_response_code }}
  • 8 |
  • Response reason code: {{ request.POST.x_response_reason_code }}
  • 9 |
  • Authorization code: {{ request.POST.x_auth_code }}
  • 10 |
  • Response subcode: {{ request.POST.x_response_subcode }}
  • 11 |
  • AVS code: {{ request.POST.x_avs_code }}
  • 12 |
  • Invoice Number: {{ request.POST.x_invoice_num }}
  • 13 |
  • Transaction ID: {{ request.POST.x_trans_id }}
  • 14 |
  • Amount: {{ request.POST.x_amount }}
  • 15 |
  • Description: {{ request.POST.x_description }}
  • 16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /sample_project/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% if site_name %}{{ site_name }} : {% endif %}{% block head_title %}{% endblock %} 6 | {% block extra_head_base %} 7 | {% block extra_head %}{% endblock %} 8 | {% endblock %} 9 | 10 | 11 | {% block body_outer %} 12 |
13 |

Sample store

14 |

15 | {% if user.is_authenticated %} 16 | Logged in as {{ user.username }} Logout 17 | {% else %} 18 | Login 19 | {% endif %} 20 |

21 | {% if messages %} 22 |
    23 | {% for message in messages %} 24 |
  • clear {{ message }}
  • 25 | {% endfor %} 26 |
27 | {% endif %} 28 | 29 | {% block body %} 30 | {% endblock %} 31 | 32 |
33 | {% endblock %} 34 | 35 | 36 | 37 | 38 | {% block extra_body_base %} 39 | {% block extra_body %}{% endblock %} 40 | {% endblock %} 41 | 42 | 43 | -------------------------------------------------------------------------------- /sample_project/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 | {% if form.errors %} 5 |

Your username and password didn't match. Please try again.

6 | {% endif %} 7 |
{% csrf_token %} 8 | {{ form.as_p }} 9 | 10 |

11 |
12 | {% endblock %} 13 | -------------------------------------------------------------------------------- /sample_project/templates/samplestore/capture.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 | {% if new_response.is_approved %} 5 |

Your transaction {{ response.trans_id }} has been sucessfully captured

6 | {% else %} 7 |

There was an error capturing your transaction {{ response.trans_id }}

8 | {% endif %} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /sample_project/templates/samplestore/capture_index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 |
    5 | {% for response in responses %} 6 |
  • {{ response }}
  • 7 | {% endfor %} 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /sample_project/templates/samplestore/commit_to_buy.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 | Are you sure you want to buy the following item: {{ item.title }} - {{ item.price }}? 5 |
{% csrf_token %} 6 | 7 | 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /sample_project/templates/samplestore/edit_cim_profile.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | Example Page 23 | 24 | 25 | 29 | 30 | 31 | 32 | 36 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 52 | 71 | 72 | 73 | 74 | 75 |

CIM Profile Data

76 | 77 | {% for pp in payment_profiles %} 78 |

Payment Profile ID: {{ pp.payment_profile_id }}

79 |
    80 |
  • Billing: 81 |
      82 | {% for k, v in pp.billing.iteritems %} 83 |
    • {{ k }}: {{ v}}
    • 84 | {% endfor %} 85 |
    86 |
  • 87 |
  • Credit Card: 88 |
      89 |
    • Number: {{ pp.credit_card.card_number }}
    • 90 |
    • Expires: {{ pp.credit_card.expiration_date }}
    • 91 |
    92 |
  • 93 |
94 | {% endfor %} 95 | 99 | 102 | 103 | 104 | 108 | 109 | 110 | 111 | 116 | 139 | 140 | 141 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /sample_project/templates/samplestore/items.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 | {% if items %} 5 | 10 | {% endif %} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /sample_project/templates/samplestore/make_payment.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block body %} 4 |
5 | {{ form.as_p }} 6 | {{ billing_form.as_p }} 7 | 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /sample_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, include, patterns 2 | from django.conf import settings 3 | from django.views.generic.base import RedirectView 4 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 5 | 6 | # Uncomment the next two lines to enable the admin: 7 | from django.contrib import admin 8 | admin.autodiscover() 9 | 10 | urlpatterns = patterns('', 11 | # Example: 12 | url(r'^$', RedirectView.as_view(url='/store/')), 13 | url(r'^accounts/login/$', 'django.contrib.auth.views.login', name="auth_login"), 14 | url(r'^accounts/logout/$', 'django.contrib.auth.views.logout_then_login', name="auth_logout"), 15 | url(r'^authnet/', include('authorizenet.urls')), 16 | url(r'^store/', include('samplestore.urls')), 17 | 18 | url(r'^admin/doc/', include('django.contrib.admindocs.urls')), 19 | url(r'^admin/', include(admin.site.urls)), 20 | 21 | url(r'^media/(?P.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}), 22 | ) 23 | 24 | urlpatterns += staticfiles_urlpatterns() 25 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = docs/ 3 | build-dir = docs/_build 4 | all_files = 1 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | import os 5 | 6 | setup(name='django-authorizenet', 7 | version='2.1', 8 | description='Django and Authorize.NET payment gateway integration', 9 | author='Andrii Kurinnyi', 10 | author_email='andrew@zen4ever.com', 11 | url='http://github.com/zen4ever/django-authorizenet/tree/master', 12 | packages=['authorizenet', 'authorizenet.migrations'], 13 | keywords=['django', 'Authorize.NET', 'payment'], 14 | classifiers=[ 15 | 'Development Status :: 4 - Beta', 16 | 'Programming Language :: Python', 17 | 'Intended Audience :: Developers', 18 | 'License :: OSI Approved :: MIT License', 19 | 'Operating System :: OS Independent', 20 | 'Framework :: Django', 21 | 'Topic :: Office/Business :: Financial', 22 | ], 23 | long_description=open( 24 | os.path.join(os.path.dirname(__file__), 'README.rst'), 25 | ).read().strip(), 26 | test_suite='runtests.runtests', 27 | tests_require=['httmock'], 28 | install_requires=['requests', 'django>=1.4', 'django-relatives>=0.2.0']) 29 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/tests/__init__.py -------------------------------------------------------------------------------- /tests/models.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zen4ever/django-authorizenet/0342e79eabffc49a85a40b0937b5e89663c334ca/tests/models.py -------------------------------------------------------------------------------- /tests/templates/authorizenet/create_payment_profile.html: -------------------------------------------------------------------------------- 1 |
2 | {{ form }} 3 | 4 |
5 | -------------------------------------------------------------------------------- /tests/templates/authorizenet/update_payment_profile.html: -------------------------------------------------------------------------------- 1 |
2 | {{ form }} 3 | 4 |
5 | -------------------------------------------------------------------------------- /tests/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .cim import * 2 | from .views import * 3 | from .models import * 4 | -------------------------------------------------------------------------------- /tests/tests/cim.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from datetime import datetime 3 | from django.test import TestCase 4 | from xml.dom.minidom import parseString 5 | from httmock import HTTMock 6 | 7 | from authorizenet.cim import extract_form_data, extract_payment_form_data, \ 8 | add_profile 9 | 10 | from .utils import xml_to_dict 11 | from .mocks import cim_url_match, customer_profile_success 12 | from .test_data import create_profile_success 13 | 14 | 15 | class ExtractFormDataTests(TestCase): 16 | 17 | """Tests for utility functions converting form data to CIM data""" 18 | 19 | def test_extract_form_data(self): 20 | new_data = extract_form_data({'word': "1", 'multi_word_str': "2"}) 21 | self.assertEqual(new_data, {'word': "1", 'multiWordStr': "2"}) 22 | 23 | def test_extract_payment_form_data(self): 24 | data = extract_payment_form_data({ 25 | 'card_number': "1111", 26 | 'expiration_date': datetime(2020, 5, 1), 27 | 'card_code': "123", 28 | }) 29 | self.assertEqual(data, { 30 | 'cardNumber': "1111", 31 | 'expirationDate': "2020-05", 32 | 'cardCode': "123", 33 | }) 34 | 35 | 36 | class AddProfileTests(TestCase): 37 | 38 | """Tests for add_profile utility function""" 39 | 40 | def setUp(self): 41 | self.payment_form_data = { 42 | 'card_number': "5586086832001747", 43 | 'expiration_date': datetime(2020, 5, 1), 44 | 'card_code': "123", 45 | } 46 | self.billing_form_data = { 47 | 'first_name': "Danielle", 48 | 'last_name': "Thompson", 49 | 'company': "", 50 | 'address': "101 Broadway Avenue", 51 | 'city': "San Diego", 52 | 'state': "CA", 53 | 'country': "US", 54 | 'zip': "92101", 55 | } 56 | self.request_data = deepcopy(create_profile_success) 57 | profile = self.request_data['createCustomerProfileRequest']['profile'] 58 | del profile['paymentProfiles']['billTo']['phoneNumber'] 59 | del profile['paymentProfiles']['billTo']['faxNumber'] 60 | 61 | def test_add_profile_minimal(self): 62 | """Success test with minimal complexity""" 63 | @cim_url_match 64 | def request_handler(url, request): 65 | request_xml = parseString(request.body) 66 | self.assertEqual(xml_to_dict(request_xml), self.request_data) 67 | return customer_profile_success.format('createCustomerProfileResponse') 68 | with HTTMock(request_handler): 69 | result = add_profile(42, self.payment_form_data, 70 | self.billing_form_data) 71 | response = result.pop('response') 72 | self.assertEqual(result, { 73 | 'profile_id': '6666', 74 | 'payment_profile_ids': ['7777'], 75 | 'shipping_profile_ids': [], 76 | }) 77 | self.assertEqual(response.result, 'Ok') 78 | self.assertEqual(response.result_code, 'I00001') 79 | self.assertEqual(response.result_text, 'Successful.') 80 | self.assertIsNone(response.transaction_response) 81 | -------------------------------------------------------------------------------- /tests/tests/mocks.py: -------------------------------------------------------------------------------- 1 | from httmock import urlmatch 2 | 3 | 4 | cim_url_match = urlmatch(scheme='https', netloc=r'^api\.authorize\.net$', 5 | path=r'^/xml/v1/request\.api$') 6 | 7 | 8 | delete_success = ( 9 | '' 10 | '<{0}>' 11 | '' 12 | 'Ok' 13 | 'I00001Successful.' 14 | '' 15 | '' 16 | ) 17 | 18 | 19 | customer_profile_success = ( 20 | '' 21 | '<{0}>' 22 | '' 23 | 'Ok' 24 | 'I00001Successful.' 25 | '' 26 | '6666' 27 | '' 28 | '7777' 29 | '' 30 | '' 31 | '' 32 | '' 33 | ) 34 | 35 | 36 | payment_profile_success = ( 37 | '' 38 | '<{0}>' 39 | '' 40 | 'Ok' 41 | 'I00001Successful.' 42 | '' 43 | '6666' 44 | '7777' 45 | '' 46 | ) 47 | -------------------------------------------------------------------------------- /tests/tests/models.py: -------------------------------------------------------------------------------- 1 | from httmock import HTTMock, with_httmock 2 | from xml.dom.minidom import parseString 3 | from django.test import TestCase 4 | from authorizenet.models import CustomerProfile 5 | 6 | from .utils import create_user, xml_to_dict 7 | from .mocks import cim_url_match, customer_profile_success, delete_success 8 | from .test_data import create_empty_profile_success, delete_profile_success 9 | 10 | 11 | class RequestError(Exception): 12 | pass 13 | 14 | 15 | def error_on_request(url, request): 16 | raise RequestError("CIM Request") 17 | 18 | 19 | class CustomerProfileModelTests(TestCase): 20 | 21 | """Tests for CustomerProfile model""" 22 | 23 | def setUp(self): 24 | self.user = create_user(id=42, username='billy', password='password') 25 | 26 | def create_profile(self): 27 | return CustomerProfile.objects.create( 28 | customer=self.user, profile_id='6666', sync=False) 29 | 30 | def test_create_sync_no_data(self): 31 | @cim_url_match 32 | def request_handler(url, request): 33 | request_xml = parseString(request.body) 34 | self.assertEqual(xml_to_dict(request_xml), 35 | create_empty_profile_success) 36 | return customer_profile_success.format( 37 | 'createCustomerProfileResponse') 38 | profile = CustomerProfile(customer=self.user) 39 | with HTTMock(error_on_request): 40 | self.assertRaises(RequestError, profile.save) 41 | self.assertEqual(profile.profile_id, '') 42 | with HTTMock(request_handler): 43 | profile.save(sync=True) 44 | self.assertEqual(profile.profile_id, '6666') 45 | 46 | @with_httmock(error_on_request) 47 | def test_create_no_sync(self): 48 | profile = CustomerProfile(customer=self.user) 49 | profile.save(sync=False) 50 | self.assertEqual(profile.profile_id, '') 51 | 52 | @with_httmock(error_on_request) 53 | def test_edit(self): 54 | profile = self.create_profile() 55 | self.assertEqual(profile.profile_id, '6666') 56 | profile.profile_id = '7777' 57 | profile.save() 58 | self.assertEqual(profile.profile_id, '7777') 59 | profile.profile_id = '8888' 60 | profile.save(sync=True) 61 | self.assertEqual(profile.profile_id, '8888') 62 | profile.profile_id = '9999' 63 | profile.save(sync=False) 64 | self.assertEqual(profile.profile_id, '9999') 65 | 66 | def test_delete(self): 67 | @cim_url_match 68 | def request_handler(url, request): 69 | request_xml = parseString(request.body) 70 | self.assertEqual(xml_to_dict(request_xml), 71 | delete_profile_success) 72 | return delete_success.format( 73 | 'deleteCustomerProfileResponse') 74 | profile = self.create_profile() 75 | with HTTMock(request_handler): 76 | profile.delete() 77 | self.assertEqual(profile.__class__.objects.count(), 0) 78 | -------------------------------------------------------------------------------- /tests/tests/test_data.py: -------------------------------------------------------------------------------- 1 | create_empty_profile_success = { 2 | 'createCustomerProfileRequest': { 3 | 'xmlns': 'AnetApi/xml/v1/schema/AnetApiSchema.xsd', 4 | 'profile': { 5 | 'merchantCustomerId': '42', 6 | }, 7 | 'merchantAuthentication': { 8 | 'transactionKey': 'key', 9 | 'name': 'loginid', 10 | }, 11 | } 12 | } 13 | 14 | 15 | create_profile_success = { 16 | 'createCustomerProfileRequest': { 17 | 'xmlns': 'AnetApi/xml/v1/schema/AnetApiSchema.xsd', 18 | 'profile': { 19 | 'merchantCustomerId': '42', 20 | 'paymentProfiles': { 21 | 'billTo': { 22 | 'firstName': 'Danielle', 23 | 'lastName': 'Thompson', 24 | 'company': '', 25 | 'phoneNumber': '', 26 | 'faxNumber': '', 27 | 'address': '101 Broadway Avenue', 28 | 'city': 'San Diego', 29 | 'state': 'CA', 30 | 'zip': '92101', 31 | 'country': 'US' 32 | }, 33 | 'payment': { 34 | 'creditCard': { 35 | 'cardCode': '123', 36 | 'cardNumber': "5586086832001747", 37 | 'expirationDate': '2020-05' 38 | } 39 | } 40 | } 41 | }, 42 | 'merchantAuthentication': { 43 | 'transactionKey': 'key', 44 | 'name': 'loginid', 45 | }, 46 | } 47 | } 48 | 49 | 50 | update_profile_success = { 51 | 'updateCustomerPaymentProfileRequest': { 52 | 'xmlns': 'AnetApi/xml/v1/schema/AnetApiSchema.xsd', 53 | 'customerProfileId': '6666', 54 | 'paymentProfile': { 55 | 'customerPaymentProfileId': '7777', 56 | 'billTo': { 57 | 'firstName': 'Danielle', 58 | 'lastName': 'Thompson', 59 | 'company': '', 60 | 'phoneNumber': '', 61 | 'faxNumber': '', 62 | 'address': '101 Broadway Avenue', 63 | 'city': 'San Diego', 64 | 'state': 'CA', 65 | 'zip': '92101', 66 | 'country': 'US' 67 | }, 68 | 'payment': { 69 | 'creditCard': { 70 | 'cardCode': '123', 71 | 'cardNumber': "5586086832001747", 72 | 'expirationDate': '2020-05' 73 | } 74 | } 75 | }, 76 | 'merchantAuthentication': { 77 | 'transactionKey': 'key', 78 | 'name': 'loginid', 79 | }, 80 | } 81 | } 82 | 83 | 84 | create_payment_profile_success = { 85 | 'createCustomerPaymentProfileRequest': { 86 | 'xmlns': 'AnetApi/xml/v1/schema/AnetApiSchema.xsd', 87 | 'customerProfileId': '6666', 88 | 'paymentProfile': { 89 | 'billTo': { 90 | 'firstName': 'Danielle', 91 | 'lastName': 'Thompson', 92 | 'phoneNumber': '', 93 | 'faxNumber': '', 94 | 'company': '', 95 | 'address': '101 Broadway Avenue', 96 | 'city': 'San Diego', 97 | 'state': 'CA', 98 | 'zip': '92101', 99 | 'country': 'US' 100 | }, 101 | 'payment': { 102 | 'creditCard': { 103 | 'cardCode': '123', 104 | 'cardNumber': "5586086832001747", 105 | 'expirationDate': '2020-05' 106 | } 107 | } 108 | }, 109 | 'merchantAuthentication': { 110 | 'transactionKey': 'key', 111 | 'name': 'loginid', 112 | }, 113 | } 114 | } 115 | 116 | 117 | delete_profile_success = { 118 | 'deleteCustomerProfileRequest': { 119 | 'xmlns': u'AnetApi/xml/v1/schema/AnetApiSchema.xsd', 120 | 'customerProfileId': '6666', 121 | 'merchantAuthentication': { 122 | 'transactionKey': 'key', 123 | 'name': 'loginid' 124 | }, 125 | }, 126 | } 127 | -------------------------------------------------------------------------------- /tests/tests/utils.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | 3 | 4 | def create_user(id=None, username='', password=''): 5 | user = User(username=username) 6 | user.id = id 7 | user.set_password(password) 8 | user.save() 9 | return user 10 | 11 | 12 | def xml_to_dict(node): 13 | """Recursively convert minidom XML node to dictionary""" 14 | node_data = {} 15 | if node.nodeType == node.TEXT_NODE: 16 | node_data = node.data 17 | elif node.nodeType not in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE): 18 | node_data.update(node.attributes.items()) 19 | if node.nodeType not in (node.TEXT_NODE, node.DOCUMENT_TYPE_NODE): 20 | for child in node.childNodes: 21 | child_name, child_data = xml_to_dict(child) 22 | if not child_data: 23 | child_data = '' 24 | if child_name not in node_data: 25 | node_data[child_name] = child_data 26 | else: 27 | if not isinstance(node_data[child_name], list): 28 | node_data[child_name] = [node_data[child_name]] 29 | node_data[child_name].append(child_data) 30 | if node_data.keys() == ['#text']: 31 | node_data = node_data['#text'] 32 | if node.nodeType == node.DOCUMENT_NODE: 33 | return node_data 34 | else: 35 | return node.nodeName, node_data 36 | -------------------------------------------------------------------------------- /tests/tests/views.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from django.test import LiveServerTestCase 3 | from xml.dom.minidom import parseString 4 | from httmock import HTTMock 5 | 6 | from authorizenet.models import CustomerProfile, CustomerPaymentProfile 7 | 8 | from .utils import create_user, xml_to_dict 9 | from .mocks import cim_url_match, customer_profile_success, \ 10 | payment_profile_success 11 | from .test_data import create_profile_success, update_profile_success, \ 12 | create_payment_profile_success 13 | 14 | 15 | class PaymentProfileCreationTests(LiveServerTestCase): 16 | 17 | def setUp(self): 18 | self.user = create_user(id=42, username='billy', password='password') 19 | self.client.login(username='billy', password='password') 20 | 21 | def test_create_new_customer_get(self): 22 | response = self.client.get('/customers/create') 23 | self.assertNotIn("This field is required", response.content) 24 | self.assertIn("Credit Card Number", response.content) 25 | self.assertIn("City", response.content) 26 | 27 | def test_create_new_customer_post_error(self): 28 | response = self.client.post('/customers/create') 29 | self.assertIn("This field is required", response.content) 30 | self.assertIn("Credit Card Number", response.content) 31 | self.assertIn("City", response.content) 32 | 33 | def test_create_new_customer_post_success(self): 34 | @cim_url_match 35 | def create_customer_success(url, request): 36 | request_xml = parseString(request.body) 37 | self.assertEqual(xml_to_dict(request_xml), create_profile_success) 38 | return customer_profile_success.format('createCustomerProfileResponse') 39 | with HTTMock(create_customer_success): 40 | response = self.client.post('/customers/create', { 41 | 'card_number': "5586086832001747", 42 | 'expiration_date_0': "5", 43 | 'expiration_date_1': "2020", 44 | 'card_code': "123", 45 | 'first_name': "Danielle", 46 | 'last_name': "Thompson", 47 | 'address': "101 Broadway Avenue", 48 | 'city': "San Diego", 49 | 'state': "CA", 50 | 'country': "US", 51 | 'zip': "92101", 52 | }, follow=True) 53 | self.assertIn("success", response.content) 54 | 55 | def test_create_new_payment_profile_post_success(self): 56 | @cim_url_match 57 | def request_handler(url, request): 58 | request_xml = parseString(request.body) 59 | self.assertEqual(xml_to_dict(request_xml), 60 | create_payment_profile_success) 61 | return payment_profile_success.format('createCustomerPaymentProfileResponse') 62 | CustomerProfile.objects.create(customer=self.user, profile_id='6666', sync=False) 63 | with HTTMock(request_handler): 64 | response = self.client.post('/customers/create', { 65 | 'card_number': "5586086832001747", 66 | 'expiration_date_0': "5", 67 | 'expiration_date_1': "2020", 68 | 'card_code': "123", 69 | 'first_name': "Danielle", 70 | 'last_name': "Thompson", 71 | 'address': "101 Broadway Avenue", 72 | 'city': "San Diego", 73 | 'state': "CA", 74 | 'country': "US", 75 | 'zip': "92101", 76 | }, follow=True) 77 | self.assertIn("success", response.content) 78 | 79 | 80 | class PaymentProfileUpdateTests(LiveServerTestCase): 81 | 82 | def setUp(self): 83 | self.user = create_user(id=42, username='billy', password='password') 84 | profile = CustomerProfile(customer=self.user, profile_id='6666') 85 | profile.save(sync=False) 86 | self.payment_profile = CustomerPaymentProfile( 87 | customer=self.user, 88 | customer_profile=profile, 89 | payment_profile_id='7777', 90 | ) 91 | self.payment_profile.save(sync=False) 92 | self.client.login(username='billy', password='password') 93 | 94 | def test_update_profile_get(self): 95 | response = self.client.get('/customers/update') 96 | self.assertNotIn("This field is required", response.content) 97 | self.assertIn("Credit Card Number", response.content) 98 | self.assertIn("City", response.content) 99 | 100 | def test_update_profile_post_error(self): 101 | response = self.client.post('/customers/update') 102 | self.assertIn("This field is required", response.content) 103 | self.assertIn("Credit Card Number", response.content) 104 | self.assertIn("City", response.content) 105 | 106 | def test_update_profile_post_success(self): 107 | @cim_url_match 108 | def create_customer_success(url, request): 109 | request_xml = parseString(request.body) 110 | self.assertEqual(xml_to_dict(request_xml), 111 | update_profile_success) 112 | return customer_profile_success.format('updateCustomerProfileResponse') 113 | with HTTMock(create_customer_success): 114 | response = self.client.post('/customers/update', { 115 | 'card_number': "5586086832001747", 116 | 'expiration_date_0': "5", 117 | 'expiration_date_1': "2020", 118 | 'card_code': "123", 119 | 'first_name': "Danielle", 120 | 'last_name': "Thompson", 121 | 'address': "101 Broadway Avenue", 122 | 'city': "San Diego", 123 | 'state': "CA", 124 | 'country': "US", 125 | 'zip': "92101", 126 | }, follow=True) 127 | self.assertIn("success", response.content) 128 | payment_profile = self.user.customer_profile.payment_profiles.get() 129 | self.assertEqual(payment_profile.raw_data, { 130 | 'id': payment_profile.id, 131 | 'customer_profile': self.user.customer_profile.id, 132 | 'customer': self.user.id, 133 | 'payment_profile_id': '7777', 134 | 'card_number': 'XXXX1747', 135 | 'expiration_date': date(2020, 5, 31), 136 | 'card_code': None, 137 | 'first_name': 'Danielle', 138 | 'last_name': 'Thompson', 139 | 'company': '', 140 | 'fax_number': '', 141 | 'phone_number': '', 142 | 'address': '101 Broadway Avenue', 143 | 'city': 'San Diego', 144 | 'state': 'CA', 145 | 'country': 'US', 146 | 'zip': '92101', 147 | }) 148 | 149 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url, patterns 2 | from .views import CreateCustomerView, UpdateCustomerView, success_view 3 | 4 | urlpatterns = patterns( 5 | '', 6 | url(r"^customers/create$", CreateCustomerView.as_view()), 7 | url(r"^customers/update$", UpdateCustomerView.as_view()), 8 | url(r"^success$", success_view), 9 | ) 10 | -------------------------------------------------------------------------------- /tests/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from authorizenet.views import PaymentProfileCreateView, PaymentProfileUpdateView 3 | 4 | 5 | class CreateCustomerView(PaymentProfileCreateView): 6 | def get_success_url(self): 7 | return '/success' 8 | 9 | 10 | class UpdateCustomerView(PaymentProfileUpdateView): 11 | 12 | def get_object(self): 13 | return self.request.user.customer_profile.payment_profiles.get() 14 | 15 | def get_success_url(self): 16 | return '/success' 17 | 18 | 19 | def success_view(request): 20 | return HttpResponse("success") 21 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py26-1.4, py26-1.5, py26-1.6, 4 | py27-1.4, py27-1.5, py26-1.6, py27-trunk, 5 | docs 6 | 7 | 8 | [testenv] 9 | commands = coverage run -a setup.py test 10 | 11 | 12 | [testenv:docs] 13 | changedir = docs 14 | deps = 15 | Sphinx 16 | commands = 17 | sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 18 | 19 | 20 | [testenv:py26-1.4] 21 | basepython = python2.6 22 | deps = 23 | django == 1.4.2 24 | coverage == 3.6 25 | 26 | [testenv:py26-1.5] 27 | basepython = python2.6 28 | deps = 29 | django == 1.5 30 | coverage == 3.6 31 | 32 | [testenv:py26-1.6] 33 | basepython = python2.6 34 | deps = 35 | https://github.com/django/django/tarball/stable/1.6.x 36 | coverage == 3.6 37 | 38 | 39 | [testenv:py27-1.4] 40 | basepython = python2.7 41 | deps = 42 | django == 1.4.2 43 | coverage == 3.6 44 | 45 | [testenv:py27-1.5] 46 | basepython = python2.7 47 | deps = 48 | django == 1.5 49 | coverage == 3.6 50 | 51 | [testenv:py27-1.6] 52 | basepython = python2.7 53 | deps = 54 | https://github.com/django/django/tarball/stable/1.6.x 55 | coverage == 3.6 56 | 57 | [testenv:py27-trunk] 58 | basepython = python2.7 59 | deps = 60 | https://github.com/django/django/tarball/master 61 | coverage == 3.6 62 | --------------------------------------------------------------------------------