adrianhajdin / project_e_commerce

This is a code repository for the corresponding video tutorial. In this video, we're going to build a fully functional eCommerce application using commerce.js.
https://jsmastery.pro
1.88k stars 507 forks source link

customer payload object sent to Stripe is empty. shippingData not being set in PaymentForm.jsx 422 error #54

Open rjmead23 opened 2 years ago

rjmead23 commented 2 years ago

I'm about 3/4 of the way through the video and really enjoying it so far. I'm struggling with the ShippingData part. For some reason it doesn't look like the FormInput fields are being set in shippingData request payload to api.chec - As you can see its an empty object below

{,…} customer: {} fulfillment: {shipping_method: "ship_p6dP5gbmYon7kA"} line_items: [{id: "item_7RyWOwmK5nEa2V", product_id: "prod_Kvg9l61n0d51bB", name: "Robin Hood",…},…] payment: {gateway: "stripe", stripe: {payment_method_id: "pm_1KhaFELTXNlEjoivg8TNLkUu"}} shipping: {name: "Primary", county_state: "IA", country: "US"}

{"status_code":422,"error":{"message":"The given data was invalid.","type":"unprocessable_entity","errors":{"customer.email":["The Email field is required when customer.id is not present."],"shipping.street":["The Shipping street address field is required when customer.id is not present."],"shipping.town_city":["The Shipping town\/city field is required when customer.id is not present."]}},

Only the fields that have a set like SetShippingCountries are being passed to the ShippingData.. The other values that come from the onSubmit are not being populated - The fields shown below do not pass into customer or shipping.

`

        <FormInput name='address1' label='Address line 1' />
        <FormInput name='email' label='Email' />
        <FormInput name='city' label='City' />
        <FormInput name='zip' label='ZIP / Postal Code' />`

Here's my AddressForm.jsx `import React, { useState, useEffect } from 'react'; import { InputLabel, Select, MenuItem, Button, Grid, Typography, } from '@material-ui/core';

import { useForm, FormProvider } from 'react-hook-form'; import { Link } from 'react-router-dom';

import { commerce } from '../../lib/commerce'; import FormInput from './CustomTextField';

const AddressForm = ({ checkoutToken, test }) => { const [shippingCountries, setShippingCountries] = useState([]); const [shippingCountry, setShippingCountry] = useState(''); const [shippingSubdivisions, setShippingSubdivisions] = useState([]); const [shippingSubdivision, setShippingSubdivision] = useState(''); const [shippingOptions, setShippingOptions] = useState([]); const [shippingOption, setShippingOption] = useState(''); const methods = useForm();

const countries = Object.entries(shippingCountries).map(([code, name]) => ({ id: code, label: name, }));

const subdivisions = Object.entries(shippingSubdivisions).map( ([code, name]) => ({ id: code, label: name, }) );

const options = shippingOptions.map((so) => ({ id: so.id, label: ${so.description} - (${so.price.formatted_with_symbol}), }));

const fetchShippingCountries = async (checkoutTokenId) => { const { countries } = await commerce.services.localeListShippingCountries( checkoutTokenId );

// console.log(countries);
setShippingCountries(countries);
setShippingCountry(Object.keys(countries)[0]);

};

const fetchSubdivisions = async (countryCode) => { const { subdivisions } = await commerce.services.localeListSubdivisions( countryCode );

setShippingSubdivisions(subdivisions);
setShippingSubdivision(Object.keys(subdivisions)[0]);

};

const fetchShippingOptions = async ( checkoutTokenId, country, // region = null stateProvince ) => { const options = await commerce.checkout.getShippingOptions( checkoutTokenId, { country, stateProvince } );

setShippingOptions(options);
setShippingOption(options[0].id);

};

useEffect(() => { fetchShippingCountries(checkoutToken.id); }, []);

useEffect(() => { if (shippingCountry) fetchSubdivisions(shippingCountry); }, [shippingCountry]);

useEffect(() => { if (shippingSubdivision) fetchShippingOptions( checkoutToken.id, shippingCountry, shippingSubdivision ); }, [shippingSubdivision]);

return ( <>

Shipping Address
  <FormProvider {...methods}>
    <form
      onSubmit={methods.handleSubmit((data) =>
        test({
          ...data,
          shippingCountry,
          shippingSubdivision,
          shippingOption,
        })
      )}
    >
      <Grid container spacing={3}>
        <FormInput name='firstName' label='First name' />
        <FormInput name='lastName' label='Last name' />
        <FormInput name='address1' label='Address line 1' />
        <FormInput name='email' label='Email' />
        <FormInput name='city' label='City' />
        <FormInput name='zip' label='ZIP / Postal Code' />
        <Grid item xs={12} sm={6}>
          <InputLabel>Shipping Country</InputLabel>
          <Select
            value={shippingCountry}
            fullwidth='true'
            onChange={(e) => setShippingCountry(e.target.value)}
          >
            {countries.map((country) => (
              <MenuItem key={country.id} value={country.id}>
                {country.label}
              </MenuItem>
            ))}
          </Select>
        </Grid>
        <Grid item xs={12} sm={6}>
          <InputLabel>Shipping Subdivision</InputLabel>
          <Select
            value={shippingSubdivision}
            fullwidth='true'
            onChange={(e) => setShippingSubdivision(e.target.value)}
          >
            {subdivisions.map((subdivision) => (
              <MenuItem key={subdivision.id} value={subdivision.id}>
                {subdivision.label}
              </MenuItem>
            ))}
          </Select>
        </Grid>
        <Grid item xs={12} sm={6}>
          <InputLabel>Shipping Options</InputLabel>
          <Select
            value={shippingOption}
            fullwidth='true'
            onChange={(e) => setShippingOption(e.target.value)}
          >
            {options.map((option) => (
              <MenuItem key={option.id} value={option.id}>
                {option.label}
              </MenuItem>
            ))}
          </Select>
        </Grid>
      </Grid>
      <br />
      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
        <Button component={Link} to='/cart' variant='outlined'>
          Back to Cart
        </Button>
        <Button type='submit' variant='contained' color='primary'>
          Next
        </Button>
      </div>
    </form>
  </FormProvider>
</>

); };

export default AddressForm; ` Here's my PaymentForm.jsx

`import React from 'react'; import { Typography, Button, Divider } from '@material-ui/core'; import { Elements, CardElement, ElementsConsumer, } from '@stripe/react-stripe-js'; import { loadStripe } from '@stripe/stripe-js';

import Review from './Review';

const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PUBLIC_KEY);

const PaymentForm = ({ checkoutToken, backStep, nextStep, shippingData, onCaptureCheckout, }) => { const handleSubmit = async (event, elements, stripe) => { event.preventDefault();

if (!stripe || !elements) return;

const cardElement = elements.getElement(CardElement);

const { error, paymentMethod } = await stripe.createPaymentMethod({
  type: 'card',
  card: cardElement,
});

if (error) {
  console.log('[error]', error);
} else {
  const orderData = {
    line_items: checkoutToken.live.line_items,
    customer: {
      firstname: shippingData.firstName,
      lastname: shippingData.lastName,
      email: shippingData.email,
      // firstname: 'John',
      // lastname: 'Smith',
      // email: 'this@that.com',
    },
    shipping: {
      name: 'Primary',
      street: shippingData.address1,
      town_city: shippingData.city,
      county_state: shippingData.shippingSubdivision,
      postal_zip_code: shippingData.zip,
      country: shippingData.shippingCountry,
    },
    fulfillment: { shipping_method: shippingData.shippingOption },
    payment: {
      gateway: 'stripe',
      stripe: {
        payment_method_id: paymentMethod.id,
      },
    },
  };
  onCaptureCheckout(checkoutToken.id, orderData);

  nextStep();
}

};

return ( <>

  <Divider />
  <Typography variant='h6' gutterBottom style={{ margin: '20px 0' }}>
    Payment method
  </Typography>
  <Elements stripe={stripePromise}>
    <ElementsConsumer>
      {({ elements, stripe }) => (
        <form onSubmit={(e) => handleSubmit(e, elements, stripe)}>
          <CardElement />
          <br /> <br />
          <div style={{ display: 'flex', justifyContent: 'space-between' }}>
            <Button variant='outlined' onClick={backStep}>
              Back
            </Button>
            <Button
              type='submit'
              variant='contained'
              disabled={!stripe}
              color='primary'
            >
              Pay {checkoutToken.live.subtotal.formatted_with_symbol}
            </Button>
          </div>
        </form>
      )}
    </ElementsConsumer>
  </Elements>
</>

); };

export default PaymentForm; `

Here's my app.js

`import React, { useState, useEffect } from 'react'; import { commerce } from './lib/commerce'; import { Products, Navbar, Cart, Checkout, PageNotFound } from './components'; import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';

const App = () => { const [products, setProducts] = useState([]); const [cart, setCart] = useState({}); const [order, setOrder] = useState({}); const [errorMessage, setErrorMessage] = useState('');

const fetchProducts = async () => { const { data } = await commerce.products.list(); setProducts(data); };

const fetchCart = async () => { setCart(await commerce.cart.retrieve()); };

const handleAddToCart = async (productId, quantity) => { const item = await commerce.cart.add(productId, quantity);

setCart(item.cart);

};

const handleUpdateCartQty = async (productId, quantity) => { const { cart } = await commerce.cart.update(productId, { quantity });

setCart(cart);

};

const handleRemoveFromCart = async (productId) => { const { cart } = await commerce.cart.remove(productId);

setCart(cart);

};

const handleEmptyCart = async () => { const { cart } = await commerce.cart.empty();

setCart(cart);

};

const refreshCart = async () => { const newCart = await commerce.cart.refresh();

setCart(newCart);

};

const handleCaptureCheckout = async (checkoutTokenId, newOrder) => { try { console.log(HandleCaptureCheckout Function entered); const incomingOrder = await commerce.checkout.capture( checkoutTokenId, newOrder ); setOrder(incomingOrder); console.log(This is neworder ${newOrder}); console.log(This is incomingOrder ${incomingOrder});

  refreshCart();
} catch (error) {
  setErrorMessage(error.data.error.message);
}

};

useEffect(() => { fetchProducts(); fetchCart(); }, []);

//console.log(products); // console.log(cart);

return (

); };

export default App; `

Here's my App.js

import React, { useState, useEffect } from 'react'; import { InputLabel, Select, MenuItem, Button, Grid, Typography, } from '@material-ui/core';

import { useForm, FormProvider } from 'react-hook-form'; import { Link } from 'react-router-dom';

import { commerce } from '../../lib/commerce'; import FormInput from './CustomTextField';

const AddressForm = ({ checkoutToken, test }) => { const [shippingCountries, setShippingCountries] = useState([]); const [shippingCountry, setShippingCountry] = useState(''); const [shippingSubdivisions, setShippingSubdivisions] = useState([]); const [shippingSubdivision, setShippingSubdivision] = useState(''); const [shippingOptions, setShippingOptions] = useState([]); const [shippingOption, setShippingOption] = useState(''); const methods = useForm();

const countries = Object.entries(shippingCountries).map(([code, name]) => ({ id: code, label: name, }));

const subdivisions = Object.entries(shippingSubdivisions).map( ([code, name]) => ({ id: code, label: name, }) );

const options = shippingOptions.map((so) => ({ id: so.id, label: ${so.description} - (${so.price.formatted_with_symbol}), }));

const fetchShippingCountries = async (checkoutTokenId) => { const { countries } = await commerce.services.localeListShippingCountries( checkoutTokenId );

// console.log(countries);
setShippingCountries(countries);
setShippingCountry(Object.keys(countries)[0]);

};

const fetchSubdivisions = async (countryCode) => { const { subdivisions } = await commerce.services.localeListSubdivisions( countryCode );

setShippingSubdivisions(subdivisions);
setShippingSubdivision(Object.keys(subdivisions)[0]);

};

const fetchShippingOptions = async ( checkoutTokenId, country, // region = null stateProvince ) => { const options = await commerce.checkout.getShippingOptions( checkoutTokenId, { country, stateProvince } );

setShippingOptions(options);
setShippingOption(options[0].id);

};

useEffect(() => { fetchShippingCountries(checkoutToken.id); }, []);

useEffect(() => { if (shippingCountry) fetchSubdivisions(shippingCountry); }, [shippingCountry]);

useEffect(() => { if (shippingSubdivision) fetchShippingOptions( checkoutToken.id, shippingCountry, shippingSubdivision ); }, [shippingSubdivision]);

return ( <>

Shipping Address
  <FormProvider {...methods}>
    <form
      onSubmit={methods.handleSubmit((data) =>
        test({
          ...data,
          shippingCountry,
          shippingSubdivision,
          shippingOption,
        })
      )}
    >
      <Grid container spacing={3}>
        <FormInput name='firstName' label='First name' />
        <FormInput name='lastName' label='Last name' />
        <FormInput name='address1' label='Address line 1' />
        <FormInput name='email' label='Email' />
        <FormInput name='city' label='City' />
        <FormInput name='zip' label='ZIP / Postal Code' />
        <Grid item xs={12} sm={6}>
          <InputLabel>Shipping Country</InputLabel>
          <Select
            value={shippingCountry}
            fullwidth='true'
            onChange={(e) => setShippingCountry(e.target.value)}
          >
            {countries.map((country) => (
              <MenuItem key={country.id} value={country.id}>
                {country.label}
              </MenuItem>
            ))}
          </Select>
        </Grid>
        <Grid item xs={12} sm={6}>
          <InputLabel>Shipping Subdivision</InputLabel>
          <Select
            value={shippingSubdivision}
            fullwidth='true'
            onChange={(e) => setShippingSubdivision(e.target.value)}
          >
            {subdivisions.map((subdivision) => (
              <MenuItem key={subdivision.id} value={subdivision.id}>
                {subdivision.label}
              </MenuItem>
            ))}
          </Select>
        </Grid>
        <Grid item xs={12} sm={6}>
          <InputLabel>Shipping Options</InputLabel>
          <Select
            value={shippingOption}
            fullwidth='true'
            onChange={(e) => setShippingOption(e.target.value)}
          >
            {options.map((option) => (
              <MenuItem key={option.id} value={option.id}>
                {option.label}
              </MenuItem>
            ))}
          </Select>
        </Grid>
      </Grid>
      <br />
      <div style={{ display: 'flex', justifyContent: 'space-between' }}>
        <Button component={Link} to='/cart' variant='outlined'>
          Back to Cart
        </Button>
        <Button type='submit' variant='contained' color='primary'>
          Next
        </Button>
      </div>
    </form>
  </FormProvider>
</>

); };

export default AddressForm;

Maaattqc commented 2 years ago

up please, same error image

rjmead23 commented 2 years ago

Has anyone else experienced this issue and found a solution for it? @Maaattqc - Were you able to figure out the problem with this?

Maaattqc commented 2 years ago

@rjmead23 i still dont know :/

TAKEYE01 commented 2 years ago

@rjmead23 i still dont know :/

Has anyone else experienced this issue and found a solution for it? @Maaattqc - Were you able to figure out the problem with this?

I fixed the billing part by disabling billing in the products of chec. There is a toggle option when you edit your product items on their website to require billing address. I now am only having the issue of my customer {} object being empty.

kwyjibo089 commented 2 years ago

I could fix the issue. I'm sure there are better solutions but I'm a react.js beginner so that's as good as I can do it.

I'm setting the values of the input fields like we did for the dropdowns. Check it out here: https://github.com/kwyjibo089/ecommerce/blob/master/src/components/CheckoutForm/CustomTextField.jsx

This also means I had to update the AddressForm: https://github.com/kwyjibo089/ecommerce/blob/master/src/components/CheckoutForm/AddressForm.jsx

I'm using useState to save firstname, lastname etc

mehulpuri commented 2 years ago

@rjmead23 did you find any fix for it

thiernoa98 commented 1 year ago

fixing this issue, you must disable/toggle off the REQUIRE BILLING ADDRESS on all of your products in your commercejs account because you don't the fields for billing address you only have fields for names and shipping address