Back to Blog
Tutorials Featured

Implementing Stripe Payments in Django: A Complete Guide

admin
December 4, 2025 4 min read
293 views
Learn how to integrate Stripe payment processing into your Django e-commerce application with webhooks, subscriptions, and error handling.

Implementing Stripe Payments in Django

Stripe is the gold standard for payment processing. Here's how we implemented it in DjangoZen for a seamless checkout experience.

Why Stripe?

  • Developer-friendly API: Clean, well-documented, and intuitive
  • Global support: 135+ currencies, 40+ countries
  • Built-in fraud detection: Radar protects against fraud
  • Webhooks: Real-time payment status updates
  • PCI compliant: Handles sensitive card data securely

Initial Setup

Install the Stripe Python library:

pip install stripe

Configure in settings:

# settings.py
STRIPE_PUBLIC_KEY = os.environ.get('STRIPE_PUBLIC_KEY')
STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY')
STRIPE_WEBHOOK_SECRET = os.environ.get('STRIPE_WEBHOOK_SECRET')

# Initialize Stripe
import stripe
stripe.api_key = STRIPE_SECRET_KEY

Creating a Checkout Session

The modern approach uses Stripe Checkout for a hosted payment page:

# views.py
import stripe
from django.conf import settings
from django.shortcuts import redirect
from django.urls import reverse

def create_checkout_session(request):
    cart = request.user.cart

    line_items = []
    for item in cart.items.all():
        line_items.append({
            'price_data': {
                'currency': 'eur',
                'product_data': {
                    'name': item.product.name,
                    'description': item.product.short_description[:500],
                    'images': [request.build_absolute_uri(item.product.image.url)] if item.product.image else [],
                },
                'unit_amount': int(item.price * 100),  # Stripe uses cents
            },
            'quantity': item.quantity,
        })

    try:
        checkout_session = stripe.checkout.Session.create(
            payment_method_types=['card'],
            line_items=line_items,
            mode='payment',
            success_url=request.build_absolute_uri(
                reverse('eshop:checkout_success')
            ) + '?session_id={CHECKOUT_SESSION_ID}',
            cancel_url=request.build_absolute_uri(reverse('eshop:cart')),
            customer_email=request.user.email,
            metadata={
                'user_id': str(request.user.id),
                'cart_id': str(cart.id),
            },
            shipping_address_collection={
                'allowed_countries': ['US', 'CA', 'GB', 'DE', 'FR', 'NL'],
            } if cart.has_physical_items else None,
        )

        return redirect(checkout_session.url)

    except stripe.error.StripeError as e:
        messages.error(request, f'Payment error: {str(e)}')
        return redirect('eshop:cart')

Handling Webhooks

Webhooks are crucial for reliable payment confirmation:

# views.py
import stripe
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

@csrf_exempt
@require_POST
def stripe_webhook(request):
    payload = request.body
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
        )
    except ValueError:
        return HttpResponse(status=400)
    except stripe.error.SignatureVerificationError:
        return HttpResponse(status=400)

    # Handle specific events
    if event['type'] == 'checkout.session.completed':
        session = event['data']['object']
        handle_successful_payment(session)

    elif event['type'] == 'payment_intent.payment_failed':
        payment_intent = event['data']['object']
        handle_failed_payment(payment_intent)

    elif event['type'] == 'customer.subscription.updated':
        subscription = event['data']['object']
        handle_subscription_update(subscription)

    return HttpResponse(status=200)


def handle_successful_payment(session):
    """Process successful payment"""
    from .models import Order, OrderItem

    user_id = session['metadata']['user_id']
    user = User.objects.get(id=user_id)
    cart = user.cart

    # Create order
    order = Order.objects.create(
        user=user,
        stripe_session_id=session['id'],
        stripe_payment_intent=session['payment_intent'],
        total=session['amount_total'] / 100,
        status='completed',
        payment_status='paid',
    )

    # Create order items
    for cart_item in cart.items.all():
        OrderItem.objects.create(
            order=order,
            product=cart_item.product,
            quantity=cart_item.quantity,
            price=cart_item.price,
        )

        # Generate license keys
        if cart_item.product.requires_license:
            generate_license(order, cart_item.product, user)

    # Clear cart
    cart.items.all().delete()

    # Send confirmation email
    send_order_confirmation.delay(order.id)

Frontend Integration

Add Stripe.js for card elements:

<!-- checkout.html -->
<script src="https://js.stripe.com/v3/"></script>

<form id="payment-form">
    <div id="card-element"></div>
    <div id="card-errors" role="alert"></div>
    <button type="submit" id="submit-btn">
        <span id="button-text">Pay €{{ total }}</span>
        <span id="spinner" class="d-none">
            <i class="fas fa-spinner fa-spin"></i>
        </span>
    </button>
</form>

<script>
const stripe = Stripe('{{ stripe_public_key }}');
const elements = stripe.elements();

const style = {
    base: {
        color: '#32325d',
        fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
        fontSmoothing: 'antialiased',
        fontSize: '16px',
        '::placeholder': {
            color: '#aab7c4'
        }
    },
    invalid: {
        color: '#fa755a',
        iconColor: '#fa755a'
    }
};

const cardElement = elements.create('card', {style: style});
cardElement.mount('#card-element');

// Handle errors
cardElement.on('change', function(event) {
    const displayError = document.getElementById('card-errors');
    if (event.error) {
        displayError.textContent = event.error.message;
    } else {
        displayError.textContent = '';
    }
});

// Handle form submission
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
    event.preventDefault();

    const submitBtn = document.getElementById('submit-btn');
    submitBtn.disabled = true;
    document.getElementById('spinner').classList.remove('d-none');

    const {error, paymentIntent} = await stripe.confirmCardPayment(
        clientSecret,
        {
            payment_method: {
                card: cardElement,
                billing_details: {
                    email: '{{ user.email }}'
                }
            }
        }
    );

    if (error) {
        document.getElementById('card-errors').textContent = error.message;
        submitBtn.disabled = false;
        document.getElementById('spinner').classList.add('d-none');
    } else {
        window.location.href = '/checkout/success/?payment_intent=' + paymentIntent.id;
    }
});
</script>

Subscription Billing

For SaaS products with recurring payments:

def create_subscription(request, price_id):
    """Create a subscription for SaaS products"""

    # Get or create Stripe customer
    if not request.user.stripe_customer_id:
        customer = stripe.Customer.create(
            email=request.user.email,
            metadata={'user_id': str(request.user.id)}
        )
        request.user.stripe_customer_id = customer.id
        request.user.save()

    # Create subscription
    subscription = stripe.Subscription.create(
        customer=request.user.stripe_customer_id,
        items=[{'price': price_id}],
        payment_behavior='default_incomplete',
        expand=['latest_invoice.payment_intent'],
        metadata={
            'user_id': str(request.user.id),
        }
    )

    return JsonResponse({
        'subscriptionId': subscription.id,
        'clientSecret': subscription.latest_invoice.payment_intent.client_secret,
    })

Error Handling

Always handle Stripe errors gracefully:

try:
    charge = stripe.Charge.create(...)
except stripe.error.CardError as e:
    # Card declined
    return handle_card_error(e)
except stripe.error.RateLimitError:
    # Too many requests
    return retry_with_backoff()
except stripe.error.InvalidRequestError as e:
    # Invalid parameters
    logger.error(f'Invalid Stripe request: {e}')
    return handle_invalid_request(e)
except stripe.error.AuthenticationError:
    # API key issues
    logger.critical('Stripe authentication failed')
    return handle_auth_error()
except stripe.error.APIConnectionError:
    # Network issues
    return handle_network_error()
except stripe.error.StripeError as e:
    # Generic error
    logger.error(f'Stripe error: {e}')
    return handle_generic_error(e)

Testing

Use Stripe's test cards:

Card Number Result
4242 4242 4242 4242 Success
4000 0000 0000 9995 Declined
4000 0025 0000 3155 Requires 3D Secure

Best Practices

  1. Always use webhooks - Don't rely on redirect success
  2. Store payment intent IDs - For refunds and disputes
  3. Implement idempotency - Prevent duplicate charges
  4. Log everything - Debug payment issues
  5. Use test mode - Thoroughly test before going live

Check out our e-commerce templates with Stripe already integrated!

Comments (0)

Please login to leave a comment.

No comments yet. Be the first to comment!