refund roadmap pr-1 and pr-2
This commit is contained in:
@@ -6,17 +6,21 @@ import Image from "next/image";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { tripService } from "@/server/services/trip.service";
|
||||
import { bookingService } from "@/server/services/booking.service";
|
||||
import { bookingRepo } from "@/server/repositories/booking.repo";
|
||||
import { trustService } from "@/server/services/trust.service";
|
||||
import { formatRupiah } from "@/lib/utils";
|
||||
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
|
||||
import { siteConfig, siteUrl, absoluteUrl } from "@/lib/site";
|
||||
import { JoinTripButton } from "@/features/trip/components/join-trip-button";
|
||||
import { CancelTripButton } from "@/features/trip/components/cancel-trip-button";
|
||||
import { CancelBookingButton } from "@/features/booking/components/cancel-booking-button";
|
||||
import { OrganizerJoinRequests } from "@/features/trip/components/organizer-join-requests";
|
||||
import { OrganizerTrustPanel } from "@/features/trip/components/organizer-trust-panel";
|
||||
import { TripProgramBlock } from "@/features/trip/components/trip-program-block";
|
||||
import { OrganizerPaymentQueue } from "@/features/booking/components/organizer-payment-queue";
|
||||
import { ImageGallery } from "@/features/trip/components/image-gallery";
|
||||
import { TripReviewSection } from "@/features/review/components/trip-review-section";
|
||||
import { RefundPolicySection } from "@/features/refund/components/refund-policy-section";
|
||||
import { categoryMeta } from "@/lib/activity-category";
|
||||
import { vibeMeta } from "@/lib/vibe";
|
||||
import { isFreeTrip } from "@/lib/trip-pricing";
|
||||
@@ -24,6 +28,7 @@ import {
|
||||
isPastTripLastDayForReview,
|
||||
isTripDepartureDayPast,
|
||||
} from "@/lib/trip-dates";
|
||||
import { previewRefund } from "@/lib/refund-policy";
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
@@ -137,6 +142,31 @@ export default async function TripDetailPage({
|
||||
? await bookingService.getAwaitingManualForTrip(trip.id)
|
||||
: [];
|
||||
|
||||
// Booking peserta saat ini — dipakai untuk render CancelBookingButton vs
|
||||
// tombol "Batal Ikut" biasa. Hanya untuk non-organizer yang ikut trip.
|
||||
const myBooking =
|
||||
session?.user && !isOrganizer && currentParticipation
|
||||
? await bookingService.getByTripAndUser(trip.id, session.user.id)
|
||||
: null;
|
||||
|
||||
// Untuk CancelTripButton: jumlah booking PAID/PARTIALLY_REFUNDED (yang akan
|
||||
// auto-refund). Hanya dihitung saat organizer mengakses trip yang masih
|
||||
// bisa dibatalkan.
|
||||
const canOrganizerCancel =
|
||||
isOrganizer &&
|
||||
(trip.status === "OPEN" || trip.status === "FULL") &&
|
||||
!isDeparturePast;
|
||||
const paidBookingCount = canOrganizerCancel
|
||||
? await bookingRepo.countSettledForTrip(trip.id)
|
||||
: 0;
|
||||
|
||||
// Preview refund untuk CancelBookingButton (server-side supaya konsisten
|
||||
// dengan service yang juga pakai policy yang sama).
|
||||
const refundPreview =
|
||||
myBooking && myBooking.status === "PAID" && !isDeparturePast
|
||||
? previewRefund(myBooking.amount, trip.date)
|
||||
: null;
|
||||
|
||||
const catMeta = categoryMeta(trip.category);
|
||||
|
||||
const tripUrl = absoluteUrl(`/trips/${trip.id}`);
|
||||
@@ -480,8 +510,33 @@ export default async function TripDetailPage({
|
||||
isFull={spotsLeft <= 0}
|
||||
tripStatus={trip.status}
|
||||
isDeparturePast={isDeparturePast}
|
||||
hideCancelButton={!!refundPreview}
|
||||
/>
|
||||
|
||||
{/* Peserta PAID: cancel + request refund (lewat policy default). */}
|
||||
{refundPreview && (
|
||||
<CancelBookingButton
|
||||
tripId={trip.id}
|
||||
preview={{
|
||||
days: refundPreview.days,
|
||||
refundAmount: refundPreview.refundAmount,
|
||||
bookingAmount: refundPreview.bookingAmount,
|
||||
tierLabel: refundPreview.tier.label,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Organizer: batalkan trip (auto-refund peserta PAID). */}
|
||||
{canOrganizerCancel && (
|
||||
<CancelTripButton
|
||||
tripId={trip.id}
|
||||
paidParticipantCount={paidBookingCount}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Kebijakan refund — transparency sebelum user cancel. */}
|
||||
{!tripIsFree && <RefundPolicySection />}
|
||||
|
||||
<TripReviewSection
|
||||
tripId={trip.id}
|
||||
reviews={trip.reviews.map((r) => ({
|
||||
|
||||
@@ -158,7 +158,14 @@ function FreeTripSection({
|
||||
bookingStatus,
|
||||
}: {
|
||||
tripId: string;
|
||||
bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "EXPIRED";
|
||||
bookingStatus:
|
||||
| "PENDING"
|
||||
| "AWAITING_PAY"
|
||||
| "PAID"
|
||||
| "CANCELLED"
|
||||
| "REFUNDED"
|
||||
| "PARTIALLY_REFUNDED"
|
||||
| "EXPIRED";
|
||||
}) {
|
||||
return (
|
||||
<section className="rounded-2xl border border-emerald-200 bg-emerald-50/60 p-6 text-center shadow-sm sm:p-8">
|
||||
@@ -208,7 +215,14 @@ async function PaidTripSection({
|
||||
organizerId: string;
|
||||
organizerName: string;
|
||||
price: number;
|
||||
bookingStatus: "PENDING" | "AWAITING_PAY" | "PAID" | "CANCELLED" | "REFUNDED" | "EXPIRED";
|
||||
bookingStatus:
|
||||
| "PENDING"
|
||||
| "AWAITING_PAY"
|
||||
| "PAID"
|
||||
| "CANCELLED"
|
||||
| "REFUNDED"
|
||||
| "PARTIALLY_REFUNDED"
|
||||
| "EXPIRED";
|
||||
paymentMarkedAt: Date | null;
|
||||
paymentPaidAt: Date | null;
|
||||
}) {
|
||||
|
||||
Reference in New Issue
Block a user