The value entered for creating a payment method is invalid: the card must be in the form of an object

I am in the process of setting up a payment method using the Next.js library from Stripe. Here is the code snippet:

import React, { FunctionComponent } from 'react';
import type { DisplaySettingsProps } from '@company/frontoffice/types';
import { Row, Col,  Input } from '@company/ui';
import { useIntl } from '@company/frontoffice/providers';


import messages from './payment.lang';

import { loadStripe } from '@stripe/stripe-js';
import {
  Elements,
  useStripe,
  useElements,
  CardElement,

} from '@stripe/react-stripe-js';

/* Make sure to call `loadStripe` outside of a component’s render to avoid. recreating the `Stripe` object on every render. */
const stripePromise = loadStripe(
  'pk_test_51JzJxAGAsaImrqUbu1pKDHiCSaq9A0sZTgpV6Li3sd8yYrCCVvPhCPfZ3i9JJ8ySgY5Uhceu6iGxlgylx2vuXBj100SpHqClA8'
);

const PaymentForm: FunctionComponent = () => {
  const stripe = useStripe();
  const elements = useElements();

  const f = useIntl();

  const handleSubmit = async (e) => {
    e.preventDefault();
    console.log('inside handleSubmit');

    if (!stripe || !elements) {
      console.log('!stripe || !elements');
      return;
    }

    const cardElement = elements.getElement(CardElement);

    /*
    Returns:
    result.paymentMethod: a PaymentMethod was created successfully.
    result.error: there was an error.
    */
    const { paymentMethod, error: backendError } = await stripe.createPaymentMethod({
      type: 'card',
      card: cardElement,
      billing_details: {
        name: 'Jenny Rosen',
      },
    });

    if (backendError) {
      console.error(backendError.message);
      return;
    }
  };

  return (
    <Elements stripe={stripePromise}>
      <form
        onSubmit={(e) => {
          console.log('handleSubmit');
          handleSubmit(e);
        }}
      >
        <Row mb={4}>
          <Col col={12}>
            <Input label={f(messages.name)} placeholder={f(messages.name)} required={true} />
          </Col>
        </Row>
        <Row>
          <Col>
            <CardElement />
          </Col>
        </Row>
        <Row>
          <button>[submit /]</button>
        </Row>
      </form>
    </Elements>
  );
};

export type StripePaymentFormProps = DisplaySettingsProps & {
  displaySettings: any /* TODO */;
};

const StripePaymentForm: FunctionComponent<StripePaymentFormProps> = () => {
  return (
    <Elements stripe={stripePromise}>
      <PaymentForm />
    </Elements>
  );
};

export default StripePaymentForm;

The generated form can be viewed here:

https://i.stack.imgur.com/z6o5i.png

When I click the submit button, I encounter this error in the console:

v3:1 Uncaught (in promise) IntegrationError: Invalid value for createPaymentMethod: card should be an object or element. You specified: null.

Upon checking console.log(cardElement), it displays null. Any insights into why this may be happening?

-EDIT-

I faced the same issue when splitting the CardElement into CardNumber, CardExpiry, and CardCVC as needed:

    /* global fetch */
    import React, { FunctionComponent } from 'react';
    import type { DisplaySettingsProps } from '@company/frontoffice/types';
    
    import { Row, Col, Text, Image, Input } from '@company/ui';
    import { StyledInput } from './styledPayment';
    import StripeInput from './Stripeinput';
    import messages from './payment.lang';
    import { useIntl } from '@company/frontoffice/providers';
    
    import { loadStripe } from '@stripe/stripe-js';
    import {
      Elements,
      useStripe,
      useElements,
      CardNumberElement,
      CardCvcElement,
      CardExpiryElement,
    } from '@stripe/react-stripe-js';
    
    /* Make sure to call `loadStripe` outside of a component’s render to avoid. recreating the `Stripe` object on every render. */
    const stripePromise = loadStripe(
      'pk_test_51JzJxAGAsaImrqUbu1pKDHiCSaq9A0sZTgpV6Li3sd8yYrCCVvPhCPfZ3i9JJ8ySgY5Uhceu6iGxlgylx2vuXBj100SpHqClA8'
    );
    
    const PaymentForm: FunctionComponent = () => {
      const stripe = useStripe();
      const elements = useElements();
    
      const f = useIntl();
    
      const handleSubmit = async (e) => {
        e.preventDefault();
        console.log('inside handleSubmit');
    
        if (!stripe || !elements) {
          console.log('!stripe || !elements');
          return;
        }
    
        // what we had
        const cardNumber = elements.getElement(CardNumberElement);
        const cardCVC = elements.getElement(CardCvcElement);
        const cardExpiry = elements.getElement(CardExpiryElement);
    
        console.log(cardNumber, cardCVC, cardExpiry);
    
        const { paymentMethod, error: backendError } = await stripe.createPaymentMethod({
          type: 'card',
          card: cardNumber,
          billing_details: {
            name: 'Jenny Rosen',
          },
        });
    
        /* Create payment intent on server */
        const { error: backendError_, clientSecret } = await fetch('/create-payment-intent', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            paymentMethodType: 'card',
            currency: 'eur',
          }),
        }).then((r) => r.json());
    
        if (backendError) {
          console.error(backendError.message);
          return;
        }
    
        if (backendError_) {
          console.error(backendError_.message);
          return;
        }
    
    
        console.log(`clientSecret generated: ${clientSecret}`);
        // Confirm de payment on the client
        const { error: stripeError, paymentIntent, error } = await stripe.confirmCardPayment(
          clientSecret,
          {
            payment_method: {
              card: cardNumber,
            },
          }
        );
    
        if (stripeError) {
          console.error(stripeError.message);
          return;
        }
    
        console.log(`PaymentIntent ${paymentIntent.id} is ${paymentIntent.status}`);
    
        // what we had
        console.log(cardExpiry);
      };
    
      return (
        <Elements stripe={stripePromise}>
          <form
            onSubmit={(e) => {
              handleSubmit(e);
            }}
          >
            <Row mb={4}>
              <Col col={12}>
                <Input label={f(messages.name)} placeholder={f(messages.name)} required={true} />
              </Col>
            </Row>
            <Row mb={4}>
              <Col col={12}>
                <StyledInput
                  label={f(messages.number)}
                  placeholder={f(messages.number)}
                  required={true}
                  InputProps={{
                    inputComponent: StripeInput,
                    inputProps: {
                      component: CardNumberElement,
                    },
                  }}
                />
              </Col>
            </Row>
    
            <Row colGap={5} mb={4}>
              <Col col={[12, 6]}>
                <StyledInput
                  label={f(messages.expiry)}
                  placeholder={f(messages.expiry)}
                  required={true}
                  InputProps={{
                    inputComponent: StripeInput,
                    inputProps: {
                      component: CardExpiryElement,
                    },
                  }}
                />
              </Col>
              <Col col={[10, 5]}>
                <StyledInput
                  label={f(messages.security)}
                  placeholder="CVC"
                  required={true}
                  InputProps={{
                    inputComponent: StripeInput,
                    inputProps: {
                      component: CardCvcElement,
                    },
                  }}
                />
                <Text as="p" fontSize="xs" color="text2" mt={2}>
                  {f(messages.securitySub)}
                </Text>
              </Col>
              <Col col={[2, 1]}>
                {/* Todo - replace the scr when we receive from design (already requested) */}
                <Image
                  mt={[6, 6]}
                  display="block"
                  mx="auto"
                  width="size10"
                  maxWidth="sizeFull"
                  src="card-cvc.png"
                  alt="Credit card cvc icon"
                />
              </Col>
            </Row>
            <Row>
              <button>[submit /]</button>
            </Row>
          </form>
        </Elements>
      );
    };
    
    export type StripePaymentFormProps = DisplaySettingsProps & {
      displaySettings: any /* TODO */;
    };
    
    const StripePaymentForm: FunctionComponent<StripePaymentFormProps> = () => {
      return (
        <Elements stripe={stripePromise}>
          <PaymentForm />
        </Elements>
      );
    };

export default StripePaymentForm;

You can view the resulting form here:

https://i.stack.imgur.com/Rtnod.png

All three console.logs show null and the purpose of stripeinput is to style them:

    import React, { FunctionComponent, useRef, useImperativeHandle } from 'react';
    
    type StripeInputPrpps = {
      component: any; // todo type this
      inputRef: any; // todo type this
      props: any; // todo type this
    };
    
    const StripeInput: FunctionComponent<StripeInputPrpps> = ({
      component: Component,
      inputRef,
      ...props
    }) => {
      const elementRef = useRef();
    
      /* Todo - obtain this values from theme.props */
      const color = 'rgb(19, 40, 72)';
      const placeholderColor = 'rgb(137, 147, 164)';
    
      const inputStyle = {
        color: color,
        '::placeholder': {
          color: placeholderColor,
        },
      };
    
      useImperativeHandle(inputRef, () => ({
        focus: () => {
          if (elementRef && elementRef.current) {
            elementRef.current.focus;
          }
        },
      }));
    
      return (
        <Component
          onReady={(element) => (elementRef.current = element)}
          options={{
            style: {
              base: inputStyle,
            },
          }}
          {...props}
        />
      );
    };
    
    export default StripeInput;

Answer №1

Following @Jonatan-steele's advice, I removed the redundant Elements provider (specifically in the PaymentForm component) and everything is now working smoothly. The createPaymentMethod function now correctly returns the paymentMethod as outlined in the documentation.

return (
  <form
    onSubmit={(e) => {
      handleSubmit(e);
    }}
  >
    <Row mb={4}>
      <Col col={12}>
        <Input label={f(messages.name)} placeholder={f(messages.name)} required={true} />
      </Col>
    </Row>
    <Row mb={4}>
      <Col col={12}>
        <StyledInput
          label={f(messages.number)}
          placeholder={f(messages.number)}
          required={true}
          InputProps={{
            inputComponent: StripeInput,
            inputProps: {
              component: CardNumberElement,
            },
          }}
        />
      </Col>
    </Row>

    <Row colGap={[0, 5]} mb={4}>
      <Col col={[12, 6]}>
        <StyledInput
          label={f(messages.expiry)}
          placeholder={f(messages.expiry)}
          required={true}
          InputProps={{
            inputComponent: StripeInput,
            inputProps: {
              component: CardExpiryElement,
            },
          }}
        />
      </Col>
      <Col col={[10, 5]}>
        <StyledInput
          label={f(messages.security)}
          placeholder="CVC"
          required={true}
          InputProps={{
            inputComponent: StripeInput,
            inputProps: {
              component: CardCvcElement,
            },
          }}
        />
        <Text as="p" fontSize="xs" color="text2" mt={2}>
          {f(messages.securitySub)}
        </Text>
      </Col>
      <Col col={[2, 1]}></Col>
    </Row>
    <Row>
      <button>[submit /]</button>
    </Row>
  </form>
);

Answer №2

During my experience, a loader that replaced the payment component after pressing the payment button caused a disruption in the stripe flow. Once I removed the loader, everything functioned properly.

Two hours of valuable time wasted but lesson learned!

Answer №3

It is recommended to include the parameter as shown:

{
    method: 'create'
    cardDetails: getDetails(CardElement),
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

I am facing unresolved peer dependency problems that seem insurmountable

Currently, I am in the process of revamping an application that is built using expo sdk version 45. Since this version has been deprecated, I decided to upgrade the sdk. Upon running npx expo-doctor, I received the following output: ✔ Validating global p ...

Developing a Javascript object using Typescript

Trying my hand at crafting a TypeScript object from JavaScript. The specific JavaScript object I'm attempting to construct can be found here: https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.2/chess.js In the provided JavaScript example, the obj ...

Dealing with errors in getServerSideProps in Next.js by utilizing next-connect

Recently, I've been working with Next.js and utilizing the next-connect library to manage middlewares in my project. However, I'm encountering some difficulties when it comes to handling errors while using multiple middlewares within the getServ ...

How to efficiently initialize a Next.js app with Phusion Passenger while triggering the production build using a specific file?

In most cases, a Next.js application is launched by using the command npm run start after compiling it with npm run build. I am unable to start it using a command because my web server stack (Phusion Passenger) requires a specific startup script. https:// ...

Applying hover effect to material-ui IconButton component

As stated in the React Material-UI documentation, I have access to a prop called hoveredStyle: http://www.material-ui.com/#/components/icon-button I intend to utilize the IconButton for two specific purposes: Make use of its tooltip prop for improved ac ...

AngularJS synchronous $resource functionality allows for the ability to make parallel API

I'm facing a dilemma because I understand that Javascript isn't designed for synchronous work, especially in the case of AngularJS. However, I find myself in a situation where I require it. The main page on the "www" domain (built with AngularJS ...

Scrolling a div automatically without affecting its parent element

On a page, I have a scrollable list of items that can be updated with a PUT request. Once the update is successful, another network request is made to fetch the updated list. The goal is to automatically highlight the recently updated item in the list. Al ...

Steps to duplicate the package.json to the dist or build directory while executing the TypeScript compiler (tsc)

I am facing an issue with my TypeScript React component and the package.json file when transpiling it to es5 using tsc. The package.json file does not get copied automatically to the dist folder, as shown in the provided screenshot. I had to manually copy ...

creating a post action using Node.js with Express and Angular

Process Overview I have an Angular post that communicates with my Node.js Express backend to send data to an API. My goal is to redirect the user to a different Angular page upon successful post, or display an error message if the post fails. One approac ...

Guide to changing the class of a particular child element when the parent element is clicked with vanilla JavaScript

Upon clicking the parent button, a class within its child element will toggle to show or hide. If the child element is selected, a loading animation will appear for a brief moment before reverting back to its original hidden state. I attempted to achieve ...

Best method for reverting react-native to previous version

Here's the dilemma I'm facing: I had a functional version of a react-native project that was running smoothly and committed to my git repository. Deciding to upgrade from react-native 0.26.3 to 0.28 led me into a tangled web of dependencies, so ...

ng-bind-html not refreshed following ng-click triggering

Recently, I started learning about the MEAN stack. I have implemented an ng-repeat in my HTML code that displays a list of titles. Each title has an ng-click function attached to it, which is supposed to show more details in an overlay popup. blogApp.co ...

Text input setting for jQuery UI Slider

Currently, I am utilizing jQuery UI sliders to input values in text boxes. However, I would like this functionality to be bidirectional; meaning if a value is entered into the text box, I want the slider to move to the corresponding position. I am unsure ...

Tips for effectively invoking a method in a Vue component

As a Vue2 beginner, I am currently working with the Vue CLI and following the structure generated from it. My goal is to submit form data, but I keep encountering a warning and error: [Vue warn]: Property or method "onSubmit" is not defined on the insta ...

Error: rzp.open cannot be utilized as a function

While working on my React website, I encountered an error when trying to integrate Razorpay and calling rzp.open(); openCheckout() { let options = { "key_id": "My_key", //I have ensured the correct key "key_secret& ...

Managing state in React for a nested object while still maintaining the data type of the

Exploring the question further from this particular query Updating State in React for Nested Objects The process of updating nested state involves breaking down the object and reconstructing it as shown below: this.setState({ someProperty: { ...this.stat ...

EmeraldSocks Tweenmax motion design

I need help with Tweenmax animation. I'm attempting to animate an id selector, but nothing is happening. Both the selector and content are unresponsive. Can someone assist me? Here is the code: <!DOCTYPE html> <html> <head> ...

Building a Delayed Execution Loop in React without Circular Dependencies

I'm facing a unique challenge and struggling to find the best approach. I need a component that can switch between two states, playing and items. When playing is set to true, it should add a new item to the items array every second, with each new item ...

The redirect feature in getServerSideProps may not function properly across all pages

Whenever a user visits any page without a token, I want them to be redirected to the /login page. In my _app.js file, I included the following code: export const getServerSideProps = async () => { return { props: {}, redirect: { des ...

switching the content of an element using Javascript

I'd like to switch between two icons when clicking on .switch and apply the style of .nightTextNight to .nightText, my JavaScript code is working well everywhere except here. Is there a simpler way to achieve this? I currently have to create two clas ...