pipe + автопокупка после оплаты тарифа

This commit is contained in:
Nastya 2024-08-11 07:42:49 +03:00
parent cf3b60e19f
commit e29e41d8b8
6 changed files with 144 additions and 94 deletions

@ -4,7 +4,7 @@ import { clearUserData, setCustomerAccount, setUser, setUserAccount, useUserStor
import ContactFormModal from "@ui_kit/ContactForm"; import ContactFormModal from "@ui_kit/ContactForm";
import FloatingSupportChat from "@ui_kit/FloatingSupportChat"; import FloatingSupportChat from "@ui_kit/FloatingSupportChat";
import PrivateRoute from "@ui_kit/PrivateRoute"; import PrivateRoute from "@ui_kit/PrivateRoute";
import { useAfterpay } from "@utils/hooks/useAfterpay"; import { useAfterPay } from "@utils/hooks/useAutoPay";
import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher"; import { useUserAccountFetcher } from "@utils/hooks/useUserAccountFetcher";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import type { SuspenseProps } from "react"; import type { SuspenseProps } from "react";
@ -113,7 +113,7 @@ export default function App() {
}, },
}); });
useAfterpay(); useAfterPay();
if (location.state?.redirectTo) if (location.state?.redirectTo)
return ( return (

@ -21,7 +21,7 @@ export default function CreateButtons({ mt }: string) {
}} }}
> >
{ {
user.user._id === "6692e068983ee77f8e1e682e" && user?.user?._id === "6692e068983ee77f8e1e682e" &&
<Link to="/gallery" style={{ textDecoration: "none" }}> <Link to="/gallery" style={{ textDecoration: "none" }}>
<Button <Button
sx={{ sx={{

@ -0,0 +1,38 @@
import moment from "moment";
import { create } from "zustand";
import { devtools } from "zustand/middleware";
interface CartStore {
notEnoughMoneyAmount: number;
siteReadyPayCart: Record<string, string> | null;
}
const initialState: CartStore = {
notEnoughMoneyAmount: 0,
siteReadyPayCart: null
}
//Была попытка оплатить корзину. Тут записанна недостающая сумма.
export const useNotEnoughMoneyAmount = create<CartStore>()(
devtools(
(get, set) => initialState,
{
name: "notEnoughMoneyAmount",
enabled: process.env.NODE_ENV === "development",
trace: true,
actionsBlacklist: "rejected",
}
)
);
export const setNotEnoughMoneyAmount = (amount: number) => useNotEnoughMoneyAmount.setState({ notEnoughMoneyAmount: amount });
export const setSiteReadyPayCart = (flag: Record<string, string> | null) => useNotEnoughMoneyAmount.setState({ siteReadyPayCart: flag });
export const startPayCartProcess = (userId: string) => setSiteReadyPayCart({ [userId]: moment().add(20, 'minutes').format("X") });
export const cancelPayCartProcess = () => setSiteReadyPayCart(null);
export const calcTimeOfReadyPayCart = (deadline: string) => {
const ready = Number(deadline) > Number(moment().format("X"))
if (!ready) {
cancelPayCartProcess()
}
return ready
}

@ -1,76 +0,0 @@
import { useNavigate } from "react-router-dom";
import { setCash } from "@root/cash";
import { useUserStore } from "@root/user";
import { cartApi } from "@api/cart";
import { currencyFormatter } from "../../pages/Tariffs/tariffsUtils/currencyFormatter";
const MINUTE = 1000 * 60;
export const useAfterpay = () => {
const userId = useUserStore((state) => state.userId);
const navigate = useNavigate();
const checkPayment = () => {
const redirectUrl = new URL(window.location.href);
const paymentUserId = redirectUrl.searchParams.get("userid");
redirectUrl.searchParams.set("afterpay", "false");
navigate(redirectUrl);
if (userId !== paymentUserId) {
return;
}
const payCartPendingRequestDeadline = localStorage.getItem(
"payCartPendingRequestDeadline",
);
const deadline = payCartPendingRequestDeadline
? Number(payCartPendingRequestDeadline)
: Date.now() + 20 * MINUTE;
localStorage.setItem("payCartPendingRequestDeadline", deadline.toString());
let tryCount = 0;
tryPayCart();
async function tryPayCart() {
tryCount += 1;
const [data, payCartError] = await cartApi.pay();
if (data !== null)
setCash(
currencyFormatter.format(Number(data.wallet.cash) / 100),
Number(data.wallet.cash),
Number(data.wallet.cash) / 100,
);
if (!payCartError || Date.now() > deadline) {
localStorage.removeItem("payCartPendingRequestDeadline");
return;
}
setTimeout(tryPayCart, tryCount > 5 ? MINUTE : MINUTE / 6);
}
};
const checkParams = () => {
const afterpay = new URLSearchParams(window.location.search).get(
"afterpay",
);
const payCartPendingRequestDeadline = localStorage.getItem(
"payCartPendingRequestDeadline",
);
const deadline = payCartPendingRequestDeadline
? Number(payCartPendingRequestDeadline)
: Date.now() + 20 * MINUTE;
if (Date.now() <= deadline) {
if (afterpay) {
checkPayment();
}
}
};
setInterval(checkParams, 5000);
checkParams();
};

@ -1,41 +1,62 @@
import { payCart } from "@root/api/cart"; import { cartApi } from "@api/cart";
import { setSiteReadyPayCart } from "@root/stores/notEnoughMoneyAmount"; import { useUserStore } from "@/stores/user";
import { useUserStore } from "@root/stores/user";
import moment from "moment"; import moment from "moment";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { useEffect } from "react"; import { useEffect } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { calcTimeOfReadyPayCart, cancelPayCartProcess, startPayCartProcess, useNotEnoughMoneyAmount } from "@/stores/notEnoughMoneyAmount";
export const useAfterPay = () => { export const useAfterPay = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const paymentUserId = searchParams.get("userid");
const userId = useUserStore(store => store.userId) const userId = useUserStore(store => store.userId)
const userAccount = useUserStore(state => state.userAccount);
const siteReadyPayCart = useNotEnoughMoneyAmount(state => state.siteReadyPayCart);
const purpose = searchParams.get("purpose"); const purpose = searchParams.get("purpose");
const from = searchParams.get("from") || "hub"; const paymentUserId = searchParams.get("userid");
const action = searchParams.get("action");
useEffect(() => { useEffect(() => {
//Звёзды сошлись, будем оплачивать корзину //Звёзды сошлись, будем оплачивать корзину
if (from !== "quiz" && paymentUserId && paymentUserId === userId) { if (paymentUserId && paymentUserId === userId) {
//Чистим url адрес от параметров. (Если нет action. Если есть - значит мы пришли из квиза)
if (action === null) navigate(`/tariffs`, {
replace: true,
});
if (purpose === "paycart") { if (purpose === "paycart") {
setSearchParams({}, { replace: true });
(async () => { (async () => {
//Проверяем можем ли мы оплатить корзину здесь и сейчас //Проверяем можем ли мы оплатить корзину здесь и сейчас
const [, payCartError] = await payCart(); const [, payCartError] = await cartApi.pay();
console.log("попытка оплаты не удалась")
if (payCartError) { if (payCartError) {
//Не получилось купить корзину. Ставим флаг, что сайт в состоянии ожидания пополнения счёта для оплаты (потом проверим .isAfter) //Не получилось купить корзину. Ставим флаг, что сайт в состоянии ожидания пополнения счёта для оплаты
setSiteReadyPayCart({ [paymentUserId]: moment().add(20, 'minutes').format("x") }) startPayCartProcess(paymentUserId)
} else { } else {
enqueueSnackbar("Товары успешно приобретены") enqueueSnackbar("Товары успешно приобретены")
cancelPayCartProcess()
} }
})() })()
} }
} }
}, [purpose, from, paymentUserId]) }, [purpose, paymentUserId])
useEffect(() => {
if (userId !== null && siteReadyPayCart !== null && siteReadyPayCart[userId] !== undefined) {
const deadline = siteReadyPayCart[userId]
if (calcTimeOfReadyPayCart(deadline)) {
//Время ещё не вышло. У нас стоит флаг покупать корзину если время не вышло.
(async () => {
console.log("Время ещё не вышло. У нас стоит флаг покупать корзину если время не вышло.")
const [, payCartError] = await cartApi.pay();
if (!payCartError) {
enqueueSnackbar("Товары успешно приобретены")
cancelPayCartProcess()
}
})()
}
}
}, [userAccount, userId, siteReadyPayCart])
}

@ -0,0 +1,67 @@
import { Ticket, UserAccount, UserName, getAuthToken, useSSESubscription } from "@frontend/kitui"
import { useUserStore } from "@root/user";
import { useSSETab } from "./useSSETab";
import { cancelPayCartProcess } from "@/stores/notEnoughMoneyAmount";
import { setCash } from "@/stores/cash";
import { currencyFormatter } from "@/pages/Tariffs/tariffsUtils/currencyFormatter";
import { inCart } from "@/pages/Tariffs/Tariffs";
type Ping = [{ event: "ping" }]
type SomeChange = [{
"id": UserAccount["_id"],
"userId": UserAccount["userId"],
"cart": UserAccount["cart"],
"wallet": {
"cash": UserAccount["wallet"]["cash"],
"currency": UserAccount["wallet"]["currency"],
"spent": UserAccount["wallet"]["spent"],
"purchasesAmount": UserAccount["wallet"]["purchasesAmount"],
"money": UserAccount["wallet"]["money"],
"lastPaymentId": string;
},
"name": UserName,
"status": UserAccount["status"],
"isDeleted": UserAccount["isDeleted"],
"createdAt": UserAccount["createdAt"];
"updatedAt": UserAccount["updatedAt"];
"from": string;
"partner": string;
}]
type PipeMessage = Ping | SomeChange
export const usePipeSubscriber = () => {
const token = getAuthToken();
const userId = useUserStore((state) => state.userId);
const { isActiveSSETab, updateSSEValue } = useSSETab<Ticket[]>("pipe");
useSSESubscription({
enabled: Boolean(token) && Boolean(userId) && isActiveSSETab,
url:
process.env.REACT_APP_DOMAIN +
`/customer/v1.0.1/account/pipe?Authorization=${token}`,
onNewData: (data) => {
let message = data[0] as PipeMessage
updateSSEValue(message)
//Пропускаем пингование
if ('event' in message && message.event === "ping") return
if ('cart' in message) {
//корзина была изменена, мы больше не хотим покупать
cancelPayCartProcess()
inCart()
}
else if ('wallet' in message) {
setCash(
currencyFormatter.format(Number(message.wallet) / 100),
Number(message.wallet),
Number(message.wallet) / 100,
);
}
},
marker: "pipe",
});
}