This content originally appeared on flaviocopes.com and was authored by flaviocopes.com
This post is part of a new series where we build a clone of Airbnb with Next.js. See the first post here.
- Part 1: Let’s start by installing Next.js
- Part 2: Build the list of houses
- Part 3: Build the house detail view
- Part 4: CSS and navigation bar
- Part 5: Start with the date picker
- Part 6: Add the sidebar
- Part 7: Add react-day-picker
- Part 8: Add the calendar to the page
- Part 9: Configure the DayPickerInput component
- Part 10: Sync the start and end dates
- Part 11: Show the price for the chosen dates
- Part 12: Login and signup forms
- Part 13: Activate the modal
- Part 14: Send registration data to the server
- Part 15: Add postgres
- Part 16: Implement model and DB connection
- Part 17: Create a session token
- Part 18: Implement login
- Part 19: Determine if we are logged in
- Part 20: Change state after we login
- Part 21: Log in after registration
- Part 22: Create the models and move data to the db
- Part 23: Use the database instead of the file
- Part 24: Handle bookings
- Part 25: Handle booked dates
- Part 26: Prevent booking if already booked
In this lesson we’re going to add payment integration using Stripe.
Stripe has a product called Connect that allows to create a marketplace where people get money directly from customers.
That looks something worth exploring, but not in our context. In our app we’ll do like Airbnb does: we collect the payment ourselves, and we’ll distribute the earning in the backend, once every month or so.
This is not something we’ll implement
What we’ll do is, we’ll collect the payment for the booking. That’s it.
We do this using Stripe Checkout.
Sign up to Stripe if you don’t have an account yet.
We have 2 different types of Checkout.
One is the client-only integration, the other is the client & server integration.
From the Stripe docs:
With the client-only integration, you define your products directly in the Stripe Dashboard and reference them by ID on the client side. This approach makes it possible to integrate Checkout into your website without needing any server-side code. It is best suited for simple integrations that don’t need dynamic pricing.
It’s clear that this is not enough. We must define each product separately, as a “booking”, because our prices vary depending on the house, and on the duration of the stay.
Also client-only integration does not support placing a hold on a card before charging it. This could be interesting: you don’t charge a card immediately, but just after the person stayed at the house. Or checks in.
But in our case, to simplify the workflow, we’ll bill directly at the booking time.
So, the work on the following workflow:
- we create a checkout session server-side, when the user clicks “Reserve now”, and we provide a success URL that will be where people are sent, on our site, after the payment was successful
- we store the booking, set as paid=false
- we redirect to checkout on Stripe’s webpage
- as soon as the payment is done, Stripe redirects to the success URL, where we’ll later set up the lists of booked places of the user, along with the dates
- meanwhile, Stripe will send us a confirmation via Webhook to set the booking as paid
As soon as a person clicks “Reserve now”, we’ll add the reservation into the database, along with the Stripe session id.
This clears the possibility that a person books meanwhile another person books, so the dates are immediately marked as unavailable on the calendar.
We’ll store it with a new paid
field set to false.
As soon as Stripe calls our Webhook to inform of the payment, we’ll set the reservation to paid
= true
.
Let’s do it!
I start by adding this new paid
field to the Booking model, and a new sessionId
string, too:
Booking.init(
{
//...
paid: {
type: Sequelize.DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
sessionId: { type: Sequelize.DataTypes.STRING }
},
{
//...
}
)
Remember to call Booking.sync({ alter: true })
to sync the database table. You can add this line at the end of the model.js
file.
Next we install the stripe
npm package for server-side usage:
npm install stripe
and we need to add the Stripe frontend code:
<script src="https://js.stripe.com/v3/"></script>
How? In the components/Layout.js
file, which is what every page component includes, we’re going to add this to the top:
import Head from 'next/head'
and then, when we return the JSX:
return (
<div>
<Head>
<script src='https://js.stripe.com/v3/'></script>
</Head>
This will put this script tag in the page <head>
tag.
Now I’m going to modify the process we use to reserve the house, a little bit.
Before actually POSTing to /api/reserve
, I’m going to POST to a new endpoint we’ll create that listens on /api/stripe/session
and wait for the end result.
In that endpoint we’ll set up the payment, with the amount and details, and Stripe will give us a sessionId
for the payment, which we’ll use in the frontend.
Before going on, we must go on the Stripe dashboard and gather the API secret key and the public key. The first must never be exposed to the frontend, while the second will be used in code that can be seen by users (hence the name public).
In my case they look like sk_SOMETHING
and pk_SOMETHING
(fill your actual keys!)
We add them to the .env.local
in the project root folder:
STRIPE_SECRET_KEY=sk_SOMETHING
STRIPE_PUBLIC_KEY=pk_SOMETHING
BASE_URL=http://localhost:3000
Create a pages/api/stripe/session.js
endpoint:
import dotenv from 'dotenv'
dotenv.config()
export default async (req, res) => {
if (req.method !== 'POST') {
res.status(405).end() //Method Not Allowed
return
}
const amount = req.body.amount
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY)
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [
{
name: 'Booking house on Airbnb clone',
amount: amount * 100,
currency: 'usd',
quantity: 1
}
],
success_url: process.env.BASE_URL + '/bookings',
cancel_url: process.env.BASE_URL + '/bookings'
})
res.writeHead(200, {
'Content-Type': 'application/json'
})
res.end(
JSON.stringify({
status: 'success',
sessionId: session.id,
stripePublicKey: process.env.STRIPE_PUBLIC_KEY
})
)
}
We get the amount
value from the POST request body.
Once we have that, we can require the stripe
library and create a session. We pass an object that defines the payment, which includes the payment accepted, the item purchased and 2 lines that set the URLs of the pages to redirect to, after the purchase is done or cancelled.
Finally, we return the session id
value, and also the process.env.STRIPE_PUBLIC_KEY
, because the frontend can’t access it directly and we’ll need it later to invoke the Stripe checkout.
Now we can call this endpoint in pages/houses/[id].js
before we call /api/reserve
:
const sessionResponse = await axios.post('/api/stripe/session', {
amount: house.price * numberOfNightsBetweenDates
})
if (sessionResponse.data.status === 'error') {
alert(sessionResponse.data.message)
return
}
const sessionId = sessionResponse.data.sessionId
const stripePublicKey = sessionResponse.data.stripePublicKey
Once this is done, we pass sessionId
to the api/reserve
call, because we want to store it in the bookings table.
Why? Because when the Stripe payment confirmation webhook will be sent to us, that’s the way we can link the payment with the booking.
const reserveResponse = await axios.post('/api/reserve', {
houseId: house.id,
startDate,
endDate,
sessionId
})
In the pages/api/reserve.js
, in the reserve endpoint, we now need to gather this new sessionId
field and then we pass it to Booking.create()
:
import { User, Booking } from '../../model.js'
export default async (req, res) => {
//...
User.findOne({ where: { session_token: user_session_token } }).then(
(user) => {
Booking.create({
houseId: req.body.houseId,
userId: user.id,
startDate: req.body.startDate,
endDate: req.body.endDate,
sessionId: req.body.sessionId
}).then(() => {
res.writeHead(200, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status: 'success', message: 'ok' }))
})
}
)
}
Finally in pages/houses/[id].js
, I can redirect the user to Stripe checkout, with this code:
pages/houses/[id].js
const stripe = Stripe(stripePublicKey)
const { error } = await stripe.redirectToCheckout({
sessionId
})
This all happens transparently to the user. They are immediately sent to the Stripe checkout:
Stripe has this great testing tool that lets you add a credit card numbered 4242 4242 4242 4242
, you add 4
or 2
to the expiration date and code, and it’s considered valid.
The success URL route
Remember? In the /api/stripe/session
API endpoint, we set
success_url: process.env.BASE_URL + '/bookings',
This is a page on our site, where people will be redirected to when the Stripe payment is successful.
Let’s create this page. Create a pages/bookings.js
file, and add this content to it:
import Layout from '../components/Layout'
const Bookings = () => {
return <Layout content={<p>TODO</p>} />
}
export default Bookings
The app should respond to http://localhost:3000/bookings
with:
This will later list our bookings.
In the next lesson we’ll handle webhooks.
This content originally appeared on flaviocopes.com and was authored by flaviocopes.com

flaviocopes.com | Sciencx (2021-12-27T05:00:00+00:00) Airbnb clone, adding Stripe for payments. Retrieved from https://www.scien.cx/2021/12/27/airbnb-clone-adding-stripe-for-payments/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.