'Stripe cards elements keep refreshing when I type
I use two components for my checkout flow. I integrated Stripe card elements but whenever I type a card number, it refreshes at least 3 times before it stops and when I submit my data it doesn't seem to be well capture by my headless commerce. I followed this doc
When I submit this error shows:
v3:1 Uncaught (in promise) IntegrationError: We could not retrieve data from the specified Element.
Please make sure the Element you are attempting to use is still mounted.
at et (v3:1:94950)
at e._handleMessage (v3:1:103953)
at e._handleMessage (v3:1:74083)
at v3:1:101904
This is my PaymentOptions component - level 3 (inside UserPayment component):
//Required
import React, { useState, useContext } from "react";
import { loadStripe } from '@stripe/stripe-js';
import { Elements } from "@stripe/react-stripe-js";
import getConfig from 'next/config';
import { useRouter } from 'next/router';
import * as ga from '../../../lib/ga/index.js';
import { uid } from 'uid';
import CheckoutForm from './Checkout_Form_Custom';
//Styles
import { Flex, Box } from 'reflexbox';
import styled from '@emotion/styled';
import { LoaderInBlue, PaypalButton, CustomCardButton } from 'components/GlobalStyles/Buttons';
import { SimpleErrorMessage } from 'components/GlobalStyles/Errors'
//Context
import EcommerceContext from '../../../context/EcommerceContext';
const { publicRuntimeConfig } = getConfig();
const stripePromise = loadStripe(publicRuntimeConfig.STRIPE_PK_TEST);
function PaymentOptions({formData, token, cartItems}){
const router = useRouter();
const [processing, setProcessing] = useState('');
const [isStripe, setIsStripe] = useState(false);
const [isPaypal, setIsPaypal] = useState(false);
// Add auth context
const { error, buttonProcessing } = useContext(EcommerceContext)
const handleStripeButton = async (event) =>{
event.preventDefault();
setIsStripe(true);
const generateNewTransactionId = uid()
const totalPrice = cartItems && cartItems.length > 0 && cartItems.reduce((a, c) => a + c.unitPrice * c.quantity, 0);
}
const handlePaypalButton = async (event) =>{
event.preventDefault();
setIsStripe(false);
}
return(
<PaymentOptionsStyled className="payment-options">
{buttonProcessing &&
/** if something is loading, show the loading icon**/
<Flex justifyContent="center">
<LoaderInBlue />
</Flex>
}
{error && <SimpleErrorMessage />}
{
!isStripe && !isPaypal &&
/** if no payment is selected, show payment buttons **/
<>
<Flex justifyContent="center" className="card-option payment-option">
<CustomCardButton
handleStripeButton={handleStripeButton}
/>
</Flex>
<Flex justifyContent="center" className="paypal-option payment-option">
<PaypalButton
handlePaypalButton={handlePaypalButton}
formData={formData}
cartItems={cartItems}
/>
</Flex>
</>
}
{
isStripe &&
/**** if card payment is selected, show show Stripe Elements */
<Box className='center-box'>
<Flex justifyContent="center">
<Elements /*options={options}*/ stripe={stripePromise}>
<CheckoutForm token={token} />
</Elements>
</Flex>
<Flex my={20}>
<Box as="button" onClick={()=> setIsStripe(false)}>
{'Retour'}
</Box>
</Flex>
</Box>
}
</PaymentOptionsStyled>
)
}
const PaymentOptionsStyled = styled.div`
...
`
export default PaymentOptions
This is my CheckoutForm component - last level (inside PaymentOptions component):
//Required
import React, { useState, useEffect, useContext } from "react";
import getConfig from 'next/config';
import swell from 'swell-js';
import { useRouter } from 'next/router';
import {
useStripe,
useElements,
CardElement
} from "@stripe/react-stripe-js";
import { success } from 'components/Utils/pageUrls';
import {
submitOrder
} from '../../../context/CommerceServices';
import {
newIntent
} from 'components/Utils/apis';
//Styles
import { Flex, Box } from 'reflexbox';
import styled from '@emotion/styled';
import Checkmark from 'components/GlobalStyles/Animations/Checkmark';
import { LoaderInBlue, PayOrder, WaitingForPayment, SubmitPaymentButton } from 'components/GlobalStyles/Buttons';
//SEO & Cookies
import { setCookie, parseCookies, destroyCookie } from 'nookies';
//Contexts
import EcommerceContext from '../../../context/EcommerceContext';
const { API_URL } = process.env;
function CheckoutForm({formData, token, getPaymentIntentId}){
//Commerce context
const {
cartItems,
cart,
errorBeforePayment,
setErrorBeforePayment,
paymentIntent,
setPaymentIntent,
} = useContext(EcommerceContext);
//Defines states
const router = useRouter();
const [isData, setIsData] = useState(false);
const [succeeded, setSucceeded] = useState(false);
const [error, setError] = useState(null);
const [tokenized, setTokenized] = useState(null);
const [processing, setProcessing] = useState('');
const [disabled, setDisabled] = useState(true);
const [newOrderId, setNewOrderId] = useState('');
const [clientSecret, setClientSecret] = useState('');
const elements = useElements();
const stripe = useStripe();
//Fetch stripe paymentIntent
useEffect(async(ctx) => {
//Cart informations
const intentData = {
cartItems,
cart
}
//if user is logged in, fetch payment intent
if(token){
// Create paymentIntent as soon as the page loads
paymentIntent = await fetch(`${API_URL}${newIntent}`, {
method: 'POST',
body: JSON.stringify({intentData}),
headers: {
'Content-type': 'application/json',
'Authorization': `Bearer ${token}`
}
})
.then(res => {
//console.log('res payment:', res)
return res.json();
})
.then(data => {
console.log('data payment:', data)
if(data.info && data.info.clientSecret){
setClientSecret(data.info.clientSecret);
setPaymentIntent(data.info.newId);
setIsData(true)
}else{
setIsData(false)
}
if(data.info && data.info.newId){
setCookie(ctx, 'paymentIntentId', data.info.newId)
}
if(data.error){
setErrorBeforePayment(true)
}
});
}else{
return
}
}, []);
//Get form
const form = document.getElementById('payment-form');
//Tokenize card details with Swell
form && form.addEventListener('submit', function(event) {
event.preventDefault();
console.log('adding listener');
console.log('form', form)
//raise flag!
setProcessing(true);
console.log('before tokenize');
const tokenize = swell.payment.tokenize({
card: {
onError: (err) => {
//inform the customer there was an error
console.log('error tokenizing:', err);
},
onSuccess: () => {
//submit the form
console.log('succes tokenizing:', );
setTokenized(true);
if(tokenized){
console.log('tokenized so submit:', tokenized)
form.submit();
}
}
}
});
console.log('end listener');
setProcessing(false);
});
//Submit payment to Stripe and handle error
const handleSubmit = async ev => {
ev.preventDefault();
//raise flag!
setProcessing(true);
console.log('in submit func')
if(clientSecret && tokenized){
//if client secret is received, submit form
console.log('submiting payment to stripe')
const payload = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement)
}
});
console.log('end payload:')
if (payload && payload.error) {
setError(`Payment failed ${payload.error.message}`);
setProcessing(false);
} else {
console.log('succeed !:', payload)
setError(null);
setProcessing(false);
//const submitOrder = await swell.cart.submitOrder()
setSucceeded(true);
destroyCookie(null, 'paymentIntentId');
}
}else{
//if tokenization was unsuccessful
console.log('not tokenized so...bye!')
return
}
};
//handle payment success
/*if(succeeded) return (
<>
<CheckoutFormStyled>
<Flex flexDirection="column" justifyContent="center">
<p className=" checkout-success center">Paiement réussi!</p>
<Checkmark />
</Flex>
</CheckoutFormStyled>
{setTimeout(()=> router.push(success + '?orderId=' + `${newOrderId}&si=${successId}` ), 2000)}
</>
)*/
//Render Stripe Card Element
const customCardElement = swell && swell.payment.createElements({
card: {
elementId: '#card-element-id', // default: #card-element
options: {
style: {
base: {
//iconColor: '#c4f0ff',
fontWeight: 500,
fontFamily: 'Ideal Sans, system-ui, sans-serif',
fontSize: "16px",
},
},
},
onChange: event => {
// optional, called when the Element value changes
// Listen for changes in the CardElement
// and display any errors as the customer types their card details
//event.preventDefault();
setDisabled(event.empty);
setError(event.error ? event.error.message : "");
console.log('changing field !', event)
},
onSuccess: result => {
// optional, called on card payment success
console.log('result !');
},
onError: error => {
// optional, called on card payment error
console.log('error !');
setError(`Payment failed`);
setProcessing(false);
}
}
});
return(
<CheckoutFormStyled>
{
<form id="payment-form" onSubmit={(ev) => handleSubmit(ev)}>
{
!isData &&
/** if there is no clientSecret yet, show the loading icon **/
<Flex justifyContent="center"><LoaderInBlue /></Flex>
}
{
isData &&
/** When the clientSecret is ready, show Stripe CardElement and payment button**/
<>
<CardElement id="card-element-id"/>
<button as="button" className="ck-button order payment order-form-square"
disabled={processing || disabled || succeeded}
type="submit"
>
<Flex justifyContent='center' my={10}>
{ processing ?
<WaitingForPayment />
:
<PayOrder className="order-button-content"/>
}
</Flex>
</button>
</>
}
{/* Show any error that happens when processing the payment */}
{error &&
<Box as="div" className="card-error" role="alert">
{error}
</Box>
}
{/* Show a success message upon completion */}
<Box as ="p" className={succeeded ? "result-message" : "result-message hidden"}>
Payé avec succès ! see result in your
<Box as="a"
href={`https://dashboard.stripe.com/test/payments`}
>
{" "}
Stripe dashboard.
</Box> Refresh the page to pay again.
</Box>
</form>
}
{errorBeforePayment &&
<Box>Une erreur est survenue. Veuillez réessayer ou contacter Famous in Vogue.</Box>
}
</CheckoutFormStyled>
)
}
const CheckoutFormStyled = styled.div`
...
`
export default CheckoutForm
and lastly, this is my MultiStep component - level 1 (parent) UserPayment is level 2:
//Required
import { useContext } from 'react';
import { useForm, useStep } from 'react-hooks-helper';
import { UserIdentity, UserIdentityReview } from './Steps/User_Identity_Custom';
import { UserPaymentInactive, UserPayment } from './Steps/User_Payment_Custom';
import OrderSummary from 'components/Ecommerce/Order/Summary';
//Styles
import styled from '@emotion/styled';
import { Flex, Box } from 'reflexbox';
//contexts
import BreakPointContext from '../../../context/BreakPointContext';
const steps = [
{ id: 'détails de facturation'},
{ id: 'payment'},
];
function MultiStep ({cartItems, cart, token, auth_user, getPaymentIntentId, saveCartChanges}) {
const defaultData = {
firstnameInput: auth_user.myAddress && auth_user.myAddress.firstname,
lastnameInput: auth_user.myAddress && auth_user.myAddress.lastname,
emailInput: auth_user.myAddress && auth_user.email,
postcodeInput: '',
streetInput: '',
cityInput: '',
countryInput: ''
};
const { isBreakpoint } = useContext(BreakPointContext);
const [formData, setForm] = useForm(defaultData);
const {step, navigation} = useStep({
steps,
initialStep: 0,
});
const props = { formData, setForm, navigation };
if(isBreakpoint){
//if mobile
switch(step.id){
case 'détails de facturation':
return(
<CheckoutStyled >
<Flex className="checkout">
<Box clasName="order-summary checkout-step" width={isBreakpoint ? 1/1 : 1.5/3} mx={isBreakpoint ? 0 : 20}>
<OrderSummary {...props} cartItems={cartItems} />
</Box>
<Box clasName="checkout-steps" width={ isBreakpoint ? 1/1 : 2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentity {...props} auth_user={auth_user} cartItems={cartItems} cart={cart}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPaymentInactive {...props} cartItems={cartItems} cart={cart}/>
</Box>
</Box>
</Flex>
</CheckoutStyled>
)
case 'payment':
return(
<CheckoutStyled>
<Flex className="checkout">
<Box clasName="order-summary" width={isBreakpoint ? 1/1 : 1.5/3} mx={isBreakpoint ? 0 : 20}>
<OrderSummary {...props} cartItems={cartItems} cart={cart} saveCartChanges={saveCartChanges}/>
</Box>
<Box clasName="checkout-steps" width={ isBreakpoint ? 1/1 : 2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentityReview {...props} auth_user={auth_user}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPayment {...props} cartItems={cartItems} cart={cart} token={token} getPaymentIntentId={getPaymentIntentId} />
</Box>
</Box>
</Flex>
</CheckoutStyled>
)
}
}else{
//if desktop
switch(step.id){
case 'détails de facturation':
return(
<CheckoutStyled>
<Flex className="checkout">
<Box clasName="checkout-steps" width={ isBreakpoint ? 1/1 : 2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentity {...props} auth_user={auth_user} cartItems={cartItems} cart={cart}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPaymentInactive {...props} cartItems={cartItems} cart={cart}/>
</Box>
</Box>
<Box clasName="order-summary checkout-step" width={isBreakpoint ? 1/1 : 1.5/3} mx={isBreakpoint ? 0 : 20}>
<OrderSummary {...props} cartItems={cartItems} cart={cart}/>
</Box>
</Flex>
</CheckoutStyled>
)
case 'payment':
return(
<CheckoutStyled>
<Flex className="checkout">
<Box clasName="checkout-steps" width={2/3}>
<Box clasName="checkout-step" my={20}>
<UserIdentityReview {...props} auth_user={auth_user}/>
</Box>
<Box clasName="checkout-step" my={20}>
<UserPayment {...props} cartItems={cartItems} cart={cart} token={token} getPaymentIntentId={getPaymentIntentId} />
</Box>
</Box>
<Box clasName="order-summary" width={1.5/3}>
<OrderSummary {...props} cartItems={cartItems} cart={cart} saveCartChanges={saveCartChanges} />
</Box>
</Flex>
</CheckoutStyled>
)
}
}
}
const CheckoutStyled = styled.div`
...
`
export default MultiStep
Solution 1:[1]
I removed all the useStates from CheckoutForm and the card payment form stopped refreshing when typing. I still have one error to fixed so my card details can be captured ! Thank you for your input.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
