Files
web/components/PagesComponent/Subscription/StripePayment.jsx
Husanjonazamov 64af77101f classify web
2026-02-24 12:52:49 +05:00

180 lines
5.7 KiB
JavaScript

import React, { useState, useEffect, useCallback } from "react";
import {
Elements,
ElementsConsumer,
PaymentElement,
} from "@stripe/react-stripe-js";
import { loadStripe } from "@stripe/stripe-js";
import { createPaymentIntentApi } from "@/utils/api";
import { toast } from "sonner";
import { t } from "@/utils";
import { Loader2 } from "lucide-react";
const StripePayment = ({
selectedPackage,
packageSettings,
PaymentModalClose,
setShowStripePayment,
setToggleApiAfterPaymentSuccess
}) => {
const [stripePromise, setStripePromise] = useState(null);
const [clientSecret, setClientSecret] = useState("");
const [loading, setLoading] = useState(true);
const [formattedPrice, setFormattedPrice] = useState('')
useEffect(() => {
const loadStripeInstance = async () => {
if (packageSettings?.Stripe?.api_key) {
const stripeInstance = await loadStripe(packageSettings.Stripe.api_key);
setStripePromise(stripeInstance);
}
};
loadStripeInstance();
}, [packageSettings?.Stripe?.api_key]);
const handleStripePayment = useCallback(async () => {
try {
const res = await createPaymentIntentApi.createIntent({
package_id: selectedPackage.id,
payment_method: packageSettings.Stripe.payment_method,
});
if (res.data.error === true) {
toast.error(res.data.message);
return;
}
// Extract payment intent data from response
const paymentIntent =
res.data.data.payment_intent?.payment_gateway_response;
// Extract formatted price from payment_intent (not from payment_gateway_response)
const formattedPrice = res.data.data.payment_intent?.formatted_price;
if (formattedPrice) {
setFormattedPrice(formattedPrice);
}
const clientSecret = paymentIntent.client_secret;
setClientSecret(clientSecret);
setShowStripePayment(true);
} catch (error) {
console.error("Error during Stripe payment", error);
toast.error(t("errorOccurred"));
} finally {
setLoading(false);
}
}, [
selectedPackage.id,
packageSettings?.Stripe?.payment_method,
setShowStripePayment,
]);
useEffect(() => {
handleStripePayment();
}, [handleStripePayment]);
// PaymentForm component that uses the formattedPrice from parent scope
const PaymentForm = ({ elements, stripe }) => {
const [IsProcessing, setIsProcessing] = useState(false);
const handleSubmit = async (event) => {
event.preventDefault();
setIsProcessing(true);
// First, submit the elements to validate and prepare the payment method
// This must be called before confirmPayment
const { error: submitError } = await elements.submit();
if (submitError) {
// Handle validation errors
toast.error(submitError.message || t("paymentFailed"));
setIsProcessing(false);
return;
}
// PaymentElement handles payment method creation internally
// Just confirm the payment with the client secret
try {
const { error: confirmError } = await stripe.confirmPayment({
elements,
clientSecret,
redirect: "if_required",
});
if (confirmError) {
// Handle payment confirmation error
console.error(confirmError.message);
toast.error(confirmError.message || t("paymentFailed"));
} else {
// Payment succeeded
toast.success(t("paymentSuccess"));
setToggleApiAfterPaymentSuccess((prev) => !prev)
PaymentModalClose();
}
} catch (error) {
console.error("Error during payment:", error);
} finally {
setIsProcessing(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div className="stripe_module">
{/* <div className="mb-4 text-lg font-semibold">
{t("pay")} {amountToPay / 100} {currency?.toUpperCase()}
</div> */}
<PaymentElement />
<button
className="w-full bg-primary text-white p-2 rounded-md my-4 flex items-center justify-center"
type="submit"
disabled={!stripePromise || IsProcessing}
>
{IsProcessing ? (
<Loader2 className="size-4 animate-spin" />
) : (
t("pay") + " " + formattedPrice
)}
</button>
</div>
</form>
);
};
return (
<>
{loading ? (
<div className="">
<div className="animate-pulse">
<div className="w-full h-10 bg-gray-200 rounded-md mb-2"></div>
<div className="flex justify-between mb-4">
<div className="w-1/2 h-5 bg-gray-200 rounded-md"></div>
<div className="w-1/4 h-5 bg-gray-200 rounded-md"></div>
</div>
<div className="w-full h-12 bg-gray-200 rounded-md mt-6"></div>
</div>
</div>
) : (
stripePromise &&
clientSecret && (
<div className="card">
{/* <div className="card-header">{t("payWithStripe")} :</div> */}
<div className="card-body">
<Elements stripe={stripePromise} options={{
clientSecret
}} >
<ElementsConsumer>
{({ stripe, elements }) => (
<PaymentForm elements={elements} stripe={stripe} />
)}
</ElementsConsumer>
</Elements>
</div>
</div>
)
)}
</>
);
};
export default StripePayment;