#Using Stripe CardElement in Remix

1 messages · Page 1 of 1 (latest)

wise vortex
#

I'm trying to rewrite a certain project from Vite + React to Remix. I currently have this basic checkout flow using Stripe's custom <CardElement />, and processing the payments client-side, plus a simple Netlify function to handle some server-side logic for creating a PaymentIntent:

https://github.com/Insidiae/crwn-clothing/blob/main/src/components/PaymentForm.tsx

I'm browsing the Stripe docs for this checkout flow (https://stripe.com/docs/payments/accept-a-payment?platform=web&ui=elements&html-or-react=react), but it basically has the same checkout flow I'm doing in my project:

  1. Send some data (containing the amount to be paid) to the Netlify function, which then creates the PaymentIntent
  2. Get the PaymentIntent back from the request, use Stripe's custom hooks to get the data from the mounted <CardElement />, and then confirm the payment.

I'm thinking there's an opportunity here to move this client-side payment logic into the server, and take advantage of Remix's actions to handle creating the PaymentIntent and also confirm the payment with Stripe all in one place. Can some Stripe pros here help me out on how to move this logic into one Remix action?

GitHub

Capstone project from the ZTM React course. Contribute to Insidiae/crwn-clothing development by creating an account on GitHub.

Securely accept payments online.

wise vortex
#

Hmmm... After a bit more reading on Stripe's docs, it seems I can really only call the stripe.confirmCardPayment() function on the client side. That would require me to:

  1. event.preventDefault() the payment form submission
  2. fetch() the PaymentIntent from somewhere (previously the Netlify function)
  3. Use the PaymentIntent's client_secret as the required argument to confirmCardPayment()
  4. Handle the confirmCardPayment()'s result whether it succeeds or errors out

Relevant diagram from the Stripe docs:

#

Is there a way to take advantage of Remix loaders/actions for this checkout flow? Or would I need to stick with the Netlify function like I was doing previously?

spring relic
#

You can only send the payment information (user's credit/debig card data) from the client

#

this is needed for security reasons

#

unless you want to be audited to be PCI compliance

#

aside of that, once you receive from Stripe the token that represents the card, you can do the rest server-side

wise vortex
#

Yeah, that's the tricky part I think
I can only call confirmCardPayment() after I already have the PaymentIntent, but with the way my current project is built, I need some things from the client side before I create said PaymentIntent

#

Guess I'll stick with the old Netlify function for now then

spring relic
#

btw, whatever you do in a Netlify function you can do it in an action or loader

#

because Netlify functions run server-side

wise vortex
#

Yeah, that's what I was planning to do at first

#

Hmmm... I think I replace my 2nd step with an action/loader? All I really need at this point is to get the PaymentIntent from a fetch() call

#

Here's what that step looks like at the moment:

const response = await fetch(
  "/.netlify/functions/create-payment-intent",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      //? Convert dollars to cents
      amount: amount * 100,
    }),
  }
).then((res) => res.json());
spring relic
wise vortex
wise vortex
#

Alright, I moved the Netlify function code into a separate route action.

I'm not sure if I'm using useFetcher() the intended way however, Here's what it looks like right now after I got the checkout flow to work:

  async function handlePayment(event: React.MouseEvent) {
    event.preventDefault();

    if (stripe && elements) {
      setIsProcessingPayment(true);

      fetcher.submit(
        { amount: `${cartTotal * 100}` },
        {
          action: "/create-payment-intent",
          method: "post",
        }
      );
    }
  }

  React.useEffect(() => {
    async function finalizePayment() {
      if (fetcher.data && stripe && elements) {
        const { paymentIntent } = JSON.parse(fetcher.data.body);

        const clientSecret = paymentIntent.client_secret;

        const cardDetails = elements.getElement(CardElement);

        if (isValidCardElement(cardDetails)) {
          const paymentResult = await stripe.confirmCardPayment(clientSecret, {
            payment_method: {
              card: cardDetails,
              billing_details: {
                name: currentUser?.displayName || "Guest User",
              },
            },
          });

          setIsProcessingPayment(false);

          if (paymentResult.error) {
            alert(paymentResult.error);
          } else if (paymentResult.paymentIntent.status === "succeeded") {
            alert("Payment Succeeded!");
          }
        }
      }
    }

    finalizePayment();
  }, [fetcher, currentUser, elements, stripe]);
#

I'm hoping there's a way I can simply await something after I call that fetcher.submit()? That fetcher.data only seems to update after the component re-renders, so I had to wrap it an a useEffect for now which feels like it's not the way I'm supposed to do this 😅