Started implementing the Paymob integration
This commit is contained in:
@@ -47,6 +47,7 @@
|
||||
"next": "13.1.6",
|
||||
"nodemailer": "^6.9.5",
|
||||
"nodemailer-express-handlebars": "^6.1.0",
|
||||
"paymob-react": "git+https://github.com/tiago-ecrop/paymob-react-oman.git",
|
||||
"primeicons": "^6.0.1",
|
||||
"primereact": "^9.2.3",
|
||||
"qrcode": "^1.5.3",
|
||||
|
||||
47
src/components/PaymobPayment.tsx
Normal file
47
src/components/PaymobPayment.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import {DurationUnit} from "@/interfaces/paypal";
|
||||
import {User} from "@/interfaces/user";
|
||||
import axios from "axios";
|
||||
import {useState} from "react";
|
||||
import Button from "./Low/Button";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
currency: string;
|
||||
price: number;
|
||||
title: string;
|
||||
description: string;
|
||||
paymentID: string;
|
||||
duration: number;
|
||||
duration_unit: DurationUnit;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
onSuccess: (duration: number, duration_unit: DurationUnit) => void;
|
||||
}
|
||||
|
||||
export default function PaymobPayment({
|
||||
user,
|
||||
price,
|
||||
currency,
|
||||
title,
|
||||
description,
|
||||
paymentID,
|
||||
duration,
|
||||
duration_unit,
|
||||
setIsLoading,
|
||||
onSuccess,
|
||||
}: Props) {
|
||||
const [iframeURL, setIFrameURL] = useState<string>();
|
||||
|
||||
const handleCardPayment = async () => {
|
||||
try {
|
||||
} catch (error) {
|
||||
console.error("Error starting card payment process:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={handleCardPayment}>Pay</Button>
|
||||
{iframeURL}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -4,19 +4,20 @@ import PayPalPayment from "@/components/PayPalPayment";
|
||||
import useGroups from "@/hooks/useGroups";
|
||||
import usePackages from "@/hooks/usePackages";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import { User } from "@/interfaces/user";
|
||||
import {User} from "@/interfaces/user";
|
||||
import clsx from "clsx";
|
||||
import { capitalize } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import {capitalize} from "lodash";
|
||||
import {useEffect, useState} from "react";
|
||||
import getSymbolFromCurrency from "currency-symbol-map";
|
||||
import useInvites from "@/hooks/useInvites";
|
||||
import { BsArrowRepeat } from "react-icons/bs";
|
||||
import {BsArrowRepeat} from "react-icons/bs";
|
||||
import InviteCard from "@/components/Medium/InviteCard";
|
||||
import { useRouter } from "next/router";
|
||||
import { PayPalScriptProvider } from "@paypal/react-paypal-js";
|
||||
import { usePaypalTracking } from "@/hooks/usePaypalTracking";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import {useRouter} from "next/router";
|
||||
import {PayPalScriptProvider} from "@paypal/react-paypal-js";
|
||||
import {usePaypalTracking} from "@/hooks/usePaypalTracking";
|
||||
import {ToastContainer} from "react-toastify";
|
||||
import useDiscounts from "@/hooks/useDiscounts";
|
||||
import PaymobPayment from "@/components/PaymobPayment";
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
@@ -25,37 +26,24 @@ interface Props {
|
||||
reload: () => void;
|
||||
}
|
||||
|
||||
export default function PaymentDue({
|
||||
user,
|
||||
hasExpired = false,
|
||||
clientID,
|
||||
reload,
|
||||
}: Props) {
|
||||
export default function PaymentDue({user, hasExpired = false, clientID, reload}: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [appliedDiscount, setAppliedDiscount] = useState(0);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { packages } = usePackages();
|
||||
const { discounts } = useDiscounts();
|
||||
const { users } = useUsers();
|
||||
const { groups } = useGroups();
|
||||
const {
|
||||
invites,
|
||||
isLoading: isInvitesLoading,
|
||||
reload: reloadInvites,
|
||||
} = useInvites({ to: user?.id });
|
||||
const {packages} = usePackages();
|
||||
const {discounts} = useDiscounts();
|
||||
const {users} = useUsers();
|
||||
const {groups} = useGroups();
|
||||
const {invites, isLoading: isInvitesLoading, reload: reloadInvites} = useInvites({to: user?.id});
|
||||
const trackingId = usePaypalTracking();
|
||||
|
||||
useEffect(() => {
|
||||
const userDiscounts = discounts.filter((x) =>
|
||||
user.email.endsWith(`@${x.domain}`),
|
||||
);
|
||||
const userDiscounts = discounts.filter((x) => user.email.endsWith(`@${x.domain}`));
|
||||
if (userDiscounts.length === 0) return;
|
||||
|
||||
const biggestDiscount = [...userDiscounts]
|
||||
.sort((a, b) => b.percentage - a.percentage)
|
||||
.shift();
|
||||
const biggestDiscount = [...userDiscounts].sort((a, b) => b.percentage - a.percentage).shift();
|
||||
if (!biggestDiscount) return;
|
||||
|
||||
setAppliedDiscount(biggestDiscount.percentage);
|
||||
@@ -68,9 +56,7 @@ export default function PaymentDue({
|
||||
|
||||
if (userGroups.length === 0) return true;
|
||||
|
||||
const userGroupsAdminTypes = userGroups
|
||||
.map((g) => users?.find((u) => u.id === g.admin)?.type)
|
||||
.filter((t) => !!t);
|
||||
const userGroupsAdminTypes = userGroups.map((g) => users?.find((u) => u.id === g.admin)?.type).filter((t) => !!t);
|
||||
return userGroupsAdminTypes.every((t) => t !== "corporate");
|
||||
};
|
||||
|
||||
@@ -81,9 +67,7 @@ export default function PaymentDue({
|
||||
<div className="absolute left-0 top-0 z-[999] h-screen w-screen overflow-hidden bg-black/60">
|
||||
<div className="absolute left-1/2 top-1/2 flex h-fit w-fit -translate-x-1/2 -translate-y-1/2 animate-pulse flex-col items-center gap-8 text-white">
|
||||
<span className={clsx("loading loading-infinity w-48")} />
|
||||
<span className={clsx("text-2xl font-bold")}>
|
||||
Completing your payment...
|
||||
</span>
|
||||
<span className={clsx("text-2xl font-bold")}>Completing your payment...</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -94,17 +78,9 @@ export default function PaymentDue({
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
onClick={reloadInvites}
|
||||
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out"
|
||||
>
|
||||
<span className="text-mti-black text-lg font-bold">
|
||||
Invites
|
||||
</span>
|
||||
<BsArrowRepeat
|
||||
className={clsx(
|
||||
"text-xl",
|
||||
isInvitesLoading && "animate-spin",
|
||||
)}
|
||||
/>
|
||||
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out">
|
||||
<span className="text-mti-black text-lg font-bold">Invites</span>
|
||||
<BsArrowRepeat className={clsx("text-xl", isInvitesLoading && "animate-spin")} />
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
|
||||
@@ -124,16 +100,11 @@ export default function PaymentDue({
|
||||
)}
|
||||
|
||||
<div className="flex w-full flex-col items-center justify-center gap-4 text-center">
|
||||
{hasExpired && (
|
||||
<span className="text-lg font-bold">
|
||||
You do not have time credits for your account type!
|
||||
</span>
|
||||
)}
|
||||
{hasExpired && <span className="text-lg font-bold">You do not have time credits for your account type!</span>}
|
||||
{isIndividual() && (
|
||||
<div className="scrollbar-hide flex w-full flex-col items-center gap-12 overflow-x-scroll">
|
||||
<span className="max-w-lg">
|
||||
To add to your use of EnCoach, please purchase one of the time
|
||||
packages available below:
|
||||
To add to your use of EnCoach, please purchase one of the time packages available below:
|
||||
</span>
|
||||
<div className="flex w-full flex-wrap justify-center gap-8">
|
||||
<PayPalScriptProvider
|
||||
@@ -142,30 +113,15 @@ export default function PaymentDue({
|
||||
currency: "USD",
|
||||
intent: "capture",
|
||||
commit: true,
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
{packages.map((p) => (
|
||||
<div
|
||||
key={p.id}
|
||||
className={clsx(
|
||||
"flex flex-col items-start gap-6 rounded-xl bg-white p-4",
|
||||
)}
|
||||
>
|
||||
<div key={p.id} className={clsx("flex flex-col items-start gap-6 rounded-xl bg-white p-4")}>
|
||||
<div className="mb-2 flex flex-col items-start">
|
||||
<img
|
||||
src="/logo_title.png"
|
||||
alt="EnCoach's Logo"
|
||||
className="w-32"
|
||||
/>
|
||||
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-32" />
|
||||
<span className="text-xl font-semibold">
|
||||
EnCoach - {p.duration}{" "}
|
||||
{capitalize(
|
||||
p.duration === 1
|
||||
? p.duration_unit.slice(
|
||||
0,
|
||||
p.duration_unit.length - 1,
|
||||
)
|
||||
: p.duration_unit,
|
||||
p.duration === 1 ? p.duration_unit.slice(0, p.duration_unit.length - 1) : p.duration_unit,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -183,15 +139,12 @@ export default function PaymentDue({
|
||||
{getSymbolFromCurrency(p.currency)}
|
||||
</span>
|
||||
<span className="text-2xl text-mti-red-light">
|
||||
{(
|
||||
p.price -
|
||||
p.price * (appliedDiscount / 100)
|
||||
).toFixed(2)}
|
||||
{(p.price - p.price * (appliedDiscount / 100)).toFixed(2)}
|
||||
{getSymbolFromCurrency(p.currency)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<PayPalPayment
|
||||
{/* <PayPalPayment
|
||||
key={clientID}
|
||||
clientID={clientID}
|
||||
setIsLoading={setIsLoading}
|
||||
@@ -208,18 +161,29 @@ export default function PaymentDue({
|
||||
p.price * (appliedDiscount / 100)
|
||||
).toFixed(2)
|
||||
}
|
||||
/> */}
|
||||
<PaymobPayment
|
||||
key={clientID}
|
||||
user={user}
|
||||
description="Description"
|
||||
paymentID="123"
|
||||
title="Title"
|
||||
setIsLoading={setIsLoading}
|
||||
onSuccess={() => {
|
||||
setTimeout(reload, 500);
|
||||
}}
|
||||
currency={p.currency}
|
||||
duration={p.duration}
|
||||
duration_unit={p.duration_unit}
|
||||
price={+(p.price - p.price * (appliedDiscount / 100)).toFixed(2)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<span>This includes:</span>
|
||||
<ul className="flex flex-col items-start text-sm">
|
||||
<li>- Train your abilities for the IELTS exam</li>
|
||||
<li>
|
||||
- Gain insights into your weaknesses and strengths
|
||||
</li>
|
||||
<li>
|
||||
- Allow yourself to correctly prepare for the exam
|
||||
</li>
|
||||
<li>- Gain insights into your weaknesses and strengths</li>
|
||||
<li>- Allow yourself to correctly prepare for the exam</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,36 +192,20 @@ export default function PaymentDue({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isIndividual() &&
|
||||
user.type === "corporate" &&
|
||||
user?.corporateInformation.payment && (
|
||||
{!isIndividual() && user.type === "corporate" && user?.corporateInformation.payment && (
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="max-w-lg">
|
||||
To add to your use of EnCoach and that of your students and
|
||||
teachers, please pay your designated package below:
|
||||
To add to your use of EnCoach and that of your students and teachers, please pay your designated package below:
|
||||
</span>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex flex-col items-start gap-6 rounded-xl bg-white p-4",
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex flex-col items-start gap-6 rounded-xl bg-white p-4")}>
|
||||
<div className="mb-2 flex flex-col items-start">
|
||||
<img
|
||||
src="/logo_title.png"
|
||||
alt="EnCoach's Logo"
|
||||
className="w-32"
|
||||
/>
|
||||
<span className="text-xl font-semibold">
|
||||
EnCoach - {user.corporateInformation?.monthlyDuration}{" "}
|
||||
Months
|
||||
</span>
|
||||
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-32" />
|
||||
<span className="text-xl font-semibold">EnCoach - {user.corporateInformation?.monthlyDuration} Months</span>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
<span className="text-2xl">
|
||||
{user.corporateInformation.payment.value}
|
||||
{getSymbolFromCurrency(
|
||||
user.corporateInformation.payment.currency,
|
||||
)}
|
||||
{getSymbolFromCurrency(user.corporateInformation.payment.currency)}
|
||||
</span>
|
||||
<PayPalPayment
|
||||
key={clientID}
|
||||
@@ -279,18 +227,11 @@ export default function PaymentDue({
|
||||
<span>This includes:</span>
|
||||
<ul className="flex flex-col items-start text-sm">
|
||||
<li>
|
||||
- Allow a total of{" "}
|
||||
{
|
||||
user.corporateInformation.companyInformation
|
||||
.userAmount
|
||||
}{" "}
|
||||
students and teachers to use EnCoach
|
||||
- Allow a total of {user.corporateInformation.companyInformation.userAmount} students and teachers to
|
||||
use EnCoach
|
||||
</li>
|
||||
<li>- Train their abilities for the IELTS exam</li>
|
||||
<li>
|
||||
- Gain insights into your students' weaknesses
|
||||
and strengths
|
||||
</li>
|
||||
<li>- Gain insights into your students' weaknesses and strengths</li>
|
||||
<li>- Allow them to correctly prepare for the exam</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -300,27 +241,22 @@ export default function PaymentDue({
|
||||
{!isIndividual() && user.type !== "corporate" && (
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="max-w-lg">
|
||||
You are not the person in charge of your time credits, please
|
||||
contact your administrator about this situation.
|
||||
You are not the person in charge of your time credits, please contact your administrator about this situation.
|
||||
</span>
|
||||
<span className="max-w-lg">
|
||||
If you believe this to be a mistake, please contact the
|
||||
platform's administration, thank you for your patience.
|
||||
If you believe this to be a mistake, please contact the platform's administration, thank you for your
|
||||
patience.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isIndividual() &&
|
||||
user.type === "corporate" &&
|
||||
!user.corporateInformation.payment && (
|
||||
{!isIndividual() && user.type === "corporate" && !user.corporateInformation.payment && (
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="max-w-lg">
|
||||
An admin nor your agent have yet set the price intended to
|
||||
your requirements in terms of the amount of users you desire
|
||||
and your expected monthly duration.
|
||||
An admin nor your agent have yet set the price intended to your requirements in terms of the amount of users you
|
||||
desire and your expected monthly duration.
|
||||
</span>
|
||||
<span className="max-w-lg">
|
||||
Please try again later or contact your agent or an admin,
|
||||
thank you for your patience.
|
||||
Please try again later or contact your agent or an admin, thank you for your patience.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
101
src/pages/api/paymob.ts
Normal file
101
src/pages/api/paymob.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type {NextApiRequest, NextApiResponse} from "next";
|
||||
import {app} from "@/firebase";
|
||||
import {getFirestore, collection, getDocs, setDoc, doc} from "firebase/firestore";
|
||||
import {withIronSessionApiRoute} from "iron-session/next";
|
||||
import {sessionOptions} from "@/lib/session";
|
||||
import {Group} from "@/interfaces/user";
|
||||
import {Payment} from "@/interfaces/paypal";
|
||||
import {v4} from "uuid";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import axios from "axios";
|
||||
|
||||
const db = getFirestore(app);
|
||||
|
||||
export default withIronSessionApiRoute(handler, sessionOptions);
|
||||
|
||||
interface BillingData {
|
||||
apartment: string;
|
||||
email: string;
|
||||
floor: string;
|
||||
first_name: string;
|
||||
street: string;
|
||||
building: string;
|
||||
phone_number: string;
|
||||
shipping_method: string;
|
||||
postal_code: string;
|
||||
city: string;
|
||||
country: string;
|
||||
last_name: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!req.session.user) {
|
||||
res.status(401).json({ok: false});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method === "GET") await get(req, res);
|
||||
if (req.method === "POST") await post(req, res);
|
||||
}
|
||||
|
||||
async function get(req: NextApiRequest, res: NextApiResponse) {
|
||||
const snapshot = await getDocs(collection(db, "payments"));
|
||||
|
||||
res.status(200).json(
|
||||
snapshot.docs.map((doc) => ({
|
||||
id: doc.id,
|
||||
...doc.data(),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
async function post(req: NextApiRequest, res: NextApiResponse) {
|
||||
const body = req.body as Payment;
|
||||
|
||||
const shortUID = new ShortUniqueId();
|
||||
await setDoc(doc(db, "payments", shortUID.randomUUID(8)), body);
|
||||
res.status(200).json({ok: true});
|
||||
}
|
||||
|
||||
const authenticatePaymob = async () => {
|
||||
const response = await axios.post<{token: string}>(
|
||||
"https://oman.paymob.com/api/auth/tokens",
|
||||
{
|
||||
api_key: process.env.PAYMOB_API_KEY,
|
||||
},
|
||||
{headers: {Authorization: `Bearer ${process.env.PAYMOB_SECRET_KEY}`}},
|
||||
);
|
||||
|
||||
return response.data.token;
|
||||
};
|
||||
|
||||
const createOrder = async (token: string) => {
|
||||
const response = await axios.post<{id: number}>(
|
||||
"https://oman.paymob.com/api/ecommerce/orders",
|
||||
{auth_token: token, delivery_needed: "false", currency: "OMR", amount_cents: "100", items: []},
|
||||
{headers: {Authorization: `Bearer ${token}`}},
|
||||
);
|
||||
|
||||
return response.data.id;
|
||||
};
|
||||
|
||||
const createTransactionIFrame = async (token: string, orderID: number, billingData: BillingData) => {
|
||||
const response = await axios.post<{token: string}>(
|
||||
"https://oman.paymob.com/api/acceptance/payment_keys",
|
||||
{
|
||||
auth_token: token,
|
||||
amount_cents: "100",
|
||||
order_id: orderID,
|
||||
currency: "OMR",
|
||||
expiration: 3600,
|
||||
integration_id: 1540,
|
||||
lock_order_when_paid: "true",
|
||||
billing_data: billingData,
|
||||
},
|
||||
{headers: {Authorization: `Bearer ${token}`}},
|
||||
);
|
||||
|
||||
return response.data.token;
|
||||
};
|
||||
29
yarn.lock
29
yarn.lock
@@ -4870,6 +4870,13 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
"paymob-react@git+https://github.com/tiago-ecrop/paymob-react-oman.git":
|
||||
version "1.0.0"
|
||||
resolved "git+https://github.com/tiago-ecrop/paymob-react-oman.git#9e7d1e86f01d29dd10192bbd371517849a264e5d"
|
||||
dependencies:
|
||||
react "^18.2.0"
|
||||
react-dom "^18.2.0"
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
||||
@@ -5201,6 +5208,14 @@ react-dom@18.2.0:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.0"
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
|
||||
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
scheduler "^0.23.2"
|
||||
|
||||
react-fast-compare@^3.0.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.1.tgz"
|
||||
@@ -5330,6 +5345,13 @@ react@18.2.0:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
|
||||
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
read-cache@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz"
|
||||
@@ -5535,6 +5557,13 @@ scheduler@^0.23.0:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
scheduler@^0.23.2:
|
||||
version "0.23.2"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3"
|
||||
integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
seedrandom@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
|
||||
|
||||
Reference in New Issue
Block a user