Search code examples
reactjsstripe-paymentsstrapi

Stripe checkout not working in my react + strapi app - getting "undefined" error with this.props.stripe.createToken()


System:

  • React 16.11.0
  • Strapi 3.0.0-beta.17.5
  • Stripe: 7.13.0

Goal: to when clicking on submit on the modal, the stripe should work. According to the documentation on stripe: https://stripe.com/docs/recipes/elements-react The injectStripe function wraps the component, creating a new component with an injected stripe prop, which contains a Stripe object. You must use the wrapped component in your application instead of the original CheckoutForm.

So I assume that when I wrap it I can naturally access the stripe object within the CheckoutForm.

Error: can't read props of undefined

Then I have another error: index.js:1375 Warning: State updates from the useState() and useReducer() Hooks don't support the second callback argument. To execute a side effect after rendering, declare it in the component body with useEffect().

I have no idea what caused the above error.

Code:

import React, {useState, useEffect, Fragment } from 'react'
import { Container, Box, Button, Heading, Text, TextField, Modal, Spinner} from 'gestalt'
import {Elements, StripeProvider, CardElement, injectStripe} from 'react-stripe-elements'
import axios from 'axios'
import { withRouter } from "react-router-dom"
import ToastMessage from './ToastMessage'
import { getCart, calculatePrice, clearCart, calculateAmount} from './utils'

const apiUrl = process.env.API_URL || 'http://localhost:1337/'


function _CheckoutForm ({ history}) {

    const [formData, setFormData] = useState({
        address: '',
        zipcode: '',
        city: '', 
        confirmEmail: '',
        toast: false,
        toastMsg: '',
        orderProcessing: false,
        modal: false
    })

    const [cartItems,setCartItems] = useState([])

    useEffect(()=> {
        const items = getCart()
        setCartItems(items)
    }, [])

    const { address, zipcode, city, confirmEmail, toast, toastMsg, orderProcessing, modal } = formData

    const handleChange = ({event}) => {
        setFormData({...formData, [event.target.name]: event.target.value})
    }

    const showToast = (toastMsg, redirect = false) => {
        setFormData({...formData, toast: true, toastMsg})
        setTimeout(() => setFormData({...formData, toast: false, toastMsg:''},
        // if true passed to 'redirect' argument, redirect home
         ()=> redirect && history.push('/')), 
        5000)
    }

    const isFormEmpty = (address, zipcode, city, confirmEmail) => {
        return !address || !zipcode || !city || !confirmEmail
    }

    const closeModal = ( )=> setFormData({...formData, modal: false})

    const handleConfirmOrder = event => {
        event.preventDefault()
         if (isFormEmpty(address, zipcode, city, confirmEmail)){
             showToast('Please fill in all the fields')
             return
         }
         setFormData({...formData, modal:true })
    }

    const handleSubmitOrder = async () => {
        const amount = calculateAmount(cartItems)
        setFormData({...formData, orderProcessing: true})
        let token
        try {
            //create stripe token
            //create order with strapi (request to backend)
            //set orderProcessing to false set modal to false
            //clear user cart 
            //show success toast

            const response = await this.props.stripe.createToken()
            token = response.token.id
            console.log(token)
            await axios
            .post(`${apiUrl}/orders`, {
                amount, 
                productItems: cartItems,
                city,
                zipcode, 
                address,
                token
            })
            setFormData({...formData, orderProcessing: false, modal: false})
            clearCart()
            showToast('Your order has been successfully submitted', true)
        } catch (error) {
            //set order processing to false, modal to false
            //show error message in toast
            setFormData({...formData, orderProcessing: false, modal: false})
            showToast(error.message)
        }
    }

    return (
        <div>
            some mark up code 
        </div>
    )
}

const ConfirmationModal = ({ orderProcessing, cartItems, closeModal, handleSubmitOrder}) => (
    <Modal
    accessibilityCloseLabel="close"
    accessibilityModalLabel="confirm your order"
    heading="Confirm Your Order"
    onDismiss={closeModal}
    footer={
        <Box display="flex" marginRight={-1} marginLeft={-1} justifyContent="center">
            <Box padding={1}>
                <Button
                size="lg"
                color="blue"
                text="Submit"
                disabled={orderProcessing}
                onClick={handleSubmitOrder}
                />
            </Box>
            <Box padding={1}>
                <Button
                size="lg"
                text="Cancel"
                disabled={orderProcessing}
                onClick={closeModal}
                />
            </Box>
        </Box>
    }
    role="alertdialog"
    size="sm"
    >
    {/* order summary */}
    {!orderProcessing && (
        <Box display="flex" justifyContent="center" alignItems="center" direction="column" padding={2} color="lightWash">
            {cartItems.map( item => (
                <Box key={item._id} padding={1}>
                    <Text size="lg" color="blue">
                    {item.name} x {item.quantity} - $ {item.quantity * item.price}
                    </Text>
                </Box>
            ))}
            <Box padding={2}>
                <Text size="lg" color="blue" bold>
                    Total: {calculatePrice(cartItems)}
                </Text>
            </Box>
        </Box>
    )}
        {/* order processing spinner */}
        <Spinner show={orderProcessing} accessibilityLabel="Order Processing Spinner" />
        {orderProcessing && <Text align="center" italic>Submitting your order...</Text> }
    </Modal>
)

const CheckoutForm = withRouter(injectStripe(_CheckoutForm))

const Checkout = () => (
    <StripeProvider apiKey="PUBLIC_KEY">
        <Elements>
            <CheckoutForm />
        </Elements>
    </StripeProvider>
)

export default Checkout

Question: why can't I access this.props.stripe.createToken() in the CheckoutForm? How could I fix it?


Solution

  • I finally figured out somehow: that I need to pass props in the function _CheckoutForm, then I can just write props.history,or props.stripe.createToken().