refund roadmap pr-1 and pr-2

This commit is contained in:
2026-05-11 13:04:20 +07:00
parent d2b0a780d5
commit 54f4569107
36 changed files with 5750 additions and 19 deletions
+55
View File
@@ -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) => ({