I'm creating a multi-step form in React using Formik and a stepper from Material-UI. That part is working (finally!) and I mostly understand how. Step 1 is a billing address, Step 2 is choosing a subscription plan, and Step 3 is payments using Stripe and their CardElements. This is where I fall off track.
I load Stripe high up in the app, so it's wrapped and available to my form. All good. I can successfully call Stripe methods when testing individually.
My Formik outer layer is here:
<Formik
{...props}
validationSchema={currentChild.props.validationSchema}
onSubmit={async (values, helpers) => {
if (isLastStep()) {
await props.onSubmit(values, helpers);
setCompleted(true);
} else {
setStep((s) => s + 1);
helpers.setTouched({});
if (step === 1) {
await props.getCustomerId(values.billingEmail);
}
}
}}
>
{... map over the steps, then the Back/Next/Submit buttons}
</Formik>
At the formStepper creation, I call on useStripe()
and useElements()
and create a CardElement
which gets passed into Step 3:
const stripe = useStripe();
const elements = useElements();
let stripeCard = CardElement;
<FormikStepper
initialValues={{ ...billingValues }}
onSubmit={(values, helpers) => {
createCustomer(values); //successfully sends formValues to be processed
}}
// I tried adding this here, but it isn't accessible inside the steps
// ETA: I put stripeProps inside the FormikStepper, which passes its properties down to the FormikStep inside. For non-Formik users, this works for the onBlur, onFocus, etc. as well as passing the initialValues. Basically, I was hoping the card element could hitch a ride
stripeProps={{ card: stripeCard }}
getCustomerId={async (billingEmail) => {
await fetchCustomerId(billingEmail);
}}
>
<FormikStep
label="Billing Info"
validationSchema={object({ ...validationSchemaUsingYup })}
>
<Grid container item spacing={1}>
<CompanyProfile />
</Grid>
</FormikStep>
<FormikStep label="Choose Subscription">
<Grid container item spacing={1}>
<ChooseSubscription />
</Grid>
</FormikStep>
<FormikStep label="Review & Pay">
<Grid container item spacing={1}>
<StripePayment card={stripeCard} />
</Grid>
</FormikStep>
</FormikStepper>
Inside Step 3 I can render and use the CardElement as expected with this:
<props.card
options={{
style: {...styling}
}}
/>
My confusion is in how to access this element in order to submit the data to Stripe?? How to I access the props that I passed into Step 3 and use that to process payment?
I tried putting the Stripe functions inside the function of Step 3, but they never got called, as the onSubmit functions are on the Parent of the step. I can't put the card number into the Formik initialValues without ruining the security aspect of using Stripe Elements. The closest I can get to anything is this error:
Warning: An unhandled error was caught from submitForm() IntegrationError: A valid Element name must be provided. Valid Elements are:
card, cardNumber, cardExpiry, cardCvc, postalCode, paymentRequestButton, iban, idealBank, p24Bank, auBankAccount, fpxBank; you passed: object.
Updates in response to comments:
The stripeProps was a failed attempt to package info into FormikStepper
, which passes props down to the children, FormikStep
.
The onSubmit
calls createPaymentMethod
, below:
async function createPaymentMethod(
values,
stripeData = stripeCard,
customer = customerId
) {
if (customer === undefined) {
customer = fetchCustomerId(values.billingEmail);
}
return stripe
.createPaymentMethod({
type: "card",
card: elements.getElement(stripeData), //no errors with CardElement, but not sure that's the SAME element from the form
})
.then((result) => {
if (result.error) {
console.log(result.error);
} else {
createSubscription({
customer: customer,
paymentMethodId: result.paymentMethod.id,
items: [
{
price: values.TCPlan,
quantity: values.TCPlanQuantity,
},
],
});
}
})
.catch((err) => console.log(err));
}
// Step 2 of the Stripe process;
// Called if createPaymentMethod is error-free
function createSubscription({ customer, paymentMethodId, items }) {
console.log(customer, paymentMethodId, items);
let created = fetch(
"https://xxxxxx.amazonaws.com/xxx/xxxx", // My API Gateway endpoint
{
method: "post",
headers: {
"Content-type": "application/json",
},
body: JSON.stringify({
customerId: customer,
paymentMethodId: paymentMethodId,
priceId: items,
}),
}
);
created
.then((response) => {
console.log(response.json());
})
// various error-handling offered by Stripe...
}
question from:
https://stackoverflow.com/questions/65836392/how-to-submit-stripe-payment-in-a-formik-wizard-using-react-elements