462 lines
14 KiB
React
462 lines
14 KiB
React
import { FlutterWaveButton, closePaymentModal } from "flutterwave-react-v3";
|
|
import React, { useState } from "react";
|
|
import { useSelector } from "react-redux";
|
|
import { toast } from "react-toastify";
|
|
import debounce from "../../../hooks/debounce";
|
|
import usersService from "../../../services/UsersService";
|
|
import LoadingSpinner from "../../Spinners/LoadingSpinner";
|
|
|
|
/**
|
|
* Renders a React component that displays the description and last four digits of a payment card.
|
|
*/
|
|
function ThePaymentText({ value, type }) {
|
|
const { cardNum } = value;
|
|
let description = value.description;
|
|
let digits = value.digits;
|
|
|
|
if (type === "new") {
|
|
const firstDigit = cardNum[0];
|
|
if (firstDigit === "4") {
|
|
description = "Visa";
|
|
} else if (firstDigit === "5") {
|
|
description = "Master";
|
|
} else {
|
|
description = "ATM";
|
|
}
|
|
digits = cardNum.slice(-4);
|
|
}
|
|
|
|
return (
|
|
<div className="my-2 flex items-center gap-5">
|
|
<div className="card-details flex items-center gap-3">
|
|
<h1 className="text-xl font-normal text-dark-gray dark:text-white tracking-tighter my-1 space-x-1">
|
|
{description} Card
|
|
</h1>
|
|
<p className="text-xl font-normal text-dark-gray dark:text-white tracking-wide">
|
|
Bank **************{digits}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Renders the amount of a transaction in a specific currency.
|
|
* @returns {JSX.Element} - The rendered component.
|
|
*/
|
|
function AmountSection({ currency, amount, country }) {
|
|
const formattedAmount = (+amount * 0.01)?.toFixed(2);
|
|
const gapClassName = country === "US" ? "gap-14" : "gap-4";
|
|
|
|
return (
|
|
<div className={`flex items-center ${gapClassName}`}>
|
|
<h1 className="text-xl font-bold text-dark-gray dark:text-white tracking-tighter my-1">
|
|
Amount({currency})
|
|
</h1>
|
|
<span className="text-xl font-normal text-dark-gray dark:text-white tracking-tighter my-1">
|
|
{formattedAmount}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Renders the transaction fee for a payment.
|
|
* @returns {JSX.Element} - Rendered JSX displaying the transaction fee with the label "Transaction Fee".
|
|
*/
|
|
function TransactionFeeSection({ currency, fee, country }) {
|
|
const formattedFee = (+fee).toFixed(2);
|
|
const gapClass = country === "US" ? "gap-[2.7rem]" : "gap-4";
|
|
|
|
return (
|
|
<div className={`flex items-center border-b border-gray-600 ${gapClass}`}>
|
|
<h1 className="text-xl font-bold text-dark-gray dark:text-white tracking-tighter my-1">
|
|
Transaction Fee
|
|
</h1>
|
|
<span className="text-xl font-normal text-dark-gray dark:text-white tracking-tighter my-1">
|
|
{formattedFee}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Calculates the total amount by adding the `amount` and `fee` values together.
|
|
* Formats the total amount to two decimal places and displays it.
|
|
* @returns {JSX.Element} - The TotalSection component.
|
|
*/
|
|
function TotalSection({ currency, amount, fee, country }) {
|
|
const total = Number(amount) + Number(fee);
|
|
const formattedTotal = (total * 0.01)?.toFixed(2);
|
|
|
|
const gap = country === "US" ? "gap-[8rem]" : "gap-[6.3rem]";
|
|
|
|
return (
|
|
<div className={`flex items-center ${gap}`}>
|
|
<h1 className="text-xl font-bold text-dark-gray dark:text-white tracking-tighter my-1">
|
|
Total
|
|
</h1>
|
|
<span className="text-xl font-normal text-dark-gray dark:text-white tracking-tighter my-1">
|
|
{formattedTotal}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ConfirmAddFund({
|
|
confirmCredit,
|
|
onClose,
|
|
walletItem,
|
|
setConfirmCredit,
|
|
}) {
|
|
const __confirmData = confirmCredit?.data;
|
|
const __confirmCountry = walletItem?.country;
|
|
const __confirmCardDetails =
|
|
__confirmData.cardType === "prev"
|
|
? JSON.parse(__confirmData.card)
|
|
: __confirmData.card;
|
|
|
|
const apiURL = new usersService();
|
|
|
|
const { userDetails } = useSelector((state) => state?.userDetails);
|
|
|
|
const [requestStatus, setRequestStatus] = useState({
|
|
message: "",
|
|
loading: false,
|
|
status: false,
|
|
});
|
|
|
|
const config = {
|
|
public_key: __confirmData?.flutterwave_key,
|
|
tx_ref: __confirmData?.credit_reference,
|
|
currency: "NGN",
|
|
amount: Number(__confirmData.amount) * 0.01,
|
|
payment_options: "card,mobilemoney,ussd",
|
|
customer: {
|
|
email: userDetails.email,
|
|
phone_number: userDetails.phone,
|
|
name: `${userDetails.lastname} ${userDetails.firstname}`,
|
|
},
|
|
customizations: {
|
|
title: "WrenchBoard",
|
|
description: "Add Credit Payment",
|
|
logo: "https://www.wrenchboard.com/assets/images/wrench-500-500-icon.png",
|
|
},
|
|
};
|
|
|
|
const fwConfig = {
|
|
...config,
|
|
text: "Proceed",
|
|
callback: (response) => {
|
|
debouncedSuccessPayment(); //delays the call for 5 secs
|
|
|
|
setTimeout(() => {
|
|
closePaymentModal();
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
data: response,
|
|
}));
|
|
}, 1500);
|
|
},
|
|
};
|
|
|
|
const onSuccessPayment = () => {
|
|
setRequestStatus({ message: "", loading: true, status: false });
|
|
const reqData = { tx_ref: __confirmData?.credit_reference };
|
|
console.log("**** onSuccessPayment **** THIS WAS REACHED");
|
|
apiURL
|
|
.resultTopUp(reqData)
|
|
.then((res) => {
|
|
if (res.data.internal_return < 0) {
|
|
setRequestStatus({
|
|
message: "Could not finish transaction",
|
|
loading: false,
|
|
status: false,
|
|
});
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
setRequestStatus({
|
|
message: "Opps! An Error Occured",
|
|
loading: false,
|
|
status: false,
|
|
});
|
|
|
|
setTimeout(() => {
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
data: err,
|
|
}));
|
|
}, 1500);
|
|
toast.success("Opps! something went wrong");
|
|
});
|
|
};
|
|
|
|
const debouncedSuccessPayment = debounce(onSuccessPayment, 5000);
|
|
|
|
/**
|
|
* Handles the process of making a payment using a previously saved card.
|
|
* Updates the state to show a loader while the payment is being processed,
|
|
* sends a request to the server to make the payment, and updates the state with the response.
|
|
* If the payment is successful, it also dispatches an action to reload the wallet table.
|
|
*/
|
|
const handlePrevCard = async () => {
|
|
try {
|
|
// Show loader while the payment is being processed
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
acceptConfirm: { loader: true },
|
|
},
|
|
}));
|
|
|
|
// Extract necessary data from confirmCredit and confirmCardDetails objects
|
|
const { amount, credit_reference, currency } = __confirmData;
|
|
const { card_uid } = __confirmCardDetails;
|
|
|
|
// Create request data object with required parameters for making the payment
|
|
const reqData = {
|
|
amount: amount,
|
|
card_uid,
|
|
credit_reference,
|
|
currency,
|
|
};
|
|
|
|
// Send request to server to make the payment using getPaidPrevCard method of usersService
|
|
const res = await apiURL.getPaidPrevCard(reqData);
|
|
const _response = res.data;
|
|
|
|
// If internal_return value in the response is less than 0, hide the loader and return
|
|
if (_response.internal_return < 0) {
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
}));
|
|
return;
|
|
}
|
|
|
|
// Update state to show the acceptConfirm state and the response data
|
|
setTimeout(() => {
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
data: _response,
|
|
}));
|
|
}, 1500);
|
|
} catch (error) {
|
|
// Handle error and hide the loader
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
}));
|
|
console.log(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Handles the payment process when a new card is used.
|
|
* @async
|
|
*/
|
|
const handleNewCard = async () => {
|
|
try {
|
|
// Extract necessary data from __confirmData and __confirmCardDetails
|
|
const { amount, credit_reference, uid } = __confirmData;
|
|
const { address, cardNum, cvv, expirationMonth, expirationYear } =
|
|
__confirmCardDetails;
|
|
|
|
// Set loading state to indicate payment is being processed
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
acceptConfirm: { loader: true },
|
|
},
|
|
}));
|
|
|
|
// Prepare request data
|
|
const reqData = {
|
|
amount: amount,
|
|
cardnumber: cardNum.replace(/\s/g, ""),
|
|
credit_reference,
|
|
cvc: cvv,
|
|
description: address,
|
|
exp_month: expirationMonth,
|
|
exp_year: expirationYear,
|
|
paymenttype: 100,
|
|
uid,
|
|
};
|
|
|
|
// Send request to server to process payment
|
|
const res = await apiURL.getPaidNewCard(reqData);
|
|
const _response = res.data;
|
|
|
|
// Handle response from server
|
|
if (res.data.internal_return < 0) {
|
|
// Payment could not be completed
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
data: _response,
|
|
}));
|
|
} else {
|
|
// Payment was successful
|
|
setTimeout(() => {
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
data: _response,
|
|
}));
|
|
}, 1500);
|
|
}
|
|
} catch (error) {
|
|
// Handle error during payment process
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: true },
|
|
},
|
|
}));
|
|
setTimeout(() => onClose, 10000);
|
|
console.log(error);
|
|
}
|
|
};
|
|
|
|
const getBack = () => {
|
|
setConfirmCredit((prev) => ({
|
|
...prev,
|
|
show: {
|
|
awaitConfirm: { loader: false, state: false },
|
|
acceptConfirm: { loader: false, state: false },
|
|
},
|
|
data: {},
|
|
}));
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="content-wrapper w-full h-[32rem]">
|
|
<div className="w-full mb-10">
|
|
<div className="add-fund w-full bg-white dark:bg-dark-white rounded-2xl">
|
|
<div className="px-4 md:p-8 py-4 add-fund-info">
|
|
<div className="field w-full mb-3 min-h-[45px]">
|
|
{confirmCredit?.show?.awaitConfirm?.state && (
|
|
<div className="flex flex-col gap-2">
|
|
<AmountSection
|
|
currency={__confirmData?.currency}
|
|
amount={__confirmData?.amount}
|
|
country={__confirmCountry}
|
|
/>
|
|
<TransactionFeeSection
|
|
currency={__confirmData?.currency}
|
|
fee={__confirmData?.fee}
|
|
country={__confirmCountry}
|
|
/>
|
|
<TotalSection
|
|
currency={__confirmData?.currency}
|
|
amount={__confirmData?.amount}
|
|
fee={__confirmData?.fee}
|
|
country={__confirmCountry}
|
|
/>
|
|
{__confirmCountry === "US" && (
|
|
<div className="flex items-center gap-8">
|
|
<label
|
|
htmlFor="payment"
|
|
className="text-xl font-bold text-dark-gray dark:text-white tracking-tighter my-1"
|
|
>
|
|
Payment Method
|
|
</label>
|
|
<span className="text-[#181c32] dark:text-white">
|
|
<ThePaymentText
|
|
value={__confirmCardDetails}
|
|
type={__confirmData?.cardType}
|
|
/>
|
|
</span>
|
|
</div>
|
|
)}
|
|
<div
|
|
className={`${
|
|
__confirmCountry === "US"
|
|
? "gap-[3.7rem]"
|
|
: "gap-[1.81rem]"
|
|
} flex items-center`}
|
|
>
|
|
<h1 className="text-xl font-bold text-dark-gray dark:text-white tracking-tighter my-1">
|
|
Reference No
|
|
</h1>
|
|
<span className="text-xl font-normal text-dark-gray dark:text-white tracking-tighter my-1">
|
|
{__confirmData?.credit_reference}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className={
|
|
__confirmCountry === "US" ? "min-h-[96px]" : "min-h-[157px]"
|
|
}
|
|
></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="modal-footer-wrapper flex justify-end items-center gap-4">
|
|
<button
|
|
className="custom-btn bg-[#f5a430] text-black text-base"
|
|
onClick={getBack}
|
|
>
|
|
Back
|
|
</button>
|
|
{__confirmCountry === "US" && (
|
|
<button
|
|
className="custom-btn btn-gradient text-white text-base"
|
|
onClick={
|
|
__confirmData?.cardType === "prev"
|
|
? handlePrevCard
|
|
: handleNewCard
|
|
}
|
|
>
|
|
{confirmCredit?.show?.acceptConfirm?.loader ? (
|
|
<LoadingSpinner size="6" color="sky-blue" />
|
|
) : (
|
|
"Proceed"
|
|
)}
|
|
</button>
|
|
)}
|
|
{__confirmCountry === "NG" && (
|
|
<FlutterWaveButton
|
|
{...fwConfig}
|
|
className="px-2 py-1 h-11 flex justify-center items-center btn-gradient text-base rounded-full text-white"
|
|
/>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default ConfirmAddFund;
|