Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
December 26, 2022 07:00 am GMT

Integrate Stripe Payments with Django By Building a Digital Products Selling App

One of the great features of online selling is being able to process payments online. With the ever-growing online businesses, having a good payment processing system is necessary. Online selling has a lot of advantages among which are; low start-up and operating costs, high flexibility, a wide selection of available products, and improved customer experience.

In this tutorial, you will learn how to integrate Stripe to accept one-time payments for your digital products through a Django app. A digital product can be a book, for example, to which the buyer will be given access after completing the payment.

Stripe is a suite of APIs powering online payment processing and commerce solutions for internet businesses of all sizes. It is one of the most widely used tools for handling payments due to its ease of integration, convenience of use, and nice documentation.

There are two ways to accept one-time payments using Stripe. These are:

  • Stripe Checkout: This is a low-code payment integration that you can use to quickly integrate payments solution into your application.
  • Payment Intent: This provides a more custom experience to end users by storing necessary information about the payment and linking to a Stripe customer. It handles complex payment flows as well.

This tutorial is going to focus on Stripe Checkout. We are going to build an application where you as an admin can upload your products, and your customers (end users) will be able to purchase them.

Note:- If you encounter any issues throughout the tutorial, you can check out the code in theGitHub repository.

Table of Contents

  1. Prerequisite
  2. Project Configuration
  3. Building the Model
  4. Setting Up the Views
  5. Stripe Configuration
  6. Stripe Checkout
  7. Payment Confirmation Using WebHook
  8. Give the Customer Access to the Product
  9. Create Payment History
  10. Conclusion

Prerequisite

This guide assumes that you are familiar with Django.

Project Configuration

First, create a virtual environment and activate it:

python3 -m venv .venvsource .venv/bin/activate

Next, install Django and create a new Django project:

pip install django==4.1.4django-admin startproject config .

Run migrations:

python manage.py migrate

Create a superuser:

python manage.py createsuperuser

Run the server to ensure the installation worked correctly:

python manage.py runserver

Great, now lets create a new app called products. This app will contain the model of the product we are selling, the views, and URL configurations that are related to it.

py manage.py startapp products

Add it to the installed apps list in the settings:

# config/settings.pyINSTALLED_APPS = [    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    'django.contrib.messages',    'django.contrib.staticfiles',    # Local apps    'products',]

Building the Model

To keep it simple for the main scope of this tutorial, we are going to build a simple product model. A product can have tags. In addition, we also want to create a price model so that we can track the changing prices of a product over time. Go to the models.py file of the products app and add the following code:

# products/models.pyfrom django.conf import settingsfrom django.db import modelsfrom django.template.defaultfilters import slugifyfrom django.utils.translation import gettext_lazy as _User = settings.AUTH_USER_MODELdef get_image_filename(instance, filename):    name = instance.name    slug = slugify(name)    return f"products/{slug}-{filename}"class ProductTag(models.Model):    name = models.CharField(        max_length=100, help_text=_("Designates the name of the tag.")    )    created_at = models.DateTimeField(auto_now_add=True)    updated_at = models.DateTimeField(auto_now=True)    def __str__(self) -> str:        return self.nameclass Product(models.Model):    name = models.CharField(max_length=200)    tags = models.ManyToManyField(ProductTag, blank=True)    desc = models.TextField(_("Description"), blank=True)    thumbnail = models.ImageField(upload_to=get_image_filename, blank=True)    url = models.URLField()    quantity = models.IntegerField(default=1)    created_at = models.DateTimeField(auto_now_add=True)    updated_at = models.DateTimeField(auto_now=True)    class Meta:        ordering = ("-created_at",)    def __str__(self):        return self.nameclass Price(models.Model):    product = models.ForeignKey(Product, on_delete=models.CASCADE)    price = models.DecimalField(decimal_places=2, max_digits=10)    created_at = models.DateTimeField(auto_now_add=True)    updated_at = models.DateTimeField(auto_now=True)    def __str__(self) -> str:        return f"{self.product.name} {self.price}"
  • Note that the product has a url field that is going to be used to send the customer a link to the digital product you are selling (book).
  • The Product model also contains an image field for the thumbnail so lets go ahead and install Pillow.
pip install pillow==9.3.0

Then, lets register the models as follows:

# products/admin.pyfrom django.contrib import adminfrom .models import Price, Product, ProductTagclass PriceAdmin(admin.StackedInline):    model = Price@admin.register(Product)class ProductAdmin(admin.ModelAdmin):    inlines = (PriceAdmin,)    class Meta:        model = Productadmin.site.register(ProductTag)admin.site.register(Price)
  • The Price model is registered inline with the Product model so that we can add the prices of a product while creating the product inside the admin panel.

Since we are working with user-uploaded images, we need to set MEDIA_URL and MEDIA_ROOT in the settings:

# config/settings.pyMEDIA_ROOT = os.path.join(BASE_DIR, 'media')MEDIA_URL = '/media/'

Next, configure the project'surls.py to serve user-uploaded media files duringdevelopment.

# config/urls.pyfrom django.conf import settingsfrom django.conf.urls.static import static# Media Assetsurlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Great, lets now test the functionality we have so far:

py mangage.py makemigrationspy manage.py migratepy mangage.py runserver

Go to the admin panel and you can now create products

Create Products

Setting Up the Views

Lets now create views and templates to list our available products and view them in detail.

# products/views.pyfrom django.views import Viewfrom django.views.generic import DetailView, ListViewfrom .models import Price, Productclass ProductListView(ListView):    model = Product    context_object_name = "products"    template_name = "products/product_list.html"class ProductDetailView(DetailView):    model = Product    context_object_name = "product"    template_name = "products/product_detail.html"    def get_context_data(self, **kwargs):        context = super(ProductDetailView, self).get_context_data()        context["prices"] = Price.objects.filter(product=self.get_object())        return context

Then, update the projects urls.py file to include the URL configuration for products app:

# config/urls.pyfrom django.urls import include, pathurlpatterns = [    #...    path("", include("products.urls", namespace="products")),]

Create a urls.py file inside the products app and add the URL configuration for the product list and detail views:

# products/urls.pyfrom django.urls import pathfrom .views import (    ProductDetailView,    ProductListView,)app_name = "products"urlpatterns = [    path("", ProductListView.as_view(), name="product-list"),    path("<int:pk>/", ProductDetailView.as_view(), name="product-detail"),]

Then, create a templates folder at the root of your project:

mkdir templates

Add it to the settings:

# config/settings.pyTEMPLATES = [    {        "DIRS": [BASE_DIR / "templates"],        ...    },]

Inside the templates directory, create a file named base.html that will be used as a base template for all the other templates we are going to create. Note that we are using Bootstrap for styling.

<!DOCTYPE html><html lang="en">  <head>    <!-- Required meta tags -->    <meta charset="utf-8" />    <meta      name="viewport"      content="width=device-width, initial-scale=1, shrink-to-fit=no"    />    <!-- Bootstrap CSS -->    <link      rel="stylesheet"      href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"      integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"      crossorigin="anonymous"    />    <!--Font awesome icons -->    <link      rel="stylesheet"      href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"      integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay"      crossorigin="anonymous"    />    {% block script %}{% endblock %}    <title>{% block title %} {% endblock %}</title>  </head>  <body>    <div class="container p-3 my-3">      <div class="row">        <div class="col-md-12">          <!--Any flash messages pop up in any page because this is the base template-->          {% if messages %}          <div class="alert alert-dismissible" role="alert">            {% for message in messages %}            <div class="alert alert-{{ message.tags }}">{{ message }}</div>            {% endfor %}            <button              type="button"              class="close"              data-dismiss="alert"              aria-label="Close"            >              <span aria-hidden="true">&times;</span>            </button>          </div>          {% endif %} {% block content %}{% endblock %}        </div>      </div>    </div>    <!-- jQuery first, then Popper.js, then Bootstrap JS -->    <script      src="https://code.jquery.com/jquery-3.2.1.slim.min.js"      integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"      crossorigin="anonymous"    ></script>    <script      src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"      integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"      crossorigin="anonymous"    ></script>    <script      src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"      integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"      crossorigin="anonymous"    ></script>  </body></html>

Next, create a folder named products inside the templates folder, and inside it, create 2 files named product_list.html and product_detail.html

{% comment %} templates/products/product_list.html {% endcomment %}{% extends "base.html" %} {% block title %} Products {% endblock title%} {% block content %} {% for product in products %}<div class="card" style="width: 18rem">  <img    class="card-img-top"    src="{{product.thumbnail.url}}"    alt="Card image cap"  />  <div class="card-body">    <h5 class="card-title">{{product.name}}</h5>    <p class="card-text">{{product.desc}}</p>    <a      href="{% url 'products:product-detail' product.id %}"      class="btn btn-primary"      >View Detail</a    >  </div></div>{% endfor %} {% endblock content %}
{% comment %} templates/products/product_detail.html {% endcomment %}{% extends "base.html" %} {% block title %} Buy Product {% endblock title%} {% block content %}<div class="card-deck">  {% for price in prices %}  <div class="card">    <img      class="card-img-top"      src="{{product.thumbnail.url}}"      alt="Card image cap"    />    <div class="card-body">      <h5 class="card-title">{{product.name}}</h5>      <p class="card-text">{{product.desc}}</p>      <div class="card-text">        <h5 class="card-text">${{ price.price }}</h5>        <form          action=""          method="POST"        >          {% csrf_token %}          <button type="submit" class="btn btn-primary">Checkout</button>        </form>      </div>    </div>  </div>  {% endfor %}</div>{% endblock content %}
  • On the product detail page, we are looping through and displaying the different prices of the product with a checkout button attached.
  • In this tutorial, the only variation for a product is its price. However, you can configure the models to include and update the structure as it seems fit to your requirements.

Go to http://localhost:8000/admin/ and create a sample product. Then go to the index page http://localhost:8000 to see the product and its detail.

Stripe Configuration

First, install the Stripe package which provides convenient access to the Stripe API from Python applications:

pip install stripe==5.0.0

Next, log in to your Stripe account or create a new one and head over to the dashboard

Stripe Dashboard

Click on Developers then on the left sidebar click on API keys:

Stripe API Keys

Grab the public and secret keys for use in your project. Note that you will get a different pair in production (when test mode is disabled).

Then, create a .env file at the root of your project:

touch .env

and store the keys in your environment file:

# StripeSTRIPE_PUBLISHABLE_KEY=STRIPE_SECRET_KEY=

In order to access these keys in the project, we need to install Python Decouple:

pip install python-decouple==3.6

Then, update the settings to include the public and secret keys you grabbed from the Stripe dashboard:

# config/settings.pyfrom decouple import config# StripeSTRIPE_PUBLISHABLE_KEY = config('STRIPE_PUBLISHABLE_KEY')STRIPE_SECRET_KEY = config('STRIPE_SECRET_KEY')

Finally, make sure that you have set up an account name on the Stripe Dashboard https://dashboard.stripe.com/settings/account

Stripe Checkout

In this section, we are going to create a Stripe checkout session. Normally, you would want to put payment-related logic into its own app, however, for the scope of this tutorial, we will use the products app we created earlier to create the views for a Stripe checkout.

Before proceeding, lets first understand what the flow is going to look like:

  • We will create a Checkout session when the Checkout button is pressed.
  • The Checkout Session is unique with different properties among which is a URL that redirects customers to a Stripe-hosted payment page. This is done by using Stripes JavaScript module.
  • Customers will fill in their payment details like card number and complete the transaction.
  • Customers will be redirected to a success page for successful payments and to a cancellation page for canceled payments.
  • After the transaction is completed, payment is confirmed using webhook.

Head over to products/views.py and create the Stripe checkout session view as follows:

# products/views.pyimport stripefrom django.conf import settingsfrom django.shortcuts import redirectfrom django.views import Viewfrom .models import Pricestripe.api_key = settings.STRIPE_SECRET_KEYclass CreateStripeCheckoutSessionView(View):    """    Create a checkout session and redirect the user to Stripe's checkout page    """    def post(self, request, *args, **kwargs):        price = Price.objects.get(id=self.kwargs["pk"])        checkout_session = stripe.checkout.Session.create(            payment_method_types=["card"],            line_items=[                {                    "price_data": {                        "currency": "usd",                        "unit_amount": int(price.price) * 100,                        "product_data": {                            "name": price.product.name,                            "description": price.product.desc,                            "images": [                                f"{settings.BACKEND_DOMAIN}/{price.product.thumbnail}"                            ],                        },                    },                    "quantity": price.product.quantity,                }            ],            metadata={"product_id": price.product.id},            mode="payment",            success_url=settings.PAYMENT_SUCCESS_URL,            cancel_url=settings.PAYMENT_CANCEL_URL,        )        return redirect(checkout_session.url)

Here are a couple of things to note from the above code:

  • The view will be called with a price id URL parameter.
  • The view creates a new Checkout Session by calling the stripe.checkout.Session.create method with the appropriate parameters.
  • One of the parameters is line_items. It provides a list of items the customer is purchasing.
  • Stripe allows you to create product and price objects in the dashboard. However, in our case, we are managing the product and prices ourselves so we need to create the Stripe Price object inline. To do so, we used the price_data parameter of line_items by passing in the currency, unit amount, product name, description, and image. For a list of fields and parameters, you can use, check out the doc.
  • The metadata parameter defines a set of key-value pairs that you can attach to an object. You can store additional information about the object using it. We will use the metadata value later when we create a webhook handler.
  • We are implementing a one-time payment so we used mode with payment value.
  • The success and cancel URLs define the URLs the customer will be redirected to when the transaction is completed, or canceled respectively.

Now, update the .env file to include the following environment variables:

BACKEND_DOMAIN=http://127.0.0.1:8000PAYMENT_SUCCESS_URL=http://127.0.0.1:8000/success/PAYMENT_CANCEL_URL=http://127.0.0.1:8000/cancel/

Then, update the settings:

# config/settings.pyBACKEND_DOMAIN = config("BACKEND_DOMAIN")PAYMENT_SUCCESS_URL = config("PAYMENT_SUCCESS_URL")PAYMENT_CANCEL_URL = config("PAYMENT_CANCEL_URL")

Add the URL configuration for the CreateStripeCheckoutSessionView

# products/urls.pyfrom django.urls import pathfrom .views import CreateStripeCheckoutSessionViewapp_name = "products"urlpatterns = [    path(        "create-checkout-session/<int:pk>/",        CreateStripeCheckoutSessionView.as_view(),        name="create-checkout-session",    ),]

Great, we now need to update the product_detail template so that the Checkout form is functional:

{% comment %} templates/products/product_detail.html {% endcomment %}{% extends "base.html" %} {% block title %} Buy Product {% endblock title%} {% block script %} <script src="https://js.stripe.com/v3/"></script> {% endblock script%} {% block content %}<div class="card-deck">  {% for price in prices %}  <div class="card">    <img      class="card-img-top"      src="{{product.thumbnail.url}}"      alt="Card image cap"    />    <div class="card-body">      <h5 class="card-title">{{product.name}}</h5>      <p class="card-text">{{product.desc}}</p>      <div class="card-text">        <h5 class="card-text">${{ price.price }}</h5>        <form          action="{% url 'products:create-checkout-session' price.id %}"          method="POST"        >          {% csrf_token %}          <button type="submit" class="btn btn-primary">Checkout</button>        </form>      </div>    </div>  </div>  {% endfor %}</div>{% endblock content %} 

In the above code:

  • We added the Stripe.js library at the top.
  • We updated the form action attribute to send form data to the CreateStripeCheckoutSessionView

Lets also add payment success and cancel views:

# products/views.pyfrom django.views.generic import TemplateViewclass SuccessView(TemplateView):    template_name = "products/success.html"class CancelView(TemplateView):    template_name = "products/cancel.html"

Create the templates (success.html and cancel.html) for the above 2 views inside templates/products directory.

Success.html

{% comment %} templates/products/success.html {% endcomment %}{% extends "base.html" %} {% block title %} Success {% endblock title%} {% block content %}<div class="alert alert-success" role="alert">  Thanks for purchasing our product  <a      href="{% url 'products:product-list' %}"      >Go to Products</a    ></div>{% endblock content %}

Cancel.html

{% comment %} templates/products/cancel.html {% endcomment %}{% extends "base.html" %} {% block title %} Cancel {% endblock title%} {% block content %}<div class="alert alert-info" role="alert">  Forgot to add something to your cart?  <a      href="{% url 'products:product-list' %}"      >Go to Products</a    ></div>{% endblock content %}

Finally, configure the routes for success and cancel views:

# products/urls.pyfrom django.urls import pathfrom .views import CancelView SuccessViewapp_name = "products"urlpatterns = [    path("success/", SuccessView.as_view(), name="success"),    path("cancel/", CancelView.as_view(), name="cancel"),]

Time to test the functionality, head over to the product detail page (here I have one product with three different prices):

Product detail page

Then click the Checkout button on one of the products and you will get redirected to the Stripe checkout page:

Stripe Checkout

As mentioned in the docs too, use the following form fields for testing:

  • For card number use 4242 4242 4242 4242
  • For Date use a valid future date, such as12/34
  • Use any three-digit CVC

You can use any value you like for the other fields.

Great, after you click the Pay button, you will be redirected to the success page:

Success Page

To make sure everything worked correctly, head over to https://dashboard.stripe.com/test/payments and you will see your recent payment listed:

Stripe recent payments

Everything seems to be working fine now, However, even if the payment failed, the user will be redirected to the success page i.e. being redirected to the success page doesnt guarantee a successful payment. Therefore, we need a way to ensure the process went smoothly. This is where WebHooks come into play.

Payment Confirmation Using WebHook

Stripe uses webhooks to notify your application when an event happens. Although there are a couple of events that you can handle, the event we are interested in right now is the checkout.session.completed event that occurs when a Checkout Session has been successfully completed.

First and foremost install the Stripe CLI tool for your OS.

Then, run the following command to send all Stripe events in test mode to a local webhook endpoint called /webhooks/stripe/

stripe listen --forward-to localhost:8000/webhooks/stripe/

Copy the webhook secret key generated by running the above command and paste it into the .env file found at the root of your project:

STRIPE_WEBHOOK_SECRET=

Update the settings as well:

STRIPE_WEBHOOK_SECRET = config("STRIPE_WEBHOOK_SECRET")

Great, now head over to products/views.py and create the webhook handler as an HTTP endpoint:

# products/views.pyimport stripefrom django.conf import settingsfrom django.http import HttpResponsefrom django.utils.decorators import method_decoratorfrom django.views import Viewfrom django.views.decorators.csrf import csrf_exemptfrom .models import Price, Productstripe.api_key = settings.STRIPE_SECRET_KEY@method_decorator(csrf_exempt, name="dispatch")class StripeWebhookView(View):    """    Stripe webhook view to handle checkout session completed event.    """    def post(self, request, format=None):        payload = request.body        endpoint_secret = settings.STRIPE_WEBHOOK_SECRET        sig_header = request.META["HTTP_STRIPE_SIGNATURE"]        event = None        try:            event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)        except ValueError as e:            # Invalid payload            return HttpResponse(status=400)        except stripe.error.SignatureVerificationError as e:            # Invalid signature            return HttpResponse(status=400)        if event["type"] == "checkout.session.completed":            print("Payment successful")        # Can handle other events here.        return HttpResponse(status=200)
  • Note that we have used the csrf_exempt decorator to make the view exempt from the CSRF protection ensured by Djangos middleware This allows us to receive data from another domain (Stripe) even if it has no CSRF token.
  • Stripe can optionally sign the webhook events it sends to your endpoints by including a signature in each eventsStripe-Signature header. By adding this, we are verifying that the events were indeed sent by Stripe and not by a third party.

Add the URL configuration:

# products/urls.pyfrom django.urls import pathfrom .views import StripeWebhookViewapp_name = "products"urlpatterns = [    path("webhooks/stripe/", StripeWebhookView.as_view(), name="stripe-webhook"),]
  • Since we have configured Stripe earlier in this section to send all events to a local endpoint named webhooks/stripe/, make sure the name of the URL is also /webhooks/stripe/

Let's test it. Now when you make a payment, you will see the payment successful output on the console:

Stripe WebHooks

Give the Customer Access to the Product

We now need to give the customer access to the product he/she purchased upon successful payment. Since we are selling a digital product (book), we can do so by sending the user an email containing a link to the product.

Go to products/views.py and update the webhook as follows:

# products/views.pyimport stripefrom django.conf import settingsfrom django.core.mail import send_mail # Add thisfrom django.http import HttpResponsefrom django.utils.decorators import method_decoratorfrom django.views import Viewfrom django.views.decorators.csrf import csrf_exemptfrom .models import Price, Productstripe.api_key = settings.STRIPE_SECRET_KEY@method_decorator(csrf_exempt, name="dispatch")class StripeWebhookView(View):    """    Stripe webhook view to handle checkout session completed event.    """    def post(self, request, format=None):        payload = request.body        endpoint_secret = settings.STRIPE_WEBHOOK_SECRET        sig_header = request.META["HTTP_STRIPE_SIGNATURE"]        event = None        try:            event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)        except ValueError as e:            # Invalid payload            return HttpResponse(status=400)        except stripe.error.SignatureVerificationError as e:            # Invalid signature            return HttpResponse(status=400)        if event["type"] == "checkout.session.completed":            print("Payment successful")            # Add this            session = event["data"]["object"]            customer_email = session["customer_details"]["email"]            product_id = session["metadata"]["product_id"]            product = get_object_or_404(Product, id=product_id)            send_mail(                subject="Here is your product",                message=f"Thanks for your purchase. The URL is: {product.url}",                recipient_list=[customer_email],                from_email="[email protected]",            )        # Can handle other events here.        return HttpResponse(status=200)
  • Remember the product_id we passed as metadata in the CreateStripeCheckoutSessionView? we are using that to get the product URL and send it to the user through his/her email.

For testing purposes, we can use the console email backend so update the settings as follows:

# config/settings.pyEMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

Now, test the payment process again and you will get an email on your console with a link to the product.

Create Payment History

Aside from making sure the payment went smoothly, you may also want to keep a users payment history in your database. To do so, lets first create a PaymentHistory model with the following fields:

# products/models.pyclass PaymentHistory(models.Model):    PENDING = "P"    COMPLETED = "C"    FAILED = "F"    STATUS_CHOICES = (        (PENDING, _("pending")),        (COMPLETED, _("completed")),        (FAILED, _("failed")),    )    email = models.EmailField(unique=True)    product = models.ForeignKey(Product, on_delete=models.CASCADE)    payment_status = models.CharField(        max_length=1, choices=STATUS_CHOICES, default=PENDING    )    created_at = models.DateTimeField(auto_now_add=True)    updated_at = models.DateTimeField(auto_now=True)    def __str__(self):        return self.product.name

Then, in the webhook view, create a payment history object upon a successful payment:

# products/views.pyfrom .models import PaymentHistory # Add this@method_decorator(csrf_exempt, name="dispatch")class StripeWebhookView(View):    """    Stripe webhook view to handle checkout session completed event.    """    def post(self, request, format=None):        payload = request.body        endpoint_secret = settings.STRIPE_WEBHOOK_SECRET        sig_header = request.META["HTTP_STRIPE_SIGNATURE"]        event = None        try:            event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)        except ValueError as e:            # Invalid payload            return HttpResponse(status=400)        except stripe.error.SignatureVerificationError as e:            # Invalid signature            return HttpResponse(status=400)        if event["type"] == "checkout.session.completed":            print("Payment successful")            session = event["data"]["object"]            customer_email = session["customer_details"]["email"]            product_id = session["metadata"]["product_id"]            product = get_object_or_404(Product, id=product_id)            send_mail(                subject="Here is your product",                message=f"Thanks for your purchase. The URL is: {product.url}",                recipient_list=[customer_email],                from_email="[email protected]",            )            PaymentHistory.objects.create(                email=customer_email, product=product, payment_status="completed"            ) # Add this        # Can handle other events here.        return HttpResponse(status=200)

Conclusion

In this tutorial, we have created a minimal product listing and checkout functionality using Stripe. Your next steps can be integrating Celery for sending emails upon successful payment, registration/login system for your customers, updating the user interface, expanding the model structure, and so on.

If you got lost somewhere throughout the guide, check out the project onGitHub

Happy coding!


Original Link: https://dev.to/documatic/integrate-stripe-payments-with-django-by-building-a-digital-products-selling-app-le5

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To