sarlink-portal/actions/payment.ts
i701 aff9d26e0e
Some checks failed
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 1m37s
refactor: enhance error handling and add pagination to device queries
2025-04-12 14:35:23 +05:00

361 lines
9.6 KiB
TypeScript

"use server";
import { authOptions } from "@/app/auth";
import type {
ApiError,
ApiResponse,
NewPayment,
Payment,
} from "@/lib/backend-types";
import type { User } from "@/lib/types/user";
import { tryCatch } from "@/utils/tryCatch";
import { getServerSession } from "next-auth";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export async function createPayment(data: NewPayment) {
const session = await getServerSession(authOptions);
console.log("data", data);
const response = await fetch(
`${
process.env.SARLINK_API_BASE_URL // });
}/api/billing/payment/`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${session?.apiToken}`,
},
body: JSON.stringify(data),
},
);
if (!response.ok) {
const errorData = (await response.json()) as ApiError;
const errorMessage =
errorData.message || errorData.detail || "An error occurred.";
const error = new Error(errorMessage);
(error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object
throw error;
}
const payment = (await response.json()) as Payment;
revalidatePath("/devices");
return payment;
}
export async function getPayment({ id }: { id: string }) {
const session = await getServerSession(authOptions);
const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${session?.apiToken}`,
},
},
);
if (!response.ok) {
const errorData = (await response.json()) as ApiError;
const errorMessage =
errorData.message || errorData.detail || "An error occurred.";
const error = new Error(errorMessage);
(error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object
throw error;
}
const data = (await response.json()) as Payment;
return data;
}
export async function getPayments() {
const session = await getServerSession(authOptions);
const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${session?.apiToken}`,
},
},
);
if (!response.ok) {
const errorData = (await response.json()) as ApiError;
const errorMessage =
errorData.message || errorData.detail || "An error occurred.";
const error = new Error(errorMessage);
(error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object
throw error;
}
const data = (await response.json()) as ApiResponse<Payment>;
return data;
}
export async function cancelPayment({ id }: { id: string }) {
const session = await getServerSession(authOptions);
const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}/delete/`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${session?.apiToken}`,
},
},
);
if (!response.ok) {
const errorData = (await response.json()) as ApiError;
const errorMessage =
errorData.message || errorData.detail || "An error occurred.";
const error = new Error(errorMessage);
(error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object
throw error;
}
return { message: "Payment successfully canceled." };
}
type UpdatePayment = Pick<
Payment,
"id" | "paid" | "paid_at" | "method" | "number_of_months"
>;
export async function updatePayment({
id,
method,
paid,
paid_at,
number_of_months,
}: UpdatePayment) {
const session = await getServerSession(authOptions);
const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/billing/payment/${id}/update/`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${session?.apiToken}`,
},
body: JSON.stringify({
method,
paid,
paid_at,
number_of_months,
}),
},
);
if (!response.ok) {
const errorData = (await response.json()) as ApiError;
const errorMessage =
errorData.message || errorData.detail || "An error occurred.";
const error = new Error(errorMessage);
(error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object
throw error;
}
const payment = (await response.json()) as Payment;
return payment;
}
type TUpdateWalletBalance = Pick<User, "id" | "wallet_balance">;
export async function updateWalletBalance({
id,
wallet_balance,
}: TUpdateWalletBalance) {
const session = await getServerSession(authOptions);
console.log("wallet bal in server action", wallet_balance);
const response = await fetch(
`${process.env.SARLINK_API_BASE_URL}/api/auth/update-wallet/${id}/`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Token ${session?.apiToken}`,
},
body: JSON.stringify({
wallet_balance: Number.parseFloat(wallet_balance?.toFixed(2) ?? "0"),
}),
},
);
if (!response.ok) {
const errorData = (await response.json()) as ApiError;
const errorMessage =
errorData.message || errorData.detail || "An error occurred.";
const error = new Error(errorMessage);
(error as ApiError & { details?: ApiError }).details = errorData; // Attach the errorData to the error object
throw error;
}
const message = (await response.json()) as {
message: "Wallet balance updated successfully.";
};
return message;
}
type VerifyPaymentType = {
userId: string;
paymentId?: string;
benefName: string;
accountNo?: string;
absAmount: string;
time: string;
type?: "TRANSFER" | "WALLET";
};
class InsufficientFundsError extends Error {
constructor() {
super("Insufficient funds in wallet");
this.name = "InsufficientFundsError";
}
}
export async function processWalletPayment({
payment,
amount,
}: { payment: Payment | undefined; amount: number }) {
const session = await getServerSession(authOptions);
if (!session?.user || !payment) {
throw new Error("User or payment not found");
}
const walletBalance = session.user.wallet_balance ?? 0;
if (walletBalance < amount) {
throw new InsufficientFundsError();
}
const [updatePaymentError, _] = await tryCatch(
updatePayment({
id: payment.id,
method: "WALLET",
paid: true,
paid_at: new Date().toISOString(),
number_of_months: payment.number_of_months,
}),
);
if (updatePaymentError) {
throw new Error(updatePaymentError.message);
}
console.log("Wallet balance before update:", walletBalance);
const updated_balance = walletBalance - amount;
const [walletUpdateError, response] = await tryCatch(
updateWalletBalance({
id: session.user.id,
wallet_balance: Number.parseFloat(updated_balance?.toFixed(2) ?? "0"),
}),
);
if (walletUpdateError) {
throw new Error(walletUpdateError.message);
}
revalidatePath("/payments/[paymentsId]", "page");
return response;
}
type VerifyPaymentResponse =
| {
success: boolean;
message: string;
}
| {
success: boolean;
message: string;
transaction: {
ref: string;
sourceBank: string;
trxDate: string;
};
};
// async function verifyExternalPayment(
// data: VerifyPaymentType,
// payment: PaymentWithDevices | null,
// ): Promise<VerifyPaymentResponse> {
// console.log("payment verify data ->", data);
// const response = await fetch(
// "https://verifypaymentsapi.baraveli.dev/verify-payment",
// {
// method: "POST",
// headers: { "Content-Type": "application/json" },
// body: JSON.stringify(data),
// },
// );
// const json = await response.json();
// console.log(json);
// if (!payment) {
// throw new Error("Payment verification failed or payment not found");
// }
// if (json.success) {
// const expiryDate = new Date();
// expiryDate.setMonth(expiryDate.getMonth() + payment.numberOfMonths);
// await prisma.payment.update({
// where: { id: payment.id },
// data: {
// paid: true,
// paidAt: new Date(),
// method: "TRANSFER",
// devices: {
// updateMany: payment.devices.map((device) => ({
// where: { id: device.id },
// data: {
// isActive: true,
// expiryDate: expiryDate,
// },
// })),
// },
// },
// });
// }
// return json;
// }
// async function updateDevices(payment: PaymentWithDevices | null) {
// if (!payment) return;
// const newDevices = payment.devices.map((d) => ({
// name: d.name,
// macAddress: formatMacAddress(d.mac),
// }));
// return await addDevicesToGroup({
// groupId: process.env.OMADA_GROUP_ID,
// siteId: process.env.OMADA_SITE_ID,
// newDevices,
// });
// }
// export async function verifyPayment(data: VerifyPaymentType) {
// try {
// const [payment, user] = await Promise.all([
// prisma.payment.findUnique({
// where: { id: data.paymentId },
// include: { devices: true },
// }),
// prisma.user.findUnique({
// where: { id: data.userId },
// }),
// ]);
// if (data.type === "WALLET") {
// console.log("WALLET");
// await processWalletPayment(user, payment, Number(data.absAmount));
// redirect("/payments");
// }
// if (data.type === "TRANSFER") {
// console.log({ data, payment });
// const verificationResult = await verifyExternalPayment(data, payment);
// await updateDevices(payment);
// revalidatePath("/payment[paymentId]");
// return verificationResult;
// }
// } catch (error) {
// console.error("Payment verification failed:", error);
// throw error; // Re-throw to handle at a higher level
// }
// }
// export async function addDevicesToOmada() {
// console.log("hi");
// }