python and django

Django Stripe Tutorial

Django Stripe Tutorial

In this tutorial I'll demonstrate how to configure a new Django website from scratch to accept one-time payments with Stripe. If you need help configuring your dev environment to use pipenv and Python 3, please see this documentation here.


Initial Setup

The first step is to install Django, start a new project djangostripe, and create our first app payments. In a new command-line console, enter the following:

$ pipenv install django
$ pipenv shell
(env) $ django-admin startproject djangostripe .
(env) $ python startapp payments

I've assumed the virtual environment is called (env) for simplicity but really it will be a variation of your code directory.

Now add the new app to the INSTALLED_APPS configuration in


    # Local
    'payments.apps.PaymentsConfig', # new

Update the project-level file with the payments app.

from django.contrib import admin
from django.urls import path, include # new

urlpatterns = [
    path('', include('payments.urls')), # new

Create a file within our new app, too.

(env) $ touch payments/

Then populate it as follows:

# payments/
from django.urls import path

from . import views

urlpatterns = [
    path('', views.HomePageView.as_view(), name='home'),

Now add a file:

# payments/
from django.views.generic.base import TemplateView

class HomePageView(TemplateView):
    template_name = 'home.html'

And create a dedicated templates folder and file for our homepage.

(env) $ mkdir templates
(env) $ touch templates/home.html
<!-- templates/home.html -->
Hello, World!

Make sure to update the file so Django knows to look for a templates folder.

        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'], # new

Finally run migrate to sync our database and runserver to start Django's local web server.

(env) $ python migrate
(env) $ python runserver

That's it! Check out and you'll see the homepage with our text.

Hello World

Add Stripe

Time for Stripe. The first step is to install stripe via pipenv.

$ pipenv install stripe

Then go to the Stripe website and create a new account. After that you'll be redirected to the dashboard page. Click on "Developers" in the left sidebar.

Stripe Developers

Then from the dropdown list click on "API keys."

Stripe Developers Key

Each Stripe account has four API keys: two for testing and two for live in production. Each pair has a "secret key" and a "publishable key". Do not reveal the secret key to anyone; the publishable key will be embedded in the JavaScript on the page that anyone can see.

Currently the toggle for "Viewing test data" in the upper right tells us we're using the test keys now. That's what we want.

At the bottom of your file, add the following two lines including your own test secret and test publishable keys. Make sure to include the '' characters around the actual keys.

STRIPE_SECRET_KEY = '<your test secret key here>'
STRIPE_PUBLISHABLE_KEY = '<your test publishable key here>'

Stripe Checkout

Ok, now that we have our API keys we need to add them to our website. We can use Stripe Checkout so we don't have to wire up all the forms ourself.

Update the home.html template as follows.

<!-- templates/home.html -->
<h2>Buy for $5.00</h2>
<script src="" class="stripe-button"
    data-key="{{ key }}"
    data-description="A Django Charge"

Now refresh the web page and a blue button appears.

Blue Button

Click on it and a fancy form appears, styled by Stripe for us.


We can test the form by using one of several test card numbers Stripe provides. Let's use 4242 4242 4242 4242. Make sure the expiration date is in the future and add any 3 numbers for the CVC.

opup filled

But there's a problem after clicking on the "Pay $5.00" button which Stripe highlights for us: we never passed in the key value.

Popup set key

Right here is where many newcomers become confused. Why is the charge failing, right?

The reason is that the full flow is as follows:

  • the form is submitted in the client with our publishable key to Stripe
  • Stripe stores the credit card information securely--we never see it ourselves--and issues us a unique token for the customer which is sent back to us
  • next we use the token to make a charge via the secret key stored only on our server

Right now we haven't included our publishable key yet, so Stripe is complaining. We can update our payments/ file to pass it in as the value key. We do this by overriding get_context_data and importing settings at the top.

# payments/
from django.conf import settings # new
from django.views.generic.base import TemplateView

class HomePageView(TemplateView):
    template_name = 'home.html'

    def get_context_data(self, **kwargs): # new
        context = super().get_context_data(**kwargs)
        context['key'] = settings.STRIPE_PUBLISHABLE_KEY
        return context

Now refresh the web page and try again. It will "work" in that the button turns green. If you look at the Stripe Dashboard and click on "Logs" under "Developers" in the left menu, you can see that tokens are created.


But if you then click on "Payments" in the same lefthand menu, there are no charges. So what's happening?

Think back to the Stripe flow. We have used the publishable key to send the credit card information to Stripe, and Stripe has sent us back a token. But we haven't used the token yet to make a charge! We'll do that now.


Creating a charge is not as hard as it seems. The first step is to make our payment button a form. We include {% csrf_token %} in the form as required for security reasons in Django.

<!-- templates/home.html -->
<h2>Buy for $5.00</h2>
<form action="{% url 'charge' %}" method="post">
  {% csrf_token %}
  <script src="" class="stripe-button"
          data-key="{{ key }}"
          data-description="A Django Charge"

Note it will redirect to a charge page so let's create that now.

$ touch templates/charge.html

Add some text to it.

<!-- templates/charge.html -->
<h2>Thanks, you paid <strong>$5.00</strong>!</h2>

Update our URL routes with the new charge/ page.

# payments/
from django.urls import path

from . import views

urlpatterns = [
    path('charge/', views.charge, name='charge'), # new
    path('', views.HomePageView.as_view(), name='home'),

Now for the "magic" logic which will occur in our payments/ file. We create a charge view that receives the token from Stripe, makes the charge, and then redirects to the charge page upon success.

Import stripe on the top line, also render, and set the stripe.api_key.

# payments/
import stripe # new

from django.conf import settings
from django.views.generic.base import TemplateView
from django.shortcuts import render # new

stripe.api_key = settings.STRIPE_SECRET_KEY # new

class HomePageView(TemplateView):
    template_name = 'home.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['key'] = settings.STRIPE_PUBLISHABLE_KEY
        return context

def charge(request): # new
    if request.method == 'POST':
        charge = stripe.Charge.create(
            description='A Django charge',
        return render(request, 'charge.html')

The charge function-based view assumes a POST request: we are sending data to Stripe here. We make a charge that includes the amount, currency, description, and crucially the source which has the unique token Stripe generated for this transaction. Then we return the request object and load the charge.html template.

In the real-world you would want to include robust error handling here but we can just assume it all works for now.

Ok, refresh the web page at Click on the button for the popup which looks the same. Use the credit card number 4242 4242 4242 4242 again and you'll end up on our charge page.

Charge page

To confirm a charge was actually made, go back to the Stripe dashboard under "Payments."

Payments page

To review, we used Stripe Checkout and our publishable key to send a customer's credit card information to Stripe. The Stripe API then sent us back a unique token for the customer, which we used alongside our secret key on the server to submit a charge.

What's Next

On a live website it is required to have HTTPS so your connection is secure. Also while we hardcoded our API keys here for simplicity, it's a better idea to use environment variables instead to store them.

Want to learn more? I'm busy working on a course that will cover how to build and deploy two separate Django e-commerce stores: an online book store for one-time charges and a monthly subscription site with customers.

Sign up with the newsletter below to be notified when they are ready!

Join our mailing list to be notified about course updates and new tutorials.


Microservices with Docker, Flask, and React

Get the full course. Learn how to build, test, and deploy microservices to Amazon ECS powered by Docker, Flask, and React!

Table of Contents