Skip to main content

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, endDate
  • guestServiceFeePct, hostServiceFeePct
  • weeklyDiscountPct, monthlyDiscountPct
  • deliveryFee, isDeliverySelected
  • specialPricing (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 securityDeposit or guestFeePct.

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 the CancellationPolicy.
  • getDisplayItems: Maps internal state items to user-friendly UI objects.

The Execution Pipeline

runBilling(ctx)

  1. Initializes a fresh BillingState.
  2. Loops through all registered sections (Rental, Delivery, Discount, etc.).
  3. Each section calls state.addItem(...) to record its impact.
  4. The Engine calculates the final totals.

runRefund(ctx, originalState, policy)

  1. Initializes a fresh BillingState (the refund state).
  2. Loops through the sections again.
  3. Each section looks at the originalState (what was originally charged).
  4. Based on the policy (Prior/Before/During) and the cancellation date, it adds "Refund" line items to the refund state.

generateUI(ctx, state, view)

  1. Takes a BillingState (either from runBilling or runRefund).
  2. Calls getDisplayItems on every section.
  3. 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.