94 lines
2.8 KiB
TypeScript
94 lines
2.8 KiB
TypeScript
/**
|
||
* 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)
|
||
* - 3–6 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 (0–100). */
|
||
refundPercentage: number;
|
||
/** Label untuk UI. */
|
||
label: string;
|
||
}
|
||
|
||
const TIERS: RefundTier[] = [
|
||
{ minDaysBefore: 7, refundPercentage: 80, label: "≥ 7 hari sebelum berangkat" },
|
||
{ minDaysBefore: 3, refundPercentage: 50, label: "3–6 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,
|
||
};
|
||
}
|