Files
setrip/server/repositories/refund.repo.ts
T

167 lines
4.4 KiB
TypeScript

import { prisma } from "@/lib/prisma";
import { Prisma } from "@/app/generated/prisma/client";
const refundListInclude = {
booking: {
include: {
trip: { select: { id: true, title: true, date: true, organizerId: true } },
user: { select: { id: true, name: true, email: true, image: true } },
payments: {
orderBy: { createdAt: "desc" },
select: {
id: true,
provider: true,
method: true,
amount: true,
status: true,
paidAt: true,
},
},
},
},
payment: {
select: {
id: true,
provider: true,
method: true,
amount: true,
status: true,
},
},
reviewedBy: { select: { id: true, name: true, email: true } },
} satisfies Prisma.RefundInclude;
export const refundRepo = {
async findById(id: string) {
return prisma.refund.findUnique({
where: { id },
include: refundListInclude,
});
},
async listByStatus(
status?: "PENDING" | "APPROVED" | "REJECTED" | "PROCESSING" | "SUCCEEDED" | "FAILED",
filters?: {
dateFrom?: Date;
dateTo?: Date;
reviewerEmail?: string;
reason?:
| "USER_CANCELLATION"
| "ORGANIZER_CANCELLED"
| "TRIP_ISSUE"
| "ADMIN_ADJUSTMENT"
| "DISPUTE_RESOLVED";
}
) {
const where: Prisma.RefundWhereInput = {};
if (status) where.status = status;
if (filters?.dateFrom || filters?.dateTo) {
where.createdAt = {
...(filters.dateFrom && { gte: filters.dateFrom }),
...(filters.dateTo && { lte: filters.dateTo }),
};
}
if (filters?.reviewerEmail) {
where.reviewedBy = { email: filters.reviewerEmail };
}
if (filters?.reason) {
where.reason = filters.reason;
}
return prisma.refund.findMany({
where,
orderBy: { createdAt: "desc" },
include: refundListInclude,
});
},
async countByStatus(
status: "PENDING" | "APPROVED" | "REJECTED" | "PROCESSING" | "SUCCEEDED" | "FAILED"
) {
return prisma.refund.count({ where: { status } });
},
/** Refund terbaru untuk satu status — dipakai dashboard admin. */
async listRecent(
status: "PENDING" | "APPROVED" | "REJECTED" | "PROCESSING" | "SUCCEEDED" | "FAILED",
limit = 3
) {
return prisma.refund.findMany({
where: { status },
orderBy: { createdAt: "desc" },
take: limit,
select: {
id: true,
amount: true,
reason: true,
createdAt: true,
booking: {
select: {
user: { select: { id: true, name: true } },
trip: { select: { id: true, title: true } },
},
},
},
});
},
async listByBooking(bookingId: string) {
return prisma.refund.findMany({
where: { bookingId },
orderBy: { createdAt: "desc" },
});
},
/** Total nominal yang sudah SUCCEEDED untuk satu booking. Dipakai service untuk
* validasi `SUM(SUCCEEDED) + new.amount <= payment.amount`. */
async sumSucceededAmount(bookingId: string, tx?: Prisma.TransactionClient): Promise<number> {
const client = tx ?? prisma;
const agg = await client.refund.aggregate({
where: { bookingId, status: "SUCCEEDED" },
_sum: { amount: true },
});
return agg._sum.amount ?? 0;
},
/** Pending + approved + processing — refund yang "in-flight" (belum settled).
* Dipakai untuk cek apakah booking masih punya refund aktif. */
async hasActiveRefund(bookingId: string, tx?: Prisma.TransactionClient): Promise<boolean> {
const client = tx ?? prisma;
const count = await client.refund.count({
where: {
bookingId,
status: { in: ["PENDING", "APPROVED", "PROCESSING"] },
},
});
return count > 0;
},
async create(
data: Pick<
Prisma.RefundUncheckedCreateInput,
| "bookingId"
| "paymentId"
| "amount"
| "reason"
| "reportedBy"
| "reportNote"
| "initiatedBy"
| "idempotencyKey"
>,
tx?: Prisma.TransactionClient
) {
const client = tx ?? prisma;
return client.refund.create({ data });
},
async update(
id: string,
data: Prisma.RefundUncheckedUpdateInput,
tx?: Prisma.TransactionClient
) {
const client = tx ?? prisma;
return client.refund.update({ where: { id }, data });
},
};
export type RefundWithRelations = Awaited<ReturnType<typeof refundRepo.findById>>;