Loan Calculator Widget

Comprehensive Developer Guide

v0.3.0

Overview

The loan calculator is delivered as a single script, loan-calculator-combined.js. It provides createLoanCalculatorWidget for the two-column UI (slider, deposit, trade-in, and results), exports LoanCalculator for a compact form-based flow, injects its own styles, and includes lightweight polyfills for broader mobile browser support (e.g. Samsung Internet).

For most sites, you initialise the widget with createLoanCalculatorWidget(container, config). The factory returns a LoanCalculatorWidget instance, or null if creation fails (a small fallback message may be rendered in the container).

Single file

No separate CSS bundle — styles are injected when the widget loads.

Configurable API

Point apiEndpoint at your Kyiper loan calculate URL; authenticate with apiToken.

Responsive layout

Desktop two-card layout with a mobile apply flow; debounced recalculation on input changes.

Apply URL

Optional applyURL; after a successful quote, users can open apply with pAmount derived from credit amount.

Commercial GBP / VAT

When isCommercial and currency are GBP, initial asset cost handling includes VAT logic for the first calculation.

Callbacks

Optional onCalculate runs after a successful widget quote. When using LoanCalculator directly, you can also supply onError for failures handled by that class.

Installation

1. Host the script

Serve loan-calculator-combined.js from your site (or CDN). In Kyiper Dashboard it lives under static resources and is available at the site root, for example:

<script th:src="@{/loan-calculator-combined.js}" src="/loan-calculator-combined.js"></script>

Plain HTML sites can use a relative or absolute src URL.

2. Add a container

<div id="my-calculator"></div>

3. Initialise after DOM ready

document.addEventListener('DOMContentLoaded', function () {
    const widget = createLoanCalculatorWidget('#my-calculator', {
        apiEndpoint: 'https://your-host/api/loan/calculate',
        apiToken: 'your-api-token',
        environment: 'TEST',
        currency: 'EUR',
        assetCost: 25000,
        termMonths: 60,
        deposit: 0
    });
});
Note: The bundle is self-contained (styles injected at runtime). You do not need a separate CSS file for the widget UI.

Basic Usage

Factory function

Use createLoanCalculatorWidget(container, config) where container is a CSS selector string or a DOM element.

const widget = createLoanCalculatorWidget('#my-calculator', {
    apiEndpoint: '/api/loan/calculate',
    apiToken: 'your-token',
    environment: 'TEST',
    currency: 'GBP',
    assetCost: 46366,
    termMonths: 60,
    deposit: 0,
    isCommercial: false,
    lenderLogo: 'https://example.com/logo.svg',
    applyURL: 'https://apply.example.com/init/motor'
});

Minimal HTML page

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Loan Calculator Demo</title>
    <script src="loan-calculator-combined.js"></script>
</head>
<body>
    <div id="my-calculator"></div>
    <script>
        document.addEventListener('DOMContentLoaded', function () {
            createLoanCalculatorWidget('#my-calculator', {
                apiEndpoint: 'https://your-api.example.com/api/loan/calculate',
                apiToken: 'your-token',
                environment: 'TEST',
                currency: 'EUR',
                assetCost: 25000,
                termMonths: 48,
                deposit: 0
            });
        });
    </script>
</body>
</html>

Declarative Setup

If you prefer markup-driven setup, add data-loan-calculator-widget to a container. On DOMContentLoaded, the script looks for matching elements and constructs a widget from data-* attributes.

<div data-loan-calculator-widget
     data-environment="TEST"
     data-api-endpoint="https://your-host/api/loan/calculate"
     data-api-token="your-token"
     data-currency="EUR"
     data-lender-logo="https://example.com/logo.svg"
     data-apply-url="https://apply.example.com/"
     data-asset-cost="25000"
     data-term-months="60"
     data-deposit="0">
</div>
<script src="loan-calculator-combined.js"></script>

Optional hooks: data-on-calculate and data-on-error must be the names of functions on window (e.g. data-on-calculate="myHandler" resolves to window.myHandler). Optional data-interest-rate sends a fixed rate when set.

Declarative coverage: The auto-init block in loan-calculator-combined.js only reads the attributes above. It does not currently wire isCommercial, theme, or other options from data-* attributes — use createLoanCalculatorWidget in JavaScript for those.

Standalone LoanCalculator (same bundle)

window.LoanCalculator renders a compact form (asset cost, term, optional monthly payment and deposit) inside a container you provide. createLoanCalculatorWidget uses LoanCalculator with isWidget: true for shared calculation behaviour while keeping the two-column layout as the visible UI.

Constructor & validation

Typical non-widget construction:

const calc = new LoanCalculator({
    container: '#my-form-or-element',
    apiEndpoint: 'https://example.com/api/loan/calculate',
    apiToken: 'your-token',
    environment: 'TEST',  // non-widget: must be TEST or LIVE
    currency: 'EUR',
    minTermMonths: 12,
    maxTermMonths: 360,
    onCalculate: function (result) { /* ... */ },
    onError: function (err) { /* err has code, message */ }
});

Default config also includes locale, defaultProductType (e.g. 'HP'), and theme object — see DEFAULT_CONFIG in the source. For non-widget mode, apiToken, apiEndpoint, and environment are required; environment must be TEST or LIVE.

Request body (form submit)

On submit, the LoanCalculator form posts JSON with assetCost, termMonths, optional monthlyPayment, and optional depositAmount. The widget request uses the same endpoint shape with tradeIn as its own field.

Events & methods

  • Events (EventEmitter): calculating, calculate (payload includes result and warnings), error.
  • reset() — clears form and hides results.
  • setValue(field, value) — sets an input by name and triggers input handling.
  • getValue(field) — reads last calculated state value.
  • destroy() — clears container and removes listeners.

Configuration Options

These options apply to the embedded widget (createLoanCalculatorWidget / LoanCalculatorWidget). Properties marked required must be set for API calls to run.

Property Type Default Description
apiEndpoint string null Required. Full or relative URL of the loan calculate endpoint.
apiToken string null Required. Sent as X-API-Token on each request.
environment string null Required for widget initialisation checks (e.g. TEST, LIVE). The current script build does not send X-Environment on fetch (commented in source); confirm with your API if the header is needed server-side.
currency string 'EUR' ISO-style code: EUR, GBP, or USD. Display uses mapped symbols (£, €, $).
assetCost number null Initial vehicle/asset price; used for first paint and initial calculation.
termMonths number 60 Initial term; the UI slider typically ranges 12–60 in steps of 6 (see script).
deposit number 0 Initial deposit shown in the form; sent as depositAmount in the JSON body.
isCommercial boolean false When true and currency is GBP, initial asset cost is adjusted for VAT before the first API call (see script).
lenderLogo string null Optional image URL (or site-relative path) shown in the widget chrome.
applyURL string null Optional apply journey URL. After a quote, the widget can open this URL with pAmount appended from totalCreditAmount.
interestRate number null If set, included in request JSON as interestRate.
theme object see script Default visual tokens live on the module-level WIDGET_CONFIG.theme in the source. The injected stylesheet (WIDGET_STYLES) is built from that object when the script loads, not from your instance config. Passing theme or calling setTheme updates this.config.theme only; it does not change the already-injected CSS (and injectStyles returns immediately if the style tag already exists). Use host-page CSS under .loan-calc-widget to rebrand the live widget.
onCalculate function null After a successful widget API response, called with the result object (see handleCalculation in the script).
onError function null Used with new LoanCalculator({ ... }) when handleError invokes your callback. For createLoanCalculatorWidget, network failures are surfaced in the console and the UI returns to an idle state; use onCalculate for successful responses.

Theme object (defaults in source)

The widget’s look is defined by WIDGET_CONFIG.theme inside loan-calculator-combined.js and baked into WIDGET_STYLES at parse time. To change colours or radii for a given deployment, edit those defaults in the bundle (or override with your own CSS selectors). Do not rely on constructor theme or setTheme to restyle the embedded widget in the current implementation.

// Reference only — these are the bundled defaults (see WIDGET_CONFIG.theme)
{
    primaryColor: '#0056b3',
    secondaryColor: '#ffb84d',
    backgroundColor: 'transparent',
    textColor: '#222',
    borderRadius: '18px',
    fontFamily: 'system-ui, -apple-system, sans-serif'
}

API Integration

Widget request (embedded UI)

On each calculation, the widget reads the form and POSTs JSON to apiEndpoint:

POST {apiEndpoint}
Content-Type: application/json
X-API-Token: {apiToken}

{
    "assetCost": 50000,
    "depositAmount": 3000,
    "tradeIn": 1000,
    "termMonths": 60,
    "interestRate": 8.5
}

depositAmount comes from the deposit field; tradeIn is sent as its own field. interestRate is only sent when configured.

The X-Environment header is present in the design but currently commented out in loan-calculator-combined.js; align with your backend expectations.

Standalone LoanCalculator form (same file)

The LoanCalculator form submit payload may include optional monthlyPayment alongside assetCost and termMonths.

Response shape

The UI maps numeric fields onto elements with data-field attributes, including:

  • standardMonthlyRepayment — monthly payment
  • totalAmountPayable, interestPayable, totalCreditAmount
  • interestRate — shown in the “APR” row (formatted with %)
  • depositAmount, termMonths — may drive follow-up UI updates
{
    "termMonths": 60,
    "initialMonthlyRepayment": 950.00,
    "standardMonthlyRepayment": 850.00,
    "finalMonthlyRepayment": 850.00,
    "assetCost": 50000,
    "depositAmount": 4000,
    "totalCreditAmount": 45000,
    "interestPayable": 6000,
    "totalAmountPayable": 51000,
    "interestRate": 8.5,
    "warnings": {}
}
Warnings: If the API returns a string warning for warnings.depositAmount together with an updated depositAmount, the widget may sync the deposit input to match the server.

Apply URL

When applyURL is configured, the widget’s primary Apply For Finance control (desktop form submit and the mobile button path) calls redirectToApplyURL() instead of running calculate() again. The opened URL is your applyURL plus pAmount when a totalCreditAmount is present in the results panel (the numeric value is parsed from the formatted display text).

Ensure users have a successful quote in the panel before relying on pAmount. If applyURL is omitted, the same control runs a normal calculation.

Features

Widget UI

Two-column layout

Input card (vehicle price, deposit, trade-in, term slider) and results card (key figures and warnings).

Debounced calculate

Input changes trigger recalculation after a debounce (currently about 800 ms in the script) to limit API traffic.

Primary CTA

“Apply For Finance” runs a calculation or navigates to applyURL when appropriate.

Mobile layout

Secondary mobile button path in the same bundle; responsive spacing and logo placement.

Kyiper branding

Footer “Powered by” artwork is embedded in the widget template.

Browser polyfills

Lightweight shims where needed (e.g. fetch / Promise / Intl) — see console compatibility warnings.

Standalone calculator

Use new LoanCalculator(config) for a form-driven flow in your own container. Non-widget mode requires environment values LIVE or TEST — refer to the source for exact validation rules.

Instance Methods

Assign the return value of createLoanCalculatorWidget to a variable (when not null). Useful instance APIs on LoanCalculatorWidget include:

  • setTheme(partialTheme)

    widget.setTheme({ primaryColor: '#003087', secondaryColor: '#ffcc00' })

    Shallow-merges into config.theme. The injected stylesheet is not regenerated from these values; injectStyles is a no-op once #loan-calc-widget-styles exists.

  • setFormValues(values)

    widget.setFormValues({ assetCost: 30000, deposit: 2000, tradeIn: 500, termMonths: 48 })

    Updates visible inputs (and related labels where applicable) programmatically.

  • calculate()

    widget.calculate()

    Runs a calculation using current DOM field values (subject to validation guards in the script).

  • debounce(fn, wait)

    widget.debounce(myFn, 300)

    Utility exposed on the instance for advanced integrations.

The file also exports window.LoanCalculator with methods such as calculate(data), destroy(), and event emitter style hooks — see loan-calculator-combined.js for the full standalone API.

Examples

Typical integration

document.addEventListener('DOMContentLoaded', function () {
    createLoanCalculatorWidget('#my-calculator', {
        environment: 'TEST',
        apiEndpoint: 'https://your-api.example.com/api/loan/calculate',
        apiToken: 'your-token',
        currency: 'GBP',
        assetCost: 46366,
        termMonths: 60,
        deposit: 0,
        isCommercial: true,
        lenderLogo: 'https://your-cdn.example.com/lender-logo.svg',
        applyURL: 'https://apply.example.com/init/motor',
        onCalculate: function (result) {
            console.log('Quote:', result.standardMonthlyRepayment);
        }
    });
});

Use onError with new LoanCalculator({ ... }) (non-widget). It is not called for embedded widget network failures in this build.

Branding with host CSS

/* Example: override the results card after the widget mounts */
.loan-calc-widget .card-right {
    background: #111827;
}
.loan-calc-widget .card-right .calculate-button {
    background: #f59e0b;
    color: #111827;
}

Styling & Theming

Injected stylesheet

The widget mounts under .loan-calc-widget. The bundle injects one <style id="loan-calc-widget-styles"> in document.head, whose rules are generated from the module’s WIDGET_CONFIG.theme when the script loads — not from per-instance theme options.

Changing the default look

Edit WIDGET_CONFIG.theme (and thus WIDGET_STYLES) in loan-calculator-combined.js for a forked build, or layer host CSS on .loan-calc-widget … as in the example above.

Host page overrides

Custom CSS is the practical way to rebrand without modifying the bundle. Watch specificity and internal class names, which may change between releases.

Layout notes

  • Desktop: Side-by-side cards with lender logo offset.
  • Mobile: Adjusted logo and bottom spacing reserved for Kyiper footer artwork.

Troubleshooting

Common issues

Nothing renders or you see the fallback panel

Problem: createLoanCalculatorWidget returned null or the container shows the generic error panel.
Solution: Confirm the script loaded without 404s, the container selector matches a real element, and check the console for thrown errors (e.g. missing container).

CORS / network errors

Problem: Browser blocks fetch to apiEndpoint.
Solution: Use a same-origin path, or configure the API for cross-origin POSTs from your site. Absolute URLs to another host require correct CORS and HTTPS/mixed-content rules.

401 / 403 from API

Problem: Token rejected.
Solution: Ensure apiToken matches an active Kyiper API token for the intended environment and company.

Environment header

The script currently comments out X-Environment on requests. If your deployment requires it, either enable it in the script or accept server defaults — coordinate with backend owners.

Console diagnostics

The script logs VAT/debug and calculation traces during development. Open DevTools to inspect payloads and warning handling.

Performance

  • Serve loan-calculator-combined.js with caching headers or via CDN.
  • Avoid mounting dozens of widgets on one page without testing debounce behaviour.