createPaymentMethod 的值无效:card 应该是一个对象或元素

Invalid value for createPaymentMethod: card should be an object or element

我正在尝试使用来自 stripe 的 nextJs 库创建一种支付方式,如下所示:

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;

打印出这个表格:

但是当我点击提交按钮时,我在开发者控制台中看到了这个错误:

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

如果我 console.log(cardElement) 打印出 null

知道为什么吗?

-编辑-

如果我将 CardElement 拆分为 CardNumber、CardExpiry 和 CardCVC(这实际上是我需要的方式),我会遇到同样的情况

    /* 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;

提供此表格:

所有 3 个 console.log 的 null

还有 stripeinput:(只是为了给它们设置样式)

    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;

根据@Jonatan-steele 的建议,我删除了重复的 Elements 提供程序(PaymentForm 组件中的提供程序)并且它工作正常,createPaymentMethod returns paymentMethod 如文档中所述。

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>
);

在我的例子中,我有一个加载器在按下支付按钮后替换了支付组件,这中断了条带流!我删除了装载机,然后它工作正常。

成功浪费 2 小时