Hosted Fields SDK

A secure, embeddable payment form SDK that integrates card payment fields (number, expiry, CVV, and cardholder name) into your web application via iframes. This SDK provides a PCI-compliant way to handle sensitive payment data.

Installation

Include the SDK in your project directly via a script tag. The SDK script should be loaded from your environment's Base URL.

<script src="https://api.example.com/sdk/index.umd.js"></script>

Alternatively, you can load it dynamically in JavaScript:

const script = document.createElement('script');
script.src = 'https://api.example.com/sdk/index.umd.js';
document.head.appendChild(script);

script.onload = async () => {
  // SDK is ready on window.HostedFieldsSDK
};

Configuration

Basic Setup

createHostedFields is an async function that returns a Promise resolving when all iframes are mounted.

// Ensure the SDK script is loaded before calling this
const fields = await window.HostedFieldsSDK.createHostedFields({
  baseUrl: 'https://api.example.com',
  apiKey: '<your_api_key>',
  merchantAccountId: '<your_merchant_id>',
  paymentRequestId: '<your_payment_request_id>',
  cardNumber: {
    mountPoint: '#card-number',
    placeholder: '0000 0000 0000 0000',
  },
  expiryDate: {
    mountPoint: '#card-expiry',
    placeholder: 'MM/YY',
  },
  cvv: {
    mountPoint: '#card-csc',
    placeholder: 'CVV',
  },
  cardholderName: {
    mountPoint: '#cardholder-name',
    placeholder: 'Cardholder Name',
  },
});

Configuration Object

interface FieldsConfig {
  baseUrl?: string; // Base URL for iframe endpoints (optional)
  apiKey: string; // Your API key for authentication (required)
  merchantAccountId: string; // Your merchant account ID (required)
  paymentRequestId: string; // The payment request session identifier (required)
  timeout?: number; // Network timeout in ms for requests (optional)
  initTimeout?: number; // SDK initialization timeout in ms (optional)
  loadingBehavior?: 'disabled' | 'readonly'; // How fields behave during loading/submission (optional)
  style?: Partial<CommonStyling>; // Global styling applied to all fields (optional)
  cardNumber: Partial<CommonField>; // Card number field configuration (required)
  expiryDate: Partial<CommonField>; // Expiry date field configuration (required)
  cvv: Partial<CommonField>; // CVV field configuration (required)
  cardholderName?: Partial<CommonField>; // Cardholder name field configuration (optional)
}

interface CommonField {
  mountPoint?: string | HTMLElement; // CSS selector or DOM element reference (required internally)
  placeholder?: string; // Placeholder text (optional)
  loadingBehavior?: 'disabled' | 'readonly'; // Override global loading behavior (optional)
  style?: Partial<CommonStyling>; // Field-specific styling overriding global (optional)
}

interface CommonStyling {
  height?: string; // Field height (e.g., '40px', '100%')
  width?: string; // Field width (e.g., '100%')
  fontSize?: string; // Font size (e.g., '16px')
  fontFamily?: string; // Font family (e.g., 'Arial, sans-serif')
  color?: string; // Text color (e.g., '#333', 'rgb(51, 51, 51)')
  background?: string; // Background color (e.g., '#fff', 'transparent')
  paddingTop?: string; // Top padding (e.g., '10px')
  paddingBottom?: string; // Bottom padding (e.g., '10px')
  paddingLeft?: string; // Left padding (e.g., '12px')
  paddingRight?: string; // Right padding (e.g., '12px')
  lineHeight?: string; // Line height (e.g., '40px', '1.5')
  letterSpacing?: string; // Letter spacing (e.g., '1px', '0.5em')
  placeholderColor?: string; // Placeholder text color (e.g., '#999')
}

Mount Point Options

The mountPoint parameter accepts either:

  • CSS Selector (string): '#card-number', '.payment-field'
  • HTMLElement: Direct reference to a DOM element

Dynamic Configuration Updates

Update field configurations at runtime using updateConfig():

await fields.updateConfig({
  style: {
    fontSize: '18px',
    color: '#333',
  },
  cardNumber: {
    placeholder: 'Enter Card Number',
  },
});
🚧

Important Restrictions:

  • Cannot change baseUrl, apiKey, merchantAccountId, or mountPoint
  • Only styling properties and placeholder text can be updated

Styling

Supported CSS Value Formats

Length Units

  • Pixels: '10px', '40px'
  • Rem: '1rem', '2.5rem'
  • Em: '1em', '1.5em'
  • Percentage: '100%', '50%'
  • Viewport units: '100vw', '50vh'
  • Auto/inherit: 'auto', 'inherit'

Colors

  • Hex: '#ffffff','#fff'
  • RGB: 'rgb(255, 255, 255)'
  • RGBA: 'rgba(255, 255, 255, 0.5)'
  • Named colors: 'white', 'black', 'red', etc.

Font Families

  • Generic: 'serif', 'sans-serif', 'monospace'
  • Safe fonts: 'Arial', 'Helvetica', 'Times New Roman', 'Georgia'

Global vs Field-Specific Styles

Global styling applies to all fields:

const fields = await window.HostedFieldsSDK.createHostedFields({
  style: { height: '40px', fontSize: '16px' },
  cardNumber: { mountPoint: '#card-number' },
});

Field-specific styling overrides global:

const fields = await window.HostedFieldsSDK.createHostedFields({
  style: { color: '#333' },
  cardNumber: {
    mountPoint: '#card-number',
    style: { color: '#007bff' }, // Override global color
  },
});

Field Interactions

Available Methods

Each field (cardNumber, expiryDate, cvv, cardholderName) provides these methods:

  • focus(): Set focus to the field.
  • blur(): Remove focus from the field.
  • validate(): Validate the individual field value asynchronously.
  • onFocus(callback): Listen to focus events. Returns an unsubscribe function.
  • onBlur(callback): Listen to blur events. Returns an unsubscribe function.
  • onInput(callback): Listen to input/change events. Returns an unsubscribe function.
  • onDisabledChange(callback): Listen to disabled state changes. Callback receives (disabled: boolean). Returns an unsubscribe function.
  • onReadonlyChange(callback): Listen to readonly state changes. Callback receives (readonly: boolean). Returns an unsubscribe function.

Global State Management

The fields instance provides methods to control the loading and disabled state of all fields or specific fields at once:

  • setLoading(isLoading: boolean): Toggles the loadingBehavior (disabling or making readonly) for all fields based on your global/field configurations. Useful during tokenization or network requests.
  • setDisabled(disabled: boolean, fieldKey?: string): Forcefully enables or disables all fields. Optionally provide a fieldKey (e.g., 'cardNumber') to target a specific field.
  • setReadonly(readonly: boolean, fieldKey?: string): Forcefully sets all fields to readonly or editable. Optionally provide a fieldKey to target a specific field.

Global Validation

validateAll()Validate all required fields at once. This method is crucial for checking the form state before attempting to submit payment data.

const result = await fields.validateAll();
// Returns (valid):
// { valid: true }
// OR (invalid):
// {
//   valid: false,
//   errors: {
//     cardNumber: {
//       type: 'invalid_card_number',
//       message: 'Invalid card number format',
//       field: 'cardNumber',
//       retryable: true
//     }
//   }
// }

Submitting Payment Data

tokenize() Submit payment data and create a token.

🚧

Important: The tokenize()method will automatically abort and will not send any network requests if the fields are obviously invalid. Best Practice: You should always call validateAll()before calling tokenize(). This allows you to retrieve a complete map of validation errors for all fields to update your UI accordingly.

// 1. Validate all fields first
const validation = await fields.validateAll();

if (!validation.valid) {
  console.error('Form contains invalid fields:', validation.errors);
  return;
}

// 2. If validation passes, attempt to tokenize
const result = await fields.tokenize();

if (result.token) {
  // Send the token to your backend securely
  await processPaymentOnBackend(result.token);
} else {
  console.error('Tokenization failed:', result.error?.message);
}

Cleanup with destroy()

Call destroy() when removing the payment form from the page, especially in single-page applications.

// Clean up fields, remove iframes from DOM, and automatically remove all event listeners
fields.destroy();

Error Handling

Error Codes

Validation Errors:

  • field_invalid - Field validation failed
  • field_required - Required field is empty
  • invalid_card_number - Invalid card number format
  • invalid_expiry_date - Invalid or expired expiry date
  • invalid_cvv - Invalid CVV/CVC code
  • invalid_cardholder_name - Invalid cardholder name
  • validation_failed - General validation failure

Network & Token Errors:

  • network_timeout - Network request timed out
  • invalid_config - Invalid SDK configuration provided
  • forbidden_origin - Domain is not authorized to load the hosted fields
  • token_creation_failed - Token creation failed on the server
  • unknown_error - Unknown error occurred

Security & Compliance

This SDK is designed from the ground up to minimize your PCI DSS compliance scope by ensuring sensitive card data never touches your application environment.

Data Protection & Architecture

  • Isolated Iframes: All sensitive card data is exclusively processed within secure iframes.
  • No Data Leakage: Data never leaves the secure iframe boundary. Input masking prevents data leakage via browser history.
  • PCI DSS SAQ A: Designed specifically to support SAQ A for merchants by isolating PAN and CVV.
  • Secure Communication: Uses secure cross-origin communication with strict origin validation. No external API calls are allowed from the parent domain.

Integration Best Practices (Do's & Don'ts)

To maintain the integrity of the secure fields, adhere to the following rules:

  • Never expose the fields instance to the window object or global scope.
  • Never log, inspect, or attempt to extract raw field data in the browser console.
  • Never pass user-controlled data directly into the SDK styles or configuration.
  • Always keep the fields instance private within your module scope.
  • Always use HTTPS in production environments.
  • Always validate generated tokens on your backend before processing payments.

Strict Input & Styling Validation

The SDK actively validates all configurations to prevent CSS injection attacks and ensure visual integrity.

  • Rejected CSS Patterns: Dangerous CSS patterns are strictly rejected. This includes URL functions (url()), expressions (calc()), !important declarations, CSS variables (var()), and animations/transforms.
  • Safe Formatting: Numeric values are auto-normalized with safe units (px, rem, %). Colors and font families are validated against a strict whitelist of safe formats and generic/system fonts.
  • DOM Validation: Mount point selectors are validated before any iframe creation occurs.

Complete Example

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Payment Form</title>
  </head>
  <body>
    <div class="payment-form">
      <h1>Payment Details</h1>

      <div class="form-group">
        <label>Card Number</label>
        <div class="field-wrapper" id="card-number"></div>
      </div>

      <div class="form-group">
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
          <div>
            <label>Expiry Date</label>
            <div class="field-wrapper" id="card-expiry"></div>
          </div>
          <div>
            <label>CVV</label>
            <div class="field-wrapper" id="card-csc"></div>
          </div>
        </div>
      </div>

      <button onclick="window.handlePaymentSubmit()">Pay Now</button>
    </div>

    <script src="https://api.example.com/sdk/index.umd.js"></script>

    <script type="module">
      let fields;

      async function initializePaymentForm() {
        try {
          fields = await window.HostedFieldsSDK.createHostedFields({
            baseUrl: 'https://api.example.com',
            apiKey: '<your_api_key>',
            merchantAccountId: '<your_merchant_id>',
            cardNumber: { mountPoint: '#card-number' },
            expiryDate: { mountPoint: '#card-expiry' },
            cvv: { mountPoint: '#card-csc' },
            style: {
              height: '40px',
              fontSize: '16px',
            },
          });
        } catch (error) {
          console.error('Initialization failed:', error.message);
        }
      }

      if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializePaymentForm);
      } else {
        initializePaymentForm();
      }

      window.handlePaymentSubmit = async () => {
        if (!fields) return;

        const validation = await fields.validateAll();
        if (!validation.valid) {
          alert('Please correct the highlighted fields.');
          return;
        }

        try {
          const result = await fields.tokenize();
          if (result.token) {
            alert('Success! Token: ' + result.token);
          } else {
            alert('Payment failed: ' + (result.error?.message || 'Unknown error'));
          }
        } catch (error) {
          alert('Submission failed. Please try again.');
        }
      };
    </script>
  </body>
</html>