DEV Community

Husnain Mustafa
Husnain Mustafa

Posted on • Edited on

How to integrate Paypal with NextJs

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.

  1. @paypal/react-paypal-js
  2. @paypal/checkout-server-sdk

@paypal/react-paypal-js

For the front-end, where we need Paypal Payment Buttons to be shown and the functionality of:

  1. Creating an order
  2. 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:

  1. Click on 'Sandbox Accounts':
    Image description

  2. Scroll down and view the details of business account:
    Image description

  3. 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.
    Image description

.env:



PAYPAL_CLIENT_ID = "<your cliend id>"
PAYPAL_CLIENT_SECRET = "<your client secret>"
NEXT_PUBLIC_PAYPAL_CLIENT_ID = "<your client id>"


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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"})
  }

}


Enter fullscreen mode Exit fullscreen mode

/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}})
}


Enter fullscreen mode Exit fullscreen mode

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>

   ...
   ...
)


Enter fullscreen mode Exit fullscreen mode

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
    }
  }

   ...
   ...


Enter fullscreen mode Exit fullscreen mode

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')
    }
  }

   ...
   ...


Enter fullscreen mode Exit fullscreen mode

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:

Image description

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:

  1. Sandbox Accounts:
    Image description

  2. Under Accounts, select Personal Account:
    Image description

  3. Finally use the credentials to make the payment:
    Image description


That's all
If you got any questions, feel free to ask.

Top comments (10)

Collapse
 
matheusdamiao profile image
Matheus Damião

That was absolutely great, Husnain!
With your explanation I could easily set up PayPal to work in a Next13 project.
Thanks for your post!

Collapse
 
tajalde49174992 profile image
TAJALDEEN

this is super useful but I find it hard to connect my redux with it

Collapse
 
husnain profile image
Husnain Mustafa

I know its really late, but do you still need help?

Collapse
 
mrstevedev profile image
Steve Pulido

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.

Collapse
 
eaclegend profile image
EAClegend

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

Collapse
 
husnain profile image
Husnain Mustafa

Do you still need help?

Collapse
 
mitch1009 profile image
Mitch Chimwemwe Chanza • Edited

its coming from this file:

/utils/paypal/index.js:
Enter fullscreen mode Exit fullscreen mode

remember it was exported as client, he made a mistake on the import

Collapse
 
ibnu_rasikh profile image
Ibnu Rasikh

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.

Collapse
 
samuelnihoul profile image
Samuel Nihoul

The "order" and "wallet" objects that you return in your respective API responses are not declared. What did you mean to do?

Collapse
 
jhayboy profile image
justice

hello, please is there a video example of this