Ultimate Guide to Integrating Payment Gateways in Python, React, and DRF
Introduction
Monetizing your application is a critical step for many SaaS products and e-commerce platforms. In this guide, we'll walk through the process of integrating a robust payment gateway—Stripe—into a modern stack comprising a Python backend (specifically Django Rest Framework) and a React frontend.
We will cover:
- Setting up the backend with Django and Stripe.
- Creating a payment intent.
- Building the checkout form in React.
- Handling webhooks for post-payment actions.
Why Stripe?
While there are many libraries like PayPal, Braintree, and Razorpay, Stripe stands out for its developer-friendly API, comprehensive documentation, and robust testing environment.
Backend: Django Rest Framework (DRF) Setup
First, install the Stripe python library:
pip install stripe
1. Configure Stripe Keys
Add your keys to settings.py (use environment variables in production!):
# settings.py
import os
STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY')
STRIPE_PUBLISHABLE_KEY = os.environ.get('STRIPE_PUBLISHABLE_KEY')
2. Create the Payment Intent API
We need an endpoint that the frontend handles to initiate a payment. This endpoint creates a PaymentIntent on Stripe servers and returns the client_secret to the frontend.
# views.py
import stripe
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
stripe.api_key = settings.STRIPE_SECRET_KEY
class CreatePaymentIntentView(APIView):
def post(self, request, *args, **kwargs):
try:
data = request.data
amount = data.get('amount') # Amount in cents
# Create a PaymentIntent with the order amount and currency
intent = stripe.PaymentIntent.create(
amount=amount,
currency='usd',
automatic_payment_methods={
'enabled': True,
},
)
return Response({
'clientSecret': intent['client_secret']
})
except Exception as e:
return Response({'error': str(e)}, status=status.HTTP_403_FORBIDDEN)
Don't forget to register this view in your urls.py.
Frontend: React Integration
On the client side, we'll use react-stripe-js.
1. Installation
npm install @stripe/react-stripe-js @stripe/stripe-js
2. The Checkout Form
We need to wrap our payment elements in the Elements provider.
import React, { useState, useEffect } from "react";
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm";
// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
const stripePromise = loadStripe("pk_test_..."); // Your Publishable Key
export default function App() {
const [clientSecret, setClientSecret] = useState("");
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch("/api/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount: 1000 }), // $10.00
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
const appearance = {
theme: 'stripe',
};
const options = {
clientSecret,
appearance,
};
return (
<div className="App">
{clientSecret && (
<Elements options={options} stripe={stripePromise}>
<CheckoutForm />
</Elements>
)}
</div>
);
}
3. The CheckoutForm Component
import React, { useEffect, useState } from "react";
import {
PaymentElement,
useStripe,
useElements
} from "@stripe/react-stripe-js";
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!stripe) {
return;
}
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000",
},
});
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
Handling Webhooks
To ensure your database stays in sync with Stripe (e.g., fulfilling orders only after a successful payment), you must use webhooks. Relying solely on the frontend redirect is insecure as the user can close the window before the redirect happens.
- Create a webhook endpoint in Django.
- Verify the signature using the Stripe library.
- Listen for
payment_intent.succeededevents.
@csrf_exempt
def stripe_webhook(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
event = None
try:
event = stripe.Webhook.construct_event(
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
# Invalid signature
return HttpResponse(status=400)
# Handle the event
if event['type'] == 'payment_intent.succeeded':
payment_intent = event['data']['object']
# Fulfill the purchase...
print('Payment for {} succeeded'.format(payment_intent['amount']))
return HttpResponse(status=200)
Other Payment Libraries
While Stripe is a giant, there are other noteworthy options:
- PayPal SDK: Great for global reach, especially in markets where credit cards are less common.
- Razorpay: Excellent for Indian markets, offering UPI support out of the box.
- Braintree: Owned by PayPal, offers a similar developer experience to Stripe.
Conclusion
Integrating payments creates a bridge between your users and your value proposition. By using Django Rest Framework for secure backend handling and React for a seamless frontend experience, you can build a professional-grade checkout flow. Always use webhooks for reliability and keep your API keys secure!
Comments
Sign in to join the conversation