fix race condition issue

This commit is contained in:
2026-05-20 13:16:25 +07:00
parent 43ea725107
commit da217c2946
3 changed files with 445 additions and 421 deletions
+9 -34
View File
@@ -1,43 +1,10 @@
import { randomBytes } from "crypto";
import { Prisma } from "@/app/generated/prisma/client";
import { prisma } from "@/lib/prisma";
import { refundRepo } from "@/server/repositories/refund.repo";
import { calculateRefundAmount, daysUntilDeparture } from "@/lib/refund-policy";
import { isTripDepartureDayPast } from "@/lib/trip-dates";
import { payoutService } from "@/server/services/payout.service";
const SERIAL_TX_ATTEMPTS = 6;
function isSerializationConflict(err: unknown): boolean {
return (
typeof err === "object" &&
err !== null &&
"code" in err &&
(err as { code: string }).code === "P2034"
);
}
async function runSerializable<T>(fn: (tx: Prisma.TransactionClient) => Promise<T>): Promise<T> {
let lastErr: unknown;
for (let attempt = 0; attempt < SERIAL_TX_ATTEMPTS; attempt++) {
try {
return await prisma.$transaction(fn, {
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
maxWait: 5000,
timeout: 15000,
});
} catch (e) {
lastErr = e;
if (isSerializationConflict(e) && attempt < SERIAL_TX_ATTEMPTS - 1) {
continue;
}
throw e;
}
}
throw lastErr instanceof Error
? lastErr
: new Error("Gagal memproses refund. Coba lagi sebentar.");
}
import { runSerializable } from "@/lib/serializable-tx";
function newIdempotencyKey(): string {
return `refund_${randomBytes(16).toString("hex")}`;
@@ -358,6 +325,14 @@ export const refundService = {
if (booking.userId !== input.userId) {
throw new Error("Booking ini bukan milikmu");
}
if (
booking.trip.status === "CLOSED" ||
booking.trip.status === "COMPLETED"
) {
throw new Error(
"Trip sudah dibatalkan/selesai — pembatalan mandiri ditutup. Hubungi admin untuk proses refund."
);
}
if (booking.status !== "PAID") {
throw new Error(
"Hanya booking PAID yang bisa cancel dengan refund. Booking yang belum lunas bisa cancel dari tombol 'Batal Ikut'."