fix: cart payment

This commit is contained in:
IlyaDoronin 2024-04-08 15:56:14 +03:00
parent 3074ca383e
commit 464c92392a
3 changed files with 362 additions and 304 deletions

@ -15,10 +15,12 @@ interface PaymentBody {
}
export async function sendPayment({
userId,
body,
fromSquiz,
paymentPurpose,
}: {
userId: string;
body: PaymentBody;
fromSquiz: boolean;
paymentPurpose: "paycart" | "replenishwallet";
@ -44,9 +46,11 @@ export async function sendPayment({
},
phoneNumber: "79000000000",
login: "login_test",
returnUrl: `https://${isStaging}hub.pena.digital/afterpay?from=${fromSquiz ? "quiz" : "hub"}&purpose=${paymentPurpose}`,
...body
}
returnUrl: `https://${isStaging}hub.pena.digital/afterpay?from=${
fromSquiz ? "quiz" : "hub"
}&purpose=${paymentPurpose}&userid=${userId}`,
...body,
},
});
return [sendPaymentResponse];

@ -1,3 +1,4 @@
import { useState, useEffect } from "react";
import {
Box,
Button,
@ -8,7 +9,7 @@ import {
import { payCart } from "@root/api/cart";
import { useUserStore } from "@root/stores/user";
import wallet_icon from "@root/assets/Icons/ColorWallet.svg";
import { Link } from "react-router-dom";
import { Link, useSearchParams } from "react-router-dom";
const MINUTE = 1000 * 60;
@ -54,12 +55,60 @@ const { domain, pathname } = (() => {
})();
export default () => {
const [redirectUrl, setRedirectUrl] = useState<string>("/");
const theme = useTheme();
const phone = useMediaQuery(theme.breakpoints.down(375));
const userId = useUserStore((state) => state.user?._id);
const redirectUrl = new URL(`https://${domain}.pena.digital${pathname}`);
redirectUrl.searchParams.append("afterpay", "true");
redirectUrl.searchParams.append("userid", userId ?? "");
const [searchParams] = useSearchParams();
const paymentUserId = searchParams.get("userid");
useEffect(() => {
const from = searchParams.get("from") || "hub";
const purpose = searchParams.get("purpose");
if (purpose === "paycart" || from === "quiz") {
let tryCount = 0;
if (userId !== paymentUserId) {
return;
}
const payCartPendingRequestDeadline = localStorage.getItem(
"payCartPendingRequestDeadline"
);
const deadline = payCartPendingRequestDeadline
? Number(payCartPendingRequestDeadline)
: Date.now() + 20 * MINUTE;
localStorage.setItem(
"payCartPendingRequestDeadline",
deadline.toString()
);
tryPayCart();
async function tryPayCart() {
tryCount += 1;
const [, payCartError] = await payCart();
if (!payCartError || Date.now() > deadline) {
localStorage.removeItem("payCartPendingRequestDeadline");
return;
}
setTimeout(tryPayCart, tryCount > 10 ? MINUTE / 60 : MINUTE / 6);
}
}
const host = window.location.hostname;
const domain = (host.includes("s") ? "s" : "") + from;
const pathname = from === "hub" ? "/tariffs" : "/list";
setRedirectUrl(
`https://${domain}.pena.digital${pathname}?afterpay=${true}&userid=${userId}`
);
}, []);
return (
<Box

@ -1,12 +1,12 @@
import SectionWrapper from "@components/SectionWrapper";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import {
Box,
Button,
IconButton,
Typography,
useMediaQuery,
useTheme
Box,
Button,
IconButton,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { activatePromocode } from "@root/api/promocode";
import { sendPayment, sendRSPayment } from "@root/api/wallet";
@ -33,315 +33,320 @@ import { WarnModal } from "./WarnModal";
import { mutate } from "swr";
type PaymentMethod = {
label: string;
name: string;
image: string;
unpopular?: boolean;
label: string;
name: string;
image: string;
unpopular?: boolean;
};
const paymentMethods: PaymentMethod[] = [
{ label: "Банковская карта", name: "bankCard", image: bankCardLogo },
{ label: "Тинькофф", name: "tinkoffBank", image: tinkoffLogo },
{ label: "СБП", name: "sbp", image: spbLogo },
{ label: "SberPay", name: "sberbank", image: sberpayLogo },
{ label: "B2B Сбербанк", name: "b2bSberbank", image: b2bLogo },
{ label: "ЮMoney", name: "yoomoney", image: umoneyLogo },
{ label: "Банковская карта", name: "bankCard", image: bankCardLogo },
{ label: "Тинькофф", name: "tinkoffBank", image: tinkoffLogo },
{ label: "СБП", name: "sbp", image: spbLogo },
{ label: "SberPay", name: "sberbank", image: sberpayLogo },
{ label: "B2B Сбербанк", name: "b2bSberbank", image: b2bLogo },
{ label: "ЮMoney", name: "yoomoney", image: umoneyLogo },
];
type PaymentMethodType = (typeof paymentMethods)[number]["name"];
export default function Payment() {
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const [promocodeField, setPromocodeField] = useState<string>("");
const [selectedPaymentMethod, setSelectedPaymentMethod] =
useState<PaymentMethodType | null>("");
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
const [sorryModalOpen, setSorryModalOpen] = useState<boolean>(false);
const [paymentValueField, setPaymentValueField] = useState<string>("0");
const [paymentLink, setPaymentLink] = useState<string>("");
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false);
const location = useLocation();
const verificationStatus = useUserStore((state) => state.verificationStatus);
const navigate = useNavigate();
const handleCustomBackNavigation = useHistoryTracker();
const theme = useTheme();
const upMd = useMediaQuery(theme.breakpoints.up("md"));
const upSm = useMediaQuery(theme.breakpoints.up("sm"));
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const [promocodeField, setPromocodeField] = useState<string>("");
const [selectedPaymentMethod, setSelectedPaymentMethod] =
useState<PaymentMethodType | null>("");
const [warnModalOpen, setWarnModalOpen] = useState<boolean>(false);
const [sorryModalOpen, setSorryModalOpen] = useState<boolean>(false);
const [paymentValueField, setPaymentValueField] = useState<string>("0");
const [paymentLink, setPaymentLink] = useState<string>("");
const [fromSquiz, setIsFromSquiz] = useState<boolean>(false);
const location = useLocation();
const verificationStatus = useUserStore((state) => state.verificationStatus);
const userId = useUserStore((state) => state.userId);
const navigate = useNavigate();
const handleCustomBackNavigation = useHistoryTracker();
const notEnoughMoneyAmount = (location.state?.notEnoughMoneyAmount as number) ?? 0;
const notEnoughMoneyAmount =
(location.state?.notEnoughMoneyAmount as number) ?? 0;
console.log("notEnoughMoneyAmount ", paymentValueField)
const paymentValue = parseFloat(paymentValueField) * 100;
console.log("notEnoughMoneyAmount ", paymentValueField);
const paymentValue = parseFloat(paymentValueField) * 100;
useLayoutEffect(() => {
console.log("notEnoughMoneyAmount ", notEnoughMoneyAmount)
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
const params = new URLSearchParams(window.location.search);
const fromSquiz = params.get("action");
if (fromSquiz === "squizpay") {
setIsFromSquiz(true);
setPaymentValueField((Number(params.get("dif") || "0") / 100).toString());
}
history.pushState(null, document.title, "/payment");
console.log(fromSquiz);
}, []);
async function handleChoosePaymentClick() {
if (!selectedPaymentMethod) {
enqueueSnackbar("Введите метод оплаты");
return;
}
if (Number(paymentValueField) === 0) {
enqueueSnackbar("Введите сумму");
return;
}
if (selectedPaymentMethod !== "rspay") {
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
fromSquiz,
body: {
type: selectedPaymentMethod,
amount: Math.trunc(Number(paymentValueField) * 100),
},
paymentPurpose: notEnoughMoneyAmount ? "paycart" : "replenishwallet",
});
if (sendPaymentError) {
return enqueueSnackbar(sendPaymentError);
}
if (sendPaymentResponse) {
setPaymentLink(sendPaymentResponse.link);
}
return;
} else {
if (verificationStatus !== VerificationStatus.VERIFICATED) {
setWarnModalOpen(true);
return;
}
if (Number(paymentValueField) < 900) {
enqueueSnackbar("Минимальная сумма 900р");
return;
}
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
if (sendRSPaymentError) {
return enqueueSnackbar(sendRSPaymentError);
}
enqueueSnackbar(
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
);
navigate("/settings");
}
useLayoutEffect(() => {
console.log("notEnoughMoneyAmount ", notEnoughMoneyAmount);
setPaymentValueField((notEnoughMoneyAmount / 100).toString());
const params = new URLSearchParams(window.location.search);
const fromSquiz = params.get("action");
if (fromSquiz === "squizpay") {
setIsFromSquiz(true);
setPaymentValueField((Number(params.get("dif") || "0") / 100).toString());
}
history.pushState(null, document.title, "/payment");
console.log(fromSquiz);
}, []);
async function handleChoosePaymentClick() {
if (!selectedPaymentMethod) {
enqueueSnackbar("Введите метод оплаты");
return;
}
function handleApplyPromocode() {
if (!promocodeField) return;
activatePromocode(promocodeField).then(response => {
enqueueSnackbar(response);
mutate("discounts");
}).catch(error => {
enqueueSnackbar(error.message);
});
if (Number(paymentValueField) === 0) {
enqueueSnackbar("Введите сумму");
return;
}
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: "25px",
mb: "70px",
px: isTablet ? (upMd ? "40px" : "18px") : "20px",
}}
if (selectedPaymentMethod !== "rspay") {
const [sendPaymentResponse, sendPaymentError] = await sendPayment({
userId: userId ?? "",
fromSquiz,
body: {
type: selectedPaymentMethod,
amount: Math.trunc(Number(paymentValueField) * 100),
},
paymentPurpose: notEnoughMoneyAmount ? "paycart" : "replenishwallet",
});
if (sendPaymentError) {
return enqueueSnackbar(sendPaymentError);
}
if (sendPaymentResponse) {
setPaymentLink(sendPaymentResponse.link);
}
return;
} else {
if (verificationStatus !== VerificationStatus.VERIFICATED) {
setWarnModalOpen(true);
return;
}
if (Number(paymentValueField) < 900) {
enqueueSnackbar("Минимальная сумма 900р");
return;
}
const sendRSPaymentError = await sendRSPayment(Number(paymentValueField));
if (sendRSPaymentError) {
return enqueueSnackbar(sendRSPaymentError);
}
enqueueSnackbar(
"Cпасибо за заявку, в течении 24 часов вам будет выставлен счёт для оплаты услуг."
);
navigate("/settings");
}
}
function handleApplyPromocode() {
if (!promocodeField) return;
activatePromocode(promocodeField)
.then((response) => {
enqueueSnackbar(response);
mutate("discounts");
})
.catch((error) => {
enqueueSnackbar(error.message);
});
}
return (
<SectionWrapper
maxWidth="lg"
sx={{
mt: "25px",
mb: "70px",
px: isTablet ? (upMd ? "40px" : "18px") : "20px",
}}
>
<Box
sx={{
mt: "20px",
mb: "40px",
display: "flex",
gap: "10px",
}}
>
{!upMd && (
<IconButton
onClick={handleCustomBackNavigation}
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Способ оплаты</Typography>
</Box>
{!upMd && (
<Typography variant="body2" mb="30px">
Выберите способ оплаты
</Typography>
)}
<Box
sx={{
backgroundColor: upMd ? "white" : undefined,
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: upMd ? cardShadow : undefined,
}}
>
<Box
sx={{
width: upMd ? "68.5%" : undefined,
p: upMd ? "20px" : undefined,
display: "flex",
flexDirection: "column",
alignItems: "start",
gap: "40px",
}}
>
<Box
sx={{
mt: "20px",
mb: "40px",
display: "flex",
gap: "10px",
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: upSm ? "row" : "column",
flexWrap: "wrap",
gap: upMd ? "14px" : "20px",
alignContent: "start",
}}
>
{paymentMethods.map(({ name, label, image, unpopular = false }) => (
<PaymentMethodCard
isSelected={selectedPaymentMethod === name}
key={name}
label={label}
image={image}
onClick={() => {
setSelectedPaymentMethod(name);
setPaymentLink("");
}}
>
{!upMd && (
<IconButton
onClick={handleCustomBackNavigation}
sx={{ p: 0, height: "28px", width: "28px", color: "black" }}
>
<ArrowBackIcon />
</IconButton>
)}
<Typography variant="h4">Способ оплаты</Typography>
</Box>
{!upMd && (
<Typography variant="body2" mb="30px">
Выберите способ оплаты
</Typography>
unpopular={false}
/>
))}
<PaymentMethodCard
isSelected={selectedPaymentMethod === "rspay"}
label={"Расчётный счёт"}
image={rsPayLogo}
onClick={async () => {
setSelectedPaymentMethod("rspay");
setPaymentLink("");
}}
unpopular={false}
/>
</Box>
<CollapsiblePromocodeField
fieldValue={promocodeField}
onFieldChange={setPromocodeField}
onPromocodeApply={handleApplyPromocode}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "start",
color: theme.palette.gray.dark,
width: upMd ? "31.5%" : undefined,
p: upMd ? "20px" : undefined,
pl: upMd ? "33px" : undefined,
mt: upMd ? undefined : "30px",
borderLeft: upMd
? `1px solid ${theme.palette.gray.main}`
: undefined,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
maxWidth: "85%",
}}
>
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
<Typography mb="20px">К оплате</Typography>
{paymentLink ? (
<Typography
sx={{
fontWeight: 500,
fontSize: "20px",
lineHeight: "48px",
mb: "28px",
}}
>
{currencyFormatter.format(paymentValue / 100)}
</Typography>
) : (
<InputTextfield
TextfieldProps={{
placeholder: "К оплате",
value: paymentValueField,
type: "number",
}}
onChange={(e) => {
const value = parseFloat(
e.target.value.replace(/^0+(?=\d\.)/, "")
);
setPaymentValueField(isNaN(value) ? "" : value.toString());
}}
id="payment-amount"
gap={upMd ? "16px" : "10px"}
color={"#F2F3F7"}
FormInputSx={{ mb: "28px" }}
/>
)}
<Box
sx={{
backgroundColor: upMd ? "white" : undefined,
display: "flex",
flexDirection: upMd ? "row" : "column",
borderRadius: "12px",
boxShadow: upMd ? cardShadow : undefined,
}}
</Box>
{paymentLink ? (
<Button
variant="pena-outlined-light"
component="a"
href={paymentLink}
sx={{
mt: "auto",
color: "black",
border: `1px solid ${theme.palette.purple.main}`,
"&:hover": {
backgroundColor: theme.palette.purple.dark,
border: `1px solid ${theme.palette.purple.dark}`,
},
}}
>
<Box
sx={{
width: upMd ? "68.5%" : undefined,
p: upMd ? "20px" : undefined,
display: "flex",
flexDirection: "column",
alignItems: "start",
gap: "40px",
}}
>
<Box
sx={{
width: "100%",
display: "flex",
flexDirection: upSm ? "row" : "column",
flexWrap: "wrap",
gap: upMd ? "14px" : "20px",
alignContent: "start",
}}
>
{paymentMethods.map(({ name, label, image, unpopular = false }) => (
<PaymentMethodCard
isSelected={selectedPaymentMethod === name}
key={name}
label={label}
image={image}
onClick={() => {
setSelectedPaymentMethod(name);
setPaymentLink("");
}}
unpopular={false}
/>
))}
<PaymentMethodCard
isSelected={selectedPaymentMethod === "rspay"}
label={"Расчётный счёт"}
image={rsPayLogo}
onClick={async () => {
setSelectedPaymentMethod("rspay");
setPaymentLink("");
}}
unpopular={false}
/>
</Box>
<CollapsiblePromocodeField
fieldValue={promocodeField}
onFieldChange={setPromocodeField}
onPromocodeApply={handleApplyPromocode}
/>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "start",
color: theme.palette.gray.dark,
width: upMd ? "31.5%" : undefined,
p: upMd ? "20px" : undefined,
pl: upMd ? "33px" : undefined,
mt: upMd ? undefined : "30px",
borderLeft: upMd
? `1px solid ${theme.palette.gray.main}`
: undefined,
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
maxWidth: "85%",
}}
>
{upMd && <Typography mb="56px">Выберите способ оплаты</Typography>}
<Typography mb="20px">К оплате</Typography>
{paymentLink ? (
<Typography
sx={{
fontWeight: 500,
fontSize: "20px",
lineHeight: "48px",
mb: "28px",
}}
>
{currencyFormatter.format(paymentValue / 100)}
</Typography>
) : (
<InputTextfield
TextfieldProps={{
placeholder: "К оплате",
value: paymentValueField,
type: "number",
}}
onChange={(e) => {
const value = parseFloat(e.target.value.replace(/^0+(?=\d\.)/, ""));
setPaymentValueField(isNaN(value) ? "" : value.toString());
}}
id="payment-amount"
gap={upMd ? "16px" : "10px"}
color={"#F2F3F7"}
FormInputSx={{ mb: "28px" }}
/>
)}
</Box>
{paymentLink ? (
<Button
variant="pena-outlined-light"
component="a"
href={paymentLink}
sx={{
mt: "auto",
color: "black",
border: `1px solid ${theme.palette.purple.main}`,
"&:hover": {
backgroundColor: theme.palette.purple.dark,
border: `1px solid ${theme.palette.purple.dark}`,
},
}}
>
Оплатить
</Button>
) : (
<Button
variant="pena-outlined-light"
disabled={!isFinite(paymentValue)}
onClick={handleChoosePaymentClick}
sx={{
mt: "auto",
color: "black",
border: `1px solid ${theme.palette.purple.main}`,
"&:hover": {
color: "white",
},
"&:active": {
color: "white",
},
}}
>
Выбрать
</Button>
)}
</Box>
</Box>
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
<SorryModal open={sorryModalOpen} setOpen={setSorryModalOpen} />
</SectionWrapper>
);
Оплатить
</Button>
) : (
<Button
variant="pena-outlined-light"
disabled={!isFinite(paymentValue)}
onClick={handleChoosePaymentClick}
sx={{
mt: "auto",
color: "black",
border: `1px solid ${theme.palette.purple.main}`,
"&:hover": {
color: "white",
},
"&:active": {
color: "white",
},
}}
>
Выбрать
</Button>
)}
</Box>
</Box>
<WarnModal open={warnModalOpen} setOpen={setWarnModalOpen} />
<SorryModal open={sorryModalOpen} setOpen={setSorryModalOpen} />
</SectionWrapper>
);
}