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

@neocash/bnpl-widget

v0.1.12

Published

Embeddable NeoCash BNPL checkout widget

Readme

@neocash/bnpl-widget

Embeddable NeoCash BNPL checkout widget — let your customers split a purchase into a small upfront payment plus a few monthly installments ("Pay Small Small"), without leaving your site.

The widget renders as a modal overlay on top of your page. It handles the full BNPL flow end-to-end: plan selection, identity verification (BVN + selfie liveness), credit decisioning, and the deposit-account hand-off for the customer's first payment. You give it a cart and a public key — it returns an application id when the customer submits.


Quick start

Drop-in script tag

<script src="https://unpkg.com/@neocash/bnpl-widget"></script>
<script>
  document.getElementById('pay-button').onclick = () => {
    NeoCashBNPL.init({
      publicKey: 'pk_test_xxx',
      cart: {
        items: [{ name: 'Hisense 55" TV', qty: 1, price: 21500000 }],
        total: 21500000,
      },
      onApprovalPending: (applicationId) => {
        // Customer submitted — application is under review.
        console.log('pending:', applicationId);
      },
      onClose: () => console.log('widget closed'),
    });
  };
</script>

npm / bundler

npm install @neocash/bnpl-widget
import { init } from '@neocash/bnpl-widget';

const handle = init({ publicKey, cart, onApprovalPending, onClose });
// later, if you want to dismiss the widget yourself:
handle.close();

The money contract

All amounts are in kobo (integer naira × 100.) Pass 21500000 for ₦215,000.

This applies to every monetary field the widget accepts or returns:

  • CartItem.price, Cart.total
  • PartnerPrefill.monthlySalary
  • Server-returned deposit amounts

The widget converts kobo to naira only at display time. The backend stores and returns kobo. Don't pre-convert on your side.


Configuration

init(options) accepts:

| Field | Type | Required | Description | |---|---|:---:|---| | publicKey | string | yes | Your merchant public key (pk_test_... or pk_live_...). Issued from the NeoCash dashboard. | | cart | Cart | yes | The basket the customer is checking out. See below. | | partnerPrefill | PartnerPrefill | no | Customer details you already know — skips re-entry on identity/employment screens. | | theme | ThemeOptions | no | Color and font overrides. See Theming. | | container | HTMLElement \| string | no | Element (or selector) to mount inside. Defaults to a fresh <div> appended to <body>. | | onApprovalPending | (applicationId: string) => void | no | Fires once when the customer submits and the application enters review. | | onClose | () => void | no | Fires whenever the modal is dismissed (X, backdrop, or handle.close()). | | onError | (err: Error) => void | no | Surfaced for unrecoverable widget errors. Useful for telemetry. |

Cart shape

{
  items: [
    { name: 'iPhone 15 Pro', qty: 2, price: 145000000, imageUrl: '...' },
    { name: 'AirPods Pro',   qty: 1, price: 18500000 },
  ],
  total: 308500000,    // kobo
  currency: 'NGN',     // optional, defaults to NGN
}

imageUrl is optional; if provided it shows on the cart summary screen.

PartnerPrefill (optional)

Any subset of the customer's details you already have. The widget pre-populates the matching fields and skips screens where every required field is filled:

partnerPrefill: {
  firstName: 'Adaeze',
  lastName: 'Okafor',
  phone: '+2348012345678',
  email: '[email protected]',
  bvn: '12345678901',
  employer: 'Acme Industries',
  role: 'Product Manager',
  monthlySalary: 85000000,           // kobo — ₦850,000
  officeAddress: '12 Bourdillon Rd, Ikoyi, Lagos',
  homeAddress: '7 Admiralty Way, Lekki, Lagos',
}

Theming

The widget ships with NeoCash defaults but you can match it to your brand. Pass any subset of these — omitted keys keep their built-in values:

NeoCashBNPL.init({
  publicKey,
  cart,
  theme: {
    primary:       '#ff5a1f',
    primaryStrong: '#e84a10',   // hover/active shade
    primarySoft:   '#ffb89a',   // muted accent
    primaryWash:   '#fff2eb',   // selected-row background
    ink:           '#1a1a2e',   // main text color
    surface:       '#f7f8fa',   // panel background
    border:        '#e9eaf0',   // hairline borders
    fontFamily:    '"Inter", system-ui, sans-serif',
  },
});

All values are passed straight through to CSS, so any valid color string (#rrggbb, rgb(), hsl(), named) and any valid font stack work. Theme overrides are scoped to the widget's mount root and never leak to your page.

If you only set primary, the derived shades keep their defaults — fine for purple-ish brands, less so for a hot-orange brand. Set the full primary* family when the contrast needs to match.


Handling outcomes

The widget is a self-contained flow; you don't poll for state. The two events you care about:

NeoCashBNPL.init({
  // ...
  onApprovalPending: (applicationId) => {
    // The customer has submitted. The application is now in review
    // (typically seconds, sometimes minutes for manual checks).
    // Persist `applicationId` against your order so you can reconcile
    // when the decision webhook fires.
  },
  onClose: () => {
    // Modal dismissed — could be after success, mid-flow drop-off,
    // or after an error. Use this to clean up UI on your page.
  },
  onError: (err) => {
    // Network failure, malformed config, or an unexpected widget error.
    // Forward to your monitoring (Sentry/Datadog/etc).
  },
});

Decision delivery (approved / declined / additional checks required) happens via the webhook configured in your NeoCash dashboard, keyed by applicationId. The widget itself does not surface the final decision to your page.


Mounting

By default the widget appends a <div> to <body> and renders an overlay on top of your page. To mount inside an existing element instead:

init({
  publicKey,
  cart,
  container: '#checkout-slot',     // selector
  // or: container: document.getElementById('checkout-slot'),
});

init() returns a handle so you can dismiss the widget programmatically:

const handle = init({ ... });
// some time later:
handle.close();

Each init() call is independent — you can mount multiple instances on the same page if needed; closing one does not affect another.


Test mode

Public keys come in two flavors:

  • pk_test_... — sandbox. Uses test BVNs, test liveness, and never debits real accounts. Use this throughout development and CI.
  • pk_live_... — production. Real identity checks, real money.

Switching modes is a key swap only; no other code changes are required.


TypeScript

Types ship in the package:

import type {
  InitOptions,
  ThemeOptions,
  Cart,
  CartItem,
  PartnerPrefill,
  WidgetHandle,
} from '@neocash/bnpl-widget';

Peer dependencies (npm install only)

The identity-verification step uses AWS Amplify's FaceLivenessDetector, which is lazy-loaded so it never enters your main bundle.

If you install the widget via npm, also install the peers:

npm install @aws-amplify/ui-react-liveness aws-amplify

The script-tag (unpkg) build loads these from CDN on demand, so drop-in embeds need no extra setup.


Browser support

Evergreen Chromium, Firefox, Safari (current and previous major). The liveness step requires camera access (getUserMedia) and so won't work in restricted browser contexts (iframes without allow="camera", etc.).


Support

Integration questions: [email protected] Dashboard, keys, webhooks: dashboard.neocash.ng