@nlpay/js
v1.0.5
Published
NLPay JavaScript SDK - Secure payment elements for card and ACH capture
Downloads
502
Readme
@nlpay/js
Secure, PCI-compliant payment elements for card and ACH capture. The SDK renders payment inputs inside isolated iframes, ensuring that sensitive cardholder data never touches your servers. Tokenization is handled entirely within the iframe, and your application receives only a token suitable for server-side API calls.
Table of Contents
- Installation
- Quick Start
- API Reference
- Styling
- Events
- Token Response
- React Integration
- Environments
- Supported Card Types
- Publishing Updates
Installation
npm / yarn
npm install @nlpay/jsyarn add @nlpay/jsThen import the default export:
import NLPay from '@nlpay/js';CDN (UMD)
Include the script directly in your HTML. The SDK is exposed as window.NLPay.
<script
src="https://js.nextlevelpayments.io/nlp.umd.js"
integrity="sha384-6EWRjP7RKaeylMz6qUXJmnHsNx6/ca8i1k3gxzAObytsCBOSNQNuPLs2TZdYYiax"
crossorigin="anonymous"
></script>ES Module (CDN)
<script type="module">
import NLPay from 'https://js.nextlevelpayments.io/nlp.es.js';
</script>Quick Start
<div id="card-element"></div>
<button id="pay-btn">Pay</button>
<script type="module">
import NLPay from '@nlpay/js';
// 1. Initialize the SDK with your public key
NLPay.init({ publicKey: 'pk_live_xxxxxxxx' });
// 2. Create a card element
const card = NLPay.createElement('card');
// 3. Mount it to the DOM
card.mount('#card-element');
// 4. Wait for the element to be ready
card.on('ready', () => {
console.log('Card element is ready');
});
// 5. Tokenize on form submission
document.getElementById('pay-btn').addEventListener('click', async () => {
try {
const result = await NLPay.createToken(card);
console.log('Token:', result.token);
// Send result.token to your server to complete the payment
} catch (err) {
console.error('Tokenization failed:', err.error);
}
});
</script>API Reference
NLPay.init(options)
Initialize the SDK. Must be called before creating any elements.
NLPay.init({
publicKey: 'pk_live_xxxxxxxx',
mode: 'production',
fonts: ['Inter', 'Roboto Mono'],
});Parameters
| Option | Type | Default | Description |
|-------------|------------|----------------|--------------------------------------------------------|
| publicKey | string | (required) | Your NLPay public API key |
| mode | string | 'production' | Environment: 'local', 'staging', 'sandbox', or 'production' |
| apiUrl | string | (auto) | Override the API URL for the selected mode |
| cdnUrl | string | (auto) | Override the CDN URL where iframe files are hosted |
| fonts | string[] | [] | Google Fonts to load inside the iframe |
Returns the NLPay instance (for chaining).
Throws if publicKey is not provided.
NLPay.createElement(type, options)
Create a payment element. Call init() first.
const card = NLPay.createElement('card', {
styles: { colorPrimary: '#4F46E5' },
hideZipCode: false,
hideCardholderName: false,
});
const ach = NLPay.createElement('ach', {
styles: { colorPrimary: '#4F46E5' },
hideAccountType: false,
});Parameters
| Parameter | Type | Description |
|------------|----------|-----------------------------|
| type | string | 'card' or 'ach' |
| options | object | Optional configuration (see below) |
Element Options
| Option | Type | Default | Applies To | Description |
|-----------------------|-----------|---------|-------------|------------------------------------------|
| styles | object | {} | card, ach | Style overrides (see Styling) |
| labels | object | {} | card, ach | Custom labels for form fields |
| placeholders | object | {} | card, ach | Custom placeholder text for form fields |
| hideZipCode | boolean | false | card, ach | Hide the zip/postal code field |
| hideCardholderName | boolean | false | card | Hide the cardholder name field |
| hideAccountType | boolean | false | ach | Hide the account type dropdown |
Returns a CardElement or AchElement instance.
Throws if init() has not been called or type is invalid.
element.mount(selector)
Mount the element into a DOM container. The SDK creates a secure iframe inside the target container.
card.mount('#card-element');
// Or pass a DOM element directly
const container = document.getElementById('card-element');
card.mount(container);Parameters
| Parameter | Type | Description |
|------------|-------------------------|--------------------------------------|
| selector | string \| HTMLElement | A CSS selector or a DOM element |
Returns the element instance (for chaining).
Throws if the element is already mounted or the container is not found.
element.unmount()
Remove the element from the DOM, tear down the iframe, and clean up all event listeners.
card.unmount();element.update(options)
Update the element's options after creation. Changes are applied immediately if the element is ready.
card.update({
styles: { colorPrimary: '#DC2626' },
labels: { cardNumber: 'Card #' },
hideZipCode: true,
});Accepts the same options as createElement (except type).
element.focus(field)
Programmatically focus a specific field inside the iframe.
card.focus('cardNumber'); // 'cardNumber', 'expiry', 'cvv', 'cardholderName', 'zipCode'element.clear()
Clear all form fields inside the element.
card.clear();element.on(event, callback)
Subscribe to element events. Returns an unsubscribe function.
const unsubscribe = card.on('change', (data) => {
console.log('Valid:', data.complete);
});
// Later, remove the listener
unsubscribe();See the Events section for all available events and their payloads.
NLPay.createToken(element)
Validate the element's form data and tokenize the payment information. The actual tokenization occurs inside the secure iframe -- sensitive data never reaches your page.
try {
const result = await NLPay.createToken(card);
// result.token - the payment token
// result.last4 - last 4 digits of the card number
// result.cardType - detected card brand
} catch (err) {
// err.error - error message string
console.error(err.error);
}Parameters
| Parameter | Type | Description |
|-----------|-----------|--------------------------------------------|
| element | Element | A mounted and ready CardElement or AchElement |
Returns Promise<object> -- resolves with token data or rejects with an error object.
Throws (rejects) if the element is not mounted or not ready.
NLPay.destroy()
Unmount all elements, remove all event listeners, and reset the SDK. Call this when the payment form is no longer needed.
NLPay.destroy();After calling destroy(), you must call init() again before creating new elements.
Styling
Pass a styles object when creating an element to customize the appearance of the secure iframe inputs.
const card = NLPay.createElement('card', {
styles: {
fontFamily: 'Inter, sans-serif',
fontSize: '16px',
colorText: '#1E293B',
colorPrimary: '#4F46E5',
colorBorder: '#CBD5E1',
colorError: '#DC2626',
colorBackground: '#FFFFFF',
borderRadius: '8px',
inputHeight: '44px',
spacing: '12px',
},
});Available Style Properties
| Property | Type | Default | Description |
|-------------------|----------|-----------|-------------------------------------------------|
| fontFamily | string | System default | Font family for all inputs |
| fontSize | string | '14px' | Font size for input text |
| colorText | string | '#1E293B' | Input text color |
| colorPrimary | string | '#4F46E5' | Focus ring and accent color |
| colorBorder | string | '#CBD5E1' | Input border color |
| colorError | string | '#DC2626' | Error state text and border color |
| colorBackground | string | '#FFFFFF' | Input background color |
| borderRadius | string | '6px' | Border radius for inputs |
| inputHeight | string | '40px' | Height of each input field |
| spacing | string | '12px' | Vertical spacing between fields |
To load custom Google Fonts inside the iframe, pass them in init():
NLPay.init({
publicKey: 'pk_live_xxxxxxxx',
fonts: ['Inter', 'Roboto Mono'],
});
const card = NLPay.createElement('card', {
styles: { fontFamily: 'Inter, sans-serif' },
});Events
Subscribe to events with element.on(event, callback). Each call returns an unsubscribe function.
ready
Fired when the iframe has loaded and the element is ready to accept input.
card.on('ready', () => {
console.log('Element is interactive');
});Callback receives: no data.
change
Fired when the form state changes (field input, validation state).
card.on('change', (data) => {
console.log(data.complete); // boolean - all fields valid
console.log(data.error); // string | null - current validation error
console.log(data.cardType); // string | null - detected card brand (card only)
});focus
Fired when any field inside the element gains focus.
card.on('focus', (data) => {
console.log(data.field); // string - the field that received focus
});blur
Fired when any field inside the element loses focus.
card.on('blur', (data) => {
console.log(data.field); // string - the field that lost focus
});error
Fired when a validation error or iframe-level error occurs.
card.on('error', (data) => {
console.error(data.message); // string - error description
console.error(data.field); // string | undefined - related field
});Removing Listeners
// Option 1: Use the returned unsubscribe function
const unsub = card.on('change', handler);
unsub();
// Option 2: Use element.off()
card.off('change', handler);Token Response
Card Element
On a successful createToken() call with a card element, the resolved object contains:
{
token: 'tok_xxxxxxxxxxxxxxxx', // Payment token for server-side use
last4: '4242', // Last 4 digits of the card number
cardType: 'visa', // Card brand identifier
// ...additional fields as returned by the API
}ACH Element
For ACH elements, the resolved object contains:
{
token: 'tok_xxxxxxxxxxxxxxxx', // Payment token for server-side use
// ...additional fields as returned by the API
}Fallback
If the API returns a non-token response, the result will contain a paymentData object:
{
paymentData: { ... } // Raw payment data from the iframe
}Error
On failure, the promise rejects with:
{
error: 'Validation failed: card number is invalid'
}Always wrap createToken() in a try/catch block.
React Integration
import { useRef, useEffect, useState } from 'react';
import NLPay from '@nlpay/js';
function PaymentForm({ publicKey, onToken }) {
const cardRef = useRef(null);
const elementRef = useRef(null);
const [ready, setReady] = useState(false);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
// Initialize the SDK
NLPay.init({ publicKey });
// Create and mount the card element
const card = NLPay.createElement('card', {
styles: {
fontFamily: 'Inter, sans-serif',
colorPrimary: '#4F46E5',
borderRadius: '8px',
},
});
card.mount(cardRef.current);
elementRef.current = card;
card.on('ready', () => setReady(true));
card.on('change', (data) => setError(data.error || null));
// Cleanup on unmount
return () => {
NLPay.destroy();
};
}, [publicKey]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!elementRef.current || !ready) return;
setLoading(true);
setError(null);
try {
const result = await NLPay.createToken(elementRef.current);
onToken(result);
} catch (err) {
setError(err.error || 'Tokenization failed');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div ref={cardRef} />
{error && <p style={{ color: '#DC2626' }}>{error}</p>}
<button type="submit" disabled={!ready || loading}>
{loading ? 'Processing...' : 'Pay'}
</button>
</form>
);
}
export default PaymentForm;ACH Variant
useEffect(() => {
NLPay.init({ publicKey });
const ach = NLPay.createElement('ach', {
hideAccountType: false,
});
ach.mount(achRef.current);
elementRef.current = ach;
ach.on('ready', () => setReady(true));
return () => NLPay.destroy();
}, [publicKey]);Environments
The SDK supports four environments. The mode is set during init() and determines which API and CDN endpoints are used.
| Mode | API URL | CDN URL |
|---------------|-----------------------------------------------|----------------------------------------------|
| local | http://localhost:8081 | http://localhost:4600 |
| staging | https://api-staging.nextlevelpayments.io | https://js-staging.nextlevelpayments.io |
| sandbox | https://api-sandbox.nextlevelpayments.io | https://js-sandbox.nextlevelpayments.io |
| production | https://api.nextlevelpayments.io | https://js.nextlevelpayments.io |
// Development
NLPay.init({ publicKey: 'pk_test_xxxxxxxx', mode: 'staging' });
// Production (default)
NLPay.init({ publicKey: 'pk_live_xxxxxxxx' });You can also override the URLs directly:
NLPay.init({
publicKey: 'pk_test_xxxxxxxx',
mode: 'staging',
apiUrl: 'https://custom-api.example.com',
cdnUrl: 'https://custom-cdn.example.com',
});Supported Card Types
The SDK automatically detects and validates the following card brands:
| Brand | Prefix Pattern | Accepted Lengths | CVV Length |
|------------------|-----------------------|------------------|------------|
| Visa | 4 | 13, 16, 19 | 3 |
| Mastercard | 51-55, 22-27 | 16 | 3 |
| American Express | 34, 37 | 15 | 4 |
| Discover | 6011, 65, 644-649 | 16, 19 | 3 |
| Diners Club | 300-305, 36, 38 | 14, 16, 19 | 3 |
| JCB | 2131, 1800, 35 | 15, 16, 19 | 3 |
Card numbers are validated with the Luhn algorithm. Expiry dates are checked against the current date.
Publishing Updates
When you make changes to the SDK and need to publish a new version to npm:
1. Bump the version
# Patch release (1.0.0 -> 1.0.1) — bug fixes
npm version patch
# Minor release (1.0.0 -> 1.1.0) — new features, backward compatible
npm version minor
# Major release (1.0.0 -> 2.0.0) — breaking changes
npm version majornpm version automatically updates package.json and creates a git tag.
2. Publish to npm
npm publish --access publicThe prepublishOnly script runs npm run build automatically before publishing, so the dist/ output is always fresh.
3. Update consumers
After publishing, update the dependency in each app that uses @nlpay/js:
npm update @nlpay/jsCurrent consumers:
nlp-portal— merchant admin payment modalnlp-bill-pay-app— customer bill pay (BillPayContent, QuickPayModal)nlp-payment-link— hosted payment page
Authentication
Publishing requires an npm account that belongs to the nlpay organization.
Recommended: enable 2FA on your npm account (npmjs.com > Account Settings > Two-Factor Authentication). This protects against unauthorized publishes. Once enabled, publish with:
npm publish --access public --otp YOUR_CODEAlternatively, create a granular access token at npmjs.com > Access Tokens with "Bypass 2FA" enabled for CI/CD use. Store it as a secret and authenticate via .npmrc:
echo "//registry.npmjs.org/:_authToken=YOUR_TOKEN" > .npmrc
npm publish --access publicNever commit .npmrc with tokens to version control. The .gitignore should include .npmrc.
License
Proprietary. All rights reserved.
