1

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.

  • 1
    To use StripeJS such as `stripe.confirmCardPayment` or `stripe.confirmPayment`, they require Card Element and Payment Element to be used for collecting payment method information. It's part of [PCI DSS](https://stripe.com/docs/security/guide) requirement. You can only collect card information yourself if your PCI level 1 or complete SAQ D. – yuting Jun 05 '23 at 00:15
  • @yuting Thank you so much for this information it makes complete sense to me now – Chirag Lalwani Jun 06 '23 at 21:47

0 Answers0