refund roadmap pr-1 and pr-2
This commit is contained in:
@@ -71,4 +71,17 @@ export const bookingRepo = {
|
||||
data: { status },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Jumlah booking PAID/PARTIALLY_REFUNDED di trip. Dipakai untuk preview
|
||||
* dampak cancel-trip (berapa peserta yang akan dapat auto-refund).
|
||||
*/
|
||||
async countSettledForTrip(tripId: string) {
|
||||
return prisma.booking.count({
|
||||
where: {
|
||||
tripId,
|
||||
status: { in: ["PAID", "PARTIALLY_REFUNDED"] },
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
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"
|
||||
) {
|
||||
return prisma.refund.findMany({
|
||||
where: status ? { status } : undefined,
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: refundListInclude,
|
||||
});
|
||||
},
|
||||
|
||||
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>>;
|
||||
Reference in New Issue
Block a user