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
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 )
You can use forceReRender props in the PaypalButtons component and the cart will update accordingly
Here is an example with this props in the official documentation https://paypal.github.io/react-paypal-js/?path=/docs/example-paypalbuttons–default