skip to Main Content

I’m encountering an issue with the React PayPal Buttons integration. When I change the quantity of products directly in the cart, the products are updating in the local storage and in the cart, but the changes are not reflected in the PaypalButtons component props createOrder and onApprove. As a result, the total amount in the PayPal standard checkout is incorrect, and can’t put the right quantity of products in my database.

Here’s the relevant code :

'use client'

import { PayPalButtons, PayPalScriptProvider } from '@paypal/react-paypal-js'
import { createPaypalOrder, handlePaypalCheckout } from '../../_actions/paypal'
import { useRouter } from 'next/navigation'
import { useContext } from 'react'
import { CartContext } from '@/app/_context/CartContext'
import { toast } from '../ui/use-toast'
import { useSession } from 'next-auth/react'
export default function PaypalButton({ total, uniqueProducts }) {
  console.log('🚀 ~ PaypalButton ~ uniqueProducts:', uniqueProducts)
  console.log('🚀 ~ total:', total)
  const { data: session } = useSession()
  console.log('🚀 ~ session:', session?.user.email)
  const { setShowCart } = useContext(CartContext)

  const router = useRouter()

  const captureOrder = async (orderId) => {
    try {
      const res = await fetch('/api/paypal/capture', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ orderId: orderId }),
      })

      const orderCapture = await res.json()

      if (orderCapture.status == 200) {
        const orderId = await createPaypalOrder(
          uniqueProducts,
          orderCapture.response.result,
          total,
          session?.user.email,
        )
        setShowCart(false)
        router.push(`/order/success?order_id=${orderId}`)
      }
    } catch (error) {
      toast({
        title: 'Invalid payment',
        variant: 'destructive',
      })
      console.log('🚀 ~ captureOrder ~ error:', error)
    }
  }

  const createOrderAction = async () => {
    try {
      console.log(total)
      console.log(uniqueProducts)

      const res = await fetch('/api/paypal', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ total: total }),
      })
      const order = await res.json()
      if (order.id) return order.id
      else {
        const errorDetail = order?.details?.[0]
        const errorMessage = errorDetail
          ? `${errorDetail.issue} ${errorDetail.description} (${orderData.debug_id})`
          : JSON.stringify(orderData)

        throw new Error(errorMessage)
      }
    } catch (error) {
      console.log('🚀 ~ createOrderAction ~ error:', error)
      console.error(error)
    }
  }

  return (
    <div>
      <PayPalScriptProvider
        options={{
          clientId:
            'test',
          disableFunding: 'card',
          currency: 'EUR',
        }}
      >
        <PayPalButtons
          style={{
            color: 'gold',
            disableMaxWidth: 'true',
          }}
          createOrder={async (data, actions) => {
            const orderId = createOrderAction()
            return orderId
          }}
          onApprove={async (data, actions) => {
            const orderCapture = await captureOrder(data.orderID)
            if (orderCapture) return true
          }}
          onCancel={(data) => {}}
          onError={(err) => {
            console.log('🚀 ~ err:', err)
            console.error('PayPal Checkout onError', err)
          }}
        />
      </PayPalScriptProvider>
    </div>
  )
}

The console log in createOrder and onApprove always display the old uniqueProducts and total before I changed the quantity

I’ve attempted to pass the updated product items in the request body when creating the order, but I couldn’t retrieve them when capturing the order. I’ve also tried to retrieve the updated products directly in the local storage and the total using useRef just before fetching the backend, it was working in the createOrderAction but not the captureOrder which is the crucial one to send data to my database.

2

Answers


  1. The code in this question is not well designed nor secure. From createOrderAction, the frontend should send a body with a list of cart items to the backend — NOT a total (except perhaps for extra verification that UI total matches backend). Backend should calculate the total itself based on the list of items, and track the items in its own backend state/storage (not any react/browser/client-side storage)


    Additional best practice: The backend can include the list of items in its create order API call to PayPal, as part of the purchase_unit’s items array. If an items array is included, an amount with breakdown is also required.

    The items will show in the PayPal checkout during approval, and be stored as part of the resulting PayPal transaction (visible to both the payer and seller in http://www.paypal.com interface )

    Login or Signup to reply.
  2. You can use forceReRender props in the PaypalButtons component and the cart will update accordingly

      <PayPalButtons
          forceReRender={[uniqueProducts]}
          style={{
            color: 'gold',
            disableMaxWidth: 'true',
          }}
          createOrder={async (data, actions) => {
            const orderId = createOrderAction()
            return orderId
          }}
          onApprove={async (data, actions) => {
            const orderCapture = await captureOrder(data.orderID)
            if (orderCapture) return true
          }}
          onCancel={(data) => {}}
          onError={(err) => {
            console.log('🚀 ~ err:', err)
            console.error('PayPal Checkout onError', err)
          }}
        />
    

    Here is an example with this props in the official documentation https://paypal.github.io/react-paypal-js/?path=/docs/example-paypalbuttons–default

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search