Environment: I am using material ui checkout template Ref: Link To UI and source Code: Source Code for my e-commerce site where at step 2: user has to enter payment details and at Step 3: user will be able to review the Cart List, Shipping Address & contact and payment Details.
Condition: At Step 3, When user Place Order
, I wanted to make the payment using Stripe and for that payment details gets through Step 2 using UseState.
Problem: I am not sure which Stripe Payment Method Shall I used? I tried confirmCardPayment but its ask for CardElement which I am not using
Condition: I don't want to use any Stripe Elements Like Payment Element/Card Element or Any prebuilt checkout page by stripe.
So Far According to Stripe Documentation, We can make the use of stripe.confirmCardPayment(paymentClientSecret, {payment_method: {card: elements.getElement(`NOT SURE WHAT TO ADD HERE?`), billing_details: {`....`}},})
. But for this method, we had to add CardElement by Stripe UI.
Code for Understanding:
index.js
const stripe = require("stripe")([SECRET KEY GOES HERE]);
app.post("/payments/create", async (request, response) => {
const total = request.query.total;
console.log("payment request recieved", total);
const paymentIntent = await stripe.paymentIntents.create({
amount: total,
currency: "usd",
});
response.status(201).send({
clientSecret: paymentIntent.client_secret,
});
});
App.js
import { loadStripe } from "@stripe/stripe-js";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
const promise = loadStripe(PUBLISH KEY GOES HERE)
function App(){
return (
<Router>
<div className="app">
<Switch>
{... ALL OTHER ROUTE PATH GOES HERE....}
<Route path="/checkout">
<Elements stripe={promise}>
<Checkout promise={promise} />
</Elements>
</Route>
{... ALL OTHER ROUTE PATH GOES HERE....}
</Switch>
</div>
</Router>)
}
export default App
Checkout.js
import {
Elements,
PaymentElement,
useElements,
useStripe,
} from "@stripe/react-stripe-js";
import axios from "../../API/axios";
import { useHistory } from "react-router-dom";
const Checkout = ({
submitShippingAddress,
userUid,
deleteDataFromDB,
promise,
}) => {
const INITIAL_PAYMENT_FORM_STATE = {
cardName: {
value: "",
error: false,
errorMessage: "Please enter the full name on your card",
},
cardNumber: {
value: "",
error: false,
errorMessage: "Please enter your card number",
},
expDate: {
value: "",
error: false,
errorMessage: "Please enter the expiry date of your card",
},
cvv: {
value: "",
error: false,
errorMessage: "Please enter your cvv",
},
};
const [paymentFormValues, setPaymentFormValues] = React.useState(
INITIAL_PAYMENT_FORM_STATE
);
const [productList, setProductList] = React.useState([]);
const [totalAmount, setTotalAmount] = React.useState(0);
const [cardType, setCardtype] = React.useState("");
const [orderPlacing, setOrderPlacing] = React.useState(false);
const [paymentClientSecret, setPaymentClientSecret] = React.useState("");
const [activeStep, setActiveStep] = React.useState(0);
const stripe = useStripe();
const elements = useElements();
const history = useHistory();
const handlePaymentFormChange = (e) => {
let { name, value } = e.target;
let error = true;
value !== "" ? (error = false) : (error = true);
if (name === "expDate") {
value = value
.replace(
/[^0-9]/g,
"" // To allow only numbers
)
.replace(
/^([2-9])$/g,
"0$1" // To handle 3 > 03
)
.replace(
/^(1{1})([3-9]{1})$/g,
"0$1/$2" // 13 > 01/3
)
.replace(
/^0{1,}/g,
"0" // To handle 00 > 0
)
.replace(
/^([0-1]{1}[0-9]{1})([0-9]{1,2}).*/g,
"$1/$2" // To handle 113 > 11/3
);
}
setPaymentFormValues({
...paymentFormValues,
[name]: {
...paymentFormValues[name],
value,
error,
},
});
};
const handlePaymentFormSubmit = () => {
const formFields = Object.keys(paymentFormValues);
let newFormValues = { ...paymentFormValues }; // new State Values
formFields.map((formField) => {
const currentField = formField;
const currentValue = paymentFormValues[currentField].value;
if (
currentValue === "" ||
(currentField === "cardNumber" &&
!handleCardNumberValidations(currentValue)) ||
(currentField === "expDate" &&
!handleExpDateValidation(currentValue)) ||
(currentField === "cvv" && !handleCvvValidation(currentValue))
) {
newFormValues = {
...newFormValues,
[currentField]: {
...newFormValues[currentField],
error: true,
},
};
}
});
setPaymentFormValues(newFormValues);
let formValidate = Object.values(newFormValues).every(
(element) => element.error === false
);
if (formValidate) {
setActiveStep(activeStep + 1);
}
};
const placeOrder = async () => {
setOrderPlacing(true);
if (paymentClientSecret !== undefined) {
await axios({
method: "post",
url: `/payments/create?total=${Math.round(totalAmount * 100, 2)}`,
}).then((res) => {
// Which Payment METHOD SHALL I USE?
stripe
.confirmCardPayment(res.data.clientSecret, {
payment_method: {
card: "WHAT SHOULD GO HERE INSTEAD ANY CARD/PAYMENT ELEMENT",
billing_details: {.... PAYMENT DETAILS....},
},
})
.then(() => {
//payment confirmation
setOrderPlacing(false);
deleteDataFromDB();
history.replace("/home");
})
.catch((err) => console.log(err));
setPaymentClientSecret(res.data.clientSecret); // its asynchronous
});
console.log("payment Client Secret in Place Order", paymentClientSecret);
}
};
function getStepContent(step) {
switch (step) {
case 0:
return (
<AddressForm
shippingFormValues={shippingFormValues}
handleShippingFormChange={handleShippingFormChange}
/>
);
case 1:
return (
<PaymentForm
paymentFormValues={paymentFormValues}
handlePaymentFormChange={handlePaymentFormChange}
/>
);
case 2:
return (
<Review
totalAmount={totalAmount}
productList={productList}
paymentFormValues={paymentFormValues}
shippingFormValues={shippingFormValues}
cardType={cardType}
/>
);
default:
throw new Error("Unknown step");
}
}
const handleNext = async (e) => {
if (activeStep === 0) {
//validate shipping Address and Submit
e.preventDefault();
await handleShippingFormSubmit();
} else if (activeStep === 1) {
handlePaymentFormSubmit();
} else if (activeStep === 2) {
await placeOrder();
}
};
const handleBack = () => {
setActiveStep(activeStep - 1);
};
return (
<Container component="main" maxWidth="md" sx={{ mb: 4 }}>
<Paper
variant="outlined"
sx={{ my: { xs: 3, md: 6 }, p: { xs: 2, md: 3 } }}
>
<Typography component="h1" variant="h4" align="center">
Checkout
</Typography>
<Stepper activeStep={activeStep} sx={{ pt: 3, pb: 5 }}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{getStepContent(activeStep)}
<Box sx={{ display: "flex", justifyContent: "flex-end" }}>
{activeStep !== 0 && (
<Button onClick={handleBack} sx={{ mt: 3, ml: 1 }}>
Back
</Button>
)}
<Button
disabled={orderPlacing}
variant="contained"
onClick={(e) => handleNext(e)}
sx={{ mt: 3, ml: 1 }}
>
{activeStep === steps.length - 1 ? "Place order" : "Next"}
</Button>
</Box>
</React.Fragment>
</Paper>
<Copyright />
</Container>
)
}
export default Checkout;
PaymentForm.js
export default function PaymentForm({
paymentFormValues,
handlePaymentFormChange,
}) {
return (
<React.Fragment>
<Typography variant="h6" gutterBottom>
Payment method
</Typography>
<form onChange={(e) => handlePaymentFormChange(e)}>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<TextField
required
id="cardName"
name="cardName"
label="Name on card"
fullWidth
variant="standard"
error={paymentFormValues.cardName.error}
value={
paymentFormValues.cardName.value
? paymentFormValues.cardName.value
: ""
}
/>
</Grid>
<Grid item xs={12} md={6}>
<TextField
required
type="number"
id="cardNumber"
name="cardNumber"
label="Card number"
fullWidth
helperText="ex: 4242424242424242 for Testing"
error={paymentFormValues.cardNumber.error}
value={
paymentFormValues.cardNumber.value
? paymentFormValues.cardNumber.value
: ""
}
sx={{
"input[type='number']::-webkit-outer-spin-button": {
WebkitAppearance: "none",
},
"input[type=number]::-webkit-inner-spin-button": {
WebkitAppearance: "none",
},
"input[type='number']": { MozAppearance: "textfield" },
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} md={6}>
<TextField
required
id="expDate"
name="expDate"
label="Expiry date"
fullWidth
helperText="MM/YY"
//onChange={(e) => handleExpDateValidation(e.target.value)}
error={paymentFormValues.expDate.error}
value={
paymentFormValues.expDate.value
? paymentFormValues.expDate.value
: ""
}
variant="standard"
/>
</Grid>
<Grid item xs={12} md={6}>
<TextField
required
id="cvv"
name="cvv"
label="CVV"
sx={{
"input[type='number']::-webkit-outer-spin-button": {
WebkitAppearance: "none",
},
"input[type=number]::-webkit-inner-spin-button": {
WebkitAppearance: "none",
},
"input[type='number']": { MozAppearance: "textfield" },
}}
helperText="Last three digits on signature strip"
fullWidth
error={paymentFormValues.cvv.error}
value={
paymentFormValues.cvv.value ? paymentFormValues.cvv.value : ""
}
variant="standard"
type="password"
/>
</Grid>
</Grid>
</form>
</React.Fragment>
);
}
Review.js
Note: This Component only shows the ProductList, Shipping Details, Payment Details in Exact form shown in template, Where as the Place Order button is in checkout.js and it handles the functionality in PlaceOrder function
Note: Since these files are pretty big therefore I have shown only the relevant code for Step 2 and Step 3, I have not mention the Validation functions in this code as there sole purpose was to check if input is validate or not!
Any Suggestions or solution to this will be really great! Thank You.