npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

canadian-car-finance

v0.2.1

Published

Canadian car financing & leasing calculation utilities — amortization, lease formulas, provincial taxes, and dealer fee analysis

Readme

Canadian Car Finance

The calculation engine for Canadian car financing. It keeps tax math accurate, surfaces dealer fees clearly, and exports extensible rules you can update without forking.

npm version license: MIT tests CI zero dependencies Node >=18

Powers the live Car Expense Pro web app.

Designed for developers who need correct Canadian car finance and lease calculations without hidden assumptions.

See CHANGELOG.md for release notes and project history.


Quickstart · 🧮 API Reference · 🔧 Extensibility · 🗺️ Provincial Taxes · 📦 Exported Constants · 🛠 Develop


Why it exists

Most Canadian car financing calculators on npm either ignore provincial tax split rules or silently use the wrong formula. Three problems compound:

  • BC, MB, and SK charge PST on the full vehicle price — the trade-in does not reduce the PST base. Every calculator we checked (including major dealer sites) got this wrong and underquoted tax by hundreds of dollars.
  • Dealer fee rules change. PPSA registration fees, admin fee caps, and AMVIC levy amounts are updated by regulators. Hard-coded thresholds become stale and there is no way to fix them without patching the library.
  • There is no open package that does all three: loan amortization + lease formula + provincial taxes + fee analysis — with correct math and extensible constants.

This library is the corrected, extensible foundation. All tax rates, fee thresholds, and keyword lists are exported so you can update a single constant when the rules change, without waiting for a new package release.


⚡ Quickstart

npm install canadian-car-finance

Requires Node.js ≥ 18. Ships ESM, CommonJS, and .d.ts declarations.

Loan (financing)

import { calculateLoan, formatCAD } from 'canadian-car-finance';

const result = calculateLoan({
  vehiclePrice: 35_000,
  province: 'AB',
  downPayment: 5_000,
  tradeInValue: 0,
  fees: [
    { name: 'Freight + PDI',   amount: 2200, category: 'mandatory'  },
    { name: 'PPSA',            amount: 25,   category: 'mandatory'  },
    { name: 'Admin / Doc Fee', amount: 499,  category: 'negotiable' },
  ],
  interestRate: 5.99,   // APR %
  termMonths: 60,
  frequency: 'bi-weekly',
});

console.log(formatCAD(result.payment));       // "$742.16"
console.log(formatCAD(result.totalInterest)); // "$4,832.10"

Lease

import { calculateLease } from 'canadian-car-finance';

const result = calculateLease({
  vehiclePrice: 45_977,
  province: 'ON',
  downPayment: 3_825,
  tradeInNet: 0,
  manufacturerRebate: 0,
  fees: [{ name: 'Freight + PDI', amount: 2200, category: 'mandatory' }],
  residualValue: 32_797,
  leaseTermMonths: 24,
  interestRate: 1.99,
  securityDeposit: 0,
  frequency: 'monthly',
});

console.log(result.basePayment);  // monthly before tax
console.log(result.totalPayment); // monthly all-in

Fee analysis

import { analyzeInput } from 'canadian-car-finance';

const warnings = analyzeInput(loanInput);
// FeeWarning[] sorted: red → yellow → green
// Each warning: { severity, title, detail, script? }

🔧 Extensibility

All defaults are exported constants. Override only what changed — no fork, no monkey-patching.

Override a tax rate

import { computeFinancedAmount, type TaxOverrides } from 'canadian-car-finance';

// QC raises QST — combined rate moves to 15.0%
const overrides: TaxOverrides = {
  QC: { label: 'GST+QST', rate: 0.15 },
};

const { financedAmount, taxAmount } =
  computeFinancedAmount(40_000, 'QC', 5_000, 0, 1_800, overrides);

Override fee warning thresholds

import { analyzeInput, type FeeAnalysisOptions } from 'canadian-car-finance';

const options: FeeAnalysisOptions = {
  ppsa:     { yellowThreshold: 50,  redThreshold: 120  },
  adminFee: { yellowThreshold: 600, redThreshold: 1100 },
};

const warnings = analyzeInput(input, options);

Add custom red-flag or negotiable keywords

const options: FeeAnalysisOptions = {
  extraRedFlagKeywords:    ['rustproofing', 'undercoating', 'dealer markup'],
  extraNegotiableKeywords: ['placement fee'],
};

[!TIP] Use RULES_VERSION to assert at runtime that the constants your app was built against are still the active set:

import { RULES_VERSION } from 'canadian-car-finance';
// '0.2.0'

🧮 API Reference

calculateLoan(input: LoanInput): LoanResult

| Field | Description | |---|---| | payment | Per-period payment | | totalPaid | Total paid over term | | totalInterest | Interest portion | | financedAmount | Principal financed | | taxAmount | Tax paid on vehicle | | totalCashPrice | Out-of-pocket if paying cash |

calculateLease(input: LeaseInput): LeaseResult

| Field | Description | |---|---| | basePayment | Payment before tax | | taxOnMonthly | Tax applied per payment | | totalPayment | Payment including tax | | totalCost | Total outflow over term | | capitalizedCost | Net cap cost (depreciation base) |

computeFinancedAmount(vehiclePrice, province, downPayment, tradeInValue, totalFees, taxOverrides?)

Returns { financedAmount: number, taxAmount: number }. Clamped to 0 — never negative.

analyzeInput(input: LoanInput | LeaseInput, options?: FeeAnalysisOptions): FeeWarning[]

Returns warnings sorted 'red' → 'yellow' → 'green'. Each FeeWarning:

{ severity: 'red' | 'yellow' | 'green', title: string, detail: string, script?: string }

script is a dealer negotiation script when present (red/yellow warnings only).

suggestCategory(name: string, options?): 'mandatory' | 'negotiable' | 'watchout'

Keyword-matches a fee name. Merge extra keywords via options.


🗺️ Provincial Tax Handling

The three split-PST provinces are where most calculators get the math wrong:

HST / GST-only provinces (AB, ON, QC, Atlantic, territories):
  tax = (vehiclePrice − tradeIn) × rate

Split GST+PST provinces (BC, MB, SK):
  GST = (vehiclePrice − tradeIn) × 0.05   ← trade-in reduces GST base
  PST = vehiclePrice × pstRate             ← PST is on the FULL price
  tax = GST + PST

| Province | Tax label | Combined rate | Trade-in reduces PST? | |---|---|---|---| | AB, NT, NU, YT | GST | 5% | N/A | | ON | HST | 13% | Yes | | NB, NL, NS, PE | HST | 15% | Yes | | QC | GST+QST | ~14.975% | Yes | | BC | GST+PST | 12% (5+7) | PST: No | | MB | GST+PST | 12% (5+7) | PST: No | | SK | GST+PST | 11% (5+6) | PST: No |

[!IMPORTANT] If a tax rate changes in a provincial budget, update it via TaxOverrides in your app — no library update needed. See CONTRIBUTING.md for how to submit a rate correction upstream.


📦 Exported Constants

| Export | Value / Description | |---|---| | RULES_VERSION | '0.2.0' — bump this when any threshold or rate changes | | PROVINCE_TAX | Full Record<Province, TaxInfo> — all 13 provinces/territories | | PROVINCE_NAMES | Full English province names | | DEFAULT_RED_FLAG_KEYWORDS | Built-in watchout fee keywords (nitrogen, etching, protection packages…) | | DEFAULT_NEGOTIABLE_KEYWORDS | Built-in negotiable fee keywords (admin, doc fee, processing…) | | DEFAULT_PPSA_THRESHOLDS | { yellowThreshold: 40, redThreshold: 100 } | | DEFAULT_ADMIN_FEE_THRESHOLDS | { yellowThreshold: 499, redThreshold: 900 } | | FILING_FEE_KEYWORDS | ['filing', 'registry', 'licensing', 'document'] — one-time lease fees | | AB_DEFAULT_FEES | Pre-populated Alberta fees (freight, AMVIC levy, PPSA, admin) | | QUICK_ADD_PRESETS | Common fee presets for all provinces |


🛠 Development

git clone https://github.com/nishant-tamilselvan/canadian-car-finance
cd canadian-car-finance
npm install

npm test           # vitest — 82 tests across 4 files
npm run build      # tsup → dist/ (ESM + CJS + .d.ts)
npm run lint       # tsc type-check, no emit
npx vitest run --coverage   # coverage report (≥90% statements)

The library has zero runtime dependencies. The build produces three artifacts:

dist/index.js      ESM
dist/index.cjs     CommonJS
dist/index.d.ts    TypeScript declarations

Contributions welcome — see CONTRIBUTING.md. When submitting a rate or threshold change, link the official government source in your PR description.


License

MIT © nishant-tamilselvan