Giter Club home page Giter Club logo

django-zebra's Introduction

Overview

Zebra is a library that makes using Stripe with Django even easier.

It's made of:

  • zebra, the core library, with forms, webhook handlers, abstract models, mixins, signals, and templatetags that cover most stripe implementations.
  • marty, an example app for how to integrate zebra, that also serves as its test suite.

Pull requests are quite welcome!

Usage

Installation

  1. pip install django-zebra

  2. Edit your settings.py:

    INSTALLED_APPS += ("zebra",)
    STRIPE_SECRET = "YOUR-SECRET-API-KEY"
    STRIPE_PUBLISHABLE = "YOUR-PUBLISHABLE-API-KEY"
    # Set any optional settings (below)
    
  3. (optional) ./manage.py syncdb if you have ZEBRA_ENABLE_APP = True

  4. (optional) Add in the webhook urls:

    urlpatterns += patterns('',          
    	url(r'zebra/',   include('zebra.urls',  namespace="zebra",  app_name='zebra') ),
    )
    
  5. Enjoy easy billing.

Optional Settings:

  • ZEBRA_ENABLE_APP Defaults to False. Enables Customer, Plan, and Subscription django models, as a part of zebra.
  • ZEBRA_CUSTOMER_MODEL The app+model string for the model that implements the StripeCustomerMixin. ie "myapp.MyCustomer". If ZEBRA_ENABLE_APP is true, defaults to "zebra.Customer".
  • ZEBRA_AUTO_CREATE_STRIPE_CUSTOMERS Defaults to True. Automatically creates a stripe customer object on stripe_customer access, if one doesn't exist.

Webhooks

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:

Note: The initial Stripe webhook system is being deprecated. See below for a description of Zebra's support for the new system.

Zebra provides:

  • zebra_webhook_recurring_payment_failed
  • zebra_webhook_invoice_ready
  • zebra_webhook_recurring_payment_succeeded
  • zebra_webhook_subscription_trial_ending
  • zebra_webhook_subscription_final_payment_attempt_failed

All of the webhooks provide the same arguments:

  • 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.
  • full_json - the full json response, parsed with simplejson.

So, for example, to update the customer's new billing date after a successful payment, you could:

(assuming you've set ZEBRA_CUSTOMER_MODEL or are using ZEBRA_ENABLE_APP):

from zebra.signals import zebra_webhook_recurring_payment_succeeded

def update_last_invoice_date(sender, **kwargs):
	customer = kwargs.pop("customer", None)
	full_json = kwargs.pop("full_json", None)
	customer.billing_date = full_json.date
	customer.save()

zebra_webhook_recurring_payment_succeeded.connect(update_last_invoice_date)

Webhooks Update

Stripe recently updated their webhook implementation (see https://stripe.com/blog/webhooks). Zebra includes an implementation of the new system.

Zebra provides:

  • zebra_webhook_charge_succeeded
  • zebra_webhook_charge_failed
  • zebra_webhook_charge_refunded
  • zebra_webhook_charge_disputed
  • zebra_webhook_customer_created
  • zebra_webhook_customer_updated
  • zebra_webhook_customer_deleted
  • zebra_webhook_customer_subscription_created
  • zebra_webhook_customer_subscription_updated
  • zebra_webhook_customer_subscription_deleted
  • zebra_webhook_customer_subscription_trial_will_end
  • zebra_webhook_customer_discount_created
  • zebra_webhook_customer_discount_updated
  • zebra_webhook_customer_discount_deleted
  • zebra_webhook_invoice_created
  • zebra_webhook_invoice_updated
  • zebra_webhook_invoice_payment_succeeded
  • zebra_webhook_invoice_payment_failed
  • zebra_webhook_invoiceitem_created
  • zebra_webhook_invoiceitem_updated
  • zebra_webhook_invoiceitem_deleted
  • zebra_webhook_plan_created
  • zebra_webhook_plan_updated
  • zebra_webhook_plan_deleted
  • zebra_webhook_coupon_created
  • zebra_webhook_coupon_updated
  • zebra_webhook_coupon_deleted
  • zebra_webhook_transfer_created
  • zebra_webhook_transfer_failed
  • zebra_webhook_ping

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:

for event_key, webhook_signal in WEBHOOK_MAP.iteritems():
    webhook_signal.connect(webhook_logger)

Forms

The StripePaymentForm sets up a form with fields like the official stripe example.

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).

Use it in a view like so:

if request.method == 'POST':
    zebra_form = StripePaymentForm(request.POST)
    if zebra_form.is_valid():
    	my_profile = request.user.get_profile()
        stripe_customer = stripe.Customer.retrieve(my_profile.stripe_customer_id)
        stripe_customer.card = zebra_form.cleaned_data['stripe_token']
        stripe_customer.save()

        my_profile.last_4_digits = zebra_form.cleaned_data['last_4_digits']
        my_profile.stripe_customer_id = stripe_customer.id
        my_profile.save()

        # Do something kind for the user

else:
    zebra_form = StripePaymentForm()

Template Tags

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.

To use in a template:

{% extends "base.html" %}{% load zebra_tags %}

{% block head %}{{block.super}}
	{% zebra_head_and_stripe_key %}
{% endblock %}

{% block content %}
	{% zebra_card_form %}
{% endblock %}

That's it - all the stripe tokeny goodness happens, and errors are displayed to your users.

Models and Mixins

Model and Mixin docs coming. For now, the code is pretty self-explanatory, and decently documented inline.

Other Useful Bits

Zebra comes with a manage.py command to clear out all the test customers from your account. To use it, run:

./manage.py clear_stripe_test_customers

It responds to --verbosity=[0-3].

Credits

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

Code credits are in the AUTHORS file. Pull requests welcome!

django-zebra's People

Contributors

bufke avatar danawoodman avatar davidykay avatar erikcw avatar iepathos avatar jarcoal avatar joeconyers avatar leetrout avatar mixmastamyk avatar mwisner avatar skoczen avatar thrashr888 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-zebra's Issues

Add test coverage

Using 4242424242424242 as a test card, there should be comprehensive test coverage of this project. Otherwise it is hard to recommend to clients and customers.

Webhooks broken in Django 1.6

Webhooks are throwing exceptions since upgrading to Django 1.6.

According to the Django Deprecation Timeline for 1.6, The attribute HttpRequest.raw_post_data was renamed to HttpRequest.body in 1.4. The backward compatibility will be removed โ€“ HttpRequest.raw_post_data will no longer work.

Stacktrace (most recent call last):

  File "django/core/handlers/base.py", line 114, in get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "newrelic/hooks/framework_django.py", line 485, in wrapper
    return wrapped(*args, **kwargs)
  File "django/views/decorators/csrf.py", line 57, in wrapped_view
    return view_func(*args, **kwargs)
  File "zebra/views.py", line 67, in webhooks_v2
    event_json = simplejson.loads(request.raw_post_data)

Looks like changing request.raw_post_data to request.body will fix the problem. Might need to add sniffing for older versions of Django...

audit_customer_subscription fails when subscription == null

Auditing a customer subscription for a newly created customer object fails when subscription data has not been created. It looks like Customer.subscription is null on creation and if this isnt updated before audit_customer_subscription it fails because subscription doesnt have a status.

*** AttributeError: 'NoneType' object has no attribute 'status'

Order won't submit on Mobile Safari

This is a bigger problem than django-zebra, but I wanted to post this here anyways because I figured some better minds might be able to help work through the problem.

This question roughly mirrors the problem I've been having. We integrated Stripe and Zebra into our site. When I test submit on an iPad, it'll prompt to save the credit card. When you press any of the buttons, it breaks the submit process. The $("button[type=submit]").attr("disabled","disabled").html("Submitting..") line has been fired, so you can't click again, but the submit process never moves forward.

The most obvious answer would be to change the name of the credit card field to something that iOS wouldn't recognize; but overriding forms.py doesn't feel like a very good solution. Not sure if sidestepping the feature is a great solution anyways; ideally; we'd come up with something that detects the problem and addresses it directly. Not sure if there's a way to use JS to monitor the submit process to allow for a second submit in a situation like this?

I'll continue to research the problem on my own, but any help is appreciated! iOS is pretty big for mobile commerce these days, so I'm sure I'm not gonna be the only one who has this problem.

Transfer Webhooks like transfer.paid and transfer.updated

Hi,

Any plans of supporting these "transfer" events:

  1. transfer.updated describes a transfer
    Occurs whenever the amount of a pending transfer is updated.
  2. transfer.paid describes a transfer
    Occurs whenever a sent transfer is expected to be available in the destination bank account. If the transfer failed, a transfer.failed webhook will additionally be sent at a later time.

Is the assumption that if we don't receive transfer.failed, then it was successful? Also, what if there was a usecase to execute a task upon receiving a transfer.created hook - how can we handle that today?

need model howto

Have installed the app and it is almost working correctly, however its not clear from the docs or sample app what fields I should add to my user profiles?

From the form blurb I see:
user.stripe_id, profile.last_4_digits, profile.stripe_customer_id

Would appreciate some guidance on where these come from and how to define these.

Missing semicolons in javascript

Line 19:

$("button[type=submit]").attr("disabled","disabled").html("Submitting..")

and Line 30

return true

Are both missing semicolons.

Custom User Model Breaking Profile Functionality

With Django 1.5 the profile functionality was deprecated in favor of the new custom user model implementation. In my app, I'm using a custom user model for authentication. Currently I'm using Django 1.6.2

When trying to run the example code for stripe:

class UpgradeView(View):
    form_class = StripePaymentForm
    template = 'account/checkout.html'

    def get(self, request, *args, **kwargs):
        return render(request, self.template, {'form': self.form_class()})

    def post(self, request, *args, **kwargs):
        zebra_form = StripePaymentForm(request.POST)
        if zebra_form.is_valid():
            my_profile = request.user.get_profile()
            stripe_customer = stripe.Customer.retrieve(my_profile.stripe_customer_id)
            stripe_customer.card = zebra_form.cleaned_data['stripe_token']
            stripe_customer.save()

            my_profile.last_4_digits = zebra_form.cleaned_data['last_4_digits']
            my_profile.stripe_customer_id = stripe_customer.id
            my_profile.save()
        return render(request, self.template, {'form': zebra_form})

I get an exception saying my custom user model doesn't have the method "get_profile()". Do I need to add some mixins to my user class or just use a profile model anyway?

Here's a copy of my user model and it's manager just in case, note the inclusion of data that would normally be in the profile:

class WUserManager(BaseUserManager):
    def create_user(self, email, password=None):
        """
        Creates and saves a User with the given email and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=self.normalize_email(email),
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password):
        """
        Creates and saves a superuser with the given email and password.
        """
        user = self.create_user(email,
            password=password,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user


class WUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True,
        db_index=True,
    )

    first_name = models.CharField(max_length=100, null=True, blank=True)
    last_name = models.CharField(max_length=150, null=True, blank=True)
    address = models.CharField(max_length=200, null=True, blank=True)
    city = models.CharField(max_length=100, null=True, blank=True)
    state = models.CharField(max_length=50, null=True, blank=True)
    zip = models.PositiveIntegerField(max_length=5, null=True, blank=True)
    account_type = models.CharField(max_length=20)
    created = models.DateTimeField(auto_now_add=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = WUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def get_full_name(self):
        # The user is identified by their email address
        if self.first_name and self.last_name:
            return self.first_name + ' ' + self.last_name

        return self.email

    def get_short_name(self):
        # The user is identified by their email address
        if self.first_name:
            return self.first_name

        return self.email.split('@')[0]

    def get_license_status(self):
        try:
            license = self.license
        except License.DoesNotExist:
            license = None

        if license is not None and isinstance(license, License):
            return True

        return False

    # On Python 3: def __str__(self):
    def __unicode__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):        
        return True

    @property
    def is_staff(self):
        return self.is_admin

    class Meta:
        verbose_name = 'User'
        verbose_name_plural = 'Users'

This project is dead

It looks like this project is dead. I've tried to contact GoodCloud, but they have not responded to any emails / pull requests.

I have a fork over here that has a few small updates and removed some deprecated functionality that I don't anyone is still using. If anyone is interested I can submit that to PyPi and we can make it the new "master". If anyone else has a good fork, I'm all for using that instead.

Thoughts? GoodCloud are you there?

zebra not syncing with database on makemigrations?

Using Django 1.9.4 with mezzanine 4.1.0.

Upon installation (zebra was under INSTALLED_APPS, included URLS, had the Stripe API keys, and had ZEBRA_ENABLE_APP set to true), the necessary tables in my PostgreSQL table were not created by a normal manage.py makemigrations but I had to run makemigrations zebra in order to get this to work.

Getting error when initialising django-zebra

actions done

  • pip install django-zebra
  • zebra included in INSTALLED_APPS
  • added the webhook urls
    re_path(r'zebra/', include(('zebra.urls', 'zebra'), namespace="zebra",)),
  • ZEBRA_ENABLE_APP set to True
  • then tried ./manage.py makemigrations , got error
  File "project_name/manage.py", line 11, in <module>
    execute_from_command_line(sys.argv)
  File "/home/aditya/dev/cn/pmx_env/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/home/aditya/dev/cn/pmx_env/lib/python3.7/site-packages/django/core/management/__init__.py", line 357, in execute
    django.setup()
  File "/home/aditya/dev/cn/pmx_env/lib/python3.7/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/home/aditya/dev/cn/pmx_env/lib/python3.7/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/home/aditya/dev/cn/pmx_env/lib/python3.7/site-packages/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/usr/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/home/aditya/dev/cn/pmx_env/lib/python3.7/site-packages/zebra/models.py", line 51, in <module>
    class Subscription(DatesModelBase, StripeSubscription):
  File "/home/aditya/dev/cn/pmx_env/lib/python3.7/site-packages/zebra/models.py", line 52, in Subscription
    customer = models.ForeignKey(Customer)
TypeError: __init__() missing 1 required positional argument: 'on_delete'```

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.