Next.js Discord

Discord Forum

Should I use API route or server action to fetch data on client (stripe customer's cards info)?

Answered
Nebelung posted this in #help-forum
Open in Discord
NebelungOP
If in client component I need to make a call to Stripe API to display customer's cards in a dialog should I fetch it using server actions or API route or maybe something completely different? I don't want to fetch this in server component because i don't want to make a call to this api on every page refresh.

i tried with api route but passing stripe customer id like this seems to me a bit dangerous:
const response = await fetch(
    `/api/stripe/route?customerId=${stripeCustomerId}`
);


In stripe/api/route I tried this:
export async function GET(req: Request) {
  cookies();
  
  // pass customerId to the function retrievePaymentMethods
  // GOAL: call retrievePaymentMethods from stripe/server.ts file to get payment methods

  const url = new URL(req.url);
  const customerId = url.searchParams.get('customerId');
  if (!customerId) {
    return new Response('Customer ID is required', { status: 400 });
  }

  try {
    const paymentMethods = await retrievePaymentMethods(customerId);
    return new Response(JSON.stringify(paymentMethods), {
      status: 200,
      headers: {
        'Content-Type': 'application/json'
      }
    });
  } catch (error) {
    return new Response(error.message, { status: 500 });
  }
}

thank you for help
Answered by Ray
yes, it can be used with useEffect and event handler too but it make a POST request instead of GET
View full answer

17 Replies

Entlebucher Mountain Dog
is retrievePyamentMethods() server action? then you call this action in server component.
NebelungOP
It's a function calling to stripe.
export async function retrievePaymentMethods(customerId: string) {
  try {
    const paymentMethods =
      await stripe.customers.listPaymentMethods(customerId);
    console.log('Payment methods from server stripe', paymentMethods);
    return paymentMethods;
  } catch (error) {
    console.error(error);
    throw new Error('Could not retrieve payment methods.');
  }
}
But how do I call this function if I want to on button "Show used cards" click fetch this data?
I have it like this atm:
'use client'
...
export default function BillingInfo(stripeCustomerId) {
  ...
  const handleDisplayPaymentMethods = async () => {
    // fetch data
  }
}
return (
  <DialogTrigger>
    <Button
      variant="slim" 
      loading={isSubmitting}
      onClick={(e) => handleDisplayPaymentMethods(e)}
     >
     Update payment method
    </Button>
   </DialogTrigger>
  ...
  )
}
I feel like I'm overcomplicating the problem.
This should be done without api route, right?

 const handleDisplayPaymentMethods = async () => {
    // safely fetch data from stripe
    const data = await retrievePaymentMethods(stripeCustomerId);
    console.log('data', data);
  };
retrievePaymentMethods function is executed only on the server.
NebelungOP
I don't think that's true. From nextjs docs:
Server Actions are asynchronous functions that are executed on the server. They can be used in Server and Client Components to handle form submissions and data mutations in Next.js applications.
Entlebucher Mountain Dog
yes, it is. and server action runs only on server side too.
NebelungOP
i wasn't sure if server actions aren't meant only for forms or not but sounds like it's valid to use them. they seem to be easier to write so i'll stick with it. thank you @Ray
Answer
NebelungOP
I think the retrievePaymentMethods will be making POST request because it's a server action.
Where exactly would you use useEffect?
'use client'
...
export default function BillingInfo(stripeCustomerId) {
  ...
  const handleDisplayPaymentMethods = async () => {
    // safely fetch data from stripe (retrievePaymentMethods is server action)
    try {
      const data = await retrievePaymentMethods(stripeCustomerId);
      if (data.length > 0 ) {
        setPaymentMethods(data.map((method) => method.card.brand)); 
      }
    } catch (error) {
      console.error('Failed to retrieve payment methods:', error);
    } 
    // [
    //   {
    //     billing_details: {...},
    //     card: {brand: 'visa'}
    //   }
    // ]
  }
}
return (
  <DialogTrigger>
    <Button
      variant="slim" 
      loading={isSubmitting}
      onClick={(e) => handleDisplayPaymentMethods(e)}
     >
     Update payment method
    </Button>
   </DialogTrigger>
  ...
<DialogDescription>
  <ul className="">
    {paymentMethods.length > 0 ? (
       paymentMethods.map((brand, index) => (
         <li key={index}>Card: {brand}</li>
       ))
    ) : (
       <li>Loading...</li>
    )}
   </ul>
  </DialogDescription>
  )
}
NebelungOP
but it works already with onClick and the data is returned correctly from the api call.
@Nebelung but it works already with onClick and the data is returned correctly from the api call.
yea I meant server action can be used with useEffect or event handler
and also form
NebelungOP
ahh okay, thank you