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
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.
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 theclient_secret
and confirmit using Elements.
The alternative would be what @qichuan posted above.