├── .gitignore ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst ├── tutorial.rst └── zebra │ ├── configuration.rst │ ├── index.rst │ ├── mixins.rst │ ├── models.rst │ ├── signals.rst │ ├── utils.rst │ └── views.rst ├── requirements.txt ├── setup.py ├── zebra ├── __init__.py ├── admin.py ├── conf │ ├── __init__.py │ └── options.py ├── forms.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── clear_stripe_test_customers.py ├── mixins.py ├── models.py ├── signals.py ├── static │ └── zebra │ │ ├── card-form.css │ │ └── card-form.js ├── templates │ └── zebra │ │ ├── _basic_card_form.html │ │ └── _stripe_js_and_set_stripe_key.html ├── templatetags │ ├── __init__.py │ └── zebra_tags.py ├── urls.py ├── utils.py ├── views.py └── widgets.py └── zebra_sample_project ├── __init__.py ├── manage.py ├── marty ├── __init__.py ├── models.py ├── templates │ └── marty │ │ ├── base.html │ │ └── basic_update.html ├── tests.py ├── urls.py └── views.py ├── requirements.txt ├── settings.py └── urls.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.pyc 4 | *.pyo 5 | *.swp 6 | pip-log.txt 7 | zebra_sample_project/zebra_sample.db 8 | zebra_sample_project/locals.py 9 | django_zebra.egg-info/* 10 | dist/* 11 | build/* 12 | _build 13 | scratch 14 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Basic ideas came from: 2 | 3 | Stripe's library: https://github.com/stripe/stripe-python/ 4 | Stripe's monospace sample app: https://github.com/stripe/monospace-django 5 | 6 | Code came from: 7 | 8 | Steven Skoczen, GoodCloud http://www.agoodcloud.com 9 | Lee Trout, http://www.leetrout.com 10 | 11 | 12 | Pulls from: 13 | Minor cleanups - mixmastamyk: https://github.com/mixmastamyk 14 | Implementation of v2 webhooks from stripe - sivy: https://github.com/sivy 15 | Mixins fix - danawoodman: https://github.com/danawoodman 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 GoodCloud, LLC (http://agoodcloud.com) 2 | 3 | The MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md AUTHORS LICENSE 2 | recursive-include zebra/templates/zebra * 3 | recursive-include zebra/static/zebra * 4 | recursive-include zebra_sample_project/marty/templates/marty * 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Overview 2 | ======== 3 | 4 | Zebra is a library that makes using Stripe with Django even easier. 5 | 6 | It's made of: 7 | 8 | * `zebra`, the core library, with forms, webhook handlers, abstract models, mixins, signals, and templatetags that cover most stripe implementations. 9 | * `marty`, an example app for how to integrate zebra, that also serves as its test suite. 10 | 11 | Pull requests are quite welcome! 12 | 13 | 14 | Usage 15 | ===== 16 | 17 | ## Installation ## 18 | 19 | 1. `pip install django-zebra` 20 | 21 | 2. Edit your `settings.py:` 22 | 23 | ``` 24 | INSTALLED_APPS += ("zebra",) 25 | STRIPE_SECRET = "YOUR-SECRET-API-KEY" 26 | STRIPE_PUBLISHABLE = "YOUR-PUBLISHABLE-API-KEY" 27 | # Set any optional settings (below) 28 | ``` 29 | 30 | 3. (optional) `./manage.py syncdb` if you have `ZEBRA_ENABLE_APP = True` 31 | 32 | 4. (optional) Add in the webhook urls: 33 | 34 | ``` 35 | urlpatterns += patterns('', 36 | url(r'zebra/', include('zebra.urls', namespace="zebra", app_name='zebra') ), 37 | ) 38 | ``` 39 | 40 | 5. Enjoy easy billing. 41 | 42 | 43 | ### Optional Settings: 44 | 45 | * `ZEBRA_ENABLE_APP` 46 | Defaults to `False`. Enables Customer, Plan, and Subscription django models, as a part of zebra. 47 | * `ZEBRA_CUSTOMER_MODEL` 48 | The app+model string for the model that implements the StripeCustomerMixin. ie `"myapp.MyCustomer"`. If `ZEBRA_ENABLE_APP` is true, defaults to `"zebra.Customer"`. 49 | * `ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS` 50 | Defaults to `True`. Automatically creates a stripe customer object on stripe_customer access, if one doesn't exist. 51 | 52 | 53 | ## Webhooks ## 54 | 55 | Zebra handles all the webhooks that stripe sends back and calls a set of signals that you can plug your app into. To use the webhooks: 56 | 57 | * Include the zebra urls 58 | * Update your stripe account to point to your webhook URL (aka https://www.mysite.com/zebra/webhooks) 59 | * Plug into any webhook signals you care about. 60 | 61 | **Note: The initial Stripe webhook system is being deprecated. See below for a description of Zebra's support for the new system.** 62 | 63 | Zebra provides: 64 | 65 | * `zebra_webhook_recurring_payment_failed` 66 | * `zebra_webhook_invoice_ready` 67 | * `zebra_webhook_recurring_payment_succeeded` 68 | * `zebra_webhook_subscription_trial_ending` 69 | * `zebra_webhook_subscription_final_payment_attempt_failed` 70 | 71 | All of the webhooks provide the same arguments: 72 | 73 | * `customer` - if `ZEBRA_CUSTOMER_MODEL` is set, returns an instance that matches the `stripe_customer_id`, or `None`. If `ZEBRA_CUSTOMER_MODEL` is not set, returns `None`. 74 | * `full_json` - the full json response, parsed with simplejson. 75 | 76 | 77 | So, for example, to update the customer's new billing date after a successful payment, you could: 78 | 79 | (assuming you've set `ZEBRA_CUSTOMER_MODEL` or are using `ZEBRA_ENABLE_APP`): 80 | 81 | ``` 82 | from zebra.signals import zebra_webhook_recurring_payment_succeeded 83 | 84 | def update_last_invoice_date(sender, **kwargs): 85 | customer = kwargs.pop("customer", None) 86 | full_json = kwargs.pop("full_json", None) 87 | customer.billing_date = full_json.date 88 | customer.save() 89 | 90 | zebra_webhook_recurring_payment_succeeded.connect(update_last_invoice_date) 91 | ``` 92 | 93 | ### Webhooks Update ### 94 | 95 | Stripe recently updated their webhook implementation (see https://stripe.com/blog/webhooks). Zebra includes an implementation of the new system. 96 | 97 | * Include the zebra urls 98 | * Update your stripe account to point to your webhook URL (aka https://www.mysite.com/zebra/webhooks/v2/) 99 | * Plug into any webhook signals you care about. 100 | 101 | Zebra provides: 102 | 103 | * `zebra_webhook_charge_succeeded` 104 | * `zebra_webhook_charge_failed` 105 | * `zebra_webhook_charge_refunded` 106 | * `zebra_webhook_charge_disputed` 107 | * `zebra_webhook_customer_created` 108 | * `zebra_webhook_customer_updated` 109 | * `zebra_webhook_customer_deleted` 110 | * `zebra_webhook_customer_subscription_created` 111 | * `zebra_webhook_customer_subscription_updated` 112 | * `zebra_webhook_customer_subscription_deleted` 113 | * `zebra_webhook_customer_subscription_trial_will_end` 114 | * `zebra_webhook_customer_discount_created` 115 | * `zebra_webhook_customer_discount_updated` 116 | * `zebra_webhook_customer_discount_deleted` 117 | * `zebra_webhook_invoice_created` 118 | * `zebra_webhook_invoice_updated` 119 | * `zebra_webhook_invoice_payment_succeeded` 120 | * `zebra_webhook_invoice_payment_failed` 121 | * `zebra_webhook_invoiceitem_created` 122 | * `zebra_webhook_invoiceitem_updated` 123 | * `zebra_webhook_invoiceitem_deleted` 124 | * `zebra_webhook_plan_created` 125 | * `zebra_webhook_plan_updated` 126 | * `zebra_webhook_plan_deleted` 127 | * `zebra_webhook_coupon_created` 128 | * `zebra_webhook_coupon_updated` 129 | * `zebra_webhook_coupon_deleted` 130 | * `zebra_webhook_transfer_created` 131 | * `zebra_webhook_transfer_failed` 132 | * `zebra_webhook_ping` 133 | 134 | Zebra also provides an easy map of all the signals as `zebra.signals.WEBHOOK_MAP`, which maps events (`charge_succeeded`) to the Zebra signal (`zebra_webhook_charge_succeeded`). To assign a handler to all the signals that zebra sends, for example, loop over the items in the map: 135 | 136 | for event_key, webhook_signal in WEBHOOK_MAP.iteritems(): 137 | webhook_signal.connect(webhook_logger) 138 | 139 | 140 | ## Forms ## 141 | 142 | The StripePaymentForm sets up a form with fields like [the official stripe example](https://gist.github.com/1204718#file_stripe_tutorial_page.html). 143 | 144 | In particular, the form is stripped of the name attribute for any of the credit card fields, to prevent accidental submission. Media is also provided to set up stripe.js (it assumes you have jQuery). 145 | 146 | Use it in a view like so: 147 | 148 | ``` 149 | if request.method == 'POST': 150 | zebra_form = StripePaymentForm(request.POST) 151 | if zebra_form.is_valid(): 152 | my_profile = request.user.get_profile() 153 | stripe_customer = stripe.Customer.retrieve(my_profile.stripe_customer_id) 154 | stripe_customer.card = zebra_form.cleaned_data['stripe_token'] 155 | stripe_customer.save() 156 | 157 | my_profile.last_4_digits = zebra_form.cleaned_data['last_4_digits'] 158 | my_profile.stripe_customer_id = stripe_customer.id 159 | my_profile.save() 160 | 161 | # Do something kind for the user 162 | 163 | else: 164 | zebra_form = StripePaymentForm() 165 | ``` 166 | 167 | ## Template Tags ## 168 | 169 | There are a couple of template tags that take care of setting up the stripe env, and rendering a basic cc update form. Note that it's expected your `StripePaymentForm` is called either `zebra_form` or `form`. 170 | 171 | To use in a template: 172 | 173 | ``` 174 | {% extends "base.html" %}{% load zebra_tags %} 175 | 176 | {% block head %}{{block.super}} 177 | {% zebra_head_and_stripe_key %} 178 | {% endblock %} 179 | 180 | {% block content %} 181 | {% zebra_card_form %} 182 | {% endblock %} 183 | 184 | ``` 185 | 186 | That's it - all the stripe tokeny goodness happens, and errors are displayed to your users. 187 | 188 | ## Models and Mixins ## 189 | 190 | Model and Mixin docs coming. For now, the code is pretty self-explanatory, and decently documented inline. 191 | 192 | 193 | ## Other Useful Bits ## 194 | 195 | Zebra comes with a manage.py command to clear out all the test customers from your account. To use it, run: 196 | 197 | ``` 198 | ./manage.py clear_stripe_test_customers 199 | ``` 200 | 201 | It responds to `--verbosity=[0-3]`. 202 | 203 | 204 | Credits 205 | ======= 206 | 207 | I did not write any of stripe. It just makes me happy to use, and inspired to make better APIs for my users. For Stripe info, ask them: [stripe.com](http://stripe.com) 208 | 209 | Code credits are in the AUTHORS file. Pull requests welcome! 210 | 211 | 212 | -------------------------------------------------------------------------------- /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 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 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 " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-zebra.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-zebra.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-zebra" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-zebra" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-zebra documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Feb 16 22:57:53 2012. 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.todo', '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-zebra' 44 | copyright = u'2012, Steven Skoczen & Lee Trout' 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 = '1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '1.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 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'django-zebradoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'django-zebra.tex', u'django-zebra Documentation', 187 | u'Steven Skoczen \\& Lee Trout', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'django-zebra', u'django-zebra Documentation', 217 | [u'Steven Skoczen & Lee Trout'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'django-zebra', u'django-zebra Documentation', 231 | u'Steven Skoczen & Lee Trout', 'django-zebra', 'Stripe library for Django', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-zebra documentation master file, created by 2 | sphinx-quickstart on Thu Feb 16 22:57:53 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | django-zebra 7 | ============ 8 | 9 | Zebra is a library that makes using Stripe_ with Django_ even easier. 10 | 11 | Please report errors and omissions at 12 | https://github.com/GoodCloud/django-zebra/issues 13 | 14 | django-zebra contains two components: 15 | 16 | - ``zebra`` which is the core library providing forms, webhook handlers, 17 | abstract models, mixins, signals, and template tags that cover most of 18 | the Stripe functionality for many implementations. 19 | - ``marty`` which is an example app that integrates django-zebra and provides 20 | a test suite. 21 | 22 | .. _Django: http://www.djangoproject.com/ 23 | .. _Stripe: http://www.stripe.com/ 24 | 25 | 26 | Installation 27 | ============ 28 | 29 | **Install django-zebra**:: 30 | 31 | pip install django-zebra 32 | 33 | 34 | **Edit your Django settings**:: 35 | 36 | INSTALLED_APPS += ('zebra',) 37 | STRIPE_SECRET = "YOUR-SECRET-API-KEY" 38 | STRIPE_PUBLISHABLE = "YOUR-PUBLISHABLE-API-KEY" 39 | 40 | 41 | **Enable optional settings** 42 | 43 | See :doc:`zebra/configuration` for settings available. 44 | 45 | 46 | **Enable Webhook URLs** 47 | 48 | This is optional and only if you wish to use zebra's webhook signals. 49 | 50 | :: 51 | 52 | urlpatterns += patterns('', 53 | url(r'zebra/', include('zebra.urls', namespace="zebra", app_name='zebra')), 54 | ) 55 | 56 | 57 | Quick Start 58 | =========== 59 | 60 | By default, adding zebra to your installed apps will not provide any 61 | functionality beyond the webhooks *if* you enable the url pattern. 62 | 63 | The library was designed to be as flexible as possible so it doesn't step on 64 | your toes or get in your way. 65 | 66 | So following along from the installation above you'll discover that running 67 | ``syncdb`` has no affect. At this point you have 2 options: 68 | 69 | - Use the mixins and abstract base classes to extend your own models 70 | - Enable the default application (models & admin) 71 | 72 | Customizing your existing application with ABCs and mixins 73 | -------------------------------------------------------------- 74 | 75 | If you already have an existing model for customers you can still use the 76 | default zebra application and simply provide your model class (see the next 77 | section). 78 | 79 | If you want to forego using the default application you can find all the 80 | documentation on this site and in the source code which can be viewed in the 81 | `GitHub repo`_. 82 | 83 | The easiest way to get started is to extend one of the base classes provided. 84 | 85 | Check out :doc:`zebra/mixins` for a list of all the mixins that come with the 86 | zebra library. The :doc:`tutorial` also goes into detail on mixing zebra with 87 | an existing application. 88 | 89 | 90 | Enabling the default application 91 | -------------------------------- 92 | 93 | To enable the default application simply edit your projects settings and 94 | include:: 95 | 96 | ZEBRA_ENABLE_APP = True 97 | 98 | Then run ``syncdb`` to install the models. 99 | 100 | You should now find the zebra application listed in the admin with three default 101 | models: 102 | 103 | - Customers 104 | - Plans 105 | - Subscriptions 106 | 107 | If you already have a model that you want to use for customers simply 108 | add the following setting:: 109 | 110 | ZEBRA_CUSTOMER_MODEL = 'your_app.YourCustomerModel' 111 | 112 | Then in your models file simply update your model in inherit from 113 | ``zebra.models.StripeCustomer`` abstract base class:: 114 | 115 | from zebra.models import StripeCustomer 116 | 117 | class YourCustomerModel(StripeCustomer): 118 | ... 119 | 120 | Now ``YourCustomerModel`` will have 2 new attributes, ``stripe`` and 121 | ``stripe_customer`` along with a new field, stripe_customer_id. If you've 122 | decided to go this route, simply replace ``zebra.models.Customer`` with 123 | ``your_app.models.YourCustomerModel`` in the examples below. 124 | 125 | By default ``ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS`` is ``True`` so creating new 126 | customers is a breeze by simply using the mixed-in helper. Simply instantiate 127 | your customer class and access the ``stripe_customer`` attribute. 128 | 129 | >>> import zebra 130 | >>> c = zebra.models.Customer() 131 | >>> c.stripe_customer_id 132 | >>> c.id 133 | >>> c.stripe_customer 134 | JSON: { 135 | "account_balance": 0, 136 | "created": 1329775998, 137 | "id": "cus_A0FULkVQwzwlUz", 138 | "livemode": false, 139 | "object": "customer" 140 | } 141 | >>> c.id 142 | 1 143 | >>> c.stripe_customer_id 144 | u'cus_A0FULkVQwzwlUz' 145 | 146 | As you can see in the example above, with the default customer model there is 147 | only 1 field so once the model is in a savable state (all required fields 148 | containing valid values) simply calling ``c.stripe_customer`` created a new 149 | customer within Stripe, updated the instance with the newly created customer ID 150 | and returned the Stripe API customer instance. 151 | 152 | Let's charge our new customer $10. 153 | 154 | First we must give our customer a credit card:: 155 | 156 | >>> card = { 157 | ... 'number': '4242424242424242', 158 | ... 'exp_month': '03', 159 | ... 'exp_year': '2013', 160 | ... 'cvc': '123', 161 | ... 'name': 'John Doe' 162 | ... } 163 | >>> stripe_cust = c.stripe_customer 164 | >>> stripe_cust.card = card 165 | >>> stripe_cust.save() 166 | JSON: { 167 | "account_balance": 0, 168 | "active_card": { 169 | "country": "US", 170 | "cvc_check": "pass", 171 | "exp_month": 3, 172 | "exp_year": 2013, 173 | "last4": "4242", 174 | "name": "John Doe", 175 | "object": "card", 176 | "type": "Visa" 177 | }, 178 | "created": 1329775998, 179 | "id": "cus_A0FULkVQwzwlUz", 180 | "livemode": false, 181 | "object": "customer" 182 | } 183 | 184 | Now we can charge John $10 for that pizza we split:: 185 | 186 | >>> stripe.Charge.create( 187 | ... amount=1000, 188 | ... currency='usd', 189 | ... customer=c.stripe_customer_id, 190 | ... description='the money you owed me for the pizza' 191 | ... ) 192 | JSON: { 193 | "amount": 1000, 194 | "card": { 195 | "country": "US", 196 | "cvc_check": "pass", 197 | "exp_month": 3, 198 | "exp_year": 2013, 199 | "last4": "4242", 200 | "name": "John Doe", 201 | "object": "card", 202 | "type": "Visa" 203 | }, 204 | "created": 1329776973, 205 | "currency": "usd", 206 | "customer": "cus_A0FULkVQwzwlUz", 207 | "description": "the money you owed me", 208 | "disputed": false, 209 | "fee": 59, 210 | "id": "ch_lCSjHD3hAxXcjO", 211 | "livemode": false, 212 | "object": "charge", 213 | "paid": true, 214 | "refunded": false 215 | } 216 | 217 | 218 | 219 | Contents: 220 | ========= 221 | 222 | .. toctree:: 223 | :maxdepth: 2 224 | :glob: 225 | 226 | * 227 | zebra/index 228 | 229 | 230 | 231 | Indices and tables 232 | ================== 233 | 234 | * :ref:`genindex` 235 | * :ref:`modindex` 236 | * :ref:`search` 237 | 238 | .. _GitHub repo: https://github.com/GoodCloud/django-zebra/ 239 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ========== 3 | 4 | **THIS DOCUMENT IS STILL UNDER DEVELOPMENT** 5 | 6 | This is an example quickstart that makes many assumptions about your development 7 | environment. Please report errors and omissions at 8 | https://github.com/GoodCloud/django-zebra/issues 9 | 10 | 11 | Activate Environment 12 | -------------------- 13 | 14 | Create or activate your development environment. 15 | 16 | For this example we will be using virtual environment and virtual environment 17 | wrapper to create a demo environment. 18 | 19 | :: 20 | 21 | mkvirtualenv --no-site-packages --distribute zebra-demo 22 | 23 | 24 | Install Dependencies 25 | -------------------- 26 | 27 | ``django-zebra`` is a library for integrating Stripe payments into Django 28 | applications so you will need to install the following dependencies: 29 | 30 | - pycurl *(recommended but not required)* 31 | - django 32 | - stripe 33 | - django-zebra 34 | 35 | :: 36 | 37 | pip install pycurl django stripe django-zebra 38 | 39 | 40 | Configure Django 41 | ---------------- 42 | 43 | You'll need to include your Stripe account information in your environment and 44 | there are 2 supported ways to do this. 45 | 46 | #. Environment Variables 47 | #. Django's settings file 48 | 49 | **Environment Variables** 50 | 51 | In BASH just export the variables:: 52 | 53 | export STRIPE_PUBLISHABLE=YOUR_PUB_KEY 54 | export STRIPE_SECRET=YOUR_SECRET_KEY 55 | 56 | **Django Settings** 57 | 58 | Append the following lines to your project's settings.py file:: 59 | 60 | STRIPE_PUBLISHABLE = "YOUR_PUB_KEY" 61 | STRIPE_SECRET = "YOUR_SECRET_KEY" 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/zebra/configuration.rst: -------------------------------------------------------------------------------- 1 | .. py:module:: zebra.config.options 2 | 3 | Configuration 4 | ============= 5 | 6 | There are several options for Zebra. To override any of these options simply 7 | add them to your ``settings.py`` with the value your desire. 8 | 9 | 10 | ------------------------------------------------------------------------------- 11 | 12 | 13 | .. py:data:: STRIPE_PUBLISHABLE 14 | 15 | default: 16 | ``''`` 17 | 18 | **Required to use the Stripe API.** 19 | 20 | Your Stripe API publishable key. 21 | 22 | 23 | ------------------------------------------------------------------------------- 24 | 25 | 26 | .. py:data:: STRIPE_SECRET 27 | 28 | default: 29 | ``''`` 30 | 31 | **Required to use the Stripe API.** 32 | 33 | Your Stripe API secret key. 34 | 35 | 36 | ------------------------------------------------------------------------------- 37 | 38 | 39 | .. py:data:: ZEBRA_AUDIT_RESULTS 40 | 41 | default :: 42 | 43 | { 44 | 'active': 'active', 45 | 'no_subscription': 'no_subscription', 46 | 'past_due': 'past_due', 47 | 'suspended': 'suspended', 48 | 'trialing': 'trialing', 49 | 'unpaid': 'unpaid', 50 | 'cancelled': 'cancelled' 51 | } 52 | 53 | Dictionary in which the keys are possible responses from Stripe when 54 | checking the status of a subscription. Values are returned when the key 55 | matches the subscription status returned from Stripe. 56 | 57 | 58 | ------------------------------------------------------------------------------- 59 | 60 | 61 | .. py:data:: ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS 62 | 63 | default: 64 | ``True`` 65 | 66 | 67 | Defaults to ``True`` but is only applicable if :py:data:`ZEBRA_ENABLE_APP` 68 | is ``True``. 69 | 70 | Boolean to control whether accessing ``stripe_customer`` on 71 | :py:data:`ZEBRA_CUSTOMER_MODEL` automatically creates a stripe customer 72 | if one doesn't exist for the instance. 73 | 74 | 75 | ------------------------------------------------------------------------------- 76 | 77 | 78 | .. py:data:: ZEBRA_CARD_YEARS 79 | 80 | default: 81 | ``range(_today.year, _today.year+12)`` 82 | 83 | List of years used to populate :py:data:`ZEBRA_CARD_YEARS_CHOICES`. 84 | 85 | 86 | ------------------------------------------------------------------------------- 87 | 88 | 89 | .. py:data:: ZEBRA_CARD_YEARS_CHOICES 90 | 91 | default: 92 | ``[(i,i) for i in ZEBRA_CARD_YEARS]`` 93 | 94 | List of pairs (Django choices format) to be used in the credit card year 95 | field in :py:class:`StripePaymentForm`. 96 | 97 | 98 | ------------------------------------------------------------------------------- 99 | 100 | 101 | .. py:data:: ZEBRA_CUSTOMER_MODEL 102 | 103 | default: 104 | ``None`` 105 | 106 | If :py:data:`ZEBRA_ENABLE_APP` is ``True`` then the default value is 107 | ``zebra.Customer`` 108 | 109 | 110 | ------------------------------------------------------------------------------- 111 | 112 | 113 | .. py:data:: ZEBRA_ENABLE_APP 114 | 115 | default: 116 | ``False`` 117 | 118 | Boolean that enables the default models and admin that comes with zebra. 119 | Not to be confused with ``marty``. 120 | 121 | 122 | ------------------------------------------------------------------------------- 123 | 124 | 125 | .. py:data:: ZEBRA_MAXIMUM_STRIPE_CUSTOMER_LIST_SIZE 126 | 127 | default: 128 | ``100`` 129 | 130 | Number of customers to return from querying Stripe when running the 131 | managment command to delete test users. 132 | 133 | 134 | ------------------------------------------------------------------------------- 135 | 136 | 137 | 138 | .. py:data:: ZEBRA_ACTIVE_STATUSES 139 | 140 | default: 141 | ``('active', 'past_due', 'trialing')`` 142 | 143 | Iterable of strings that should be considered active based on the values 144 | in :py:data:`ZEBRA_AUDIT_RESULTS`. 145 | 146 | ------------------------------------------------------------------------------- 147 | 148 | 149 | .. py:data:: ZEBRA_INACTIVE_STATUSES 150 | 151 | default: 152 | ``('cancelled', 'suspended', 'unpaid', 'no_subscription')`` 153 | 154 | Iterable of strings that should be considered inactive based on the values 155 | in :py:data:`ZEBRA_AUDIT_RESULTS`. 156 | 157 | -------------------------------------------------------------------------------- /docs/zebra/index.rst: -------------------------------------------------------------------------------- 1 | Zebra 2 | ===== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | :glob: 7 | 8 | * 9 | -------------------------------------------------------------------------------- /docs/zebra/mixins.rst: -------------------------------------------------------------------------------- 1 | .. py:module:: zebra.mixins 2 | 3 | zebra.mixins 4 | ============ 5 | 6 | .. automodule:: zebra.mixins 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/zebra/models.rst: -------------------------------------------------------------------------------- 1 | .. py:module:: zebra.models 2 | 3 | zebra.models 4 | ============ 5 | 6 | .. automodule:: zebra.models 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/zebra/signals.rst: -------------------------------------------------------------------------------- 1 | .. py:module:: zebra.signals 2 | 3 | zebra.signals 4 | ============= 5 | 6 | .. automodule:: zebra.signals 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/zebra/utils.rst: -------------------------------------------------------------------------------- 1 | .. py:module:: zebra.utils 2 | 3 | zebra.utils 4 | =========== 5 | 6 | .. automodule:: zebra.utils 7 | :members: 8 | -------------------------------------------------------------------------------- /docs/zebra/views.rst: -------------------------------------------------------------------------------- 1 | .. py:module:: zebra.views 2 | 3 | zebra.views 4 | =========== 5 | 6 | .. automodule:: zebra.views 7 | :members: 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | stripe 2 | django 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | import os 3 | from setuptools import setup, find_packages 4 | 5 | ROOT_DIR = os.path.dirname(__file__) 6 | SOURCE_DIR = os.path.join(ROOT_DIR) 7 | 8 | setup( 9 | name = "django-zebra", 10 | description = "Library for Django and Stripe", 11 | author = "Steven Skoczen", 12 | author_email = "steven@agoodcloud.com", 13 | url = "https://github.com/GoodCloud/django-zebra", 14 | version = "0.4.5", 15 | packages = find_packages(), 16 | zip_safe = False, 17 | include_package_data=True, 18 | classifiers = [ 19 | "Programming Language :: Python", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | "Development Status :: 4 - Beta", 23 | "Environment :: Web Environment", 24 | "Framework :: Django", 25 | "Intended Audience :: Developers", 26 | "Topic :: Internet :: WWW/HTTP", 27 | "Topic :: Software Development :: Libraries :: Python Modules", 28 | ], 29 | ) -------------------------------------------------------------------------------- /zebra/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodCloud/django-zebra/fdb2f2eb9eddb83eebb339359e629c88f4f6ba17/zebra/__init__.py -------------------------------------------------------------------------------- /zebra/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from zebra.conf import options 4 | 5 | if options.ZEBRA_ENABLE_APP: 6 | from zebra.models import Customer, Plan, Subscription 7 | 8 | admin.site.register(Customer) 9 | admin.site.register(Plan) 10 | admin.site.register(Subscription) 11 | -------------------------------------------------------------------------------- /zebra/conf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodCloud/django-zebra/fdb2f2eb9eddb83eebb339359e629c88f4f6ba17/zebra/conf/__init__.py -------------------------------------------------------------------------------- /zebra/conf/options.py: -------------------------------------------------------------------------------- 1 | """ 2 | Default settings for zebra 3 | """ 4 | import datetime 5 | import os 6 | 7 | from django.conf import settings as _settings 8 | 9 | 10 | if hasattr(_settings, 'STRIPE_PUBLISHABLE'): 11 | STRIPE_PUBLISHABLE = getattr(_settings, 'STRIPE_PUBLISHABLE') 12 | else: 13 | try: 14 | STRIPE_PUBLISHABLE = os.environ['STRIPE_PUBLISHABLE'] 15 | except KeyError: 16 | STRIPE_PUBLISHABLE = '' 17 | 18 | if hasattr(_settings, 'STRIPE_SECRET'): 19 | STRIPE_SECRET = getattr(_settings, 'STRIPE_SECRET') 20 | else: 21 | try: 22 | STRIPE_SECRET = os.environ['STRIPE_SECRET'] 23 | except KeyError: 24 | STRIPE_SECRET = '' 25 | 26 | ZEBRA_ENABLE_APP = getattr(_settings, 'ZEBRA_ENABLE_APP', False) 27 | ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS = getattr(_settings, 28 | 'ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS', True) 29 | 30 | _today = datetime.date.today() 31 | ZEBRA_CARD_YEARS = getattr(_settings, 'ZEBRA_CARD_YEARS', 32 | range(_today.year, _today.year+12)) 33 | ZEBRA_CARD_YEARS_CHOICES = getattr(_settings, 'ZEBRA_CARD_YEARS_CHOICES', 34 | [(i,i) for i in ZEBRA_CARD_YEARS]) 35 | 36 | ZEBRA_MAXIMUM_STRIPE_CUSTOMER_LIST_SIZE = getattr(_settings, 37 | 'ZEBRA_MAXIMUM_STRIPE_CUSTOMER_LIST_SIZE', 100) 38 | 39 | _audit_defaults = { 40 | 'active': 'active', 41 | 'no_subscription': 'no_subscription', 42 | 'past_due': 'past_due', 43 | 'suspended': 'suspended', 44 | 'trialing': 'trialing', 45 | 'unpaid': 'unpaid', 46 | 'cancelled': 'cancelled' 47 | } 48 | 49 | ZEBRA_AUDIT_RESULTS = getattr(_settings, 'ZEBRA_AUDIT_RESULTS', _audit_defaults) 50 | 51 | ZEBRA_ACTIVE_STATUSES = getattr(_settings, 'ZEBRA_ACTIVE_STATUSES', 52 | ('active', 'past_due', 'trialing')) 53 | ZEBRA_INACTIVE_STATUSES = getattr(_settings, 'ZEBRA_INACTIVE_STATUSES', 54 | ('cancelled', 'suspended', 'unpaid', 'no_subscription')) 55 | 56 | if ZEBRA_ENABLE_APP: 57 | ZEBRA_CUSTOMER_MODEL = getattr(_settings, 'ZEBRA_CUSTOMER_MODEL', 'zebra.Customer') 58 | else: 59 | ZEBRA_CUSTOMER_MODEL = getattr(_settings, 'ZEBRA_CUSTOMER_MODEL', None) 60 | -------------------------------------------------------------------------------- /zebra/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.core.exceptions import NON_FIELD_ERRORS 3 | from django.utils.dates import MONTHS 4 | 5 | from zebra.conf import options 6 | from zebra.widgets import NoNameSelect, NoNameTextInput 7 | 8 | 9 | class MonospaceForm(forms.Form): 10 | def addError(self, message): 11 | self._errors[NON_FIELD_ERRORS] = self.error_class([message]) 12 | 13 | 14 | class CardForm(MonospaceForm): 15 | last_4_digits = forms.CharField(required=True, min_length=4, max_length=4, 16 | widget=forms.HiddenInput()) 17 | stripe_token = forms.CharField(required=True, widget=forms.HiddenInput()) 18 | 19 | 20 | class StripePaymentForm(CardForm): 21 | def __init__(self, *args, **kwargs): 22 | super(StripePaymentForm, self).__init__(*args, **kwargs) 23 | self.fields['card_cvv'].label = "Card CVC" 24 | self.fields['card_cvv'].help_text = "Card Verification Code; see rear of card." 25 | months = [ (m[0], u'%02d - %s' % (m[0], unicode(m[1]))) 26 | for m in sorted(MONTHS.iteritems()) ] 27 | self.fields['card_expiry_month'].choices = months 28 | 29 | card_number = forms.CharField(required=False, max_length=20, 30 | widget=NoNameTextInput()) 31 | card_cvv = forms.CharField(required=False, max_length=4, 32 | widget=NoNameTextInput()) 33 | card_expiry_month = forms.ChoiceField(required=False, widget=NoNameSelect(), 34 | choices=MONTHS.iteritems()) 35 | card_expiry_year = forms.ChoiceField(required=False, widget=NoNameSelect(), 36 | choices=options.ZEBRA_CARD_YEARS_CHOICES) 37 | -------------------------------------------------------------------------------- /zebra/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodCloud/django-zebra/fdb2f2eb9eddb83eebb339359e629c88f4f6ba17/zebra/management/__init__.py -------------------------------------------------------------------------------- /zebra/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodCloud/django-zebra/fdb2f2eb9eddb83eebb339359e629c88f4f6ba17/zebra/management/commands/__init__.py -------------------------------------------------------------------------------- /zebra/management/commands/clear_stripe_test_customers.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | import stripe 4 | 5 | from zebra.conf import options as zoptions 6 | 7 | 8 | CLEAR_CHUNK_SIZE = zoptions.ZEBRA_MAXIMUM_STRIPE_CUSTOMER_LIST_SIZE 9 | 10 | class Command(BaseCommand): 11 | help = "Clear all test mode customers from your stripe account." 12 | __test__ = False 13 | 14 | def handle(self, *args, **options): 15 | verbosity = int(options.get('verbosity', 1)) 16 | stripe.api_key = zoptions.STRIPE_SECRET 17 | customer_chunk = [0] 18 | 19 | if verbosity > 0: 20 | print "Clearing stripe test customers:" 21 | 22 | num_checked = 0 23 | while len(customer_chunk) is not 0: 24 | customer_chunk = stripe.Customer.all(count=CLEAR_CHUNK_SIZE, offset=num_checked).data 25 | 26 | if verbosity > 1: 27 | print "Processing records %s-%s" % (num_checked, num_checked+len(customer_chunk)) 28 | 29 | for c in customer_chunk: 30 | if verbosity > 2: 31 | print "Deleting %s..." % (c.description), 32 | 33 | if not c.livemode: 34 | c.delete() 35 | 36 | if verbosity > 2: 37 | print "done" 38 | 39 | num_checked = num_checked + len(customer_chunk) 40 | 41 | if verbosity > 0: 42 | print "Finished clearing stripe test customers." 43 | -------------------------------------------------------------------------------- /zebra/mixins.py: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | from zebra.conf import options 4 | 5 | 6 | def _get_attr_value(instance, attr, default=None): 7 | """ 8 | Simple helper to get the value of an instance's attribute if it exists. 9 | 10 | If the instance attribute is callable it will be called and the result will 11 | be returned. 12 | 13 | Optionally accepts a default value to return if the attribute is missing. 14 | Defaults to `None` 15 | 16 | >>> class Foo(object): 17 | ... bar = 'baz' 18 | ... def hi(self): 19 | ... return 'hi' 20 | >>> f = Foo() 21 | >>> _get_attr_value(f, 'bar') 22 | 'baz' 23 | >>> _get_attr_value(f, 'xyz') 24 | 25 | >>> _get_attr_value(f, 'xyz', False) 26 | False 27 | >>> _get_attr_value(f, 'hi') 28 | 'hi' 29 | """ 30 | value = default 31 | if hasattr(instance, attr): 32 | value = getattr(instance, attr) 33 | if callable(value): 34 | value = value() 35 | return value 36 | 37 | 38 | class StripeMixin(object): 39 | """ 40 | Provides a property `stripe` that returns an instance of the Stripe module. 41 | 42 | It optionally supports the ability to set `stripe.api_key` if your class 43 | has a `stripe_api_key` attribute (method or property), or if 44 | settings has a `STRIPE_SECRET` attribute (method or property). 45 | """ 46 | def _get_stripe(self): 47 | if hasattr(self, 'stripe_api_key'): 48 | stripe.api_key = _get_attr_value(self, 'stripe_api_key') 49 | elif hasattr(options, 'STRIPE_SECRET'): 50 | stripe.api_key = _get_attr_value(options, 'STRIPE_SECRET') 51 | return stripe 52 | stripe = property(_get_stripe) 53 | 54 | 55 | class StripeCustomerMixin(object): 56 | """ 57 | Provides a property property `stripe_customer` that returns a stripe 58 | customer instance. 59 | 60 | Your class must provide: 61 | 62 | - an attribute `stripe_customer_id` (method or property) 63 | to provide the customer id for the returned instance, and 64 | - an attribute `stripe` (method or property) that returns an instance 65 | of the Stripe module. StripeMixin is an easy way to get this. 66 | 67 | """ 68 | def _get_stripe_customer(self): 69 | c = None 70 | if _get_attr_value(self, 'stripe_customer_id'): 71 | c = self.stripe.Customer.retrieve(_get_attr_value(self, 72 | 'stripe_customer_id')) 73 | if not c and options.ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS: 74 | c = self.stripe.Customer.create() 75 | self.stripe_customer_id = c.id 76 | self.save() 77 | 78 | return c 79 | stripe_customer = property(_get_stripe_customer) 80 | 81 | 82 | class StripeSubscriptionMixin(object): 83 | """ 84 | Provides a property `stripe_subscription` that returns a stripe 85 | subscription instance. 86 | 87 | Your class must have an attribute `stripe_customer` (method or property) 88 | to provide a customer instance with which to lookup the subscription. 89 | """ 90 | def _get_stripe_subscription(self): 91 | subscription = None 92 | customer = _get_attr_value(self, 'stripe_customer') 93 | if hasattr(customer, 'subscription'): 94 | subscription = customer.subscription 95 | return subscription 96 | stripe_subscription = property(_get_stripe_subscription) 97 | 98 | 99 | class StripePlanMixin(object): 100 | """ 101 | Provides a property `stripe_plan` that returns a stripe plan instance. 102 | 103 | Your class must have an attribute `stripe_plan_id` (method or property) 104 | to provide the plan id for the returned instance. 105 | """ 106 | def _get_stripe_plan(self): 107 | return stripe.Plan.retrieve(_get_attr_value(self, 'stripe_plan_id')) 108 | stripe_plan = property(_get_stripe_plan) 109 | 110 | 111 | class StripeInvoiceMixin(object): 112 | """ 113 | Provides a property `stripe_invoice` that returns a stripe invoice instance. 114 | 115 | Your class must have an attribute `stripe_invoice_id` (method or property) 116 | to provide the invoice id for the returned instance. 117 | """ 118 | def _get_stripe_invoice(self): 119 | return stripe.Invoice.retrieve(_get_attr_value(self, 120 | 'stripe_invoice_id')) 121 | stripe_invoice = property(_get_stripe_invoice) 122 | 123 | 124 | class StripeInvoiceItemMixin(object): 125 | """ 126 | Provides a property `stripe_invoice_item` that returns a stripe 127 | invoice item instance. 128 | 129 | Your class must have an attribute `stripe_invoice_item_id` (method or 130 | property) to provide the invoice id for the returned instance. 131 | """ 132 | def _get_stripe_invoice_item(self): 133 | return stripe.InvoiceItem.retrieve(_get_attr_value(self, 134 | 'stripe_invoice_item_id')) 135 | stripe_invoice_item = property(_get_stripe_invoice_item) 136 | 137 | 138 | class StripeChargeMixin(object): 139 | """ 140 | Provides a property `stripe_charge` that returns a stripe charge instance. 141 | 142 | Your class must have an attribute `stripe_charge_id` (method or 143 | property) to provide the invoice id for the returned instance. 144 | """ 145 | def _get_stripe_charge(self): 146 | return stripe.Charge.retrieve(_get_attr_value(self, 'stripe_charge_id')) 147 | stripe_charge = property(_get_stripe_charge) 148 | 149 | 150 | class ZebraMixin(StripeMixin, StripeCustomerMixin, StripeSubscriptionMixin, 151 | StripePlanMixin, StripeInvoiceMixin, StripeInvoiceItemMixin, 152 | StripeChargeMixin): 153 | """ 154 | Provides all available Stripe mixins in one class. 155 | 156 | `self.stripe` 157 | `self.stripe_customer` 158 | `self.stripe_subscription` 159 | `self.stripe_plan` 160 | """ 161 | pass 162 | -------------------------------------------------------------------------------- /zebra/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from zebra import mixins 4 | from zebra.conf import options 5 | 6 | 7 | class StripeCustomer(models.Model, mixins.StripeMixin, mixins.StripeCustomerMixin): 8 | stripe_customer_id = models.CharField(max_length=50, blank=True, null=True) 9 | 10 | class Meta: 11 | abstract = True 12 | 13 | def __unicode__(self): 14 | return u"%s" % self.stripe_customer_id 15 | 16 | 17 | class StripePlan(models.Model, mixins.StripeMixin, mixins.StripePlanMixin): 18 | stripe_plan_id = models.CharField(max_length=50, blank=True, null=True) 19 | 20 | class Meta: 21 | abstract = True 22 | 23 | def __unicode__(self): 24 | return u"%s" % self.stripe_plan_id 25 | 26 | 27 | class StripeSubscription(models.Model, mixins.StripeMixin, mixins.StripeSubscriptionMixin): 28 | """ 29 | You need to provide a stripe_customer attribute. See zebra.models for an 30 | example implimentation. 31 | """ 32 | class Meta: 33 | abstract = True 34 | 35 | 36 | # Non-abstract classes must be enabled in your project's settings.py 37 | if options.ZEBRA_ENABLE_APP: 38 | class DatesModelBase(models.Model): 39 | date_created = models.DateTimeField(auto_now_add=True) 40 | date_modified = models.DateTimeField(auto_now=True) 41 | 42 | class Meta: 43 | abstract = True 44 | 45 | class Customer(DatesModelBase, StripeCustomer): 46 | pass 47 | 48 | class Plan(DatesModelBase, StripePlan): 49 | pass 50 | 51 | class Subscription(DatesModelBase, StripeSubscription): 52 | customer = models.ForeignKey(Customer) 53 | plan = models.ForeignKey(Plan) 54 | 55 | def __unicode__(self): 56 | return u"%s: %s" % (self.customer, self.plan) 57 | 58 | @property 59 | def stripe_customer(self): 60 | return self.customer.stripe_customer -------------------------------------------------------------------------------- /zebra/signals.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provides the following signals: 3 | 4 | V1 5 | 6 | - zebra_webhook_recurring_payment_failed 7 | - zebra_webhook_invoice_ready 8 | - zebra_webhook_recurring_payment_succeeded 9 | - zebra_webhook_subscription_trial_ending 10 | - zebra_webhook_subscription_final_payment_attempt_failed 11 | - zebra_webhook_subscription_ping_sent 12 | 13 | v2 14 | 15 | - zebra_webhook_charge_succeeded 16 | - zebra_webhook_charge_failed 17 | - zebra_webhook_charge_refunded 18 | - zebra_webhook_charge_disputed 19 | - zebra_webhook_customer_created 20 | - zebra_webhook_customer_updated 21 | - zebra_webhook_customer_deleted 22 | - zebra_webhook_customer_subscription_created 23 | - zebra_webhook_customer_subscription_updated 24 | - zebra_webhook_customer_subscription_deleted 25 | - zebra_webhook_customer_subscription_trial_will_end 26 | - zebra_webhook_customer_discount_created 27 | - zebra_webhook_customer_discount_updated 28 | - zebra_webhook_customer_discount_deleted 29 | - zebra_webhook_invoice_created 30 | - zebra_webhook_invoice_updated 31 | - zebra_webhook_invoice_payment_succeeded 32 | - zebra_webhook_invoice_payment_failed 33 | - zebra_webhook_invoiceitem_created 34 | - zebra_webhook_invoiceitem_updated 35 | - zebra_webhook_invoiceitem_deleted 36 | - zebra_webhook_plan_created 37 | - zebra_webhook_plan_updated 38 | - zebra_webhook_plan_deleted 39 | - zebra_webhook_coupon_created 40 | - zebra_webhook_coupon_updated 41 | - zebra_webhook_coupon_deleted 42 | - zebra_webhook_transfer_created 43 | - zebra_webhook_transfer_failed 44 | - zebra_webhook_ping 45 | """ 46 | import django.dispatch 47 | 48 | WEBHOOK_ARGS = ["customer", "full_json"] 49 | 50 | zebra_webhook_recurring_payment_failed = django.dispatch.Signal(providing_args=WEBHOOK_ARGS) 51 | zebra_webhook_invoice_ready = django.dispatch.Signal(providing_args=WEBHOOK_ARGS) 52 | zebra_webhook_recurring_payment_succeeded = django.dispatch.Signal(providing_args=WEBHOOK_ARGS) 53 | zebra_webhook_subscription_trial_ending = django.dispatch.Signal(providing_args=WEBHOOK_ARGS) 54 | zebra_webhook_subscription_final_payment_attempt_failed = django.dispatch.Signal(providing_args=WEBHOOK_ARGS) 55 | zebra_webhook_subscription_ping_sent = django.dispatch.Signal(providing_args=[]) 56 | 57 | # v2 webhooks 58 | WEBHOOK2_ARGS = ["full_json"] 59 | 60 | zebra_webhook_charge_succeeded = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 61 | zebra_webhook_charge_failed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 62 | zebra_webhook_charge_refunded = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 63 | zebra_webhook_charge_disputed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 64 | zebra_webhook_customer_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 65 | zebra_webhook_customer_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 66 | zebra_webhook_customer_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 67 | zebra_webhook_customer_subscription_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 68 | zebra_webhook_customer_subscription_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 69 | zebra_webhook_customer_subscription_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 70 | zebra_webhook_customer_subscription_trial_will_end = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 71 | zebra_webhook_customer_discount_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 72 | zebra_webhook_customer_discount_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 73 | zebra_webhook_customer_discount_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 74 | zebra_webhook_invoice_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 75 | zebra_webhook_invoice_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 76 | zebra_webhook_invoice_payment_succeeded = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 77 | zebra_webhook_invoice_payment_failed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 78 | zebra_webhook_invoiceitem_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 79 | zebra_webhook_invoiceitem_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 80 | zebra_webhook_invoiceitem_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 81 | zebra_webhook_plan_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 82 | zebra_webhook_plan_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 83 | zebra_webhook_plan_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 84 | zebra_webhook_coupon_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 85 | zebra_webhook_coupon_updated = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 86 | zebra_webhook_coupon_deleted = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 87 | zebra_webhook_transfer_created = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 88 | zebra_webhook_transfer_failed = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 89 | zebra_webhook_ping = django.dispatch.Signal(providing_args=WEBHOOK2_ARGS) 90 | 91 | WEBHOOK_MAP = { 92 | 'charge_succeeded': zebra_webhook_charge_succeeded, 93 | 'charge_failed': zebra_webhook_charge_failed, 94 | 'charge_refunded': zebra_webhook_charge_refunded, 95 | 'charge_disputed': zebra_webhook_charge_disputed, 96 | 'customer_created': zebra_webhook_customer_created, 97 | 'customer_updated': zebra_webhook_customer_updated, 98 | 'customer_deleted': zebra_webhook_customer_deleted, 99 | 'customer_subscription_created': zebra_webhook_customer_subscription_created, 100 | 'customer_subscription_updated': zebra_webhook_customer_subscription_updated, 101 | 'customer_subscription_deleted': zebra_webhook_customer_subscription_deleted, 102 | 'customer_subscription_trial_will_end': zebra_webhook_customer_subscription_trial_will_end, 103 | 'customer_discount_created': zebra_webhook_customer_discount_created, 104 | 'customer_discount_updated': zebra_webhook_customer_discount_updated, 105 | 'customer_discount_deleted': zebra_webhook_customer_discount_deleted, 106 | 'invoice_created': zebra_webhook_invoice_created, 107 | 'invoice_updated': zebra_webhook_invoice_updated, 108 | 'invoice_payment_succeeded': zebra_webhook_invoice_payment_succeeded, 109 | 'invoice_payment_failed': zebra_webhook_invoice_payment_failed, 110 | 'invoiceitem_created': zebra_webhook_invoiceitem_created, 111 | 'invoiceitem_updated': zebra_webhook_invoiceitem_updated, 112 | 'invoiceitem_deleted': zebra_webhook_invoiceitem_deleted, 113 | 'plan_created': zebra_webhook_plan_created, 114 | 'plan_updated': zebra_webhook_plan_updated, 115 | 'plan_deleted': zebra_webhook_plan_deleted, 116 | 'coupon_created': zebra_webhook_coupon_created, 117 | 'coupon_updated': zebra_webhook_coupon_updated, 118 | 'coupon_deleted': zebra_webhook_coupon_deleted, 119 | 'transfer_created': zebra_webhook_transfer_created, 120 | 'transfer_failed': zebra_webhook_transfer_failed, 121 | 'ping': zebra_webhook_ping, 122 | } 123 | -------------------------------------------------------------------------------- /zebra/static/zebra/card-form.css: -------------------------------------------------------------------------------- 1 | #id_card_number { 2 | width: 15ex; 3 | } 4 | #id_card_cvv { 5 | width: 3ex; 6 | } 7 | #id_card_expiry_year { 8 | width: 10ex; 9 | } -------------------------------------------------------------------------------- /zebra/static/zebra/card-form.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $("#id_card_number").parents("form").submit(function() { 3 | if ( $("#id_card_number").is(":visible")) { 4 | var form = this; 5 | var card = { 6 | number: $("#id_card_number").val(), 7 | expMonth: $("#id_card_expiry_month").val(), 8 | expYear: $("#id_card_expiry_year").val(), 9 | cvc: $("#id_card_cvv").val() 10 | }; 11 | 12 | Stripe.createToken(card, function(status, response) { 13 | if (status === 200) { 14 | // console.log(status, response); 15 | $("#credit-card-errors").hide(); 16 | $("#id_last_4_digits").val(response.card.last4); 17 | $("#id_stripe_token").val(response.id); 18 | form.submit(); 19 | $("button[type=submit]").attr("disabled","disabled").html("Submitting..") 20 | } else { 21 | $(".payment-errors").text(response.error.message); 22 | $("#user_submit").attr("disabled", false); 23 | } 24 | }); 25 | 26 | return false; 27 | 28 | } 29 | 30 | return true 31 | 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /zebra/templates/zebra/_basic_card_form.html: -------------------------------------------------------------------------------- 1 |
{% csrf_token %} 2 | 3 | 4 | {{ zebra_form.as_p }} 5 | 6 |
7 | -------------------------------------------------------------------------------- /zebra/templates/zebra/_stripe_js_and_set_stripe_key.html: -------------------------------------------------------------------------------- 1 | {{zebra_form.media}} 2 | -------------------------------------------------------------------------------- /zebra/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodCloud/django-zebra/fdb2f2eb9eddb83eebb339359e629c88f4f6ba17/zebra/templatetags/__init__.py -------------------------------------------------------------------------------- /zebra/templatetags/zebra_tags.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django import template 3 | from django.template.loader import render_to_string 4 | from django.utils.encoding import force_unicode 5 | from django.utils.safestring import mark_safe 6 | from django.utils.translation import ugettext as _ 7 | 8 | from zebra.conf import options 9 | 10 | 11 | register = template.Library() 12 | 13 | def _set_up_zebra_form(context): 14 | if not "zebra_form" in context: 15 | if "form" in context: 16 | context["zebra_form"] = context["form"] 17 | else: 18 | raise Exception, "Missing stripe form." 19 | context["STRIPE_PUBLISHABLE"] = options.STRIPE_PUBLISHABLE 20 | return context 21 | 22 | 23 | @register.inclusion_tag('zebra/_stripe_js_and_set_stripe_key.html', takes_context=True) 24 | def zebra_head_and_stripe_key(context): 25 | return _set_up_zebra_form(context) 26 | 27 | 28 | @register.inclusion_tag('zebra/_basic_card_form.html', takes_context=True) 29 | def zebra_card_form(context): 30 | return _set_up_zebra_form(context) 31 | -------------------------------------------------------------------------------- /zebra/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, url 2 | 3 | from zebra import views 4 | 5 | urlpatterns = patterns('', 6 | url(r'webhooks/$', views.webhooks, name='webhooks'), 7 | url(r'webhooks/v2/$', views.webhooks_v2, name='webhooks_v2'), 8 | ) 9 | -------------------------------------------------------------------------------- /zebra/utils.py: -------------------------------------------------------------------------------- 1 | from zebra.conf import options 2 | 3 | AUDIT_RESULTS = options.ZEBRA_AUDIT_RESULTS 4 | 5 | 6 | def audit_customer_subscription(customer, unknown=True): 7 | """ 8 | Audits the provided customer's subscription against stripe and returns a pair 9 | that contains a boolean and a result type. 10 | 11 | Default result types can be found in zebra.conf.defaults and can be 12 | overridden in your project's settings. 13 | """ 14 | if (hasattr(customer, 'suspended') and customer.suspended): 15 | result = AUDIT_RESULTS['suspended'] 16 | else: 17 | if hasattr(customer, 'subscription'): 18 | try: 19 | result = AUDIT_RESULTS[customer.subscription.status] 20 | except KeyError, err: 21 | # TODO should this be a more specific exception class? 22 | raise Exception("Unable to locate a result set for \ 23 | subscription status %s in ZEBRA_AUDIT_RESULTS") % str(err) 24 | else: 25 | result = AUDIT_RESULTS['no_subscription'] 26 | return result -------------------------------------------------------------------------------- /zebra/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | try: 3 | import json as simplejson 4 | except: 5 | from django.utils import simplejson 6 | 7 | from django.db.models import get_model 8 | import stripe 9 | from zebra.conf import options 10 | from zebra.signals import * 11 | from django.views.decorators.csrf import csrf_exempt 12 | 13 | import logging 14 | log = logging.getLogger("zebra.%s" % __name__) 15 | 16 | stripe.api_key = options.STRIPE_SECRET 17 | 18 | def _try_to_get_customer_from_customer_id(stripe_customer_id): 19 | if options.ZEBRA_CUSTOMER_MODEL: 20 | m = get_model(*options.ZEBRA_CUSTOMER_MODEL.split('.')) 21 | try: 22 | return m.objects.get(stripe_customer_id=stripe_customer_id) 23 | except: 24 | pass 25 | return None 26 | 27 | @csrf_exempt 28 | def webhooks(request): 29 | """ 30 | Handles all known webhooks from stripe, and calls signals. 31 | Plug in as you need. 32 | """ 33 | 34 | if request.method != "POST": 35 | return HttpResponse("Invalid Request.", status=400) 36 | 37 | json = simplejson.loads(request.POST["json"]) 38 | 39 | if json["event"] == "recurring_payment_failed": 40 | zebra_webhook_recurring_payment_failed.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json) 41 | 42 | elif json["event"] == "invoice_ready": 43 | zebra_webhook_invoice_ready.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json) 44 | 45 | elif json["event"] == "recurring_payment_succeeded": 46 | zebra_webhook_recurring_payment_succeeded.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json) 47 | 48 | elif json["event"] == "subscription_trial_ending": 49 | zebra_webhook_subscription_trial_ending.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json) 50 | 51 | elif json["event"] == "subscription_final_payment_attempt_failed": 52 | zebra_webhook_subscription_final_payment_attempt_failed.send(sender=None, customer=_try_to_get_customer_from_customer_id(json["customer"]), full_json=json) 53 | 54 | elif json["event"] == "ping": 55 | zebra_webhook_subscription_ping_sent.send(sender=None) 56 | 57 | else: 58 | return HttpResponse(status=400) 59 | 60 | return HttpResponse(status=200) 61 | 62 | @csrf_exempt 63 | def webhooks_v2(request): 64 | """ 65 | Handles all known webhooks from stripe, and calls signals. 66 | Plug in as you need. 67 | """ 68 | if request.method != "POST": 69 | return HttpResponse("Invalid Request.", status=400) 70 | 71 | try: 72 | event_json = simplejson.loads(request.body) 73 | except AttributeError: 74 | # Backwords compatibility 75 | # Prior to Django 1.4, request.body was named request.raw_post_data 76 | event_json = simplejson.loads(request.raw_post_data) 77 | event_key = event_json['type'].replace('.', '_') 78 | 79 | if event_key in WEBHOOK_MAP: 80 | WEBHOOK_MAP[event_key].send(sender=None, full_json=event_json) 81 | 82 | return HttpResponse(status=200) 83 | -------------------------------------------------------------------------------- /zebra/widgets.py: -------------------------------------------------------------------------------- 1 | from django.forms.widgets import Select, TextInput 2 | from django.utils.safestring import mark_safe 3 | 4 | 5 | class NoNameWidget(object): 6 | 7 | def _update_to_noname_class_name(self, name, kwargs_dict): 8 | if "attrs" in kwargs_dict: 9 | if "class" in kwargs_dict["attrs"]: 10 | kwargs_dict["attrs"]["class"] += " %s" % (name.replace("_", "-"), ) 11 | else: 12 | kwargs_dict["attrs"].update({'class': name.replace("_", "-")}) 13 | else: 14 | kwargs_dict["attrs"] = {'class': name.replace("_", "-")} 15 | 16 | return kwargs_dict 17 | 18 | def _strip_name_attr(self, widget_string, name): 19 | return widget_string.replace("name=\"%s\"" % (name,), "") 20 | 21 | class Media: 22 | css = { 23 | 'all': ('zebra/card-form.css',) 24 | } 25 | js = ('zebra/card-form.js', 'https://js.stripe.com/v1/') 26 | 27 | 28 | 29 | class NoNameTextInput(TextInput, NoNameWidget): 30 | 31 | def render(self, name, *args, **kwargs): 32 | kwargs = self._update_to_noname_class_name(name, kwargs) 33 | return mark_safe(self._strip_name_attr(super(NoNameTextInput, self).render(name, *args, **kwargs), name)) 34 | 35 | 36 | class NoNameSelect(Select, NoNameWidget): 37 | 38 | def render(self, name, *args, **kwargs): 39 | kwargs = self._update_to_noname_class_name(name, kwargs) 40 | return mark_safe(self._strip_name_attr(super(NoNameSelect, self).render(name, *args, **kwargs), name)) 41 | -------------------------------------------------------------------------------- /zebra_sample_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodCloud/django-zebra/fdb2f2eb9eddb83eebb339359e629c88f4f6ba17/zebra_sample_project/__init__.py -------------------------------------------------------------------------------- /zebra_sample_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | 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" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /zebra_sample_project/marty/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoodCloud/django-zebra/fdb2f2eb9eddb83eebb339359e629c88f4f6ba17/zebra_sample_project/marty/__init__.py -------------------------------------------------------------------------------- /zebra_sample_project/marty/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /zebra_sample_project/marty/templates/marty/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% block head %} 4 | {% endblock %} 5 | 6 | 7 | {% block content %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /zebra_sample_project/marty/templates/marty/basic_update.html: -------------------------------------------------------------------------------- 1 | {% extends "marty/base.html" %} 2 | {% load zebra_tags %} 3 | 4 | {% block head %}{{block.super}} 5 | 6 | {% zebra_head_and_stripe_key %} 7 | {% endblock %} 8 | 9 | {% block content %} 10 | {% zebra_card_form %} 11 | {% endblock %} -------------------------------------------------------------------------------- /zebra_sample_project/marty/tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from django.conf import settings 3 | from django.test.client import Client 4 | from zebra.signals import * 5 | from django.utils import simplejson 6 | 7 | 8 | from django.core.urlresolvers import reverse 9 | 10 | class TestWebhooks(unittest.TestCase): 11 | 12 | def setUp(self): 13 | self.signal_kwargs = None 14 | 15 | def _signal_reciever(self, **kwargs): 16 | self.signal_kwargs = kwargs 17 | 18 | def _customized_signal_reciever(self, **kwargs): 19 | self.customer = kwargs["customer"] 20 | self.full_json = kwargs["full_json"] 21 | 22 | def test_recurring_payment_failed_signal_fired(self): 23 | zebra_webhook_recurring_payment_failed.connect(self._signal_reciever) 24 | 25 | self.assertEqual(self.signal_kwargs, None) 26 | 27 | # Pulled directly from the stripe docs 28 | test_post_data = {'json': simplejson.dumps({ 29 | "customer":1083, 30 | "livemode": True, 31 | "event": "recurring_payment_failed", 32 | "attempt": 2, 33 | "invoice": { 34 | "attempted": True, 35 | "charge": "ch_sUmNHkMiag", 36 | "closed": False, 37 | "customer": "1083", 38 | "date": 1305525584, 39 | "id": "in_jN6A1g8N76", 40 | "object": "invoice", 41 | "paid": True, 42 | "period_end": 1305525584, 43 | "period_start": 1305525584, 44 | "subtotal": 2000, 45 | "total": 2000, 46 | "lines": { 47 | "subscriptions": [ 48 | { 49 | "period": { 50 | "start": 1305525584, 51 | "end": 1308203984 52 | }, 53 | "plan": { 54 | "object": "plan", 55 | "name": "Premium plan", 56 | "id": "premium", 57 | "interval": "month", 58 | "amount": 2000 59 | }, 60 | "amount": 2000 61 | } 62 | ] 63 | } 64 | }, 65 | "payment": { 66 | "time": 1297887533, 67 | "card": { 68 | "type": "Visa", 69 | "last4": "4242" 70 | }, 71 | "success": False 72 | } 73 | }) } 74 | 75 | c = Client() 76 | response = c.post(reverse("zebra:webhooks"), test_post_data) 77 | 78 | self.assertEqual(response.status_code, 200) 79 | self.assertEqual(self.signal_kwargs["full_json"]["customer"], 1083) 80 | 81 | 82 | 83 | def test_invoice_ready_signal_fired(self): 84 | zebra_webhook_invoice_ready.connect(self._signal_reciever) 85 | 86 | self.assertEqual(self.signal_kwargs, None) 87 | 88 | # Pulled directly from the stripe docs 89 | test_post_data = {'json': simplejson.dumps( 90 | { 91 | "customer":1083, 92 | "event":"invoice_ready", 93 | "invoice": { 94 | "total": 1500, 95 | "subtotal": 3000, 96 | "lines": { 97 | "invoiceitems": [ 98 | { 99 | "id": "ii_N17xcRJUtn", 100 | "amount": 1000, 101 | "date": 1303586118, 102 | "currency": "usd", 103 | "description": "One-time setup fee" 104 | } 105 | ], 106 | "subscriptions": [ 107 | { 108 | "amount": 2000, 109 | "period": { 110 | "start": 1304588585, 111 | "end": 1307266985 112 | }, 113 | "plan": { 114 | "amount": 2000, 115 | "interval": "month", 116 | "object": "plan", 117 | "id": "p_Mr2NgWECmJ", 118 | "id": "premium" 119 | } 120 | } 121 | ] 122 | }, 123 | "object": "invoice", 124 | "discount": { 125 | "code": "50OFF", 126 | "percent_off": 50 127 | }, 128 | "date": 1304588585, 129 | "period_start": 1304588585, 130 | "id": "in_jN6A1g8N76", 131 | "period_end": 1304588585 132 | } 133 | } 134 | ) } 135 | 136 | c = Client() 137 | response = c.post(reverse("zebra:webhooks"), test_post_data) 138 | 139 | self.assertEqual(response.status_code, 200) 140 | self.assertEqual(self.signal_kwargs["full_json"]["invoice"]["date"], 1304588585) 141 | 142 | 143 | 144 | 145 | def test_recurring_payment_succeeded_signal_fired(self): 146 | zebra_webhook_recurring_payment_succeeded.connect(self._signal_reciever) 147 | 148 | self.assertEqual(self.signal_kwargs, None) 149 | 150 | # Pulled directly from the stripe docs 151 | test_post_data = {'json': simplejson.dumps( 152 | { 153 | "customer":"1083", 154 | "livemode": True, 155 | "event":"recurring_payment_succeeded", 156 | "invoice": { 157 | "total": 2000, 158 | "subtotal": 2000, 159 | "lines": { 160 | "subscriptions": [ 161 | { 162 | "amount": 2000, 163 | "period": { 164 | "start": 1304588585, 165 | "end": 1307266985 166 | }, 167 | "plan": { 168 | "amount": 2000, 169 | "interval": "month", 170 | "object": "plan", 171 | "id": "premium", 172 | "name": "Premium plan" 173 | } 174 | } 175 | ] 176 | }, 177 | "object": "invoice", 178 | "date": 1304588585, 179 | "period_start": 1304588585, 180 | "id": "in_jN6A1g8N76", 181 | "period_end": 1304588585 182 | }, 183 | "payment": { 184 | "time": 1297887533, 185 | "card": 186 | { 187 | "type": "Visa", 188 | "last4": "4242" 189 | }, 190 | "success": True 191 | } 192 | } 193 | ) } 194 | 195 | c = Client() 196 | response = c.post(reverse("zebra:webhooks"), test_post_data) 197 | 198 | self.assertEqual(response.status_code, 200) 199 | self.assertEqual(self.signal_kwargs["full_json"]["payment"]["time"], 1297887533) 200 | 201 | 202 | 203 | def test_subscription_trial_ending_signal_fired(self): 204 | zebra_webhook_subscription_trial_ending.connect(self._signal_reciever) 205 | 206 | self.assertEqual(self.signal_kwargs, None) 207 | 208 | # Pulled directly from the stripe docs 209 | test_post_data = {'json': simplejson.dumps( 210 | { 211 | "customer":1083, 212 | "event":"subscription_trial_ending", 213 | "subscription": 214 | { 215 | "trial_start": 1304627445, 216 | "trial_end": 1307305845, 217 | "plan": { 218 | "trial_period_days": 31, 219 | "amount": 2999, 220 | "interval": "month", 221 | "id": "silver", 222 | "name": "Silver" 223 | }, 224 | } 225 | } 226 | ) } 227 | 228 | c = Client() 229 | response = c.post(reverse("zebra:webhooks"), test_post_data) 230 | 231 | self.assertEqual(response.status_code, 200) 232 | self.assertEqual(self.signal_kwargs["full_json"]["subscription"]["trial_end"], 1307305845) 233 | 234 | 235 | 236 | def test_subscription_final_payment_attempt_failed_signal_fired(self): 237 | zebra_webhook_subscription_final_payment_attempt_failed.connect(self._signal_reciever) 238 | 239 | self.assertEqual(self.signal_kwargs, None) 240 | 241 | # Pulled directly from the stripe docs 242 | test_post_data = {'json': simplejson.dumps( 243 | { 244 | "customer":1083, 245 | "event":"subscription_final_payment_attempt_failed", 246 | "subscription": { 247 | "status": "canceled", 248 | "start": 1304585542, 249 | "plan": { 250 | "amount": 2000, 251 | "interval": "month", 252 | "object": "plan", 253 | "id": "p_ag2NgWECmJ", 254 | "id": "silver" 255 | }, 256 | "canceled_at": 1304585552, 257 | "ended_at": 1304585552, 258 | "object": "subscription", 259 | "current_period_end": 1307263942, 260 | "id": "sub_kP4M63kFrb", 261 | "current_period_start": 1304585542 262 | } 263 | } 264 | ) } 265 | 266 | c = Client() 267 | response = c.post(reverse("zebra:webhooks"), test_post_data) 268 | 269 | self.assertEqual(response.status_code, 200) 270 | self.assertEqual(self.signal_kwargs["full_json"]["subscription"]["start"], 1304585542) 271 | 272 | 273 | def test_webhooks_return_valid_customer_obj(self): 274 | zebra_webhook_subscription_trial_ending.connect(self._signal_reciever) 275 | 276 | from zebra.models import Customer 277 | cust = Customer.objects.create() 278 | 279 | # since ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS is on (default), this creates a customer 280 | cust.stripe_customer 281 | 282 | self.assertEqual(self.signal_kwargs, None) 283 | 284 | # Pulled directly from the stripe docs 285 | test_post_data = {'json': simplejson.dumps( 286 | { 287 | "customer":cust.stripe_customer_id, 288 | "event":"subscription_trial_ending", 289 | "subscription": 290 | { 291 | "trial_start": 1304627445, 292 | "trial_end": 1307305845, 293 | "plan": { 294 | "trial_period_days": 31, 295 | "amount": 2999, 296 | "interval": "month", 297 | "id": "silver", 298 | "name": "Silver" 299 | }, 300 | } 301 | } 302 | ) } 303 | 304 | c = Client() 305 | response = c.post(reverse("zebra:webhooks"), test_post_data) 306 | 307 | self.assertEqual(response.status_code, 200) 308 | self.assertEqual(self.signal_kwargs["customer"], cust) 309 | 310 | def test_webhooks_return_valid_customer_obj_as_an_arg(self): 311 | zebra_webhook_subscription_trial_ending.connect(self._customized_signal_reciever) 312 | 313 | from zebra.models import Customer 314 | cust = Customer.objects.create() 315 | 316 | # since ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS is on (default), this creates a customer 317 | cust.stripe_customer 318 | 319 | self.assertEqual(self.signal_kwargs, None) 320 | 321 | # Pulled directly from the stripe docs 322 | test_post_data = {'json': simplejson.dumps( 323 | { 324 | "customer":cust.stripe_customer_id, 325 | "event":"subscription_trial_ending", 326 | "subscription": 327 | { 328 | "trial_start": 1304627445, 329 | "trial_end": 1307305845, 330 | "plan": { 331 | "trial_period_days": 31, 332 | "amount": 2999, 333 | "interval": "month", 334 | "id": "silver", 335 | "name": "Silver" 336 | }, 337 | } 338 | } 339 | ) } 340 | 341 | c = Client() 342 | response = c.post(reverse("zebra:webhooks"), test_post_data) 343 | 344 | self.assertEqual(response.status_code, 200) 345 | self.assertEqual(self.customer, cust) 346 | 347 | 348 | 349 | 350 | def test_ping_webhook_signal_fired(self): 351 | zebra_webhook_subscription_ping_sent.connect(self._signal_reciever) 352 | 353 | self.assertEqual(self.signal_kwargs, None) 354 | 355 | # Pulled directly from the stripe docs 356 | test_post_data = {'json': simplejson.dumps( 357 | { 358 | "event":"ping", 359 | } 360 | ) } 361 | 362 | c = Client() 363 | response = c.post(reverse("zebra:webhooks"), test_post_data) 364 | 365 | self.assertEqual(response.status_code, 200) 366 | -------------------------------------------------------------------------------- /zebra_sample_project/marty/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | from marty import views 3 | 4 | urlpatterns = patterns('', 5 | url(r'update$', views.update, name='update'), 6 | ) 7 | -------------------------------------------------------------------------------- /zebra_sample_project/marty/views.py: -------------------------------------------------------------------------------- 1 | 2 | from django.http import HttpResponseRedirect 3 | from django.shortcuts import render_to_response, redirect 4 | from django.template import RequestContext 5 | from django.http import HttpResponse 6 | from django.utils import simplejson 7 | from zebra.conf import options 8 | 9 | import stripe 10 | stripe.api_key = options.STRIPE_SECRET 11 | 12 | from zebra.forms import StripePaymentForm 13 | 14 | 15 | # In a real implementation, do login required, etc. 16 | def update(request): 17 | user = request.user 18 | success_updating = False 19 | 20 | if request.method == 'POST': 21 | zebra_form = StripePaymentForm(request.POST) 22 | if zebra_form.is_valid(): 23 | 24 | customer = stripe.Customer.retrieve(user.stripe_id) 25 | customer.card = zebra_form.cleaned_data['stripe_token'] 26 | customer.save() 27 | 28 | profile = user.get_profile() 29 | profile.last_4_digits = zebra_form.cleaned_data['last_4_digits'] 30 | profile.stripe_customer_id = customer.id 31 | profile.save() 32 | 33 | success_updating = True 34 | 35 | else: 36 | zebra_form = StripePaymentForm() 37 | 38 | return render_to_response('marty/basic_update.html', 39 | { 40 | 'zebra_form': zebra_form, 41 | 'publishable': options.STRIPE_PUBLISHABLE, 42 | 'success_updating': success_updating, 43 | }, 44 | context_instance=RequestContext(request) 45 | ) 46 | -------------------------------------------------------------------------------- /zebra_sample_project/requirements.txt: -------------------------------------------------------------------------------- 1 | pip > 1.0 2 | pycurl 3 | stripe 4 | django 5 | django-extensions 6 | 7 | # we include it from the parent dir. You'll want to install it. 8 | # zebra 9 | -------------------------------------------------------------------------------- /zebra_sample_project/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for zebra_sample_project project. 2 | 3 | # Custom path to include zebra from this repo, intstead of pip installing itself. 4 | import sys 5 | from os.path import abspath, dirname, join 6 | sys.path.insert(0, join(abspath(dirname(__file__)), "../")) 7 | 8 | 9 | DEBUG = True 10 | TEMPLATE_DEBUG = DEBUG 11 | 12 | DATABASES = { 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 15 | 'NAME': 'zebra_sample.db', # Or path to database file if using sqlite3. 16 | } 17 | } 18 | 19 | TIME_ZONE = None 20 | LANGUAGE_CODE = 'en-us' 21 | SITE_ID = 1 22 | USE_I18N = True 23 | USE_L10N = True 24 | MEDIA_ROOT = '' 25 | MEDIA_URL = '' 26 | STATIC_ROOT = '' 27 | STATIC_URL = '/static/' 28 | ADMIN_MEDIA_PREFIX = '/static/admin/' 29 | 30 | STATICFILES_DIRS = ( 31 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 32 | ) 33 | STATICFILES_FINDERS = ( 34 | 'django.contrib.staticfiles.finders.FileSystemFinder', 35 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 36 | ) 37 | 38 | # Make this unique, and don't share it with anybody. 39 | SECRET_KEY = '1fdl=4jdiaa=x*=x%=%k&y*b@pcw6ir-vw-(&2^y766v1+6=6o' 40 | 41 | # List of callables that know how to import templates from various sources. 42 | TEMPLATE_LOADERS = ( 43 | 'django.template.loaders.filesystem.Loader', 44 | 'django.template.loaders.app_directories.Loader', 45 | ) 46 | 47 | MIDDLEWARE_CLASSES = ( 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | ) 54 | 55 | ROOT_URLCONF = 'zebra_sample_project.urls' 56 | 57 | TEMPLATE_DIRS = ( 58 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 59 | # Always use forward slashes, even on Windows. 60 | # Don't forget to use absolute paths, not relative paths. 61 | ) 62 | 63 | INSTALLED_APPS = ( 64 | 'django.contrib.auth', 65 | 'django.contrib.contenttypes', 66 | 'django.contrib.sessions', 67 | 'django.contrib.sites', 68 | 'django.contrib.messages', 69 | 'django.contrib.staticfiles', 70 | # 'django.contrib.admin', 71 | # 'django.contrib.admindocs', 72 | 'django_extensions', 73 | 'zebra', 74 | 'marty', 75 | 76 | 77 | ) 78 | 79 | # A sample logging configuration. The only tangible logging 80 | # performed by this configuration is to send an email to 81 | # the site admins on every HTTP 500 error. 82 | # See http://docs.djangoproject.com/en/dev/topics/logging for 83 | # more details on how to customize your logging configuration. 84 | LOGGING = { 85 | 'version': 1, 86 | 'disable_existing_loggers': False, 87 | 'handlers': { 88 | 'mail_admins': { 89 | 'level': 'ERROR', 90 | 'class': 'django.utils.log.AdminEmailHandler' 91 | } 92 | }, 93 | 'loggers': { 94 | 'django.request': { 95 | 'handlers': ['mail_admins'], 96 | 'level': 'ERROR', 97 | 'propagate': True, 98 | }, 99 | } 100 | } 101 | 102 | 103 | 104 | # Zebra Config 105 | ZEBRA_ENABLE_APP = True 106 | 107 | # Set these, or include them in an untracked locals.py 108 | STRIPE_PUBLISHABLE = None 109 | STRIPE_SECRET = None 110 | 111 | try: 112 | from locals import * 113 | except: 114 | pass 115 | -------------------------------------------------------------------------------- /zebra_sample_project/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | 3 | urlpatterns = patterns('', 4 | url(r'zebra/', include('zebra.urls', namespace="zebra", app_name='zebra') ), 5 | url(r'', include('marty.urls', namespace="marty", app_name='marty') ), 6 | ) 7 | --------------------------------------------------------------------------------