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

@paypercut/checkout-js

v1.0.14

Published

Lightweight JavaScript SDK for Paypercut Checkout

Readme

Paypercut Checkout JavaScript SDK

A lightweight, framework-agnostic JavaScript SDK for embedding Paypercut Checkout into your web application. Works seamlessly with vanilla JavaScript, TypeScript, React, Vue, Angular, and other modern frameworks.

npm version License: MIT Bundle Size TypeScript


Table of Contents


Installation

Via NPM

npm install @paypercut/checkout-js

Via Yarn

yarn add @paypercut/checkout-js

Via PNPM

pnpm add @paypercut/checkout-js

Via CDN

<!-- jsDelivr (recommended with SRI) -->
<script src="https://cdn.jsdelivr.net/npm/@paypercut/[email protected]/dist/paypercut-checkout.iife.min.js"
        integrity="sha384-..."
        crossorigin="anonymous"></script>

<!-- or UNPKG -->
<script src="https://unpkg.com/@paypercut/[email protected]/dist/paypercut-checkout.iife.min.js"></script>

Quick Start

This returns a checkout session ID like CHK_12345.

1. Embed the Checkout

Use the checkout ID to initialize the checkout on your frontend:

import { PaypercutCheckout } from '@paypercut/checkout-js';

// Initialize the checkout
const checkout = PaypercutCheckout({
  id: 'CHK_12345',              // Your checkout session ID from step 1
  containerId: '#checkout'       // CSS selector or HTMLElement
});

// Listen for payment events
checkout.on('success', () => {
  console.log('Payment successful!');
});

checkout.on('error', () => {
  console.error('Payment failed');
});

// Optional: Listen for when iframe finishes loading (useful for modal implementations)
checkout.on('loaded', () => {
  console.log('Checkout loaded and ready');
});

// Render the checkout
checkout.render();

That's it! The checkout is now embedded in your page.

2. Display Mode

The SDK supports one display mode:

| Mode | When to Use | Configuration | |------|-------------|---------------| | Embedded | Checkout is part of your page layout | Provide containerId |

API Reference

PaypercutCheckout(options)

Creates a new checkout instance.

Options

| Option | Type | Required | Default | Description | |------------------|------|----------|---------|----------------------------------------------------------------------------------------------------------------------------| | id | string | Yes | — | Checkout session identifier (e.g., CHK_12345) | | containerId | string \| HTMLElement | Yes | — | CSS selector or element where iframe mounts | | locale | string | No | 'auto' | Locale for checkout UI. Options: 'auto', 'en', 'en-GB', 'bg', 'bg-BG' | | lang | string | No | 'auto' | Locale for checkout UI. Options: 'auto', 'en', 'en-GB', 'bg', 'bg-BG' | | ui_mode | 'hosted' \| 'embedded' | No | 'embedded' | UI mode for checkout display | | wallet_options | string[] | No | ['apple_pay', 'google_pay'] | Digital wallet options. Pass [] to disable all wallets | | form_only | boolean | No | false | Show only payment form (no Pay Now button - use external button with submit()) | | validate_form | boolean | No | false | This indicates that Google Pay/Apple Pay flow to proceed you need to confirm form validation. For EMBEDDED checkouts only) |

Examples

Basic initialization:

const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout-container'
});

With all options:

const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout-container',
  locale: 'en',                              // 'auto' | 'en' | 'en-GB' | 'bg' | 'bg-BG'
  lang: 'en',                              // 'auto' | 'en' | 'en-GB' | 'bg' | 'bg-BG'
  ui_mode: 'embedded',                       // 'hosted' | 'embedded'
  wallet_options: ['apple_pay', 'google_pay'], // Can be empty array [] or contain one/both options
  form_only: false,                          // Set true to hide Pay Now button (use external button),
  validate_form: true             // Set true to require form validation before wallet payment
});

Disable wallet payments:

const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout-container',
  wallet_options: []  // No Apple Pay or Google Pay buttons
});

Only Apple Pay:

const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout-container',
  wallet_options: ['apple_pay']  // Only Apple Pay, no Google Pay
});

Form-only mode (external submit button):

const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout-container',
  form_only: true  // No Pay Now button inside checkout
});

// Use your own button to trigger payment
document.getElementById('my-pay-button').addEventListener('click', () => {
  checkout.submit();
});

Instance Methods

render()

Mounts and displays the checkout iframe.

checkout.render();

destroy()

Destroys the instance and cleans up all event listeners. Call this when you're done with the checkout instance.

checkout.destroy();

on(event, handler)

Subscribes to checkout events. Returns an unsubscribe function.

const unsubscribe = checkout.on('success', () => {
  console.log('Payment successful!');
});

// Later, to unsubscribe
unsubscribe();

once(event, handler)

Subscribes to a checkout event that automatically unsubscribes after the first emission. Returns an unsubscribe function.

checkout.once('loaded', () => {
  console.log('Checkout loaded - this will only fire once');
});

off(event, handler)

Unsubscribes from checkout events.

const handler = () => console.log('Payment successful!');
checkout.on('success', handler);
checkout.off('success', handler);

isMounted()

Returns whether the checkout is currently mounted.

if (checkout.isMounted()) {
  console.log('Checkout is visible');
}

Events

Subscribe to events using the on() method. You can use string event names or the SdkEvent enum.

Event Reference

| Event | Enum | Description | Payload | |-------|------|-------------|---------| | loaded | SdkEvent.Loaded | Checkout iframe has finished loading | void | | success | SdkEvent.Success | Payment completed successfully | PaymentSuccessPayload | | error | SdkEvent.Error | Terminal failure (tokenize, confirm, or 3DS) | ApiErrorPayload | | expired | SdkEvent.Expired | Checkout session expired | void | | threeds_started | SdkEvent.ThreeDSStarted | 3DS challenge flow started | object | | threeds_complete | SdkEvent.ThreeDSComplete | 3DS challenge completed | object | | threeds_canceled | SdkEvent.ThreeDSCanceled | 3DS challenge canceled by user | object | | threeds_error | SdkEvent.ThreeDSError | 3DS challenge error | ApiErrorPayload or object |

Usage Examples

Using string event names:

const checkout = PaypercutCheckout({ id: 'CHK_12345', containerId: '#checkout' });

checkout.on('loaded', () => {
  console.log('Checkout loaded');
});

checkout.on('success', (payload) => {
  // PaymentSuccessPayload
  console.log('Payment successful');
  console.log('Card brand:', payload.payment_method.brand);
  console.log('Last 4:', payload.payment_method.last4);
  console.log('Expiry:', payload.payment_method.exp_month + '/' + payload.payment_method.exp_year);
});

checkout.on('error', (err) => {
  console.error('Payment error:', err.code, err.message);
});

checkout.on('expired', () => {
  console.warn('Checkout session expired');
});

Using SdkEvent enum (recommended for TypeScript):

import { PaypercutCheckout, SdkEvent } from '@paypercut/checkout-js';

const checkout = PaypercutCheckout({ id: 'CHK_12345', containerId: '#checkout' });

checkout.on(SdkEvent.Loaded, () => {
  console.log('Checkout loaded');
});

checkout.on(SdkEvent.Success, (payload) => {
  console.log('Payment successful', payload.payment_method);
});

checkout.on(SdkEvent.Error, (err) => {
  console.error('Payment error:', err.code, err.message);
});

checkout.on(SdkEvent.Expired, () => {
  console.warn('Checkout session expired');
});

// 3DS events
checkout.on(SdkEvent.ThreeDSStarted, (ctx) => {
  console.log('3DS challenge started');
});

checkout.on(SdkEvent.ThreeDSComplete, (payload) => {
  console.log('3DS completed');
});

checkout.on(SdkEvent.ThreeDSCanceled, (payload) => {
  console.log('3DS canceled by user');
});

checkout.on(SdkEvent.ThreeDSError, (err) => {
  console.error('3DS error:', err);
});

Success Payload

The success event returns a PaymentSuccessPayload with the payment method details:

type PaymentSuccessPayload = {
  payment_method: {
    brand: string;      // e.g., 'visa', 'mastercard', 'amex'
    last4: string;      // Last 4 digits of card number
    exp_month: number;  // Expiration month (1-12)
    exp_year: number;   // Expiration year (e.g., 2030)
  };
};

Example:

checkout.on('success', (payload) => {
  // payload:
  // {
  //   payment_method: {
  //     brand: 'visa',
  //     last4: '4242',
  //     exp_month: 12,
  //     exp_year: 2030
  //   }
  // }

  console.log(`Paid with ${payload.payment_method.brand} ending in ${payload.payment_method.last4}`);
  // Output: "Paid with visa ending in 4242"
});

Error Handling

checkout.on('error', (err) => {
  switch (err.code) {
    case 'card_validation_error':
      // err.errors[] has field-level issues. Messages are localized.
      break;
    case 'card_declined':
      // Optional err.decline_code and user-friendly err.message
      break;
    case 'threeds_error':
    case 'threeds_authentication_failed':
    case 'threeds_canceled':
      // 3DS issue. Some servers use status_code 424 with a detailed message.
      break;
    default:
      // Other terminal errors (e.g., authentication_failed, session_expired)
      break;
  }
});

Notes

  • error: the SDK forwards a normalized ApiErrorPayload when provided by Hosted Checkout.
  • threeds_error: forwards payload.error when available; otherwise the raw message data.
  • three![img.png](img.png)ds_* non-error events: payload is forwarded as-is from Hosted Checkout (shape may evolve).

Form Validation for Wallet Payments

How It Works

  1. User clicks Apple Pay or Google Pay button in the checkout
  2. SDK emits form_validation event to your code
  3. You validate your form and call either:
    • completeFormValidation(wallet) - allows wallet to proceed
    • failFormValidation(wallet, errors) - blocks wallet, you show your own errors
  4. If validation passes, the wallet payment sheet opens

Event: form_validation

| Property | Type | Description | |----------|------|-------------| | checkoutId | string | The checkout session ID | | wallet | 'apple_pay' \| 'google_pay' | Which wallet button was clicked |

Methods

completeFormValidation(wallet)

Call this when your form is valid. The wallet payment sheet will open.

checkout.completeFormValidation('google_pay');

failFormValidation(wallet, errors?)

Call this when your form has errors. The wallet will not open, and you should display your own error messages.

checkout.failFormValidation('apple_pay', [
  { code: 'invalid_email', message: 'Please enter a valid email address' },
  { code: 'missing_address', message: 'Shipping address is required' }
]);

Basic Example

import { PaypercutCheckout, SdkEvent } from '@paypercut/checkout-js';

const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout',
  wallet_options: ['apple_pay', 'google_pay']
});

// Handle form validation for wallet payments
checkout.on(SdkEvent.FormValidation, ({ wallet, checkoutId }) => {
  // Validate your own form fields
  const email = document.getElementById('email').value;
  const isEmailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

  if (isEmailValid) {
    // Form is valid - allow wallet to proceed
    checkout.completeFormValidation(wallet);
  } else {
    // Form has errors - block wallet and show your errors
    document.getElementById('email-error').textContent = 'Please enter a valid email';
    checkout.failFormValidation(wallet, [
      { code: 'invalid_email', message: 'Please enter a valid email' }
    ]);
  }
});

checkout.render();

React Example

import { useEffect, useRef, useState } from 'react';
import { PaypercutCheckout, CheckoutInstance, SdkEvent } from '@paypercut/checkout-js';

export function CheckoutWithForm({ checkoutId }: { checkoutId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const checkoutRef = useRef<CheckoutInstance | null>(null);
  const [email, setEmail] = useState('');
  const [emailError, setEmailError] = useState('');

  useEffect(() => {
    if (!containerRef.current) return;

    const checkout = PaypercutCheckout({
      id: checkoutId,
      containerId: containerRef.current,
      ui_mode: 'embedded',
      wallet_options: ['apple_pay', 'google_pay'],
      validate_form: true
    });

    // Handle wallet form validation
    checkout.on(SdkEvent.FormValidation, ({ wallet }) => {
      const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

      if (isValid) {
        setEmailError('');
        checkout.completeFormValidation(wallet);
      } else {
        setEmailError('Please enter a valid email address');
        checkout.failFormValidation(wallet, [
          { code: 'invalid_email', message: 'Invalid email' }
        ]);
      }
    });

    checkout.on('success', (payload) => {
      console.log('Payment successful:', payload.payment_method);
    });

    checkout.render();
    checkoutRef.current = checkout;

    return () => checkout.destroy();
  }, [checkoutId, email]);

  return (
    <div>
      {/* Your custom form fields */}
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="[email protected]"
        />
        {emailError && <span style={{ color: 'red' }}>{emailError}</span>}
      </div>

      {/* Checkout iframe */}
      <div ref={containerRef} />
    </div>
  );
}

Timeout Behavior

If you don't respond to the form_validation event within 10 seconds, the SDK will:

  1. Emit an error event with code form_validation_timeout
  2. Block the wallet payment from starting

This ensures the checkout doesn't hang indefinitely if the handler is not implemented.

When to Use

Use form validation when you have:

  • Custom email/phone fields outside the checkout iframe
  • Shipping address forms that must be filled before payment
  • Terms & conditions checkboxes that must be accepted
  • Any other merchant-side validation requirements

If you don't need to validate anything, you can simply auto-approve:

checkout.on('form_validation', ({ wallet }) => {
  // No validation needed - always allow wallet to proceed
  checkout.completeFormValidation(wallet);
});

Types

ApiErrorPayload

The SDK forwards the error payload provided by Hosted Checkout. Common shapes include:

// Card validation error with per-field details (messages are localized)
type CardField = 'card_number' | 'expiration_date' | 'cvc' | 'cardholder_name';

type CardValidationErrorCode =
  | 'invalid_number' | 'incomplete_number'
  | 'invalid_expiry' | 'incomplete_expiry'
  | 'invalid_cvc' | 'incomplete_cvc'
  | 'required';

type VendorValidationType = 'invalid' | 'incomplete' | 'required';

type CardValidationErrorItem = {
  field: CardField;
  code: CardValidationErrorCode;
  message: string; // localized per current checkout locale
  vendor_type: VendorValidationType;
};

type ApiErrorPayload =
  | {
      checkoutId: string;
      code: 'card_validation_error';
      message: string; // e.g., 'Card details are incomplete or invalid'
      status_code: number; // typically 400
      timestamp: string; // ISO8601
      errors: CardValidationErrorItem[];
    }
  | {
      code:
        | 'threeds_error'
        | 'threeds_authentication_failed'
        | 'threeds_canceled'
        | 'card_declined'
        | 'authentication_failed'
        | 'session_expired'
        | string; // other server codes may appear, e.g. 'permission_denied'
      type?: 'card_error' | string;
      message?: string;
      status_code?: number; // some sources use `status`
      status?: number;      // compatibility with some payloads
      decline_code?: string; // Optional decline reason
      trace_id?: string;
      timestamp?: string; // ISO8601
      [key: string]: any; // passthrough
    };

Error payload examples

{
  "checkoutId": "CHK_12345",
  "code": "card_validation_error",
  "message": "Card details are incomplete or invalid",
  "status_code": 400,
  "timestamp": "2025-01-01T12:00:00.000Z",
  "errors": [
    { "field": "card_number", "code": "invalid_number", "message": "Card number is invalid", "vendor_type": "invalid" },
    { "field": "expiration_date", "code": "incomplete_expiry", "message": "Incomplete date", "vendor_type": "incomplete" }
  ]
}
{
  "checkoutId": "01KA8EG3KA2A61YXX4XVD4FYPT",
  "code": "permission_denied",
  "message": "forbidden",
  "status_code": 403,
  "trace_id": "00761104aff14f33adb84d7437d7e320",
  "timestamp": "2025-12-02T09:31:22.779Z"
}
{
  "checkoutId": "01KA8EG3KA2A61YXX4XVD4FYPT",
  "code": "threeds_error",
  "type": "card_error",
  "message": "Card authentication failed.",
  "status": 400,
  "timestamp": "2025-12-02T09:31:44.250Z"
}

Note: Some payloads provide status_code while others provide status. Treat them interchangeably.

Error codes explained

  • authentication_failed — General authentication failure during confirm or 3DS. Usually recoverable by retrying or using another card.
  • card_declined — Issuer declined the card. Optional decline_code may be present (e.g., insufficient_funds).
  • wallet_authentication_canceled — User canceled Apple/Google Pay authentication sheet.
  • wallet_not_available — Apple/Google Pay not available on the device/browser.
  • card_validation_error — Client-side validation of card fields failed. errors[] contains localized, per-field issues.
  • threeds_error — 3DS flow encountered an error (e.g., ACS unavailable). Often accompanied by a server message.
  • threeds_authentication_failed — 3DS authentication failed (e.g., challenged and consumer failed/denied).
  • threeds_canceled — Customer canceled the 3DS challenge.
  • session_expired — Checkout session is no longer valid; create a new session.
  • permission_denied — Server responded with permission error (e.g., session not allowed). Not enumerated above; SDK forwards unknown codes unchanged.

Notes:

  • For 3DS issues, you may receive threeds_error with status_code 424 and a server-provided message.
  • For declines, expect code: 'card_declined', optional decline_code, and a user-friendly message.
  • The validation error message fields are already localized based on the checkout’s locale.

3DS payloads (representative samples)

The SDK forwards whatever Hosted Checkout sends for 3DS events. Shapes may evolve.

// threeds_started
{
  type: 'THREEDS_START_FLOW',
  checkoutId: 'CHK_12345',
  // additional context as provided by Hosted Checkout (e.g., method, acsUrl)
}

// threeds_complete
{
  type: 'THREEDS_COMPLETE',
  checkoutId: 'CHK_12345',
  // authentication result context (e.g., status: 'Y' | 'A' | 'C' | 'D')
}

// threeds_canceled
{
  type: 'THREEDS_CANCELED',
  checkoutId: 'CHK_12345',
}

// threeds_error (non-terminal)
// Note: terminal failures will also be emitted on `error` with ApiErrorPayload
{
  type: 'THREEDS_ERROR',
  checkoutId: 'CHK_12345',
  error?: ApiErrorPayload,
}

Tip: Prefer subscribing with the SdkEvent enum for stronger typing.

Usage Examples

Vanilla JavaScript (CDN)

<!DOCTYPE html>
<html>
<head>
  <title>Paypercut Checkout</title>
</head>
<body>
  <div id="checkout"></div>

  <script src="https://cdn.jsdelivr.net/npm/@paypercut/[email protected]/dist/paypercut-checkout.iife.min.js"></script>
  <script>
    const checkout = PaypercutCheckout({
      id: 'CHK_12345',
      containerId: '#checkout',
      locale: 'en',
      ui_mode: 'embedded',
      wallet_options: ['apple_pay', 'google_pay']
    });

    checkout.on('success', function(payload) {
      // payload.payment_method: { brand, last4, exp_month, exp_year }
      alert('Payment successful with ' + payload.payment_method.brand + ' ending in ' + payload.payment_method.last4);
    });

    checkout.on('error', function(error) {
      alert('Payment failed: ' + error.message);
    });

    checkout.render();
  </script>
</body>
</html>

TypeScript / ESM

import { PaypercutCheckout } from '@paypercut/checkout-js';

const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout',
  locale: 'auto',
  ui_mode: 'embedded',
  wallet_options: ['apple_pay', 'google_pay'],
  form_only: false
});

checkout.on('success', (payload) => {
  // payload.payment_method: { brand, last4, exp_month, exp_year }
  console.log('Payment successful');
  console.log(`Paid with ${payload.payment_method.brand} **** ${payload.payment_method.last4}`);
  // Redirect to success page
  window.location.href = '/success';
});

checkout.on('error', (error) => {
  console.error('Payment error:', error);
  // Show error message to user
  alert(`Payment failed: ${error.message}`);
});

checkout.render();

React

import { useEffect, useRef, useState } from 'react';
import { PaypercutCheckout, CheckoutInstance } from '@paypercut/checkout-js';

interface PaymentMethod {
  brand: string;
  last4: string;
  exp_month: number;
  exp_year: number;
}

export function CheckoutComponent({ checkoutId }: { checkoutId: string }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const checkoutRef = useRef<CheckoutInstance | null>(null);
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [paymentMethod, setPaymentMethod] = useState<PaymentMethod | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    const checkout = PaypercutCheckout({
      id: checkoutId,
      containerId: containerRef.current,
      locale: 'auto',
      ui_mode: 'embedded',
      wallet_options: ['apple_pay', 'google_pay'],
      form_only: false
    });

    checkout.on('loaded', () => {
      setStatus('idle');
      console.log('Checkout loaded');
    });

    checkout.on('success', (payload) => {
      setStatus('success');
      setPaymentMethod(payload.payment_method);
      console.log('Payment successful:', payload.payment_method);
    });

    checkout.on('error', (error) => {
      setStatus('error');
      console.error('Payment error:', error);
    });

    checkout.render();
    checkoutRef.current = checkout;

    return () => {
      checkout.destroy();
    };
  }, [checkoutId]);

  return (
    <div>
      <div ref={containerRef} style={{ width: '100%', height: '600px' }} />
      {status === 'loading' && <p>Processing payment...</p>}
      {status === 'success' && paymentMethod && (
        <p>✅ Payment successful with {paymentMethod.brand} ending in {paymentMethod.last4}!</p>
      )}
      {status === 'error' && <p>❌ Payment failed. Please try again.</p>}
    </div>
  );
}

Modal-like Implementation

If you want to create a modal-like experience, you can use the loaded event to show/hide a loading overlay:

import { useEffect, useRef, useState } from 'react';
import { PaypercutCheckout, CheckoutInstance } from '@paypercut/checkout-js';

export function ModalCheckout({ checkoutId, onClose }: { checkoutId: string; onClose: () => void }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    if (!containerRef.current) return;

    const checkout = PaypercutCheckout({
      id: checkoutId,
      containerId: containerRef.current
    });

    checkout.on('loaded', () => {
      setIsLoading(false);
    });

    checkout.on('success', () => {
      console.log('Payment successful');
      onClose();
    });

    checkout.on('error', (error) => {
      console.error('Payment error:', error);
    });

    checkout.render();

    return () => {
      checkout.destroy();
    };
  }, [checkoutId, onClose]);

  return (
    <div style={{
      position: 'fixed',
      top: 0,
      left: 0,
      right: 0,
      bottom: 0,
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      zIndex: 9999
    }}>
      <div style={{
        position: 'relative',
        width: '90%',
        maxWidth: '500px',
        height: '90%',
        maxHeight: '700px',
        backgroundColor: '#fff',
        borderRadius: '12px',
        overflow: 'hidden'
      }}>
        {isLoading && (
          <div style={{
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            backgroundColor: '#fff',
            zIndex: 1
          }}>
            <p>Loading checkout...</p>
          </div>
        )}
        <div ref={containerRef} style={{ width: '100%', height: '100%' }} />
        <button
          onClick={onClose}
          style={{
            position: 'absolute',
            top: '10px',
            right: '10px',
            zIndex: 2
          }}
        >
          ✕
        </button>
      </div>
    </div>
  );
}

Container Styling

The SDK automatically resizes the iframe to match the checkout content height. To ensure proper display, style your container with max-width and max-height constraints rather than fixed dimensions.

Recommended Container Styles

.checkout-container {
  /* Use max-width/max-height to constrain the container */
  max-width: 450px;
  max-height: 600px;
  overflow: hidden;
  overflow-y: auto;
  scroll-behavior: smooth;
}

/* The mount point should be full width with no fixed height */
#checkout-mount {
  width: 100%;
  /* Height is set dynamically by SDK via resize messages */
  overflow: hidden;
  position: relative;
}

Key Points

  1. Avoid fixed height or min-height on the container - the SDK will resize the iframe to fit the checkout content
  2. Use max-height if you want to limit the container size and enable scrolling for longer content
  3. Use max-width to constrain the container width (recommended: 375px–450px for optimal checkout display)
  4. Set overflow-y: auto on the container if using max-height to enable scrolling

Example HTML

<div class="checkout-container">
  <div id="checkout-mount"></div>
</div>
const checkout = PaypercutCheckout({
  id: 'CHK_12345',
  containerId: '#checkout-mount'
});
checkout.render();

Security

Origin Validation

The SDK automatically validates all postMessage communications against the configured checkoutHost origin. This prevents unauthorized messages from being processed.

Content Security Policy

For enhanced security, configure your Content Security Policy headers:

Content-Security-Policy:
  frame-src https://buy.paypercut.io OR something else;
  script-src 'self' https://cdn.jsdelivr.net https://unpkg.com;
  connect-src https://buy.paypercut.io  OR something else;

HTTPS Only

Always use HTTPS in production. The SDK is designed for secure communication over HTTPS.

Troubleshooting

Checkout not displaying

Problem: The iframe doesn't appear after calling render().

Solutions:

  • Ensure the container element exists in the DOM before calling render()
  • Check that the id is a valid checkout session ID
  • Verify there are no CSP errors in the browser console
  • Enable debug mode: debug: true to see detailed logs

Events not firing

Problem: Event handlers are not being called.

Solutions:

  • Ensure you subscribe to events before calling render()
  • Check that the checkout session is active and not expired
  • Enable debug mode to see message logs

TypeScript errors

Problem: TypeScript shows type errors when using the SDK.

Solutions:

  • Ensure you're importing types: import { PaypercutCheckout, CheckoutInstance } from '@paypercut/checkout-js'
  • Update to the latest version of the SDK
  • Check that your tsconfig.json includes the SDK's type definitions

Performance Optimization

Preconnect to Checkout Host

Add DNS prefetch and preconnect hints to your HTML for faster loading:

<link rel="preconnect" href="https://buy.paypercut.io ">
<link rel="dns-prefetch" href="https://buy.paypercut.io ">

Lazy Loading

Load the SDK only when needed to reduce initial bundle size:

async function loadCheckout() {
  const { PaypercutCheckout } = await import('@paypercut/checkout-js');
  return PaypercutCheckout;
}

// Load when user clicks pay button
payButton.addEventListener('click', async () => {
  const PaypercutCheckout = await loadCheckout();
  const checkout = PaypercutCheckout({ id: 'CHK_12345' });
  checkout.render();
});

Best Practices

1. Always Verify Payments on Your Backend

Never rely solely on frontend events for payment confirmation. Always verify payments using webhooks on your backend:

// ✅ Good: Use frontend events for UI updates only
checkout.on('success', () => {
  // Show success message
  showSuccessMessage();
  // Redirect to order confirmation page
  window.location.href = '/orders/confirmation';
});

// ❌ Bad: Don't grant access based on frontend events alone
checkout.on('success', () => {
  // This can be manipulated by users!
  grantPremiumAccess(userId); // Don't do this!
});

2. Handle All Event Types

Always handle both success and error events:

checkout.on('success', () => {
  // Handle success
  showSuccessMessage();
});

checkout.on('error', () => {
  // Show user-friendly error message
  alert('Payment failed. Please try again.');
});

3. Clean Up on Component Unmount

Always call destroy() when your component unmounts to prevent memory leaks:

// React example
useEffect(() => {
  const checkout = PaypercutCheckout({ id: checkoutId });
  checkout.render();

  return () => {
    checkout.destroy(); // Clean up!
  };
}, []);

FAQ For Internal Validation

Q: How do I handle successful payments? Listen to the success event and redirect users or update your UI accordingly. Always verify the payment on your backend using webhooks.

Q: Can I use this with server-side rendering (SSR)? Yes, but ensure the SDK is only initialized on the client side. For Next.js, use dynamic imports with ssr: false.

Q: What happens if the user closes the browser during payment?

Q: Can I have multiple checkouts on one page? Maybe Yes, but only one should be active at a time to avoid user confusion. If no, we destroy the previous one and create a new one.

Q: How do I handle errors? Listen to the error event and display user-friendly error messages. Log errors for debugging.