I’m having a tricky problem. I’ve created this ecommerce platform with react and I’m using the commerce JS API. I connected my sandbox stripe API key with the sandbox commerce JS key so I could do a test transaction, but each time I get an error and the transaction doesn’t go through, leaving my cart quantity unchanged.
In console I get the following error from the commerce JS API:
According to the commerce JS developer setting logs this is the request I’m sending:
{
"line_items": [
{
"id": "item_7RyWOwmK5nEa2V",
"product_id": "prod_LvJjoPJGale0nO",
"name": "Ferrari",
"product_name": "Ferrari",
"media": {
"type": "image",
"source": "https://cdn.chec.io/merchants/28537/assets/o0wPb0EDWAlJnEV1|1.jpg"
},
"sku": null,
"permalink": "kKwz9a",
"quantity": 1,
"price": {
"raw": 1.5,
"formatted": "1.50",
"formatted_with_symbol": "£1.50",
"formatted_with_code": "1.50 GBP"
},
"line_total": {
"raw": 1.5,
"formatted": "1.50",
"formatted_with_symbol": "£1.50",
"formatted_with_code": "1.50 GBP"
},
"is_valid": true,
"product_meta": [],
"tax": {
"is_taxable": false,
"taxable_amount": null,
"amount": null,
"breakdown": null
},
"selected_options": [],
"variant": []
}
],
"customer": [],
"shipping": {
"name": "Primary",
"county_state": "WSM",
"country": "GB"
},
"fulfillment": {
"shipping_method": "ship_RqEv5xvDqwZz4j"
},
"payment": {
"gateway": "stripe",
"stripe": {
"payment_method_id": "pm_1J1FitHjVwDyRBqGNRnw8Ryt"
}
}
}
I don’t understand how the email field, shipping address and shipping town/city are missing as they are all in my checkout form and I filled in everything accurately.
Here is my AddressForm component:
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, next }) => {
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 fetchShippingCountries = async (checkoutTokenId) => {
const { countries } = await commerce.services.localeListShippingCountries(
checkoutTokenId
);
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
) => {
const options = await commerce.checkout.getShippingOptions(
checkoutTokenId,
{ country, region }
);
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 (
<>
<Typography variant="h6" gutterBottom>
Shipping address
</Typography>
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit((data) =>
next({
...data,
shippingCountry,
shippingSubdivision,
shippingOption,
})
)}
>
<Grid container spacing={3}>
<Grid item xs={12} sm={6}>
<FormInput required name="firstName" label="First name" />{" "}
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="lastName" label="Last name" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="address1" label="Address line 1" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="email" label="Email" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="city" label="City" />
</Grid>
<Grid item xs={12} sm={6}>
{" "}
<FormInput required name="zip" label="Zip / Postal code" />
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Country</InputLabel>
<Select
value={shippingCountry}
fullWidth
onChange={(e) => setShippingCountry(e.target.value)}
>
{Object.entries(shippingCountries)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Subdivision</InputLabel>
<Select
value={shippingSubdivision}
fullWidth
onChange={(e) => setShippingSubdivision(e.target.value)}
>
{Object.entries(shippingSubdivisions)
.map(([code, name]) => ({ id: code, label: name }))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
<Grid item xs={12} sm={6}>
<InputLabel>Shipping Options</InputLabel>
<Select
value={shippingOption}
fullWidth
onChange={(e) => setShippingOption(e.target.value)}
>
{shippingOptions
.map((sO) => ({
id: sO.id,
label: `${sO.description} - (${sO.price.formatted_with_symbol})`,
}))
.map((item) => (
<MenuItem key={item.id} value={item.id}>
{item.label}
</MenuItem>
))}
</Select>
</Grid>
</Grid>
<br />
<div style={{ display: "flex", justifyContent: "space-between" }}>
<Button component={Link} variant="outlined" to="/cart">
Back to Cart
</Button>
<Button type="submit" variant="contained" color="primary">
Next
</Button>
</div>
</form>
</FormProvider>
</>
);
};
export default AddressForm;
My payment form component:
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,
nextStep,
backStep,
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,
},
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 (
<>
<Review checkoutToken={checkoutToken} />
<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;
And also my app js code:
import React, { useState, useEffect } from "react";
import { Products, Navbar, Cart, Checkout } from "./components";
import { commerce } from "./lib/commerce";
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 { cart } = await commerce.cart.add(productId, quantity);
setCart(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();
console.log(newCart);
setCart(newCart);
};
const handleCaptureCheckout = async (checkoutTokenId, newOrder) => {
try {
const incomingOrder = await commerce.checkout.capture(
checkoutTokenId,
newOrder
);
setOrder(incomingOrder);
refreshCart();
} catch (error) {
setErrorMessage(error.data.error.message);
}
};
useEffect(() => {
fetchProducts();
fetchCart();
}, []);
return (
<Router>
<div>
<Navbar totalItems={cart.total_items} />
<Switch>
<Route exact path="/">
<Products products={products} onAddToCart={handleAddToCart} />
</Route>
<Route exact path="/cart">
<Cart
cart={cart}
handleUpdateCartQty={handleUpdateCartQty}
handleRemoveFromCart={handleRemoveFromCart}
handleEmptyCart={handleEmptyCart}
/>
</Route>
<Route exact path="/checkout">
<Checkout
cart={cart}
order={order}
onCaptureCheckout={handleCaptureCheckout}
error={errorMessage}
/>
</Route>
</Switch>
</div>
</Router>
);
};
export default App;
I spoke to some people on the Commerce JS slack and they pointed out that these fields were ‘missing’ but I don’t understand what they mean by that. I’ve read that others have had this issue too (specifically people following this tutorial: ECommerce Web Shop - Build & Deploy an Amazing App | React.js, Commerce.js, Stripe - YouTube) so I’m not sure if it’s a problem with the code or the API.
I’m at a loss here so I’d really appreciate any help or suggestions.
Thanks