javascriptreactjsstripe-paymentsnetlifynetlify-function

stripe.confirmCardPayment intent secret Returns Empty String


I'm trying to implement Stripe Elements Payment Intents into my React app using Netlify Functions. Submitting the payment form returns the error: Unhandled Rejection (IntegrationError): Invalid value for stripe.confirmCardPayment intent secret: value should be a client secret of the form ${id}_secret_${secret}. You specified: .

I can see the client_secret value in the paymentIntent object, so the serverless function is returning the client_secret, but I must not implementing it appropriate in the client. If I pass data.clientSecret as the parameter to stripe.confirmCardPayment, then my payment completes successfully, but the Elements form does not reset which indicates to me that there is a problem with this implementation.

Is setClientSecret not updating state on clientSecret?

Here's my Netlify function:

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

exports.handler = async (event) => {  
  const paymentObject = JSON.parse(event.body);
  const amount = paymentObject.amount;
  const donorName = paymentObject.metadata.donorName;
  const address = paymentObject.metadata.address;
  const city = paymentObject.metadata.city;
  const state = paymentObject.metadata.state;
  const zip = paymentObject.metadata.zip;
  const email = paymentObject.metadata.email

  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount,
      currency: 'usd',
      description: 'USS Idaho Donation',
      receipt_email: email,
      metadata: {
        donorName,
        address,
        city,
        state,
        zip,
        email,
      },
    });
    return {
      statusCode: 200,
      body: JSON.stringify({ 
        paymentIntent, 
        clientSecret: paymentIntent.client_secret
      })
    };
  } catch (error) {
    console.log({ error });

    return {
      statusCode: 400,
      body: JSON.stringify({ error }),
    };
  }
};

handleSubmit function:

const handleSubmit = async ev => {
    ev.preventDefault();
    setProcessing(true);

    window
      .fetch("/.netlify/functions/charge", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          amount,
          receipt_email: email,
            metadata: {
             donorName: donorName,
             address: address,
             city: city,
             state: state,
             zip: zip,
             email: email
            }
          }
        )
      })
      .then(res => {
        return res.json();
        
      })
      .then(data => {
        setClientSecret(data.clientSecret)
        console.log(`clientSecret: ${clientSecret}`)
        
        const payload = stripe.confirmCardPayment(data.clientSecret, {
          payment_method: {
            card: elements.getElement(CardElement)
          }
        });
        
        if (payload.error) {
          setError(`Payment failed: ${payload.error.message}`);
          setProcessing(false);
        } else {
          setError(null);
          setProcessing(false);
          setSucceeded(true);
          reset();
        } 
        
      })
  };

Solution

  • The stripe.confirmCardPayment function returns a Promise, so you need wait for that Promise to resolve before updating your component state:

    In your code, this would look something like:

    const handleSubmit = async ev => {
        ev.preventDefault();
        setProcessing(true);
    
        window
          .fetch("/.netlify/functions/charge", {
            method: "POST",
            headers: {
              "Content-Type": "application/json"
            },
            body: JSON.stringify({
              amount,
              receipt_email: email,
                metadata: {
                 donorName: donorName,
                 address: address,
                 city: city,
                 state: state,
                 zip: zip,
                 email: email
                }
              }
            )
          })
          .then(res => {
            return res.json();
            
          })
          .then(data => {
            setClientSecret(data.clientSecret);
            
            return stripe.confirmCardPayment(data.clientSecret, {
              payment_method: {
                card: elements.getElement(CardElement)
              }
            });
          })
          .then((paymentResult) => {
            if (paymentResult.error) {
              setError(`Payment failed: ${payload.error.message}`);
              setProcessing(false);
            } else {
              if (paymentResult.paymentIntent.status === 'succeeded') {
                setError(null);
                setProcessing(false);
                setSucceeded(true);
                reset();
              }
            }
          });
      };