Stripe.js Error – Invalid value for createPaymentMethod: card was `payment` Element, which cannot be used to create card PaymentMethods

I am receiving this error in my JS console when I click the submit button in my form, which is using a Stripe Payment Element:

[Error] Unhandled Promise Rejection: IntegrationError: Invalid value for createPaymentMethod: card was 'payment' Element, which cannot be used to create card PaymentMethods. (anonymous function) (checkout2.js:44)

The error is not accompanied by any stacktraces, and the error only occurs in the console, with no errors in the server terminal.

Here is the file with the error, checkout2.js:

// Set your publishable key: remember to change this to your live publishable key in production
// See your keys here: https://dashboard.stripe.com/apikeys
const stripe = Stripe('publishable key goes here (removed for the sake of the StackOverflow question)');

const appearance = {
  theme: 'night',

  variables: {
    colorPrimary: 'rgb(75, 67, 188)',
    fontFamily: 'Ideal Sans, system-ui, sans-serif',
    borderRadius: '5px',
  }
};

const options = {
  layout: {
    type: 'tabs',
    defaultCollapsed: false,
  }
};
let clientSecret; // declare clientSecret outside the fetch callback
let elements; // declare elements outside the fetch callback

fetch('/secret')
  .then(response => response.json())
  .then(data => {
    clientSecret = data.client_secret;
    elements = stripe.elements({ clientSecret, appearance });
    const paymentElement = elements.create('payment', options);
    paymentElement.mount('#payment-element');
  });

// Handle form submission
const form = document.getElementById('payment-form');
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  if (!clientSecret || !elements) {
    console.error('Client secret or elements not loaded yet');
    return;
  }
  // Get the payment element
  const paymentElement = elements.getElement('payment');
  // Create a PaymentMethod
  const { paymentMethod, error } = await stripe.createPaymentMethod({
    type: 'card',
    card: paymentElement,
  });
  if (error) {
    // Handle error
    console.log(error);
  } else {
    const result = await stripe.confirmCardPayment(clientSecret, {
      payment_method: paymentMethod.id,
    });
    if (result.error) {
      // Handle error
      document.getElementById('error-message').innerText = result.error.message;
    } else {
      // Handle successful payment
      console.log('Payment successful!');
    }
  }
});

Here’s my server.js file that’s running being run by Node:

import { createRequire } from 'module';
const require = createRequire(import.meta.url);

// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
const express = require('express');
const app = express();

const stripe = require('stripe')('secret key would be here, but removed for StackOverflow');

app.use(express.static('public'));

app.get('/secret', async (req, res) => {
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 1099,
    currency: 'usd',
    automatic_payment_methods: {
      enabled: true,
    },
  });
  res.json({ client_secret: paymentIntent.client_secret });
});

app.listen(3000, () => {
  console.log('Server started on port 3000. Open http://localhost:3000 in your browser to view the public directory.');
});

Finally, my index.html file:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Stripe Payment</title>
  <script src="https://js.stripe.com/v3/"></script>
  <style>
    body {
      background-color: #000000;
    }

    #payment-form {
      margin-left: 100px;
      margin-right: 100px;
    }

    #submit {
      margin-top: 30px;
      height: 40px;
      width: 175px;
      font-size: 20px;
      background-color: rgb(55, 48, 163);
      color: #ffffff;
      border: 0px;
      border-radius: 5px;
      cursor: pointer;
    }

    #submit:hover {
      background-color: rgb(75, 67, 188);
    }
  </style>
</head>
<body>
  <form id="payment-form">
    <div id="payment-element">
      <!-- Elements will create form elements here -->
    </div>
    <button id="submit">Submit</button>
    <div id="error-message">
      <!-- Display error message to your customers here -->
    </div>
  </form>
  <script src="../js/checkout2.js"></script>
</body>
</html>

For those wondering, this is running on a Node.js server.

The error occurs in my JS console when I click ‘Submit’ on my HTML page. Because this error occurs, it makes it so the form completely fails. No POST request is sent to Stripe, at all, and the error will continue occurring as you press the ‘Submit’ button over and over again.