How do I set up a Stripe payment form in React?

153 Views Asked by At

I'm building a web app in React, and want to be able to accept Stripe payments. I've followed the documentation on their site, but something is clearly missing because it's throwing an error. During the process I haven't at any point specified how much the user should be paying, or anything like that, so I imagine that's the issue, but I can't find anything in the docs which shows me how to set that up (I found a bit about PaymentIntents, which I think is related, but I've no idea where to use that).

So, the process is that the user signs up, I send their details to the backend, and (if everything is ok) the backend returns stripeID (cus_****) and paymentIntentID (pi_*****). We only have one product, which is a monthly subscription at a set price, so the paymentIntentID will indicate that the payment is for that product.

Once I have that information, I want to show them a Stripe payment page which allows them to pay for the subscription. If that's successful, I want to redirect them to a dashboard; if there's an error, I want to show it and let them retry the payment.

I've installed the react-stripe-js package.

In App.js I have (currently myPublishableKey (pk_test_) and myClientSecret (sk_test_) are hardcoded, but obviously I'll change that):

<Elements stripe={loadStripe(myPublishableKey)} options={{ clientSecret: myClientSecret }}>
  <BrowserRouter>
      <Routes>
        <Route path="/" element={<LayoutMain />}>
          <Route index element={<Home />} />
          <Route
            path="signup" element={<SignUp/>}
          />
          <Route
            path="payment" element={<Payment />}
          />
        </Route>
      </Routes>
    </BrowserRouter>
</Elements>

In the SignUp component, there's basically a form asking for name etc; in the onSubmit method of the form, the form details are sent to the backend; if the submission is successful (basically as long as they don't already have an account), then the returned data is saved to the context (this includes stripeID and paymentIntentID) and the user is sent to the Payment page:

    const onSubmit = () => {
      try {
        const res = await postData('user/create_user', {...formState});
        appDispatch({ type: 'onGetUser', user: { ...res } });
        // Navigate to stripe payment page
        navigate('/payment');
      } catch {
        setSubmissionError(true);
      }
    }

Then I created a Payment Component, following the Stripe docs:

const Payment = () => {
  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (event: any) => {
    event.preventDefault();

    if (!stripe || !elements) {
      return;
    }

    const result = await stripe.confirmPayment({
      elements,
      confirmParams: { return_url: ON_SUCCESS_URL },
    });

    if (result.error) {
      console.log(result.error.message);
    } else {
      console.log('ok');
    }
  };

  return (
    <MainContainer>
      <InnerContainer>
        <Centred>
          <form onSubmit={handleSubmit}>
            <PaymentElement />
            <Button>Submit</Button>
          </form>
        </Centred>
      </InnerContainer>
    </MainContainer>
  );
};

export default Payment;

When I view the Payment component, the console shows the error "v3:1 Uncaught IntegrationError: In order to create a payment element, you must pass a valid PaymentIntent or SetupIntent client secret when creating the Elements group".

I'm just not sure how to do that! I'm setting the clientSecret in App.js - at that point I don't have the paymentIntentID. Do I need to pass the paymentIntentID into something in the Payment component? If so, what?

0

There are 0 best solutions below