How to Not Charge Credit Card Again on Refresh Django
In this tutorial I'll demonstrate how to configure a new Django website from scratch to accept one-fourth dimension payments with Stripe.
Need to handle subscription payments? Cheque out Django Stripe Subscriptions.
- Stripe Payment Options
- Projection Setup
- Add Stripe
- Create a Production
- User Flow
- Become Publishable Central
- Create Checkout Session
- Redirect the User Appropriately
- Ostend Payment with Stripe Webhooks
- What'south Next
Stripe Payment Options
There are currently three ways to accept ane-time payments with Stripe:
- Charges API (legacy)
- Stripe Checkout (the focus of this tutorial)
- Payment Intents API (often coupled with Stripe Elements)
Which i should you use?
- Use Checkout if y'all want to get up and running fast. If you lot're familiar with the sometime modal version of Checkout, this is the way to go. Information technology provides a ton of features out-of-the-box, supports multiple languages, and includes an easy path to implementing recurring payments. Virtually importantly, Checkout manages the entire payment process for you, and then you can begin accepting payments without even having to add a single form!
- Use the Payment Intents API If you want a more custom experience for your cease users.
Although y'all can all the same utilise the Charges API, if you're new to Stripe do not apply it since it does non support the latest banking regulations (like SCA). Yous volition see a loftier rate of declines. For more, review the Charges vs. Payment Intents APIs page from the official Stripe docs.
Still using the Charges API? If about of your customers are based in the U.s. or Canada you don't need to drift just withal. Review the Checkout migration guide guide for more info.
Project Setup
Create a new project directory forth with a new Django projection called djangostripe
:
$ mkdir django-stripe-checkout && cd django-stripe-checkout $ python3.x -m venv env $ source env/bin/activate (env)$ pip install django == 3.2.9 (env)$ django-admin startproject djangostripe .
Feel free to swap out virtualenv and Pip for Poetry or Pipenv. For more, review Mod Python Environments.
Side by side, create a new app called payments
:
(env)$ python manage.py startapp payments
Now add together the new app to the INSTALLED_APPS
configuration in settings.py:
# djangostripe/settings.py INSTALLED_APPS = [ 'django.contrib.admin' , 'django.contrib.auth' , 'django.contrib.contenttypes' , 'django.contrib.sessions' , 'django.contrib.messages' , 'django.contrib.staticfiles' , # Local 'payments.apps.PaymentsConfig' , # new ]
Update the project-level urls.py file with the payments
app:
# djangostripe/urls.py from django.contrib import admin from django.urls import path , include # new urlpatterns = [ path ( 'admin/' , admin . site . urls ), path ( '' , include ( 'payments.urls' )), # new ]
Create a urls.py file within the new app, also:
(env)$ bear on payments/urls.py
And so populate it as follows:
# payments/urls.py from django.urls import path from . import views urlpatterns = [ path ( '' , views . HomePageView . as_view (), name = 'home' ), ]
At present add a views.py file:
# payments/views.py from django.views.generic.base of operations import TemplateView class HomePageView ( TemplateView ): template_name = 'dwelling house.html'
And create a dedicated "templates" binder and file for our homepage.
(env)$ mkdir templates (env)$ touch templates/abode.html
Then, add the post-obit HTML:
<!-- templates/home.html --> <!DOCTYPE html> < html > < caput > < meta charset = "utf-8" > < meta name = "viewport" content = "width=device-width, initial-scale=i" > < title >Django + Stripe Checkout</ championship > < link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css" > < script defer src = "https://utilize.fontawesome.com/releases/v5.15.four/js/all.js" ></ script > </ head > < body > < section grade = "section" > < div grade = "container" > < button class = "push button is-chief" id = "submitBtn" >Buy!</ push > </ div > </ section > </ body > </ html >
Make certain to update the settings.py file so Django knows to look for a "templates" folder:
# djangostripe/settings.py TEMPLATES = [ { 'BACKEND' : 'django.template.backends.django.DjangoTemplates' , 'DIRS' : [ 'templates' ], # new ...
Finally run migrate
to sync the database and runserver
to start Django's local web server.
(env)$ python manage.py drift (env)$ python manage.py runserver
That's it! Check out http://localhost:8000/ and y'all'll see the homepage:
Add Stripe
Time for Stripe. Start by installing it:
(env)$ pip install stripe == 2.63.0
Next, register for a Stripe business relationship (if you haven't already done and so) and navigate to the dashboard. Click on "Developers":
Then in the left sidebar click on "API keys":
Each Stripe account has four API keys: 2 for testing and two for production. Each pair has a "secret fundamental" and a "publishable key". Practise not reveal the cloak-and-dagger key to anyone; the publishable primal volition be embedded in the JavaScript on the folio that anyone can meet.
Currently the toggle for "Viewing test data" in the upper correct indicates that we're using the test keys now. That'southward what we want.
At the bottom of your settings.py file, add the following ii lines including your ain test hugger-mugger and test publishable keys. Brand sure to include the ''
characters around the actual keys.
# djangostripe/settings.py STRIPE_PUBLISHABLE_KEY = '<your test publishable key hither>' STRIPE_SECRET_KEY = '<your test surreptitious key here>'
Finally, you'll demand to specify an "Account name" within your "Account settings" at https://dashboard.stripe.com/settings/business relationship:
Create a Product
Adjacent, we need to create a product to sell.
Click "Products" and and then "Add production":
Add a product name, enter a price, and select "Once":
Click "Salvage production".
User Menstruation
After the user clicks the purchase button nosotros demand to do the following:
-
Get Publishable Key
- Transport an XHR request from the client to the server requesting the publishable central
- Answer with the cardinal
- Apply the key to create a new instance of Stripe.js
-
Create Checkout Session
- Send some other XHR request to the server requesting a new Checkout Session ID
- Generate a new Checkout Session and ship back the ID
- Redirect to the checkout folio for the user to terminate their purchase
-
Redirect the User Appropriately
- Redirect to a success page after a successful payment
- Redirect to a cancellation page after a cancelled payment
-
Confirm Payment with Stripe Webhooks
- Ready the webhook endpoint
- Test the endpoint using the Stripe CLI
- Register the endpoint with Stripe
Get Publishable Fundamental
JavaScript Static File
Let's start by creating a new static file to agree all of our JavaScript:
(env)$ mkdir static (env)$ touch static/main.js
Add together a quick sanity check to the new main.js file:
// static/main.js console . log ( "Sanity check!" );
So update the settings.py file and then Django knows where to find static files:
# djangostripe/settings.py STATIC_URL = '/static/' # for django >= 3.i STATICFILES_DIRS = [ BASE_DIR / 'static' ] # new # for django < three.i # STATICFILES_DIRS = [bone.path.join(BASE_DIR, 'static')] # new
Add the static template tag along with the new script tag inside the HTML template:
<!-- templates/dwelling.html --> {% load static %} <!-- new --> <!DOCTYPE html> < html > < head > < meta charset = "utf-8" > < meta proper noun = "viewport" content = "width=device-width, initial-calibration=ane" > < title >Django + Stripe Checkout</ title > < link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css" > < script src = "{% static 'main.js' %}" ></ script > <!-- new --> < script defer src = "https://apply.fontawesome.com/releases/v5.fifteen.4/js/all.js" ></ script > </ head > < body > < section class = "section" > < div class = "container" > < push button class = "button is-primary" id = "submitBtn" >Purchase!</ button > </ div > </ section > </ trunk > </ html >
Run the development server again. Navigate to http://localhost:8000/, and open the JavaScript console. You should see the sanity check:
View
Next, add a new view to payments/views.py to handle the XHR request:
# payments/views.py from django.conf import settings # new from django.http.response import JsonResponse # new from django.views.decorators.csrf import csrf_exempt # new from django.views.generic.base import TemplateView class HomePageView ( TemplateView ): template_name = 'home.html' # new @csrf_exempt def stripe_config ( asking ): if asking . method == 'Go' : stripe_config = { 'publicKey' : settings . STRIPE_PUBLISHABLE_KEY } return JsonResponse ( stripe_config , rubber = False )
Add a URL as well:
# payments/urls.py from django.urls import path from . import views urlpatterns = [ path ( '' , views . HomePageView . as_view (), name = 'home' ), path ( 'config/' , views . stripe_config ), # new ]
XHR Asking
Adjacent, use the Fetch API to brand an XHR (XMLHttpRequest) request to the new /config/
endpoint in static/main.js:
// static/main.js console . log ( "Sanity check!" ); // new // Get Stripe publishable primal fetch ( "/config/" ) . then (( result ) => { return outcome . json (); }) . then (( data ) => { // Initialize Stripe.js const stripe = Stripe ( information . publicKey ); });
The response from a fetch
request is a ReadableStream. consequence.json()
returns a promise, which nosotros resolved to a JavaScript object -- due east.g., data
. Nosotros and then used dot-notation to access the publicKey
in social club to obtain the publishable key.
Include Stripe.js in templates/home.html like and so:
<!-- templates/home.html --> {% load static %} <!DOCTYPE html> < html > < head > < meta charset = "utf-8" > < meta proper noun = "viewport" content = "width=device-width, initial-scale=1" > < title >Django + Stripe Checkout</ title > < link rel = "stylesheet" href = "https://cdn.jsdelivr.cyberspace/npm/[electronic mail protected]/css/bulma.min.css" > < script src = "https://js.stripe.com/v3/" ></ script > <!-- new --> < script src = "{% static 'main.js' %}" ></ script > < script defer src = "https://use.fontawesome.com/releases/v5.15.4/js/all.js" ></ script > </ head > < body > < section class = "section" > < div form = "container" > < button class = "push button is-primary" id = "submitBtn" >Purchase!</ push > </ div > </ department > </ body > </ html >
Now, afterwards the page load, a call will be made to /config/
, which will reply with the Stripe publishable primal. We'll so employ this key to create a new instance of Stripe.js.
Flow:
-
Get Publishable Central-
Send an XHR request from the customer to the server requesting the publishable key -
Respond with the central -
Employ the key to create a new instance of Stripe.js
-
-
Create Checkout Session
- Send another XHR request to the server requesting a new Checkout Session ID
- Generate a new Checkout Session and send back the ID
- Redirect to the checkout page for the user to cease their buy
-
Redirect the User Appropriately
- Redirect to a success page after a successful payment
- Redirect to a cancellation folio later on a cancelled payment
-
Confirm Payment with Stripe Webhooks
- Set up the webhook endpoint
- Test the endpoint using the Stripe CLI
- Annals the endpoint with Stripe
Create Checkout Session
Moving on, we need to attach an event handler to the button's click event which will send another XHR request to the server to generate a new Checkout Session ID.
View
First, add the new view:
# payments/views.py @csrf_exempt def create_checkout_session ( asking ): if asking . method == 'GET' : domain_url = 'http://localhost:8000/' stripe . api_key = settings . STRIPE_SECRET_KEY try : # Create new Checkout Session for the order # Other optional params include: # [billing_address_collection] - to display billing address details on the page # [customer] - if yous have an existing Stripe Customer ID # [payment_intent_data] - capture the payment afterward # [customer_email] - prefill the e-mail input in the form # For full details encounter https://stripe.com/docs/api/checkout/sessions/create # ?session_id={CHECKOUT_SESSION_ID} means the redirect will accept the session ID fix equally a query param checkout_session = stripe . checkout . Session . create ( success_url = domain_url + 'success?session_id= {CHECKOUT_SESSION_ID} ' , cancel_url = domain_url + 'cancelled/' , payment_method_types = [ 'card' ], manner = 'payment' , line_items = [ { 'proper name' : 'T-shirt' , 'quantity' : 1 , 'currency' : 'usd' , 'amount' : '2000' , } ] ) return JsonResponse ({ 'sessionId' : checkout_session [ 'id' ]}) except Exception as e : return JsonResponse ({ 'fault' : str ( e )})
Here, if the asking method is GET
, we divers a domain_url
, assigned the Stripe clandestine key to stripe.api_key
(so information technology volition be sent automatically when we make a asking to create a new Checkout Session), created the Checkout Session, and sent the ID dorsum in the response. Take note of the success_url
and cancel_url
. The user will exist redirected dorsum to those URLs in the event of a successful payment or cancellation, respectively. We'll set those views up shortly.
Don't forget the import:
Add the URL:
# payments/urls.py from django.urls import path from . import views urlpatterns = [ path ( '' , views . HomePageView . as_view (), name = 'abode' ), path ( 'config/' , views . stripe_config ), path ( 'create-checkout-session/' , views . create_checkout_session ), # new ]
XHR Request
Add the result handler and subsequent XHR request to static/main.js:
// static/principal.js console . log ( "Sanity check!" ); // Get Stripe publishable key fetch ( "/config/" ) . so (( result ) => { return outcome . json (); }) . then (( data ) => { // Initialize Stripe.js const stripe = Stripe ( data . publicKey ); // new // Outcome handler document . querySelector ( "#submitBtn" ). addEventListener ( "click" , () => { // Get Checkout Session ID fetch ( "/create-checkout-session/" ) . then (( result ) => { return result . json (); }) . and so (( data ) => { console . log ( data ); // Redirect to Stripe Checkout render stripe . redirectToCheckout ({ sessionId : data . sessionId }) }) . then (( res ) => { panel . log ( res ); }); }); });
Here, afterwards resolving the result.json()
promise, we chosen redirectToCheckout with the Checkout Session ID from the resolved promise.
Navigate to http://localhost:8000/. On push click you should be redirected to an instance of Stripe Checkout (a Stripe-hosted page to securely collect payment data) with the T-shirt product data:
We can test the form past using ane of the several examination carte numbers that Stripe provides. Let's use 4242 4242 4242 4242
. Make sure the expiration appointment is in the future. Add whatsoever 3 numbers for the CVC and any 5 numbers for the postal lawmaking. Enter any e-mail accost and name. If all goes well, the payment should be processed, but the redirect will fail since nosotros have not prepare the /success/
URL however.
Flow:
-
Get Publishable Central-
Ship an XHR request from the client to the server requesting the publishable central -
Respond with the key -
Use the primal to create a new instance of Stripe.js
-
-
Create Checkout Session-
Ship another XHR asking to the server requesting a new Checkout Session ID -
Generate a new Checkout Session and ship back the ID -
Redirect to the checkout page for the user to finish their purchase
-
-
Redirect the User Appropriately
- Redirect to a success page after a successful payment
- Redirect to a counterfoil page after a cancelled payment
-
Confirm Payment with Stripe Webhooks
- Set the webhook endpoint
- Test the endpoint using the Stripe CLI
- Annals the endpoint with Stripe
Redirect the User Appropriately
Finally, allow'southward wire up the templates, views, and URLs for handling the success and cancellation redirects.
Success template:
<!-- templates/success.html --> <!DOCTYPE html> < html > < head > < meta charset = "utf-viii" > < meta proper noun = "viewport" content = "width=device-width, initial-scale=one" > < championship >Django + Stripe Checkout</ title > < link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/[e-mail protected]/css/bulma.min.css" > < script defer src = "https://use.fontawesome.com/releases/v5.15.4/js/all.js" ></ script > </ head > < trunk > < section class = "department" > < div form = "container" > < p >Your payment succeeded.</ p > </ div > </ section > </ trunk > </ html >
Cancelled template:
<!-- templates/cancelled.html --> <!DOCTYPE html> < html > < head > < meta charset = "utf-8" > < meta name = "viewport" content = "width=device-width, initial-calibration=ane" > < title >Django + Stripe Checkout</ title > < link rel = "stylesheet" href = "https://cdn.jsdelivr.net/npm/[electronic mail protected]/css/bulma.min.css" > < script defer src = "https://utilise.fontawesome.com/releases/v5.15.4/js/all.js" ></ script > </ head > < torso > < section form = "section" > < div grade = "container" > < p >Your payment was cancelled.</ p > </ div > </ department > </ trunk > </ html >
Views:
# payments/views.py class SuccessView ( TemplateView ): template_name = 'success.html' course CancelledView ( TemplateView ): template_name = 'cancelled.html'
URLs:
# payments/urls.py from django.urls import path from . import views urlpatterns = [ path ( '' , views . HomePageView . as_view (), name = 'home' ), path ( 'config/' , views . stripe_config ), path ( 'create-checkout-session/' , views . create_checkout_session ), path ( 'success/' , views . SuccessView . as_view ()), # new path ( 'cancelled/' , views . CancelledView . as_view ()), # new ]
Ok, refresh the spider web page at http://localhost:8000/. Click on the payment button and utilize the credit menu number 4242 4242 4242 4242
again along with the rest of the dummy info. Submit the payment. You should be redirected dorsum to http://localhost:8000/success/.
To ostend a charge was actually made, go back to the Stripe dashboard under "Payments":
To review, nosotros used the secret key to create a unique Checkout Session ID on the server. This ID was then used to create a Checkout case, which the end user gets redirected to after clicking the payment push button. After the charge occurred, they are and so redirected back to the success folio.
Flow:
-
Get Publishable Key-
Transport an XHR request from the client to the server requesting the publishable key -
Respond with the cardinal -
Use the central to create a new instance of Stripe.js
-
-
Create Checkout Session-
Send another XHR asking to the server requesting a new Checkout Session ID -
Generate a new Checkout Session and send back the ID -
Redirect to the checkout page for the user to terminate their purchase
-
-
Redirect the User Appropriately-
Redirect to a success page subsequently a successful payment -
Redirect to a counterfoil folio after a cancelled payment
-
-
Ostend Payment with Stripe Webhooks
- Set up the webhook endpoint
- Test the endpoint using the Stripe CLI
- Annals the endpoint with Stripe
Confirm Payment with Stripe Webhooks
Our app works well at this point, but we still can't programmatically confirm payments and possibly run some code if a payment was successful. We already redirect the user to the success page after they bank check out, but we tin't rely on that page alone since payment confirmation happens asynchronously.
There are ii types of events in Stripe and programming in general: Synchronous events, which take an immediate event and results (e.g., creating a customer), and asynchronous events, which don't have an immediate event (e.g., confirming payments). Considering payment confirmation is done asynchronously, the user might get redirected to the success page before their payment is confirmed and before we receive their funds.
1 of the easiest ways to become notified when the payment goes through is to use a callback or so-called Stripe webhook. Nosotros'll need to create a simple endpoint in our application, which Stripe volition call whenever an event occurs (i.eastward., when a user buys a T-shirt). By using webhooks, we tin can be absolutely sure the payment went through successfully.
In guild to employ webhooks, we need to:
- Set upwards the webhook endpoint
- Test the endpoint using the Stripe CLI
- Register the endpoint with Stripe
This section was written by Nik Tomazic.
Endpoint
Create a new view called stripe_webhook
which prints a message every time a payment goes through successfully:
# payments/views.py @csrf_exempt def stripe_webhook ( request ): stripe . api_key = settings . STRIPE_SECRET_KEY endpoint_secret = settings . STRIPE_ENDPOINT_SECRET payload = request . trunk sig_header = asking . META [ 'HTTP_STRIPE_SIGNATURE' ] outcome = None attempt : result = stripe . Webhook . construct_event ( payload , sig_header , endpoint_secret ) except ValueError every bit e : # Invalid payload return HttpResponse ( status = 400 ) except stripe . error . SignatureVerificationError as eastward : # Invalid signature render HttpResponse ( status = 400 ) # Handle the checkout.session.completed event if event [ 'type' ] == 'checkout.session.completed' : print ( "Payment was successful." ) # TODO: run some custom code hither return HttpResponse ( status = 200 )
stripe_webhook
now serves as our webhook endpoint. Hither, we're only looking for checkout.session.completed
events which are called whenever a checkout is successful, but y'all can employ the same pattern for other Stripe events.
Brand sure to add together the HttpResponse
import to the tiptop:
from django.http.response import JsonResponse , HttpResponse
The but affair left to exercise to brand the endpoint accessible is to annals it in urls.py:
# payments/urls.py from django.urls import path from . import views urlpatterns = [ path ( '' , views . HomePageView . as_view (), name = 'home' ), path ( 'config/' , views . stripe_config ), path ( 'create-checkout-session/' , views . create_checkout_session ), path ( 'success/' , views . SuccessView . as_view ()), path ( 'cancelled/' , views . CancelledView . as_view ()), path ( 'webhook/' , views . stripe_webhook ), # new ]
Testing the webhook
We'll use the Stripe CLI to examination the webhook.
In one case downloaded and installed, run the post-obit command in a new terminal window to log in to your Stripe account:
This command should generate a pairing code:
Your pairing lawmaking is: peach-loves-classy-cozy This pairing code verifies your authentication with Stripe. Printing Enter to open up the browser (^C to quit)
By pressing Enter, the CLI volition open up your default web browser and ask for permission to access your account information. Go alee and allow access. Back in your concluding, you should meet something similar to:
> Done! The Stripe CLI is configured for Django Test with account id acct_<ACCOUNT_ID> Delight note: this key will elapse after 90 days, at which point you'll need to re-authenticate.
Next, nosotros can start listening to Stripe events and forward them to our endpoint using the following command:
$ stripe listen --forrad-to localhost:8000/webhook/
This volition also generate a webhook signing surreptitious:
> Ready! Your webhook signing hush-hush is whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (^C to quit)
In order to initialize the endpoint, add the hush-hush to the settings.py file:
# djangostripe/settings.py STRIPE_ENDPOINT_SECRET = '<your webhook signing secret here>'
Stripe volition now forrard events to our endpoint. To examination, run another examination payment through with 4242 4242 4242 4242
. In your terminal, you should see the Payment was successful.
bulletin.
In one case washed, stop the stripe listen --frontward-to localhost:8000/webhook/
process.
If you'd like to identify the user making the purchase, you tin can use the client_reference_id to attach some sort of user identifier to the Stripe session.
For instance:
# payments/views.py @csrf_exempt def create_checkout_session ( request ): if request . method == 'Become' : domain_url = 'http://localhost:8000/' stripe . api_key = settings . STRIPE_SECRET_KEY endeavor : checkout_session = stripe . checkout . Session . create ( # new client_reference_id = request . user . id if request . user . is_authenticated else None , success_url = domain_url + 'success?session_id= {CHECKOUT_SESSION_ID} ' , cancel_url = domain_url + 'cancelled/' , payment_method_types = [ 'card' ], mode = 'payment' , line_items = [ { 'proper name' : 'T-shirt' , 'quantity' : 1 , 'currency' : 'usd' , 'amount' : '2000' , } ] ) render JsonResponse ({ 'sessionId' : checkout_session [ 'id' ]}) except Exception as east : return JsonResponse ({ 'error' : str ( e )})
Annals the endpoint
Finally, after deploying your app, you can register the endpoint in the Stripe dashboard, under Developers > Webhooks. This volition generate a webhook signing secret for use in your production app.
For example:
Flow:
-
Get Publishable Key-
Ship an XHR request from the client to the server requesting the publishable key -
Reply with the central -
Use the key to create a new example of Stripe.js
-
-
Create Checkout Session-
Send another XHR request to the server requesting a new Checkout Session ID -
Generate a new Checkout Session and send back the ID -
Redirect to the checkout page for the user to finish their purchase
-
-
Redirect the User Appropriately-
Redirect to a success page later on a successful payment -
Redirect to a cancellation page afterward a cancelled payment
-
-
Confirm Payment with Stripe Webhooks-
Prepare upward the webhook endpoint -
Examination the endpoint using the Stripe CLI -
Register the endpoint with Stripe
-
What'southward Side by side
On a live website it's required to accept HTTPS so your connection is secure. Also while we hardcoded our API keys and webhook signing hole-and-corner for simplicity, these should really be stored in surroundings variables. You'll probably want to store the domain_url
as an environment variable as well.
Take hold of the code from the django-stripe-checkout repo on GitHub.
Source: https://testdriven.io/blog/django-stripe-tutorial/
0 Response to "How to Not Charge Credit Card Again on Refresh Django"
Post a Comment