add payment, trust badge, handle race condition, fix booking schema

This commit is contained in:
arifal
2026-04-20 23:57:31 +07:00
parent ba5f64ae0e
commit fcdca34460
33 changed files with 1781 additions and 138 deletions
+77 -3
View File
@@ -1,4 +1,5 @@
import { prisma } from "@/lib/prisma";
import type { ParticipantStatus } from "@/app/generated/prisma/client";
export const participantRepo = {
async findByTripAndUser(tripId: string, userId: string) {
@@ -7,9 +8,31 @@ export const participantRepo = {
});
},
async findById(id: string) {
return prisma.tripParticipant.findUnique({ where: { id } });
},
async create(tripId: string, userId: string) {
return prisma.tripParticipant.create({
data: { tripId, userId, status: "CONFIRMED" },
data: { tripId, userId, status: "PENDING" },
});
},
async setStatus(id: string, status: ParticipantStatus) {
return prisma.tripParticipant.update({
where: { id },
data: { status },
});
},
async setStatusAndClearPayment(id: string, status: ParticipantStatus) {
return prisma.tripParticipant.update({
where: { id },
data: {
status,
markedPaidAt: null,
paymentConfirmedAt: null,
},
});
},
@@ -22,14 +45,65 @@ export const participantRepo = {
async cancel(tripId: string, userId: string) {
return prisma.tripParticipant.update({
where: { tripId_userId: { tripId, userId } },
data: { status: "CANCELLED" },
data: {
status: "CANCELLED",
markedPaidAt: null,
paymentConfirmedAt: null,
},
});
},
async reactivate(tripId: string, userId: string) {
return prisma.tripParticipant.update({
where: { tripId_userId: { tripId, userId } },
data: { status: "CONFIRMED" },
data: {
status: "PENDING",
markedPaidAt: null,
paymentConfirmedAt: null,
},
});
},
async markPaidByUser(tripId: string, userId: string) {
return prisma.tripParticipant.update({
where: { tripId_userId: { tripId, userId } },
data: { markedPaidAt: new Date() },
});
},
/**
* Satu baris update atomik — aman dari double-submit / race saat tandai bayar.
*/
async tryMarkPaidByUser(tripId: string, userId: string) {
return prisma.tripParticipant.updateMany({
where: {
tripId,
userId,
status: { not: "CANCELLED" },
markedPaidAt: null,
paymentConfirmedAt: null,
},
data: { markedPaidAt: new Date() },
});
},
async confirmPaymentByOrganizer(participantId: string) {
return prisma.tripParticipant.update({
where: { id: participantId },
data: { paymentConfirmedAt: new Date() },
});
},
/** Konfirmasi pembayaran hanya jika masih eligible — idempotent terhadap double klik. */
async tryConfirmPaymentByOrganizer(participantId: string) {
return prisma.tripParticipant.updateMany({
where: {
id: participantId,
markedPaidAt: { not: null },
paymentConfirmedAt: null,
status: { not: "CANCELLED" },
},
data: { paymentConfirmedAt: new Date() },
});
},
+9 -1
View File
@@ -92,7 +92,15 @@ export const tripRepo = {
return prisma.trip.findUnique({
where: { id },
include: {
organizer: { select: { id: true, name: true, email: true, image: true } },
organizer: {
select: {
id: true,
name: true,
email: true,
image: true,
isVerified: true,
},
},
images: { orderBy: { order: "asc" } },
participants: {
include: { user: { select: { id: true, name: true, image: true } } },