Billing Engine Documentation
Overview
The Billing Engine is a modular, pipeline-based system designed to handle complex financial calculations for car rentals, including discounts, service fees, delivery fees, and security deposits. It manages both the Initial Billing (when a trip is booked) and Refunds (when a trip is cancelled) using the same business logic fragments.
Core Concepts
1. BillingContext
The "Input" to the engine. It contains all raw parameters required for calculation:
basePrice,startDate,endDateguestServiceFeePct,hostServiceFeePctweeklyDiscountPct,monthlyDiscountPctdeliveryFee,isDeliverySelectedspecialPricing(array of days with specific overrides)cancellation(object containing cancellation date and performer)
2. BillingState
The "Accumulator" or "Output". It tracks:
lineItems: An array of calculated charges/credits.guestTotal/hostTotal: Computed sums derived from the line items.- Metadata: State-specific values like
securityDepositorguestFeePct.
3. BillingSection
An abstract class that every "feature" (Rental, Discount, Fee) must implement.
processBilling: Adds charges/credits for a standard booking.processRefund: Calculates how much of a specific charge should be returned based on theCancellationPolicy.getDisplayItems: Maps internal state items to user-friendly UI objects.
The Execution Pipeline
runBilling(ctx)
- Initializes a fresh
BillingState. - Loops through all registered sections (
Rental,Delivery,Discount, etc.). - Each section calls
state.addItem(...)to record its impact. - The Engine calculates the final totals.
runRefund(ctx, originalState, policy)
- Initializes a fresh
BillingState(the refund state). - Loops through the sections again.
- Each section looks at the
originalState(what was originally charged). - Based on the
policy(Prior/Before/During) and the cancellation date, it adds "Refund" line items to the refund state.
generateUI(ctx, state, view)
- Takes a
BillingState(either fromrunBillingorrunRefund). - Calls
getDisplayItemson every section. - Groups items into sections (Rental Costs, Deductions, Payout, etc.) based on the
ViewContext(Guest vs Owner).
calculateFinalSettlement(ctx, bookingState, finalState)
Use this for transaction-safe settlement outputs. The calculation is owned by the Claim Settlement section and always returns normalized final values for:
- booking only
- claim only
- cancellation only
- cancellation + claim
Returned values include final guest refund, final host payout, claim deduction, and deposit refund after claim.
Extension Guide: Adding a new Section
To add a new feature (e.g., Discount Codes), follow these steps:
1. Create the Section Class
Create a new file in sections/DiscountCodeSection.ts extending BillingSection.
import { BillingSection } from "./BillingSection";
import {
BillingContext,
BillingState,
CancellationPolicy,
PriceBreakdownItem,
ViewContext,
} from "../core/types";
export class DiscountCodeSection extends BillingSection {
name = "discount_code";
processBilling(ctx: BillingContext, state: BillingState): void {
const promoCode = ctx.params.metadata?.promoCode;
if (promoCode === "SAVE10") {
// logic to subtract 10 from total
state.addItem({
code: "promo_discount",
amount: -10,
isDeduction: true,
metadata: { code: promoCode },
});
}
}
processRefund(
ctx: BillingContext,
originalState: BillingState,
refundState: BillingState,
policy: CancellationPolicy
): void {
const originalPromo = originalState.findItem("promo_discount");
if (originalPromo) {
// Promo codes are usually non-refundable or reversed 1:1
refundState.addItem({
code: "promo_discount_reversal",
amount: originalPromo.amount, // e.g. -10
isDeduction: false,
});
}
}
getDisplayItems(
ctx: BillingContext,
state: BillingState,
view: ViewContext
): PriceBreakdownItem[] {
const promo = state.findItem("promo_discount");
if (!promo) return [];
return [
{
label: "billing.promo_discount",
value: Math.abs(promo.amount),
isDeduction: true,
code: "promo_discount",
},
];
}
}
2. Register the Section
Add your new section to the BillingEngine.createDefault() method:
// BillingEngine.ts
static createDefault(): BillingEngine {
return new BillingEngine([
new RentalSection(),
new DeliverySection(),
new DiscountSection(),
new DiscountCodeSection(), // <-- Add it here
new ServiceFeeSection(),
new SecurityDepositSection(),
]);
}
3. Add Translations
Ensure you add the translation key (e.g., billing.promo_discount) to the locales/en.json file in the frontend.