skip to Main Content

I am trying to create a cart page, in which the client/customer can edit/remove cart items(items in the cart). So when they edit/remove items in the cart the total changes.

Currently, if I add items to cart and enter the cart page the payment gateway will be shown with the total (let’s say its $100). And then I edit the cart items (the price increase/decreases, let’s say now the price is $50). Now if I press "Pay Now" the payment gateway will charge me $100 and not $50.

I want to get the stripe payment gateway to change the cart total in the transaction.

code below is the code from PaymentGateway.jsx

import React, { useEffect, useState } from "react";
import CheckoutForm from "./CheckoutForm";
import { loadStripe } from "@stripe/stripe-js";
import axios from "axios";
import { Elements } from "@stripe/react-stripe-js";

const PaymentGateway = ({ cart, handleCart, handleValidation }) => {
    //set cart total and watch for changes in the variable to rerender component
    const [cartTotal, setCartTotal] = useState(cart.total);

    // Stripe functions
    const [stripePromise, setStripePromise] = useState(() =>
        loadStripe('stripe public key' )
    );
    const [clientSecret, setClientSecret] = useState("");
    const appearance = {
        theme: "flat",
    };
    let options = {
        clientSecret: clientSecret,
        appearance: appearance,
    };
    useEffect(() => {
        if (cartTotal > 0) {
            console.log(cartTotal);
            axios
                .post("/api/stripe/create-payment-intent", {
                    total: cartTotal,
                })
                .then((data) => setClientSecret(() => data.data?.clientSecret));
        }
    }, [cartTotal]);

    useEffect(() => {
        setCartTotal(cart.total);
    }, [cart.total]);

    return (
        <div>
            <>
                <div className="cartPage-containerRight-title">
                    Payment Information
                </div>
                {clientSecret && (
                    <>
                        <Elements stripe={stripePromise} options={options}>
                            <CheckoutForm
                                handleCart={handleCart}
                                handleValidation={handleValidation}
                            />
                        </Elements>
                    </>
                )}
            </>
        </div>
    );
};

export default PaymentGateway;

the code below is from CheckoutForm.jsx

import React, { useEffect, useState } from "react";
import {
    useStripe,
    useElements,
    PaymentElement,
    LinkAuthenticationElement,
} from "@stripe/react-stripe-js";

const CheckoutForm = ({ handleCart, handleValidation }) => {
    const stripe = useStripe();

    const elements = useElements();
    const [message, setMessage] = useState("");
    const [isLoading, setIsLoading] = useState(false);

    useEffect(() => {
        if (!stripe) {
            return;
        }

        const clientSecret = new URLSearchParams(window.location.search).get(
            "payment_intent_client_secret"
        );

        if (!clientSecret) {
            return;
        }

        stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
            switch (paymentIntent.status) {
                case "succeeded":
                    setMessage("Payment succeeded!");
                    break;
                case "processing":
                    setMessage("Your payment is processing.");
                    break;
                case "requires_payment_method":
                    setMessage(
                        "Your payment was not successful, please try again."
                    );
                    break;
                default:
                    setMessage("Something went wrong.");
                    break;
            }
        });
    }, [stripe]);

    const handleSubmit = async (event) => {
        event.preventDefault();
        if (handleValidation()) {
            // We don't want to let default form submission happen here,
            // which would refresh the page.

            if (!stripe || !elements) {
                // Stripe.js hasn't yet loaded.
                // Make sure to disable form submission until Stripe.js has loaded.
                return;
            }

            setIsLoading(true);

            const { error } = await stripe.confirmPayment({
                //`Elements` instance that was used to create the Payment Element
                elements,
                confirmParams: {
                    return_url: "http://localhost:3000",
                },
                redirect: "if_required",
            });

            if (error) {
                // This point will only be reached if there is an immediate error when
                // confirming the payment. Show error to your customer (for example, payment
                // details incomplete)
                setMessage(error.message);
                setIsLoading(false);
            } else {
                handleCart();
                // Your customer will be redirected to your `return_url`. For some payment
                // methods like iDEAL, your customer will be redirected to an intermediate
                // site first to authorize the payment, then redirected to the `return_url`.
            }
        }
    };
    const paymentElementOptions = {
        layout: "tabs",
    };

    return (
        <form onSubmit={handleSubmit} className="cartCheckout-paymentDetails">
            <PaymentElement options={paymentElementOptions} />
            <div className="cartCheckout-button-container">
                <button
                    disabled={isLoading && !stripe && !elements}
                    id="submit"
                    className="cartCheckout-button"
                >
                    <span id="button-text">
                        {isLoading ? (
                            <div className="spinner" id="spinner"></div>
                        ) : (
                            "Pay now"
                        )}
                    </span>
                </button>
            </div>
            {message && (
                <div
                    id="payment-message"
                    style={{ color: "red", textAlign: "center" }}
                >
                    {message}
                </div>
            )}
        </form>
    );
};

export default CheckoutForm;

this is the code block for the payment gateway. I want to re-render the Stripe Payment Element everytime the clientSecret changes.
Or I want to have a way to get the payment gateway to charge only the final amount in the cart and not extra.

2

Answers


  1. Once specified, you can’t change the client_secrete passed to the Elements without remounting it.

    When cart items change, you should consider updating the PaymentIntent’s amount (send an update request from your backend), and call elements.fetchUpdates() to reflect the updates in the Payment Element.

    Login or Signup to reply.
  2. I think this integration would help:
    https://stripe.com/docs/payments/accept-a-payment-deferred

    This is called the ‘deferred Payment Element’. Instead of creating the Intent first (which gives you the client_secret), you create a Payment Element instance with some params that may not be final. Only when your customer submits the form would you then create the Intent to get the client_secret and confirm
    it using Elements.

    The alternative would be what @qichuan posted above.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search