From ee461bbbf804d94acacea34d928fd2a8c2b76fca Mon Sep 17 00:00:00 2001 From: i701 Date: Fri, 4 Jul 2025 23:06:13 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20implement=20topup=20functionality=20wit?= =?UTF-8?q?h=20create,=20get,=20and=20cancel=20operations=20=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- actions/payment.ts | 116 ++++++++++++++++++++++++++++++++++++++++--- lib/backend-types.ts | 14 ++++++ lib/types.ts | 14 +++++- 3 files changed, 135 insertions(+), 9 deletions(-) diff --git a/actions/payment.ts b/actions/payment.ts index a2a156b..fdc0544 100644 --- a/actions/payment.ts +++ b/actions/payment.ts @@ -1,25 +1,31 @@ "use server"; +import { revalidatePath } from "next/cache"; +import { getServerSession } from "next-auth"; import { authOptions } from "@/app/auth"; import type { ApiError, ApiResponse, NewPayment, Payment, + Topup } from "@/lib/backend-types"; +import type { TopupResponse } from "@/lib/types"; import type { User } from "@/lib/types/user"; -import { checkSession } from "@/utils/session"; -import { handleApiResponse, tryCatch } from "@/utils/tryCatch"; -import { getServerSession } from "next-auth"; -import { revalidatePath } from "next/cache"; -import { redirect } from "next/navigation"; +import { handleApiResponse } from "@/utils/tryCatch"; + +type GenericGetResponseProps = { + offset?: number; + limit?: number; + page?: number; + [key: string]: string | number | undefined; +}; 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 // }); + `${process.env.SARLINK_API_BASE_URL // }); }/api/billing/payment/`, { method: "POST", @@ -43,6 +49,22 @@ export async function createPayment(data: NewPayment) { return payment; } +export async function createTopup(data: { amount: number }) { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/topup/`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + body: JSON.stringify(data), + }, + ); + return handleApiResponse(response, "createTopup"); +} + export async function getPayment({ id }: { id: string }) { const session = await getServerSession(authOptions); const response = await fetch( @@ -92,6 +114,67 @@ export async function getPayments() { return data; } +export async function getTopups(params: GenericGetResponseProps) { + + // Build query string from all defined params + const query = Object.entries(params) + .filter(([_, value]) => value !== undefined && value !== "") + .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`) + .join("&"); + + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/topup/?${query}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + }, + ); + return handleApiResponse>(response, "getTopups"); +} + +export async function getTopup({ id }: { id: string }) { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/topup/${id}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + }, + ); + + return handleApiResponse(response, "getTopup"); +} + +export async function cancelTopup({ id }: { id: string }) { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/topup/${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: "Topup successfully canceled." }; +} + export async function cancelPayment({ id }: { id: string }) { const session = await getServerSession(authOptions); const response = await fetch( @@ -142,6 +225,25 @@ export async function verifyPayment({ id, method }: UpdatePayment) { return handleApiResponse(response, "updatePayment"); } +type UpdateTopupPayment = { + id: string; +}; + +export async function verifyTopupPayment({ id }: UpdateTopupPayment) { + const session = await getServerSession(authOptions); + const response = await fetch( + `${process.env.SARLINK_API_BASE_URL}/api/billing/topup/${id}/verify/`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Token ${session?.apiToken}`, + }, + }, + ); + revalidatePath("/top-ups/[topupId]", "page"); + return handleApiResponse(response, "verifyTopupPayment"); +} export async function getProfile() { const session = await getServerSession(authOptions); diff --git a/lib/backend-types.ts b/lib/backend-types.ts index 6d80a85..1053660 100644 --- a/lib/backend-types.ts +++ b/lib/backend-types.ts @@ -57,6 +57,20 @@ export interface ApiError { detail?: string; } +export interface Topup { + id: string; + amount: number; + user: Pick & { + name: string; + }; + paid: boolean; + mib_reference: string | null; + expires_at: string; + is_expired: boolean; + created_at: string; + updated_at: string; +} + export interface Payment { id: string; devices: Device[]; diff --git a/lib/types.ts b/lib/types.ts index 5fcbb85..beebbe7 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -1,9 +1,19 @@ export type TopupType = { amount: number; - userId: string; - paid: boolean; }; +export type Transaction = { + ref: string; + sourceBank: string; + trxDate: string; +} + +export type TopupResponse = { + status: boolean; + message: string; + transaction?: Transaction +} + interface IpAddress { ip: string; mask: number;