Payment integration is the most required thing when we are heading to develop a an eCommerce website. And Paypal is one of the most common platforms which allow us to make our transactions smoothly. So let's learn how we can integrate Paypal with our NextJs eCommerce App.
Installing the required packages
First, we need some packages which will help us a lot in the integration of Paypal and we would just need to use those according to our need.
@paypal/react-paypal-js
For the front-end, where we need Paypal Payment Buttons to be shown and the functionality of:
- Creating an order
- Capturing an order
This package will help us in those requriements
@paypal/checkout-server-sdk
For the back-end, where we actually need to call the paypal Api, this package provides us classes which are helpful in that.
Getting Credentials and Setting Environment
Head over to Paypal Developer and create and account.
Once you are on the dashboard:
Under Rest Api Apps, Copy the Cliend ID and Secret and store those in your app in .env file
If you are unable to find these things under Rest Api Apps, you need to create an app by clicking the button, Create App. When popup is shown, select Merchant account.
.env:
PAYPAL_CLIENT_ID = "<your cliend id>"
PAYPAL_CLIENT_SECRET = "<your client secret>"
NEXT_PUBLIC_PAYPAL_CLIENT_ID = "<your client id>"
Note: We are using sandbox account for this tutorial purpose. You can easily switch to live account by replacing the sandbox credentials, cliend ID and Secret, with Live one.
Back-end Integration
We need to define api endpoints through which we will create and capture order from the front-end.
We need to define two endpoints, one for creating an order, other for capturing an order.
But before that, we need to make a client with our required configuration. This client will help us call Paypal Api to create and capture orders. So make a file in
/utils/paypal/index.js:
import checkoutNodeJssdk from '@paypal/checkout-server-sdk'
const configureEnvironment = function () {
const clientId = process.env.PAYPAL_CLIENT_ID
const clientSecret = process.env.PAYPAL_CLIENT_SECRET
return process.env.NODE_ENV === 'production'
? new checkoutNodeJssdk.core.LiveEnvironment(clientId, clientSecret)
: new checkoutNodeJssdk.core.SandboxEnvironment(clientId, clientSecret)
}
const client = function () {
return new checkoutNodeJssdk.core.PayPalHttpClient(configureEnvironment())
}
export default client
Now we have configured our client to use the client ID and client secret that we defined earlier in .env file. These client ID and client secret are required by Paypal to create and capture order.
Now back to our api endpoints.
/api/paypal/createorder.js:
import client from 'backend/paypal'
import paypal from '@paypal/checkout-server-sdk'
...
...
export default async function Handler(req, res) {
if(req.method != "POST")
return res.status(404).json({success: false, message: "Not Found"})
if(!req.body.order_price || !req.body.user_id)
return res.status(400).json({success: false, message: "Please Provide order_price And User ID"})
try{
const PaypalClient = client()
//This code is lifted from https://github.com/paypal/Checkout-NodeJS-SDK
const request = new paypal.orders.OrdersCreateRequest()
request.headers['prefer'] = 'return=representation'
request.requestBody({
intent: 'CAPTURE',
purchase_units: [
{
amount: {
currency_code: 'USD',
value: req.body.order_price+"",
},
},
],
})
const response = await PaypalClient.execute(request)
if (response.statusCode !== 201) {
console.log("RES: ", response)
return res.status(500).json({success: false, message: "Some Error Occured at backend"})
}
...
// Your Custom Code for doing something with order
// Usually Store an order in the database like MongoDB
...
res.status(200).json({success: true, data: {order}})
}
catch(err){
console.log("Err at Create Order: ", err)
return res.status(500).json({success: false, message: "Could Not Found the user"})
}
}
/api/paypal/captureorder.js
import client from 'backend/paypal'
import paypal from '@paypal/checkout-server-sdk'
...
export default async function Handler(req, res) {
if(req.method != "POST")
return res.status(404).json({success: false, message: "Not Found"})
if(!req.body.orderID)
return res.status(400).json({success: false, message: "Please Provide Order ID"})
//Capture order to complete payment
const { orderID } = req.body
const PaypalClient = client()
const request = new paypal.orders.OrdersCaptureRequest(orderID)
request.requestBody({})
const response = await PaypalClient.execute(request)
if (!response) {
return res.status(500).json({success: false, message: "Some Error Occured at backend"})
}
...
// Your Custom Code to Update Order Status
// And Other stuff that is related to that order, like wallet
// Here I am updateing the wallet and sending it back to frontend to update it on frontend
...
res.status(200).json({success: true, data: {wallet}})
}
Front-end Integration
Now open a component where you need to show the Paypal Payment Buttons.
Import the following:
import { PayPalScriptProvider, PayPalButtons } from '@paypal/react-paypal-js'
Move over to the 'return' statement and add the Paypal Payment Buttons by:
return (
...
...
<PayPalScriptProvider
options={{
'client-id': process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID,
currency: 'USD',
intent: 'capture'
}}
>
<PayPalButtons
style={{
color: 'gold',
shape: 'rect',
label: 'pay',
height: 50
}}
createOrder={async (data, actions) => {
let order_id = await paypalCreateOrder()
return order_id + ''
}}
onApprove={async (data, actions) => {
let response = await paypalCaptureOrder(data.orderID)
if (response) return true
}}
/>
</PayPalScriptProvider>
...
...
)
You can alter the currency and styles of the buttons.
As you can see in the code, we are calling 'paypalCreateOrder' in 'createOrder' and 'paypalCaptureOrder' in 'onApprove'.
createOrder is where we need to define a function which must return an order id of the generated order in Paypal.
For that we are defining another function paypalCaptureOrder which will call our backend api and return order id of generated order.
onApprove is where we define a function which is called when our payment is approved. Here we will call our backend api to capture the order and to update the wallet and order status.
paypalCreateOrder:
...
...
const paypalCreateOrder = async () => {
try {
let response = await axios.post('/api/paypal/createorder', {
user_id: store.getState().auth.user._id,
order_price: amountRef.current.value
})
return response.data.data.order.order_id
} catch (err) {
// Your custom code to show an error like showing a toast:
// toast.error('Some Error Occured')
return null
}
}
...
...
paypalCreateOrder is the function which will call our backend api which is responsible for creating an order in Paypal.
paypalCaptureOrder:
...
...
const paypalCaptureOrder = async orderID => {
try {
let response = await axios.post('/api/paypal/captureorder', {
orderID
})
if (response.data.success) {
// Order is successful
// Your custom code
// Like showing a success toast:
// toast.success('Amount Added to Wallet')
// And/Or Adding Balance to Redux Wallet
// dispatch(setWalletBalance({ balance: response.data.data.wallet.balance }))
} catch (err) {
// Order is not successful
// Your custom code
// Like showing an error toast
// toast.error('Some Error Occured')
}
}
...
...
paypalCaptureOrder is the function which will call our backend api which is responsible for capturing an order, i.e fulfilling an order, in Paypal.
Our Buttons will look like this:
Note: Don't forget to replace the back-end endpoints with your back-end api endpoints in these two functions.
Testing
Now you can test the paypal integration with sandbox personal account's email and password which you will find in:
That's all
If you got any questions, feel free to ask.
Top comments (10)
That was absolutely great, Husnain!
With your explanation I could easily set up PayPal to work in a Next13 project.
Thanks for your post!
this is super useful but I find it hard to connect my redux with it
I know its really late, but do you still need help?
This is not working for me. The buttons do not render on the page.
I was using ** react-paypal-button-v2 ** for quite a long time but out of nowhere it has suddenly stopped working, something about 403 Forbidden on capture.
I tried to implement this and it also is not working but just not rendering the button on the page.
I have a question regarding the backend/paypal client import, I am unsure what I should put there or what I should be doing for that
Do you still need help?
its coming from this file:
remember it was exported as client, he made a mistake on the import
not work, mine doesn't even render. your instruction unclear, so many miss writing/typo, makes me confused a lot. doesn't even tell me this is for the app router or pages router, what next js version do you use, even doesn't tell this is for typescript or javascript, i get so many red lines, if you think it will make the article too long of less effective, please at least give us the full code in your repo, so we can manually check the details in your package.json or anything.
The "order" and "wallet" objects that you return in your respective API responses are not declared. What did you mean to do?
hello, please is there a video example of this