Ok. So. I’m building a simple e-commerce site using Next.js (version 13.4) and React; I am using the app router and server actions.
I’ve successfully integrated the PayPal SDK for payments. Right now, I’m using react-paypal-js to render the buttons on the frontend, and I define the createOrder and onApprove functions in my actions
folder (so they are server actions, not routes). My thinking was that, as per Next.js., do the data fetching/posting on the server or in server components, that way it can keep sensitive data (like API keys) away from the browser.
However, the PayPalScriptProvider
must be used in a client component, plus I have to pass initial options; the clientId is included in these options. This essentially has exposed my Paypal_Client_Id
environment variable
Ideally, I would like to keep the Paypal_Client_Id
secret from the browser, but I’m not sure this is possible. I tried defining my initialOptions in a server component and passing it as a prop to the client component, but the console log in the client still shows the Paypal_Client_Id
. So, I’d appreciate thoughts on either one of these two things:
-
How significant is it to have the PayPal Client Id exposed? Like, someone has the Id and now my life is going to be completely ruined, or is it more like someone just trying to use my id to get into a bar?
-
Any thoughts on how to keep my Client Id from being exposed on the frontend? Perhaps a route.ts file, instead? But even in that case, the PayPalScriptProvider has to be used in a client component, and it needs those initial options.
Here are my files, for reference, though I’m not sure if you need to see them to address the issue. It’s just the bare bones so far, lots of console logging errors at the moment:
app>cart>page.tsx: …like I said, I tried passing the environment variable from a SC into a Client Component, so this is the Server Component
import CartPay from "./CartPay";
export default function Cart() {
const client = process.env.PAYPAL_CLIENT_ID || "";
console.log(client) //logs on the server, not on the client
const initialOptions = {
clientId: client,
currency: "USD",
intent: "capture",
};
return (
<CartPay initialOptions={initialOptions}/>
);
}
this is the CartPay.tsx:
'use client'
import React from 'react'
import { useCart } from "../components/CartContext";
import Product from "../products/[id]/Product";
import { PayPalButtons, PayPalScriptProvider } from '@paypal/react-paypal-js';
import { createOrder, payOrder } from '../actions';
interface CartPayProps {
initialOptions: {
clientId: string,
currency: string,
intent: string
}
}
const CartPay = ({initialOptions} : CartPayProps) => {
console.log(initialOptions) //logs the client id and other information in browser
const {state} = useCart();
const {cart} = state
return (
<PayPalScriptProvider options={initialOptions}>
<div>
{cart.map((item) => (
<Product key={item.id} product={item} />
))}
</div>
<div>
<PayPalButtons
createOrder={async (data, actions) => {
let response = await createOrder();
if (response.success === "true") {
return response.orderId; //success
} else {
// error from createOrder function
console.error("Error creating order:", response.error);
return undefined; // undefined if createOrder failed
}
}}
onApprove={async (data, actions) => {
let response = await payOrder(data.orderID);
if (response && response.success === "true") {
console.log("Payment successful");
} else {
console.error("Payment failed:", response.error);
}
}}
/>
</div>
</PayPalScriptProvider>
);
}
export default CartPay
2
Answers
All PayPal JS SDK integrations use the client-id as a required config or query string parameter. There is no way to integrate them without a client-id.
The value that needs to be kept out of the browser isn’t the client-id; rather, it is the REST app’s
secret
.I’m facing a similar issue trying to use serverActions with Paypal. In my case I need to handle subscriptions and one-time payments and conditionally render the button passing different methods and initialOptions.
It would be very helpful if you could share the GitHub repository for your server actions or perhaps expand on your
createOrder
andpayOrder
actions here.