();
+ useEffect(() => {
+ axios
+ .put<{ ok: boolean; trackingId: string }>("/api/paypal/raas")
+ .then((response) => {
+ if (response.data.ok) {
+ setTrackingId(response.data.trackingId);
+ }
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+ }, []);
+
+ return trackingId;
+};
diff --git a/src/pages/(status)/PaymentDue.tsx b/src/pages/(status)/PaymentDue.tsx
index b358c860..4a2cbc07 100644
--- a/src/pages/(status)/PaymentDue.tsx
+++ b/src/pages/(status)/PaymentDue.tsx
@@ -14,6 +14,7 @@ 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";
interface Props {
user: User;
@@ -31,7 +32,8 @@ export default function PaymentDue({user, hasExpired = false, clientID, reload}:
const {users} = useUsers();
const {groups} = useGroups();
const {invites, isLoading: isInvitesLoading, reload: reloadInvites} = useInvites({to: user?.id});
-
+ const trackingId = usePaypalTracking();
+
const isIndividual = () => {
if (user?.type === "developer") return true;
if (user?.type !== "student") return false;
@@ -121,6 +123,7 @@ export default function PaymentDue({user, hasExpired = false, clientID, reload}:
onSuccess={() => {
setTimeout(reload, 500);
}}
+ trackingId={trackingId}
/>
@@ -165,6 +168,7 @@ export default function PaymentDue({user, hasExpired = false, clientID, reload}:
setTimeout(reload, 500);
}}
loadScript
+ trackingId={trackingId}
/>
diff --git a/src/pages/api/paypal/approve.ts b/src/pages/api/paypal/approve.ts
index 9b105c88..2a176a3b 100644
--- a/src/pages/api/paypal/approve.ts
+++ b/src/pages/api/paypal/approve.ts
@@ -1,58 +1,79 @@
// 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 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 axios from "axios";
-import {DurationUnit, TokenError, TokenSuccess} from "@/interfaces/paypal";
-import {base64} from "@firebase/util";
-import {v4} from "uuid";
-import {OrderResponseBody} from "@paypal/paypal-js";
-import {getAccessToken} from "@/utils/paypal";
+import { DurationUnit, TokenError, TokenSuccess } from "@/interfaces/paypal";
+import { base64 } from "@firebase/util";
+import { v4 } from "uuid";
+import { OrderResponseBody } from "@paypal/paypal-js";
+import { getAccessToken } from "@/utils/paypal";
import moment from "moment";
-import {Group} from "@/interfaces/user";
+import { Group } from "@/interfaces/user";
const db = getFirestore(app);
export default withIronSessionApiRoute(handler, sessionOptions);
async function handler(req: NextApiRequest, res: NextApiResponse) {
- if (req.method !== "POST") return res.status(404).json({ok: false, reason: "Method not supported!"});
- if (!req.session.user) return res.status(401).json({ok: false});
+ if (req.method !== "POST")
+ return res.status(404).json({ ok: false, reason: "Method not supported!" });
+ if (!req.session.user) return res.status(401).json({ ok: false });
- const accessToken = await getAccessToken();
- if (!accessToken) return res.status(401).json({ok: false, reason: "Authorization failed!"});
+ const accessToken = await getAccessToken();
+ if (!accessToken)
+ return res.status(401).json({ ok: false, reason: "Authorization failed!" });
- const {id, duration, duration_unit} = req.body as {id: string; duration: number; duration_unit: DurationUnit};
+ const { id, duration, duration_unit, trackingId } = req.body as {
+ id: string;
+ duration: number;
+ duration_unit: DurationUnit;
+ trackingId: string;
+ };
- const request = await axios.post(
- `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v2/checkout/orders/${id}/capture`,
- {},
- {
- headers: {
- Authorization: `Bearer ${accessToken}`,
- },
- },
- );
+ if (!trackingId)
+ return res.status(401).json({ ok: false, reason: "Missing tracking id!" });
- if (request.data.status === "COMPLETED") {
- const user = req.session.user;
- const subscriptionExpirationDate = req.session.user.subscriptionExpirationDate;
- const today = moment(new Date());
- const dateToBeAddedTo = !subscriptionExpirationDate
- ? today
- : moment(subscriptionExpirationDate).isAfter(today)
- ? moment(subscriptionExpirationDate)
- : today;
+ const request = await axios.post(
+ `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v2/checkout/orders/${id}/capture`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ "PayPal-Client-Metadata-Id": trackingId,
+ },
+ }
+ );
- const updatedExpirationDate = dateToBeAddedTo.add(duration, duration_unit);
- await setDoc(
- doc(db, "users", req.session.user.id),
- {subscriptionExpirationDate: updatedExpirationDate.toISOString(), status: "active"},
- {merge: true},
- );
+ if (request.data.status === "COMPLETED") {
+ const user = req.session.user;
+ const subscriptionExpirationDate =
+ req.session.user.subscriptionExpirationDate;
+ const today = moment(new Date());
+ const dateToBeAddedTo = !subscriptionExpirationDate
+ ? today
+ : moment(subscriptionExpirationDate).isAfter(today)
+ ? moment(subscriptionExpirationDate)
+ : today;
+ const updatedExpirationDate = dateToBeAddedTo.add(duration, duration_unit);
+ await setDoc(
+ doc(db, "users", req.session.user.id),
+ {
+ subscriptionExpirationDate: updatedExpirationDate.toISOString(),
+ status: "active",
+ },
+ { merge: true }
+ );
+
try {
await setDoc(
doc(db, 'paypalpayments', v4()),
@@ -72,31 +93,40 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
console.error('Failed to insert paypal payment!', err);
}
- if (user.type === "corporate") {
- const snapshot = await getDocs(collection(db, "groups"));
- const groups: Group[] = (
- snapshot.docs.map((doc) => ({
- id: doc.id,
- ...doc.data(),
- })) as Group[]
- ).filter((x) => x.admin === user.id);
+ if (user.type === "corporate") {
+ const snapshot = await getDocs(collection(db, "groups"));
+ const groups: Group[] = (
+ snapshot.docs.map((doc) => ({
+ id: doc.id,
+ ...doc.data(),
+ })) as Group[]
+ ).filter((x) => x.admin === user.id);
- await Promise.all(
- groups
- .flatMap((x) => x.participants)
- .map(
- async (x) =>
- await setDoc(
- doc(db, "users", x),
- {subscriptionExpirationDate: updatedExpirationDate.toISOString(), status: "active"},
- {merge: true},
- ),
- ),
- );
- }
+ await Promise.all(
+ groups
+ .flatMap((x) => x.participants)
+ .map(
+ async (x) =>
+ await setDoc(
+ doc(db, "users", x),
+ {
+ subscriptionExpirationDate:
+ updatedExpirationDate.toISOString(),
+ status: "active",
+ },
+ { merge: true }
+ )
+ )
+ );
+ }
- return res.status(200).json({ok: true});
- }
+ return res.status(200).json({ ok: true });
+ }
- res.status(404).json({ok: false, reason: "Order ID not found or purchase was not approved!"});
+ res
+ .status(404)
+ .json({
+ ok: false,
+ reason: "Order ID not found or purchase was not approved!",
+ });
}
diff --git a/src/pages/api/paypal/index.ts b/src/pages/api/paypal/index.ts
index 139a33b7..9a44eac4 100644
--- a/src/pages/api/paypal/index.ts
+++ b/src/pages/api/paypal/index.ts
@@ -20,7 +20,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const accessToken = await getAccessToken();
if (!accessToken) return res.status(401).json({ok: false, reason: "Authorization failed!"});
- const {currencyCode, price} = req.body as {currencyCode: string; price: number};
+ const {currencyCode, price, trackingId} = req.body as {currencyCode: string; price: number, trackingId: string};
+
+ if(!trackingId) return res.status(401).json({ok: false, reason: "Missing tracking id!"});
const request = await axios.post(
`${process.env.PAYPAL_ACCESS_TOKEN_URL}/v2/checkout/orders`,
@@ -34,11 +36,24 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
reference_id: v4(),
},
],
+ payment_source: {
+ paypal: {
+ email_address: req.session.user.email || "",
+ experience_context: {
+ payment_method_preference: "IMMEDIATE_PAYMENT_REQUIRED",
+ locale: "en-US",
+ landing_page: "LOGIN",
+ shipping_preference: "NO_SHIPPING",
+ user_action: "PAY_NOW",
+ },
+ },
+ },
intent: "CAPTURE",
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
+ 'PayPal-Client-Metadata-Id': trackingId,
},
},
);
diff --git a/src/pages/api/paypal/raas.ts b/src/pages/api/paypal/raas.ts
new file mode 100644
index 00000000..c4eb90af
--- /dev/null
+++ b/src/pages/api/paypal/raas.ts
@@ -0,0 +1,55 @@
+// 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 } from "firebase/firestore";
+import { withIronSessionApiRoute } from "iron-session/next";
+import { sessionOptions } from "@/lib/session";
+import axios from "axios";
+import { v4 } from "uuid";
+import { OrderResponseBody } from "@paypal/paypal-js";
+import { getAccessToken } from "@/utils/paypal";
+
+const db = getFirestore(app);
+
+export default withIronSessionApiRoute(handler, sessionOptions);
+
+async function handler(req: NextApiRequest, res: NextApiResponse) {
+ if (req.method !== "PUT")
+ return res.status(404).json({ ok: false, reason: "Method not supported!" });
+
+ if (!req.session.user) return res.status(401).json({ ok: false });
+
+ const accessToken = await getAccessToken();
+ if (!accessToken)
+ return res.status(401).json({ ok: false, reason: "Authorization failed!" });
+
+ const trackingId = `${req.session.user.id}-${Date.now()}`;
+
+ try {
+ const request = await axios.put(
+ `${process.env.PAYPAL_ACCESS_TOKEN_URL}/v1/risk/transaction-contexts/${process.env.PAYPAL_MERCHANT_ID}/${trackingId}`,
+ {
+ additional_data: [
+ {
+ key: "user_id",
+ value: req.session.user.id,
+ },
+ ],
+ },
+ {
+ headers: {
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+
+ return res.status(request.status).json({
+ ok: true,
+ trackingId,
+ });
+ } catch (err) {
+ return res
+ .status(500)
+ .json({ ok: false, reason: "Failed to create tracking ID" });
+ }
+}
diff --git a/src/pages/api/storage/insert.ts b/src/pages/api/storage/insert.ts
index bd664ff8..f74d84e5 100644
--- a/src/pages/api/storage/insert.ts
+++ b/src/pages/api/storage/insert.ts
@@ -28,7 +28,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
}
const audioFile = files.audio;
- const audioFileRef = ref(storage, `${fields.root}/${(audioFile as any).path.split("/").pop()!.replace("upload_", "")}`);
+ const audioFileRef = ref(storage, `${fields.root}/${(audioFile as any).path.replace("upload_", "")}`);
const binary = fs.readFileSync((audioFile as any).path).buffer;
const snapshot = await uploadBytes(audioFileRef, binary);
diff --git a/src/pages/tickets.tsx b/src/pages/tickets.tsx
index e89ff857..21c6fd92 100644
--- a/src/pages/tickets.tsx
+++ b/src/pages/tickets.tsx
@@ -170,7 +170,9 @@ export default function Tickets() {
{dateSorting === "asc" && }
) as any,
- cell: (info) => moment(info.getValue()).format("DD/MM/YYYY - HH:mm"),
+ cell: (info) => (
+ {moment(info.getValue()).format("DD/MM/YYYY - HH:mm")}
+ ),
}),
columnHelper.accessor("subject", {
header: "Subject",