167 lines
4.4 KiB
TypeScript
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>>;
|