Files
setrip/lib/refund-policy.ts
T
2026-05-11 13:04:20 +07:00

94 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Refund policy hardcoded untuk MVP (PR-R3). Akan jadi data-driven di PR-R5.
*
* Aturan: hitung persentase refund berdasarkan jarak hari ke tanggal berangkat
* (UTC calendar day). Selalu integer rupiah — pakai Math.floor supaya tidak
* ada sub-rupiah dan total refund tidak pernah melebihi nominal yang dibayar.
*
* Tier:
* - ≥7 hari sebelum berangkat → 80% refund (organizer ambil 20% admin fee)
* - 36 hari sebelum berangkat → 50% refund
* - <3 hari sebelum berangkat / sudah lewat → 0% (tidak ada refund)
*/
import { utcStartOfDay } from "@/lib/trip-dates";
export interface RefundTier {
/** Minimum jumlah hari sebelum berangkat untuk tier ini. */
minDaysBefore: number;
/** Persentase nominal yang di-refund (0100). */
refundPercentage: number;
/** Label untuk UI. */
label: string;
}
const TIERS: RefundTier[] = [
{ minDaysBefore: 7, refundPercentage: 80, label: "≥ 7 hari sebelum berangkat" },
{ minDaysBefore: 3, refundPercentage: 50, label: "36 hari sebelum berangkat" },
{ minDaysBefore: 0, refundPercentage: 0, label: "Kurang dari 3 hari / sudah lewat" },
];
export function getRefundPolicyTiers(): RefundTier[] {
return TIERS;
}
/**
* Jumlah hari kalender UTC dari sekarang ke tanggal berangkat. Negative kalau
* tanggal sudah lewat. Pakai start-of-day UTC supaya jam tidak mempengaruhi.
*/
export function daysUntilDeparture(
departureDate: Date,
now: Date = new Date()
): number {
const todayMs = utcStartOfDay(now).getTime();
const depMs = utcStartOfDay(departureDate).getTime();
const oneDayMs = 24 * 60 * 60 * 1000;
return Math.floor((depMs - todayMs) / oneDayMs);
}
/** Tier aktif untuk jumlah hari yang diberikan. */
export function getTierForDays(days: number): RefundTier {
for (const tier of TIERS) {
if (days >= tier.minDaysBefore) {
return tier;
}
}
return TIERS[TIERS.length - 1];
}
/**
* Hitung nominal refund (IDR integer) berdasarkan harga booking dan jarak ke
* tanggal berangkat. Floor supaya tidak pernah > bookingAmount.
*/
export function calculateRefundAmount(
bookingAmount: number,
days: number
): number {
if (bookingAmount <= 0) return 0;
const tier = getTierForDays(days);
return Math.floor((bookingAmount * tier.refundPercentage) / 100);
}
export interface RefundPreview {
days: number;
tier: RefundTier;
refundAmount: number;
bookingAmount: number;
}
/** Bundle lengkap untuk display di UI — preview cancel booking. */
export function previewRefund(
bookingAmount: number,
departureDate: Date,
now: Date = new Date()
): RefundPreview {
const days = daysUntilDeparture(departureDate, now);
const tier = getTierForDays(days);
return {
days,
tier,
refundAmount: calculateRefundAmount(bookingAmount, days),
bookingAmount,
};
}