import type { Metadata } from "next"; import Link from "next/link"; import { notFound, redirect } from "next/navigation"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth"; import { tripService } from "@/server/services/trip.service"; import { organizerService } from "@/server/services/organizer.service"; import { bookingService } from "@/server/services/booking.service"; import { formatRupiah } from "@/lib/utils"; import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates"; import { isFreeTrip } from "@/lib/trip-pricing"; import { categoryMeta } from "@/lib/activity-category"; import { MarkPaidButton } from "@/features/booking/components/mark-paid-button"; import { MidtransPayButton } from "@/features/booking/components/midtrans-pay-button"; import { CopyButton } from "@/features/booking/components/copy-button"; export const metadata: Metadata = { title: "Detail Pembayaran", robots: { index: false, follow: false }, }; interface PageProps { params: Promise<{ id: string }>; } export default async function PaymentPage({ params }: PageProps) { const { id } = await params; const session = await getServerSession(authOptions); if (!session?.user) { redirect(`/login?callbackUrl=/trips/${id}/payment`); } let trip; try { trip = await tripService.getTripById(id); } catch { notFound(); } // Organizer trip-nya sendiri tidak butuh halaman pembayaran. if (trip.organizerId === session.user.id) { redirect(`/trips/${id}`); } const booking = await bookingService.getByTripAndUser( trip.id, session.user.id ); if (!booking || booking.status === "CANCELLED") { return ; } const latestManualPayment = booking.payments.find( (p) => p.provider === "MANUAL" ); const tripIsFree = isFreeTrip(trip); const catMeta = categoryMeta(trip.category); const dateRange = formatTripCalendarDateRangeLong(trip.date, trip.endDate); // Header info โ€” sama untuk free vs paid const tripHeader = (
{catMeta.icon}

{catMeta.label}

{trip.title}

๐Ÿ“… {dateRange} ยท ๐Ÿ“ {trip.location}

Organizer:{" "} {trip.organizer.name}

); return (
โ† Kembali ke trip

Detail Pembayaran

{tripIsFree ? "Trip ini gratis โ€” kamu tidak perlu transfer apa-apa." : "Transfer manual ke rekening organizer di bawah, lalu tandai sebagai sudah bayar."}

{tripHeader} {tripIsFree ? ( ) : ( )}
); } function NotJoinedNotice({ tripId, title }: { tripId: string; title: string }) { return (

Kamu belum terdaftar di trip ini

Halaman pembayaran hanya tersedia untuk peserta trip{" "} {title}.

Lihat detail trip
); } function FreeTripSection({ tripId, bookingStatus, }: { tripId: string; bookingStatus: | "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "PARTIALLY_REFUNDED" | "EXPIRED"; }) { return (
๐ŸŽ‰

Trip ini gratis

Tidak ada biaya yang perlu kamu transfer.

Status keikutsertaan

{bookingStatus === "PAID" ? "โœ… Terkonfirmasi sebagai peserta" : "โณ Menunggu persetujuan organizer"}

Kembali ke detail trip
); } async function PaidTripSection({ tripId, organizerId, organizerName, price, bookingStatus, paymentMarkedAt, paymentPaidAt, }: { tripId: string; organizerId: string; organizerName: string; price: number; bookingStatus: | "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "PARTIALLY_REFUNDED" | "EXPIRED"; paymentMarkedAt: Date | null; paymentPaidAt: Date | null; }) { const verification = await organizerService.getStatusForUser(organizerId); const bankAvailable = verification?.status === "APPROVED"; const isApproved = bookingStatus === "AWAITING_PAY" || bookingStatus === "PAID"; const isPendingApproval = bookingStatus === "PENDING"; const hasMarkedPaid = !!paymentMarkedAt || !!paymentPaidAt; const isFullyPaid = bookingStatus === "PAID"; const canMarkPaid = bookingStatus === "AWAITING_PAY" && !paymentMarkedAt; return (
{!bankAvailable && (

Rekening organizer belum tersedia

Organizer trip ini belum melengkapi data verifikasi (bank). Hubungi organizer langsung lewat profilnya untuk koordinasi pembayaran.

)} {bankAvailable && (

Transfer ke rekening organizer

Pastikan nominal persis seperti tercantum supaya organizer mudah mencocokkan.

  • โ€ข Transfer dengan nominal pas, jangan dibulatkan.
  • โ€ข Simpan bukti transfer untuk jaga-jaga jika ada konfirmasi.
  • โ€ข Setelah transfer, tekan tombol Saya sudah bayar di bawah supaya organizer tahu dan bisa konfirmasi.
)} {isPendingApproval && (
Kamu belum disetujui organizer untuk ikut trip ini. Tunggu persetujuan dulu sebelum transfer โ€” supaya tidak perlu refund kalau ditolak.
)} {canMarkPaid && (
{bankAvailable && ( <>
atau
)}
)} {hasMarkedPaid && (
{isFullyPaid ? (

โœ… Pembayaran kamu sudah dikonfirmasi oleh{" "} {organizerName} . Sampai jumpa di trip!

) : (

โณ Kamu sudah menandai sudah bayar. Menunggu organizer mengecek dan mengonfirmasi.

)}
)}
โ† Kembali ke detail trip
); } function PaymentTimeline({ approved, markedPaid, confirmedPaid, }: { approved: boolean; markedPaid: boolean; confirmedPaid: boolean; }) { const steps = [ { label: "Disetujui organizer", done: approved }, { label: "Kamu menandai sudah bayar", done: markedPaid }, { label: "Organizer konfirmasi pembayaran", done: confirmedPaid }, ]; return (

Status pembayaran

    {steps.map((s, i) => (
  1. {s.done ? "โœ“" : i + 1} {s.label}
  2. ))}
); } function BankRow({ label, value, mono, strong, copyable, copyValue, }: { label: string; value: string; mono?: boolean; strong?: boolean; copyable?: boolean; copyValue?: string; }) { return (

{label}

{value}

{copyable && }
); }