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

@akinon/akifast-secure-form

v2.0.2

Published

Akifast Secure Form Library. This library allows clients to create payment forms within their styles. By using this form clients are also stay in secure field of PCI-DSS

Readme

@akinon/akifast-secure-form

A comprehensive PCI-DSS compliant payment form library for secure card data collection in web applications.

npm version TypeScript License: ISC

📚 Table of Contents


Overview

@akinon/akifast-secure-form is a production-ready payment form library designed to help merchants collect card data securely without sensitive information touching their servers. Built with TypeScript and following PCI-DSS security standards, it provides two flexible implementation approaches to suit different use cases.

Why Use This Library?

PCI-DSS Compliant - Sensitive card data never touches your servers
Two Flexible Approaches - Choose between single iframe or individual field iframes
Full TypeScript Support - Comprehensive type definitions with JSDoc
Event-Driven Architecture - Type-safe event subscriptions
Customizable Styling - Full control over form appearance
Automatic Font Sync - Seamlessly matches your site's typography
Input Validation - Real-time validation with custom error messages
BIN Detection - Automatic card brand detection
Production Ready - Used in production by Akinon clients


Features

Core Security Features

  • Iframe Isolation: Card data is processed in secure, isolated iframes
  • Origin Validation: Strict origin checking prevents XSS attacks
  • PostMessage Communication: Secure cross-origin communication
  • No Data Leakage: Card details never exposed to parent page JavaScript
  • CSP Support: Content Security Policy headers for additional protection

Developer Experience

  • TypeScript First: Written in TypeScript with full type definitions
  • Event System: Comprehensive event system for all form interactions
  • Custom Validation: Define custom validation rules and error messages
  • Dynamic Styling: Apply styles programmatically or via configuration
  • Font Management: Automatic detection and synchronization of web fonts
  • Automatic Resizing: Iframe height adjusts automatically to content

Payment Features

  • Multiple Card Types: Visa, Mastercard, American Express, and more
  • BIN Lookup: Automatic card brand detection from first 6-8 digits
  • Card Tokenization: Secure token generation for payment processing
  • Provider Tokenization: Support for payment provider-specific tokenization (e.g., Tap)
  • Input Masking: Automatic formatting for card numbers, expiry dates
  • CVV Protection: Built-in secret field support with toggle visibility

Installation

NPM

npm install @akinon/akifast-secure-form

Yarn

yarn add @akinon/akifast-secure-form

Requirements

  • Node.js >= 18.17.0
  • Modern browser with ES6+ support
  • iframe-enabled environment

Two Implementation Approaches

This library offers two distinct approaches for integrating secure payment forms:

| Feature | SecurePaymentForm | SecurePaymentFields | |---------|------------------|---------------------| | Iframe Structure | Single iframe containing entire form | Multiple iframes, one per field | | Use Case | Complete form solutions, simple integration | Fine-grained control, custom layouts | | Styling Flexibility | Form-level styling | Individual field-level styling | | DOM Integration | Single container element | Multiple container elements | | Event Granularity | Form-level events | Field-level events | | Best For | Checkout pages, simple forms | Complex UIs, custom designs |


SecurePaymentForm (Single Iframe)

Overview

SecurePaymentForm (also available as AkifastSecureForm) renders the entire payment form inside a single iframe. This approach is ideal for straightforward implementations where you want a complete, self-contained payment form.

Quick Start

import { SecurePaymentForm } from '@akinon/akifast-secure-form';

const paymentForm = new SecurePaymentForm({
  iframeHostUrl: 'https://secure-form.example.com',
  publicKeyToken: 'pk_test_your_public_key_here',
  iframeContainerId: 'payment-form-container',
  lang: 'tr', // optional, defaults to 'tr'
  formFields: {
    groups: [
      {
        direction: 'column',
        fields: [
          {
            name: 'cardNumber',
            type: 'card',
            label: 'Kart Numarası',
            placeholder: '1234 5678 9012 3456',
            style: {
              width: '100%',
              padding: '12px 16px',
              fontSize: '16px',
              border: '2px solid #e2e8f0',
              borderRadius: '8px'
            }
          },
          {
            name: 'cardHolder',
            type: 'cardHolder',
            label: 'Kart Üzerindeki İsim',
            placeholder: 'JOHN DOE'
          },
          {
            name: 'expiryMonth',
            type: 'monthText',
            label: 'Ay',
            placeholder: '12'
          },
          {
            name: 'expiryYear',
            type: 'yearText',
            label: 'Yıl',
            placeholder: '25'
          },
          {
            name: 'cvv',
            type: 'cvv',
            label: 'CVV',
            placeholder: '123',
            secret: true,
            showSecretToggle: true
          }
        ]
      }
    ]
  }
});

// Initialize the form
paymentForm.init();

// Listen for events
paymentForm.onReady(() => {
  console.log('Form is ready');
});

paymentForm.onTokenizeSuccess((response) => {
  const { merchant_card_token, masked_card_number } = response.data;
  console.log('Token:', merchant_card_token);
  console.log('Masked Card:', masked_card_number);
  // Send token to your backend for payment processing
});

// Trigger tokenization
document.getElementById('pay-button').addEventListener('click', () => {
  paymentForm.tokenize();
});

Configuration Options

Constructor Parameters

interface IAkifastSecureForm {
  iframeHostUrl: string;        // Required: HPP server URL
  publicKeyToken: string;        // Required: Your public API key
  iframeContainerId: string;     // Required: Container element ID
  formFields: IFieldset;         // Required: Form structure definition
  lang?: string;                 // Optional: Language code (default: 'tr')
  baseStyles?: {                 // Optional: Global iframe styles
    [selector: string]: Partial<CSSStyleDeclaration>;
  };
}

Field Types

| Type | Description | Validation | |------|-------------|------------| | card | Card number input | Luhn algorithm, brand detection | | cvv | Security code | 3-4 digits | | cardHolder | Full cardholder name | String validation | | firstname | First name only | String validation | | lastname | Last name only | String validation | | expiryDate | MM/YY combined (masked input) | Date validation | | expiryDateText | MM/YY combined (text inputs) | Date validation | | monthText | Expiry month (select/input) | 01-12 | | yearText | Expiry year (select/input) | Current year + range |

Field Configuration

interface IFlatField {
  name: string;                              // Required: Unique field identifier
  type: IFieldType;                          // Required: Field type from table above
  label: string;                             // Required: Display label
  placeholder?: string;                      // Optional: Placeholder text
  style?: Partial<CSSStyleDeclaration>;      // Optional: Field-specific CSS
  labelStyle?: 'normal' | 'normal-animated' | 'border' | 'border-animated'; // Optional
  secret?: boolean;                          // Optional: Hide input (for CVV)
  showSecretToggle?: boolean;                // Optional: Show/hide toggle button
  maxLength?: number;                        // Optional: Max character length
  minLength?: number;                        // Optional: Min character length
  disabled?: boolean;                        // Optional: Disable field
  disabledValidationErrors?: boolean;        // Optional: Suppress error messages
  validationMessages?: {                     // Optional: Custom validation messages
    required?: string;
    invalid?: string;
    min?: string;
    max?: string;
  };
  customProperties?: {                       // Optional: For expiryDate field
    year?: { /* year field config */ };
    month?: { /* month field config */ };
  };
}

Field Validation Rules

Conflicting Field Combinations (will throw error):

  • cardHolder + (firstname OR lastname)
  • expiryDate + expiryDateText
  • ❌ (expiryDate OR expiryDateText) + (monthText OR yearText)
  • ❌ Duplicate field types in the same form

Event System

Lifecycle Events

// Form ready
paymentForm.onReady((data: IReadyEvent) => {
  console.log('Form is ready for user interaction');
});

Validation Events

// Form becomes valid
paymentForm.onValid((data: IValidEvent) => {
  console.log('All fields valid:', data.data);
  document.getElementById('pay-button').disabled = false;
});

// Form becomes invalid
paymentForm.onInvalid((data: IInvalidEvent) => {
  console.log('Invalid fields:', data.data);
  document.getElementById('pay-button').disabled = true;
});

// Field loses focus
paymentForm.onBlur((data: IBlurEvent) => {
  console.log(`Field ${data.fieldType} lost focus`);
  console.log('Field state:', data.fieldState);
});

Card Detection Events

// Valid BIN entered (first 6-8 digits)
paymentForm.onBinEntered((data: IBinEnteredEvent) => {
  console.log('Card brand:', data.data.niceType); // e.g., "Visa"
  console.log('Card type:', data.data.cardType);  // e.g., "visa"
  console.log('BIN:', data.data.binNumber);
  // Update UI with card brand logo
});

// Invalid BIN entered
paymentForm.onInvalidBinEntered((data: IInvalidBinEnteredEvent) => {
  console.log('Invalid card number detected');
});

// BIN lookup failed
paymentForm.onBinNotFound((data: IBinNotFoundEvent) => {
  console.log('Could not determine card brand');
});

Tokenization Events

// Tokenization success
paymentForm.onTokenizeSuccess((data: ITokenizeSuccessEvent) => {
  const {
    merchant_card_token,  // Use this token for payment
    masked_card_number,   // e.g., "1234 56** **** 7890"
    trace_id              // For tracking/debugging
  } = data.data;
  
  // Send to your backend
  processPayment(merchant_card_token);
});

// Tokenization error
paymentForm.onTokenizeError((data: ITokenizeErrorEvent) => {
  console.error('Tokenization failed:', data.message);
  showErrorToUser(data.message);
});

// Security violation detected
paymentForm.onSecurityViolation((data: ISecurityViolationEvent) => {
  console.warn('Security violation:', data);
  // Log security incident
});

Payment Provider Tokenization

// Provider-specific tokenization success (e.g., Tap)
paymentForm.onPaymentProviderTokenizationSuccess((data) => {
  const {
    token,
    masked_card_number,
    payment_system_code,
    tokenization_type
  } = data.data;
  
  console.log(`${payment_system_code} token:`, token);
});

// Provider tokenization error
paymentForm.onPaymentProviderTokenizationError((data) => {
  console.error('Provider tokenization failed:', data.message);
});

API Methods

init(): void

Initializes the secure form iframe and mounts it to the DOM.

paymentForm.init();

tokenize(): void

Triggers the card tokenization process. Listen for success/error with event handlers.

document.getElementById('pay-button').onclick = () => {
  paymentForm.tokenize();
};

paymentProviderTokenization(options): void

Requests tokenization from a payment provider (e.g., Tap for Apple Pay).

paymentForm.paymentProviderTokenization({
  paymentSystemCode: 'TAP',
  tokenizationType: 'TAP_ENCRYPTION'
});

updateFontFamily(): void

Manually syncs the current page's fonts with the iframe.

paymentForm.updateFontFamily();

setFontFamily(options): void

Sets a specific font family for the iframe.

paymentForm.setFontFamily({
  fontFamily: "'Inter', -apple-system, sans-serif",
  stylesheetUrls: [
    'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'
  ],
  fontFaceRules: [
    `@font-face {
      font-family: 'CustomFont';
      src: url('https://cdn.example.com/font.woff2') format('woff2');
      font-weight: 400;
      font-display: swap;
    }`
  ]
});

destroy(): void

Cleans up resources, removes event listeners, and destroys the iframe.

window.addEventListener('beforeunload', () => {
  paymentForm.destroy();
});

Styling

Global Styles (baseStyles)

Apply styles to all elements in the iframe:

const paymentForm = new SecurePaymentForm({
  // ... other options
  baseStyles: {
    body: {
      fontFamily: "'Inter', sans-serif",
      fontSize: '16px',
      color: '#1a202c',
      backgroundColor: '#ffffff',
      padding: '20px'
    },
    input: {
      borderRadius: '8px',
      border: '2px solid #e2e8f0',
      padding: '12px 16px',
      fontSize: '16px',
      transition: 'border-color 0.2s ease',
      backgroundColor: '#ffffff'
    },
    'input:focus': {
      borderColor: '#3b82f6',
      outline: 'none',
      boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)'
    },
    label: {
      fontSize: '14px',
      fontWeight: '500',
      color: '#4a5568',
      marginBottom: '6px'
    }
  }
});

Field-Specific Styles

{
  name: 'cardNumber',
  type: 'card',
  label: 'Card Number',
  style: {
    width: '100%',
    padding: '14px 18px',
    fontSize: '18px',
    fontWeight: '500',
    border: '2px solid #cbd5e0',
    borderRadius: '12px',
    backgroundColor: '#f7fafc',
    color: '#2d3748',
    transition: 'all 0.2s ease'
  }
}

Label Styles

Four built-in label animation styles:

// Static label above field
{ labelStyle: 'normal' }

// Label animates above field on focus/fill
{ labelStyle: 'normal-animated' }

// Label inside border (Material Design style)
{ labelStyle: 'border' }

// Label animates to border on focus/fill (Material Design animated)
{ labelStyle: 'border-animated' }

Font Management

Automatic Font Synchronization

Fonts are automatically detected and synchronized from:

  • Google Fonts
  • Adobe Fonts (Typekit)
  • Font Awesome
  • Custom @font-face rules
  • System fonts
<!-- These fonts are automatically synced to the iframe -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">

<style>
  @font-face {
    font-family: 'CustomFont';
    src: url('/fonts/custom.woff2') format('woff2');
  }
  
  body {
    font-family: 'Inter', sans-serif;
  }
</style>

Dynamic Font Changes

Font changes are automatically detected via MutationObserver when:

  • CSS files are loaded/unloaded
  • Style attributes change
  • CSS classes affecting fonts are modified
// Fonts will auto-sync when this happens:
document.body.style.fontFamily = "'Roboto', sans-serif";

SecurePaymentFields (Multiple Iframes)

Overview

SecurePaymentFields renders each payment field in its own isolated iframe. This approach provides maximum flexibility and control, allowing you to:

  • Position fields anywhere in your layout
  • Apply individual styling to each field
  • Handle events at the field level
  • Create completely custom payment UIs

Quick Start

import { SecurePaymentFields } from '@akinon/akifast-secure-form';

// Initialize the payment form instance
const paymentForm = SecurePaymentFields({
  iframeHostUrl: 'https://secure-form.example.com',
  publicKeyToken: 'pk_test_your_public_key_here',
  locale: 'tr' // optional
});

// Create individual fields
const cardNumber = paymentForm.createElement('cardNumber', {
  placeholder: '1234 5678 9012 3456',
  style: {
    base: {
      fontSize: '16px',
      color: '#2d3748',
      '::placeholder': {
        color: '#a0aec0'
      }
    },
    invalid: {
      color: '#e53e3e',
      borderColor: '#fc8181'
    },
    complete: {
      borderColor: '#48bb78'
    }
  },
  classes: {
    base: 'secure-field',
    focus: 'secure-field--focus',
    invalid: 'secure-field--invalid',
    complete: 'secure-field--complete'
  }
});

const cardExpiry = paymentForm.createElement('cardExpiry', {
  placeholder: 'MM / YY',
  useExpirySelect: false // true for dropdown, false for input
});

const cardCvv = paymentForm.createElement('cardCvv', {
  placeholder: '123'
});

const cardHolder = paymentForm.createElement('cardHolder', {
  placeholder: 'JOHN DOE'
});

// Mount fields to DOM elements
cardNumber.mount('#card-number-element');
cardExpiry.mount('#card-expiry-element');
cardCvv.mount('#card-cvv-element');
cardHolder.mount('#card-holder-element');

// Field-level events
cardNumber.on('change', (state) => {
  console.log('Card number changed:', state);
  if (state.brand) {
    updateCardBrandIcon(state.brand);
  }
});

// Form-level events (fires for any field)
paymentForm.on('change', (fieldType, state) => {
  console.log(`${fieldType} changed:`, state);
});

// Tokenization
document.getElementById('pay-button').addEventListener('click', async () => {
  try {
    const result = await paymentForm.tokenize();
    console.log('Token:', result);
    // Send to backend
  } catch (error) {
    console.error('Tokenization failed:', error);
  }
});

HTML Structure

<form id="payment-form">
  <div class="form-row">
    <label for="card-number-element">Card Number</label>
    <div id="card-number-element" class="field-container"></div>
  </div>
  
  <div class="form-row">
    <label for="card-holder-element">Cardholder Name</label>
    <div id="card-holder-element" class="field-container"></div>
  </div>
  
  <div class="form-row">
    <div class="form-col">
      <label for="card-expiry-element">Expiry</label>
      <div id="card-expiry-element" class="field-container"></div>
    </div>
    
    <div class="form-col">
      <label for="card-cvv-element">CVV</label>
      <div id="card-cvv-element" class="field-container"></div>
    </div>
  </div>
  
  <button type="submit" id="pay-button">Pay Now</button>
</form>

Configuration

PaymentForm Constructor

interface PaymentFieldConfig {
  iframeHostUrl: string;    // Required: HPP server URL
  publicKeyToken: string;   // Required: Your public API key
  locale?: string;          // Optional: Language code
}

Field Types

type FieldType = 'cardNumber' | 'cardExpiry' | 'cardCvv' | 'cardHolder';

Field Options

interface FieldOptions {
  placeholder?: string;
  useExpirySelect?: boolean;  // Only for cardExpiry - use dropdowns instead of input
  
  // Styling via CSS properties (applied to iframe input)
  style?: {
    base?: CSSProperties;     // Default state
    complete?: CSSProperties; // When field is complete
    empty?: CSSProperties;    // When field is empty
    invalid?: CSSProperties;  // When field is invalid
    focus?: CSSProperties;    // When field is focused
  };
  
  // Styling via CSS classes (applied to container element)
  classes?: {
    base?: string;
    complete?: string;
    empty?: string;
    invalid?: string;
    focus?: string;
  };
}

Styling Approaches

Approach 1: CSS Classes (Recommended)

Apply classes to the container element based on field state:

const cardNumber = paymentForm.createElement('cardNumber', {
  classes: {
    base: 'secure-field',
    focus: 'is-focused',
    invalid: 'has-error',
    complete: 'is-complete',
    empty: 'is-empty'
  }
});
.secure-field {
  border: 2px solid #e2e8f0;
  border-radius: 8px;
  padding: 12px;
  transition: all 0.2s ease;
}

.secure-field.is-focused {
  border-color: #3b82f6;
  box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}

.secure-field.has-error {
  border-color: #ef4444;
}

.secure-field.is-complete {
  border-color: #10b981;
}

.secure-field iframe {
  border: none;
  width: 100%;
  height: 40px;
}

Approach 2: Direct Styles

Apply styles directly to the input element inside the iframe:

const cardNumber = paymentForm.createElement('cardNumber', {
  style: {
    base: {
      fontSize: '16px',
      fontFamily: "'Inter', sans-serif",
      color: '#2d3748',
      '::placeholder': {
        color: '#a0aec0'
      }
    },
    focus: {
      color: '#1a202c'
    },
    invalid: {
      color: '#e53e3e'
    },
    complete: {
      color: '#10b981'
    }
  }
});

Approach 3: Hybrid (Both Classes and Styles)

Combine both approaches for maximum flexibility:

const cardNumber = paymentForm.createElement('cardNumber', {
  classes: {
    base: 'field-wrapper',
    focus: 'field-wrapper--focus'
  },
  style: {
    base: {
      fontSize: '16px',
      padding: '12px'
    },
    invalid: {
      color: '#e53e3e'
    }
  }
});

Field Events

Each field supports individual event listeners:

// ready: Field iframe is loaded and ready
cardNumber.on('ready', (state) => {
  console.log('Card number field is ready');
});

// focus: Field received focus
cardNumber.on('focus', (state) => {
  console.log('Card number focused');
});

// blur: Field lost focus
cardNumber.on('blur', (state) => {
  console.log('Card number blurred');
  if (!state.isValid && !state.isEmpty) {
    showFieldError('Please enter a valid card number');
  }
});

// change: Field value changed
cardNumber.on('change', (state) => {
  console.log('Card number state:', state);
  
  // state properties:
  // - isValid: boolean
  // - isEmpty: boolean
  // - isComplete: boolean
  // - isFocused: boolean
  // - error?: string
  // - brand?: string (only for cardNumber)
  // - bin?: string (only for cardNumber, first 8 digits)
});

// validation: Field validation state changed
cardNumber.on('validation', (state) => {
  if (!state.isValid && !state.isEmpty) {
    console.error('Invalid card number');
  }
});

// binEntered: Valid BIN detected (cardNumber only)
cardNumber.on('binEntered', (state) => {
  console.log('BIN detected:', state.bin);
  console.log('Card brand:', state.brand);
  updateCardBrandIcon(state.brand);
});

// binCleared: BIN cleared (cardNumber only)
cardNumber.on('binCleared', (state) => {
  console.log('BIN cleared');
  removeCardBrandIcon();
});

Form-Level Events

Listen to events from all fields:

// Fires when any field changes
paymentForm.on('change', (fieldType, state) => {
  console.log(`${fieldType} changed:`, state);
  updateFormValidation();
});

// Fires when any field receives focus
paymentForm.on('focus', (fieldType, state) => {
  console.log(`${fieldType} focused`);
});

// Fires when any field loses focus
paymentForm.on('blur', (fieldType, state) => {
  console.log(`${fieldType} blurred`);
});

// Fires when any field is ready
paymentForm.on('ready', (fieldType, state) => {
  console.log(`${fieldType} is ready`);
});

// Fires when BIN is entered in card number field
paymentForm.on('binEntered', (fieldType, state) => {
  if (fieldType === 'cardNumber') {
    console.log('Card brand:', state.brand);
  }
});

Field Methods

mount(selector: string | HTMLElement): void

Mounts the field iframe to a DOM element.

cardNumber.mount('#card-number-element');
// or
cardNumber.mount(document.getElementById('card-number-element'));

unmount(): void

Removes the field iframe from the DOM.

cardNumber.unmount();

clear(): void

Clears the field value.

cardNumber.clear();

focus(): void

Programmatically focuses the field.

cardNumber.focus();

blur(): void

Programmatically blurs the field.

cardNumber.blur();

update(options: FieldOptions): void

Updates field options (styles, placeholder, etc.).

cardNumber.update({
  placeholder: 'Enter your card number',
  style: {
    base: {
      fontSize: '18px'
    }
  }
});

getState(): FieldState

Gets the current field state.

const state = cardNumber.getState();
console.log('Is valid:', state.isValid);
console.log('Is complete:', state.isComplete);
console.log('Card brand:', state.brand);

Form Methods

createElement(fieldType: FieldType, options?: FieldOptions): SecureField

Creates a new secure field.

const cardNumber = paymentForm.createElement('cardNumber', {
  placeholder: '1234 5678 9012 3456'
});

getField(fieldType: FieldType): SecureField | undefined

Gets a field by type.

const cardNumber = paymentForm.getField('cardNumber');

removeField(fieldType: FieldType): void

Removes a field and unmounts it.

paymentForm.removeField('cardNumber');

tokenize(): Promise<TokenResult>

Creates a payment token from all field values.

try {
  const result = await paymentForm.tokenize();
  console.log('Token:', result.token);
  // Send token to backend
  await processPayment(result.token);
} catch (error) {
  console.error('Tokenization failed:', error.message);
  showErrorToUser(error.message);
}

paymentProviderTokenization(options): Promise<TokenResult>

Creates a token using a payment provider.

try {
  const result = await paymentForm.paymentProviderTokenization({
    paymentSystemCode: 'TAP',
    tokenizationType: 'TAP_ENCRYPTION'
  });
  console.log('Provider token:', result.token);
} catch (error) {
  console.error('Provider tokenization failed:', error);
}

getStates(): Map<FieldType, FieldState>

Gets all field states.

const states = paymentForm.getStates();
states.forEach((state, fieldType) => {
  console.log(`${fieldType}:`, state.isValid);
});

clear(): void

Clears all fields.

paymentForm.clear();

destroy(): void

Destroys all fields and cleans up resources.

paymentForm.destroy();

Complete Example

import { SecurePaymentFields } from '@akinon/akifast-secure-form';

// Initialize
const paymentForm = SecurePaymentFields({
  iframeHostUrl: 'https://secure-form.example.com',
  publicKeyToken: 'pk_test_abc123',
  locale: 'tr'
});

// Create fields
const fields = {
  cardNumber: paymentForm.createElement('cardNumber', {
    placeholder: 'Card Number',
    classes: {
      base: 'field',
      focus: 'field--focus',
      invalid: 'field--error',
      complete: 'field--success'
    }
  }),
  
  cardExpiry: paymentForm.createElement('cardExpiry', {
    placeholder: 'MM / YY',
    classes: { base: 'field' }
  }),
  
  cardCvv: paymentForm.createElement('cardCvv', {
    placeholder: 'CVV',
    classes: { base: 'field' }
  }),
  
  cardHolder: paymentForm.createElement('cardHolder', {
    placeholder: 'CARDHOLDER NAME',
    classes: { base: 'field' }
  })
};

// Mount fields
fields.cardNumber.mount('#card-number');
fields.cardExpiry.mount('#card-expiry');
fields.cardCvv.mount('#card-cvv');
fields.cardHolder.mount('#card-holder');

// Field events
fields.cardNumber.on('binEntered', (state) => {
  document.getElementById('card-brand').textContent = state.brand;
});

// Form validation
const payButton = document.getElementById('pay-button');
paymentForm.on('change', () => {
  const states = paymentForm.getStates();
  const allValid = Array.from(states.values()).every(s => s.isValid);
  payButton.disabled = !allValid;
});

// Handle payment
document.getElementById('payment-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  
  payButton.disabled = true;
  payButton.textContent = 'Processing...';
  
  try {
    const result = await paymentForm.tokenize();
    
    // Send to backend
    const response = await fetch('/api/process-payment', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token: result.token })
    });
    
    if (response.ok) {
      window.location.href = '/success';
    } else {
      throw new Error('Payment failed');
    }
  } catch (error) {
    alert('Payment failed: ' + error.message);
    payButton.disabled = false;
    payButton.textContent = 'Pay Now';
  }
});

// Cleanup
window.addEventListener('beforeunload', () => {
  paymentForm.destroy();
});

Security

PCI-DSS Compliance

This library helps maintain PCI-DSS compliance by:

  1. Iframe Isolation: Sensitive data is processed in iframes hosted on your secure domain
  2. No Direct Access: Card data never touches your client-side JavaScript
  3. Tokenization: Card data is converted to tokens before leaving the iframe
  4. Origin Validation: Strict cross-origin checks prevent data leakage

Security Best Practices

1. Use HTTPS Everywhere

// ✅ GOOD
iframeHostUrl: 'https://secure-form.example.com'

// ❌ BAD
iframeHostUrl: 'http://secure-form.example.com'

2. Implement CSP Headers

Content-Security-Policy: 
  default-src 'self'; 
  frame-src https://secure-form.example.com;
  script-src 'self';
  style-src 'self' 'unsafe-inline';

3. Validate Tokens Server-Side

// Never trust client-side validation
// Always verify tokens on your backend
app.post('/process-payment', async (req, res) => {
  const { token } = req.body;
  
  // Verify token with payment provider
  const result = await paymentProvider.verifyToken(token);
  
  if (!result.valid) {
    return res.status(400).json({ error: 'Invalid token' });
  }
  
  // Process payment
});

4. Monitor Security Events

paymentForm.onSecurityViolation((data) => {
  // Log to security monitoring system
  logSecurityIncident({
    type: 'form_violation',
    timestamp: new Date(),
    data: data
  });
});

TypeScript Support

Full TypeScript support with comprehensive type definitions.

Importing Types

// SecurePaymentForm types
import {
  SecurePaymentForm,
  IAkifastSecureForm,
  IFieldset,
  IGroup,
  IFlatField,
  IFieldType,
  IReadyEvent,
  IBlurEvent,
  IValidEvent,
  IInvalidEvent,
  IBinEnteredEvent,
  ITokenizeSuccessEvent,
  ITokenizeErrorEvent,
  IFontInfo
} from '@akinon/akifast-secure-form';

// SecurePaymentFields types
import {
  SecurePaymentFields,
  PaymentForm,
  SecureField,
  FieldType,
  FieldOptions,
  FieldState,
  PaymentFieldConfig,
  TokenResult,
  EventType,
  EventHandler
} from '@akinon/akifast-secure-form';

// Utility types (validators, formatters)
import {
  validateCardNumber,
  validateCVV,
  formatCardNumber,
  formatExpiry
} from '@akinon/akifast-secure-form';

Type-Safe Configuration

const config: IAkifastSecureForm = {
  iframeHostUrl: 'https://secure.example.com',
  publicKeyToken: 'pk_test_123',
  iframeContainerId: 'form-container',
  formFields: {
    groups: [
      {
        direction: 'column',
        fields: [
          {
            name: 'cardNumber',
            type: 'card',
            label: 'Card Number',
            placeholder: '1234 5678 9012 3456'
          }
        ]
      }
    ]
  }
};

const form = new SecurePaymentForm(config);

Type-Safe Event Handlers

form.onTokenizeSuccess((response: ITokenizeSuccessEvent) => {
  // response.data is fully typed
  const token: string = response.data.merchant_card_token;
  const masked: string = response.data.masked_card_number;
});

form.onBinEntered((response: IBinEnteredEvent) => {
  const cardType: string | null = response.data.cardType;
  const niceType: string | null = response.data.niceType;
});

Browser Support

Minimum Requirements

  • Chrome: 80+
  • Firefox: 75+
  • Safari: 13+
  • Edge: 80+
  • iOS Safari: 13+
  • Chrome Mobile: 80+

Required Browser Features

  • ✅ ES6+ JavaScript support
  • postMessage API
  • MutationObserver API
  • Promise support
  • ✅ CSS Custom Properties
  • ✅ iframe support

Polyfills

No polyfills required for modern browsers. For older browsers, include:

<script src="https://cdn.polyfill.io/v3/polyfill.min.js?features=Promise,MutationObserver"></script>

Migration Guide

From Custom Solution to SecurePaymentForm

Before (Custom Form)

<form>
  <input type="text" name="cardNumber" />
  <input type="text" name="cvv" />
  <button>Pay</button>
</form>

After (SecurePaymentForm)

import { SecurePaymentForm } from '@akinon/akifast-secure-form';

const form = new SecurePaymentForm({
  iframeHostUrl: 'https://secure.example.com',
  publicKeyToken: 'pk_test_xxx',
  iframeContainerId: 'payment-form',
  formFields: {
    groups: [{
      direction: 'column',
      fields: [
        { name: 'card', type: 'card', label: 'Card Number' },
        { name: 'cvv', type: 'cvv', label: 'CVV' }
      ]
    }]
  }
});

form.init();

From SecurePaymentForm to SecurePaymentFields

If you need more control, migrate to individual field iframes:

Before (Single Iframe)

const form = new SecurePaymentForm({
  iframeHostUrl: 'https://secure.example.com',
  publicKeyToken: 'pk_test_xxx',
  iframeContainerId: 'form',
  formFields: { /* ... */ }
});

After (Multiple Iframes)

const form = SecurePaymentFields({
  iframeHostUrl: 'https://secure.example.com',
  publicKeyToken: 'pk_test_xxx'
});

const cardNumber = form.createElement('cardNumber');
cardNumber.mount('#card-number');

const cardCvv = form.createElement('cardCvv');
cardCvv.mount('#card-cvv');

Troubleshooting

Common Issues

Form Not Loading

Symptoms: Iframe doesn't appear or shows blank content

Solutions:

  1. Verify iframeHostUrl is correct and accessible
  2. Check browser console for CORS errors
  3. Ensure publicKeyToken is valid
  4. Verify container element exists: document.getElementById(iframeContainerId)
// Debug
console.log('Container exists:', !!document.getElementById('form-container'));
console.log('HPP URL accessible:', 'https://secure.example.com');

Events Not Firing

Symptoms: Event handlers are not being called

Solutions:

  1. Call init() before setting up event listeners (SecurePaymentForm)
  2. Call mount() before adding event listeners (SecurePaymentFields)
  3. Verify origin matches iframe URL
  4. Check browser console for security errors
// ✅ CORRECT ORDER
form.init();
form.onReady(() => console.log('Ready'));

// ❌ WRONG ORDER
form.onReady(() => console.log('Ready'));
form.init();

Styling Not Applied

Symptoms: Custom styles don't appear in iframe

Solutions:

  1. Use camelCase for CSS properties: fontSize not font-size
  2. Ensure styles don't violate iframe CSP
  3. Check browser console for style injection errors
  4. Verify selectors in baseStyles target correct elements
// ✅ CORRECT
style: {
  fontSize: '16px',
  borderRadius: '8px'
}

// ❌ INCORRECT
style: {
  'font-size': '16px',  // Use camelCase
  border-radius: '8px'  // Use camelCase
}

Font Synchronization Issues

Symptoms: Iframe uses different fonts than parent page

Solutions:

  1. Ensure fonts are loaded before calling init()
  2. Verify font URLs are accessible from iframe domain
  3. Call updateFontFamily() after dynamic font changes
  4. Use setFontFamily() for manual font control
// Wait for fonts to load
document.fonts.ready.then(() => {
  form.init();
});

// Or manually set fonts
form.setFontFamily({
  fontFamily: "'Inter', sans-serif",
  stylesheetUrls: ['https://fonts.googleapis.com/css2?family=Inter']
});

Tokenization Fails

Symptoms: onTokenizeError fires instead of onTokenizeSuccess

Solutions:

  1. Verify all required fields are filled
  2. Check validation state before calling tokenize()
  3. Ensure network connection to HPP server
  4. Verify publicKeyToken is valid and not expired
// Check form validity first
form.onValid(() => {
  document.getElementById('pay-button').disabled = false;
});

form.onInvalid(() => {
  document.getElementById('pay-button').disabled = true;
});

Debugging Tips

Enable Verbose Logging

// Check iframe creation
form.onReady(() => {
  console.log('✅ Form is ready');
});

// Monitor all events
form.onBlur((data) => {
  console.log('Field blurred:', data);
});

form.onValid((data) => {
  console.log('Form valid:', data);
});

form.onInvalid((data) => {
  console.log('Form invalid:', data);
});

Inspect Field States (SecurePaymentFields)

const states = paymentForm.getStates();
states.forEach((state, fieldType) => {
  console.log(`${fieldType}:`, {
    isValid: state.isValid,
    isEmpty: state.isEmpty,
    isComplete: state.isComplete,
    error: state.error
  });
});

Network Debugging

Check browser Network tab for:

  • Iframe HTML loading correctly
  • postMessage communication working
  • No CORS errors
  • API calls to tokenization endpoint

License

ISC License - Copyright (c) Akinon